自然言語処理続編:NLTK によるテキストマイニング及び感情分析



 最初に、自然言語処理のPython パッケージであるNLTK(Natural Language Toolkit)の使用方法を取り上げます。もともと英文の言語処理をするために構築されていますので、英文の解析には問題なく活用できます。

  次に、NLTK を用いた日本語の自然言語処理をどのように取り扱うかについて、説明します。日本語の処理は英文の処理と異なり、ある意味でかなり厄介です。NLTK をそのまま活用することはできません。Pythonによる日本語自然言語処理のサイトでの説明に準拠して、 日本語コーパスの処理方法を紹介します。

 自然言語処理の代表的な課題としてよく知られている感情分析 (sentiment analysis) を映画のデータベースを用いて取り上げてみます。感情分析でよく知られている課題は、ある話題に関して書き手が表現した意見や感情に基づいて文章を分類することです。

 最後に、AllenNPLを取り上げます。最近登場したAllenNLPはPyTorch上に構築されていて、クラウドやラップトップ上で簡単に実行できるインフラを用意しており、ほぼすべてのNLP問題のモデルの設計と評価を容易にします。

関連記事
人工知能の基礎
deep learningの基礎
自然言語処理の基礎編
PythonのTutorials/Jupyter Notebook
GitHub repositories

Last updated: 2018.10.30



****************************************************
NLTKのインストールと基本的使用方法
****************************************************



 NLTK(Natural Language Toolkit)を用いて、自然言語処理の基本的な処理について説明します。Python3を使って、以下の書籍のWeb versionを参考に進めます。入門 自然言語処理 ( Natural Language Processing with Python by Steven Bird, Ewan Klein, Edward Loper)

 NLTKのインストールは簡単です。


$ pip install nltk


Anacondaのディレクトリ下に配置されます。

 NLTKでは、50を超える文書アーカイブ(英文Corpusの集合体:Corpora)が利用できます。日本語のCorpusについては次節で説明します。これらの文書Corporaは nltk.corpus パッケージを用いて読み込まれます。各コーパスは"corpus reader" のオブジェクトになっているので、対応するcorpus readerを使用します。CategorizedTaggedCorpusReader 、BracketParseCorpusReader 、WordListCorpusReader 等々があります。一般的な平文コーパスを読み込むとき使用されるreaderは PlaintextCorpusReader です。まず最初に、nltk に組み込まれたコーパスのダウンロードとその利用方法から始めます。

 NLTKに組み込まれた文書をダウンロードしましょう。Pythonを起動して、


import nltk
 
nltk.download()


と入力すると、ダウンロードできる文書の種類等が表示されます。bookを選択して、downloadsをクリックしてください。nltk_data という名称のディレクトリが作成され、その下にcorporaという文書のアーカイブができます。サイズは約1.5GBです。次に、その中の一部を見るために、以下のコマンドを入力してください。


from nltk.book import *
 

以下のように表示されます。

*** Introductory Examples for the NLTK Book *** Loading text1, ..., text9 and sent1, ..., sent9 Type the name of the text or sentence to view it. Type: 'texts()' or 'sents()' to list the materials. text1: Moby Dick by Herman Melville 1851 text2: Sense and Sensibility by Jane Austen 1811 text3: The Book of Genesis text4: Inaugural Address Corpus text5: Chat Corpus text6: Monty Python and the Holy Grail text7: Wall Street Journal text8: Personals Corpus text9: The Man Who Was Thursday by G . K . Chesterton 1908

9冊の書籍のテキストが読み込まれたことが確認できます。最初に配置されている書籍のタイトルと著者を調べたいときは、


text1.name #text2 は2番目の本
 

と打ちます。


'Moby Dick by Herman Melville 1851'


と表示されます。文脈を調べたいときに、類似の表現などを探すことができます。例えば、"Sense and Sensibility "by Jane Austen 1811 で 'affection' という言葉がどのように使用されているかを見たいとき、メソッド 'concordance' を用いて以下のように打ちます。


text2.concordance("affection")



Displaying 25 of 25 matches:
, however , and , as a mark of his affection for the three girls , he left them
t . It was very well known that no affection was ever supposed to exist between
deration of politeness or maternal affection on the side of the former , the tw
d the suspicion -- the hope of his affection for me may warrant , without impru
hich forbade the indulgence of his affection . She knew that his mother neither
rd she gave one with still greater affection . Though her late conversation wit
 can never hope to feel or inspire affection again , and if her home be uncomfo
m of the sense , elegance , mutual affection , and domestic comfort of the fami
, and which recommended him to her affection beyond every thing else . His soci
ween the parties might forward the affection of Mr . Willoughby , an equally st
 the most pointed assurance of her affection . Elinor could not be surprised at
he natural consequence of a strong affection in a young and ardent mind . This 
 opinion . But by an appeal to her affection for her mother , by representing t
 every alteration of a place which affection had established as perfect with hi
