日本語Wikipediaデータをword2vecで類語検索する

去年、これをやっていたのだが、色々あって他のことに関心が向かったために、使いきれずにおいていたが、もう一度やっておく。類語を抽出できるようになるまでのプロセスをメモがわりに書いておく。
前半は、http://ankaji92.hatenablog.com/entry/2016/11/27/212507を参考にさせていただいた。

まず、適当なフォルダを決めて、wikiの全文を取ってくる。

$ curl https://dumps.wikimedia.org/jawiki/latest/jawiki-latest-pages-articles.xml.bz2 -o jawiki-latest-pages-articles.xml.bz2

これを使えるように変換してまとめる。

$ git clone https://github.com/attardi/wikiextractor
$ python3 wikiextractor/WikiExtractor.py jawiki-latest-pages-articles.xml.bz2

一カテゴリずつやっていくが、辛抱強く待つ。一つのファイルにまとめる。

$ cat text/*/* > jawiki.txt

分かち書きアプリのインストール。

$ brew install mecab
$ mecab -Owakati jawiki.txt -o data.txt

前は、ここでrubyを使う方法を用いた記憶があるが、使わなくていいので、ここまでは、先の引用元にあるこの方法がいいと思う。次に、word2vecのインストールである。これについては、以下のサイトにある後半部分がc言語でやるとき便利だった。

$ git clone https://github.com/svn2github/word2vec.git
$ cd word2vec
$make

macの場合、malloc.hが無いと、叱られるので、

  • compute-accuracy.c
  • distance.c
  • word-analogy.c

について、sodlib.hに変更する。そして、再度 make。

./word2vec -train data.txt -output jawiki_wakati.bin -size 200 -window 5 -sample 1e-3 -negative 5 -hs 0 -binary 1

data.txtは、先ほど作成したパスを指定し、word2vecも作成したパスを指定して実行する。オプションについては、適宜、webを参考にする。./distanceを、今作成したjawiki_wakati.binを指定して起動する。そして、単語を入力すると、類語のリストを出してくる。

./distance jawiki_wakati.bin 
Enter word or sentence (EXIT to break): コンビニ
Word: コンビニ  Position in vocabulary: 10735
           Word                         Cosine distance
-------------------------------------------------------------
          コンビニエンスストア		0.809326
          デイリーヤマザキ			0.685043
          店舗				0.652178
          スーパーマーケット		0.636046
          ドラッグストア			0.628818
          ミニストップ			0.620285
          キオスク			0.604373
          物販				0.603765
          ブックオフ			0.599682
          直営店				0.597540
          ファミリーレストラン		0.597328
          ローソン			0.594401
          ファミリーマート			0.589032
          売店				0.588488
          ホームセンター			0.587499
          キヨスク			0.587256
          惣菜				0.574461
          出店				0.572560
          セイコーマート			0.568219
          量販				0.559042
          店				0.558693
          飲食				0.557627
          セルフサービス			0.555324
          ファストフード			0.554968
          NEWDAYS			0.554501
          以下まだつづく・・・・・・

このdistanceのjava版を作らなければならない。面倒。

phpMyAdminをMacで動かす

以前も、mysql (実質的に同じ mariadb)を動かすとき、phpMyAdminを使っていた。便利だった。今回また、色々データーベースをいじるのに、使おうと思ったが、macを新しくしているせいか動かなかった。
apacheは、標準のものでいいはずだ。ただ、設定ファイルなどがどこにあるかを確認しておかなければならない。

/etc/apache2/httpd.conf

である。
php も、動いていた。標準なのか、何気にインストールしたのかは記憶が定かではない。

homebrewで、phpmyadminをインストール。

brew install phpmyadmin

と最後に、https.confに付け加える設定が出ているので、そのまま貼り付ける。また、https.confの中のモジュールの追加起動が必要だった。

LoadModule php5_module libexec/apache2/libphp7.so
LoadModule userdir_module libexec/apache2/mod_userdir.so
Include /private/etc/apache2/extra/httpd-userdir.conf

だったと思う。これでapacheを再起動する。(もう一つ何か必要だった気がするが・・・・・・)
さらに、正常起動できると

The $cfg['TempDir'] (./tmp/) is not accessible. phpMyAdmin is not able to cache templates and will be slow because of this.

というような、警告が出ていて、遅くなるっていうものだから、対応せざるを得ない。

これは、/usr/local/share/phpmyadminの下に、tmpファイルを作成し、所有者をwwwに変更すると消える。

QRコードの読み取り

ロボットが案内ができるように、あるいは位置を把握するために、QRコードを読み取る能力をもたせたいと考えた。

Raspberrypiのカメラ、それで撮ったのをgoogleのzxingというコード認識ライブラリを経由してコードを認識させる、というものだ。

このライブラリを使えば、認識とコード生成が極めて簡単に行える。以下のようなところを参考にさせていただいた。
https://qiita.com/tool-taro/items/1923f948a1908255d5df
https://qiita.com/yoshi389111/items/0fee2be43e5135b1bb15

これで、単にQRコードがきちっと入った画像だけではなく、ゴタゴタした背景の中にあってもそれを認識できる。
例えば

こんな画像でも認識できる。あとは、頭の部分についている、このカメラの動かし、このQRコードを認識させることができればいい。

ロボットの耳

両耳をつけた。ちょっと見にくいか。

小さいが、SPH0645LM4HのI2Cマイクである。これで録音したデータを、一サンプルずつずらしながら、音の到着時間さを測る。そうすると、およその方向がわかるというわけである。これについては、以下の金井宏一郎氏の学士論文「音の到着時間さを用いた音源空間推定」(2008年、高知工科大学)を参考にさせていただいた。
http://www.kochi-tech.ac.jp/library/ron/2007/2007info/1080351.pdf
とても分かり易かった。
頭は、25センチなので、最大到着時間さは25センチ。音速を使って計算すると、0.6ミリ秒くらいである。
一方のマイクに近づいて話しかけると、こんなデータが取れる。

このデータ(実際のデータは、これよりもはるかにでかい、以下参照)を、一サンプルずつずらしながら相関係数を取るとこんな風になる。(縦軸は相関係数、横軸はシフトサンプル数)
厳密には、26サンプル、プラス側にずらすと、相関係数が最大になる。計算結果は次のようである。
データ総数:132300
一サンプル辺りの時間:0.02268 ミリ秒
最大相関係数: 0.71358
最大相関係数を出すサンプルシフト: 26
距離差:20.06245 cm
三秒間、片方に偏りながら話したので、毎秒44,100サンプル取れるので、データ総数は上記のようになる。その逆数が一サンプル辺りの時間である。サンプルを26だけシフトした時に最大相関係数、0.71358を得る。悪くない。これを距離にすると、25センチより少し小さい20センチになる。方向的に、両マイクが完全に直線になる方向から、少しずれていることになる。
今の所、最大相関係数が0.5以上の時に、ロボットが反応する(振り向く)ようにしてみようかと思っている。

SPH0645LM4Hの録音フォーマットの問題

SPH0645LM4Hの二つのマイクからステレオ録音ができるようになった。再生すればきちんと音を再生できる。ただ、ここで問題が発生した。SPH0645LM4Hは、32ビットSampleSizeInBitsでしか録音できないのだ。arecordのコマンドラインで行くと、
arecord -D dmic_sv -c2 -r 44100 -f S32_LE -t wav -V stereo -v file8.wav
で録音する。この、S32_LEが32ビットリトルエンディアンのフォーマットでの録音になるのである。これをS16_LEで録音しようとすると、そのフォーマットではできないよというエラーになる。

32ビットは、高い質の録画になるのであるが、これをデータでとらえる必要があ流。4バイトが、intになるのかfloatになるのか、二つの場合があるらしい。ただし、データで読むとき、4バイトの、バイト配列で取り出せるのだが、どう変換してもきちんとしたデータに変換できない。16ビット、2バイトの音源の場合はデータ化できるのだが、32ビットだとできないのである。朝からずっとこれをどうやったら変換できるのかをやっていた。

結局どうしてもできないので、soxで元の32ビットの音源を16ビットに変換することにした。変換のコマンドラインは次のようになる。
sox -v 0.99 file8.wav -b 16 file8-16-1.wav
元ファイルの前に、 -v 0.99 をつけないと
sox WARN dither: dither clipped 5768 samples; decrease volume?
という警告が出る。元音源のボリュームが大きすぎるものがいくつかあるよというエラーらしい。若干ボリュームを下げるオプションらしい。
という、データが取れる(ごく1部だ)
青が、レベルが高いので、音源に近い方で、そちらが微妙に先についているように見える。厳密には計算して見なければならない。
バイト配列の計算部分は、

short left, right;
for (int i = 0; i < data.length; i += 4) {
left = (short)((short)data[i] & 0xff | ((short)data[i+1] << 8));
right = (short)((short)data[i+2] & 0xff | ((short)data[i+3] << 8));
System.out.println("" + left + " " + right);
}

となる。
dataにバイト配列が入っている。

SPH0645LM4H搭載 I2S MEMSマイクモジュールを動かす

アクリルロボットHAL2の頭の両脇に耳をつけて、音の到着時間さで音が聞こえてくる方向を決めようと思っている。そのために、音声をデジタル変換して差を求めなけれがならない。

結構長い時間どうするのかを探ったが、SPH0645LM4H搭載 I2S MEMSマイクモジュールが最適だと結論に至った。これは、スイッチサイエンスがアマゾンで972円で売り出している。(Amazon)これを二つ買った。(マイクの方向に気をつけて、ピンをはんだ付けすること)

これの接続や、カーネルのコンパイルについては、極めて簡単で(1)こちら のサイトをそのまま実行すれば良い。(厳密には、私は、コンパイルは、同じSPH0645LM4H を扱っている (2)こちら を参照して、自動起動がうまくいかなかったので、そこは(1)を参照した。別に物で試すと、コンパイルは(1)の通りやるとうまくいかなかった。(2)でコンパイルして、後半は(1)を参照するのがベストか)

録音できるところまでは順調だったが、マイクのゲインが低すぎた。これについては、(1)の最後に書いてあるものが、一番よくわかる。

HAL2も「謎かけネタ」ができるようになった

膨大な謎かけデータを、emilyという新たに開発した対話用スクリプト言語で書いて、それをちゃんと実行できるようにした。特に、juliusの停止と再開の制御に手間がかかった。HAL2がしゃべっているときは、音声認識はオフにしておかなければならない。juliusモジュールにterminateコマンドとresumeコマンドを送っても、実際に停止や再開が有効になるのには少しラグが発生する。喋りとそのラグの関係がうまくいかないと、ロボットがしゃべっていることをjuliusが認識してエンドレスになってしまったりした。これを確実に制御するためには、juliusモジュールに、コマンドを送って、その結果がストリーム出力で出てくるのを捉えて、状態を把握していくしかなかった。とても面倒だったな。

これで、サリーの代理もできるようなった。よかった!

Juliusのマイクデバイスの問題

juliusを使うとき、マイクデバイスが見つからないというエラーはよく出る。これは、新しいraspiのosで、/dev/dspデバイスが使えないからだった気がするが、これはjuliusのコンパイル時に、alsaのマイクデバイスを使うことを指定すれば回避できるようだ。具体的には、
Juliusのコンパイル時に、
./configure --with-mictype=alsa
を指定すれば良い。ただ、alsaのヘッダーがないと言われるので、事前に、
sudo apt-get install libasound2-dev libesd0-dev libsndfile1-dev
を実行しておかなければならない。
さらにJuliusを立ち上げる時に
export ALSADEV="plughw:1,0"
の環境変数をしてしなければならない。1,0の箇所は、マイクデバイスの番号を
arecord -l
で確認しなければならない。

ロボットの音声認識システムの調整

この間、1000行あまりのemilyスクリプトを処理しているが、色々な問題を解決してそれはできるようになった。

ただ、juliusは、すべてスクリプトのどれかの言葉に一致させてしまうので、全然関係ない言葉でも、それに反応してしまうという問題が前から気づいていた。それでは実際のネタでは使えない。(今は、サリーからtelepathyでもらった言葉で反応するようにしているので問題はないが)

そこで、通常のjuliusのディクテーションセットを同時に読み込ませて、そちらにも一致させれば問題ないだろうと思った。実際、juliusは、複数のモデルを組み込んで認識する能力を持っているということなので、それを実際に組み込もうというものだ。

まず、Juliusのサイトにあるディクテーションキットから必要なファイルを取ってくる。言語モデルを、main2.jconfとしよう。main.jconf 余計なものを切り取ってついのようなものにする。

# main2.jconf
-input mic
-LM emily-l1
# mkbingramで作成したバイナリ形式
-d bccwj.60k.bingram
## 単語辞書ファイル
-v bccwj.60k.htkdic

ここに記載してあるbingramファイルとhtkdicファイルを、この場合は同じフォルダに置いておかなければならない。パスを指定すれば良いのだが。注意点として、-input micはグローバルオプションなので、何よりも早い位置に置いておかなければならない。最初の設定ファイルのその頭という意味のようである。
-LM emily-l1
言語モデルのセクション開始の宣言である。名前をemily-l1はセクション名で、後で効いていくる
ディクテーションキットの言語モデルは巨大なのでバイナリファイル化している。
続いて、ディクテーションキットの音響モデルである。ファイル名を、am-gmm2.jconf としよう。

# ディクテーションキット GMM-HMM版音響モデル・入力設定
##
-AM emily-a1
# 音響モデル: GMM-HMM
-h jnas-tri-3k16-gid.binhmm
-hlist logicalTri-3k16-gid.bin

-AMは音響モデルのセクション開始を表しそのあとに、名前が続いている。二つのファイルも同じフォルダに用意しておかなければならない。

続いて、emily.jconfである。

##############
-LM emily-l2
-gram SALLYHAL2180809
-AM emily-a2
-h hmmdefs_ptm_gid.binhmm
-hlist logicalTri
-SR SR1 emily-a1 emily-l1
#### 探索パラメータ
-b 1500 # 第1パスのビーム幅(ノード数) triphone,PTM,engine=v2.1
-b2 100 # 第2パスの仮説数ビームの幅(仮説数)
-s 500 # 第2パスの最大スタック数 (仮説数)
-m 10000 # 第2パスの仮説オーバフローのしきい値
-n 1 # 第2パスで見つける文の数(文数)
-output 1 # 第2パスで見つかった文のうち出力する数 (文数)
-SR SR2 emily-a2 emily-l2
#### 探索パラメータ
-b 1500 # 第1パスのビーム幅(ノード数) triphone,PTM,engine=v2.1
-b2 100 # 第2パスの仮説数ビームの幅(仮説数)
-s 500 # 第2パスの最大スタック数 (仮説数)
-m 10000 # 第2パスの仮説オーバフローのしきい値
-n 1 # 第2パスで見つける文の数(文数)
-output 1 # 第2パスで見つかった文のうち出力する数 (文数)

モデルは、SALLYHAL2180809という名前になっていて、この名前と、.dfaというサフィックスのファイルおよび.dictという辞書ファイルを用意しなければならない。言語モデルと音響モデルを指定する。

さらに -SRは、これまでのモデルをまとめて指定するものである。名前と、どの言語モデルと音響モデルのセットなのかを指定する。この-SRのセクションの中で、それぞれのモデルの副次的パラメータを指定する。

emilyの作成するファイルをeucファイルとなっているのだが、ディクテーションキットはUTF-8になっている。だから、emily作成ファイルは、nkf -u などでUTF-8に返還しなければならない。違う文字コードではだめなのである。

これで、
julius -C main2.jconf -C am-gmm2.jconf -C emily.jconf
とすれば、起動できる。

ただ、これでは、当初の思い通りの結果にはならない。確かに、それぞれで認識結果を同時に出してくるので、いいのだが、どちらのものが、どれだけ一致しているかが、スコアから判定できないような感じなのだ。

だから、やはり、emilyのスクリプトで、他の言葉の可能性を受け止めるようにしなければならないようだ。