IBMの基盤モデルSlateでカスタム分類モデルを試してみた

# 1. はじめに
[Watson NLP (Natural Language Processing)](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp.html) ライブラリーは、エンティティ抽出、テキスト分類、感情分類、キーワード抽出などのさまざまな自然言語処理のモデルを提供しています。
現在は [**watsonx.ai**](https://www.ibm.com/products/watsonx-ai) としてIBMの大規模言語モデルSlateが利用できるようになっています。
Slateでは、独自データを使用してカスタムモデルを作成することで、以下のことができます。
* エンティティの抽出
* テキストの分類
* 評判の抽出

ここではSlateを使用してテキスト分類を行うカスタムモデルの作成と、カスタムモデルの分類精度の評価を行います。

# 2. 準備
### 2.1. 実行環境
[Watson Studio](https://cloud.ibm.com/catalog/services/watson-studio) の Jupyter Notebook を使用します。
Slate を利用するためには Watson Studio で 新規Notebook を作成する際に Runtime 23.1 を選択する必要があります。
### 2.2. 使用するデータ
カスタム分類モデルを作成するために、学習用の分類ラベル付きデータをCSV形式で準備してWatson Studioプロジェクトの資産にアップロードします。
今回分類評価に使用するデータは [Livedoorニュースコーパス](https://www.rondhuit.com/download.html) をダウンロードして、CSVに変換して利用しています。
9種類のニュースカテゴリーを作成し、新たな記事を分類の結果を確認します。

CSVファイルはUTF-8形式で、以下の形式となります。
・形式は **text,labels** （textはサンプル文書、labelsはカテゴリー名）
今回はデータとしてニュース記事をtext、記事カテゴリーをlabelsとしてCSVファイルを作成しています。
学習用データ：livedoor-train.csv
評価用データ：livedoor-eval.csv
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1322004/7a464cac-084c-4f12-0ece-cbf3f234ac3b.png)

準備した学習用と評価用のデータはプロジェクトのデータとしてアップロードしておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1322004/7551ad9a-3fe4-6a7d-592e-c0bcc70fe690.png)

:::note info
今回と同様の書式でCSVデータを準備することで、以下の手順で分類モデルの作成と精度評価を行うことができます
:::

## 3. データの読み込み
Watson Studioに登録したデータ資産にアクセスするために、プロジェクトトークンを作成して、以下の **プロジェクトトークン** を書き換えて実行します。詳細な手順は [ibm-watson-studio-lib (Python用)](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ws-lib-python.html?locale=ja&context=cpdaas) を参照してください。
```py
# プロジェクトにアップロードしてあるデータにアクセスするための設定
from ibm_watson_studio_lib import access_project_or_space
wslib = access_project_or_space({"token":"プロジェクトトークン"})
```
関連ライブラリを取得して、準備してある学習用データと評価用データを読み込みます
```py
import json
import seaborn as sn
import matplotlib.pyplot as plt
import pandas as pd
# 関連するテキストを探せるように、テキスト表示を大きくします
pd.options.display.max_colwidth = 400

import watson_nlp
from watson_core.data_model.streams.resolver import DataStreamResolver
from watson_nlp.blocks.classification.svm import SVM

# 学習データと評価データの２つのCSVファイルを準備します。ヘッダー行が必要で、今回は"text"と"labels"を前提としています
training_data_file = wslib.load_data("livedoor-train.csv")
eval_data_file = wslib.load_data("livedoor-eval.csv")

# 学習データをデータフレームに取り込む
train_orig_df = pd.read_csv(training_data_file)
eval_df = pd.read_csv(eval_data_file)
# 学習データの最初の2行を表示
train_orig_df.head(2)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1322004/ea404b68-47da-ea7c-f57d-6f8c15d3133f.png)

学習時に利用するデータはJSONファイルとして保存しておきます。
```py
# 学習データをJSONファイルとして保存
def prepare_data(df):
    # textカラムと分類カテゴリの*labels*が必要
    df_out = df[['text', 'labels']].reset_index(drop=True)
    # labels列は配列でなければなりません (1つしかない場合でも同様)
    df_out['labels'] = df_out['labels'].map(lambda label: [label,])
    return df_out
    
train_df = prepare_data(train_orig_df)
train_json_file = './train_data.json'
train_df.to_json(train_json_file, orient='records')
```

# 4. Slate分類モデルの学習
IBMのSlate基盤モデルを使用するトランスフォーマーアルゴリズムは、マルチクラスおよびマルチラベルのテキスト分類に使用できます。
[マニュアル](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-classify-text.html?locale=ja&context=cpdaas&audience=wdp#train-slate)に従ってSlateの分類モデルを作成します。
```py
# Slateの学習モデルを作成
from watson_nlp.blocks.classification.transformer import Transformer
from watson_core.data_model.streams.resolver import DataStreamResolver

# 学習データからデータストリームを作成
data_stream_resolver = DataStreamResolver(target_stream_type=list, expected_keys={'text': str, 'labels': list})
train_stream = data_stream_resolver.as_data_stream(train_json_file)

# 学習済みのSlateモデルをロード
pretrained_model_resource = watson_nlp.load('pretrained-model_slate.153m.distilled_many_transformer_multilingual_uncased')

# Slateの学習モデルを作成。学習回数はnum_train_cpochsで指定
classification_model = Transformer.train(train_stream, pretrained_model_resource, num_train_epochs=25)
```
***** train metrics *****
  epoch                    =       25.0
  total_flos               =  2426763GF
  train_loss               =     0.4753
  train_runtime            = 0:13:34.33
  train_samples            =       3684
  train_samples_per_second =    113.098
  train_steps_per_second   =     14.153

:::note info
学習を繰り返す回数（エポック数）は **num_train_cpochs** で指定します。デフォルトは５で、数を増やしすぎると過学習になる場合もあります。
:::

これでSlateのカスタム分類モデルが作成できました。試しに適当な文書を使って分類を実行してみます
```py
text = '大谷翔平は近代のプロ野球では非常に稀な存在となる、シーズンを通して投手と打者を兼任する「二刀流（英: two-way player）」の選手。投手としての球速165 km/hは藤浪晋太郎・佐々木朗希と並んで日本人最速記録である。また、先発投手兼打者として、NPBで1度のリーグ優勝、1度の日本シリーズ優勝、日本代表では、1度のワールド・ベースボール・クラシック（WBC）優勝に大きく貢献している。'
slate_preds = classification_model.run(text)
slate_preds
```
{
  "classes": [
    {
      "class_name": "sports-watch",
      "confidence": 0.9569842219352722
    },
    {
      "class_name": "livedoor-homme",
      "confidence": 0.015390502288937569
    },
    {
      "class_name": "movie-enter",
      "confidence": 0.008483590558171272
    },
・・・
分類結果が得られました！ sports-watch なので正しい結果に見えます。

## 5. SVM分類モデルの学習
比較対象として、[マニュアル](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-classify-text.html?locale=ja&context=cpdaas&audience=wdp#train-svm)に従ってSVM(サポート・ベクトル・マシン)の機械学習を使用した分類モデルを作成します。

```py
# 構文モデル
syntax_model = watson_nlp.load('syntax_izumo_ja_stock')
# USE埋め込みモデル
use_model = watson_nlp.load('embedding_use_multi_small')

# 学習データからデータストリームを作成
data_stream_resolver = DataStreamResolver(target_stream_type=list, expected_keys={'text': str, 'labels': list})
training_data = data_stream_resolver.as_data_stream(train_json_file)

# 構文ストリームを作成
text_stream, labels_stream = training_data[0], training_data[1]
syntax_stream = syntax_model.stream(text_stream)

use_train_stream = use_model.stream(syntax_stream, doc_embed_style='raw_text')
use_svm_train_stream = watson_nlp.data_model.DataStream.zip(use_train_stream, labels_stream)

# SVMモデルの学習
svm_model = SVM.train(use_svm_train_stream)
```

# 6. 評価用データで分類を実行

２つのカスタム分類モデルが作成できたので、評価用データで分類を実行し、各モデルの分類結果の確認します。
では、評価用データで分類モデルを実行します
```py
# SlateとSVMのモデルで評価
def predict_product(text):
    # 最初に構文モデルを実行
    syntax_result = syntax_model.run(text)
    # 構文結果に基づいてSVMモデルを実行
    svm_preds = svm_model.run(use_model.run(syntax_result, doc_embed_style='raw_text'))
    predicted_svm = svm_preds.to_dict()["classes"][0]["class_name"]
    # Slateモデルを実行
    slate_preds = classification_model.run(text)
    predicted_slate = slate_preds.to_dict()["classes"][0]["class_name"]
    return (predicted_svm, predicted_slate)
predictions = eval_df["text"].apply(lambda text: predict_product(text))
predictions_df = pd.DataFrame.from_records(predictions, columns=('SVM予測値', 'Slate予測値'))
   
result_df = eval_df[["text", "labels"]].merge(predictions_df, how='left', left_index=True, right_index=True)
result_df.head()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1322004/1874395d-4176-80bd-0637-1896cc0e0842.png)
評価用データの右に分類結果として、分類カテゴリの予測値が２列追加されています。 labelが正解の列なのでそれと比較することで精度評価が行えます。以降では混同行列(Confusion Matrix)を使用して結果を可視化します。

