「銀河鉄道の夜」を形態素解析してみた。

star

宮沢賢治の「銀河鉄道の夜」に自然言語処理(Natural Language Processing; NLP)の一分野である形態素解析(Morphological Analysis)を行ってみた記事です。
今回は、”MeCab“と”Janome“という二つのツールを比較しました。

形態素解析とは?
形態素解析とは、自然言語で書かれた文を言語上の最小単位である形態素に分割し、それぞれの品詞や変化などを割り出すこと。ITの分野ではコンピュータによる自然言語処理の一つとして、かな漢字変換や全文検索、機械翻訳などで用いられる。
【IT用語辞典より】

例えば、「東京で週末を過ごす」という文章があった時、以下のように分割することができ る。
  • 東京:固有名詞・地名
  • で:格助詞
  • 週末:普通名詞
  • を:格助詞
  • 過ごす:動詞・サ行五段・終止形

実行環境

TypeVersion
OSmacOS Mojave v10.14.6
pythonv3.7.3
mecab-python3v0.996.3
janomev0.3.10

1. 前処理 / Preprocessing

1-a. 文章の読み込み

まずは、「銀河鉄道の夜」を青空文庫のサイトからテキストデータとして保存します。宮沢賢治さんは1933年に亡くなられており、国際的な基準である70年(日本の場合50年)を過ぎていてパブリックドメインとなっているため、公開されているのだと思います。

参考 銀河鉄道の夜 - 青空文庫「銀河鉄道の夜」のダウンロードページ

まずは、pythonで以下のようにtxtファイルを読み込んでいきましょう。
ここでは、the_night_of_the_milky_way_train.txtという名前のファイルにして保存されています。

preproc.py
with open("the_night_of_the_milky_way_train.txt", mode="r", encoding="utf-8") as f:
    milky_original = f.read()

print(milky_original)

# Output
# 「ではみなさんは、そういうふうに川だと云《い》われたり、乳の流れたあとだと云われたりしていたこのぼんやりと白いものがほんとうは何かご承知ですか。」先生は、黒板に吊《つる》した大きな黒い星座の図の、上から下へ白くけぶった銀河帯のようなところを指《さ》しながら、みんなに問《とい》をかけました。
# カムパネルラが手をあげました。それから四五人手をあげました。ジョバンニも手をあげようとして、急いでそのままやめました。たしかにあれがみんな星だと、いつか雑誌で読んだのでしたが、このごろはジョバンニはまるで毎日教室でもねむく、本を読むひまも読む本もないので、なんだかどんなこともよくわからないという気持ちがするのでした。
# ところが先生...

1-b. 文章の前処理

次に、ルビや改行などを削除する前処理を行っていきましょう。
ここでは、import reというコマンドでパッケージを用いています。

preproc.py
import re

with open("the_night_of_the_milky_way_train.txt", mode="r", encoding="utf-8") as f:
    milky_original = f.read()

milky = re.sub("《[^》]+》", "", milky_original)
milky = re.sub("[[^]]+]", "", milky)
milky = re.sub("[|  「」\n]", "", milky)

print(milky)

# Output
# ではみなさんは、そういうふうに川だと云われたり、乳の流れたあとだと云われたりしていたこのぼんやりと白いものがほんとうは何かご承知ですか。先生は、黒板に吊した大きな黒い星座の図の、上から下へ白くけぶった銀河帯のようなところを指しながら、みんなに問をかけました。カムパネルラが手をあげました。それから四五人手をあげました。ジョバンニも手をあげようとして、急いでそのままやめました。たしかにあれがみんな星だと、いつか雑誌で読んだのでしたが、このごろはジョバンニはまるで毎日教室でもねむく、本を読むひまも読む本もないので、なんだかどんなこともよくわからないという気持ちがするのでした。ところが先生は...

1-c. Pickleの生成

最後に、これらのデータをpickleに格納しましょう。

Pickleとは?
簡単に言うと、python内の変数をデータファイルとして保存する際に、用いるライブラリです。Samurai Blogでは以下のように説明されています。

pickleはPythonオブジェクトの直列化(シリアライズ)や非直列化(デシリアライズ)を扱うライブラリです。
【Samurai Blogより】
preproc.py
import re
import pickle

with open("the_night_of_the_milky_way_train.txt", mode="r", encoding="utf-8") as f:
    milky_original = f.read()

milky = re.sub("《[^》]+》", "", milky_original)
milky = re.sub("[[^]]+]", "", milky)
milky = re.sub("[|  「」\n]", "", milky)

with open("the_night_of_the_milky_way_train.pickle", mode="wb") as f:
    pickle.dump(milky, f)

2. MeCab

MeCabとは?
MeCabは、京都大学情報学研究科と日本電信電話株式会社コミュニケーション科学基礎研 究所の共同研究ユニットプロジェクトによって開発された、オープンソースの形態素解析エンジンである。解析モデルとしては、bi-gram マルコフモデルを用いており、識別モデルとしては条件付き確率場(CRF)を用いている。また、辞書引きアルゴリズムとしては、ダブル配列を用いているため、高速性とコンパクト性を持っている。
【京都大学情報学研究科 (2006)より】