e will always have one claim of my affection , which no other can possibly shar
f the evening declared at once his affection and happiness . " Shall we see you
ause he took leave of us with less affection than his usual behaviour has shewn
ness ." " I want no proof of their affection ," said Elinor ; " but of their en
onths , without telling her of his affection ;-- that they should part without 
ould be the natural result of your affection for her . She used to be all unres
distinguished Elinor by no mark of affection . Marianne saw and listened with i
th no inclination for expense , no affection for strangers , no profession , an
till distinguished her by the same affection which once she had felt no doubt o
al of her confidence in Edward ' s affection , to the remembrance of every mark
 was made ? Had he never owned his affection to yourself ?" " Oh , no ; but if 

と表示されます。

 文書(text)の長さ(the length of a text from start to finish, in terms of the words and puctuation symbols)を知りたいときは、


len(text3)


のように入力します。Genesis(text3)は44,764個の 'words and punctuation symbols' から構成されていることが確認できます。これらのキャラクターの集合は "tokens" とも呼ばれます。文書(text)の語彙は"tokens"の集合となります。"tokens"の集合を表示したいときは、以下のように入力します。


text1.tokens



['[',
 'Moby',
 'Dick',
 'by',
 'Herman',
 'Melville',
 '1851',
 ']',
 'ETYMOLOGY',
 '.',
 '(',
 'Supplied',
 'by',
 'a',
 'Late',
 'Consumptive',
 'Usher',
 'to',
 'a',
 'Grammar', . . . .

と表示されます。当然のことながら、この集合には同一の単語や句読点がいくつも含まれています。異なる単語など(句読点を含む)のみからの集合に変換するためには、 'set(text1)' というコマンドを使います。単語などを整列した形で表示するために、以下のコマンドを入力します。


vocab = set(text1))
sorted(vocab)


. . .  
'?--"',
 "?--'",
 'A',
 'ABOUT',
 'ACCOUNT',
 'ADDITIONAL',
 'ADVANCING',
 'ADVENTURES',
 'AFFGHANISTAN',
 'AFRICA',
 'AFTER',
 'AGAINST',
 'AHAB',
 'ALFRED',
 'ALGERINE',
 'ALIVE',
 'ALL',
 'ALONE',
 'AM',
 'AMERICA', . . .

と表示されます。

 Pythonでは、文(text)はリスト配列で保存されます。以下の例を見てください。

sent1 =['I', 'see', 'him', 'now', '.' ]
sent2 = [ 'He', 'was', 'ever', 'dusting', 'his', 'old', 'lexicons', 'and', 'grammars', ',', 'with', 'a', 'queer',    
				  'handkerchief',' ,', 'mockingly', 'embellished', 'with', 'all', 'the', 'gay', 'flags', 'of', 'all', 'the', 
				  'known', 'nations', 'of', 'the', 'world', '.']
len(sent1)
len(snet2)

sent1 と sent2 が定義されています。それぞれの長さは5 、36 です。この二つの文を一つの文にします。


sentence = senet1 + sent2 
tokens = set(sentence)
vocab = sorted(tokens)
len(vocab)

 

新しく作成された文の構造がわかります。文は41の長さがありますが、vocabは30個の単語からなります。

 さて、文章中における単語の頻出度数などの統計を計算する方法について取り上げます。単語の頻出分布 (frequency distribution)の表示は以下のように行います。FreqDist(text2) で単語等の頻出分布を計算して、fdist2 に保存します。fdist2.keys()でdic_keysを返しています。頻出順に単語を上位から50個表示しています。


fdist2 = FreqDist(text2)
print (fdist2)

vocabulary2 = fdist2.keys()
fdist2.most_common(50)