# 7. 分類結果からモデルの品質を比較
### 7.1. 混同行列の作成とプロット
分類結果の精度の指標として、混同行列を使用して評価をしてみます。
ここでは pandas crosstab を使用して、SlateとSVMの分類精度の混同行列を作成します。Seaborn ヒートマップとしてプロットしています。
```py
SVM_confusion_df = pd.crosstab(result_df['labels'], result_df['SVM予測値'], rownames=['Actual'], normalize='index')
Slate_confusion_df = pd.crosstab(result_df['labels'], result_df['Slate予測値'], rownames=['Actual'], normalize='index')

figure, (ax1, ax2) = plt.subplots(ncols=2, figsize=(15,7))

sn.heatmap(SVM_confusion_df, annot=True, cmap="YlGnBu", ax=ax1, cbar=False)
sn.heatmap(Slate_confusion_df, annot=True, cmap="YlGnBu", ax=ax2, cbar=False)
ax1.title.set_text("SVM")
ax2.title.set_text("Slate")
ax2.set_yticklabels([])

plt.show()
```
Slate分類モデル作成時のエポック数を５にした場合とSVMモデルの比較
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1322004/28c2ad5d-b736-f274-b021-f1093261a060.png)
Slate分類モデル作成時のエポック数を２５にした場合とSVMモデルの比較
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1322004/9c8a89a7-7a34-99f2-86c1-c42d35e4ffbf.png)

