LightGBMは芥川龍之介の文章の特徴をどう捉えているのか？

#はじめに
Nishikaさんの[自社開催コンペ「AIは芥川龍之介を見分けられるのか？」](https://www.nishika.com/competitions/1/summary)（このコンペは、すでに終了）を今更ながらやってみました。TF-IDFベクトライザで分類器をRandomForestでやったところ、スコアが0.53でした。分類器をLightGBMにしたところスコアが0.93と爆上がり。そこで疑問に思ったんです。LightGBMは、芥川の文章をどのようにして他の文書から抽出しているのでしょうか？SHA P値を使って考えてみました。
#手順
作業手順は次の通り。
データの読み込み→MeCabでトークン化（文章の「名詞」、「動詞」、「副詞」、「形容詞」のみ抽出）→TF-IDFベクトライザ→LightGBMで学習→SAHP値をみてみる
##ライブラリインポート
```
import pandas as pd

import MeCab
import re

from wordcloud import WordCloud
import matplotlib.pyplot as plt
import japanize_matplotlib
%matplotlib inline

from sklearn.feature_extraction.text import TfidfVectorizer
import lightgbm as lgb #LightGBM

import numpy as np
import shap
shap.initjs()
```
##データ読み込み
データは著作権フリーの「青空文庫」から。

```
# train データ読み込み
train_df = pd.read_csv('./data/train.csv')
print(train_df.shape)
print(train_df.head())

#(3312, 3)
#   writing_id                                               body  #author
#0           0  \r\n　先ごろの本欄に僕の「風報」にかいた「天皇陛下に捧ぐる言葉」を評して俗うけを狙った媚...       0
#1           1  \r\n　旅の眼に映じた外国の正月をといふお需めで、一昔前の記憶から探してみたが、其処にはほ...       0
#2           2  \r\n　或る心持のよい夕方、日比谷公園の樹の繁みの間で、若葉楓の梢を眺めていたら、どこから...       0
#3           3  \r\n\r\n［＃３字下げ］一［＃「一」は中見出し］\r\n\r\n　島々《しま／＼》と云...       1
#4           7  \r\n\r\n　或る田舎に母と子とが住んでいた。そして或る年の秋、次のようなことがあった。...       0
```
## 著者の確認
authorの１が芥川、0がその他の方。
芥川の文章が252、その他が3060あります。

```
train_df['author'].value_counts()

# 0    3060
# 1     252
# Name: author, dtype: int64
```

## MeCabトークナイズ
文書から、名詞、動詞、副詞、形容詞のみを抽出。

```
# ユニコード正規化
train_df["body"] = train_df["body"].str.normalize("NFKC")  # trainデータ
# アルファベットを小文字に統一
train_df["body"] = train_df["body"].str.lower() # train データ

# Tagger
tagger = MeCab.Tagger('-d /usr/local/lib/mecab/dic/ipadic')

# ひらがなのみの文字列にマッチする正規表現
kana_re = re.compile("^[ぁ-ゖ]+$")

# 記号の削除
code_re = re.compile('[\t\s!"#$%&\'\\\\()*+,-./:;；：<=>?@[\\]^_`{|}~○｢｣「」〔〕“”〈〉''『』【】＆＊（）＄＃＠？！｀＋￥¥％♪…◇→←↓↑｡･ω･｡ﾟ´∀｀ΣДｘ⑥◎©︎♡★☆▽※ゞノ〆εσ＞＜┌┘]')
# 数字の削除
num_re = re.compile('\d+,?\d*')

def mecab_tokenizer(text):
    # テキストを分かち書きする関数を準備する
    parsed_lines = tagger.parse(text).split("\n")[:-2]
    surfaces = [l.split('\t')[0] for l in parsed_lines]
    features = [l.split('\t')[1] for l in parsed_lines]
    # 原型を取得
    bases = [f.split(',')[6] for f in features]
    # 品詞を取得
    pos = [f.split(',')[0] for f in features]

    # 各単語を原型に変換する
    token_list = [b if b != '*' else s for s, b in zip(surfaces, bases)]
    
    # 名刺、動詞、形容詞、副詞　に絞り込み
    target_pos = ["名詞", "動詞", "形容詞", "副詞"]
    
    
    token_list = [t for t, p in zip(token_list, pos) if p in target_pos]

    # アルファベットを小文字に統一
    token_list = [t.lower() for t in token_list]

    # ひらがなのみの単語を除く
    token_list = [t for t in token_list if not kana_re.match(t)]
    
    # 記号の削除
    token_list = [t for t in token_list if not code_re.match(t)]
    
  　
    # 算用数字の削除
    token_list = [t for t in token_list if not num_re.match(t)]

    return token_list

# 分かち書きしたデータを作成する
sentences = train_df.body.apply(mecab_tokenizer)

