前置き
Twitterのツイートを分類するAIを開発する中で、分かったことやハマったことについて共有してきたいと思います。
FastTextの設定で改善できることは微細かつ、分類内容によるので今回は取り上げません。
主にクレイジングについて熱く語っていこうと思います。
絵文字について
FastTextに絵文字を読み込ませると絶望的な精度になります。
本当に絵文字の除去は徹底してくださいね。
# テキスト
text = "ほげほげ!😀"
bytes_emoji = text.encode('shift-jis', errors='ignore')
text = bytes_emoji.decode('shift-jis', errors='ignore')
大抵これで絵文字は消えるはずです。
しかし、ツイッター上では絵文字がコミュニケーションで多用されています。
例えば...
〔絵文字除去する場合〕
「🍓が食べたい」→「が食べたい」
この様にツイートの意図が読めなくなってしまい、精度が大幅に落ちてしまいます。
そこで絵文字を文字に置換することで、精度を向上させることができます。
text = "🍓が食べたい"
# 置換
text = re.sub("🍓", "イチゴ", text)
bytes_emoji = text.encode('shift-jis', errors='ignore')
text = bytes_emoji.decode('shift-jis', errors='ignore')
この様に置換すればテキストは「イチゴが食べたい」となり、本来の意味を取り戻すことが出来ます。
置き換える必要のあるモノ
初めに例として「年月日・日時」について取り上げます。
年月日・日時を軸に分類を行っているのであれば対策を行う必要はありませんが、もし分類に年月日・日時が必要ないのであれば精度に影響を与える為、置換することをオススメします。
# 正規表現のパターン
ymd_pattern = r'[12]\d{3}[/\-年] ?(0?[1-9]|1[0-2])[/\-月] ?(0?[1-9]|[12][0-9]|3[01])日?$'
hm_pattern = r'((0?|1)[0-9]|2[0-3])[:時][0-5][0-9]分?'
# テキスト
text = "2022年2月22日10:40から打ち上げ花火があるらしい"
# 年月日除去
text = re.sub(ymd_pattern, "年月日", text)
# 時刻除去
text = re.sub(hm_pattern, "日時", text)
置換する必要はあるのか、除去するだけではダメなのかと言った考えの方もいらっしゃるとは思います。
しかし、必要のない年月日や日時も文章を作り上げる要素になるのです。
年月日や日時という”時間”だけに関する文章から除去してしまえば、ほぼ中身のない文章になってしまいます。
その為、私は除去ではなく文字と置き換えを行うようにしています。
それでは他のパターンもありますので、必要に応じて正規表現を行ってみてくださいね。
# URL除去のパターン
url = r"https?://[\w/:%#\$&\?\(\)~\.=\+\-]+"
# メアド除去のパターン
mail = r"[0-9a-zA-Z_.+-]+@[0-9a-zA-Z-]+\.[0-9a-zA-Z-.]+"
# 携帯電話番号除去のパターン
mobile_number = "^[0-9]{3}-[0-9]{4}-[0-9]{4}$"
# 固定電話番号除去のパターン
phone_number = "^[0-9]{4}-[0-9]{2}-[0-9]{4}$"
# Twitterのメンション除去パターン
mention = r"@([A-Za-z0-9_]+) "
# RT除去のパターン
rt = r"(^RT*)"
# 多用される特殊記号のパターン
temp = "[・×*/!!??::/゚➔▶→⇒➡☞↓☟👇⇩=-]+"
# 丸数字のパターン
c_int = "[①②③④⑤⑥⑦⑧⑨]+"
# 小さい「ぁ~ぉ」のパターン
aiueo = "[ぁぃぅぇぉ]"
# 笑う表現のパターン
wara = "((笑+|w+)$|(ww)(w)+)+"
# 語尾に母音を大量につけているものや、「あああ」などの意味のない文字の除去パターン
tmp = "((ああ)|(いい)|(うう)|(ええ)|(おお))((あ)|(い)|(う)|(え)|(お))+"
他にも色々とありますが、とりあえず王道のパターンを記載しておきました。
この置き換えが精度を大きく向上させた要因の一つです。
地名について
地名を軸に分類していないのであれば確実に置き換えすべきです。
都道府県地区町村や駅、橋などの名前が分類の要因でない場合、精度に大きな影響を与えます。
必要のないものはどんどん消していくスタイルでやっていきましょう!
地名に関しては正規表現での置換が難しく、スクレイピング等の方法を用いて都道府県市区町村の名前を取得してください。
ローマ字について
ローマ字には大文字小文字がありますが、それぞれ別の文字として認識されてしまいます。
同じ意味の文字なのに別のものとして扱われてしまえば勿論精度は落ちてしまいます。
そこでローマ字を全て小文字か大文字に統一する必要があります。
# 大文字から小文字へ
text = "FASTTEXT".lower()
# 小文字から大文字へ
text = "fasttext".upper()
学習データに日本語以外
収集データを厳選していても稀に外国語のツイートが学習データに入り込む場合があります。
そんなミスを防ぐためには下記の関数が有効です。
def japan(string):
for st in string:
try:
name = unicodedata.name(st)
except:
continue
# 日本語が含まれていれば戻り値がTrue
if "HIRAGANA" in name or "KATAKANA" in name or "CJK UNIFIED" in name:
return True
# ここまで来た時点で日本語が1度も出現していないのでFalseを返す
return False
print(japan("hello worldって素敵"))
True
学習データの重複
学習データが重複してしまうと、分類時に重複しているデータに判定が偏ってしまう場合があります。
学習データは一度set型に変換し、重複をなくしてみましょう。
train_list = ["aaa", "iii", "uuu", "uuu"]
train_list = list(set(train_list))
["aaa", "iii", "uuu"]
ストップワードの除去について
文章中の「が」「の」「です」「ます」等の機能語はテキスト分類の精度を下げる為、不必要と言われています。
しかし、それはニュース記事などの書かれている内容が全く別の場合に限ります。
例えば下記の様に全く別の言葉のストップワードを除去すると...
・就職したい
・就職します
就職
だけが残ります。
このように必要な部分が除去されると思い通りに分類できなくなってしまいます。
繰り返しますが、「政治」と「料理」など全く関係ないジャンルを分類したい場合には有効です。
しかし、政治内の論争の意見について分類したい場合には逆効果になりますので、注意が必要です。
分類はそのラベルだけで問題ないか
例えば下記の様な二つのラベルがあったとします。
「喜んでいる」
「落ち込んでいる」
そしてあなたは「落ち込んでいる人」を自動で見つけ出してくれる分類AIを作りたい。
そんな時にはラベルが2つである必要は全くありません。
もう一つ追加してあげましょう。
「喜んでいる」
「普通」 <<追加
「落ち込んでいる」
「普通」です。これは曖昧な内容の文章のことを指します。
学習データづくりの際に「これは…どっちだ?」といった場面はないでしょうか?
その時には曖昧な内容として分けてしまえばいいんです。
そうすることで分類の精度をあげることができます。
もし、必要なら曖昧な内容のラベルをもっと増やしてもいいかもしれません。
皆さんの状況に応じて変更してあげてください。
まとめ
いかがでしょうか。
分類精度を向上させるにはトライ&エラーです。
分類内容によっては今回紹介した方法も逆効果になってしまう場合があります。
そこを見極めてクレンジングしていきましょう!