Edited at

RubyでPyCallを使って形態素解析と機械学習によるニュース記事の分類をしてみる


はじめに

最近の仕事で「文章を機械学習させる」という案件があり、これまでPythonでは経験があったから、せっかくRubyの街に住んでいるのでRubyでやってみよう...と思いましたが、意外と障壁が高かったのでPyCallを使うことにしました。

「Pythonでやるのと同じじゃないか!」と思われるかもしれませんが、細かなTipsがあるので参考までにまとめておきます。


ニュース記事の収集

ここでは、"AI"、"IoT"および"Robot"というキーワードで記事データを収集し、それぞれを以下のようにCSV形式で保存したファイルを作ります。

ファイル名は「news.csv」とします。

分類
記事

ai
ビッグデータを分析して...

iot
センサーを設置し、温度を...

robot
モーターの制御を行う...

ai
...

記事のソースは以下のサイトを参照。


PyCallのインストール

詳細は以下をご参照下さい。

GitHub - mrkn/pycall.rb

僕は以下のコマンドでインストールしました。

sudo gem install pycall       

sudo gem install pandas


Janomeのインストール

Janomeは形態素解析をするPython用のライブラリです。

GitHub - mocobeta/janome

僕は以下のコマンドでインストールしました。

pip install janome


記事データの読込

記事データを以下のスクリプトにて読み込みます。

require 'pycall'

require 'pandas'

pd = Pandas

file = "news.csv"
df = pd.read_csv(file)


形態素解析

以下のスクリプトにて文章を形態素解析により分かち書き文章へ変換します。

tokenizer = PyCall.import_module('janome.tokenizer')

t = tokenizer.Tokenizer.new()

notes = []

df.shape[0].times do |i|

note = df["記事"].iloc[i]
tokens = t.tokenize(note.gsub(' ',' '))
words = ""

(0..tokens.length - 1).each do |i|
words += " " + tokens[i].surface
end

notes << words

end


数値化

分かち書き文章を数値化します。

vectorizer = PyCall.import_module('sklearn.feature_extraction.text')

vect = vectorizer.TfidfVectorizer.new()
vect.fit(notes)

x = vect.transform(notes)

分類を数値化します。

labels = ["ai", "iot", "robot"]

y_dic = {}
i = 0

labels.each do |label|
y_dic[label] = i
i += 1
end

y = []

df.shape[0].times do |i|
y << y_dic[df.iloc[i]["分類"]]
end


機械学習

学習データと検証データの切り分け、学習、テストデータでの精度確認。

model_selection = PyCall.import_module('sklearn.model_selection')

x_train, x_test, y_train, y_test =
model_selection.train_test_split(x, y, test_size: 0.1, random_state: 42)

linear_model = PyCall.import_module('sklearn.linear_model')
model = linear_model.LogisticRegression.new()

model.fit(x_train, y_train)

puts model.score(x_test, y_test)

僕が集めた記事は、AIが137記事、IoTが134記事、Robotが129記事で、学習精度は0.775でした。

精度が良いのかどうかよくわからない...(-_-;)


分類

また、別のサイトから新しい記事を見つけて以下のようにデータ形式をあわせて分類を予測してみる。

text = <<EOF

AI活用の検証から導入、活用までを...
(中略)
...した。
EOF

text.gsub!("\n", "")

texts = [
text
]

notes = []

texts.each do |note|
tokens = t.tokenize(note)
words = ""

tokens.each do |token|
words += " " + token.surface
end

notes << words

end

x = vect.transform(notes)

result = model.predict(x)

puts labels[result[0].to_i]

結果は「ai」でした。

...ということで、できた!