[(',', 9397),
 ('to', 4063),
 ('.', 3975),
 ('the', 3861),
 ('of', 3565),
 ('and', 3350),
 ('her', 2436),
 ('a', 2043),
 ('I', 2004),
 ('in', 1904),
 ('was', 1846),
 ('it', 1568),
 ('"', 1506),
 (';', 1419),
 ('she', 1333),
 ('be', 1305),
 ('that', 1297),
 ('for', 1234),
 ('not', 1212),
 ('as', 1179),
 ('you', 1037), . . . 

となります。

 NLTKは Project Gutenberg electronic text archive で収集された文書の一部をnltk corpusとして利用できます。以下のコマンドを打ってください。

import nltk
nltk.corpus.gutenberg.fileids()


['austen-emma.txt',
 'austen-persuasion.txt',
 'austen-sense.txt',
 'bible-kjv.txt',
 'blake-poems.txt',
 'bryant-stories.txt',
 'burgess-busterbrown.txt',
 'carroll-alice.txt',
 'chesterton-ball.txt',
 'chesterton-brown.txt',
 'chesterton-thursday.txt',
 'edgeworth-parents.txt',
 'melville-moby_dick.txt',
 'milton-paradise.txt',
 'shakespeare-caesar.txt',
 'shakespeare-hamlet.txt',
 'shakespeare-macbeth.txt',
 'whitman-leaves.txt']


と表示されて、どのような文書が利用可能か分かります。最初の文書を読み込みましょう。

emma = nltk.corpus.gutenberg.words('austen-emma.txt')
len(emma)


このテキスト・コーパスの長さは192427です。上で行ったような text1.concordance()の操作を行うためには、

emma = nltk.Text(nltk.corpus.gutenberg.words('austen-emma.txt'))
emma.concordance("surprize")


とTextクラスに変換する必要があります。また、次のようにコーパス文書を読み込むこともできます。

from nltk.corpus import gutenberg
gutenberg.fileids()
['austen-emma.txt', 'austen-persuasion.txt', 'austen-sense.txt', ...]

emma = gutenberg.words('austen-emma.txt')

nltk.corpusを用いて読み込んだテキストは、words(), raw(), and sents()などのメソッドが有効になります。

 Gutenberg Project は50000冊を超える無料の電子版文書を収集しています。Gutenberg Projectで提供されてい文書はこのサイトからダウンロードできます。ダウンロードしたファイルが '2554-0.txt' のとき、以下のようなコマンドを使用して、言語処理をすることができます。2554-0.txt は、'Crime and Punishment', by Fyodor Dostoevsky です。rawはstr形式のリストなので、'\n'や'\r'などの記号が混ざっています。単語と句読点のみからなるトークン集合を作成するために、word_torkenize()を使います。詳しいNLTKのマニュアルはこのサイトにあります。

import nltk
from nltk import word_tokenize

path = "./corpora/2554-0.txt"
f = open(path, "rb").read()
raw = f.decode("utf-8")

tokens = word_tokenize(raw)
text = nltk.Text(tokens)
vocab = sorted(tokens)
list(set(vocab))

print("Size of text: ",len(text))
chars = sorted(list(set(text)))
print("Total chars :",len(chars))

text.concordance("affection")


以下の結果が表示されます。
Displaying 2 of 2 matches:
beziatnikov , who felt a return of affection for Pyotr Petrovitch . “ And , wha
sure that it was only your special affection for my poor husband that has made 

 corpus.reader を使用するときは、

import nltk
from nltk.text import Text

from nltk.corpus.reader.plaintext import PlaintextCorpusReader

corpus = PlaintextCorpusReader('./corpora/', '2554-0.txt')
crime = Text(w for w in corpus.words())

crime.concordance("affection")


のように活用します。

 NLTKパッケージで配布されているコーパス(トークン化されて、POSタグ付けられたコーパス)の一覧は以下の通りです。

Table 1.2:

Some of the Corpora and Corpus Samples Distributed with NLTK: For information about downloading and using them, please consult the NLTK website.

Corpus Compiler Contents
Brown Corpus Francis, Kucera 15 genres, 1.15M words, tagged, categorized
CESS Treebanks CLiC-UB 1M words, tagged and parsed (Catalan, Spanish)
Chat-80 Data Files Pereira & Warren World Geographic Database
CMU Pronouncing Dictionary CMU 127k entries
CoNLL 2000 Chunking Data CoNLL 270k words, tagged and chunked
CoNLL 2002 Named Entity CoNLL 700k words, pos- and named-entity-tagged (Dutch, Spanish)
CoNLL 2007 Dependency Treebanks (sel) CoNLL 150k words, dependency parsed (Basque, Catalan)
Dependency Treebank Narad Dependency parsed version of Penn Treebank sample
FrameNet Fillmore, Baker et al 10k word senses, 170k manually annotated sentences
Floresta Treebank Diana Santos et al 9k sentences, tagged and parsed (Portuguese)
Gazetteer Lists Various Lists of cities and countries
Genesis Corpus Misc web sources 6 texts, 200k words, 6 languages
Gutenberg (selections) Hart, Newby, et al 18 texts, 2M words
Inaugural Address Corpus CSpan US Presidential Inaugural Addresses (1789-present)
Indian POS-Tagged Corpus Kumaran et al 60k words, tagged (Bangla, Hindi, Marathi, Telugu)
MacMorpho Corpus NILC, USP, Brazil 1M words, tagged (Brazilian Portuguese)
Movie Reviews Pang, Lee 2k movie reviews with sentiment polarity classification
Names Corpus Kantrowitz, Ross 8k male and female names
NIST 1999 Info Extr (selections) Garofolo 63k words, newswire and named-entity SGML markup
Nombank Meyers 115k propositions, 1400 noun frames
NPS Chat Corpus Forsyth, Martell 10k IM chat posts, POS-tagged and dialogue-act tagged
Open Multilingual WordNet Bond et al 15 languages, aligned to English WordNet
PP Attachment Corpus Ratnaparkhi 28k prepositional phrases, tagged as noun or verb modifiers
Proposition Bank Palmer 113k propositions, 3300 verb frames
Question Classification Li, Roth 6k questions, categorized
Reuters Corpus Reuters 1.3M words, 10k news documents, categorized
Roget's Thesaurus Project Gutenberg 200k words, formatted text
RTE Textual Entailment Dagan et al 8k sentence pairs, categorized
SEMCOR Rus, Mihalcea 880k words, part-of-speech and sense tagged
Senseval 2 Corpus Pedersen 600k words, part-of-speech and sense tagged
SentiWordNet Esuli, Sebastiani sentiment scores for 145k WordNet synonym sets
Shakespeare texts (selections) Bosak 8 books in XML format
State of the Union Corpus CSPAN 485k words, formatted text
Stopwords Corpus Porter et al 2,400 stopwords for 11 languages
Swadesh Corpus Wiktionary comparative wordlists in 24 languages
Switchboard Corpus (selections) LDC 36 phonecalls, transcribed, parsed
Univ Decl of Human Rights United Nations 480k words, 300+ languages
Penn Treebank (selections) LDC 40k words, tagged and parsed
TIMIT Corpus (selections) NIST/LDC audio files and transcripts for 16 speakers
VerbNet 2.1 Palmer et al 5k verbs, hierarchically organized, linked to WordNet
Wordlist Corpus OpenOffice.org et al 960k words and 20k affixes for 8 languages
WordNet 3.0 (English) Miller, Fellbaum 145k synonym sets


 ここで、自然言語処理の前処理について説明します。トークン化は原文テキスト(strクラス)を言語的な各単位(文字や句読点など)に分解することです。NLTKでダウンロードされたコーパスはすでにトークン化されており、さらに、NLTKは'tokenizer'を組み込んでいます。しかし、MSWordの文書やHTMLをダウンロードした平文のテキストファイルではトークン化をする必要があります。これを前処理と言います。 最も単純なトークン化の方法は空白を目印にしてテキストを分解するトークン化です。(この手法は日本語では使えません。)原文テキストを空白で分割するためには、 nltk の正規化表現のモジュール re を読み込んで、 re.split() メソッドを使います。Python の正規表現モジュールのre.split(' ', raw)メソッドを利用すると、同じことができますが、改行記号'\n'を含んでしまいます。

import nltk
from nltk import re
#nltk.__version__

raw = """'When I'M a Duchess,' she said to herself, (not in a very hopeful tone
... though), 'I won't have any pepper in my kitchen AT ALL. Soup does very
... well without--Maybe it's always pepper that makes people hot-tempered,'..."""

re.split(r' ', raw)


rawは以下のようにトークン化されています。

["'When", "I'M", 'a', "Duchess,'", 'she', 'said', 'to', 'herself,', '(not', 'in', 'a', 'very', 'hopeful', 'tone\nthough),', "'I", "won't", 'have', 'any', 'pepper', 'in', 'my', 'kitchen', 'AT', 'ALL.', 'Soup', 'does', 'very\nwell', 'without--Maybe', "it's", 'always', 'pepper', 'that', 'makes', 'people', "hot-tempered,'..."]


この単純なトークン化では、改行記号'\n'を含めて、'(not' ,'I'M'、空白やタブ記号も残っています。望ましいトークン化をするために、いくつかのオプションがあります。
re.split(r'[ \t\n]+', raw)
re.split(r'\W+', raw)
re.findall(r'\w+|\S\w*', raw)
re.findall(r"\w+(?:[-']\w+)*|'|[-.(]+|\S\w*", raw)
などがあります。これらを組み合わせてトークン化を行います。NLTK's Regular Expression Tokenizerである nltk.regexp_tokenize() を使えば、これらと同様のトークン化を行うことができます。個人的には、nltk.regexp_tokenize()は使用しないほうが無難です。バージョンに依存してバグがあります。

 HTMLをダウンロードした平文のテキストファイルではトークン化をする必要がありますが、実は、このときにテキストをクレンジングするというステップが必要です。HTMLからダウンロードしたテキストファイルには、マークアップや文字以外の様々な記号が含まれています。これらを洗浄するために、word stemming という操作が行われます。この説明はここでは省略します。

***********************************************
日本語のNLTKを用いた自然言語処理
***********************************************



 NLTKでは主に英語を対象とした自然言語処理を取り扱っています。内容や考え方の多くは言語に依存しませんが、日本語を取り扱う場合にはそれに相応しい処理方法が必要です。NLTKで日本語を取り扱う時の注意点などは、Python による日本語自然言語処理のサイトを参照ください。ここでの説明もこれに準拠します。ただ、httpのリンク先が消失していたり、pythonコード等が古いpython2.xのバージョンになっていますので、注意が必要です。

 日本語の平文コーパス、すなわち、単語分割や品詞付与がなされていないコーパスをどのようにして読み込み、各種の言語処理を行うかについて解説します。平文コーパスを PlaintextCorpusReader を使って読み込んでみます。青空文庫 のテキストである宮沢賢治の 『銀河鉄道の夜』 を用いることにします。このテキストは事前に corpora/gingatetsudono_yoru.txt というテキストファイルで保存されているとします。ダウンロードした時点では、エンコーディングは shift-jis なので、テキストエディターなどで UTF-8 に変換してください。始まりの注釈の部分は削除してください。以下の宣言をしてください。

import nltk
from nltk.corpus.reader import *
from nltk.corpus.reader.util import *
from nltk.text import Text

jp_sent_tokenizer = nltk.RegexpTokenizer(u'[^ 「」!?。]*[!?。]')
jp_chartype_tokenizer = nltk.RegexpTokenizer(u'([ぁ-んー]+|[ァ-ンー]+|[\u4e00-\
... \u9FFF]+|[^ぁ-んァ-ンー\u4e00-\u9FFF]+)')


各行を各パラグラフとして読み込むためには、 PlaintextCorpusReader オブジェクト生成時にコンストラクタの para_block_reader 引数に read_line_block を渡す。また、「。」、「!」、「?」で終わる一連の文字列を文として認識し分割するために、RegexpTokenizer を定義します。文字種はそれぞれ Unicode の範囲(ひらがなについては [ぁ-ん] 、カタカナについては [ァ-ン] 、漢字については U+4E00 ~ U+9FFF )を指定することによって検出できる。これらを RegexpTokenizer に渡す正規表現として書いてあります。

 次に、PlaintextCorpusReader でコーパスを読み込みます。

ginga = PlaintextCorpusReader("./corpora/", r'gingatetsudono_yoru.txt',
                               encoding='utf-8',
                               para_block_reader=read_line_block,
                               sent_tokenizer=jp_sent_tokenizer,
                               word_tokenizer=jp_chartype_tokenizer)
							   
ginga.raw()

原文が表示されます。
'銀河鉄道の夜\u3000\n宮沢賢治\n\n「ではみなさんは、そういうふうに川だと言《い》われたり、乳《ちち》の流《なが》れたあとだと言《い》われたりしていた、このぼんやりと白いものがほんとうは何かご承知《しょうち》ですか」先生は、黒板《こくばん》につるした大きな黒い星座《せいざ》の図の、上から下へ白くけぶった銀河帯《ぎんがたい》のようなところを指《さ》しながら、みんなに問《と》いをかけました。\n\u3000カムパネルラが手をあげました。それから四、五人手をあげました。ジョバンニも手をあげようとして、急《いそ》いでそのままやめました。たしかにあれがみんな星だと、いつか雑誌《ざっし》で読んだのでしたが、このごろはジョバンニはまるで毎日教室でもねむく、本を読むひまも読む本もないので、なんだかどんなこともよくわからないという気持《きも》ちがするのでした。\n\u3000ところが先生は早くもそれを見つけたのでした。\n「ジョバンニさん。あなたはわかっているのでしょう」\n\u3000ジョバンニは勢《いきお》いよく立ちあがりましたが、立ってみるともうはっきりとそれを答えることができないのでした。ザネリが前の席《せき》からふりかえって、ジョバンニを見てくすっとわらいました。ジョバンニはもうどぎまぎしてまっ赤になってしまいました。先生がまた言《い》いました。\n「大きな望遠鏡《ぼうえんきょう》で銀河《ぎんが》をよっく調《しら》べると銀河《ぎんが》はだいたい何でしょう」\n\u3000やっぱり星だとジョバンニは思いましたが、こんどもすぐに答えることができませんでした。\n\u3000先生はしばらく困《こま》ったようすでしたが、眼《め》をカムパネルラの方へ向《む》けて、\n「ではカムパネルラさん」と名指《なざ》しました。\n\u3000するとあんなに元気に手をあげたカムパネルラが、やはりもじもじ立ち上がったままやはり答えができませんでした。\n\u3000先生は意外《いがい》なようにしばらくじっとカムパネルラを見ていましたが、急《いそ》いで、\n「では、よし」と言《い》いながら、自分で星図を指《さ》しました。\n「このぼんやりと白い銀河《ぎんが》を大きないい望遠鏡《ぼうえんきょう》で見ますと、もうたくさんの小さな星に見えるのです。ジョバンニさんそうでしょう」\n\u3000ジョバンニはまっ赤《か》になってうなずきました。けれどもいつかジョバンニの眼《め》のなかには涙《なみだ》がいっぱいになりました。そうだ僕《ぼく》は知っていたのだ、もちろんカムパネルラも知っている、それはいつかカムパネルラのお父さんの博士《はかせ》のうちでカムパネルラといっしょに読んだ雑誌《ざっし》のなかにあったのだ。それどこでなくカムパネルラは、その雑誌《ざっし》を読むと、すぐお父さんの書斎《しょさい》から巨《おお》きな本をもってきて、ぎんがというところをひろげ、まっ黒な頁《ページ》いっぱいに白に点々《てんてん》のある美《うつく》しい写真《しゃしん》を二人でいつまでも見たのでした。それをカムパネルラが忘《わす》れるはずもなかったのに、すぐに返事《へんじ》をしなかったのは、このごろぼくが、朝にも午後にも仕事《しごと》がつらく、学校に出てももうみんなともはきはき遊《あそ》ばず、カムパネルラともあんまり物を言《い》わないようになったので、カムパネルラがそれを知ってきのどくがってわざと返事《へんじ》をしなかったのだ、そう考えるとたまらないほど、じぶんもカムパネルラもあわれなような気がするのでした。\n\u3000先生はまた言《い》いました。\n . . .

 CorpusReader によって読み込んだ後は、英語のコーパスと同様に、平文を表示したり 、トークンを列挙したり 、 Text オブジェクトに変換した後コンコーダンスを表示したりといった処理が簡単にできます。

 Textクラスに変換します。

ginga_text = Text(w for w in ginga.words())
ginga_text.concordance('川') 


以下が表示されます。
Displaying 25 of 25 matches:
 川 だと 言 《 い 》 われたり 、 乳 《 ちち 》 の 流 《 なが 》 
先生 はまた 言 《 い 》 いました 。
 「 ですからもしもこの 天 の 川 がほんとうに 川 だと 考 えるなら 、 その 一 つ 一 つの 小 さな 
《 い 》 いました 。
 「 ですからもしもこの 天 の 川 がほんとうに 川 だと 考 えるなら 、 その 一 つ 一 つの 小 さな 星 はみんなその 
 だと 考 えるなら 、 その 一 つ 一 つの 小 さな 星 はみんなその 川 のそこの 砂 《 すな 》 や 砂利 《 じゃり 》 の 粒 《 つぶ 》 
 《 ちち 》 の 流 《 なが 》 れと 考 えるなら 、 もっと 天 の 川 とよく 似 《 に 》 ています 。 つまりその 星 はみな 、 乳 《 ち
あぶら 》 の 球 《 たま 》 にもあたるのです 。 そんなら 何 がその 川 の 水 にあたるかと 言 《 い 》 いますと 、 それは 真空 《 しんく
 う 》 かんでいるのです 。 つまりは 私 《 わたし 》 どもも 天 の 川 の 水 のなかに 棲 《 す 》 んでいるわけです 。 そしてその 天 の 
 の 水 のなかに 棲 《 す 》 んでいるわけです 。 そしてその 天 の 川 の 水 のなかから 四方 を 見 ると 、 ちょうど 水 が 深 いほど 青
 ると 、 ちょうど 水 が 深 いほど 青 く 見 えるように 、 天 の 川 の 底 《 そこ 》 の 深 《 ふか 》 く 遠 いところほど 星 がたく
の 凸 《 とつ 》 レンズ を 指 《 さ 》 しました 。
 「 天 の 川 の 形 はちょうどこんななのです 。 このいちいちの 光 るつぶがみんな 私
 。 それはこんやの 星祭 《 ほしまつ 》 りに 青 いあかりをこしらえて 川 へ 流 《 なが 》 す 烏瓜 《 からすうり 》 を 取 《 と 》 りに
くることもあるよ 。 今夜 はみんなで 烏瓜 《 からすうり 》 のあかりを 川 へながしに 行 くんだって 。 きっと 犬 もついて 行 くよ 」
 「 そ
うにゅう 》 をとりながら 見 てくるよ 」
 「 ああ 行 っておいで 。 川 へははいらないでね 」
 「 ああぼく 岸 《 きし 》 から 見 るだけな
っそう 勢 《 いきお 》 いよくそっちへ 歩 いて 行 きました 。
 「 川 へ 行 くの 」 ジョバンニ が 言 《 い 》 おうとして 、 少 しのど
を 越 《 こ 》 えると 、 にわかにがらんと 空 がひらけて 、 天 の 川 がしらしらと 南 から 北 へ 亙 《 わた 》 っているのが 見 え 、 
ば 》 だから 。 ぼく 、 白鳥 を 見 るなら 、 ほんとうにすきだ 。 川 の 遠 くを 飛 《 と 》 んでいたって 、 ぼくはきっと 見 える 」

 ていました 。 まったく 、 その 中 に 、 白 くあらわされた 天 の 川 の 左 の 岸 《 きし 》 に 沿 《 そ 》 って 一 | 条 《 じょ
 一生 けん 命 《 めい 》 延 《 の 》 びあがって 、 その 天 の 川 の 水 を 、 見 きわめようとしましたが 、 はじめはどうしてもそれが 、
 、 ぼくなんべんもどこかできいた 」
 「 ぼくだって 、 林 の 中 や 川 で 、 何 べんも 聞 いた 」
   ごとごとごとごと 、 その 小 さな
きれいな 汽車 は 、 そらのすすきの 風 にひるがえる 中 を 、 天 の 川 の 水 や 、 三角点 《 さんかくてん 》 の 青 じろい 微光 《 びこ
ねび 》 のように 思 われました 。
   それもほんのちょっとの 間 、 川 と 汽車 との 間 は 、 すすきの 列 《 れつ 》 でさえぎられ 、 白
、 まるで 運動場 《 うんどうじょう 》 のように 平 《 たい 》 らに 川 に 沿 《 そ 》 って 出 ているのでした 。 そこに 小 さな 五 、 
 》 でね 、 この 下 からは 貝 《 かい 》 がらも 出 る 。 いま 川 の 流 れているとこに 、 そっくり 塩水 《 しおみず 》 が 寄 《 よ
うすを 見 ていました 。 汽車 はもうだんだん 早 くなって 、 すすきと 川 と 、 かわるがわる 窓 《 まど 》 の 外 から 光 りました 。
  
な 、 雑作 《 ぞうさ 》 ない 。 さぎというものは 、 みんな 天 の 川 の 砂 《 すな 》 が 凝 《 かたま 》 って 、 ぼおっとできるもんで


単語「川」が使用されている箇所が25箇所で、その部分が表示されています。

最後に、MeCab を用いて、トークン化して、NLTKを使ってみましょう。まず、夏目漱石の 'kokoro.txt' の分かち書きファイル 'text' を作成します。

path = "./corpora/kokoro.txt"
bindata = open(path, "rb").read()
raw = bindata.decode("utf-8")
i
mport MeCab

tagger = MeCab.Tagger('-Owakati')
tagger.parse("")

text = tagger.parse(raw)


from nltk.text import Text で、Textクラスのインスタンスを作成して、NLTKのメソッド .concordance() が使用できるようにします。

from nltk.text import Text

souseki = Text(text)
print ("concordance of '私'---")
souseki.concordance('私') 


このコードを実行すると、以下の内容が出力されます。

concordance of '私'---
Displaying 25 of 25 matches:
 私   《   わ た く し   》   は   そ の   人   を  
 と   い う   よ り   も   、   そ の   方   が   私   に と っ て   自 然   だ   か ら   で   あ る  
 と っ て   自 然   だ   か ら   で   あ る   。   私   は   そ の   人   の   記 憶   を   呼 び   起
 も   使 う   気   に   な ら   な い   。       私   が   先 生   と   知 り 合 い   に   な っ   た
 か ま く ら   》   で   あ る   。   そ の   時   私   は   ま だ   若 々 し い   書 生   で   あ っ  
 が き   》   を   受 け 取 っ   た   の で   、   私   は   多 少   の   金   を   工 面   《   く め
 て   、   出 掛 け る   事   に   し   た   。   私   は   金   の   工 面   に   二   《   に   》
 ち   》   を   費 や し   た   。   と こ ろ が   私   が   鎌 倉   に   着 い   て   三   日   と  
   《   た   》   た   な い   う ち   に   、   私   を   呼 び 寄 せ   た   友 達   は   、   急  
   の   で   あ る   。   彼   は   電 報   を   私   に   見 せ   て   ど う   し よ   う   と   相
   し よ   う   と   相 談   を   し   た   。   私   に   は   ど う し て   い い   か   分 ら   な
   に   な っ   た   。   せ っ か く   来   た   私   は   一   人   取 り 残 さ   れ   た   。    
   も   よ い   と い う   境 遇   に   い   た   私   は   、   当 分   元   の   宿   に   留   《
 年   な   の で   、   生 活   の   程 度   は   私   と   そ う   変 り   も   し   な か っ   た  
   ひ と り   》   ぼ っ   ち   に   な っ   た   私   は   別 に   恰 好   《   か っ こ う   》   な
   地 位   を   占 め   て   い   た   。       私   は   毎 日   海   へ   は い り   に   出 掛 け
   た   人   を   一   人   も   も た   な い   私   も   、   こ う い う   賑   《   に ぎ   》  
   の   は   愉 快   で   あ っ   た   。       私   は   実 に   先 生   を   こ の   雑 沓   《  
   ゃ や   》   が   二   軒   あ っ   た   。   私   は   ふ と し た   機 会   《   は ず み   》  
   あ る   。   海 水   着   を   持 た   な い   私   に   も   持 物   を   盗 ま   れ る   恐 れ  
   れ る   恐 れ   は   あ っ   た   の で   、   私   は   海   へ   は   い る   た び   に   そ の
 # 「   二   」   は   中   見 出 し   ]       私   《   わ た く し   》   が   そ の   掛 茶 屋  
   と   す る   と こ ろ   で   あ っ   た   。   私   は   そ の   時   反 対   に   濡   《   ぬ  
   特 別   の   事 情   の   な い   限 り   、   私   は   つ い に   先 生   を   見 逃 し   た   か
 ど   浜 辺   が   混 雑   し   、   そ れ ほ ど   私   の   頭   が   放 漫   《   ほ う   ま   ん  


「私」という単語が使用されている箇所が25であることがわかります。このようにして、MeCab を用いて、日本語の平文コーパスのトークン化を行なった後に、NLTKを用いて様々な言語処理が可能になります。

**************************************************************
IMDBを用いた感情分析:Keras + Tensorflow編
**************************************************************


 Keras+Tensorflow を用いた意見マイニングの処理を取り上げます。データとして、IMDB(Internet Movie DataBase)を使用します。 IMDBは50000件の映画レビューのデータを収集したものです。データセットは、学習用に25000、テスト用に25000に2分割されています。映画に対する肯定的(positive)、否定的(negative)な意見(ラベル)の割合は、学習用、テスト用ともに50%ずつ分けられています。IMDBデータセットは、各レビューに対して、肯定/否定のラベル(教師)づけがされています。 

   KerasにはIMDBがダウンロードされている(.keras/datasets/imdb.npz にダウンロードされています)ので、Kearsを使用してIMDB映画レビューのデータセットを使って2クラス分類をしてみます。各サンプルは映画レビューの単語を表わす数字の配列です。各ラベルは 0 か 1 の整数値で、そこでは 0 は否定的なレビューで、1 は肯定的なレビューです。レビューのテキストに登場する語彙は単語(key)と整数(index)の配列リストとして、メソッド get_word_index() で読み込まれる(key; index)の集合で(.keras/datasets/imdb_word_index.jsonで)与えられています。例えば、
"fawn": 34701,
 "tsukino": 52006,
  "nunnery": 52007,
  "sonja": 16816,
  "vani": 63951,
  "woods": 1408,
 

の様になっています。各レビューの文章はこれらのindex数値の配列で記述されています。以下のようになっています。

映画レビューは単語数が異なる長さだけれども、ニューラルネットワークへの入力は同じ長さする必要があります。Kerasを用いたコードの説明は、このサイトにあります。また、以下のコードの多くは、Tensorflowのサイトでの説明に負っています。

 以下のコードを実行してください。

import numpy as np
from tensorflow import keras
import tensorflow as tf

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

print("Training entries: {}, labels: {}".format(len(train_data), len(train_labels)))
print("Training entries: {}, labels: {}".format(len(train_data), len(train_labels)))


Training entries: 25000, labels: 25000
Training entries: 25000, labels: 25000

データセットが学習用に25000、テスト用に25000に2分割されています。 以下のコードにあるimdb.get_word_index()は単語を整数値で置き換えた集合です。 辞書のメソッド dict によって、整数のインデックスのリストで表されているデータを文字列にデコードします。1番目のデータのテキストを表示してみます。引数 num_words=10000 は訓練データにおいて最も頻度高く出現する top 10,000 の単語を保持します。データのサイズを管理可能に保つために稀な単語は捨てられます。

# A dictionary mapping words to an integer index
word_index = imdb.get_word_index()

# The first indices are reserved
word_index = {k:(v+3) for k,v in word_index.items()} 
word_index[""] = 0
word_index[""] = 1
word_index[""] = 2  # unknown
word_index[""] = 3

reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])

