0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ngram+ロジスティック回帰でDiscordのメッセージを監視したお話

Last updated at Posted at 2025-06-15

はじめに

Discordサーバーを運営していると時々、ルール通りに文言を投稿しない人が出てきますよね。
その都度指摘してはまた別の人が繰り返しまた、指摘は繰り返し...(以下略)
※今回の記事は自分の知識整理・備忘録として記載しています。

ある日

unnamed.png

何をしたの?

チャンネルで下記のような文言にそっているかDiscordボットで監視をして、その通りでなければ書き直すように指摘するボットを作りました。

作品名:
キャラの正確な名前:
英語の綴り:
希望する内容:
その他要望:

フローチャート

何の技術を使ったの?

ngram・LogisticRegression・TfidfVectorizer

使った言語は?

Python

実装

学習データ

下記のような形のデータを用意する。依頼だと思われるmsgに対しては1とラベル付けした。
※出所は自分のDiscordサーバーに投稿されている1万3500件程度のデータを人力でラベル付けした。

msg req
よろしくお願いします。 0
AIについての学習のため参加しました。 0
忙しい所悪い!この辺って依頼してもいいっすか?? 1
走っている少年の絵を描いてもらってもいいですか? 1
... ...

データ読み込み

texts = get_csv_column_as_list('dataset.csv', 'msg')
is_requests = get_csv_column_as_list('dataset.csv', 'req')
is_requests = list(map(int, is_requests))

データフレーム作成

from janome.tokenizer import Tokenizer

# Janome初期化
t = Tokenizer()

def tokenize_text(text):
    tokens = [
        token.surface
        for token in t.tokenize(text)
        if token.part_of_speech.split(',')[0] in ['名詞', '動詞', '形容詞']
    ]
    return " ".join(tokens)
    
df = pd.DataFrame({
    'text': texts,
    'is_request': is_requests
})
df['tokenized_text'] = df['text'].apply(tokenize_text)

全メッセージを形態素解析で品詞分解する。細かく分けることで、日本語の依頼っぽい文章の検出をやりやすくする。

パイプラインでまとめる

# パイプライン定義
pipeline = Pipeline([
    ('tfidf', TfidfVectorizer(ngram_range=(1, 2), max_features=1000)),
    ('clf', LogisticRegression(random_state=42, solver='liblinear', class_weight='balanced'))
])

tfidfの箇所

一段目のtfidfは文章(文字列)を数値のベクトルに変換することを目的としている。
TfidfVectorizer:単語の出現頻度を元にTF-IDFベクトルを作るクラス。
ngram_range:1-gram(単語)と2-gram(隣接する2単語の組)の両方を特徴量として使う。

例えば
「猫が好き」⇒「猫」「が」「好き」と「猫が」「が好き」に分けられる。

max_features:TF-IDF特徴量のうち上位何個までを使うか

clfの箇所

二段目のclfはtfidfでベクトル化されたテキストを使って、分類を行うことを目的としている。
LogisticRegression :2クラス(または多クラス)の分類に使えるモデル。
random_state:乱数シード値。本記事のプログラムは再現性を保つために固定する。
solver='liblinear':小~中規模のデータに適した計算アルゴリズム
class_weight='balanced':クラスに偏りがある場合、自動的にバラスを取ることで少数クラスを無視されにくくする。

クロスバリデーションによる分割

# スコア指標
scoring = {
    'precision_macro': 'precision_macro',
    'recall_macro': 'recall_macro',
    'f1_macro': 'f1_macro',
    'accuracy': 'accuracy'
}

# クロスバリデーション(5分割)
cv_results = cross_validate(
    pipeline,
    df['tokenized_text'],
    df['is_request'],
    cv=5,
    scoring=scoring,
    return_train_score=False
)

プログラムを言葉で説明すると、事前に定義したpipelineを使って、データであるdf['tokenized_text'](入力)とdf['is_request'](正解ラベル)を用いて、5分割で交差検証(cv=5)を行い、精度を計測する。

scoring:学習モデルの評価指標を複数指定できる。今回の場合、適合率・再現率・F1スコア・精度が出力される。

return_train_score=False:テスト側だけを評価する。Trueにすると、学習データ・テストデータの両方を評価する。

5分割で交差検証とは
データを5つに分割して、それを5回繰り返して学習と評価を行う。
※各回で、1つをテスト、残り4つを訓練に使う。

5分割交差検証結果
precision_macro recall_macro f1_macro accuracy
0.615 0.838 0.654 0.913

再現率が0.838と依頼の見逃しは少ないが、適合率が0.6程度なので、依頼と予測したが実際は違った(誤検出)もまぁまぁあるという結果に。まだまだ改善の余地はありそう。

動作確認

依頼系のメッセージ(ボットが反応するのが正解)
image.png

依頼系のメッセージだが、形式通りのもの(ボットが反応しないのが正解)
image.png

依頼系メッセージではないもの(ボットが反応しないのが正解)
image.png

次回もあるかも?

参考文献

・ChatGPT

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?