真面目に相性を考慮した企業推薦アプリやマッチングアプリを作りたい
企業への就職や出会いを求める場など、現在はITが進んでいますが、まだ最適な状態に至っていいないだろうと思われます。そんな課題を解決するために、人の行動ログ(ここではSNSでの発信ログ等)を利用して、真面目なマッチングエンジンを作ろうとしていました。
具体的な多くの人の行動ログを取得可能なサービスを所有していないので、Twitter社のデータを用いて、マッチングエンジンを作ろうとして現在、技術検証や精度の改善などをしています。
日本語のテキストを書くユーザ 2400万人 分の直近500 ~ 1000ツイート程度をサンプリングしており、さまざまな観点を検証しています。
安西先生...、botが邪魔です...!
狭い課題として、botと呼ばれるプログラムでの自動運用されたアカウントが少なくない数存在し、botは特定のキーワードを何度もつぶやくので、個性や特性を重要視したマッチングエンジンを作成した際に悪影響を及ぼす可能性が強くあります。
そのため、狭いスコープの課題ですが、botをデータと機械学習でうまく検出し、ブロックする仕組みを構築しましたので、ご参考にしていただけますと幸いです。
奈良先端科学技術大学院大学が数年前に出した軽めの(?)論文に、認知症者発言圧縮すると小さなサイズになるというものがあります。
認知症の人は、認知症ではない人に比べて、発話した情報が、圧縮アルゴリズムにて圧縮すると、小さくなるというものでした。
ハフマン符号化をかけることで、頻出するパターンをより短い符号に置き換えでデータの圧縮サイズをあげようというものになります。
bzip2は、明確に ブロックソート法
と ハフマン符号化
を行っており、かつ、zipという圧縮方式より結果が綺麗に出たので、採用しました。
具体的には、以下のようなプロセスで元のテキストファイルサイズと、圧縮済みのファイルサイズを比較します。
import bz2
import os
import random
from pathlib import Path
salt = f"{random.random():0.12f}"
pid = f"{os.getpid()}_{salt}"
all_text # ある特定のユーザのツイートを1つにjoinしたもの
# そのままのテキスト情報を書き込んだときのサイズを取得
with open(f"/tmp/{pid}", "w") as fp:
fp.write(all_text)
original_size = Path(f"/tmp/{pid}").stat().st_size
# bz2で圧縮した時のサイズを取得
with bz2.open(f"/tmp/{pid}", "wt") as fp:
fp.write(all_text)
bz2_size = Path(f"/tmp/{pid}").stat().st_size
# clean up
Path(f"/tmp/{pid}").unlink()
ユーザネームの末尾が _bot
になっているものをbotと定義
botはかなりの数存在し、検索を汚染するような形でよく出現します。twitterのユーザーネームで末尾に_botをつけているアカウントが存在し、これはほぼbotだろうという前提で処理して良いものであろうと理解できます。ユーザーネームの末尾が _bot
ならば、botと定義しました。
ノイズもままある
_bot
と末尾のユーザーネームにつけているのにbotでない人、それなりにいるんです。Twitterをやっていると理解できる事柄ですが、自身のアイデンティティなどを機械などと近い存在であると思っている人は _bot
とかつけることがあるようです。
手動でクリーニング仕切れない量程度には、人なのに、 _bot
のサフィックスをつけている人がいるのですが、機械学習でうまくやることで解決していきます
例えば、上記の図の左側でまるで囲まれた _bot
であると自称しているユーザは、実際は典型的な人間のツイートをしており、botではありません。
_bot
が末尾のものだけでは、precision
によりすぎている
真の課題は、_bot
のサフィックス等がつかなくても人間のユーザのフリをするbotアカウントを検出することであります。このたぐいのアカウントは大量にあり、自動運用されている宣伝・企業アカウントなどはまずbotであります。
そのため、 _bot
のサフィックスでの判定は例外があるものの、機械学習によるprecisionによりすぎた判別をrecall側に倒す作業とも捉えることが可能になります。
今回のスコープとしてrecallを上がるように倒したいので、モデルの複雑度を上げすぎないことと、ノイズとなるbotでないのにbotを自称している人を判別してはいけないので、うまく丸めるため特徴量を多くしすぎないことがポイントとなります。
以下の特徴量を選択しました。
- compression_ratio: 圧縮率
- is_near_rate: 前のツイートから10分の倍率でツイートした率
- uniq_ratio: ユニークツイート数
LightGBMで、特徴量を3つ用いて、構築する木の複雑さを3に抑えて、AUCをメトリックとして学習を行いました。
Holdoutで25%をvalidationとして、AUCが悪化しない範囲で学習を継続します。
特徴量単体だと、AUCが 0.90程度までしかでませんが、 0.922
まで上げることが可能になりました。
どの程度、botと判定していいのかを定義する
_bot
がつくアカウントがTwitterのアカウントの全体の 0.05%
程度でした。定性的な感想ですが、2%程度は完全にbotに近いアカウントであり、学習したLightGBMのモデルの閾値を0.05まで緩めることで、2%程度のリコールを得ることができました。
素の _bot
だけの散布図より、オレンジの面積が大きく広がっていることがわかります。
リコールを広げた結果、具体的にどのようなアカウントがボットと判定されたのか見ていきます。
(著作権法32条に基づき、技術検証のため、公開情報を引用しています)
例1: もう使用していないアカウントでアプリ登録をしたアカウントをボットとして判定
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>【定期】フォロバして欲しい人はリプください
— アルカラ@ワサラー団でした (@arukaragrgr) June 5, 2018
例2: 出会い系の誘導の業者アカウントをボットとして判定
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>好きなタイプは優しい人だよ( *¯ ⁻̫ ¯*)❤️
— みな@複数希望♡ (@AnnaBra86652585) September 19, 2019
歳とかは気にしないもん!
中身が大事なのー!!
❄❄
このbotの検出は価値がある作業で、Twitterの検索結果をbotや業者が激しく汚染する、という経験を体験している方は多いかと存じます。
検出されたbotを Twitter APIとそれをpythonで簡単に使えるようにしたtweepyをインストールすることで、簡単に特定のユーザをblockすることができます。
書捨てのコードだと以下のようにして実行することができます。
入力となるcsvデータは末尾のDropboxのリンクに付属します。
import time
import tweepy
import os
import pandas as pd
from tqdm import tqdm
auth = tweepy.OAuthHandler(os.environ["API_KEY"], os.environ["API_SECRET_KEY"])
auth.set_access_token(os.environ["ACCESS_TOKEN"], os.environ["ACCESS_TOKEN_SECRET"])
api = tweepy.API(auth)
df = pd.read_csv("./tmp/result.csv")
df.sort_values(by=["yhat"], ascending=False, inplace=True)
for username, yhat in tqdm(zip(df.username, df.yhat), desc="blocking...", total=len(df)):
try:
api.create_block(username)
# time.sleep(0.1) # you may need this
except Exception as exc:
print(exc)
continue
- GitHub: 再現をしたい場合、何をやったかが確認できます
- Dropbox: 最終的な、推論したスコアを付与したデータ
- Dropbox: Githubにあるコードを再現するためのデータ(オリジナルのTweet情報は含みません)
- 常に新鮮なコーパスはあり、集計はできる
- TwitterIDでログインすると、一括でブロックできるアプリは作ることができ、また、世の中に必要な気がします。