def decode_review(text):
    return ' '.join([reverse_word_index.get(i, '?') for i in text])
	
decode_review(train_data[0])


映画に対する最初のレビュー意見が表示されます。

レビュー (整数の配列) はニューラルネットワークに供給される前に tensor に変換する必要があります。さらに、映画レビューは同じ長さでなければならないので、長さを標準化するために pad_sequences 関数を使用します。maxlen=256 で指定します。

train_data = keras.preprocessing.sequence.pad_sequences(train_data,
                                                        value=word_index[""],
                                                        padding='post',
                                                        maxlen=256)

test_data = keras.preprocessing.sequence.pad_sequences(test_data,
                                                       value=word_index[""],
                                                       padding='post',
                                                       maxlen=256)

len(train_data[0]), len(train_data[1])
print(train_data[0])


 モデルを構築します。以下のコードはTensorflowサイトで説明されているネットワークの構成です。

# input shape is the vocabulary count used for the movie reviews (10,000 words)
vocab_size = 10000

model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 16))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation=tf.nn.relu))
model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))

model.summary()

model.compile(optimizer=tf.train.AdamOptimizer(),
              loss='binary_crossentropy',
              metrics=['accuracy'])

x_val = train_data[:10000]
partial_x_train = train_data[10000:]

y_val = train_labels[:10000]
partial_y_train = train_labels[10000:]

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=40,
                    batch_size=512,
                    validation_data=(x_val, y_val),
                    verbose=1)
					
