エンジニアMのAI学習記録(2017年10月分)

2017.10.06

こちらは、Webに関連するエンジニア向けの記事です。
当社のWeb関連技術の公開と採用活動のために掲載しています。

(2017年10月13日更新)

みなさんこんにちは、エンジニアのMです。
これは当社の技術やAIに興味のある方に向けた日誌になります。
今月も毎週更新していきます、よろしくお願いします。

10月2日

■ 読書
「ビジネスで使える超統計学」(ISBN 978-4-7980-4153-7)を読む。Gensimと格闘する前の頭の体操として統計の復習をする。
エクセルの解説部分は現状必要ないのでカット。例がわかりやすく、具体的な計算をバッサリと省いてエクセルに特化しているところが入門書として良いと思った。数字の意味合いさえわかっていれば、計算は丸投げでも扱えるという発想は時間のないビジネスマンにピッタリだ。

■ Gensimと格闘する
以前はGensimを使ってコードを書いてみた人の記事を日本語で探していたが、今日からは英語にも範囲を広げて情報を集める。

Gensimについてひとつ例を挙げると、

from gensim import models
(略)
model = models.Doc2Vec(documents, dm=1, size=300, window=5, alpha=.025,\
min_alpha=.0001, min_count=5, sample=1e-6, iter=600)

のようにdocumentsを渡してmodelを初期化すると、この時点で渡したパラメータに従って学習済みモデルが構築される。trainメソッドを呼び出す必要はない。

これは、

if documents is not None:
self.build_vocab(documents, trim_rule=trim_rule)
self.train(documents, total_examples=self.corpus_count, epochs=self.iter)

と、documentsが渡された時はbuild_vocabとtrainを呼び出すようにクラスDoc2Vec内に記述されているためである。

そういうことなので、

from gensim import models
(略)
model=models.Doc2Vec(dm=1, size=300, window=5, alpha=.025,min_alpha=.0001,\
min_count=5, sample=1e-6, iter=600)
model.build_vocab(documents) # trim_ruleは省略可
model.train(documents, total_examples=model.corpus_count, epochs=model.iter)

と分けて記述しても同じ実行結果になる。

■ GNU LGPLについて調べる
Gensimの調査にあまり進展が無いので、GNU LGPLについて調べることにする。
GensimはGNU LGPLに従うということなので、商用利用することになった場合
を想定してライセンスの詳細についてしっかり知る必要があると考えた。

■ GNU LGPLとは?
コピーレフトライセンスについて考察」と、「Wikipedia : GNU Lesser General Public License」を参考にした。

(Wikipediaより)
社内など私的組織内部や個人で(private)利用するにあたってのソースコード改変、再コンパイルには制限がない。

ということなので、例えば「WEBサービスに組み込みたいから適宜改変して利用します」という行為は問題ない。ライブラリを組み込んで頒布するわけでなく、自分で利用する分には自由という解釈で良さそう。
もちろん、大規模に改修した結果有益なものができたと判断すれば、GPLに基づいて頒布するということも視野に入ると思われる。

しばらくはオープンソースソフトウェア(OSS)を一方的に活用する側の人ではあると思うが、いずれは提供する側の人になれればと思う。

■ Gensim続き(オンライン学習)
build_vocabメソッドについてコードを調べてみると、内部でscan_vocab→scale_vocab→finalize_vocabを呼び出していることが分かった。
これは機能ごとに分解しているだけのようなので、基本的にbuild_vocabを使うだけで良いか。

ライブラリのソースコードからオンライン学習の仕方を見つけることはできなかったので、ここで自力での読解を断念。
ウェブで検索すると、すぐにオンライン学習のヒントになりそうなコードを発見。
参考:Online learning for Doc2Vec
11ヶ月前に作成されたページだが、エラー出力に従って手直しすることで動作。
build_vocabメソッドの引数として、update = Trueを設定することで単語辞書を更新することができるようだ。
Tagsの出力を見る限り、正常に追加が行われている様子。
検証として、自前のコードに追加して動作することを確認する。

■ 自前のコードとテキストデータに対してオンライン学習を試す
Livedoorニュースコーパスを適当に2分割して、モデルを更新できるかどうか検証する。
学習させるところまでは問題なかったが、何らかの更新をし忘れていたか。類似度解析をさせようとしたら、indexの範囲を超えてしまった。

mean.append(weight * self.doctag_syn0norm[self._int_index(doc)])
IndexError: index 7269 is out of bounds for axis 0 with size 3990

原因を探ってみたが、そもそもdoctag_syn0normについてよく分からなかった。明日はこの点について調べようと思う。

10月3日

■ Gensim続き
タグの更新はされていたが、類似度解析させようとしたらエラーを吐いたのが昨日の話。doctag_syn0normがどこで定義されて、どこで更新されているのかを探したいと思う。

一つ一つ追っていく。
まずmost_similarメソッドの中でinit_simが呼び出されてdoctag_syn0normが更新されている。
コードを見ていくと、init_simメソッドの中で、doctag_syn0normのshape(行列の形状)を定義するのに、doctag_syn0を使っている。
そしてdoctag_syn0を更新しているのはreset_weightsメソッドだということが分かったので、

