AI系基盤技術と、オープンソースを用いた機械学習による特許文書解析(補足2)

2020年10月11日
アジア特許情報研究会 西尾 潤

ラベル、非ニューラルネットワークモデル

本稿は特技懇誌 No.298, pp.25-37 (2020) に収録できなかった内容を追記するものです。

本稿(補足2)では、機械学習の学習時に与える正解/不正解の情報(ラベル)作成方法の例、ニューラルネットワークでない機械学習の例をソースコードを交えて紹介します。

特技懇誌投稿記事の図4のうち、下図のように濃色で示す部分に当たり、処理の流れは ラベル作成 → 機械学習 となります。

オープンソースを用いた機械学習による特許文書解析(補足1)を読む

ラベルについて

機械学習における正解/不正解を示す情報をラベルといいます。

ラベルは得たい情報と同じフォーマットです。例えば個数を予測するタスクでは長さ(浮動小数点型)であり、文章を要約するタスクでは文字列です。このとき、内部に複数の機械学習モデルが動いている場合は、単純な正解/不正解を示すフラグ(0, 1)(整数型)であることもあります。また、確率(0-1)を出力するタスクもあります。

2値分類のときは、たとえば機械学習モデルの出力が 0-1 のとき、その中間値である 0.5より上か下かで判別することができます。また、中間値 0.5 の値は目的に応じて 0.4 に設定しても 0.6 に設定しても構いません。この値を閾値といいます。

4値分類のときは複雑になります。たとえばニュース記事分類タスクで、文書を(政治, 経済, スポーツ, 芸能)の4つに分類するとき、それぞれのカテゴリを数値化すると取り扱いやすくなります。

このとき、(0, 1, 2, 3)と整数型のクラスに分けることもでき、([1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1])と one-hot型 のクラスに分けることもできます。

機械学習の出力がたとえば 0-1 のとき、これを 0.25 刻みで4つの整数型のクラスに分類するのは適切でありません。というのは、上記のニュース記事分類タスクは4つの独立したパラメータの予測だからです。この場合は出力が4個あるモデルや、2つに分けるミニタスクを3回適用して4つに分けるモデルを使います。

もちろん、上記の2値分類タスクで2次元 one-hot型 のクラスを使うこともできます。機械学習モデルの出力が次の図のように2個あるとき、それぞれの出力を比較して大きい方が該当するカテゴリになります。

複数の出力を持つ機械学習モデルにおいて、それぞれの出力が 0-1 であれば、独立した2つの確率を表しますので、上記のニュース記事分類タスクで 政治ニュースの確率が85%、芸能ニュースの確率が35%(カテゴリが独立なので合計100%にならない)と出力することも可能です。この場合、ラベルは [1.0, 0.0, 0.0, 0.2] のような one-hot型でない与え方も可能です。

このように、機械学習のラベルは機械学習モデルの設計と同時にタスクに応じて設計者が決めるもので、ラベルの種類も多様であると同時に、機械学習モデルの出力とラベルとの照合をどう設計するかということが、あまり議論されませんが実は重要です。

ラベルの作成方法

ここでは予めExcelワークシートの「ラベル」列に(0, 1, 2, 3)の4カテゴリ、「インキ」、「機構」、「受容シート」、「用途」の各列のどれかが1で残りが0(=one-hotカテゴリ)を準備してラベルを作成します。

one-hotカテゴリをそのままラベルにする

y_4category = data[['インキ', '機構', '受容シート', '用途']].value
# y_4category
[[0, 1, 0, 0],

[1, 0, 0, 0],
[1, 0, 0, 0],
...,
[0, 0, 0, 1],
[0, 1, 0, 0],
[1, 0, 0, 0]]

one-hotカテゴリから1つのカテゴリ(用途)の on/off 2値ラベルを作る

「用途」列が 0, 1 の2値なので、1から引くことで 0は1になり、1は0になります。 

「用途」と「用途でない」とを結合するには、ここでは pandas.concat を使っていますが、 numpy.hstack  numpy.column_stack numpy.append なども使えます。

y_2category = pd.concat([data['用途'], (1-data['用途'])], axis=1).values
# y_2category
[[0, 1],

[0, 1],
[0, 1],
...,
[1, 0],
[0, 1],
[0, 1]]

整数のカテゴリからone-hot型のカテゴリを作る

整数のカテゴリをnumpy型に変換し、対角行列との積をとると得られます。

# 対角行列の作成
import numpy as np
Dmatrix = np.eye(max(data['ラベル'])+1) # ラベルは0-3 の4値なので最大値+1
label = Dmatrix[data['ラベル'].values] # pandas DataFrameからnumpy形式に変換
# 対角行列
[[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]]

# one-hot型のカテゴリ
[[0., 1., 0., 0.],
[1., 0., 0., 0.],
[1., 0., 0., 0.],
...
[0., 0., 0., 1.],
[0., 1., 0., 0.],
[1., 0., 0., 0.]]

Tensorflowに取り込まれる前のKerasや scikit-learnの

keras.utils.np_utils.to_categorical()
sklearn.preprocessing.OneHotEncoder() 

でも作れますので検索してみてください。

非ニューラルネットワーク

ニューラルネットワークモデルを使わない機械学習モデルを紹介しますが、初心者~中級者の誤解として、非ニューラルネットワークモデルは精度が低いというのと非ニューラルネットワークモデルは教師なし学習というものがあります。

