ブログの説明

学校に通わないで学んだことを記しています。間違っているところが何かありましたらご指摘下さると幸いです。コメントに対する返信が遅れる可能性があります。その場合は申し訳ありません。

このブログではサイドバーに広告を表示しています。このブログ内の投稿記事を検索するには右上の拡大鏡のアイコンを、アーカイブやラベル付けから投稿記事を閲覧するには左上の三重線のアイコンをクリックして下さい。

数式の表示にはMathJaxを利用させていただいています。数式の表示のためにJavaScriptが有効である必要があります。そうでない場合、訳の分からないLatexのコードが表示されます。幾何学図形やチャートの表示にはHTML5 CanvasやGoogle Chartを使用しています。その表示のためにもJavaScriptが有効である必要があります。

DebianユーザーがNLTKで自然言語処理の初歩の初歩を試してみた

この投稿は自然言語処理の中で一番初歩的なテキスト分析(字句解析)についての事始め。

ダウンロードとインストール

Python 3のdebパッケージとPython3用のNLTK(Natural Language Toolkit)のdebパッケージを公式のリポジトリからダウンロードしてインストールした。

$ su
# apt install python3 python3-nltk

python3-nltkというdebパッケージは、Debianのバージョンのbusterにもbullseyeにもbookwormにも存在する。

NLTKは、そのホームページの説明等によれば、自然言語処理に必要な道具類をまとめたプラットフォームなのだそうで、しかもオープンソースの企画らしい。

ダウンロードとインストールがうまくいったのでPython3を早速起動し、NLTKのホームページに掲載されている簡単なコードを試してみた。

まずはPython3をターミナル・エミュレーター上で起動。

$ python3
Python 3.7.3 (省略)

次にNLTKをインポート。

>>> import nltk

適当に選んだ文字列をsentenceに代入した。sentenceは任意の識別子。

>>> sentence = """A similar argument applies
...  to any other a priori judgement."""
>>> print(sentence)
A similar argument applies
 to any other a priori judgement.

