この記事は、富士通ソーシアルサイエンスラボラトリ Advent Calendar 24日目の記事です。
はじめに
AI・機械学習ばやりの昨今、追いかけておきたい技術は多数あります。
しかし複数のニューラルネットワークの組み合わせや多層化などで、なかなか簡単に試せない技術も増えてきました。
最新とは言えませんが、この記事では比較的気軽に試せる自然言語処理向けの Doc2Vec を使って機械学習の実行の流れを見てみたいと思います。
Doc2Vec の考え方
Doc2Vec は単語のベクトル表現を学習する Word2Vec を任意の長さの文に使用できるように拡張したものです。
単語のベクトル表現というとピンとこない感じですが、 Word2Doc で良く使われるベクトルを用いた計算の例は以下です。
「パリ」 - 「フランス」 + 「日本」 = 「東京」
各単語の要素を数値で持つことで、上記のように単語の計算が可能になります。
この数値は例えば以下のような中身になります。
パリ | フランス | 日本 | 東京 | |
---|---|---|---|---|
地名 | 0.9 | 0.1 | 0.05 | 0.9 |
国名 | 0.06 | 0.96 | 0.98 | 0.09 |
中心 | 0.95 | 0.23 | 0.3 | 0.92 |
フランス | 0.92 | 0.99 | 0.12 | 0.08 |
日本 | 0.01 | 0.02 | 0.95 | 0.97 |
この数値がうまく単語の特徴を表すような値になっていると、パリからフランスの要素を引いて、日本の要素を足すと東京になる、といった計算が可能になります。
Word2Vec/Doc2Vec ではニューラルネットワークを使って、文章の塊から上記の数値を学習していきます。
仕組みについてに興味がある方は、以下の記事を参照してみてください。
https://deepage.net/bigdata/machine_learning/2016/09/02/word2vec_power_of_word_vector.html
https://deepage.net/machine_learning/2017/01/08/doc2vec.html
試してみる内容
富士通ソーシアルサイエンスラボラトリ Advent Calendar 2018の各記事を学習データとして使用し、記事間の類似度などを見てみたいと思います。
できるだけ直前の記事まで使用しますので、結果は現時点(12/16)で予想つきません。
たぶんオチもつきません。
学習データとしてはテキストの量が不足すると思いますので、本格的に試されたい方は Wikipedia や青空文庫のデータで試してみてください。
実行環境
Google Colaboratory 上の Notebook 環境を使用しました。
Google Colaboratory は Web 上でコード・機械学習を実行できる Google のプラットフォームで、無料で使用できます。IPython/Jupyter Notebook をご存知の方は、そのクラウド版と考えてください。
詳しく知りたい場合は下記記事が参考になります。
https://www.codexa.net/how-to-use-google-colaboratory/
python 3.6, mecab, gensim を使用します。gensim は自然言語処理を行うライブラリで、 Word2Vec/Doc2Vec の実装も含まれています。今回の学習はそれほど時間はかからず、ローカルマシンでもこれらを揃えれば十分実行可能です。
mecab はテキストデータを単語単位に分割するために使用しますが、後述する課題があります。
Notebook のファイルと学習用データは以下に置いてあります。
https://github.com/go5025/doc2vec-cal-201812
学習データ
富士通ソーシアルサイエンスラボラトリ Advent Calendar 2018 の 12/1〜12/22 までの記事を使用させていただきました。
"end_of_text" を記事の区切りとして、22の記事を連結して一つのテキストファイルを作成しました。文章の部分はブラウザー上で手でコピーしています。
コードと実行結果
Google Colaboratory/Jupyter Notebook に合わせて記載します。
! で始まる箇所は、環境作成で使用している OSコマンドを実行するマジックコマンドです。
環境設定
Google Colab で新規ページを作成すると、gensim はすでに入っているので、mecab 関連のパッケージを追加すれば OK です。お手元の Ubuntu などで実行する場合は gensim もインストールしてください。
!apt install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y
だったのですが、なんか学習結果がイマイチです。
今回 mecab をテキストデータを単語単位に分割するために使用しているのですが、どうもその分割結果が変です。
import MeCab
text = "私はバナナが好きです。"
mecabTagger = MeCab.Tagger("mecabrc")
mecabTagger.parse("")
node = mecabTagger.parseToNode(text)
while node:
word = node.surface
print(word+": "+ node.feature)
node = node.next
としたときに期待される結果は以下です。
: BOS/EOS,*,*,*,*,*,*,*,*
私: 名詞,代名詞,一般,*,*,*,私,ワタシ,ワタシ
は: 助詞,係助詞,*,*,*,*,は,ハ,ワ
バナナ: 名詞,一般,*,*,*,*,バナナ,バナナ,バナナ
が: 助詞,格助詞,一般,*,*,*,が,ガ,ガ
好き: 名詞,形容動詞語幹,*,*,*,*,好き,スキ,スキ
です: 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。: 記号,句点,*,*,*,*,。,。,。
: BOS/EOS,*,*,*,*,*,*,*,*
しかし、単に mecab の最新版を入れた時は以下のように出力されます。
私はバナナが好きです。: BOS/EOS,*,*,*,*,*,*,*,*
私はバナナが好きです。: 名詞,代名詞,一般,*,*,*,私,ワタシ,ワタシ
はバナナが好きです。: 助詞,係助詞,*,*,*,*,は,ハ,ワ
バナナが好きです。: 名詞,一般,*,*,*,*,バナナ,バナナ,バナナ
が好きです。: 助詞,格助詞,一般,*,*,*,が,ガ,ガ
好きです。: 名詞,形容動詞語幹,*,*,*,*,好き,スキ,スキ
です。: 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。: 記号,句点,*,*,*,*,。,。,。
: BOS/EOS,*,*,*,*,*,*,*,*
調べたところ、2018-12-16時点最新の mecab-python3 0.996.1 は以下のバグの対応が完全ではなさそうです。
https://github.com/SamuraiT/mecab-python3/issues/19
仕方ないので mecab-python の旧バージョンを入れて暫定対応としておきます。
!pip install mecab-python3==0.7
学習の実行
学習データをテキストファイルから読み込み学習します。
import MeCab
from gensim.models.doc2vec import Doc2Vec
from gensim.models.doc2vec import TaggedDocument
text = open('simuraly-data.txt', 'r').read()
documents = text.replace("\n", "").split("end_of_text")
def words(text):
# 文章を単語に分割する
word_list = []
tagger = MeCab.Tagger('-Ochasen')
tagger.parse('')
node = tagger.parseToNode(text)
file = open('words.txt', 'a')
while node:
word_type = node.feature.split(",")[0]
word = node.surface
if word == '*' and node.feature.split(",")[6].isalpha():
word = node.feature.split(",")[6]
if len(word) > 1 and word.isalpha() and word_type in ["名詞"]:
word_list.append(word.lower())
file.write(str(word + "\n"))
node = node.next
file.close()
return word_list
# テキストごとに単語分割して学習データを作成する
train_data = []
file = open('documents.txt', 'w')
for i, document in enumerate(documents):
train_data.append(TaggedDocument(words=words(document), tags=['text' + str(i + 1)]))
file.write("*****" + str(words(document)) + "\n")
file.close()
# doc2vec の学習を実行
model = Doc2Vec(documents=train_data, min_count=1, alpha=0.005, epochs=40, sample=20, dm=1)
学習実行の本体は最後の一文です。先頭から、ファイル読み込み、単語分割の関数、学習データの作成を行って、学習を実行する流れです。
本格的に実行する場合は評価方法を決めてパラメータ等調整を行いますが、今回は結果を見ながら手でざっくりと調整しました。
学習結果の確認
各カレンダー記事間の類似度を表示してみます。
これは機械学習の推論と呼ばれる計算で、学習をして、推論するというステップが機械学習の典型的な流れです。
for i, document in enumerate(documents):
print('text' + str(i + 1) + "\t" + document.replace("\n", "")[0:50])
for items in model.docvecs.most_similar('text' + str(i + 1), topn=2):
print("\t" + str(items[0]) + " : \t"+ str(items[1]) + "\t")
各カレンダー記事 text 1-22 に対する類似記事とその類似度を出力します。
マイコンに関する記事 1, 2, 5, 7、python のログに関する記事 6, 19、proxy に関する記事 8, 18、が類似となっているあたりは感覚に近い結果のようです。すべて良い結果というレベルではありませんが、学習はひとまずできています。
text1 マイコンボードを中継器にして PC に電子部品を接続し、Ruby で制御してみた(発光ダイオード編)
text7 : 0.9987452030181885
text5 : 0.9986720085144043text2 マイコンボードを中継器にして PC に電子部品を接続し、Ruby で制御してみた(温度センサ編)Ru
text7 : 0.9979823231697083
text1 : 0.9978688955307007text3 フロントエンド初心者が、ReactでHelloWorldしてみた(ブラウザ上編)初心者ブラウザHel
text21 : 0.9979885220527649
text20 : 0.9979159235954285text4 フロントエンド初心者が、Vue.jsでHelloWorldしてみた(ブラウザ上編)初心者ブラウザHe
text20 : 0.9967581033706665
text21 : 0.9964686036109924text5 マイコンボードを中継器にして PC に電子部品を接続し、Ruby で制御してみた(サーボモータ編)R
text1 : 0.9986720085144043
text7 : 0.9985793232917786text6 最初に覚えて欲しいPythonのロギングPython21はじめに簡単な使い捨てのコードならprint
text19 : 0.9987303018569946
text7 : 0.9971414804458618text7 マイコンボードを中継器にして PC に電子部品を接続し、Ruby で制御してみた(人感センサ編)Ru
text20 : 0.9989395141601562
text1 : 0.9987452626228333text8 proxy が存在するネットワークから、インターネット上の何かにアクセスするproxy5この記事は、
text18 : 0.8662922382354736
text16 : 0.8357861638069153text9 Adobe XD CCでモックアップをサクサク作ろうAdobeXDモックアップ1今更ですが、Adob
text17 : 0.9987900853157043
text20 : 0.9986022710800171text10 蠟引紙(ろうびきし)でアドベントツリー作ってみたクリスマスデザイン,UI蝋3< 序章 >先日、妻に急
text16 : 0.9987809658050537
text20 : 0.9987393617630005text11 RaspberryPi で人感カメラを作ってみたPythonRaspberryPiIoT4開始IoT
text16 : 0.9988595247268677
text20 : 0.9988433122634888text12 Python in RedHat Enterprise Linux 8PythonLinuxRHEL
text20 : 0.9978363513946533
text17 : 0.9974120855331421text13 手抜きのjavascript -var編-JavaScript1富士通ソーシアルサイエンスラボラトリ
text20 : 0.9978794455528259
text7 : 0.9976962804794312text14 なんか作るPythoncurses2なんか作ってみるPythonと何かでcursesを使って見た目動
text20 : 0.9948053359985352
text7 : 0.9946098327636719text15 Excelの入力規則でつくる食卓カレンダーExcel,入力規則2この記事は、富士通ソーシアルサイエン
text20 : 0.9979256391525269
text21 : 0.9978295564651489text16 CodiMDによるドキュメント作成のすすめMarkdownHackMDCodiMD4たまたま弊社のア
text20 : 0.9992533326148987
text17 : 0.9991676807403564text17 Webで動くピタゴラスイッチ風のおもちゃJavaScriptTypeScript物理演算Matter
text20 : 0.9992940425872803
text16 : 0.9991676211357117text18 認証プロキシをFiddlerで超えるHTTPFiddler3はじめに行く手を阻む認証プロキシを、Fi
text16 : 0.9972044825553894
text10 : 0.9966017007827759text19 PythonのloggingにTRACEレベルを増やすPython4はじめに前回「最初に覚えて欲しい
text6 : 0.9987303018569946
text7 : 0.9975227117538452text20 何も買わずに試すディープラーニングディープラーニングKerasTensorFlow4はじめに 相変わ
text17 : 0.9992940425872803
text16 : 0.9992532730102539text21 Cytoscape.jsを用いてデータを可視化するgraphcytoscape.js5Cytosca
text20 : 0.9991275072097778
text17 : 0.9991148114204407text22 ルーラーを表示して文字位置を揃えるofficeWordPowerPointドキュメント2はじめにルー
text20 : 0.99702388048172
text21 : 0.9970059394836426
単語間の演算を行う
単語の類似度も出力してみます。コマンドの単語に類似している単語は何でしょうか?
for result in model.most_similar(positive="コマンド", topn=10):
print(str(result))
それなりに関連のありそうな単語が並びました。
('設定', 0.9952303171157837)
('以下', 0.9942916631698608)
('情報', 0.9937410950660706)
('ファイル', 0.9932984113693237)
('fiddler', 0.9932637214660645)
('作成', 0.99315345287323)
('環境', 0.99250328540802)
('記事', 0.9917477369308472)
('id', 0.9915721416473389)
('必要', 0.9906970262527466)
それでは user を加えてみます。
for result in model.most_similar(positive=["コマンド", "user"], topn=10):
print(str(result))
するとネットワークに関連する単語が出力されます。
('https', 0.991931676864624)
('ネットワーク', 0.9820094108581543)
('存在', 0.9805724024772644)
('情報', 0.978228747844696)
('ファイル', 0.977412223815918)
('設定', 0.973896861076355)
('git', 0.9735790491104126)
('fiddler', 0.9706029295921326)
('インターネット', 0.9678129553794861)
('作成', 0.9671698808670044)
ここから http を引いてみます。
for result in model.most_similar(positive=["コマンド", "user"], negative=["http"], topn=10):
print(str(result))
ネットワーク関連の単語が無くなりました。
('python', 0.9798836708068848)
('pc', 0.9794877767562866)
('arduino', 0.9788479804992676)
('pip', 0.978778600692749)
('name', 0.9787502288818359)
('self', 0.9783775806427002)
('制御', 0.9783043265342712)
('インストール', 0.9781792759895325)
('keras', 0.9780073165893555)
('logger', 0.9779103994369507)
パリ + フランス〜 ほど面白いところまでは行きませんでしたが、演算はできているようです。
おわりに
Doc2Vec を使うと文書間の類似度などを簡単に計算することができます。
単語の演算なども行うことができますが、精度を求める場合にはデータの整備、パラメーターの調整などが必要になります。
データ量が少なければ学習も短時間で試せますので、Doc2Vec で機械学習入門してみてはいかがでしょう。
それでは良いお年を。