(sentencesの取得や各種定義の部分は省略)

model=models.Doc2Vec.load('doc2vec_div.model')
model.build_vocab(sentences, update=True)
model.docvecs.reset_weights(model)
model.train(sentences, total_examples=model.corpus_count, start_alpha=.025,\
end_alpha=.0001, epochs=20)
model.save('doc2vec_div.model')

とすることで、オンライン学習に利用したテキストデータも既知のドキュメントとして扱い類似度解析させることが可能になった。

これの応用で、

  • 初期モデル用データ(※類似度解析の対象にしたくないデータ)を使って、学習させる
  • 解析対象のデータ(※類似度解析の対象にしたいデータ)を使ってモデルを更新
  • 解析対象のデータに対して(つまり clip_start = [初期モデル用データ個数] として)most_similarメソッドを呼ぶ

という流れで解析対象を指定して扱うことができる。
また初期モデルと追加の学習とで学習率を調整してやれば、学習の比重も変えられるはず。データを分けて扱うことで、モデルの使い回しができるようになることは計算効率上大きい。

しばらくDocvecsArrayなどについて理解できればと思いコードを眺めていたが、
特に収穫はなかったので先程の処理で良さそうだという結論に至った。
Gensimについての調べるのはここまでで一区切りとする。

■ 脆弱性の例について調べてみる
SQLインジェクション、クロスサイトスクリプティングなど聞いたことはあっても、詳しく知らない脆弱性に関わる用語について調べることにした。
Wikipediaで気になる用語を探して、適宜ウェブで検索してみる。

10月4日

■ ベクター画像について
ベクター画像について調べていると、svgというxmlで記述される画像形式を発見。Python用のライブラリも存在するらしいので、これまでに調べてきた種々のライブラリと併用ができそう。
そもそもなぜベクター画像を調べようと思ったかというと、機械学習の入力データ次元数を削減して画像分析を高速化できるのではないかと考えたためである。
これは後で検討してみたいと思う。

■ 前日に続いて脆弱性について調べる
引き続き脆弱性について調べる。基礎知識として用語と概要を知っておけば、後々どういうことに気をつけなければならないか見当がつくと考えられる。

■ Pythonで画像を扱うライブラリを探す
OpenCV、Pillow、Scikit-imageなどすぐにいくつか見つかった。画像処理、画像分析のできるライブラリが揃っているようなので、導入すればすぐにでも画像を扱えるPythonコードを書くことができそうだ。

■ Gensimでオンライン学習&類似度解析
Livedoorニュースコーパスを対象にベースとなる学習モデルを作成し、こちらで手入力したテキストデータを対象に類似度解析させてみる。
追加のテキストに対する学習率は高めに設定する。

近い意味の文章を用意したはずだが、短文であったせいか期待した結果は得られなかった。
そもそもニュースコーパスに対する類似度解析でも、類似度が高くて0.2程度で内容の類似性が認められないことが多い。記事ごとに内容が違いすぎてうまく学習できていないのか、それとも単にデータ量が不足しているからなのか。

調べていたら、JUMAN++はかなり処理に時間がかかるらしいという情報を偶然得たので、形態素解析済みのデータを保存、読み込みする方策について考えたほうが良さそうだ。
いい機会なので、雑多に散らかったコードの整理も行う。

10月5日

■ 類似度解析を試行錯誤するための下地を作る
昨日はコードをどう整理するかを考えるところで終わったので、実装を進めたいと思う。
とはいっても、元がほぼサンプルコードそのままだったものに多少機能を追加するだけ。サクッと終わらせたい。

■ そもそもなぜ短文に対して精度が低いのか
理由はおそらく正規化しているからだ。数十単語で構成される文章中の一単語と、数単語だけで構成される文章中の一単語とでは影響の大きさが全く違う。
単純なコサイン類似度だけの評価では短文を処理できない。この問題に対するアイデアについても考えを巡らせながら、コーディングを進めていこうと思う。

■ バグ?
途中経過を出力させていると、どうも怪しい部分が多い。ほとんどサンプルコードから借りているので、一度ちゃんと検証したほうが良さそうだ。
pickle化する直前のテキストデータがおかしいので、pickle化したデータも正確ではない気がする。考えるほどに沼にはまりそうな気がしてきたので、一回別のことについて作業を進めたいと思う。

■ OpenCVで画像処理を試す
OpenCVを使って画像処理をさせてみることにした。
yumでpython-opencvをインストールし、GUI環境を立ち上げてチュートリアルを一通り触れてみる。
画像処理入門講座 : OpenCVとPythonで始める画像処理
OpenCVはなかなかに高機能そうなので、いろいろと応用が利くのではないか。

画像処理に機械学習を持ち込むのは人間の手に負えないときのほうがよい。
ある程度処理の内容や手順が思い浮かぶうちは、こういう画像処理ライブラリが便利だと考えられる。

10月6日

■ AngularJSの本を読む
今日はOpenCVを触る予定だったが、昨日Angularの話になって終わったこともあり、少しAngularについて勉強してみることにした。

  • AngularJS アプリケーションプログラミング(ISBN 978-4-7741-7568-3)

