#1.はじめに
ことの始まりは、1週間ほど前の友人とのこのような残念な会話↓↓↓
A:あいつすげぇ疲れてた顔してたけど、プライベートで何かあったのかな。ハラスメントになるから聞けないけど。
自分:えぇそんなことも聞けないの!?
A:いま偉くなるってそうこと。
自分:じゃぁ偉くならなくていいや(涙目)
もう何がハラスメントになるのか分からん…という端緒から、そんな問題を解決するウェブサービスを作れないかな(無料で)と考えました。
公開したサービス → doh! | AI Harassment Checker
※dohは「drive out harassment(ハラスメントを駆逐する)」の略。最近、進撃の巨人にハマっていたので。。。
#2.使った技術
テキスト解析をしやすいPythonで動かしたかったのでPythonAnywhereの無料版でサーバーを立ち上げることに。ディスク容量が限られているので、形態素解析とベクトル計算が出来るGiNZAを用いました。
- PythonAnywhere(django)
- GiNZA
- sklearn
- jQuery
#3.Djangoでサーバー立ち上げ
PythonAnyWhereでユーザー登録して、Djangoアプリの作成、SSL化まで実施。こちらが大変参考になりました→【Django】PythonAnyWhereにデプロイする
無料版では使える容量が512MBまでだったり、外部アクセスに制限があったりと多少使いづらさはありますが、フリーでpythonサーバーを立ち上げられるということで、ちょっと感動モノですね。
#4.判定AI
判定処理は以下のように考えました。
- 事前に集めた教師データをGiNZAで文章ベクトル化
- GiNZAで入力テキストを文章ベクトル化
- 入力テキストと教師データの文章ベクトルのコサイン類似度をsklearn で計算。ベクトルの類似度が近い文書が見つかればハラスメントと判定
という訳で肝はGiNZAでの文章分析なのですが、ここで無料版の障壁に激突。ディスク容量が足らず、GiNZAのインストールに失敗しました...orz
試行錯誤した結果、GiNZAが内包するSudachiPyの辞書をデフォルトのsudachidict_coreからsudachidict_smallに強引に変えることにしました(動作への影響は分かりませんが、精度には悪影響必死)
Djangoのview.pyでの処理は以下のようにし、jsonをブラウザに返すようにしました。
from django.shortcuts import render
from django.http import HttpRequest, HttpResponse
import spacy
import csv
import json
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
nlp = spacy.load('ja_ginza')
#教師データの読み込み
csv_file = open("/home/zakython/train_data.csv", "r", encoding="utf-8", errors="", newline="" )
f = csv.reader(csv_file, delimiter=",", doublequote=True, lineterminator="\r\n", quotechar='"', skipinitialspace=True)
#教師データのベクトル計算
doc_vec = []
doc_arr = []
for row in f:
doc_arr.append([row[0], row[1]])
doc_vec.append(nlp(row[1]).vector)
#入力テキストのベクトル計算→コサイン類似度を計算して結果を返す
def app_api(request):
assert isinstance(request, HttpRequest)
response_arr = []
response_dict = {}
response_judge = 0
response_label = ""
#入力テキストを取得→Ginzaで読み込み
doc = nlp(request.GET.get('text'))
#類似度計算
cs = cosine_similarity(doc.vector.reshape(1, -1), doc_vec)[0]
topn_indices = np.argsort(cs)[::-1][:10]
#類似度が0.95以上ならアウト
if cs[topn_indices[0]] > 0.95:
response_judge = 100
response_label = doc_arr[topn_indices[0]][0]
#類似度が近いものがTOP3に多ければアウト
elif cs[topn_indices[0]] > 0.9:
if (doc_arr[topn_indices[0]][0] == doc_arr[topn_indices[1]][0]) and (doc_arr[topn_indices[1]][0] == doc_arr[topn_indices[2]][0]):
response_judge = 50
response_label = doc_arr[topn_indices[0]][0]
#jsonのkey
key = ["get_text", "result", "article_url"]
#レスポンスをセットする
response_dict["app_responce"] = dict(zip(key, [request.GET.get('text'), [response_label, response_judge], "test"]))
return HttpResponse(json.dumps(response_dict, indent=2, ensure_ascii=False), content_type = "application/json")
#5.フロントと精度
ブラウザ側はjqueryやらajaxyやらを使って、Djangoから返ってきたjson
を処理する形にしました。結果の表示には「sweetalert2」を使っています。結果はというと、
入力テキスト①:経営者から性的な関係を要求されたが、拒否したら、解雇された(厚労省の「あかるい職場応援団」より)
いい感じ!
入力テキスト②:「お前なんて役に立たない」と侮辱する(社会人の教科書より)
失敗、、。多少の判定はできそうですが、まだまだ精度改善の余地はありそうです。
#6.最後に
精度はイマイチですが、無料で一通りの結果を示すサービスを公開することができました。(最初に調べろよという話ですが)北大発ベンチャーの調和技研さんがもっとちゃんとしたサービスを公開してますね...orz
類似サービスが他にもありそうなので、こちらも負けないように能力アップを目指していきたいと思います!