再訪scikit-learn

[scikit-learn](http://scikit-learn.org/)の便利なAPIについて，新しく知ったことをメモする．
$HOME/Desktop/memo.mdにメモを書き溜めていたが誤ってrmしてしまったのでQiitaを使ってみる．
新しく学びを得たら随時追加していく．

（間違いがあったらご指摘頂けると幸いです．どうかよろしくお願いします．）



## データの前処理

ライブラリの Preprocessing モジュールには神が宿っており， import する際にはコミッターに感謝を捧げる必要がある．

### 素性の次元削減

k個の素性を選択する．
SelectKBestの第一引数は関数でchi2のほかにもf_classifなどが使える．
素性の次元削減には他にも RFECV などがあるけれど，どう違うのかよく分かっていないので今後調べたい．

```python
from sklearn.feature_selection.univariate_selection import SelectKBest, chi2

fselect = SelectKBest(chi2 , k=2000)
```

参考: [How do I properly use SelectKBest, GridSearchCV, and cross-validation in the sklearn package together?](https://www.quora.com/How-do-I-properly-use-SelectKBest-GridSearchCV-and-cross-validation-in-the-sklearn-package-together)


### 欠損値を持ってるデータに対処する

Pandasという近代兵器はデータの前処理を飛躍的に楽にしてくれた．
Jupyter notebookと相性が良く，１行でNaNを含むデータを除去してくれる．
NaNや場合によっては0などは，欠損値と呼ばれる．

欠損値の除去は， scikit-learn でもできる．
参考記事（公式トキュメント）のサンプルを抜粋する．
imp.fit() をしたときに平均値や中央値を計算しておいて， imp.transform() したときには欠損値の値を平均値や中央値に置き換える．

```python
import numpy as np
from sklearn.preprocessing import Imputer
imp = Imputer(missing_values='NaN', strategy='mean', axis=0)
imp.fit([[1, 2], [np.nan, 3], [7, 6]])

X = [[np.nan, 2], [6, np.nan], [7, 6]]
print(imp.transform(X)) 
# [[ 4.          2.        ]
#  [ 6.          3.666...]
#  [ 7.          6.        ]]
```

参考: [scikit-learn公式ドキュメント](http://scikit-learn.org/stable/modules/preprocessing.html#imputation-of-missing-values)


### データを正規化する

いつも正規化は自分で書いていた（と言っても numpy のメソッドをいくつか呼ぶだけだけど）が，scikit-learnがやってくれる．
L1ノルムとL2ノルムが使える．ドキュメントのサンプルコードを抜粋する．

```python
X = [[ 1., -1.,  2.],
     [ 2.,  0.,  0.],
     [ 0.,  1., -1.]]
X_normalized = preprocessing.normalize(X, norm='l2')

print(X_normalized)
# array([[ 0.40..., -0.40...,  0.81...],
#       [ 1.  ...,  0.  ...,  0.  ...],
#       [ 0.  ...,  0.70..., -0.70...]])
```

参考: [scikit-learn公式ドキュメント](http://scikit-learn.org/stable/modules/preprocessing.html#normalization)


### データをスケーリングする

日本語がおかしい気もする．スケーリングって日本語ではなんて言うんだろう．
平均0，分散1になるようにデータを加工する．

サンプルを(ry

ドキュメントにも書いてあるが，Pipelineと組み合わせることができる．

```python
from sklearn import preprocessing
import numpy as np
X = np.array([[ 1., -1.,  2.],
              [ 2.,  0.,  0.],
              [ 0.,  1., -1.]])
X_scaled = preprocessing.scale(X)

print(X_scaled)
# array([[ 0.  ..., -1.22...,  1.33...],
#       [ 1.22...,  0.  ..., -0.26...],
#       [-1.22...,  1.22..., -1.06...]])
```

StandardScaler とか MinMaxScaler とか色々あります．
下記のドキュメントを読んでください（fit, transformなAPIが生えている）．

参考: [scikit-learn公式ドキュメント](http://scikit-learn.org/stable/modules/preprocessing.html#standardization-or-mean-removal-and-variance-scaling)


## モデルの検証

### ラベルの比率を良い感じにしつつKFold

KFold は split() を呼ぶとき，サンプル数次元の配列を入力すれば良いが， StratifiedKFold はラベルの配列を入力する必要がある．
それはなぜか？教師データのラベルの比率を良い感じにしてくれるからである．

```python
from sklearn.model_selection import StratifiedKFold
X = np.array([[1, 2], [3, 4], [1, 2], [3, 4]])
y = np.array([0, 0, 1, 1])
skf = StratifiedKFold(n_splits=2)
skf.get_n_splits(X, y)

print(skf)  

for train_index, test_index in skf.split(X, y):
   print("TRAIN:", train_index, "TEST:", test_index)
   X_train, X_test = X[train_index], X[test_index]
   y_train, y_test = y[train_index], y[test_index]
```

参考: [scikit-learn 公式ドキュメント](http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html)


## 前処理 -> 学習 をシームレスに

前処理が必要なデータを扱うとき，前処理や素性の抽出を model.fit() の中でできるとコードが綺麗になりそう．
Pipelineというシステムがこれを実現してくれる．名前のままである．

### Before

```python
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.svm import LinearSVC

# build the feature matrices
ngram_counter = CountVectorizer(ngram_range=(1, 4), analyzer='char')
X_train = ngram_counter.fit_transform(data_train)
X_test  = ngram_counter.transform(data_test)

# train the classifier
classifier = LinearSVC()
model = classifier.fit(X_train, y_train)

# test the classifier
y_test = model.predict(X_test)
```

### After

```python
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.svm import LinearSVC

# build the pipeline
ppl = Pipeline([
              ('ngram', CountVectorizer(ngram_range=(1, 4), analyzer='char')),
              ('clf',   LinearSVC())
      ])

# train the classifier
model = ppl.fit(data_train)

# test the classifier
y_test = model.predict(data_test)
```

コードは下記のブログ記事から引用したが，記事の下のほうにFeatureUnionというのもある．

参考: [Using Pipelines and FeatureUnions in scikit-learn](http://michelleful.github.io/code-blog/2015/06/20/pipelines/)


## 評価指標

### ロジスティックロス





## 自作学習器でもscikit-learnのエコシステムに乗りたい

scikit-learn が提供していないモデルを使う必要が出てきたら，自分で実装をしなければならない．
モデルを実装したあと，評価するために cross validation や grid search をする必要がある．
scikit-learn の model_selectionのレールに乗れたらこのへんの実装をさぼることができる．
これは，モデルの実装の際に BaseEstimator を継承することで達成できる．

下記の参考記事で示されているコードを引用する．

```python
from sklearn.base import BaseEstimator

class MyEstimator(BaseEstimator):
    def __init__(self, param1, param2):
        self.param1 = param1
        self.param2 = param2

    def fit(self, x, y):
        return self 

    def predict(self, x):
        return [1.0]*len(x) 

    def score(self, x, y):
        return 1

    def get_params(self, deep=True):
        return {'param1': self.param1, 'param2': self.param2}

    def set_params(self, **parameters):
        for parameter, value in parameters.items():
            setattr(self,parameter, value)
        return self
```

ちなみに， ClassifierMixin ， RegressorMixin を継承するとscikit-learn側で実装された model.score が使える．
レールには積極的に乗って行きたい．

参考: [scikit-learn で最低限の自作推定器(Estimator)を実装する](http://qiita.com/_takoika/items/89a7e42dd0dc964d0e29)