バージョンが古い内容なので、仕組みやどういったことができるのか、を中心に学習する。
HTMLやJavascriptに馴染みが薄いので時間はかかったが、基本編をざっくりと読んで終わり。ここ最近はPythonのコードしか読んでいなかったが、Pythonの読みやすさを再確認する時間だった。
HTMLはタグが多くて見づらい…。

■ OpenCVとTesseractを組み合わせてみる
Tesseractで文字を処理した後、OpenCVで画像を処理させてみたいと思う。
Tesseractには文字と認識したブロックの座標を取得する機能があるので、その座標に基づいて文字を除去すれば、文字の部分とそれ以外を別々に処理することができるはず。

■ 結果
pyocrではTesseractのオプションを与えてやれないので、日本語のbox(文字の存在するエリア)を取得する精度すら低いのが痛い。実用には程遠いか。
pyocr(Tesseract)+OpenCV+Pillow+Matplotlibというライブラリの組み合わせで解析させることができた、ということを今回の成果とする。

■ 今週の総括
Gensimを始めとするライブラリの実験に費やした週だった。コードの読み書きが多かった分、Pythonにだいぶ慣れることができたように感じる。
次週はまたGensimをなんとかしたいと思う。

10月10日

■ Vagrantの修復
前々からたまに見かけていたエラーが直らなくなったので、修復するところから。

