NLPのためのPython: StanfordCoreNLPライブラリ入門

Python for NLPの連載も今回で9回目となりました。

前回は、トークン化から品詞タグ付け、テキスト分類からセンチメント分析まで、さまざまな自然言語処理タスクの実行にPythonのPatternライブラリがどのように使われるかを見てきました。

その前に、同様の自然言語処理タスクを実行するためのTextBlobライブラリについて調べました。

この記事では、自然言語処理のための非常に便利なライブラリであるStanfordCoreNLPライブラリについて説明します。

StanfordCoreNLPの様々な機能を、例を使って見ていきます。

それでは、さっそく始めましょう。

環境の構築

StanfordCoreNLPのインストールは、他のPythonライブラリのように簡単ではありません。

実のところ、StanfordCoreNLPは実際にはJavaで書かれたライブラリです。

そのため、あなたのシステムにJavaがインストールされていることを確認してください。

最新版のJavaは自由にダウンロードできます。

Javaをインストールしたら、StanfordCoreNLPのライブラリのJARファイルをダウンロードする必要があります。

JARファイルには、さまざまなNLPタスクを実行するために使用されるモデルが含まれています。

英語モデルのJARファイルをダウンロードするには、StanfordCoreNLPの公式サイトにあるフォルダをダウンロードし、解凍してください。

次に行うことは、PythonラッパーからStanfordCoreNLPライブラリに送られるリクエストを処理するサーバを実行することです。

JARファイルフォルダを解凍したパスに移動してください。

そのフォルダの中に移動して、コマンドプロンプトで以下のコマンドを実行します。

$ java -mx6g -cp "*" edu.stanford.nlp.pipeline.StanfordCoreNLPServer -timeout 10000


上記コマンドはStanfordCoreNLPサーバーを起動します。

パラメータ -mx6g は、サーバーが使用するメモリが6ギガバイトを超えてはいけないことを指定します。

6ギガバイトのヒープを持つためには、64ビットシステムを使用することが重要です。

32ビットシステムを使用している場合は、サーバー専用のメモリサイズを小さくする必要があるかもしれません。

上記のコマンドを実行すると、次のような出力が表示されます。

[main] INFO CoreNLP - --- StanfordCoreNLPServer#main() called ---
[main] INFO CoreNLP - setting default constituency parser
[main] INFO CoreNLP - warning: cannot find edu/stanford/nlp/models/srparser/englishSR.ser.gz
[main] INFO CoreNLP - using: edu/stanford/nlp/models/lexparser/englishPCFG.ser.gz instead
[main] INFO CoreNLP - to use shift reduce parser download English models jar from:
[main] INFO CoreNLP - http://stanfordnlp.github.io/CoreNLP/download.html
[main] INFO CoreNLP -     Threads: 8
[main] INFO CoreNLP - Starting server...
[main] INFO CoreNLP - StanfordCoreNLPServer listening at /0:0:0:0:0:0:0:0:9000


サーバーは、ポート9000で実行されています。

最後のステップは、StanfordCoreNLPライブラリのPythonラッパーをインストールすることです。

今回使用するラッパーは pycorenlp です。

以下のスクリプトで、ラッパーライブラリをダウンロードします。

$ pip install pycorenlp


これでStanfordCoreNLPのサーバーに接続し、必要なNLPタスクを実行する準備が整いました。

サーバーに接続するためには、先ほど初期化したStanfordCoreNLPサーバーのアドレスを pycorenlpモジュールの StanfordCoreNLP クラスに渡す必要があります。

そして、返されたオブジェクトはNLPタスクを実行するために使うことができます。

次のスクリプトを見てください。

from pycorenlp import StanfordCoreNLP


nlp_wrapper = StanfordCoreNLP('http://localhost:9000')


NLPタスクの実行

このセクションでは、StanfordCoreNLPライブラリーを使用して、一般的な自然言語処理タスクを実行する方法を簡単に説明します。