今回のデータでは、Slate分類モデルの作成時にエポック数を２５にしたところ、分類結果としてSVMモデルよりも優れた結果が得られました。他にも学習時のオプションやパラメータの指定を行うことで精度が変わることは考えられます。
分類モデルはこれ以外にもアンサンブルモデルやHugging Faceのモデルが利用できます。[アンサンブルモデル](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-classify-text.html?locale=ja&context=cpdaas&audience=wdp#train-ensemble)を使うことで、高い精度のモデルを作成することを確認しています。

## 8. 作成したカスタム分類モデルの保存
作成したカスタム分類モデルを後から利用できるように、環境に保存します。
```py
# 作成したSlate分類モデルを保存
wslib.save_data('classification_model', data=classification_model.as_bytes(), overwrite=True)
# 作成したSVM分類モデルを保存
wslib.save_data('svm_model', data=svm_model.as_bytes(), overwrite=True)
```
このモデルを後から使うときは、次のようにして読み込んで利用できます
```py
custom_model = watxon_nlp.load(wslib.load_data('classification_model'))
```

---
# 参考情報
**Watson NLP (Natural Language Processing) ライブラリ**
https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp.html

**カスタム分類モデルによるテキストの分類**
***IBM Slate モデルを使用した変換プログラム・アルゴリズムのトレーニング***
https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-classify-text.html?locale=ja&context=cpdaas&audience=wdp#train-slate

**Watson NLP（自然言語処理ライブラリー）：テキスト分類**
https://qiita.com/Jungobu/items/d9a1f1b0ee4c3efc7df2

**Fair is fast, and fast is fair: IBM Slate Foundation models for NLP**
https://medium.com/@alex.lang/fair-is-fast-and-fast-is-fair-ibm-slate-foundation-models-for-nlp-3508412a4b04