言語処理100本ノック-76(scikit-learn使用):ラベル付け

[言語処理100本ノック 2015](http://www.cl.ecei.tohoku.ac.jp/nlp100/)の76本目「ラベル付け」の記録です。
ノックの設問内容は訓練データに対する予測でラベル付けですが、今回はあえてテストデータで実施しています。
今までは基本的に[「素人の言語処理100本ノック」](https://qiita.com/segavvy/items/fb50ba8097d59475f760)とほぼ同じ内容にしていたのでブロクに投稿していなかったのですが、[「第8章: 機械学習」](http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch8)については、真剣に時間をかけて取り組んでいてある程度変えているので投稿します。[scikit-learn](https://scikit-learn.org/)をメインに使用します。

# 参考リンク

|リンク|備考|
|:--|:--|
|[076.ラベル付け.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/08.%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92/076.%E3%83%A9%E3%83%99%E3%83%AB%E4%BB%98%E3%81%91.ipynb)|回答プログラムのGitHubリンク|
|[素人の言語処理100本ノック:76](https://qiita.com/segavvy/items/e107f764534f01c5b105)|言語処理100本ノックで常にお世話になっています|
|[言語処理100本ノックでPython入門 #76 - 機械学習、scikit-learnでの予測確率](http://gushwell.ldblog.jp/archives/52530374.html)|scikit-learn使ったノック結果|

# 環境

|種類|バージョン|内容|
|:--|:--|:--|
|OS|Ubuntu18.04.01 LTS|仮想で動かしています|
|pyenv|1.2.15|複数Python環境を使うことがあるのでpyenv使っています|
|Python|3.6.9|pyenv上でpython3.6.9を使っています<br>3.7や3.8系を使っていないことに深い理由はありません<br>パッケージはvenvを使って管理しています|

上記環境で、以下のPython追加パッケージを使っています。通常のpipでインストールするだけです。

|種類|バージョン|
|:--|:--|
|matplotlib|3.1.1|
|numpy|1.17.4|
|pandas|0.25.3|
|scikit-learn|0.21.3|

# 課題
## [第8章: 機械学習](http://www.cl.ecei.tohoku.ac.jp/nlp100/#ch8)
>本章では，Bo Pang氏とLillian Lee氏が公開している[Movie Review Data](http://www.cs.cornell.edu/people/pabo/movie-review-data/)の[sentence polarity dataset v1.0](http://www.cs.cornell.edu/people/pabo/movie-review-data/rt-polaritydata.README.1.0.txt)を用い，文を肯定的（ポジティブ）もしくは否定的（ネガティブ）に分類するタスク（極性分析）に取り組む．

### 76. ラベル付け
> 学習データに対してロジスティック回帰モデルを適用し，正解のラベル，予測されたラベル，予測確率をタブ区切り形式で出力せよ．

今回は「学習データに」という部分を無視し、テストデータに対して実施しています。学習データよりテストデータの方が有用では、と考えたからです。

# 回答
## 回答プログラム [076.ラベル付け.ipynb](https://github.com/YoheiFukuhara/nlp100/blob/master/08.%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92/076.%E3%83%A9%E3%83%99%E3%83%AB%E4%BB%98%E3%81%91.ipynb)
基本的に[前回の「回答プログラム(分析編) 075.素性の重み.ipynb」](https://github.com/YoheiFukuhara/nlp100/blob/master/08.%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92/075.%E7%B4%A0%E6%80%A7%E3%81%AE%E9%87%8D%E3%81%BF.ipynb)に予測とファイル出力ロジックを付加した程度です。

```python
import csv

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.pipeline import Pipeline
from sklearn.base import BaseEstimator, TransformerMixin

# 単語ベクトル化をGridSearchCVで使うのためのクラス
class myVectorizer(BaseEstimator, TransformerMixin):
    def __init__(self, method='tfidf', min_df=0.0005, max_df=0.10):
        self.method = method
        self.min_df = min_df
        self.max_df = max_df

    def fit(self, x, y=None):
        if self.method == 'tfidf':
            self.vectorizer = TfidfVectorizer(min_df=self.min_df, max_df=self.max_df)
        else:
            self.vectorizer = CountVectorizer(min_df=self.min_df, max_df=self.max_df)
        self.vectorizer.fit(x)
        return self

    def transform(self, x, y=None):
        return self.vectorizer.transform(x)
		
# GridSearchCV用パラメータ
PARAMETERS = [
    {
        'vectorizer__method':['tfidf', 'count'], 
        'vectorizer__min_df': [0.0003, 0.0004], 
        'vectorizer__max_df': [0.07, 0.10], 
        'classifier__C': [1, 3],    #10も試したが遅いだけでSCORE低い
        'classifier__solver': ['newton-cg', 'liblinear']},
    ]

# ファイル読込
def read_csv_column(col):
    with open('./sentiment_stem.txt') as file:
        reader = csv.reader(file, delimiter='\t')
        header = next(reader)
        return [row[col] for row in reader]    
		
x_all = read_csv_column(1)
y_all = read_csv_column(0)
x_train, x_test, y_train, y_test = train_test_split(x_all, y_all)

def train(x_train, y_train, file):
    pipline = Pipeline([('vectorizer', myVectorizer()), ('classifier', LogisticRegression())])
    
    # clf は classificationの略
    clf = GridSearchCV(
            pipline, # 
            PARAMETERS,           # 最適化したいパラメータセット 
            cv = 5)               # 交差検定の回数
    
    clf.fit(x_train, y_train)
    pd.DataFrame.from_dict(clf.cv_results_).to_csv(file)

    print('Grid Search Best parameters:', clf.best_params_)
    print('Grid Search Best validation score:', clf.best_score_)
    print('Grid Search Best training score:', clf.best_estimator_.score(x_train, y_train))    
    
    # 素性の重み出力
    output_coef(clf.best_estimator_)
    
    return clf.best_estimator_

# 素性の重み出力
def output_coef(estimator):
    vec = estimator.named_steps['vectorizer']
    clf = estimator.named_steps['classifier']

    coef_df = pd.DataFrame([clf.coef_[0]]).T.rename(columns={0: 'Coefficients'})
    coef_df.index = vec.vectorizer.get_feature_names()
    coef_sort = coef_df.sort_values('Coefficients')
    coef_sort[:10].plot.barh()
    coef_sort.tail(10).plot.barh()

def validate(estimator, x_test, y_test):
    
    for i, (x, y) in enumerate(zip(x_test, y_test)):
        y_pred = estimator.predict_proba([x])
        if y == np.argmax(y_pred).astype( str ):
            if y == '1':
                result = 'TP:正解がPositiveで予測もPositive'
            else:
                result = 'TN:正解がNegativeで予測もNegative'
        else:
            if y == '1':
                result = 'FN:正解がPositiveで予測はNegative'
            else:
                result = 'FP:正解がNegativeで予測はPositive'
        print(result, y_pred, x)
        if i == 29:
            break

    # TSV一覧出力
    y_pred = estimator.predict(x_test)
    y_prob = estimator.predict_proba(x_test)

    results = pd.DataFrame([y_test, y_pred, y_prob.T[1], x_test]).T.rename(columns={ 0: '正解', 1 : '予測', 2: '予測確率(ポジティブ)', 3 :'単語列'})
    results.to_csv('./predict.txt' , sep='\t')

estimator = train(x_train, y_train, 'gs_result.csv')
validate(estimator, x_test, y_test)

```

## 回答解説
以下のタブ区切りファイルを`pandas`の`to_csv`関数で出力しています。

|列|項目|例||
|:-:|:-:|:--|:--|
|1列目|正解ラベル|もともと持っていた正解ラベル|0(0はネガティブ)|
|2列目|予測ラベル|`predict_proba`関数を使って取得した予測結果|0(0はネガティブ)|
|3列目|予測確率|`predict_proba`関数を使って取得した予測確率。<br>関数の返り値の2列目はポジティブである確率です|0.4436|
|4列目|単語列|もともと持っていた単語列の説明変数|empti shell epic rather real deal|


```python
# TSV一覧出力
y_pred = estimator.predict(x_test)
y_prob = estimator.predict_proba(x_test)

results = pd.DataFrame([y_test, y_pred, y_prob.T[1], x_test]).T.rename(columns={ 0: '正解', 1 : '予測', 2: '予測確率(ポジティブ)', 3 :'単語列'})
results.to_csv('./predict.txt' , sep='\t')
```

出力したファイル[predict.txt](https://github.com/YoheiFukuhara/nlp100/blob/master/08.%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92/predict.txt)はGitHubに置いています。