print(sentences[:5])

# 0    [先ごろ, 本欄, 僕, 風, 報, 天皇陛下, 捧ぐ, 言葉, 評す, 俗, 狙う, 媚態...
# 1    [旅, 眼, 映じる, 外国, 正月, 需, 一昔, 前, 記憶, 探す, 其処, お正月,...
# 2    [心持, 夕方, 日比谷公園, 樹, 繁み, 間, 若葉, 楓, 梢, 眺める, ラジオ, ...
# 3    [字, 下げ, 一, 一, 見出し, 島々, 云, 町, 宿屋, 着く, 午, 過ぎ, 夕方...
# 4    [田舎, 母, 子, 住む, 年, 秋, 次, 本当に, 天気, 母, 子, 朝, 会話, ...
# Name: body, dtype: object
```

## WordCloudしてみる
試しにWordCloud。
芥川文書とそれ以外を比較するためデータを分離。

```
# 芥川テキスト
akutagawa_text = train_df[train_df['author']==1]
# それ以外
other_text = train_df[train_df['author']==0]
```
WordCloud

```
FONT_PATH = '/Library/Fonts/Arial Unicode.ttf'
wc_akutagawa = WordCloud(font_path = FONT_PATH,
               width = 300,
               height = 200,
               max_font_size=40).generate(str(akutagawa_text.body.apply(mecab_tokenizer)))

wc_other = WordCloud(font_path = FONT_PATH,
               width = 300,
               height = 200,
               max_font_size=40).generate(str(other_text.body.apply(mecab_tokenizer)))
```
WordCloudを表示

```
plt.figure(figsize=(100,200))

plt.subplot(121)
plt.title('芥川ワードクラウド', fontsize=200)
plt.imshow(wc_akutagawa)
plt.axis('off')

plt.subplot(122)
plt.title('その他ワードクラウド', fontsize =200)
plt.imshow(wc_other)
plt.axis('off')

plt.tight_layout()
plt.show()
```
![qiita_wordcloud.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483137/53f79d65-9771-2719-424c-bdef0fe31b12.png)

## TF-IDFベクトル化
```
vectorizer = TfidfVectorizer(min_df=1,lowercase=False,tokenizer=mecab_tokenizer)

# trainデータのベクトル化
train_tfidf_weighted_matrix =  vectorizer.fit_transform(train_df['body']) 
```
## LightGBMで学習

```
train_x = train_tfidf_weighted_matrix.toarray()
train_y = train_df['author']

lgb_train = lgb.Dataset(train_x, train_y)
# lgb_val = lgb.Dataset(val_x, val_y, reference=lgb_train)

lgbm_params = {
    'objective': 'regression',
    'metric': 'rmse',
    'verbose': -1,
}

clf_lgb = lgb.train(lgbm_params,
                    train_set=lgb_train,
                    num_boost_round=1000,
                    verbose_eval=50,
                    )
```

## SHAP値
explainerを構築、SHAP値取得。

```
explainer = shap.TreeExplainer(clf_lgb)
shap_values = explainer.shap_values(train_x)
```

## 上位特徴量を表示

```
shap.summary_plot(shap_values,
                  train_x,
                  feature_names=vectorizer.get_feature_names(),
                  plot_type='dot')
```

![summary_plot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483137/a4d5a36b-bf7e-ac58-933c-c7a3d006d66e.png)




## Feature Importance
Feature Importanceも。

```
shap.summary_plot(shap_values,
                  train_x,
                  feature_names=vectorizer.get_feature_names(),
                  plot_type='bar')
# savefig用　f3.savefig('./feature_impo
```

![feature_importance.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483137/5e932478-246d-e213-396c-a013b601b8a4.png)

# 特徴量の特徴を考える
特徴量のトップは「大正」。芥川は、1892年〈明治25年〉-1927年〈昭和2年〉の人なので、大正をまるまる生きた人となります。ただ芥川以外の作者がどの時代かはわからないので、この特徴量が本当に効いているかどうかは不明。一人称は「僕」中心？「唯」とか「心もち」という言葉を多用しているのか？下の方の「莫迦」って、「馬鹿」でも「ばか」でもなく「莫迦」をよく使う？「テエブル」は何かと思ったら「テーブル」らしい。芥川特有の使い方なのか？他の作者がどう使っているかはチェックしていません。ワードクラウドにある「谷崎」は谷崎潤一郎(1886年（明治19年）-1965年（昭和40年）)？だとすると、文書中には小説以外も含まれるのか？
しかし、これくらいで文書の仕分けができちゃうのが結構驚きではありますね。

# 参考文献
これのLogistic RegressionをLightGBMで置き換えました。
[Sentiment Analysis by SHAP with Logistic Regression](https://h1ros.github.io/posts/sentiment-analysis-by-shap-with-logistic-regression/)