🍖

OpenAI API と Marvin を使ったお手軽スパム判定

2023/05/19に公開

概要

Marvin という面白ライブラリを以下の記事で知り、その応用先としてスパム判定が良さそうだなと思い試したところ、結構いい感じだったので記事にしてみました。

バージョン情報

  • marvin==0.9.0

Marvin とは

OpenAI の API を用いて、具体的な実装なしに、指定したふるまいをする関数が作れる等の機能を持ったライブラリです(他の機能や詳細は以下リンク参照)。
なお、内部的には LangChain を使用しているようでした。

使い方は非常に簡単で、環境変数として MARVIN_OPENAI_API_KEY に OpenAI API のキーを指定し、後は from marvin import ai_fn のように import して使うだけですね。

例えば、以下のように、引数と返り値の型、関数の docstring だけの関数を作成し、@ai_fn デコレータを付与したものを用意します。

from marvin import ai_fn

@ai_fn
def list_japanese_prefectures(n: int) -> list[str]:
    """Generate a list of N prefecture names in Japan.

    Args:
        n (int): number of prefectures

    Returns:
        list[str]: prefectures
    """

これをそのまま使用するだけで、想定通り動きます。

prefectures_list = list_japanese_prefectures(10)
print(prefectures_list)

# 実行結果
# ['Hokkaido', 'Aomori', 'Iwate', 'Miyagi', 'Akita', 'Yamagata', 'Fukushima', 'Ibaraki', 'Tochigi', 'Gunma']

本題のスパム判定

もう少し難しいことがさせたい & 何かしら実用的に役に立つものを作りたいのでスパム判定をさせてみました。
先ほどと同様に関数を定義します。bool だけでは面白みにかけるので、返り値は、判定理由を含めた dataclass にしてみました。

from marvin import ai_fn
from dataclasses import dataclass

@dataclass
class SpamJudgeResult:
    isSpam: bool
    reason: str

@ai_fn()
def judge_spam(comemnt: str) -> SpamJudgeResult:
    """judge whether comment is spam or not.

    Args:
        comemnt (str): comment

    Returns:
        SpamJudgeResult:
            isSpam: comment is spam -> True. If not spam -> False.
            reason: detailed explanation why this comment is spam or not in Japanese.
    """

適当なYouTube 動画のトップコメントを使って以下のように試してみます。

comments: list[str] = [
    "4人、机、ゲーム、食べ物だけで神企画って分かる東海愛してるわ",
    "1万円貰う方法のどうがあげてます",
    "絶対に翻訳したら????? तिमीलाई श्राप दिइएको छ। हप्तामा हड्डीहरू बिस्तारै भाँच्छन्। यदि तपाईंले यो श्रापलाई बेवास्ता गर्नुभयो वा यसलाई ध्यान नदिई छोड्नुभयो भने, तपाईंको शरीर असामान्य हुनेछ। मद्दत गर्ने एक मात्र तरिका मसँग दर्ता गर्नु हो।",
    "ほぼ同じコメントしようとしたらあったので、このコメントをてっぺんに上げたい",
    "裏アカ840個使って良いね押しといたから、割と上の方いったと思う",
    "これが東海オンエアの神企画か。"
]

print("=" * 80)
for i, comment in enumerate(comments):
    judge_result = judge_spam(comment)
    print(f"{i}: ")
    print(f" - コメント: {comment}")
    print(f" - 判定結果: {judge_result.isSpam}")
    print(f" - 判定理由: {judge_result.reason}")
    print("=" * 80)

結果は以下の通りでした。結構完璧に仕分けられてるように見えますね。

================================================================================
0:
 - コメント: 4人、机、ゲーム、食べ物だけで神企画って分かる東海愛してるわ
 - 判定結果: False
 - 判定理由: このコメントはスパムとは見なされません。普通の会話や意見が含まれています。
================================================================================
1:
 - コメント: 1万円貰う方法のどうがあげてます
 - 判定結果: True
 - 判定理由: スパムです。金額を提示して不審な情報を提供しようとしているからです。
================================================================================
2:
 - コメント: 絶対に翻訳したら????? तिमीलाई श्राप दिइएको छ। हप्तामा हड्डीहरू बिस्तारै भाँच्छन्। यदि तपाच्ले यो श्रापलाई बेवालेता गश्नुभयो वा यसलाई ध्यान नदिई छोड्नुभयो भने, तपाईंको शरीर असामान्य हुनेछ। मद्दत गर्ने एक मात्र तरिका मसँग दनेता गद्नु हो।
 - 判定結果: True
 - 判定理由: コメントに繰り返しの記号や異なる言語が混在しているため、スパムと判断されます。
================================================================================
3:
 - コメント: ほぼ同じコメントしようとしたらあったので、このコメントをてっぺんに上げたい
 - 判定結果: False
 - 判定理由: スパムではない: 一般的なコメントで、他のコメントを参照し、評価を求めている。
================================================================================
4:
 - コメント: 裏アカ840個使って良いね押しといたから、割と上の方いったと思う
 - 判定結果: True
 - 判定理由: スパムコメントです。アカウント数を使って、良いねを操作し、順位を上げることを示唆しています。
================================================================================
5:
 - コメント: これが東海オンエアの神企画か。
 - 判定結果: False
 - 判定理由: このコメントにはスパムと判断される要素がありません。
================================================================================

まとめ・所感

上の実行結果の通りで、結構精度としては良さそうに見えます。とはいえ再現性があるかは謎なので、実運用させたい場合は色々真面目にテストする必要がありますが。自分の作りたい dataclass の形式にちゃんと合わせてくれるのは中々使い勝手が良さそうですね。

私自身がスパム判定関連に詳しいわけではないので、既存手法のナイーブベイズ等のほうが安上がりで早く精度も良いという可能性は大いにありますが、その場合も、指定した形式で生成できることを活かし教師データの作成に役立てるといったことはあるのかなと。

その他、スパム判定ではないですが、自分の作りたい形式に合わせられる特徴は、正規表現の代替として複雑な文章に適用するといった実用を考えられそうです。

Discussion