2-a. MeCab入門

まずは、pickleを読み込み、簡易的なMaCabの形態素解析を行ってみましょう。定性的に分離できていることが確認できます。

mecab.py
import MeCab as mecab
import pickle

with open("the_night_of_the_milky_way_train.pickle", mode="rb") as f:
    milky = pickle.load(f)

m_tagger = mecab.Tagger()
result = m_tagger.parse(milky)
print(result)

# Output
# では            接続詞, *, *, *, *, *, では, デハ, デワ
# みなさん    名詞, 代名詞, 一般, *, *, *, みなさん, ミナサン, ミナサン
# は                助詞, 係助詞, *, *, *, *, は, ハ, ワ
# 、                記号, 読点, *, *, *, *, 、, 、, 、
# そういう    連体詞, *, *, *, *, *, そういう, ソウイウ, ソーユウ
# ふう            名詞, 非自立, 形容動詞語幹, *, *, *, ふう, フウ, フー
# に                助詞, 副詞化, *, *, *, *, に, ニ, ニ
# 川                名詞, 一般, *, *, *, *, 川, カワ, カワ
# だ                助動詞, *, *, *, 特殊・ダ, 基本形, だ, ダ, ダ
# と                助詞, 格助詞, 引用, *, *, *, と, ト, ト
# 云わ            動詞, 自立, *, *, 五段・ワ行促音便, 未然形, 云う, イワ, イワ
# れ                動詞, 接尾, *, *, 一段, 連用形, れる, レ, レ
# たり            助詞, 並立助詞, *, *, *, *, たり, タリ, タリ
# 、                記号, 読点, *, *, *, *, 、, 、, 、
# ...

2-b. Part of Speechの計算(MeCab)

Part of Speechとは、各品詞のことを表します。ここでは、名詞のみを抽出し、その個数を算出してみましょう。

mecab.py
import MeCab as mecab
import pickle
import numpy as np

with open("the_night_of_the_milky_way_train.pickle", mode="rb") as f:
    milky = pickle.load(f)

m_tagger = mecab.Tagger()
result = m_tagger.parse(milky)
print(result)

part_of_speech = '名詞'

noun_list = []
m_parse = m_tagger.parseToNode(milky)
while m_parse:
    if m_parse.feature.split(',')[0] == part_of_speech:
        noun_list.append(m_parse.surface)
    m_parse = m_parse.next

print(noun_list)
print(len(noun_list))
# ['みなさん', 'ふう', '川', '乳', 'あと', 'ぼんやり', 'もの', 'ほんとう', '何', '承知', '先生', '黒板', '星座', '図', '上', '下', '銀河', '帯', 'よう', 'ところ', 'みんな', '問', 'カムパネルラ', '手', 'それ', '四', '五', '人', '手', 'ジョバンニ', '手', 'あれ', 'みんな', '星', 'いつか', '雑誌', 'の', 'このごろ', 'ジョバンニ', '毎日', '教室', '本', 'ひま', '本' ...]
# 5895

合計5895個あることがわかりました。
何個か誤ってしまっているところがありますが、大まかには妥当そうですね。

3. Janome

Janomeとは?
JanomeはPure Pythonで個人によって開発されたもので、MeCabとの決定的な違いとし て、辞書引きアルゴリズムにFST (Finite State Transducer)の一種であるMinimal Acyclic Subsequential Transducer を用いていて、データサイズがコンパクトな構造となっている。
【Janome v0.3 documentation (ja)より】

3-a. Janome入門

こちらでも同様に、pickleを読み込み、簡易的なJanomeの形態素解析を行ってみましょう。定性的に分離できていることが確認できます。

janome.py
import pickle
from janome.tokenizer import Tokenizer

with open("the_night_of_the_milky_way_train.pickle", mode="rb") as f:
    milky = pickle.load(f)

t = Tokenizer()
for token in t.tokenize(milky):
    print(token)

# Output
# では            接続詞, *, *, *, *, *, では, デハ, デワ
# みなさん    名詞, 代名詞, 一般, *, *, *, みなさん, ミナサン, ミナサン
# は                助詞, 係助詞, *, *, *, *, は, ハ, ワ
# 、                記号, 読点, *, *, *, *, 、, 、, 、
# そういう    連体詞, *, *, *, *, *, そういう, ソウイウ, ソーユウ
# ふう            名詞, 非自立, 形容動詞語幹, *, *, *, ふう, フウ, フー
# に                助詞, 副詞化, *, *, *, *, に, ニ, ニ
# 川                名詞, 一般, *, *, *, *, 川, カワ, カワ
# だ                助動詞, *, *, *, 特殊・ダ, 基本形, だ, ダ, ダ
# と                助詞, 格助詞, 引用, *, *, *, と, ト, ト
# 云わ            動詞, 自立, *, *, 五段・ワ行促音便, 未然形, 云う, イワ, イワ
# れ                動詞, 接尾, *, *, 一段, 連用形, れる, レ, レ
# たり            助詞, 並立助詞, *, *, *, *, たり, タリ, タリ
# 、                記号, 読点, *, *, *, *, 、, 、, 、
# ...

