Twitter 誹謗中傷撃退マシン(最強版)

前にQiitaに書いた「Twitter 誹謗中傷撃退マシン」の記事は出来が良かったのにLGTMが伸び悩んでたため、疑問に思っておりましたところ。いちいち誹謗中傷ワードを配列に入れるのを面倒くさがられたのではと思いDeep Learningの力を借りました。
本記事の目的は前回同様
### SNSの誹謗中傷をテクノロジーの力で救うこと
です
では、行ってみましょう！！！

## 事前知識
前回の記事で事前知識を学習して進めてください
[Twitter 誹謗中傷撃退マシン](https://qiita.com/pigretyasushi/items/e9900708463b5c4bc36a)

## 誹謗中傷識別AIを作ろう
word2vec, RNN(LSTM)を使ってモデルを作ります。データは"umich-sentiment-train.txt"という評判分析によく使われるデータセットを使います。
### word2vecモデル
Kerasを使って作成します。word2vecとは一言で言うと人間の言葉をベクトル(数字)におくアルゴリズムです。

```python
word2vec_model = Sequential()
word2vec_model.add(Embedding(input_dim=vocab_size, output_dim=EMBEDDING_SIZE,
                   embeddings_initializer='glorot_uniform', input_length=WINDOW_SIZE * 2))
word2vec_model.add(Lambda(lambda x: K.mean(x, axis=1), output_shape=(EMBEDDING_SIZE,)))
word2vec_model.add(Dense(vocab_size, kernel_initializer='glorot_uniform', activation='softmax'))

word2vec_model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=["accuracy"])
word2vec_model.fit(Xtrain, ytrain, batch_size=BATCH_SIZE, epochs=NUM_EPOCHS, validation_data=(Xtest, ytest))
# evaluate
word2vec_score, word2vec_acc = word2vec_model.evaluate(Xtest, ytest, batch_size=BATCH_SIZE)
print("word2vec Test score: {:.3f}, accuracy: {:.3f}".format(word2vec_score, word2vec_acc))
# get embedding_weights
embedding_weights = word2vec_model.layers[0].get_weights()[0]
```

### RNN(LSTM)モデル
RNN(LSTM)とは時系列データを扱うのに特化したAIモデルです。株価予測・機械翻訳とかにも応用されてます。

```python
rnn_model = Sequential()
rnn_model.add(Embedding(vocab_size, EMBEDDING_SIZE, input_length=MAX_SENTENCE_LENGTH,
                            weights=[embedding_weights], trainable=True))
rnn_model.add(Dropout(0.5))
rnn_model.add(LSTM(HIDDEN_LAYER_SIZE, dropout=0.5, recurrent_dropout=0.5))
rnn_model.add(Dense(1))
rnn_model.add(Activation("sigmoid"))

rnn_model.compile(loss="binary_crossentropy", optimizer="adam",
                      metrics=["accuracy"])
rnn_model.fit(Xtrain, ytrain, batch_size=BATCH_SIZE, epochs=NUM_EPOCHS, validation_data=(Xtest, ytest))
# evaluate
rnn_score, rnn_acc = rnn_model.evaluate(Xtest, ytest, batch_size=BATCH_SIZE)
print("rnn Test score: {:.3f}, accuracy: {:.3f}".format(rnn_score, rnn_acc))
# save model
rnn_model.save(os.path.join(DATA_DIR, "sentence_analyzing_rnn.hdf5"))
```

```python
rnn_model.add(Embedding(vocab_size, EMBEDDING_SIZE, input_length=MAX_SENTENCE_LENGTH,
                            weights=[embedding_weights], trainable=True))
```
ここで先ほどのword2vecで取得した重りを使い学習を進め"sentence_analyzing_rnn.hdf5"として保存します。

## 「Twitter API + 誹謗中傷識別AI」合体
これらを前回記事にした誹謗中傷撃退マシンと合体させます。

```python
# coding=utf-8
import collections
import os
import json
import nltk
import codecs
from requests_oauthlib import OAuth1Session
from keras.models import Sequential, load_model
from keras.preprocessing import sequence

# 認証処理
CK = 'YOUR OWN'
CS = 'YOUR OWN'
AT = 'YOUR OWN'
ATS = 'YOUR OWN'
twitter = OAuth1Session(CK, CS, AT, ATS)

# ツイート検索エンドポイント
url = 'https://api.twitter.com/1.1/search/tweets.json'
# ユーザーブロックエンドポイント
url2 = 'https://api.twitter.com/1.1/blocks/create.json'
# Setting parameter
DATA_DIR = "./data"
MAX_FEATURES = 2000
MAX_SENTENCE_LENGTH = 40

if os.path.exists(os.path.join(DATA_DIR, "sentence_analyzing_rnn.hdf5")):
    # Read training data and generate word2index
    maxlen = 0
    word_freqs = collections.Counter()
    with codecs.open(os.path.join(DATA_DIR, "umich-sentiment-train.txt"), "r", 'utf-8') as ftrain:
        for line in ftrain:
            label, sentence = line.strip().split("\t")
            try:
                words = nltk.word_tokenize(sentence.lower())
            except LookupError:
                print("Englisth tokenize does not downloaded. So download it.")
                nltk.download("punkt")
                words = nltk.word_tokenize(sentence.lower())
            maxlen = max(maxlen, len(words))
            for word in words:
                word_freqs[word] += 1

    vocab_size = min(MAX_FEATURES, len(word_freqs)) + 2
    word2index = {x[0]: i + 2 for i, x in enumerate(word_freqs.most_common(MAX_FEATURES))}
    word2index["PAD"] = 0
    word2index["UNK"] = 1
    # load model
    model = load_model(os.path.join(DATA_DIR, "sentence_analyzing_rnn.hdf5"))
    # エンドポイントへ渡すパラメーター
    target_account = '@hoge '  # リプライされたアカウント
    keyword = '@' + target_account + 'exclude:retweets'  # RTは除外

    params = {
        'count': 50,  # 取得するtweet数
        'q': keyword,  # 検索キーワード
    }
    req = twitter.get(url, params=params)

    if req.status_code == 200:
        res = json.loads(req.text)
        for line in res['statuses']:
            target_text = line['text'].replace(target_account, "")
            test_words = nltk.word_tokenize(target_text.lower())
            test_seqs = []
            for test_word in test_words:
                if test_word in word2index:
                    test_seqs.append(word2index[test_word])
                else:
                    test_seqs.append(word2index["UNK"])

            Xsent = sequence.pad_sequences([test_seqs], maxlen=MAX_SENTENCE_LENGTH)
            ypred = model.predict(Xsent)[0][0]
            if ypred < 0.5:
                params2 = {'user_id': line['user']['id']}  # ブロックするユーザー
                req2 = twitter.post(url2, params=params2)

                if req2.status_code == 200:
                    print("Blocked !!")
                else:
                    print("Failed2: %d" % req2.status_code)
    else:
        print("Failed: %d" % req.status_code)
else:
    print ("AI model doesn't exist")

# 注意：Twitter APIは1週間以上前のリプを検索ヒットさせることができない
# 注意：明らかな攻撃的なリプはそもそもヒットしない
```

まずモデルを走らせるために単語をIDにおく必要があるのでword2indexを作成する。
リプライされたアカウントを指定してメッセージを受け取りAIに通す。
悪口だと0,褒め言葉だと1に近くなるよう分類される。
そして、閾値0.5未満だと悪口としてそのアカウントをブロックする。

## 総括
日本語は単語ID化も難しい上に良質なデータセットもないため、本プログラムは英語のみの対応となっています。
このプログラムが少しでも世の中に役立つことを切に祈ってます。

ちなみにこれが私のTwitter垢です。気軽にメッセージどうぞ！
https://twitter.com/downtownakasiya