英語で自然言語処理するなら　〜Standford CoreNLP〜

[SmartNews](https://www.smartnews.com/ja/)の中路です。こちら[http://qiita.com/advent-calendar/2014/machinelearning](Machine Learning Advent Calendar)の10日目のエントリーです。

# はじめに
Machine Learningの非常に重要な応用として、自然言語処理があります。弊社でも、いたるところで自然言語処理の技術を使っています。さて"自然言語処理を勉強したい"というのであれば、一からアルゴリズムを勉強して、学習データを準備して、という方法はとてもよいのですが、もし"アプリケーションを作りたいので気軽に試したい"というならば一から全部用意するのはめんどくさい。

ということで、簡単に試せるライブラリは本当にありがたいわけです。

さて、弊社のサーバーサイドのシステムしかり、世の多くのシステムは、Javaで作られているわけですが、自然言語処理界隈ではC++やpythonのツールがたくさんあって、「よし仕方ないからJavaから外部プログラムとして呼び出しちゃうぞ」と考えた瞬間に、

- ユー外部プログラムを呼び出すオーバーヘッドについて十分検証したのかい?
- せっかくJVMという素敵なものを使っているのに、ユーわざわざ環境に依存する外部ライブラリをサーバーにインストールするのかい、Chefの準備はできたかい?
- ユーまさかJNI使う気かい?

などと脳内から大量のミサイルが飛んでくるので、ぜひともJavaの中で収まっていてほしい、それがJavaでサービス開発をしている人間にとっての切なる願いであります。

以上からこのエントリーのテーマを"Javaで自然言語処理"にしぼります。

さらに日本語に関しては、すでに日本語で書かれたエントリーが多くあるので、[形態素解析ライブラリのkuromoji](http://www.atilika.org/)にポインタを貼っておくにとどめ、英語の自然言語処理を行うライブラリ[Standford CoreNLP](http://nlp.stanford.edu/software/corenlp.shtml)にフォーカスして書きます。

# このエントリーのゴール
以下は[yahoo.comの記事](http://news.yahoo.com/white-florida-deputy-shoots-black-man-witnesses-had-004052289.html)から取り出した一文に、人名や地名などのタグを付けたものです。赤色が人名で青色が地名です。

![スクリーンショット 2014-12-09 20.10.35.png](https://qiita-image-store.s3.amazonaws.com/0/44359/3bd11205-f503-3ea7-dac1-9028993d802c.png "スクリーンショット 2014-12-09 20.10.35.png")


これは「固有表現抽出」としてよく知られた自然言語処理のタスクですが、このタスクをStandford CoreNLPを用いて行うことを、このエントリーのゴールにします。

# とにかく使ってみる

## Step1 ライブラリのダウンロード
素晴らしいことに、[Maven Central](http://search.maven.org/)にありますから、mavenを使っていれば、pom fileに以下を記述するだけでオッケー。versionは適宜変えてください。

```
<dependencies>
<dependency>
    <groupId>edu.stanford.nlp</groupId>
    <artifactId>stanford-corenlp</artifactId>
    <version>3.5.0</version>
</dependency>
<dependency>
    <groupId>edu.stanford.nlp</groupId>
    <artifactId>stanford-corenlp</artifactId>
    <version>3.5.0</version>
    <classifier>models</classifier>
</dependency>
</dependencies>
```
modelsは200MBくらいあって、ちょっとあなたなにやってるんですかくらいサイズがでかいんですが、学習済みの分類機が入っているから仕方ない。

## Step2 使う
### 品詞タグづけ
まずは、文章があったときに、"名詞"や"動詞"といったタグ付けをする品詞タグ付けを試してみます。

#### コピペ用コード
```

import java.util.List;
import java.util.Properties;

import edu.stanford.nlp.ling.CoreAnnotations.NamedEntityTagAnnotation;
import edu.stanford.nlp.ling.CoreAnnotations.TextAnnotation;
import edu.stanford.nlp.ling.CoreAnnotations.TokensAnnotation;
import edu.stanford.nlp.ling.CoreLabel;
import edu.stanford.nlp.pipeline.Annotation;
import edu.stanford.nlp.pipeline.StanfordCoreNLP;

/**
 * pos tagging sammple
 *
 */
public class PosTaggingSample {
	public static void main(String[] args) {
		String text = "I ask everyone to not rush to judgment and allow the investigation to be completed,"
				+ " Orange County Sheriff Jerry Demings said at a news conference in Orlando.";
				
		Properties properties = new Properties();
		properties.setProperty("annotators", "tokenize, ssplit, pos");
		StanfordCoreNLP coreNLP = new StanfordCoreNLP(properties);
		Annotation annotation = new Annotation(text);
		coreNLP.annotate(annotation);
		List<CoreLabel> labels = annotation.get(TokensAnnotation.class);
		for (CoreLabel label : labels) {
			System.out.println(label.get(TextAnnotation.class) + "\t"
					+ label.get(NamedEntityTagAnnotation.class));
		}
	}
}
```

####結果
```
I	PRP
ask	VBP
everyone	NN
to	TO
not	RB
rush	VB
to	TO
judgment	NN
and	CC
allow	VB
the	DT
investigation	NN
to	TO
be	VB
completed	VBN
,	,
Orange	NNP
County	NNP
Sheriff	NNP
Jerry	NNP
Demings	NNP
said	VBD
at	IN
a	DT
news	NN
conference	NN
in	IN
Orlando	NNP
.	.
```

PRP? VBP? は？って感じですが、それぞれが品詞名になっています。品詞名一覧は[WikipediaのBrown Corpusのページ](http://en.wikipedia.org/wiki/Brown_Corpus)にあるpart-of-Speech-taggingの部分に一覧が載っています。

一部抜粋すると、以下のようなものです。

|POSタグ名|説明|ざっくりいうと|
|-----|-------|-----|
|NN|singular or mass noun|単数名詞|
|VB|verb, base form|動詞原型|

### 固有表現抽出
このエントリーのゴールである、人名や地名を抜き出す、固有表現抽出をやってみます。

#### コピペ用コード

```
import java.util.List;
import java.util.Properties;

import edu.stanford.nlp.ling.CoreAnnotations.NamedEntityTagAnnotation;
import edu.stanford.nlp.ling.CoreAnnotations.TextAnnotation;
import edu.stanford.nlp.ling.CoreAnnotations.TokensAnnotation;
import edu.stanford.nlp.ling.CoreLabel;
import edu.stanford.nlp.pipeline.Annotation;
import edu.stanford.nlp.pipeline.StanfordCoreNLP;

/**
 *  ner sammple
 *
 */
public class NERSample {
	public static void main(String[] args) {
		String text = "I ask everyone to not rush to judgment and allow the investigation to be completed,"
				+ " Orange County Sheriff Jerry Demings said at a news conference in Orlando.";

		Properties properties = new Properties();
		properties.setProperty("annotators", "tokenize, ssplit, pos, lemma, ner");
		StanfordCoreNLP coreNLP = new StanfordCoreNLP(properties);
		Annotation annotation = new Annotation(text);
		coreNLP.annotate(annotation);
		List<CoreLabel> labels = annotation.get(TokensAnnotation.class);
		for (CoreLabel label : labels) {
			System.out.println(label.get(TextAnnotation.class) + "\t"
					+ label.get(NamedEntityTagAnnotation.class));
		}
	}
}
```

#### 結果
```
I	O
ask	O
everyone	O
to	O
not	O
rush	O
to	O
judgment	O
and	O
allow	O
the	O
investigation	O
to	O
be	O
completed	O
,	O
Orange	ORGANIZATION
County	ORGANIZATION
Sheriff	O
Jerry	PERSON
Demings	PERSON
said	O
at	O
a	O
news	O
conference	O
in	O
Orlando	LOCATION
.	O
```

できました！"Jerry Demings"に"PERSON"というタグが、Orlandoに"LOCATION"というタグがついていますね。

# CoreNLPの実装をみてみる
Standford CoreNLPの実装概要を簡単にまとめます。

一言でまとめるならば、

```
Annotation annotation = new Annotation(text);
```
で初期化した"文章をMap化したもの"に対して、


```
coreNLP.annotate(annotation);
```

によって順々にObjectをputしていく、という仕組みになっています。

Objectをputしていく役割を担う、いわゆる"annotators"は

```
Properties properties = new Properties();
properties.setProperty("annotators", "tokenize, ssplit, pos");
StanfordCoreNLP coreNLP = new StanfordCoreNLP(properties);
```
の部分で、指定されています。(この例だと、tokenize, ssplit, pos)。使用可能なannotatorについては[Standford CoreNLP本家のページ](http://nlp.stanford.edu/software/corenlp.shtml)にて。

### Part of Speech Taggingの場合の例
"Map化された文章"にObjectがputされていく例を書きます。Mapといっても、普通にannotationをMapにしてしまうと、Map\<String, Object>という、世界中を敵に回すようなオブジェクトを使うことになってしまうのを、CoreMapという特殊な"Map的なもの"を使うことでうまく回避しています。

#### step1

```
Annotation annotation = new Annotation(text);
```

として初期化した時点では、annotationは
(key, value) = (TextAnnotation.class, "I love you")
を持ったMapです。

#### step2

tokenizeがおわった時点では、annotationにはあらたに、
(key, value) = (TokensAnnotation.class, [Map, Map, Map, Map])
のような、Mapの配列をvalueとするエントリーが追加されます。配列中のそれぞれのMapには、TextAnnotationがkeyとして入っています。

#### step3
posがおわった時点では、TokensAnnotationのMap配列のそれぞれに(key, value) = (PartOfSpeechAnnotation.class, 品詞名)
のようなエントリーが追加されます。

![corenlp.001.png](https://qiita-image-store.s3.amazonaws.com/0/44359/accb5005-6567-8a64-5025-fe55930bbdf5.png "corenlp.001.png")

#### 値を取り出す
annotateが終わったannotationインスタンスは多重のMapなので、以下のように順々にgetしていけば、望むPart of speech tagが取得できるというわけです。

```
List<CoreLabel> labels = annotation.get(TokensAnnotation.class);
		for (CoreLabel label : labels) {
			System.out.println(label.get(TextAnnotation.class) + "\t"
					+ label.get(NamedEntityTagAnnotation.class));
		}
```

# まとめ
一から実装して、学習データを準備して、学習させて、とやると非常に手間のかかる自然言語処理の種々のタスクを行ってくれるJavaライブラリ Standford CoreNLPについて書きました。このライブラリを使うだけで、かなりの部分の欲求を満たす事が出来ますから、Java x 英語 x 自然言語処理をやりたければ、まずはこのライブラリを試してみて、足りなければ実装する、というのがよいかと思います。