これから取り上げるSVMとXGBoostは、機械学習コンペとして有名なKaggleで上位に現れる教師あり学習であり、分類タスクの精度としてはニューラルネットワークのもっとも精度が高いモデルと同等以上の性能を示します。

ここで、代表的な機械学習モデルを教師あり学習と教師なし学習、および非ニューラルネットワークとニューラルネットワークとで分類した表を示します。

  教師あり学習 教師なし学習
非ニューラルネットワーク 線形回帰
ロジスティック回帰
サポートベクターマシーン(SVM)
決定木(CART)
回帰木
ランダムフォレスト
ナイーブベイズ
勾配ブースティング木(XGBoost、LightGBM、CatBoostなど)
階層型クラスタリング(ユークリッド距離、ウォード法など)
非階層型クラスタリング(k-meansなど)
トピックモデル(LDAなど)
ニューラルネットワーク 畳み込みニューラルネットワーク(CNN)
再起型ニューラルネットワーク(RNN)
残差ネットワーク(ResNet)
自己組織化マップ(SOM)

この表は 代表的な機械学習手法一覧 を参考に作成しました。

SVMによる2値分類

ここではSVMの実装例を示します。教師データとして文書ベクトル x_[train_index]  、ラベル y_[train_index]  を与えます。また、推論データとして文書ベクトル x_[test_index]  、ラベル y_[test_index]   を与えます。

[train_index][test_index] は、文書データのシリアルNo.を重複がないように分けたものです。ここで重複があると、推論データの一部が学習に回り、学習モデルの本来の精度より良い(正しくない)結果が得られます。

from sklearn.svm import SVC
from sklearn.metrics import accuracy_score

kernel = 'rbf'
C = 0.01
gamma = 0.001
clf = SVC(kernel=kernel, C=C, gamma=gamma)
# 学習
clf.fit(x_[train_index], y_[train_index])
# 推論
pred_data = clf.predict(x_[test_index])
# 正答率算出
accuracy = accuracy_score(pred_data, y_[test_index])
print('未知データ推論,正答率:{0:.3%}'.format(accuracy))

リンクの実装例では、文書ベクトル が疎でメモリを消費するため、 scipy.csr_matrix   形式にしているのと、 [train_index]   、 [test_index]   は4-fold交差検証用に入れ子にしているため上のコードとは異なりますが、基本は一緒です。

加えて、SVMのハイパーパラメータとして、境界を定義する x_[test_index]  、誤分類の許容値 C、境界の複雑さ gamma  をいろいろと変えて最も良い精度を見つけるパラメータチューニング機能がscikit-learnにあるので、これを利用しています。計算時間を短縮するために最初から300個のデータでパラメータチューニングを行っています。

2値分類を開始
Fitting 5 folds for each of 56 candidates, totalling 280 fits
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 2 concurrent workers.
[Parallel(n_jobs=-1)]: Done 46 tasks | elapsed: 8.6s
[Parallel(n_jobs=-1)]: Done 196 tasks | elapsed: 26.5s
[Parallel(n_jobs=-1)]: Done 280 out of 280 | elapsed: 37.3s finished

最適化パラメータ: SVC(C=10, break_ties=False, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='scale', kernel='linear',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)

1回目 学習
未知データ推論,正答率:98.446%
2回目 学習
未知データ推論,正答率:99.112%
3回目 学習
未知データ推論,正答率:97.558%
4回目 学習
未知データ推論,正答率:98.559%

LightGBMによる4値分類

ここでは、勾配ブースティングの1つであるLightGBMの実装例を示します。SVMとほぼ同じですが、ラベルをカテゴリを表す整数(0-3)で表すため、 np.argmax  を用いて変換しています。

import lightgbm as lgb
# 学習
lgb_train = lgb.Dataset(x_[train_index], label=np.argmax(y_[train_index], axis=1))
lgb_eval = lgb.Dataset(x_[test_index], label=np.argmax(y_[test_index], axis=1))
model = lgb.train(lgbm_params, lgb_train)
# 推論
y_calc = model.predict(x_[test_index])
y_pred_max = np.argmax(y_calc, axis=1) # 最尤と判断したクラスの値にする
y_test_max = np.argmax(y_[test_index], axis=1)
# 正答率とRMSEを計算
acc = sum(y_pred_max == y_test_max) / len(y_pred_max)
RMSE = np.sqrt(mean_squared_error(y_[test_index], y_calc))
print(' 未知データ推論,正答率:{0:.3%},誤差RMSE:{1:.5}'.format(acc, RMSE))
パラメータ無調整 
mecab_TfIdfの4値分類を開始
1回目 学習 未知データ推論,正答率:94.895%,誤差RMSE:0.14596
2回目 学習 未知データ推論,正答率:94.451%,誤差RMSE:0.15605
3回目 学習 未知データ推論,正答率:93.230%,誤差RMSE:0.16304
4回目 学習 未知データ推論,正答率:94.346%,誤差RMSE:0.15739

実装例ではパラメータチューニングとしてoptunaを使ってみましたが、初めに設定したパラメータでよかったようです。

ソースコード

google Colaboratory で使用可能な jupyter notebook 形式で配布しています。

ラベル、非ニューラルネットワークモデル.ipynb

 

AI系基盤技術と、オープンソースを用いた機械学習による特許文書解析(補足3) をみる