自然言語処理で、お笑いコンビ「たりないふたり」のtwitterを分析【おまけ】

Qiitaで3回目の投稿になります。
初回投稿[お笑いコンビ「たりないふたり」のtwitterを分析](https://qiita.com/m-wakatsuru/items/16dbbe9a084be5a81de9)を知り合いに見てもらったところ、「若林と山里の分類器を作成できるのでは？」と助言頂いたので、他の方の投稿を参考にして分類器を作成してみました。

## はじめに
[お笑いコンビ「たりないふたり」のtwitterを分析](https://qiita.com/m-wakatsuru/items/16dbbe9a084be5a81de9)でtwitterAPIからtweetを抽出したので、
1. tweet内の文責〇〇という文言の削除
2. 単語切り出しのための正則化
3. Mecabで単語切り出し
4. gensimで辞書の作成
5. gensimでtweetの特微量ベクトルを作成
6. scikit-learnを用いて分類器の作成
を行いました。

なお、全面的に[scikit-learnとgensimでニュース記事を分類する](https://qiita.com/yasunori/items/31a23eb259482e4824e2)を参考にさせてもらっています。

## 1.tweet内の文責〇〇という文言の削除
本来であれば文責若林と文責山里の2つだけなのですが、さすがはお笑い芸人。様々なバリエーションをつけることで文責〇〇でも笑いを取りに来てました。よって手動で文責〇〇のパターンを抽出し、tweet dataから削除しました。

```pyhon
def delete_bunseki(df,column):
    ret =[]
    re_person = '(文責(若林|悪鼠|・若林|若|山里|謎のラッパー|たり林てる恭|逃げ林|デロリアン山里))|(歌責若林)|MASA|山里亮太'
    for i in range(df.shape[0]):
        ret.append(re.sub(re_person,'',df.loc[i,column]))
    return ret
```

## 2.単語切り出しのための正則化
Mecabのgithubに[解析前に行うことが望ましい文字列の正規化処理](https://github.com/neologd/mecab-ipadic-neologd/wiki/Regexp.ja)があったので、このコードを用いて正則化処理を実施しました。

twitterに写真が添付されている場合、twitter上ではpic.twitter.com/~~~ と表示されます。
しかしtwitterAPIでデータと落とした場合はhttps://~~ と表示されるため、httpsのリンクとタグを削除するコードを追加します。


twitter data
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">山ちゃんがグリーンカレーにつかってます。<br>文責　若林 <a href="https://t.co/yQrX8KvVPS">pic.twitter.com/yQrX8KvVPS</a></p>&mdash; たりないふたり－山里亮太と若林正恭－ (@tarinaifutari) <a href="https://twitter.com/tarinaifutari/status/1185015377798914048?ref_src=twsrc%5Etfw">October 18, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


twitterAPIでダウンロードしたデータ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522259/be33f2a8-34be-755e-c163-97b308758be4.png)

追加したコード

```pyhon
s = re.sub('https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', '', s)  # remove https link
s = re.sub('<[^>]*?>', '', s)  # remove https tag
```

<details>
<summary>**全コードをクリックで展開**</summary>
<div>

```pyhon
# encoding: utf8
from __future__ import unicode_literals
import re
import unicodedata

def unicode_normalize(cls, s):
    pt = re.compile('([{}]+)'.format(cls))

    def norm(c):
        return unicodedata.normalize('NFKC', c) if pt.match(c) else c

    s = ''.join(norm(x) for x in re.split(pt, s))
    s = re.sub('－', '-', s)
    return s

def remove_extra_spaces(s):
    s = re.sub('[ 　]+', ' ', s)
    blocks = ''.join(('\u4E00-\u9FFF',  # CJK UNIFIED IDEOGRAPHS
                      '\u3040-\u309F',  # HIRAGANA
                      '\u30A0-\u30FF',  # KATAKANA
                      '\u3000-\u303F',  # CJK SYMBOLS AND PUNCTUATION
                      '\uFF00-\uFFEF'   # HALFWIDTH AND FULLWIDTH FORMS
                      ))
    basic_latin = '\u0000-\u007F'

    def remove_space_between(cls1, cls2, s):
        p = re.compile('([{}]) ([{}])'.format(cls1, cls2))
        while p.search(s):
            s = p.sub(r'\1\2', s)
        return s

    s = remove_space_between(blocks, blocks, s)
    s = remove_space_between(blocks, basic_latin, s)
    s = remove_space_between(basic_latin, blocks, s)
    return s

def normalize_neologd(s):
    s = s.strip()
    s = unicode_normalize('０-９Ａ-Ｚａ-ｚ｡-ﾟ', s)

    def maketrans(f, t):
        return {ord(x): ord(y) for x, y in zip(f, t)}

    s = re.sub('[˗֊‐‑‒–⁃⁻₋−]+', '-', s)  # normalize hyphens
    s = re.sub('[﹣－ｰ—―─━ー]+', 'ー', s)  # normalize choonpus
    s = re.sub('[~∼∾〜〰～]', '', s)  # remove tildes
    s = re.sub('https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', '', s)  # remove https link
    s = re.sub('<[^>]*?>', '', s)  # remove https link
    s = s.translate(
        maketrans('!"#$%&\'()*+,-./:;<=>?@[¥]^_`{|}~｡､･｢｣',
              '！”＃＄％＆’（）＊＋，－．／：；＜＝＞？＠［￥］＾＿｀｛｜｝〜。、・「」'))

    s = remove_extra_spaces(s)
    s = unicode_normalize('！”＃＄％＆’（）＊＋，－．／：；＜＞？＠［￥］＾＿｀｛｜｝〜', s)  # keep ＝,・,「,」
    s = re.sub('[’]', '\'', s)
    s = re.sub('[”]', '"', s)
    return s
```
</div>
</details>

## 3.Mecabで単語切り出し
[scikit-learnとgensimでニュース記事を分類する](https://qiita.com/yasunori/items/31a23eb259482e4824e2)を参考に、Mecabで単語の切り出しを行いました。
Input dataの形式がtext dataからtwitterのtweetへ変更となったので、少しコードを修正しました。また、若林と山里のtweetを見る限り語尾に大きな違いがありそうなので、「名詞のみを抽出する場合」と「全ての品詞を抽出する場合」の2パターンで正解率を測ることにしました。

<details>
<summary>**全コードをクリックで展開**</summary>
<div>

```pyhon
def get_words(df,column):
    '''
    記事群のdictについて、形態素解析してリストにして返す
    '''
    ret = []
    for i in range(df.shape[0]):
        ret.append(get_words_main(df.loc[i,column]))
    return ret

def get_words_main(content):
    '''
    一つの記事を形態素解析して返す
    '''
    return [token for token in tokenize(content)]

def tokenize(text):
    '''
    とりあえず形態素解析して全ての品詞を取り出す感じにしてる
    '''
    node = mecab.parseToNode(text)
    while node:
#        if node.feature.split(',')[0] == '名詞':　# 名詞のみを抽出する場合は、このif文を使用する。
        yield node.surface.lower()
        node = node.next
```
</div>
</details>


## 4.gensimで辞書の作成
[scikit-learnとgensimでニュース記事を分類する](https://qiita.com/yasunori/items/31a23eb259482e4824e2)とほとんど同じコードです。3と同じくinput dataのみ修正してます。

```pyhon
dictionary = corpora.Dictionary(df['TW_TEXT_VECTOR'])
dictionary.filter_extremes(no_below=10, no_above=0.3)
dictionary.save_as_text('tarifuta_dict.txt')
```

## 5.gensimでtweetの特微量ベクトルを作成
[scikit-learnとgensimでニュース記事を分類する](https://qiita.com/yasunori/items/31a23eb259482e4824e2)とほとんど同じコードです。
get_vector関数を作成し、各tweetから得たtext dataをベクトル表現に変換します。

```pyhon
def get_vector(dictionary, content):
    '''
    ある記事の特徴語カウント
    '''
    tmp = dictionary.doc2bow(get_words_main(content))
    dense = list(matutils.corpus2dense([tmp], num_terms=len(dictionary)).T[0])
    return dense
```

get_vector関数を用い、2次元配列のリストとしてtweet dataとPerson Labelを挿入していきます。こここでもinput dataが異なるため、若干の修正を加えてます。

```pyhon
data_train = []
label_train = []
for i in range(df.shape[0]):
    data_train.append(get_vector(dictionary, df.loc[i,'TW_TEXT']))
    label_train.append(df.loc[i,'PERSON_LABEL'])
```

## 6.scikit-learnを用いて分類器の作成
[scikit-learnとgensimでニュース記事を分類する](https://qiita.com/yasunori/items/31a23eb259482e4824e2)とほぼ同じです。sklearn.grid_searchが使用できなかったので、sklearn.model_selectionからGridSearchCVをダンロードしました。また、GridSearchCV内のgrid_scores_が使用できなかったため、best_score_で最も良いスコアのみを表示するようにしました。

##結果

全ての品詞を用いた場合

```pyhon
ベストスコア
0.8697674418604651
```

名詞のみ用いた場合

```pyhon
ベストスコア
0.751937984496124
```

所要時間は、全ての品詞も用いた場合で1時間弱、名詞のみを用いた場合で15分程度です。使用したtweetも全てで1500程度と少ないため、想定したよりも早く終わりました。

不確定要素(以下)が多い中での分類だったため、精度をあげることは断念します。

- 文責〇〇が書かれていないtweetも多く、合計2100ツイートのうち1500ツイート位しか分類できなかった。
- 若林の性格を考えると、「文責山里」で何回か投稿している可能性がある。
- twitterはニュース記事等に比べて文字数が少ないので、精度の良い分類器が作成できるか疑問が残る。
- お笑い芸人独特の言い回しもあり、MeCabの精度が保証しづらい。

## 所感
若林と山里のツイートを分類できたとして、なんかの役に立つのだろうか。。。