Lemmatization, POS Tagging and Named Entity Recognition (英語)

レンマタイゼーション、品詞タグ付け、そして固有表現認識は、最も基本的なNLPタスクです。

StanfordCoreNLPライブラリは、これらのタスクを構造的に実行するために使用できるパイプライン機能をサポートしています。

以下のスクリプトでは、まず文書をセンテンスに分割し、さらにセンテンスを単語やトークンに分割するアノテータを作成します。

そして、その単語に品詞認識タグと名前付き実体認識タグを付与する。

doc = "Ronaldo has moved from Real Madrid to Juventus. While messi still plays for Barcelona"
annot_doc = nlp_wrapper.annotate(doc,
    properties={
        'annotators': 'ner, pos',
        'outputFormat': 'json',
        'timeout': 1000,
    })


上のスクリプトでは、2つの文からなる文書があります。

先ほど初期化したStanfordCoreNLPのラッパーオブジェクトの annotate メソッドを使っています。

このメソッドは3つのパラメータを持ちます。

annotatorパラメータには、テキストに対して行いたいアノテーションの種類を指定します。

ここではannotatorパラメータの値として‘ner, pos’` を渡しています。

これは、ドキュメントに品詞タグと名前付き実体をアノテーションすることを指定します。

outputFormat変数は、アノテーションされたテキストの形式を定義します。

指定できる値は、JSON オブジェクトにはjson、XML フォーマットにはxml、プレーンテキストにはtext、シリアライズされたデータにはserialize` です。

最後のパラメータは、タイムアウトをミリ秒で指定します。

これは、ラッパーがサーバーからの応答をタイムアウトするまで待つ時間を定義します。

出力には、以下のようなJSONオブジェクトが表示されるはずです。