[default] GuestAdditions 5.1.26 running --- OK.
==> default: Checking for guest additions in VM...
==> default: Configuring and enabling network interfaces...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
==> default: Rsyncing folder: /cygdrive/d/Vagrant/centos7/ => /vagrant
C:/HashiCorp/Vagrant/embedded/gems/gems/vagrant-1.9.8/lib/vagrant/util/io.rb:32:in `encode': "\xA0" on Windows-31J (Encoding::InvalidByteSequenceError)

GuestAdditionsがうまく動いていないようだ。実際にCentOSから確認すると、共有フォルダが機能していないことがわかった。

いろいろ検索してたどり着いたこの記事の変更を適用して無事修復。
そもそもこのエラーを吐いたり吐かなかったりしていたのが不思議でならない。

■ Tesseract 4.00.00devを動かすための作業
ホームディレクトリで依存するソフトのインストールから一通り作業したのだが、どうにもうまくいかず。課題はまたしても持ち越しとなった。
ほぼ丸一日触っていたおかげでCUIの操作は早くなったが、できれば今日中に解決したかったのが正直なところ。

10月11日

■ Java入門
Javaを使うことはしばらく無いと思うが、教養として触っておく。最近は知識を掘り下げる作業が多かったので、薄くなぞるような勉強も頭の体操には良い。

  • 「本格学習Java入門」(ISBN 4-7741-2002-2)

構文はほとんどCなので、リファレンスを読みながらであればすぐにコーディングを始められそうだ。オブジェクト指向もPythonで触れてある。

■ インストール作業続き
必要とされているソフトを最新版に更新してみる。
エラー内容から判断して怪しいのは、

  • autoconf (→2.69)
  • automake (→1.15.1)
  • libtool (→2.4.6)
  • autoconf-archive (→2017.09.28)

以上4点。pkg-configは更新済み(0.29)。
それぞれ相互に関連しているツールなので、全部カッコ内のバージョンに更新する。
その他必要と言われたものもyum経由、あるいはソースからビルドして追加でインストールした。

  • libattr-devel
  • attr
  • help2man

これで準備ができたはずなので、

  • Leptonica (1.74.4)
  • Tesseract (4.00.00dev)

のインストールに取り掛かる。
しかしLeptonicaが見つからないとTesseractを入れようとしたときに怒られる。

ここで、Leptonicaに忘れていたmake checkをかけたら9割Failしていることが分かった。
確認してみると、

  • libpng12-devel
  • libjpeg-turbo
  • libtiff-devel

が入っていなかったり、バージョンが古かったりした。インストールを済ませることでLeptonicaに対するmake checkを1つを除いてパスできた。
ソースからLeptonicaを入れた場合はleptonica-devも不要のはずだが、Tesseractがleptonica-devを入れるよう指示してくる。
何かしら見落としがある可能性があったので、
GitHub:Compilation guide for various platforms」と、「Leptonica:README」を熟読した。

どうやら、

(GitHub)
Note that if building Leptonica from source, you may need to ensure that /usr/local/lib is in your library path. This is a standard Linux bug, and the information at Stackoverflow is very helpful.

(README)
Configure supports installing in a local directory (e.g., one that doesn’t require root access). For example, to install in $HOME/local,

   ./configure --prefix=$HOME/local/
      make install

For different ways to build and link leptonica with tesseract, see
https://github.com/tesseract-ocr/tesseract/wiki/Compiling
In brief, using autotools to build tesseract and then install it
in $HOME/local (after installing leptonica there), do the
following from your tesseract root source directory:

       ./autogen.sh
       LIBLEPT_HEADERSDIR=$HOME/local/include ./configure \
          --prefix=$HOME/local/ --with-extra-libraries=$HOME/local/lib
       make install

このあたりを見落としていたのがまずかったらしい。

export LD_LIBRARY_PATH=/usr/local/lib/

と/etc/profileに記述して、他にも色々更新しているので一旦ゲストOSを再起動。
Tesseractのconfigureに対して、

 LIBLEPT_HEADERSDIR=/usr/local/include ./configure

と指定することによってconfigureが通った。
どうやらヘッダーの場所を教える必要があったらしい。
leptonica-devを入れる方法の場合はこのあたりを自動でやってくれるのだろう。

丸2日近く使う羽目になったが、

[root@localhost tesseract]# tesseract --version
tesseract 4.00.00alpha
 leptonica-1.74.4
  libjpeg 6b (libjpeg-turbo 1.2.90) : libpng 1.5.13 : libtiff 4.0.3 : zlib 1.2.7

 Found AVX
 Found SSE

とTesseract最新版のインストールが無事に終わった。
英語のマニュアルやフォーラムを読み、延々とCUIを叩き続けたことは良い経験となったのではと思う。

教訓としては、

  • 英語でも面倒がらずにマニュアルを熟読すること
  • 依存するソフトのバージョンは、指定がなくても出来る限り最新を使う
  • 自分が使っているOSの環境を把握すること(パスなど)

の3点。次に同じような事態に陥っても、今度はもっと早く問題を解決できるはず。

10月12日

■ Tesseract続き
インストールは無事通ったので、コマンドラインから使えるようにしたい。
しかし、指示に従ってTESSDATA_PREFIXの値を設定してもtraineddataが読み込めていないようだ。

Tesseractを入れ直したりしてみたが、結局、

export TESSDATA_PREFIX=/usr/local/share/tessdata/tessdata/

としてeng.traineddataやosd.traineddataを/usr/local/share/tessdataに置くことで読み込まれるようになった。

[root@localhost tessdata]# tesseract --list-langs
List of available languages (3):
jpn
eng
osd

毎回exportするのは面倒なので/etc/profile内に書き加えてある。
コマンドラインからOCRの実行も確認できたので、これにてTesseractのインストールは完了。

■ pyocrから確認
Tesseractのテストとしてpyocrを使ったコードを動かしてみたが、うまく動いていない様子。安定版ではない4.00.00にはまだ対応していないようなので、とりあえず保留とする。

■ Juman++で入力テキストを分かち書きして保存する
先週触っていた、分かち書きして保存するコードを修正する。
print文で内容を確認した限りでは、1つのテキストファイル全体のうち一部分しか読み込めていないようだった。コードをよく調べて原因を突き止めたい。

一つ一つ確認したところ、Juman++は改行を認識するとその手前までで解析を行って結果を返すことがわかった(またしてもマニュアルの読み抜けである)。
これは、

(import re が必要)
def split_into_words(text):
    replaced=re.sub('\n', '', text)  # 改行(\n)を削除
    result=Jumanpp().analysis(replaced)
    return [mrph.midasi for mrph in result.mrph_list()]

としてすぐに対処できた。しかし今度は、

File "/home/vagrant/.pyenv/versions/3.6.2/lib/python3.6/site-packages/pyknp-0.3-py3.6.egg/pyknp/juman/morpheme.py", line 93, in _parse_spec
ValueError: invalid literal for int() with base 10: '\\'

とエラーを吐くようになってしまった。
returnの内容をprintで表示させると、’\u3000’という文字が含まれていることがわかった。これは全角スペースのことらしい。また半角スペースが含まれていてもエラーが出るようなので、まとめて削除する。

replaced=re.sub('\n|\u3000| ', '', text)

これでJuman++による形態素解析が正常に動作するようになった。
借りてきたコードが正しく動いているように見えても、油断できない例となった。

Juman++は高性能な分、解析にとても時間がかかる。実用には学習用データの選定が必須だ。4~5KBのテキストファイル1つにつき約8秒だと、Livedoorコーパス全体では12時間程かかってしまう計算である。

10月13日

■ Gensimで類似度解析
バグ取りの済んだプログラムで類似度解析をさせる。
以前の結果はなかったことにして、Doc2Vecの性能を確かめたいと思う。
類似性を人間が判断しやすそうという理由で、「家電チャンネル」の記事を使ってモデルを作成する。

ここで仕様について一度考え直してみる。

分かち書きテキスト&ファイルパス → TaggedDocument化 → それをリストにまとめる → Pickle化 → (モデル作成プログラム側で非Pickle化)

という手順を想定していたが、TaggedDocumentにするのは学習させる直前のほうが後々便利なのではと感じる。
TaggedDocument化前の段階なら、Python標準のデータ構造だけで扱っているのでデータ加工しやすいと考えた。

つまり、

分かち書きテキスト&ファイルパス → リストにまとめる → Pickle化 → (モデル作成プログラム側で非Pickle化 → TaggedDocument化)

という手順にする。
後者の手順で10個のテキストファイルに対して動作確認は済んだので、これで実装していく。
分かち書きした状態のテキストデータを保存するというのも少し考えたが、ファイル数が多くなりすぎるので却下した。

作成したモデルに対して、適当なテキストのパスを与えて類似度解析をしてみた。どことなく近い内容のものは出てくるが、類似度が0.2程度ではこんなものだろうか。
関連記事の部分やURL、更新日時を削除すればもう少し精度が上がるかもしれない。
次にエスマックスの記事を形態素解析させる際には、URLと更新日時が書かれた最初の2行を削除するよう変更した。
これでエスマックスの記事内の類似度がどう出るかを見て判断していきたい。

待ち時間に他の形態素解析器について調べた。
処理時間についてこんな情報があった。
参考:形態素解析の速度比較
Juman++がWikipediaのデータ5MB分で3時間というのは、こちらの環境での数字とほぼ変わらない。
kuromojiが高性能そうなのだが、Javaバージョンしかないのが残念。形態素解析だけkuromojiに任せて、分かち書きしたテキストデータをPythonから読ませる形ならなんとかなるかもしれない。

そうこうしているうちに形態素解析が終わっていた。
エスマックス記事に関しても類似度解析させてみたが、少なくとも学習パラメータmin_countが1より5の時のほうが良さそうなことは分かった。
(min_count:これより出現回数が少ない単語を破棄)
あらゆる単語について考慮しても、ノイズが多くなるだけということか。

例えばmin_count=5のとき、あるスマートフォン新モデルの紹介記事に対して、他のスマートフォン新モデルのレビュー記事が上位に来たりと一定の類似性が認められる。
Androidの話をしている記事に対しては高頻度でiOS、iPhoneの記事が上位に来る。
類似度では0.15~0.19程度。類似度0.20を上回るのは主に短文の記事で、類似性があまり無いことが多い。
またそもそも類似の記事が無さそうな記事については、類似度が全くアテにならないように見える。

結果はさておき、分かち書きを済ませたデータを用意しておくことによって、モデルの作成で試行錯誤しやすくなったのは非常に便利。自分なりの学習環境を整備する重要性を感じた。

以下にここまでの作業で使ったコードを掲載する(Python3用、ファイル3つ)。

import sys
import re
from os import listdir, path
from pyknp import Jumanpp
from gensim.models.doc2vec import LabeledSentence
import pickle

# 解析対象のディレクトリ
tdir = str(sys.argv[1])
# TaggedDocuments保存先
save_td = str(sys.argv[2])

# 記事ファイルをディレクトリから取得する
def corpus_files():
    # フォルダ探索
    dirs=[path.join(tdir, x) for x in listdir(tdir) if not x.endswith('.txt')]
    # dirsで見つかったフォルダ内のファイル探索
    docs=[path.join(x, y) for x in dirs for y in listdir(x)]

    # 指定ディレクトリのファイルも探索
    for x in listdir(tdir):
        if path.isfile(tdir + x):
            docs.extend(tdir + x)

    # 記事ファイルのリストをreturn & パスのリストを保存
    with open( save_td + '.txt', 'wt') as f:
        f.write('\n'.join(docs))
    return docs

# 記事の内容をパスから取得する
def read_document(path):
    with open(path, 'r') as f:
        for x in range(1,3):
            f.readline() # 2行読み飛ばす
        return f.read()

# JUMAN++を使って記事を単語リストに変換する
def split_into_words(text):
    replaced=re.sub('\n|\u3000| ', '', text)
    result=Jumanpp().analysis(replaced)
    return [mrph.midasi for mrph in result.mrph_list()]

# 記事を単語に分割して、リストを返す
def doc_to_sentence(doc, name):
    words=split_into_words(doc)
    return words, name

# 記事のパスリストから記事コンテンツに変換し、単語分割してセンテンスのジェネレータを返す
def corpus_to_sentences(corpus):
    docs=[read_document(x) for x in corpus]
    for idx, (doc, name) in enumerate(zip(docs, corpus)):
        sys.stdout.write('\r前処理中 {}/{}'.format(idx, len(corpus)))
        yield doc_to_sentence(doc, name)

#処理メイン
corpus=corpus_files()
sentences=corpus_to_sentences(corpus)

#TaggedDocument保存
tdall = []
for words, name in sentences:
    tdall.append([words, name])
with open(save_td, 'wb') as f:
    pickle.dump(tdall, f)
print('\n処理終了')
import sys
from gensim import models
from gensim.models.doc2vec import LabeledSentence
import pickle

# TaggedDocumentファイル
tdir=str(sys.argv[1])
# モデル保存先
save_md=str(sys.argv[2])

# 読み出したリストをTaggedDocumentに変換
def make_td(list):
    for words, name in list:
        yield LabeledSentence(words=words, tags=[name])

with open(tdir, 'rb') as f:
    lists = pickle.load(f)

sentences=make_td(lists)
model=models.Doc2Vec(sentences, dm=1, size=300, window=5, alpha=.025, min_alpha=.0001, min_count=5, sample=1e-6, iter=1000)
model.save(save_md)
from gensim import models
import sys 

# sys.argv[1]=ドキュメントのタグ(jumanpp.py実行時にtxtファイルとして一覧を保存済)
# sys.argv[2]=学習対象のモデル(d2v_init.pyで作成したもの)
model = models.Doc2Vec.load(sys.argv[2])
sim = model.docvecs.most_similar(sys.argv[1], topn=10)
for x in sim:
    print(x)

もっとデータ量を増やして検証したいところではある。kuromojiのためにJavaをもう少し勉強するかどうか、検討したい。
何気なく使っていたが、Juman++のメモリ消費量はかなり大きい。一度に大量に分析させるとメモリをかなり消費するので、メモリ搭載量が少ない(おそらく4GB以下の)システムでは注意が必要である。

10月16日

■ kuromojiを試すための下準備

kuromojiを使ってみるために、Javaの開発環境を用意する。
手早く理解するために日本語環境も用意する。

  • Eclipse(Oxygen) Windows 64bit [リンク]
  • Pleiades Eclipse日本語化プラグイン [リンク]

注意書きに従ってインストール。
プラグイン更新時には、Eclipseをクリーン起動するのを忘れずに。
Windows版なら”eclipse.exe -clean.cmd”を使って -clean オプションで起動することができる。

プロジェクト作成については以下を参考に。

  • Qiita:Eclipse+Maven という便利な開発環境をインストールからプロジェクト作成まで [リンク]
  • kuromoji公式ページ [リンク]

以上でkuromojiを使う準備が整った。

■ kuromojiを試してみる

プロジェクトに新規クラスを作成して、サンプルコードを試してみる。

package kuromoji.hello;

import org.atilika.kuromoji.*;

public class Kuromoji {
    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ
        Tokenizer tokenizer = Tokenizer.builder().build();
        for (Token token : tokenizer.tokenize("形態素解析のテストをします。")) {
            System.out.println(token.getSurfaceForm() + "\t" + token.getAllFeatures());
        }
    }
}
形態素 名詞,一般,*,*,*,*,形態素,ケイタイソ,ケイタイソ
解析  名詞,サ変接続,*,*,*,*,解析,カイセキ,カイセキ
の   助詞,連体化,*,*,*,*,の,ノ,ノ
テスト 名詞,サ変接続,*,*,*,*,テスト,テスト,テスト
を   助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
し   動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
ます  助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス
。   記号,句点,*,*,*,*,。,。,。

問題なさそうなので、テキストファイルからデータを読み込んで処理できるようにコーディングしていきたいと思う。

■ 単一テキストファイルの読み込みテスト
入門書とWebの情報を頼りに短時間で実装できたので、例のごとくLivedoorコーパスを対象にテストを行った。
空白、記号が含まれていても問題なく解析できている様子。処理は早く、精度も不満のないレベル。

Gensimとの連携のためには分かち書きしたテキストファイルを、元データからまとめて生成できる必要がある。

■ ディレクトリ指定して一括処理するコード

引数は対象のファイルが中にあるディレクトリを想定した。
(ex. C:\example\texts)
テキスト以外のファイルが存在すると何らかのエラーを吐くと思われる。

package kuromoji.hello;

import org.atilika.kuromoji.*;
import java.io.*;

public class Kuromoji { 
    public static void main(String[] args) {
        File fl1 = new File(args[0]);

        if(fl1.exists()) {
            //書き込み用ディレクトリを作成
            File fl2 = new File(args[0] + "_sp");
            if( !fl2.exists()) {
                fl2.mkdir();
            }

            String[] array = fl1.list();
            for(int i=0; i<array.length; i++) {

                System.out.println("処理中:"+(i+1)+"/"+array.length);

                FileReader fr;
                FileWriter fw;

                String fin;
                String fout;

                // 入力ファイル(引数から対象ファイルパスを生成)
                fin = args[0]+"\\"+array[i];

                // 出力ファイル(対象のファイルが存在する場合上書き)
                fout = args[0]+"_sp\\"+array[i];

                try {
                    fr = new FileReader(fin);
                } catch (IOException e) {
                    System.out.println("Read File can not open!");
                    fr = null;
                }
                try {
                    fw = new FileWriter(fout);
                } catch (IOException e) {
                    System.out.println("Write File can not open!");
                    fw = null;
                }

                try {
                    BufferedReader in = new BufferedReader(fr);
                    PrintWriter out = new PrintWriter(fw);

                    // 2行読み込んで捨てる(Livedoorコーパスのヘッダー部分)
                    for(int j=0; j<2; j++) {
                        in.readLine();
                    }

                    // Tokenizer初期化
                    Tokenizer tokenizer = Tokenizer.builder().build();

                    String s;
                    while ((s = in.readLine()) != null) {
                        for (Token token : tokenizer.tokenize(s)) {
                            // 記号を除外する
                            if(!token.getPartOfSpeech().substring(0,2).contentEquals("記号")) {
                                out.print(token.getSurfaceForm()+" ");
                            }
                        }
                    }
                    fw.close();
                    fr.close();

                } catch (Exception e) {
                    System.out.println("Detected Unknown Error!");
                }
            }
        } else {
            System.out.println("No Such a Directory!");
        }
    }
}

これでkuromojiを使って分かち書き処理をさせることができるようになった。試しに3MB程度のテキストを一気に処理させたが、数秒で終了した。
とりあえずkuromojiを使うためだけに書いたコードなので、全く再利用性などは考慮していない。

■ Livedoorコーパス全件に対するモデルでDoc2Vecを検証する

空白区切りのテキストはsplitメソッドで簡単にリスト化できるので、元のコードを少し変更するだけでGensimに渡すことができた。
テキストファイル約7000件(約25MB)を対象にモデルを作成し、適当に選んだ記事に対する類似度を表示させてみた。
しかし以前の検証よりジャンルが多岐にわたるようになったからか、全く関係のない記事が上位に来る率が高くなった。
Livedoorコーパスは一つの記事内に複数の内容が入っていたりするので、類似性の検証には向かない可能性はある。

■ せっかくなのでJavaについてもう少し勉強する

正確に類似の文章を見つけ出すのはなかなか難しいということがより分かってきた。
すぐに解決するようなものではなさそうなので、こちらは焦らずに進めていきたいと思う。

せっかくJavaについて勉強して簡単なコードも書いたので、今のうちにJava(オブジェクト指向)への理解を深めておく。
– 「ずばりわかる!Java」ISBN (4-8222-2834-7)

10月17日

■ Javaの勉強

昨日の本を読み進める。Javaは使う予定はほとんどないが、オブジェクト指向の考え方について学ぶにはちょうど良い内容。
Javaについて色々と読んだ後で、Pythonでのオブジェクトの扱い方を復習する。
Pythonは言語側で色々と複雑さを吸収してくれているのがよく分かる。

■ PEP8コーディング規約

Pythonの読みやすいコードを書く上での規約。今まで特別に意識したことはなかったが、ここで過去に書いたコードについて調べてみた。

pip install pep8
pep8 --first d2v_init.py
import sys
from gensim import models
from gensim.models.doc2vec import LabeledSentence

import pickle

# TaggedDocumentファイル
tdir=str(sys.argv[1])

# モデル保存先
save_md=str(sys.argv[2])

with open(tdir, 'rb') as f:
    lists = pickle.load(f)

# print(lists)

# 読み出したリストをTaggedDocumentに変換
def make_td(list):
    for words, name in list:
        yield LabeledSentence(words=words, tags=[name])

sentences=make_td(lists)

model=models.Doc2Vec(sentences, dm=1, size=300, window=5, alpha=.025, min_alpha=.0001, min_count=5, sample=1e-6, iter=1000)

model.save(save_md)

としてTaggedDocumentからモデルを作成するコード(27行)を対象にしたところ、

d2v_init.py:8:5: E225 missing whitespace around operator
d2v_init.py:19:1: E302 expected 2 blank lines, found 1
d2v_init.py:25:80: E501 line too long (123 > 79 characters)
d2v_init.py:28:1: W391 blank line at end of file

と4種類警告された。
E225はわかりやすいところ。引数では逆に空けてはいけないらしい。アノテーションに関してはまた別の規則があってややこしい。
E302がピンとこないが、関数orクラス定義は2行空けることになっているらしい。クラス内のメソッドについても1行ずつ空ける決まり。
E501は単純に1行が長すぎると怒っている。長いときは複数行に渡る記述を使おう、というわけだ。それはまあ1行だけ200文字とかあるようなコードは私も読みたくはない。
W391はエラーではないが、ファイルに不要な空行があると怒られている。厳しい。
というわけで他にも(違反ではないが)気になる点を直し、コードが出来上がった。

import pickle
import sys
from gensim import models
from gensim.models.doc2vec import LabeledSentence

# TaggedDocumentファイル
tdir = str(sys.argv[1])

# モデル保存先
save_md = str(sys.argv[2])


# 読み出したリストをTaggedDocumentに変換
def make_tagged_docs(list):
    for words, name in list:
        yield LabeledSentence(words=words, tags=[name])


with open(tdir, 'rb') as f:
    lists = pickle.load(f)
sentences = make_tagged_docs(lists)

model = models.Doc2Vec(sentences, dm=1, size=300, window=5,
                       alpha=.025, min_alpha=.0001, min_count=5,
                       sample=1e-6, iter=1000)
model.save(save_md)

かなり読みやすくなったような気がする。コメントの付け方はさておき。
いい機会なので、「PEP 8 — Style Guide for Python Code」を一通り読む。
英語で理解できなかった部分や怪しい部分は「日本語版」で。

■ SQLについて学ぶ

いずれは知っておく必要があると思われるSQLについて触れておく。応用情報技術者試験のために多少は勉強したが、ほとんど理解できていなかったと言っていい。
SQL初心者向けの入門ページから入る。

  • 初心者のためのSQLガイド : SQLを直感的に理解しよう [リンク]
  • SQL実践講座 [リンク]
    プログラミングとは違った難しさがある。明日も勉強して概要をしっかり理解しておきたい。

10月18日

■ 引き続きSQLについて学ぶ

リファレンスを見ながらでも多少複雑な操作ができる、くらいまで行ければいいと思うが、なかなか難しい。

■ WPA2が破られたということで情報を集めた

つい先日WPA2の脆弱性が見つかったと話題になっていた。そろそろ情報が出揃ってきた頃だろうということで調べると、見つかったのはプロトコルそのものの脆弱性とのことだった。
ソフトウェア・アップデートで対応可能ということなので、パッチが公開され次第更新していけば問題はないとのこと。WPA2が全く使えなくなるほどの問題ではなかった、ということでひとまず安心できる。

■ 最近のセキュリティ関連トピックを見る

数日前にAndroid向けの新たなランサムウェアが見つかったという記事が出てきた。
この記事では、非公式の「Adobe Flash Player」アプリのインストールに注意喚起をしている。
かといって信頼できるウェブサイトからのダウンロードであれば大丈夫かというと、サイトやアプリが改竄されている可能性はゼロではないのが実情。[参考記事]
こうなると個人レベルで最も有効な対策は、感染に備えてオフラインのバックアップを常備しておくことなのかもしれない。

■ Gensimで高精度なモデルの作成を目指して

類似度を適宜確認しながら手作業で調整していくのではいつ終わるのか分からないので、パラメータ調整に役立つ情報は得られないかと試行錯誤してみる。

単語のベクトル値を保存して「Embedding Projector」を使うことによって、データを可視化して検討できそうだという結論に至った。t-SNEやPCAといった手法で次元削減させることもできる。
データをTSVに加工しないと読み込ませることができないようなので、実践するのは明日にする。

10月19日

■ マーケティング戦略について調べる

ウェブサービスを提供する会社に属している人間として、マーケティングについて知っておく必要があるだろう。ということで、本で多少知識を仕入れたことはあるが、復習も兼ねて調べて回ることにする。

■ Embedding Projectorを試してみる

データ整形用のコードを書いて、単語ベクトルの可視化を試してみた。
しかし、どうも単語同士の関係が怪しい。全く関係が無いはずの単語のコサイン類似度が高く出てしまう。
学習のさせ方に問題があるのか、入力データに問題があるのか、現段階では判断できない。

簡単のために、電機関連のITライフハック、家電チャンネル、エスマックスの3つを対象としてモデルを作成してみた。
名詞のみを抽出したTaggedDocumentに対して、min_count、sampleそしてwindowを大きめにしたところ、ある程度納得のできるコサイン類似度が出力されるようになった。
一つの文章が長いので、windowをある程度大きくするのは確かに有効かもしれない。名詞の抽出で除去しきれていない記号や数字の削除もしたら、まだ精度を上げられるかもしれない。

想像以上に単語ベクトルが怪しかったせいで、Embedding Projectorをほとんどいじれていない。明日もう少し検討を進めたいと思う。

10月20日

■ 自動機械学習について調べる

自動機械学習(AML:Automated Machine Learning)というものがあるらしい。
知識のある人間が手作業でパラメータを調節するより、コンピュータが自動で調節してくれれば遥かに楽である。

  • 機械学習を自動化する機械学習プロジェクト6選(前)[リンク]

機械学習フレームワークが流行ってから何年も経っていないと思うのだが、もう次の段階に進もうとしている。
さらに数年したら学習手法すら自己学習するようになっているのかもしれない。

■ auto-sklearnを試してみる

scikit-learnは以前に試しているので、auto-sklearnを試してみようと思う。
まずはDocumentationにあった論文を読むところから。
読んでいる間にサンプルデータを用意して回しておいたほうがいいのではということに気がついたので、とりあえず回してみることにした。
Exampleを読んでみると、

This will run for one hour should result in an accuracy above 0.98.

と書いてある。全部コンピュータ任せだとどうしても時間はかかるか。
とりあえずインタプリタにサンプルコードを打ち込んで学習を始めると、ゲストOSが4GB以上メモリを消費し始めた。機械学習はリソース&コストとの戦いである。

先行研究と比較しての改良点などについての記述が中心で、その研究について調べていないのでコメントしづらい。
とりあえず、効率・精度の両方で改善が見られ、かつ時間に対する値の安定性が高い、と解釈した。先行研究についてはまた別の機会に。

勉強中の身なので実際に活用することはしばらくないとは思うが、どうしても急ぎで最適化したパラメータが欲しい、のような状況なら使えるかもしれない。
急ぎと言っても丸1日はかかるが、それでも人力よりはマシか。

計算が終了してAccuracy scoreを確認すると約0.987と出たので、正しく実行されたようだ。アルゴリズムを全く指定せずに、データを投げただけで結果が出てくるというのは少し感動を覚える。

■ 数字と記号を除去して学習させてみる(昨日の続き)

Javaコードに正規表現でフィルタリングする部分を足して、学習時のノイズを低減できるように改良した。
word2vec_formatで出力したvocabファイル(出現単語と回数が記録されている)を使って、概ね除去できていることを確認。

以前に単語ベクトルの類似度を調べたときの記憶と照らし合わせると、だいぶ精度が上がっているように感じる。
今までは敢えて除去せずにやっていたわけだが、入力データに含まれるノイズがいかに影響していたかを実感している。それでも記事データという性質上まだまだノイズは含まれていると言えるが。

この記事が気に入ったら
いいね ! しよう

フォームズでは無料でメールフォームを作ることができます
メールフォームとは、フォームに入力された情報をメールで送るサービスです。問い合わせの受付や申し込み、アンケートなどに幅広く利用できます。