Python 3ではヒアドキュメントを3つの二重引用符号(""")で囲む。こうした場合、/nなどによって改行を明示せずとも複数行に渡って文字列を入力できる。ヒアドキュメントとはHTMLのpre要素のようなもの。

文字列として選んだのはバートランド・ラッセル著の『哲学の問題』からの一節。

そしてNLTKのword_tokenize()に引数としてその文字列を渡してトークンを抽出。

>>> tokens = nltk.word_tokenize(sentence)
Traceback (most recent call last):
(中略)
LookupError: 
**********************************************************************
  Resource punkt not found.
  Please use the NLTK Downloader to obtain the resource:

  >>> import nltk
  >>> nltk.download('punkt')
  
  Attempted to load tokenizers/punkt/PY3/english.pickle
(以下省略)

ここでいきなりエラーが生じた。リソースのpunktが見つからないと言われた。エラーのメッセージにあるとおり、NLTKのdownload()によってpunktをダウンロードしたみた。

>>> nltk.download('punkt')
[nltk_data] Downloading package punkt to(中略)
[nltk_data]   Unzipping tokenizers/punkt.zip.
True

こうしたリソースはNLTK Dataと呼ばれていてnltk.download()によってダウンロードすることができる。

もう一度やり直し。キーボードの上矢印キーを押すと前に入力したコードを順番に出力してくれるので、その機能を利用すると便利。

>>> tokens = nltk.word_tokenize(sentence)
>>> print(tokens)
['A', 'similar', 'argument', 'applies', 'to', 'any', 'other', 'a', 'priori', 'judgement', '.']

うまくいった。個々の単語や符号に分割してくれているのが見て取れる。この処理はトークン化と呼ばれているらしい。英語ではtokenization

英文の場合はそのほとんどが分ち書きされるので、このメソッドの内部コードは簡単そうだ。日本語や中国語やタイ語だと複雑になりそう。

トークン化で生成したtokensをNLTKのpos_tag()に渡して単語に品詞タグを付ける処理を行うコードが次。ただしこの処理にはaveraged_perceptron_taggerというリソースが必要になるので先ほどと同じようにダウンロードした。

>>> nltk.download('averaged_perceptron_tagger')
[nltk_data] Downloading package averaged_perceptron_tagger to(中略)
[nltk_data]   Unzipping taggers/averaged_perceptron_tagger.zip.
True
>>> tagged = nltk.pos_tag(tokens)
>>> print(tagged)
[('A', 'DT'), ('similar', 'JJ'), ('argument', 'NN'), ('applies', 'NNS'), ('to', 'TO'), ('any', 'DT'), ('other', 'JJ'), ('a', 'DT'), ('priori', 'JJ'), ('judgement', 'NN'), ('.', '.')]

この処理は品詞タグ付けと呼ばれているらしい。英語ではpart-of-speech tagging、略してPOS tagging

DTは決定詞、JJは形容詞、NNは名詞の単数形、NNSは名詞の複数形、TOはtoを直接指す。

タグがどの品詞を意味しているかについて知りたい場合は「part-of-speech tagging tagset NLTK」などの文字列でインターネット上を検索してみれば見つかる。英文だけど。

よく見ると、appliesを名詞の複数形(NNS)と判断している。これは誤りだと思う。appliesは動詞applyの三人称単数現在(VBZ)が正しいはず。

次に行う処理はチャンキングと呼ばれていること。英語ではchunking。「分割すること」というような意味。この処理にはmaxent_ne_chunker とwordsというリソースが必要になるので予めダウンロードする。

>>> nltk.download('maxent_ne_chunker')
[nltk_data] Downloading package maxent_ne_chunker to(中略)
[nltk_data]   Unzipping chunkers/maxent_ne_chunker.zip.
True
>>> nltk.download('words')
[nltk_data] Downloading package words to
(中略)
[nltk_data]   Unzipping corpora/words.zip.
True

品詞タグ付けで生成したtaggedを与えてNLTKのchunk.ne_chunk()を実行する。

>>> entities = nltk.chunk.ne_chunk(tagged)
>>> print(entities)
(S
  A/DT
  similar/JJ
  argument/NN
  applies/NNS
  to/TO
  any/DT
  other/JJ
  a/DT
  priori/JJ
  judgement/NN
  ./.)

チャンキングの処理結果を見ると、ディレクトリ状にまとめられ、その先頭には'S'という文字があるのが見て取れる。前者をセグメント化と呼び、後者をラベル付けと呼ぶらしい。

名詞句のルールを追加

名詞句(NP)を分類するために更に文法ルールを適用する。NPは名詞句を表すタグ名。波括弧内では正規表現を用いて名詞句の構造を定義している。

>>> grammar = "NP: {<DT>?<JJ>*<NN>}

波括弧内の正規表現が意味するのは、DT(決定詞)かJJ(形容詞)で始まり、NN(名詞)で終わるもの。?がorを表し、*が任意の文字列を表している。

これをRegexpParser()に引数として渡してチャンキングのルールを追加する。

>>> cp = nltk.RegexpParser(grammar)

追加したこのルールに基づいて構文解析を行う。

>>> result = cp.parse(entities)
>>> print(result)
(S
  (NP A/DT similar/JJ argument/NN)
  applies/NNS
  to/TO
  any/DT
  other/JJ
  (NP a/DT priori/JJ judgement/NN)
  ./.)

決定詞と形容詞と名詞から成る2つの名詞句が分類されているのが見て取れる。

これを構文木として視覚化するにはdraw()を実行する。

>>> result.draw()

別ウィンドウが開いてその中に構文木が表示された。

正規表現は本当ならば"NP: {<DT>?<JJ>?<JJR>?<JJS>*<NN>?<NNS>?<NNP>?<NNPS>}"としたかったが、appliesを名詞の複数形と認識してしまっているので誤った結果になってしまう。この修正ができるかどうかについてはまたの機会に。

コメント

このブログの人気の投稿

Visual Studio 2019にはC++のためのフォームデザイナーがない件

10の補数と9の補数と2の補数と1の補数

LATEXで数式:指数と順列などで使う添数・添字