`{'sentences': [{'index': 0,
 'entitymentions': [{'docTokenBegin': 0,
 'docTokenEnd': 1,
 'tokenBegin': 0,
 'tokenEnd': 1,
 'text': 'Ronaldo',
 'characterOffsetBegin': 0,
 'characterOffsetEnd': 7,
 'ner': 'PERSON'},
 {'docTokenBegin': 4,
 'docTokenEnd': 6,
 'tokenBegin': 4,
 'tokenEnd': 6,
 'text': 'Real Madrid',
 'characterOffsetBegin': 23,
 'characterOffsetEnd': 34,
 'ner': 'ORGANIZATION'},
 {'docTokenBegin': 7,
 'docTokenEnd': 8,
 'tokenBegin': 7,
 'tokenEnd': 8,
 'text': 'Juventus',
 'characterOffsetBegin': 38,
 'characterOffsetEnd': 46,
 'ner': 'ORGANIZATION'}],
 'tokens': [{'index': 1,
 'word': 'Ronaldo',
 'originalText': 'Ronaldo',
 'lemma': 'Ronaldo',
 'characterOffsetBegin': 0,
 'characterOffsetEnd': 7,
 'pos': 'NNP',
 'ner': 'PERSON',
 'before': '',
 'after': ' '},
 {'index': 2,
 'word': 'has',
 'originalText': 'has',
 'lemma': 'have',
 'characterOffsetBegin': 8,
 'characterOffsetEnd': 11,
 'pos': 'VBZ',
 'ner': 'O',
 'before': ' ',
 'after': ' '},
 {'index': 3,
 'word': 'moved',
 'originalText': 'moved',
 'lemma': 'move',
 'characterOffsetBegin': 12,
 'characterOffsetEnd': 17,
 'pos': 'VBN',
 'ner': 'O',
 'before': ' ',
 'after': ' '},
 {'index': 4,
 'word': 'from',
 'originalText': 'from',
 'lemma': 'from',
 'characterOffsetBegin': 18,
 'characterOffsetEnd': 22,
 'pos': 'IN',
 'ner': 'O',
 'before': ' ',
 'after': ' '},
 {'index': 5,
 'word': 'Real',
 'originalText': 'Real',
 'lemma': 'real',
 'characterOffsetBegin': 23,
 'characterOffsetEnd': 27,
 'pos': 'JJ',
 'ner': 'ORGANIZATION',
 'before': ' ',
 'after': ' '},
 {'index': 6,
 'word': 'Madrid',
 'originalText': 'Madrid',
 'lemma': 'Madrid',
 'characterOffsetBegin': 28,
 'characterOffsetEnd': 34,
 'pos': 'NNP',
 'ner': 'ORGANIZATION',
 'before': ' ',
 'after': ' '},
 {'index': 7,
 'word': 'to',
 'originalText': 'to',
 'lemma': 'to',
 'characterOffsetBegin': 35,
 'characterOffsetEnd': 37,
 'pos': 'TO',
 'ner': 'O',
 'before': ' ',
 'after': ' '},
 {'index': 8,
 'word': 'Juventus',
 'originalText': 'Juventus',
 'lemma': 'Juventus',
 'characterOffsetBegin': 38,
 'characterOffsetEnd': 46,
 'pos': 'NNP',
 'ner': 'ORGANIZATION',
 'before': ' ',
 'after': ''},
 {'index': 9,
 'word': '.',
 'originalText': '.',
 'lemma': '.',
 'characterOffsetBegin': 46,
 'characterOffsetEnd': 47,
 'pos': '.',
 'ner': 'O',
 'before': '',
 'after': ' '}]},
 {'index': 1,
 'entitymentions': [{'docTokenBegin': 14,
 'docTokenEnd': 15,
 'tokenBegin': 5,
 'tokenEnd': 6,
 'text': 'Barcelona',
 'characterOffsetBegin': 76,
 'characterOffsetEnd': 85,
 'ner': 'ORGANIZATION'}],
 'tokens': [{'index': 1,
 'word': 'While',
 'originalText': 'While',
 'lemma': 'while',
 'characterOffsetBegin': 48,
 'characterOffsetEnd': 53,
 'pos': 'IN',
 'ner': 'O',
 'before': ' ',
 'after': ' '},
 {'index': 2,
 'word': 'messi',
 'originalText': 'messi',
 'lemma': 'messus',
 'characterOffsetBegin': 54,
 'characterOffsetEnd': 59,
 'pos': 'NNS',
 'ner': 'O',
 'before': ' ',
 'after': ' '},
 {'index': 3,
 'word': 'still',
 'originalText': 'still',
 'lemma': 'still',
 'characterOffsetBegin': 60,
 'characterOffsetEnd': 65,
 'pos': 'RB',
 'ner': 'O',
 'before': ' ',
 'after': ' '},
 {'index': 4,
 'word': 'plays',
 'originalText': 'plays',
 'lemma': 'play',
 'characterOffsetBegin': 66,
 'characterOffsetEnd': 71,
 'pos': 'VBZ',
 'ner': 'O',
 'before': ' ',
 'after': ' '},
 {'index': 5,
 'word': 'for',
 'originalText': 'for',
 'lemma': 'for',
 'characterOffsetBegin': 72,
 'characterOffsetEnd': 75,
 'pos': 'IN',
 'ner': 'O',
 'before': ' ',
 'after': ' '},
 {'index': 6,
 'word': 'Barcelona',
 'originalText': 'Barcelona',
 'lemma': 'Barcelona',
 'characterOffsetBegin': 76,
 'characterOffsetEnd': 85,
 'pos': 'NNP',
 'ner': 'ORGANIZATION',
 'before': ' ',
 'after': ''}]}]}`


上記のスクリプトをよく見ると、各単語の品詞タグ、名前付きエンティティ、レムマター化されたバージョンを見つけることができます。

レマット化

では、アノテーションされた結果を調べてみましょう。

まず、データセットに含まれる2つの文の単語をlemmatizationしたものを表示します。

for sentence in annot_doc["sentences"]:
    for word in sentence["tokens"]:
        print(word["word"] + " => " + word["lemma"])


