はじめに
久々に機械学習ネタ。とある文章の集まったDBにおける分類を学習したモデルの作成を行い、別のDBの文書分類に使おう、というプロジェクト。詳細は伏せるが、概略は次の通り。
学習対象のDBには、それぞれの文書の情報が、分類の種類、文章(生データ)、文章から抽出した特徴語のリストが登録されている。ちなみに全部英文。英語フレーズ。特徴語の抽出は、とあるアルゴリズムでDB内全文書に共通したものが適用されている。
文章(生データ)と分類種類の関係を学習してもいいかもしれないが、それは分類の特徴を得ない自然な言葉、単語が多く含まれてしまって分類に関係ない情報を省くなどの無駄な処理が入ってしまい、学習にも時間がかかるのではないか、と考え、特徴語(フレーズ)と分類の種類を学習してみることにした。
目的
特徴語と文書分類の関係を機械学習した判別分析モデルを構築する。その応用として、別のDBの文書の自動分類システムとして組み入れる。
方法
環境
Windows11 (insider preview) WSL2 ← ”WSL2でCuda"にチャレンジします。
Xeon E5-2620(AVX対応) x2 (12Cores 24Threads) ←強そうに見えて、めっちゃ弱い。古い。ぎりぎりTensorFlowが動くレベル(💦
Memory 32GB DDR4
Geforce GTX1080 8GB ← こいつはまだ元気!TeslaPモデルの中古がWindowsで動くならRTX4000番台高くて買わねぇよ。
参考情報は、いろいろネットで調べる。(たとえば)
実装・実験
1)試験的に Bag of Words
Sklearn CountVectorizerによる処理を試してみた。
# sklearnのライブラリを用いて文章をbag-of-words形式に変換
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer(token_pattern="(?u)\\b\\w+\\b")
X = vectorizer.fit_transform(df['keywords_en'].apply(lambda x: " ".join(x)))
CountVectorizer の引数、token_pattern のところの正規表現は、公式ドキュメントと同じでいいだろう。公式だって、英単語が渡されることが想定されているだろうから。(日本語を分析するときは、token_patternが違うらしい。参考:https://blog.deepblue-ts.co.jp/nlp/countvectorizer_token/)
さて、これで、1000文書特徴語学習開始してみよう。学習には、MLPClassifierを使ってみよう。マルチパーセプトロン。甘利先生、お元気でいらっしゃるかなあ・・・
from sklearn.neural_network import MLPClassifier # アルゴリズムとしてmlpを使用
# Model設定
model = MLPClassifier(max_iter=300, hidden_layer_sizes=(100,),verbose=10,)
MLPClassifierの引数は、なんとなく作るもんだけど、ほかのひとのHPとかで使われてることが多いように感じる数字にしておいた。
さて、開始!
・・・2時間経っても終わらない。
・・・うーん、数日放置するか・・・
いや待て、それもいいかもしれないけど、別の方法にしてみようぜ。
Bag of Words を活用して、分析・学習手法を決定木にするとか。
2)BERT (Transformer)
GPUを活用してみるかなぁ・・・・と思ったので、機械生命体のやつを、地球で大暴れさせて困っちゃおうぜ。今回は、以下の先達の情報を参考に。
こちらの先達は「GPUのメモリ不足エラーを回避できなかった」とのことだったので、じゃぁ、私の環境では回避できるのか?自信がないがチャレンジしてみることにした。
<作業のポイント>
最初のポイントはWSL2+cuda、次にPytorch、TransformerのGPU対応版インストール、そしてGPUメモリにノッケて学習ではなかろうか。
まずは、WSL2でCuda実行環境をインストールしよう。公式に従えば(nvidia ドライバが新しくなってはいるが)迷いはない。
次にPytorchだが、これも公式の案内に従えばいけるはず・・・(WSL2は"Linux"だよね?)
インストールできたら、pythonで動作確認してみる。
import torch
torch.cuda.is_available()
結果、True が返れば、ここまではオッケー。
Transformerのインストールは、次のようにすればよいらしい。 (先達の「SimpleTransformer:導入」参照)
python3 -m pip install tqdm transformers tensorboardx simpletransformers
pandas や numpy , scipy など自分の環境、作業に合わせて追加すること。
「たぶん動くと思うから、動かしてみようぜ」
上記で紹介した、先達の「SimpleTransformer:使ってみる」には、おおもとになっている記事がある。
このおおもとの記事の「Prepare the data for training」をみると、学習用のデータのフォーマットがわかる。
The first column contains the text and is of type str.
The second column contains the labels and is of type int.
For multiclass classification, the labels should be integers starting from 0.
この形にデータを整形する。Pythonに慣れた人なら楽勝だろうから読み飛ばしてもらって構わないけれど、私は備忘録として処理を書き残させてもらおう。
import pandas as pd
# DBからjson形式で出力しておいたデータを、 pandas で読み込む
json_data = pd.read_json("DB_table.json")
# データの欠損があれば、対応する処理をここに書く
# (省略)
# 特徴語(keyword)は、英語フレーズのリストなので、これをスペース区切りで連結する。(フレーズではなく単語の連続として扱われることになることが結果に影響するかどうか・・・Bag of Words の考え方でいけば大差ないはず。)
text = json_data['keyword'].apply(lambda x: " ".join(x))
# ラベルは、int にしろ、とのことなので、sklearnの LabelEncoder()で、ラベルのテキスト(label_text)を数値表現にしてしまおう。数字abc昇順でソートしたデータに、ゼロから順の番号を割り付ける関数だそうだ。この数字の”連続性”が結果に影響するかどうか・・・。今回、ラベルの単語間にどれほどの距離(差異)を考えるかはわからんし、何をもって数値的連続を考えたらいいかわからん。「これは順序尺度や感覚尺度でなくてよい変数であり、数字だけど名義尺度として扱われるに違いない」と信じよう・・・。
from sklearn.preprocessing import LabelEncoder
class_le = LabelEncoder()
label = class_le.fit_transform(df['label_text'])
# train_text と train_label の対応は、リストの順番通りという前提を崩さないように、順番をランダムに入れ替える。だって、データがlabel順になってたらさ、このあとデータを訓練用と評価用で分割するとき、特定のラベルの集合で分割してしまうじゃん。はじめからランダムな順のデータならこの処理は不要だけどね。
import random
# 読み込んだデータセットをシャッフルする
p = list(zip(text,label))
random.shuffle(p)
x, y = zip(*p)
# 学習データの件数を指定する
train_size = 1000
test_size = len(text) - train_size
# データセットを学習データとテストデータに分割する
train_text = text[:train_size]
train_label = label[:train_size]
test_text = text[train_size:]
test_label = label[train_size:]
# リストとテキストを pandas データフレームに。この前提が保証されるように注意しながらここまでの処理を行っていて頂戴。
# 訓練データのpandas.dataFrameを作る
# 試したところ、 所要時間は、方法1、方法3、方法2の順で短かった。
# 方法1: リスト→Series変換→Seriesをpd.concat連結してdataFrame
text_series = pd.Series(train_text)
label_series = pd.Series(train_label)
train_df = pd.concat([text_series, label_series], axis=1)
print(train_df) # 確認用
# 方法2: リストを二次元配列化→二次元配列リストからdataFrame生成→転置する
t = [train_text, train_label]
train_df_t = pd.DataFrame(t)
train_df = train_df_t.T
print(train_df) # 確認用
# 方法3: リストをzip→zipリストからdataFrame生成
p = list(zip(train_text,train_label))
train_df = pd.DataFrame(p)
print(train_df) # 確認用
# データフレームにヘッダをつける
train_df.columns = ['text','label']
モデルの用意
こちらを参考に
from simpletransformers.classification import ClassificationModel
# Create a ClassificationModel
model = ClassificationModel('roberta', 'roberta-base', use_cuda=cuda_available, num_labels=max(train_label)+1)
一番最後の、num_labels の値は、sklearnの LabelEncoder()で割り付けた数字に1を足すことで表す。0からの割り付けだから、最大の数字より1多くなるのよ。
ClassificationModelの引数には、Model_type を文字列で渡すそうだ。'bert'でいい気もするが?何を指定できるかは、HuggingFaceのドキュメントを参照しろ、だってさ(https://github.com/ThilinaRajapakse/simpletransformers#current-pretrained-models)。
Current Pretrained Models
For a list of pretrained models, see Hugging Face docs. → https://huggingface.co/models
そして、なんといっても目玉は、use_cudaオプションですよ。 "cuda_available"で書いておけば、使えるときは使うし、使えないときにエラーを出さないってさ。(https://simpletransformers.ai/docs/usage/#enablingdisabling-cuda)
to ensure that your script will use CUDA if it is available, but won’t error out if it is not.
実行・・・すっけんどもよ、"cuda_available"は定義がないから引数につかえねぇってエラーがでっぞ。オラァこりゃわかんね(いなかっぺ大将)
まぁ、よくわかんねっけど、GPU認識してるPytorch使ってるから、 True に置き換えて再度実行。
モデルのダウンロードやらで1分ぐらいかかる。モデルのダウンロード後、なんやら警告(Warning)が出るけど無視すっぞ。
よっし、修行(訓練)すっか。
model.train_model(train_df)
よーし、これでしばらく放置だ。修行はPCに任せて、おら休憩すっぞ。(←これ、重要。)
(次回に続く)
とりあえず、SimpleTransformerを使って文書分類の訓練を行い、文書分類器を生成開始するところまではできた。機械学習を開始してみたが、何時間かかるかな?
PCの学習処理が終わったら、報告します。次回、乞うご期待。