文字TF-IDFとXGBoostで日本語の問い合わせを分類する — 学習9件と45件の比較
日本語の問い合わせ文を、文字の並びから「料金・請求」「ログイン・認証」「操作・不具合」に分けてみました。文字TF-IDFとXGBoostを使い、学習文を9件から45件へ増やすと、固定した比較文18件の正解数は10件から11件に増えました。
どの文字が数値になり、45件で学習してもどんな文を間違えたのか。架空の問い合わせデータと保存結果からたどります。
文字の並びをTF-IDFで数値化し、決定木を順番に重ねる
架空の問い合わせを「料金・請求」「ログイン・認証」「操作・不具合」に分類します。この記事で変えるのは、XGBoostに渡す学習文の数です。同じ18件で結果を比べます。
文章は、連続する1〜3文字の断片を使ったTF-IDFベクトルへ変換します。語彙とIDFは学習文だけから作り、比較文には同じ変換を当てはめます。数値の計算や、語彙にない文字列の扱いは文字TF-IDFの実習で詳しく見ます。
TF-IDFの数値ベクトルをXGBoost(勾配ブースティング決定木)に入力して分類器を学習させます。1本の決定木は「特徴『ログイ』の値がしきい値以上か」といった条件分岐を上から順にたどってスコアを出します。
浅い決定木1本では複雑な境界を表現しきれませんが、XGBoostは前の段階までの予測と正解のズレ(損失)を小さくする方向に次の決定木を順番に学習し、複数の木の出力を足し合わせて最終予測を作ります。
下の図は、決定木を順番に足して予測を補正するブースティングの概念図であり、今回学習された木の実際の分岐条件を描いたものではありません。
実験で使用した文字TF-IDFの変換とXGBoostの学習コードは次の通りです。TfidfVectorizer で1〜3文字の特徴量を抽出し、XGBClassifier で3クラス分類の確率を学習させています。
vectorizer = TfidfVectorizer(analyzer="char", ngram_range=(1, 3))
X_train = vectorizer.fit_transform(train_text)
model = XGBClassifier(
n_estimators=80, max_depth=3, learning_rate=0.15,
objective="multi:softprob", eval_metric="mlogloss",
tree_method="hist", n_jobs=2, random_state=42
)
model.fit(X_train, train_y)
コード内の n_estimators=80 は、ブースティングを80ラウンド繰り返す設定です。3クラス分類(objective="multi:softprob")では1ラウンドごとに各クラス用の決定木が作られるため、モデル全体の木の総本数が80本という意味ではありません。
max_depth=3 は1本の木の深さを最大3段に抑える設定、learning_rate=0.15 は追加する木の出力を0.15倍して足し合わせる設定です。この記事の数値は、2026年10月12日にサーバーCPU環境(scikit-learn 1.7.2、xgboost 3.1.1)で確認した記録です。
学習文9件と45件で18件の比較文を判定する
学習データの件数による変化を見るため、学習文9件で学習した場合と、学習文45件で学習した場合を比べました。
XGBoostのハイパーパラメータ(n_estimators=80、max_depth=3、learning_rate=0.15 など)はどちらも同じに揃え、各クラス6件ずつ計18件の固定した比較文で正解数を数えます。下の図は、学習9件と学習45件での比較18件の正解数です。
比較18件の正解数は、学習9件で10/18件、学習45件で11/18件となり、1件増えました。
ただし、この1件の差を学習件数が増えたことだけの因果効果とは言えません。決定木の設定は同じでも、文字TF-IDFは各学習文だけでfitするため、学習文が9件から45件になると登録される1〜3文字の語彙(45件では1,056語彙)とIDFの重みも同時に変わるからです。
なお、比較18件は教材作成者が内容を確認して用意した固定データであり、盲検の未知データに対する評価や実務上の分類精度を示すものではありません。
45件で学習しても誤分類した問い合わせ文
学習45件のモデルでも誤分類となった7件の中に、次の問い合わせ文があります。
「ログインは問題ありませんが、請求の金額がおかしいです」
この文で「いま解決したいこと」は請求金額の確認なので正解は「料金・請求」ですが、学習45件のXGBoostは「ログイン・認証」と予測しました。
文の前半に「ログインは問題ありませんが」、後半に「請求の金額がおかしいです」とあります。1〜3文字を切り出す文字TF-IDF(4文字以上の文字列は特徴量に含まれません)では、「ログ」「ログイ」「グイン」などの1〜3文字特徴と、「請求」「金額」部分の1〜3文字特徴の両方が非ゼロの値になりますが、「前半は問題ない前提で後半が本題」という文構造を直接表しているわけではありません。
なお、特徴重要度やどの木の分岐がこの誤分類の直接原因かを切り分ける調査は行っていません。今回の保存結果から言えるのは、前半で別の話題を否定して後半で本題を述べるこの文を、45件学習のモデルでも正しく分類できなかったという事実です。
こうした誤分類を減らそうとして、比較用18件の結果を見ながら文字切り出しの範囲や木の設定を何度も調整すると、その18件だけに合わせた設定になってしまいます。
データセットに学習用45件・比較用18件とは別に調整用9件を用意しているのはこのためです。設定を試行錯誤するときは調整用データで確認し、比較用18件の誤答を見て設定を変えた場合は、まだ結果を見ていない新しい比較文で改めて確かめる必要があります。
学習文の数を変えて再実行する
共通ノートブックでは、①ライブラリ → ②共通設定 → ③データ → ④ルール → ⑥分類器 → ⑨保存の順に実行します。④には共通の評価関数があるため、XGBoostでも必要です。⑤・⑦・⑧は飛ばせます。
9件から45件への比較では、分類器の学習文だけでなく、TF-IDFの語彙とIDFも変わります。次に条件を分けたい場合は45件で語彙を固定し、木の深さmax_depthだけを変える、といった手順で試します。
実装の仕様を確認した公式資料
- TfidfVectorizer — scikit-learn公式API
文字n-gram、語彙とIDFの学習、L2正規化の仕様。
- XGBClassifier — XGBoost公式API
n_estimatorsなどの設定。記事の実行版は3.1.1です。
- XGBoost: A Scalable Tree Boosting System(2016)
XGBoostの原論文。
確認日:2026年10月19日。