上のスクリプトでは、外側のループがドキュメントの各文を繰り返し、内側のループが文中の各単語を繰り返し処理する。

内側のループの中では、単語とそれに対応するレンマター化された形がコンソールに表示されます。

出力は次のようなものである。

Ronaldo=>Ronaldo
has=>have
moved=>move
from=>from
Real=>real
Madrid=>Madrid
to=>to
Juventus=>Juventus
.=>.
While=>while
messi=>messus
still=>still
plays=>play
for=>for
Barcelona=>Barcelona


例えば、movedという単語は move にレンマタイズされ、同様に plays という単語は play にレンマタイズされていることが分かる。

品詞タグ付け

同じように、各単語の品詞タグを求めることができる。

次のスクリプトを見てください。

for sentence in annot_doc["sentences"]:
    for word in sentence["tokens"]:
        print (word["word"] + "=>" + word["pos"])


出力には、次のような結果が表示されるはずです。

Ronaldo=>NNP
has=>VBZ
moved=>VBN
from=>IN
Real=>JJ
Madrid=>NNP
to=>TO
Juventus=>NNP
.=>.
While=>IN
messi=>NNS
still=>RB
plays=>VBZ
for=>IN
Barcelona=>NNP


品詞タグはPenn Treebankのタグセットを使用しており、こちらで確認することができます。

名前付き固有表現認識

文書中の名前付きエンティティを見つけるには、以下のスクリプトを使用します。

for sentence in annot_doc["sentences"]:
    for word in sentence["tokens"]:
        print (word["word"] + "=>" + word["ner"])


出力は以下のようになる。

Ronaldo=>PERSON
has=>O
moved=>O
from=>O
Real=>ORGANIZATION
Madrid=>ORGANIZATION
to=>O
Juventus=>ORGANIZATION
.=>O
While=>O
messi=>O
still=>O
plays=>O
for=>O
Barcelona=>ORGANIZATION


Ronaldo」は「PERSON」として認識され、「Barcelona」は「Organization」として認識されていることがわかりますが、この場合は正しいです。

センチメント分析

文章のセンチメントを調べるには、annotatorsプロパティの値として sentiment を渡すだけでよい。

次のスクリプトを見てください。

doc = "I like this chocolate. This chocolate is not good. The chocolate is delicious. Its a very tasty chocolate. This is so bad"
annot_doc = nlp_wrapper.annotate(doc,
    properties={
       'annotators': 'sentiment',
       'outputFormat': 'json',
       'timeout': 1000,
    })


sentiment を見つけるには、各文章を繰り返し処理し、 sentimentValue プロパティを使用します。

sentimentValueは 1 から 4 までの値を返し、1 は非常に否定的な感情、4 は非常に肯定的な感情に対応します。

sentiment プロパティを使用すると、センチメントを肯定的、否定的、中立的といった言葉で表すことができます。

以下のスクリプトは、上記で定義した文書内の各文章のセンチメントを検索します。

for sentence in annot_doc["sentences"]:
    print ( " ".join([word["word"] for word in sentence["tokens"]]) + " => " 
        + str(sentence["sentimentValue"]) + " = "+ sentence["sentiment"])


出力

I like this chocolate . => 2 = Neutral
This chocolate is not good . => 1 = Negative
The chocolate is delicious . => 3 = Positive
Its a very tasty chocolate . => 3 = Positive
This is so bad => 1 = Negative


結論

StanfordCoreNLP`は自然言語処理のための非常に便利なライブラリの一つです。

この記事では、StanfordCoreNLPを実行するための環境を構築する方法について学びました。

そして、レンマタイゼーション、品詞タグ付け、名前付きエンティティ認識などの一般的な自然言語処理タスクのためのStanfordCoreNLPライブラリの使用方法を検討し、最後にStanfordCoreNLPを使ったセンチメント分析について述べました。

タイトルとURLをコピーしました