はじめに
頑張れば、何かがあるって、信じてる。nikkieです。
2019年12月末から自然言語処理のネタで毎週1本ブログを書いています。
2/3の週から自然言語処理の基礎固めとして『入門 自然言語処理』に取り組んでいます。
- 作者:Steven Bird,Ewan Klein,Edward Loper
- 発売日: 2010/11/11
- メディア: 大型本
今週は日本語版限定の12章「Python による日本語自然言語処理」の一部に取り組みました。
※前提として、週1ブログの取り組みの中で、日本語のテキストはjanome
で扱ってきました1
12章は以下で公開されています:
公開されている12章のコードは書籍と同様にPython2系向けのようです2。
- 文字列に
u
がついている → Python 3系でも動く(写経時に付ける必要はない) sys.getdefaultencoding
→Python 3系では'utf-8'
が返った3
目次
- はじめに
- 目次
- 動作環境
- 扱う日本語テキストの取得
- 分かち書きされていないコーパスの扱い
- 分かち書きされたコーパスの扱い
- MeCabを導入する
- 別の日本語テキストを試す:先人の残したまどマギのセリフ
- まとめ
- 感想
動作環境
今週の開始時は先週までと同じ環境です。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.6 BuildVersion: 18G103 $ python -V # venvによる仮想環境を使用 Python 3.7.3 $ pip list # grepを使って抜粋して表示 beautifulsoup4 4.8.2 ipython 7.12.0 matplotlib 3.1.3 nltk 3.4.5 wordcloud 1.6.0
ファイル配置
. ├── ch12_ja # 日本語のテキストを配置 ├── env ├── # 日本語のWordCloud作成のために置いたフォントファイルなどは省略
扱う日本語テキストの取得
青空文庫から宮沢賢治の『銀河鉄道の夜』を使います。
宮沢賢治 銀河鉄道の夜
書籍では取得が省略されているので、3章の復習がてら手を動かしてみました。
urllib
を使ってWebから取得し、HTMLタグを削除します。
エンコーディングがShift JISでした!(UTF-8だと想定していたらUnicodeDecodeError
で落ちました)
<?xml version="1.0" encoding="Shift_JIS"?>
In [12]: import urllib.request In [20]: from bs4 import BeautifulSoup In [21]: with urllib.request.urlopen('https://www.aozora.gr.jp/cards/000081/files/456_15050.html') as f: ...: html_bytes = f.read() ...: html = text_bytes.decode('shift-jis') ...: In [22]: soup = BeautifulSoup(html, 'html.parser') In [23]: text = soup.get_text() In [24]: with open('ch12_ja/gingatetsudono_yoru.txt', 'w') as fout: ...: fout.write(text) ...:
この方法だとhead要素のtitleタグが残り、先頭に空行が多かったので、bodyタグを指定するのがよさそうです(本と実行結果を比べづらかったので手動で削除しました) 。
分かち書きされていないコーパスの扱い
上で取得した『銀河鉄道の夜』をNLTKのコーパスとして読み込みます。
先週の5章の取り組みではbrown
コーパスを使いましたが、ここでは『銀河鉄道の夜』のテキストからginga
コーパスを作ります。
テキストの読み込みにはnltk.corpus.reader.PlaintextCorpusReader
を使います。
イニシャライザに渡す引数は4
root
:相対パスの場合はカレントディレクトリ以下fileids
:ファイル名gingatetsudono_yoru.txt
word_tokenizer
sent_tokenizer
para_block_reader
:nltk.corpus.reader.util.read_line_block
=1行ずつ読み込む指定?
の5つです。
In [29]: from nltk import RegexpTokenizer, Text In [30]: from nltk.corpus.reader import PlaintextCorpusReader In [31]: from nltk.corpus.reader.util import read_line_block
word_tokenizer
, sent_tokenizer
にはnltk.tokenize.regexp.RegexpTokenizer
を渡します。
sent_tokenizer
(文の区切り方を指定)はセリフの「」を考慮した上で、!または?または。で区切っています。
In [32]: jp_sent_tokenizer = RegexpTokenizer(r'[^ 「」!?。]*[!?。]')
word_tokenizer
(単語の区切り方を指定)は、連続するひらがな、カタカナ、漢字を単語をすると仮で決めています。
例えば「一つ一つ」が「一/つ/一/つ」と区切られてしまうので、この分け方はあくまで仮のものです。
In [33]: jp_char_tokenizer = RegexpTokenizer(r'([ぁ-んー]+|[ァ-ンー]+|[\u4E00-\u9FFF]+|^[ぁ-んァ-ンー\u4E00-\u9FFF]+)')
コーパスを作りましょう。
In [34]: ginga = PlaintextCorpusReader('ch12_ja', 'gingatetsudono_yoru.txt', ...: encoding='utf-8', para_block_reader=read_line_block, ...: sent_tokenizer=jp_sent_tokenizer, word_tokenizer=jp_char_tokenizer) ...:
コーパスからは
raw
メソッドでPlaintextCorpusReader
に渡したファイルの全文(複数ある場合は連結)words
メソッドでトークンのリスト
を取得できます。
In [38]: print(ginga.raw()[:50]) 銀河鉄道の夜 宮沢賢治 一、午后(ごご)の授業 「ではみなさんは、そういう In [39]: print('/'.join(ginga.words()[:50])) 銀河鉄道/の/夜/宮沢賢治/一/午后/ごご/の/授業/ではみなさんは/そういうふうに/川/だと/云/い/われたり/乳/の/流/れたあとだと/云/われたりしていたこのぼんやりと/白/いものがほんとうは/何/かご/承知/ですか/先生/は/黒板/に/吊/つる/した/大/きな/黒/い/星座/の/図/の/上/から/下/へ/白/くけぶった/銀河帯
words
メソッドは5章で使ったbrown
にもありました。
NLTKのコーパスとして読み込むことで、英語でも日本語でも扱い方(インターフェース)が揃うと理解しました。
トークンのリストをnltk.text.Text
に変換します。
In [40]: ginga_t = Text(w for w in ginga.words()) # ginga_t = Text(ginga.words()) で済むように思われる In [41]: ginga_t.concordance('川') Displaying 25 of 57 matches: の 夜 宮沢賢治 一 午后 ごご の 授業 ではみなさんは そういうふうに 川 だと 云 い われたり 乳 の 流 れたあとだと 云 われたりしていたこのぼ がするのでした 先生 はまた 云 いました ですからもしもこの 天 あま の 川 がわ がほんとうに 川 だと 考 えるなら その 一 つ 一 つの 小 さな # [省略]
concordance
メソッドで指定された単語について、索引を表示しています。
分かち書きされたコーパスの扱い
In [43]: import nltk In [45]: nltk.download('jeita') Out[45]: True [nltk_data] Downloading package jeita to [nltk_data] /Users/.../nltk_data...
/Users/.../nltk_data/corpora/
にはjeita.zip
がダウンロードされますが、展開されません。
そこでコマンドラインからunzip
しました
In [49]: !unzip ~/nltk_data/corpora/jeita.zip -d ~/nltk_data/corpora/jeita In [50]: !ls ~/nltk_data/corpora/jeita/jeita/ README a0680.chasen a1370.chasen a2060.chasen g0037.chasen # [省略]
READMEの内容はこちらでも確認できました:
このコーパスはChaSen
形式で分かち書きされています。
ChaSen
形式とは、各語について
- 出現形
- 読み
- 原形
- 品詞
- 活用
がタブ区切りで並んだ形式です(後ろの項目は品詞によってはありません)。
In [67]: !head ~/nltk_data/corpora/jeita/jeita/a0010.chase
...: n
記号-空白
新潟 ニイガタ 新潟 名詞-固有名詞-地域-一般
の ノ の 助詞-連体化
停車場 テイシャジョウ 停車場 名詞-一般
を ヲ を 助詞-格助詞-一般
出る デル 出る 動詞-自立 一段 基本形
と ト と 助詞-接続助詞
列車 レッシャ 列車 名詞-一般
の ノ の 助詞-連体化
箱 ハコ 箱 名詞-一般
読み込むにはnltk.corpus.reader.ChasenCorpusReader
を使います。
In [52]: from nltk.corpus.reader import ChasenCorpusReader In [55]: jeita = ChasenCorpusReader('/Users/.../nltk_data/corpora/jeita/', '.*chasen', encoding='utf-8') In [56]: print('/'.join(jeita.words()[22100:22140])) たい/という/気持/が/、/この上なく/純粋/に/、/この上なく/強烈/で/あれ/ば/、/ついに/は/そのもの/に/なれる/。/なれ/ない/の/は/、/まだ/その/気持/が/そこ/まで/至っ/て/い/ない/から/だ/。/法
分かち書きされており、品詞タグ付け済みのため、5章で扱ったbrown
のようにtagged_sent
メソッドが使えました。
In [63]: tab = '\t' In [64]: print('\nEOS\n'.join(['\n'.join(f'{w[0]}/{w[1].split(tab)[2]}' for w in sent) for sent in jeita.tagged_sents()[2170:2171]])) を/助詞-格助詞-一般 まくっ/動詞-自立 た/助動詞 とき/名詞-非自立-副詞可能 # [省略]
※EOSが出力されなかったので、コードを写し間違えているかもしれません5
MeCabを導入する
12.2で登場するMeCabを導入します。
導入方法は『Pythonによるあたらしいデータ分析の教科書』にならいました。
$ brew install mecab-ipadic # 依存関係にあるmecabもインストールされる $ mecab -v mecab of 0.996
動作確認です。
$ mecab # 対話的に使います 天気の子 天気 名詞,一般,*,*,*,*,天気,テンキ,テンキ の 助詞,連体化,*,*,*,*,の,ノ,ノ 子 名詞,一般,*,*,*,*,子,コ,コ EOS
最新語に対応できるようmecab-ipadic-NEologdも入れました。
$ git clone --depth 1 git@github.com:neologd/mecab-ipadic-neologd.git # ホームディレクトリで実行しています $ cd mecab-ipadic-neologd/ $ ./bin/install-mecab-ipadic-neologd -n
先ほどの例を使うと、最新語に対応できているかも確認できます。
$ mecab -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd/ 天気の子 天気の子 名詞,固有名詞,一般,*,*,*,天気の子,テンキノコ,テンキノコ EOS
別の日本語テキストを試す:先人の残したまどマギのセリフ
12章を調べながら取り組んでいたところ、偉大な先人(yutakikuchiさん)の功績を偶然にも発見しました。
ここまでに学んだことから
- まどマギのテキストをMeCabでChaSen形式にする
- ChaSen形式の分かち書きされたテキストを
ChasenCorpusReader
でコーパスとして読み込む - コーパスに含まれるトークンから
Text
を作るとconcordance
が見られる
んじゃないかと電波を受信し、手を動かしてみました。Let's try!
yutakikuchiさんがリポジトリに残したまどマギのセリフテキストを取得します。
$ wget https://raw.githubusercontent.com/yutakikuchi/NLTK/master/madmagi/madmagi_corpus-euc.txt -O ch12_ja/madomagi-euc.txt
文字コードがEUC JPだったので、PythonにUTF-8に変換してもらいます。
In [69]: with open('ch12_ja/madomagi-euc.txt', 'rb') as fin, open('ch12_ja/madom ...: agi_utf8.txt', 'wb') as fout: ...: euc_bytes = fin.read() ...: euc_text = euc_bytes.decode('euc_jp') ...: utf8_bytes = euc_text.encode() ...: fout.write(utf8_bytes) ...:
MeCabでChaSen形式にします。
人名に対応できるようにneologdを指定しています。
$ mecab -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd/ -O chasen < ch12_ja/madomagi_utf8.txt > ch12_ja/madomagi.chasen
以上が1です。
では2でNLTKのコーパスとして読み込みましょう。
In [71]: madomagi = ChasenCorpusReader('ch12_ja/', 'madomagi.chasen', encoding='utf-8') In [73]: madomagi_t = Text(madomagi.words())
concordance
を見てみます。
In [74]: madomagi_t.concordance('助ける') Displaying 1 of 1 matches: … ? 死ん じゃ うって 、 わかっ て た のに … 。 私 なんか 助ける より も 、 あなたに … … 生き て て ほしかっ た のに … あな In [75]: madomagi_t.concordance('助け') Displaying 1 of 1 matches: よ ね うん … キュゥべえ に 騙さ れる 前 の バカ な 私 を 、 助け て あげ て くれ ない か な ? 約束 する わ 。 絶対 に あなた In [78]: madomagi_t.concordance('いや') Displaying 1 of 1 matches: 思う ん だ 鹿目 さん … さよなら 。 ほむらちゃん 。 元気 で ね いや ! 行かないで … 鹿目 さ ぁぁぁん ! ! どうして … ? 死ん じ
yutakikuchiさんは
事前の予想では「いやだ」とか「助けて」などの台詞が頻繁に使われていると考えた
と綴っていますが、予想通り「助ける」や「いや」はネガティブなセリフで使われていますね。
あとは、まどマギを象徴するこの語句:
In [80]: madomagi_t.concordance('魔法少女') Displaying 7 of 7 matches: 変え られる の ? もちろん さ 。 だから 僕 と 契約 し て 、 魔法少女 に なっ て よ ! 私 は 巴マミ あなた たち と 同じ 、 見滝 、 見滝 原中 の 3 年生 そして キュゥべえ と 契約 し た 、 魔法少女 よ は あー はぁ 。 うん やあ はい 、 これ う わぁ … 。 い 夫だよ 、 ほむらちゃん あ 、 あなた たち は … 彼女たち は 、 魔法少女 。 魔女 を 狩る 者 たち さ いきなり 秘密 が バレ ちゃっ た ない ! 鹿目 さん まで 死ん じゃう よ ? それでも 、 私 は 魔法少女 だ から 。 みんな の こと 、 守ら なきゃ いけ ない から ねぇ 時 、 間に合っ て 。 今 でも それ が 自慢 な の だから 、 魔法少女 に なっ て 、 本当に よかっ た って 。 そう 思う ん だ 鹿目 は 心臓 の 病気 で ずっと ・ ・ ・ あ 鹿目 さん 、 私 も 魔法少女 に なっ た ん だ よ ! これから 一緒 に 頑張ろ う ね ! え られる の ? もちろん さ 。 だから 、 僕 と 契約 し て 、 魔法少女 に なっ て よ ! ダメ ぇぇ ぇぇ ぇぇ ぇぇ ぇぇ ぇぇ ! !
魔法少女は「契約してなるもの」「魔女を狩る者」とわかりますね(まどマギを知らない方がどの程度分かるかは未知数ですが)。
まとめ
日本語テキストの扱いですが、12章を少し読んだところ、以下の方法でNLTKのコーパスとして扱えそうという認識です。
NLTKにコーパスとして取り込めれば、英語テキストと日本語テキストの扱いにそれほど大きな違いはないというのが暫定的な結論です。
感想
まどマギのテキストの扱いが受信した電波の通りにできたので、「もしかしてどんな日本語テキストでもこの扱いでいける!?」という期待半分、間違っているかもという不安半分という心境です。
不安要素は12章が1-2割程度しか読み進められず、この扱いに見落としがあるかもしれないと思うからです。
時間を見つけて読み進められればと思っています。
今回はconcordance
を使いましたが、Text
でできることに他に何があるのか見ておきたいところです(similarityもあったように思います)。
また、5章で見たのと同様に指定した品詞の単語の取り出しもできそうです。
次回は『入門 自然言語処理』6章「テキスト分類の学習」に取り組む予定です。
-
例えば、「Janome ではじめるテキストマイニング」の中のWordCloudのチュートリアルに取り組み、janomeを全然使いこなせていなかったと思い知りました - nikkie-ftnextの日記↩
-
ドキュメントによれば「Unicode 実装で使用される現在のデフォルトエンコーディング名を返」すので、期待通りの結果ですね↩
-
tab
変数はSyntaxError: f-string expression part cannot include a backslash
への対応です ref: https://stackoverflow.com/a/44780467↩