3-b. Part of Speechの計算(Janome)

ここでも、名詞のみを抽出し、その個数を算出してみましょう。

janome.py
from janome.tokenizer import Tokenizer
from janome.analyzer import Analyzer
from janome.tokenfilter import *
from janome.charfilter import *
import pickle

with open("the_night_of_the_milky_way_train.pickle", mode="rb") as f:
    milky = pickle.load(f)

t = Tokenizer()
for token in t.tokenize(milky):
    print(token)

part_of_speech = '名詞'

char_filters = [UnicodeNormalizeCharFilter(), RegexReplaceCharFilter(
    r"[IiⅠi?.*/~=()〝 <>::《°!!!?()-]+", "")]
token_filters = [POSKeepFilter([part_of_speech]), POSStopFilter(
    []), LowerCaseFilter()]
analyzer = Analyzer(char_filters, t, token_filters)

noun_list = [token.surface for token in analyzer.analyze(milky)]

print(noun_list)
print(len(noun_list))
# ['みなさん', 'ふう', '川', '乳', 'あと', 'ぼんやり', 'もの', 'ほんとう', '何', '承知', '先生', '黒板', '星座', '図', '上', '下', '銀河', '帯', 'よう', 'ところ', 'みんな', '問', 'カムパネルラ', '手', 'それ', '四', '五', '人', '手', 'ジョバンニ', '手', 'あれ', 'みんな', '星', 'いつか', '雑誌', 'の', 'このごろ', 'ジョバンニ', '毎日', '教室', '本', 'ひま', '本' ...]
# 5895

合計5895個あることがわかりました。
MeCabと同じ個数なことがわかりました。

4. その他の品詞の解析

名詞以外のPart of Speechについても解析したところ、以下のよう結果になりました。

4-a. MeCab(その他の品詞)

Part of SpeechLengthHead Data
動詞3149[‘云わ’, ‘れ’, ‘流れ’, ‘云わ’, ‘れ’, ‘し’, ‘い’, ‘吊し’, ‘けぶっ’, ‘指し’ …]
形容詞479[‘白い’, ‘黒い’, ‘白く’, ‘ねむく’, ‘ない’, ‘早く’, ‘よく’, ‘よし’, ‘白い’, ‘いい’ …]
副詞867[‘そのまま’, ‘たしかに’, ‘まるで’, ‘なんだか’, ‘よく’, ‘もう’, ‘はっきり’, ‘すっと’, ‘もう’ …]
助詞6356[‘は’, ‘に’, ‘と’, ‘たり’, ‘の’, ‘と’, ‘たり’, ‘て’, ‘と’, ‘が’, ‘は’, ‘か’, ‘か’, ‘は’, ‘に’, ‘の’, ‘の’, ‘から’ …]
助動詞2572‘だ’, ‘た’, ‘だ’, ‘た’, ‘です’, ‘た’, ‘た’, ‘な’, ‘まし’, ‘た’, ‘まし’, ‘た’, ‘まし’, ‘た’, ‘う’, ‘まし’ …]

4-b. Janome(その他の品詞)

Part of SpeechLengthHead Data
動詞3148[‘云わ’, ‘れ’, ‘流れ’, ‘云わ’, ‘れ’, ‘し’, ‘い’, ‘吊し’, ‘けぶっ’, ‘指し’…]
形容詞479[‘白い’, ‘黒い’, ‘白く’, ‘ねむく’, ‘ない’, ‘早く’, ‘よく’, ‘よし’, ‘白い’, ‘いい’ …]
副詞867[‘そのまま’, ‘たしかに’, ‘まるで’, ‘なんだか’, ‘よく’, ‘もう’, ‘はっきり’, ‘すっと’, ‘もう’ …]
助詞6350[‘は’, ‘に’, ‘と’, ‘たり’, ‘の’, ‘と’, ‘たり’, ‘て’, ‘と’, ‘が’, ‘は’, ‘か’, ‘か’, ‘は’, ‘に’, ‘の’, ‘の’, ‘から’ …]
助動詞2572[‘だ’, ‘た’, ‘だ’, ‘た’, ‘です’, ‘た’, ‘た’, ‘な’, ‘まし’, ‘た’, ‘まし’, ‘た’, ‘まし’, ‘た’, ‘う’, ‘まし’ …]

様々な品詞で解析を行うと、少しずつ結果が違うことがわかりました。

この記事(「銀河鉄道の夜」を形態素解析してみた。)は、以上になります。
最後まで読んでいただき、ありがとうございました。

2 COMMENTS

Leave a Reply to 安岡孝一 Cancel reply

Your email address will not be published. Required fields are marked *

CAPTCHA