この記事はつぁいにゃお氏主催のアドベントカレンダー「The 言語 Advent Calendar 2018」の3日目にあたります。
Who am I
こんにちは、あかれぎです。多分この記事を読んでいる方はご存知でしょうが、改めてございさつします。私、あかれぎは……何を語ればいいんだろう?
自由に述べるなら、現実での素性がわからない人とよく言われます。というのも、年齢やら性別やらの情報を一切明かすことがないからです。
私は一体何歳で性別は何でどんな人でしょうか ─ これだ! と確信したら是非直接私まで。
ネット上では Minecraft のとあるマルチサーバーで管理者を務めていたり、Twitter で人工言語「ヒュムノス」についてつぶやいています。私の SNS アカウントについては(PC上なら)左上のアイコンから参照できます。暇な方は覗いてみてください。
また、昔からちまちまと PHP と Ruby でプログラミングもしてきました。プロではなく素人レベル、それも独学なので特別高度なことができるわけではありませんが……。
そんな感じ。素性不明で情報技術にちょっと明るい人です。
ちなみに、題目では「自然言語処理」と銘打っていますが、肝心のそれはよくわかりません。増して言語学も言うまでもなく。つまり、自然言語処理はこの記事ではじめて挑みます。何故そんな無謀なことを? ─ 面白そうだからです。
レッツゴー。
TOO LONG; DON’T READ.
これから語ることのまとめ。
1. NLTK をぶちこむ
NLTK(Natural Language Tool Kit)は自然言語処理のための Python で実装されたライブラリである。デフォルトで英語をグリグリいじれるし、どうにか設定をこなせば他の言語も同じようにいじれる。
NLTK を導入するには pip install nltk
を実行する。もっと簡単にやりたいなら Anaconda をそのまま持ってくればいい。Anaconda は Python の科学技術計算用のパッケージ群であり無料で提供されている。Anaconda には Python 3.7 と NLTK、Jupyter Notebook、matplotlib、numpy などが標準で付属する。
2. 英語の形態素解析
今回は NLTK 公式のサンプルコードをそのまま利用する。日本語を処理するにあたって NLTK に慣れるための前座。word_tokenize()
だか pos_tag()
を使えばわりかし楽にできる。ついでにいくつか便利なメソッドを紹介する。
3. 日本語の形態素解析
MeCab と呼ばれる日本語の形態素解析エンジンを利用する。UNIX/Linux 上では導入は難しくないようだが、Windows の場合は真っ当にやろうとすると相当に難儀である。結論から言うと、MeCab の導入は pip install python-mecab-windows
で行う。バイナリ(MeCab)の導入も忘れない。
題材として「一房のぶどう」という短編をそのまま扱う。前半はいかにして Python で扱えるようにするか、後半は MeCab による形態素解析を行う。
この記事で触れること
題目の通り、Natural Language Tool Kit、略して NLTK をつかって簡単な自然言語処理を行う。http://www.nltk.org/ にて NLTK 自体の情報を参照できる。
英語を処理する限りでは、こいつ単体で文を単語ごとに区切ったり形態素解析ができるとのこと。構文木も NLTK 任せで作れるっちゃあ作れるけど、意味のある奴は人の手を介さないと作れないっぽい。ただ、既存のコーパス(ツリーバンクとかいう「統語構造の注釈が付与されているもの」)さえあれば構文木に起こして matplotlib でグラフにできる。
で、具体的な内容は?
みなさんが喋っているであろう、私も無論の日本語、それの形態素解析。もっというと、適当な文章を単語別に分けた上で、それぞれの単語に「こいつは名詞、こいつは動詞…」と名札をつけていく。他にもいくつかしょーもないことをやる。
それじゃあ、やってみよう。やりたいことはさして難しくはなさそうだ、多分。
NLTK 101
前提事項をちょろちょろっと述べる。
まず、OS は Windows を使っている。こいつが後々尾を引く。
私の環境下では Python の科学技術計算用パッケージ「Anaconda」を導入している。Python 3.7.0 もご一緒。Python は Python 2 ~ 3 の間で結構いろいろ変わっているようなので2の方をつかっている人は注意すること。
Anaconda は公式サイトからインストーラをもってきて導入するのが手っ取り早い。こいつを導入すると NLTK のパッケージがついてくる。特に細かいことを行うつもりがなければ追加の手順は必要ない。
次に、今回は Jupyter Notebook を使う。ざっくり言うと逐次実行のできる便利なやつ。こいつの説明は割愛するが、なんとこいつも Anaconda にくっついてくる。長いものには巻かれろ、というがまさに「長い蛇には巻かれろ」ということ。大丈夫、噛みつかれはしないはずだ……。
Let’s Get down the Business ─ 英語の処理
冒頭ちょっと後の話で日本語処理をすると述べたが、いきなり日本語処理はちょっと難儀なものがあるので先に英語処理をやる。慣れよう。
NLTK のサンプルコードは以下のとおり。
>>> import nltk
>>> sentence = """At eight o'clock on Thursday morning
... Arthur didn't feel very good."""
>>> tokens = nltk.word_tokenize(sentence)
>>> tokens
['At', 'eight', "o'clock", 'on', 'Thursday', 'morning',
'Arthur', 'did', "n't", 'feel', 'very', 'good', '.']
>>> tagged = nltk.pos_tag(tokens)
>>> tagged[0:6]
[('At', 'IN'), ('eight', 'CD'), ("o'clock", 'JJ'), ('on', 'IN'),
('Thursday', 'NNP'), ('morning', 'NN')]
わかる? 私にはようわからん。順にやろう。
文章作って言葉ごとにバラす
上記のコードの sentence
~ tokens
あたりの処理。見ての通りだから、そんなに難しくない。Jupyter の方の結果を見ると以下のようになる。
もちろん、sentence
にぶち込む文章を変えても同じように処理される。
文はアイザック・ニュートンの「自然哲学の数学的諸原理」より。見ての通り、文が長かろう短かろうが一瞬で処理が終わる。とはいえ、本一冊の文章すべて読み込ませると時間がかかるかもしれない。
英語版形態素解析
上記のコードの tagged
の部分。[0:6]
で配列が端折ってある。Jupyter にやらせよう。
念の為: 上のやつのニュートンの奴の続き。
サンプルコードの続きにはこのまま構文木的なやつが作れるよ! みたいなものがあるが、どうにかグラフにしてもサンプルコードの流れではうまくやれない。熱意のある人は是非構文木を作ってみるべきだがここでは端折る。
Interlude: 便利なメソッド
本題の前に、いくつか解析にあたって便利なメソッドを示しておく。形態素解析に直接関係はしないが自然言語処理という題に照らして役に立つものだ。
事前に以下のようにして、NLTK のサンプルコーパスを読み込んでおく。今回使うのはサンプルの一つ「The Book of Genesis 」である。
import nltk
# こいつを実行するとダウンローダがでてくるので book ってやつを
# インストールすること
nltk.download()
from nltk.book import *
# 実行するとやかましい出力が出てくる。
# The Book of Genesis は text3 という変数に突っ込まれる。
コメントの通り、以下 text3
という変数を扱うので注意。上記コードを実行した時点で定義されているから心配はいらない。
単語の検索
concordance("word")
メソッドを使う。Concordance とは日本語に直すと「コンコルダンス」であり、Wikipedia 曰く「ある書物に出てくる言葉を辞書順に配列し、その言葉が書物のどこに出現するかを、実際の文脈とともに一覧表にしたもの」。難しい? やってみればわかる。
text3.concordance(god)
結果は以下のようになる。言いたいことはわかっただろうか。
同じような使われ方の言葉の検索
ちょっとややこしいが落ち着け。例えば「神は死んだ」の「神」を引っこ抜いて「〇〇は死んだ」とする。この〇〇に入りそうな言葉を調べるのだ。
なんでこんなややこしい奴を紹介するの? 秘密。
メソッドは similar("word")
であり、使い方は concordance()
と変わらない。
text3.similar("god")
「彼」だか「ヨセフ」だか「ヤコブ」だかが出てきている。「彼は死んだ」「ヨセフは死んだ」「ヤコブは死んだ」としても通じるだろう。通じないのもあるけど精度の問題だから仕方がない。
言葉の出てくる場所を調べる
更にややこしいが落ち着け。言いたいことは「『愛』って言葉はこの本の何ページに出てくるの?」という疑問に対処するってことだ。
メソッドは dispersion_plot(array)
。引数は配列だ、気をつけろ! ちなみに大文字小文字の区別がなされる。私はハマったので注意。
text3.dispersion_plot(["God", "Joseph", "Jacob", "Abraham", "Noah", "Cain", "Abel"])
そう、matplotlib の働きによりグラフで表示される。いい感じでしょ? どうでもいいけど、カインとアベルって有名なわりに一瞬だけ出てきて終わるのね。一発屋か?
本題に入ろう ─ 日本語の処理
さて、一通り試してみたところで日本語の処理に入る。ここでの処理は概ね 「Python による日本語自然言語処理」の手順に沿っている。そちらのマニュアルはいくらか簡素であり Python 2 の文法で記述されている。必要があれば参照されたい。
処理にあたっては短編をまるごと扱う。すごいでしょ? 扱うのは青空文庫から仕入れた「一房のぶどう」。知ってる人いるかなあ。
なお、以下の通り処理をするにあたって必要なライブラリやらクラスを仕入れておく。
# NLTK 本体
import nltk
# 「一房のぶどう」読み込み用のリーダ
from nltk.corpus.reader import *
from nltk.corpus.reader.util import *
from nltk.text import Text
以後、上記のコードがすでに実行されている前提で進める。
「一房のぶどう」を読み込む
ややこしいことを手順を踏んでうまくやる。
# Sentence Tokenizer
s_tokenizer = nltk.RegexpTokenizer(u'[^ 「」!?。]*[!?。]')
# Chatacter Tokenizer
c_tokenizer = nltk.RegexpTokenizer(u'([ぁ-んー]+|[ァ-ンー]+|[\u4e00-\u9FFF]+|[^ぁ-んァ-ンー\u4e00-\u9FFF]+)')
# プレーンテキスト読み込み
grape = PlaintextCorpusReader(
"D:/Jupyter/path/to/file",
"budou.txt",
encoding="utf-8",
para_block_reader=read_line_block,
sent_tokenizer=s_tokenizer,
word_tokenizer=c_tokenizer
)
s_tokenizer
は文章の区切りを定義する。今回の場合、「!」「?」「。」で終わる文字列を一つの文とする。c_tokenizer
は使用する文字種を定義する。ここではひらがな、カタカナ、漢字を正規表現で指定している。
以上2つを定義したらさっそくファイルを読み込もう。PlaintextCorpusReader
クラスをつかってファイルを読み込む。コードを見ればだいたい分かるが、最初の引数はファイルへのパス、2つ目はファイル名を指定しなければならない。エンコーディングも必要なら書き換えること。
エラーさえでなければ、とりあえず読み込むことはできた。検証しよう。raw()
メソッドは読み込んだファイルのテキストをそのまま出力する。
こんな感じで出力されていたらいい感じだ。さっきの Interlude で述べた各メソッドもうまく動くはずだ。試してみよう……。
しかし、やらなきゃやらぬことがある。変数 grape
に実装されているメソッドを調べればわかるが、PlaintextCorpusReader
には concordance()
などのメソッドは実装されていない。ほかもしかり。なので、words()
メソッドでうまく扱えるような形式にする。
ちなみにこのメソッドは、前述の c_tokenizer
を参照して強引に単語ごとに区切っている。
grape_w = grape.words()
# concordance
grape_w.concordance()
# dispersion_plot
grape_w.dispersion_plot()
ちなみに、dispersion_plot()
の日本語表示が文字化けを起こしたときは「matplotlibの日本語文字化けを解消する(Windows編)」が参考になる。
そして、処理は佳境に入る。遂に形態素解析をやるときなのだ。でも、どうやって?
MeCab: Yet Another Part-of-Speech and Morphological Analyzer
MeCab とは、日本語を単語ごとにぶったぎり形態素解析を行うエンジンである。京都大学のプロジェクトの一環として公開されたオープンソースソフトウェア。エンジン自体の詳細については公式サイトを見るのが一番だろう。
同じような形態素解析解析エンジンとして、JUMAN や ChaSen が存在する。両者ともまた有名であるが、今回はいちばん有名な MeCab を使う。
導入手順は(Windows の場合)相当にややこしい。まずはバイナリ自体を導入する。Python のライブラリについては pip
などでそのままインストールできたら最高かつスマートであるのだが、本来のパッケージ mecab-python
はそのまま導入できない。調べてみたところ、ソースを改変するだの Visual Studio でコンパイルするなどかなり恐ろしげだ。
ともかく、結局の所は mecab-python-windows
(と 64bit 版 MeCab バイナリ)を入れれば解決する。細かいことは一番下の参考記事の当該記事を参照すること。
紆余曲折を経てなんとか導入できたら、以下のようにコードを実行する。
# 大文字小文字を区別する!
import MeCab
m = MeCab.Tagger('')
単純ではあるが、このコードが正常に動くなら勝ちも当然である。MeCab に文章を読み込ませて形態素解析をするには、m
のメソッドである parse("文")
を叩けばいい。つまり、このメソッドに上記の「一房のぶどう」を読み込ませればよい。さあ、はじめよう!
日本語版形態素解析
上記のコードで定義した変数 grape
を思い出そう。この中には「一房のぶどう」のデータが詰まっている。そして、grape
には sents()
という文章ごとに分けて出力するメソッドがある。これを一旦噛ませて MeCab に渡すことにしよう。
以下のコードは grape
から文章ごとに分割した文字列を配列にした sentences
を定義する。sentences
の中身は一つずつ「一房のぶどう」の文が入っているという寸法だ。
sentences = list()
for l in grape.sents():
sentences.append(''.join(l))
最後は簡単だ。MeCab に文章を読み込ませて形態素解析を行おう。
for s in sentences:
print(m.parse(s))
遂にここまで来た。目標達成だ。しばしこの出力を眺めるといい感じの感傷に浸れるだろう。ここまでやってきて辛かっただろうか。私はこのスクリーンショットを撮ったとき死にそうだった。
追加課題
ここまで長々と形態素解析までの苦労を追ったが、ネタバレをすると Python を使わないでスタンドアロンで MeCab などを実行すれば一瞬で終わる。しかし、それでは拡張性にかけるというものだ…….。
さて、ここまでやって思いついた次の課題に触れておく。来年同じようなアドベントカレンダーがあったらこれらのどれか、または複数をチョイスしてやるかも。
1. 「一房のぶどう」のプレーンテキストの改良
今回使った「一房のぶどう」のテキストはいわゆるルビが振ってある状態で処理していた。それだけかと思うが、多分抜いたほうがいい。これについては今回やってもよかったけどなんとなくやる気が湧かなかった。
2. 別の形態素解析エンジンも試してみる
今回は MeCab をエンジンとして使ったが、前述のどおり JUMAN やら ChaSen とか他に色々ある。私がいいなと思ったのは Kuromoji というエンジンで、Java で実装されているオープンソースソフトウェアである。デモもあるようなので色々触ってみるといいかもしれない。
3. 最近の言葉で書かれたコーパスで形態素解析を行う
今回扱った「一房のぶどう」はかなり前に著された短編であり、古い言葉はあれど今流行している単語は当然入っていない。もしこれがバリッバリの現代小説で、主人公やら取り巻きやらがネットスラングを多用するようなキャラクターだったらどうするだろう。形態素解析にも限界はあり、固有名詞やいわゆる流行語をすべて正確に把握できるわけではない。MeCab の最終更新は2013年であり時代の流れに追いつけなさそうな気がしなくもない。
解決方法としては、形態素解析を行うベースである辞書の更新が考えられる。例として NEoligd が挙げられる。こいつ自体は「多数のWeb上の言語資源から得た新語を追加することでカスタマイズした MeCab 用のシステム辞書です。」とある通り、最新の単語もある程度うまく処理してくれるだろうと思える。
4. 解析対象の正規化
mecab-ipadic-neologd の Regexp.ja の項に詳しいが、解析する前の文章の語彙をいかに同じ記述にするかは重要と言える。例えば記号類については全角半角が混ざっているよりかはどちらかに統一したほうが望ましいだろう。数字についても同様と言える。より標準的な記述とすることで、形態素解析を行う辞書との合致が多くなり精度の向上が見込める。辞書側の努力も重要だが、解析される側もいかに解析されやすくするかが肝要である。
「一房のぶどう」の正規化を考えると、ルビの中に「テイブル」という記述が見受けられる。これはつまり「テーブル」なのでそちらに直すのが適当だろう。また、「屹度《きっと》」という記述も必要ならば「きっと」とバッサリ切ってしまうのも一考。
終わりに
(いつもの口調に戻します)
さて、ここまでお付き合いいただきありがとうございます。ここまで書き連ねるのに結構な時間を費やしヘトヘトです。二度とやりたくないとまではいかないですが、ここまでやるのは一年に一回ぐらいにしたいところです。
自然言語処理と言うとなんだかやべー技術のように思えますが、こんな簡素な形態素解析の処理でさえも自然言語処理の一つ ─ もっというと大きな分野 ─ です。形態素解析の技術は検索エンジンの検索ワードの最適化、スマートフォンのニュースアプリにおける見出しの整形、翻訳エンジンなど多岐にわたって応用されているそうです。適当なパソコンでちょいちょいとやれるような技術が巡り巡って世界中で使われていると思うとビリビリっと来るものがありませんか。私は来ます。
今の流行りの曲、DA PUMP の「U.S.A」では「数十年でリレーションシップ だいぶ変化したようだ」と歌われています。歴史全体を見れば数十年単位の勘定が適当でしょうが、この自然言語処理という分野は数年で一気に変化が訪れるような世界です。もっとも AI だの機械学習だのと言われると IT 全体の潮流と言えるかもしれません。この潮流に乗る乗らないは各個人の自由ですが、スマートフォンやらを持っている限りは嫌でも乗ることになるでしょう。
手に持っているその板の中身、もしくは板から飛ぶ電波の先の計算の実態をブラックボックスとして目を背けることは簡単です。見つめるよりだいぶ易しいから。ただ、もし目を背ける理由に「技術がよくわからない」などとあるなら是非実態を見つめてみてください。案外思い込みでわけわからねえと思っているだけで、手を触れてしまえばその虜になってしまうかもしれません。
だんだんポエムじみてきたので最後の言葉を。
この度はこの記事を書く機会を与えてくださったアドベントカレンダー「The 言語 Advent Calendar 2018」の主催者であるつぁいにゃお氏に感謝の意を評します。是非来年もやっていただけたら嬉しく思います。また、アドベントカレンダーに参加した方全員にもまた感謝申し上げます。
では、またどこかでお会いしましょう。早ければ数秒後、Discord とかマストドンにて。
参考記事
英語が読めるならこちらを読むべきだろう。私は全部読みきれてないが自然言語処理について相当量のマニュアルになる。
NLTK で日本語の自然言語処理をするにあたっての要項、注意事項などをまとめている。オライリー本の「入門 自然言語処理」の第12章をそのまま公開している太っ腹。しかし他の章は購入しないと見られない。本屋へ行こう。本を買おう。本を読もう。
題目通り matplotlib で日本語が文字化けを起こしたときの対処法。
Windows 環境下で Python + MeCab をぶち込むとなると相当難儀であるが、@yukinoi 氏はこの点をうまく解決してくれる。感謝の限り。
しかし、私の環境だとこの記事で提供される 64bit 版 MeCab バイナリではなく公式から配布されるバイナリで動いたりとちょっと怪しい……。
NEologd についてのレビュー記事。書いてあることが結構難しくて私はお手上げですが、わかるひとは興味深い記事かもしれません。