RPAによるCMS投入前に、機械学習で入稿データの誤りを検出する
X-tech推進本部 小林Webサイトの更新作業では、CMSへのニュース記事投入をRPAで自動化することがあります。例えば、CSVにまとまった入稿データを読み込み、タイトル、本文、カテゴリなどをCMSへ登録していく処理です。手作業で1件ずつ登録するより速く、入力漏れや転記ミスも減らせるので、とても便利です。
RPA自体の実装ミスに関しても、投入後のページから取得できる情報が元の投入データと一致するか、といった方法で検証できます。RPAは基本的に「決まったルール通りに同じ操作を繰り返す」ものなので、そのルールが正しく動いているかは機械的に確認しやすいわけです。
しかし、ここで1つ問題があります。「そもそも入稿データ自体が間違っていたら、RPAはそのまま間違った内容を登録してしまう」ということです。例えば、記事のカテゴリが間違っているケースです。
タイトル:大学と共同で異常検知アルゴリズムの研究を開始
カテゴリ:商品・サービス
このようなデータが渡された場合、RPAは「商品・サービス」カテゴリとしてCMSに投入します。人間からすれば「これは研究開発カテゴリでは?」と気づくかもしれませんが、RPAから見れば、指定されたカテゴリがそのまま「正しい」選択です。
そこで、CMS投入前の入稿データチェックとして、「記事のタイトルと本文からカテゴリを自動推定し、誤りが疑われる入力カテゴリを指摘する」簡単なプログラムを作ってみます。
入稿データの誤りをどう見つけるか
カテゴリ間違いを見つける方法として、まず思いつくのはルールベースのチェックです。例えば、次のようなルールを作る方法です。
- 「研究」「実証」「検証」を含んでいたら研究開発
- 「採用」「役員」「制度」を含んでいたら人事
- 「環境」「地域」「削減」を含んでいたらサステナビリティ
これはシンプルで分かりやすい一方、実際に運用しようとするとかなり大変です。
記事タイトルや本文の表現は毎回少しずつ違います。既存のパターンに合わない文言を見つけるたびにルールの修正が必要になり、最終的に複雑すぎてメンテナンス自体が困難なプログラムになってしまうかもしれません。
あるいは、LLMに判定させる方法も考えられます。LLMにタイトルと本文を渡して、「このカテゴリは妥当ですか?」と聞けば、それなりに自然な判断をしてくれます。ただし、外部APIを使う場合、コストやセキュリティ(未公開情報を渡して良いか)が気になる場合もあります。ローカルLLMを使うという方法もありますが、実行時間がかかったり、ある程度のマシンスペックが必要だったりします。
そこで今回は、情報を外部に送信せず、かつローカルLLMよりも軽い方法として機械学習を使います。過去に投入済みのデータを学習材料にして、対象のサイト内で「どのような文章がどのようなカテゴリに分類されやすいか」という傾向をモデルに覚えさせ、新しい投入データの判定に使います。
今回作るもの
今回作るのは、次のような流れのプログラムです。Pythonの機械学習ライブラリscikit-learnを使います。
- 過去のCMS記事データを読み込む
- タイトルと本文をもとにカテゴリ分類モデルを学習する
- 新規投入予定の記事データを読み込む
- 各記事のカテゴリを推定する
- 入力されているカテゴリと推定カテゴリを比較する
- 比較結果をCSVに出力する
1、2の処理をtrain.py、3~6の処理をcheck.pyとして実装します。
なお、説明をシンプルにするため、モデルの性能評価は省略しています。実運用では、過去データの一部を評価用として分けるなど、未知の記事をどの程度正しく分類できるか確認すると良いでしょう。
既存データを学習させる
まず、過去に公開済みの記事データとして、次のようなcms_training_data.csvがあるとします(T006以降は省略)。
| id | title | body | category |
|---|---|---|---|
| T001 | 法人向けクラウドサービスの提供を開始 | 当社は中小企業の業務効率化を支援する... | 商品・サービス |
| T002 | 画像認識技術を用いた検品支援の実証実験を開始 | 当社は製造ラインにおける外観検査の効率化を目指し... | 研究開発 |
| T003 | 執行役員の異動に関するお知らせ | 当社は取締役会において執行役員の異動を決定しました... | 人事 |
| T004 | 再生可能エネルギー由来電力の導入を拡大 | 当社は事業所で使用する電力について... | サステナビリティ |
| T005 | 大学と共同で設備データ分析の共同研究を開始 | 当社は大学と共同で異常検知アルゴリズムの研究に取り組み... | 研究開発 |
このデータを以下のtrain.pyで読み込み、カテゴリ分類モデルをcategory_model.pklとして出力します。
# train.py
import pickle
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
TRAINING_CSV = "cms_training_data.csv"
MODEL_FILE = "category_model.pkl"
def make_text(df):
return df["title"].fillna("") + " " + df["body"].fillna("")
training_df = pd.read_csv(TRAINING_CSV) # 1.過去のCMS記事データを読み込む
model = Pipeline(
[
("tfidf", TfidfVectorizer(analyzer="char", ngram_range=(2, 4))),
("clf", LogisticRegression(max_iter=1000)),
]
)
model.fit(make_text(training_df), training_df["category"]) # 2. タイトルと本文をもとにカテゴリ分類モデルを学習する
with open(MODEL_FILE, "wb") as f:
pickle.dump(model, f)
print(f"{MODEL_FILE} にモデルを保存しました。")
既存データをモデルに学習させるにあたり、scikit-learnが提供するTfidfVectorizerクラスとLogisticRegressionクラスを使います。
TfidfVectorizerの役割は、TF-IDFと呼ばれる指標を用いて、記事データを機械学習で扱える数値ベクトル(数値の並び)に変換することです。実装例では、文字n-gramを使用し、文章を2~4文字の連続した文字列に分解して、それぞれの文字列の重要度を特徴量にしています。日本語の文章を機械学習で扱う場合、形態素解析ライブラリで単語に分ける方法もありますが、文字n-gramでもカテゴリごとの表現の傾向をある程度学習できます。実務では必要に応じて手法を選択すると良いでしょう。
LogisticRegressionの役割は、ロジスティック回帰と呼ばれる機械学習アルゴリズムを用いて、TfidfVectorizerが作成した特徴量とカテゴリとの関係を学習することです。これにより、あとで新しい記事を投入するときにも、カテゴリを予測することができるようになります。
新規投入データをチェックする
続いて、これからCMSに投入したい新規データとして次のようなcms_new_articles.csvがあるとします。N002で本来「研究開発」とするべきカテゴリを「商品・サービス」にしてしまっています。
| id | title | body | category |
|---|---|---|---|
| N001 | 法人向け勤怠管理サービスの新プランを開始 | 当社は勤怠管理サービスに小規模企業向けの新プランを... | 商品・サービス |
| N002 | 大学と共同で異常検知アルゴリズムの研究を開始 | 当社は大学と共同で設備データを用いた異常検知アルゴリズム... | 商品・サービス |
| N003 | 組織変更および新部門設置のお知らせ | 事業拡大に伴い組織変更を実施し... | 人事 |
以下のcheck.pyでは、このCSVと上記train.pyの実行で保存したモデルcategory_model.pklを読み込み、新規投入データのタイトルと本文からカテゴリを推定し、結果(入力カテゴリと推定カテゴリが一致するか)をoutput.csvに出力します。
# check.py
# 各importとmake_text()関数は省略しています
NEW_ARTICLES_CSV = "cms_new_articles.csv"
MODEL_FILE = "category_model.pkl"
OUTPUT_CSV = "output.csv"
with open(MODEL_FILE, "rb") as f:
model = pickle.load(f)
new_df = pd.read_csv(NEW_ARTICLES_CSV) # 3. 新規投入予定の記事データを読み込む
texts = make_text(new_df)
predicted_categories = model.predict(texts) # 4. 各記事のカテゴリを推定する
results = new_df[["id", "title", "category"]].copy()
results["predicted_category"] = predicted_categories
def judge(row):
if row["category"] != row["predicted_category"]:
return "カテゴリ不一致の可能性あり"
return "OK"
results["judge"] = results.apply(judge, axis=1) # 5. 入力されているカテゴリと推定カテゴリを比較する
results.to_csv(OUTPUT_CSV, index=False, encoding="utf-8-sig") # 6. 比較結果をCSVに出力する
print(f"{OUTPUT_CSV} を出力しました。")
今回は実装をシンプルにするために単純に推定カテゴリの一致・不一致のみ出力しています。実運用では、各カテゴリに分類される予測確率をmodel.predict_proba(texts)で取得し、値に応じて(確率が低い、拮抗しているなど)「要確認」と判定する、などの処理を加えても良いでしょう。
なお、pickle形式のモデルファイルを読み込む場合、信頼できないファイルを読み込むと任意のコードが実行されるセキュリティリスクがありますので、外部のモデルファイルを読み込む場合などは注意しましょう。
出力結果
check.pyの実行結果として出力されるCSVoutput.csvは、例えば次のような内容です。
| id | title | category | predicted_category | judge |
|---|---|---|---|---|
| N001 | 法人向け勤怠管理サービスの新プランを開始 | 商品・サービス | 商品・サービス | OK |
| N002 | 大学と共同で異常検知アルゴリズムの研究を開始 | 商品・サービス | 研究開発 | カテゴリ不一致の可能性あり |
| N003 | 組織変更および新部門設置のお知らせ | 人事 | 人事 | OK |
このように、入力されているカテゴリと、タイトルおよび本文から推定したカテゴリが違うものを検出できます。
もちろん、必ずしも判定がうまくいくとは限りません。今回はかなり分かりやすい例を用いましたが、実際の判定の性能は過去データの量や質、カテゴリの複雑さ、採用する手法などに依存します。実務ではより多くの工夫が必要ですが、それでもチェック方法の1つとして、十分に試してみる価値があると思います。
まとめ
今回は、CMS投入データのカテゴリ間違いを、Pythonの機械学習ライブラリを用いてチェックする簡単な例を紹介しました。scikit-learnを使ったシンプルな分類であれば、外部APIも不要で軽量に動かせるので便利です。ルールベースより柔軟で、LLMより軽量な方法として、こうした小さな機械学習の活用も検討してみてはいかがでしょうか。