青空文庫に吉川英治本が公開されていたので言語分析を試してみました。バガボンドの原作になっている宮本武蔵と、昔読んでかすかに記憶がある三国志を分析します。分析結果だけを見たい方は、"試してみる"の項目まで飛ばしてください。
今回は、pythonを使わずにnode.jsで学習と学習データの活用をしています。
word2vec-nodeという素晴らしいパッケージがあったためです。
環境
- mac OSX Elcapitan 10.11.6
- Mecab
- mecab-ipadic-neologd
- node.js 6.1.0
- mecab-async
- word2vec-node
node.jsでword2vecのデータを作ります。
学習データ作成
データ入手
ここから青空文庫の吉川英治本を持ってきて解析しています。更新が止まっているようで新書太閤記などが無いですが、三国志と宮本武蔵は入っているので、これを利用します。
データ合成
ダウンロードしてきたテキストファイルは、書籍ごとに分割されているので、Automatorを使って結合します。
- automator起動
- ワークフロー選択
- 下記の通り、設定する
- 指定されたFinder項目を取得、ダウンロードしたフォルダをドラッグドロップ
- フォルダの内容を取得、サブフォルダを取得のチェックボックスをonにしておく
- Finder項目にフィルタを適用、.txtを含むにする(画像ファイルを弾くため)
- テキストを結合
- 新規テキストファイル、必ずUTF-8にしておく事
右上の実行ボタンを押すと結合したファイルが出力されます。
分かち書きファイル作成
まずは、mecabをインストールします。
$ brew install mecab
$ brew install mecab-ipadic
次に、mecab-ipadic-neologdをインストールします。これは無くてもできるのですが、分かち書きの精度が圧倒的に上がるため導入します。
git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
cd mecab-ipadic-neologd
./bin/install-mecab-ipadic-neologd -n
インストール先を確認します。
echo `mecab-config --dicdir`"/mecab-ipadic-neologd"
> xxx
xxxが、
mecabの -O wakati を利用します。辞書データには、mecab-ipadic-neologdを利用します。
mecab -d mecab-ipadic-neologdのパス -O wakati 結合したファイル > 分かち書きファイル
私の環境では、下記でした。
mecab -d /usr/local/mecab/lib/mecab/dic/mecab-ipadic-neologd -O wakati /Users/yamamoto/Documents/webAppTest/t-SNE/takezou.txt > /Users/yamamoto/Documents/webAppTest/t-SNE/takezou-wakati.txt
学習データ作成
word2vec-nodeを利用します。適当なディレクトリを用意して移動した後、下記コマンドでインストール。
npm install word2vec
分かち書きファイルも同一フォルダに移動しておいて、下記srcを実行します。
ファイル名は適当に変更してください。
var w2v = require( 'word2vec' );
w2v.word2vec(__dirname + '/takezou-wakati.txt', __dirname + '/takezou-vector(200).txt',{
cbow: 1,
size: 200,
window: 8,
negative: 25,
hs: 0,
sample: 1e-4,
threads: 20,
iter: 30,
minCount: 2
});
実行
node app.js
これで、学習が完了し言語ベクトルデータのファイルが出力されます。
試してみる
学習したデータを試しに使ってみます。
similar関数
武蔵に似た意味の単語ベスト10を出力してみます。
w2v.loadModel( __dirname + '/takezou-vector(200).txt', function( err, model )
{
console.log(model.mostSimilar( '武蔵' , 10 ));
});
[ { word: '城太郎', dist: 0.6384114109192522 },
{ word: '小次郎', dist: 0.6315866413569574 },
{ word: '又八', dist: 0.621169999380997 },
{ word: '伊織', dist: 0.6151930209558407 },
{ word: 'お通', dist: 0.5956252925645742 },
{ word: '沢庵', dist: 0.5684391854419929 },
{ word: '清十郎', dist: 0.5529844648692981 },
{ word: '佐々木小次郎', dist: 0.48390454429300517 },
{ word: '巌流', dist: 0.4704464935360734 },
{ word: 'ばば', dist: 0.45453372671103864 } ]
武蔵の弟子である'城太郎'と'伊織'。ライバル'小次郎'と'清十郎'。親友(?)'又八'。などがランクイン。
個人的には、ある程度は納得感のある結果です。
次に、'玄徳'に似た単語を探します。
w2v.loadModel( __dirname + '/takezou-vector(200).txt', function( err, model )
{
console.log(model.mostSimilar( '玄徳' , 10 ));
});
[ { word: '曹操', dist: 0.7180967035567127 },
{ word: '孔明', dist: 0.6741360382573516 },
{ word: '関羽', dist: 0.6712023078709141 },
{ word: '張飛', dist: 0.5336668670296124 },
{ word: '呂布', dist: 0.5218688172864446 },
{ word: '劉璋', dist: 0.5122554589393479 },
{ word: '袁紹', dist: 0.5117007226097955 },
{ word: '劉表', dist: 0.5091692433292307 },
{ word: '魯粛', dist: 0.506948191658782 },
{ word: '呉', dist: 0.5030778206518065 } ]
敵軍の長'曹操'。自軍の軍師'孔明'。兄弟の契りを結んだ'関羽'と'張飛'が並びました。こちらもある程度、納得感のある結果です。('呉'が'蜀'だったらなお良かったのですが。。。。)
バガボンドでめちゃめちゃかっこよかった宝蔵院の'胤舜'。
w2v.loadModel( __dirname + '/takezou-vector(200).txt', function( err, model )
{
console.log(model.mostSimilar( '胤舜' , 10 ));
});
```
```js
[ { word: 'いんしゅ', dist: 0.6112752991126976 },
{ word: '十輪院', dist: 0.5822732133883078 },
{ word: '宝蔵院', dist: 0.5425207838358865 },
{ word: '南光坊', dist: 0.530375475403251 },
{ word: '権律師', dist: 0.5227437228724651 },
{ word: '高弟', dist: 0.44889061561825544 },
{ word: '門下', dist: 0.43473792895296903 },
{ word: '槍術', dist: 0.42852195795692083 },
{ word: '日蓮寺', dist: 0.4249810036046633 },
{ word: '胤栄', dist: 0.41261372013763375 } ]
```
一位がいまいち(分かち書きの失敗。おそらく"いんしゅん")ですが、2位以下は、納得感があります。
### analogy関数
言葉の計算ができます。
例えば、下記だと'玄徳'における'孔明'は、'曹操'における誰?となります。
```js
w2v.loadModel( __dirname + '/takazou-vector(200).txt', function( err, model )
{
console.log(model.analogy( '曹操', [ '玄徳', '孔明' ], 10 ));
});
```
```js
[ { word: '周瑜', dist: 0.5658148730176463 },
{ word: '仲達', dist: 0.5233077079061745 },
{ word: '魏', dist: 0.5040343388106403 },
{ word: '司馬懿', dist: 0.49498823518082025 },
{ word: '蜀', dist: 0.48663878357812884 },
{ word: '蜀軍', dist: 0.4817908410047144 },
{ word: '曹休', dist: 0.4614343439781039 },
{ word: '予', dist: 0.45278450513626844 },
{ word: '曹真', dist: 0.44370164491478875 },
{ word: '関羽', dist: 0.4425043447694201 } ]
```
周瑜は呉のはずです。司馬懿仲達が高いのは納得です。でも郭嘉がいないです。
呉でも試します。
```js
w2v.loadModel( __dirname + '/yoshikawa-vector(200).txt', function( err, model )
{
console.log(model.analogy( '孫策', [ '玄徳', '孔明' ], 10 ));
});
```
```js
[ { word: '周瑜', dist: 0.4707473119637872 },
{ word: '孫堅', dist: 0.41475202996970123 },
{ word: '孫権', dist: 0.3969991965730858 },
{ word: '王朗', dist: 0.3741032601775511 },
{ word: '曹操', dist: 0.35943298438182947 },
{ word: '仲達', dist: 0.35356355886289464 },
{ word: '張昭', dist: 0.35274506463254507 },
{ word: '陸遜', dist: 0.34751149697338407 },
{ word: '太史慈', dist: 0.34397296366584984 },
{ word: '孟獲', dist: 0.33982212780928855 } ]
```
こちらも周瑜が一位ですが納得の一位です。孫堅(父)、孫権(弟)も上位にいます。
間に曹操軍の人たちが入りますが、陸遜や太史慈もいます、
'吉岡清十郎'にとっての'朱実'は'武蔵'の誰?
```js
w2v.loadModel( __dirname + '/takezou-vector(200).txt', function( err, model )
{
console.log(model.analogy( '武蔵', [ '吉岡清十郎', '朱実' ], 10 ));
});
```
```js
[ { word: '又八', dist: 0.5868350583261267 },
{ word: 'お通', dist: 0.567379081474738 },
{ word: '城太郎', dist: 0.5577076254503771 },
{ word: '彼女', dist: 0.5079841819169051 },
{ word: '伊織', dist: 0.4724272440990171 },
{ word: '小次郎', dist: 0.437837237570175 },
{ word: '沢庵', dist: 0.4027557771297083 },
{ word: 'お松', dist: 0.37972070074830905 },
{ word: '婆', dist: 0.3729988845527649 },
{ word: '老婆', dist: 0.3723182597502404 } ]
```
又八ー!じゃまだー。一位にお通がきてほしかったです。。
これだけでいくらでも遊べそうですが、紹介は一旦ここまでで次へ進みます。
# 感情分析
プルチックの感情の輪を利用して分析します。
感情分類は他にもいろいろあったのですが、自分の感覚に近かったこれを選択しました。
これは、喜び - 悲しみ , 驚き - 予測 , 信頼 - 嫌悪 , 怒り - 不安 の4軸8感情を持った輪で、
これら感情の強弱や組み合わせで、多くの感情を表現しているものになります。
例えば、信頼を弱くすると受け入れる気持、強くすると憧れとなります。
喜び + 信頼 = 愛 になります。詳しくは下図。(wikipediaより)
![Plutchik-wheel.svg.png](https://qiita-image-store.s3.amazonaws.com/0/48091/1a50d749-091c-6056-8e45-26d8698fec1b.png)
word2vecは対義語が苦手です [^1]。その為、今回は、対象の語句が8感情それぞれにどの程度近いかを計測します(遠くても対義語ではない)。
## 感情辞書作成
辞書は、text形式で、
```
語句 最大の感情 喜 信頼 不安 驚き 悲しみ 嫌悪 怒り 予測
```
の順に並ぶようにしました。最大の感情はそれぞれ下記の通り数値化しています。
```
喜:0,信頼:1,不安:2,驚き:3,悲しみ:4,嫌悪:5,怒り:6,予測:7
```
ここから、node-mecabも利用します。
```
npm install mecab-async
```
takezou-vector(200).txtは、先頭に語句があり、その後ろに、スペース区切りで、ベクトルデータが並ぶフォーマットだった為、
```js
var word = line.split(' ')[0];
```
とし、全ての単語をmecabで形態素解析します。
```js
mecab.parse(word, function(err, result)
```
結果は、result内に入っており、[0]に分割されたword[^2]、[1]に品詞が入ってきます。
感情語との距離の近さは、word2vec-nodeの
```js
similarity(word1,word2)
```
を利用します。word1とword2の類似度を図り、戻り値は、近ければ1に近づき、遠ければ、-1に近づきます。
計測する感情語は"名詞","形容詞","動詞"に絞りました[^3]。
コード全文です。
```js:app.js
var MeCab = new require('mecab-async')
,mecab = new MeCab();
var w2v = require( 'word2vec' );
var fs = require('fs');
var filePath = __dirname + '/takezou-vector(200).txt';
var rs = fs.createReadStream(filePath);
var readline = require('readline');
MeCab.command = "mecab -d /usr/local/mecab/lib/mecab/dic/mecab-ipadic-neologd";
w2v.loadModel( filePath, function( err, model )
{
var rl = readline.createInterface(rs, {});
var txt = [];
rl.on('line', function(line) {
var word = line.split(' ')[0];
createData(word,model);
});
});
function createData(word,model)
{
mecab.parse(word, function(err, result) {
if (err) { throw err; }
var res = result[0];
if(res == null)
{
return;
}
var em = [];
// 単語を絞る。
if(res[1] == "名詞" || res[1] == "形容詞" || res[1]=="動詞" )
{
em.push(model.similarity("喜び",word));
em.push(model.similarity("信頼",word));
em.push(model.similarity("不安",word));
em.push(model.similarity("驚き",word));
em.push(model.similarity("悲しみ",word));
em.push(model.similarity("嫌悪",word));
em.push(model.similarity("怒り",word));
em.push(model.similarity("予測",word));
var str = word;
var maxval = Math.max.apply(null,em);
str = str + " " + em.indexOf(Math.max.apply(null,em));
for(var i = 0 ; i < em.length ; i ++)
{
str = str + " " + em[i];
}
fs.appendFile('emotion-vec.txt', str+"\n" ,'utf8', function (err) {
console.log(word);
});
}
});
}
```
実行すると、emotion-vec.txtが作成されます。今回は、これを感情辞書として利用します。
## 特徴的な感情語
それぞれの感情が強く出たものを幾つかご紹介します。
### 喜び
<img width="718" alt="喜び.png" src="https://qiita-image-store.s3.amazonaws.com/0/48091/1edcddc2-4503-31de-5110-d0a6beac6975.png">
愛、満ち足り、錯誤。プルチックの感情の輪によると、喜び+心理=愛でそれを表せている。満ち足りるも喜びに近い感覚です。錯誤は、人間の感覚では喜びと近くありません。
### 信頼
<img width="906" alt="信頼.png" src="https://qiita-image-store.s3.amazonaws.com/0/48091/127c10c3-5d8b-85a5-48e0-0831a30ad1dc.png">
敬愛、期待、自信。愛よりも信頼の要素が強いと敬愛だそうです。期待、自信も信頼に近い感情だと思います。
### 不安
<img width="901" alt="不安.png" src="https://qiita-image-store.s3.amazonaws.com/0/48091/b0cfd0e0-1b67-5c4e-cfe3-7efddeec7ea2.png">
恐怖、疑心暗鬼、危惧。プルチックの感情の輪によると不安を強くすると恐怖になります。
疑心暗鬼も危惧も不安から生まれる感情だと思います。
### 驚き
<img width="903" alt="驚き.png" src="https://qiita-image-store.s3.amazonaws.com/0/48091/2602b31b-e7a9-ec0c-c915-4da4b9719a74.png">
呆れ、立腹、戦慄。驚きと怒りで呆れ。さらに怒りが強くなると立腹。立腹は驚きより怒りが強いと思うのですが。。。驚きと不安と怒りで戦慄。戦慄は不安の方が強いような。。。
### 悲しみ
<img width="863" alt="悲しみ.png" src="https://qiita-image-store.s3.amazonaws.com/0/48091/56a6c08a-9137-f4ca-de33-77267a4d207e.png">
感傷、冥福、慟哭。感傷とは、悲しみと不安の組み合わせらしい。分かるような。。。どうだろう?冥福とは、本来悲しい意味ではないと思いますが、実際には悲しいときに祈るものでしょう。慟哭とは、悲しみのあまり声をあげて泣くこと。納得です。
### 嫌悪
<img width="717" alt="嫌悪.png" src="https://qiita-image-store.s3.amazonaws.com/0/48091/bb494b43-2ac1-3589-cbd4-ca00dba480cc.png">
残酷、残忍、吸引。残酷は、嫌悪と悲しみ。残忍は、そこから悲しみが下がった感情。それっぽいです。吸引は、人間の感覚では嫌悪と意味が近いとは思えません。
### 怒り
<img width="908" alt="怒り.png" src="https://qiita-image-store.s3.amazonaws.com/0/48091/b269877a-bd0b-953e-6d14-ddaa9482ffbf.png">
敵愾心、憤怒、遺恨。敵愾心は、怒り+信頼で生まれる?憤怒は、強い怒り。遺恨も怒りから生まれるのだろうか。。。そんな気はします。
### 予測
<img width="908" alt="予測.png" src="https://qiita-image-store.s3.amazonaws.com/0/48091/ca1dac08-c68a-d012-7861-aa48718374df.png">
予想、処理、断定。全て納得感はあるのですが、他の感情と組み合わさったりしませんでした。言葉的にも感情っぽくないので翻訳失敗したかもです。
例外はあれど、感情もかなり人間の感覚に近いデータが取得できました。予測は翻訳失敗と思われます。今後の課題です。
# 吉川英治本の感情分析をしてみた
## 宮本武蔵より
<img width="309" alt="宮本武蔵.png" src="https://qiita-image-store.s3.amazonaws.com/0/48091/cb8b2a6e-a6cc-1605-1910-04feb19c9d52.png">
登場回数の多い3名をピックアップしました。感情語ではないので、数値自体は低いです。武蔵は、嫌悪が高め、又八が不安と嫌悪、小次郎が驚きと嫌悪が高く出ました。
分かるような分からないような。あまり意味のない分析かもしれません。
## 三国志より
<img width="309" alt="三国志.png" src="https://qiita-image-store.s3.amazonaws.com/0/48091/88a27393-6b5b-3bec-25d2-a146de0d9c80.png">
登場回数の多い3名をピックアップしました。感情語ではないので、数値自体は低いです。
劉備は、嫌悪と予測以外は高めで特に驚きと怒りが高いです。曹操は、驚きと怒りが高いです。孔明は、全体的に低いです。
やはりあまり意味がないかもしれません。なんとなく、孔明が、感情的ではない点は、感覚と会っています。
宮本武蔵の面々が、嫌悪の感情が強めに出たのに対し、三国志の面々は、怒りの感情が強めに出ました。書籍の特徴だったりするかもしれません。
## 宮本武蔵を読ませてみた
行ごとにそれぞれの単語の感情語との近さの平均値を算出し、特に高くなる部分を探す形で分析してみました。高くなった部分が小説中のどういった場面であるかを比較してみます。
※ ネタバレ注意
ここからは、宮本武蔵の本の内容と密接に絡みます。ネタバレが含まれますので、これから本を読む方はご注意ください。
### 地の巻
<img width="423" alt="地の巻.png" src="https://qiita-image-store.s3.amazonaws.com/0/48091/c2d59425-1727-43b0-1706-1c59be841791.png">
- 嫌悪
武蔵がお甲の家に住まわせてもらっている中での出来事。お甲が武蔵を誘惑するが、断られ「意地悪」というシーンです。このシーンが嫌悪の感情が強いと分析されました。
- 信頼
武蔵の姉のお吟が、嫁入り話を断るセリフで、『弟の武蔵が、もうすこし大人になるまでは、わたくしが、母となっていてやりとうごさいますから——』という言葉。このシーンが信頼の感情が強いと分析されました。
- 驚き
お杉が、武蔵をかくまうふりをして闇討ちしようとしている事に武蔵が気づいた場面。総身の毛穴をよだてて、「あっ、騙《だま》されたっ」と、叫んだ。場面が驚きの感情が強いと分析されました。
- 喜び
武蔵を捉えようとしている村人に沢庵が話しかけるシーン。「まず、坐るがいい」と村人に救いを与えた場面が、喜びの感情が強いと分析されました。
- 不安
沢庵が村人とお通と沢庵の二人なら3日で武蔵を捕まえられると約束してしまい、失敗すると考えるお通が不安になっている場面が、不安の感情が強いと分析されました。
- 怒り
武蔵を捉える事に成功した沢庵の武蔵に対する処遇に納得のいかないお通が、沢庵に「女などが知ったことか。黙っておれっ」と言われた場面が、怒りの感情が強いと分析されました。
### 水の巻
<img width="416" alt="水の巻.png" src="https://qiita-image-store.s3.amazonaws.com/0/48091/34a03649-8fe7-1c86-0d20-72e7426b4b83.png">
- 驚き
私の感覚と乖離した結果です。
朱美が清十郎に三味線を歌って聞かせる場面。過去を楽しむように唄うシーンで驚きの感情が強いと分析されました。前後の文脈も読んだのですが、どの言葉で驚きと判断したか、あまりよくわかりませんでした。
- 喜びと悲しみ
武蔵が仏に姉についてと自分について祈ります。姉の息災を祈るところで喜びの感情が高くなり、武蔵自身に苦痛を与えるよう祈る場面で、悲しみの感情が高くなりました。
- 怒り
城太郎が朱美に武蔵の事を話すと笑われてしまった、それに城太郎が怒っている場面で、怒りの感情が高くなりました。実際には、城太郎はそのコミュニケーションを楽しんでいる節があるので、行間までは読めていないようです。
- 嫌悪
庄田が城太郎が剣を持っているのを見て「師匠があるのか」と尋ねた場面が、嫌悪の感情が強いと分析されました。'ある'という単語が嫌悪の感情に近いと学習されたためこのような結果に成ったようです。
- 不安と信頼
城太郎が柳生で飼っていた太郎という犬を殺してしまう。その直前、庄田喜左衛門が直感めいた何かで不安を覚えている事が描写されている場面で、不安と信頼の感情が高いと分析されました。
### まとめ
今回は、行ごとにそれぞれの単語の感情語との近さの平均値を算出し、特に高くなる部分を探す形で分析してみました。誰かが話した行などは、一単語のみで一行が終わったりするので、単語数を決めて区切って平均値を出すなど、分析方法にはもう一工夫入れてもよかったかもしれないです。また、"ある"という単語が嫌悪と近いなど、ノイズになったデータもあったため、辞書データももう一工夫入れて、精度をあげれるとよかったです。ただ、今回の雑な分析でも感情の動きは、ある程度読み取れたように思います。
# おまけ
## 感情の特徴語をPCAでマッピングしてみた
![pca-has-label.png](https://qiita-image-store.s3.amazonaws.com/0/48091/40215809-8352-8bd1-706d-97f4a4c2b3ca.png)
点の色を、プルチックの感情の輪に合わせています。上の方に喜び(黄色)、右の方に予測(オレンジ)の感情が寄りました。他の言葉も似た者同士が比較的近くマッピングされており、word2vecをPCAで表示すると分析しやすいかもしれません。世の中的には、t-sneでマッピングする事が推奨されている様子ですが、私の環境では、どちらでも似たような結果となりました。
[^1]:
[リンク先](https://deepage.net/bigdata/machine_learning/2016/09/02/word2vec_power_of_word_vector.html)のword2vec弱点を参照,word2vecは対義語に弱い
[^2]: 今回は単語のみを入力する為、分解はされない
[^3]: それ以外の品詞はノイズになると予想された為