results = model.evaluate(test_data, test_labels)

print(results)

このコードを実行すると、以下の結果が表示されます。

学習の精度がtrainデータでは、1に近くなっているのに対し、validationデータでは約0.88ほどです。過学習を改善するため、隠れ層のユニット数や層の数を調整する必要があります。

 LSTMを用いて実行するコードは以下の通りです。

from __future__ import print_function

from keras.preprocessing import sequence
from keras.models import Sequential
from keras.layers import Dense, Embedding
from keras.layers import LSTM
from keras.datasets import imdb

max_features = 20000
# cut texts after this number of words (among top max_features most common words)
maxlen = 80
batch_size = 32

print('Loading data...')
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
print(len(x_train), 'train sequences')
print(len(x_test), 'test sequences')

print('Pad sequences (samples x time)')
x_train = sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = sequence.pad_sequences(x_test, maxlen=maxlen)
print('x_train shape:', x_train.shape)
print('x_test shape:', x_test.shape)

print('Build model...')
model = Sequential()
model.add(Embedding(max_features, 128))
model.add(LSTM(128, dropout=0.2, recurrent_dropout=0.2))
model.add(Dense(1, activation='sigmoid'))

# try using different optimizers and different optimizer configs
model.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

print('Train...')
model.fit(x_train, y_train,
          batch_size=batch_size,
          epochs=15,
          validation_data=(x_test, y_test))
score, acc = model.evaluate(x_test, y_test,
                            batch_size=batch_size)
print('Test score:', score)
print('Test accuracy:', acc)


このコードを実行すると、

エポックごとの結果が表示されます。このシンプルなLSTMネットワークでも学習するためにはそれなりの時間がかかります。15回のエポック学習に10分以上が必要です。学習の精度がtrainデータでは、1に近くなっているのに対し、validationデータでは0.80ほどで差があり過学習を示しています。単純にCNNを用いた方が優れた成績になっています。LSTMを含めたネットワーク・モデルでIMBDの分析をしているexamplesは、このgithubで提供されています。

************************************
AllenNPLによる文章分析
************************************


 AllenNLPはAllen Institute for Artificial Intelligenceによってワシントン大学などの研究者と緊密に連携して構築され、維持管理されています。 ベスト・オブ・フィールドの研究者とソフトウェアエンジニアの専属チームにより、AllenNLPプロジェクトは最先端のモデルに高品質のエンジニアリングを提供するユニークな立場にあります。

____ 準備中 ____

前のページに戻る
トップ・ページに行く