scikit-learnでSVMのパラメータを調節してみた話


※ この記事は **[エキサイト Advent Calendar 2017](https://qiita.com/advent-calendar/2017/excite)** 24 日目の記事です ※

ごあいさつ
---
クリスマスイブですね。僕は一人です。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3231313136342f66376136386133632d663637352d643437302d653736352d3862306462306437373634352e706e67 (1).png](https://qiita-image-store.s3.amazonaws.com/0/211139/0cc0cde8-318a-79ac-0b53-3961d4ca9a63.png)

の予定だったんですが心優しい先輩に誘われて夜は飲みに行くことになりました。

今年から新卒一年目で配属しました [**ウーマンエキサイト**](https://woman.excite.co.jp/) エンジニアの[本田](https://qiita.com/arata-honda)です！

![ウーマンエキサイト](https://image.excite.co.jp/jp/woman/sp/images/logo-woman.png)

今年5月末にウーマンチームに配属しまして、 **[ウーマンエキサイトアプリ](
https://play.google.com/store/apps/details?id=jp.co.excite.woman.topics&hl=ja)**のAPI担当として
[リニューアルバージョンアップ](https://www.excite.co.jp/News/it_g/20171002/Cobs_1675791.html)を行いました。

アプリAPIと並行してウーマンチームで機械学習プロジェクトを鋭意進行中でありまして、
今日はその中で使っているSVM(サポートベクターマシーン)のパラメータ調節までまとめてみました。

SVM (サポートベクターマシーン)
---

SVMの話については、今日のために事前にまとめておきました。
[SVM(サポートベクターマシーン)についてまとめてみた](https://qiita.com/arata-honda/items/bc24cbd953bd9d2c743c)

こちらの記事で内容をざっくり確認すると以下の内容がすっきりわかるかと思います！

三行でSVMについて言うと

 - データ集合を線形分離する識別関数である。
 - サポートベクトルの概念を取り入れ、識別平面との距離(マージン)を最大化する。
 - データ集合が線形分離不可能な場合はソフトマージンを用いる。

なんでSVM？
---
最近の深層学習の発展でSVMは一昔前の学習器として言われがちなのですが、
今回使う理由としては、

 - そもそも深層学習するほどデータ数があるわけではない

 - 深層学習のラーニングコストを考えて、一から勉強するには時間がかかると思ったから

 - データの次元が大きくなっても識別精度がいい

 - いじるパラメータが少ない(これが一番でかい)

結局どれだけ実装が簡単でもチューニングするパラメータが多くて難しいと、
その分だけラーニングコストがかかってしますからです。
深層学習だとニューラルネットワークのアーキテクチャから考慮しなければならないので、
その点も踏まえてSVMにしました。
あと個人的に院生のときにHawaiiでカーネル法関連の研究を行ったときに、
SVMに対する障壁がなくなったので、使ってみようという好奇心もありました。


Scikit-learnの導入
---

導入に関しても、今日のために事前に[第一回Excite Open Beer Bash](http://blog.excite.co.jp/exdev/28778289/) で発表した資料でまとめています。
(ばちくそに滑りましたが)
[Scikit-learnを使って画像分類を行う](https://www.slideshare.net/aratahonda1/scikit-learn-84300176)

<a href="https://www.slideshare.net/aratahonda1/scikit-learn-84300176"><img src="https://image.slidesharecdn.com/scikit-learn-171217141657/95/scikit-learn-1-638.jpg?cb=1513520378"></a>

今回はscikit-learnを使うためにanacondaをインストールします。

```bash
pyenv install anaconda3-5.0.0
```
これだけでmacやunixユーザはインストールすることができます。(もちろんpyenvは入れる必要があります)

念のためpyenvの入れ方も

```bash
git clone git://github.com/yyuu/pyenv.git ~/.pyenv
brew install pyenv
```
もちろんhomebrewもry
少し重たいのでインストールに時間がかかると思いますが、しばらく待ちます。

SVMの導入
---

anaconda3がインストールできた状態で、atomでもpyCharmでも開いて早速svmを使いましょう。

```py3
from sklearn import svm
clf = svm.LinearSVC()
```
importしてsvmインスタンスの変数を入れるだけです。

ここで、[LinearSVC](http://scikit-learn.org/stable/modules/generated/sklearn.svm.LinearSVC.html)を使っています。
[saketさんの記事](http://d.hatena.ne.jp/saket/20130212/1360656405)が非常にわかりやすかった参考にさせていただきます。

>scikit.learnでは分類に関するSVMは

>
 - SVC
 - LinearSVC
 - NuSVC

>の3つである. SVCは標準的なソフトマージン(エラーを許容する)SVMである. 一方, NuSVCはエラーを許容する表現が異なるSVMである. LinearSVCはカーネルが線形カーネルの場合に特化したSVMであり, 計算が高速だったり, 他のSVMにはないオプションが指定できたりする

### LinearSVCの主要パラメータの簡単な解説

| パラメータ | 説明 | 
|:-----------|------------:|
| penalty | 罰則項。L1正則化・L2正則化(デフォルト)を選択可|
| loss | 評価関数。ヒンジ損失か二乗ヒンジ(デフォルト) |
| dual | 双対問題を解くか否か(デフォルトはtrue) |
| tol | アルゴリズムの終了条件(default=1e-4) |
| C | ソフトマージンの厳しさを表すパラメータ |

L1正則化・L2正則化が選択できるのがいい点ですね。上記のほかのSVMにないオプションです。
[正則化の解説](https://www.slideshare.net/aratahonda1/ss-84302572)
<a href="https://www.slideshare.net/aratahonda1/ss-84302572">
<img src="https://image.slidesharecdn.com/random-171217151855/95/-1-638.jpg?cb=1513523994">
</a>
`penalty`と`loss`の組み合わせは三通りで

- Standard : loss=L1, penalty=L2 (通常のSVM)
- LossL2 : loss=L2, penalty=L2
- PenaltyL1 : loss=L2, penalty=L1

このようになっています。
余談：L1にするとパラメータwの解がスパース(疎)になる利点があります。

**この三種類の条件で、もっとも良い精度を出してくれるのはなにかをチューニングする必要があります。**

Gridsearch-CVでのパラメータチューニング
---

scikit-learnでGridsearch-CVとは何？ということで
一言で言えば、
**[パラメータをある範囲で全部試して一番精度の良いパラメータ条件を引っ張ってくるライブラリ](http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html)**です。

パラメータをある範囲で全部試すところがGridsearchにあたり、
CVは交差検定方法(Cross-Validation)です。

CV法は簡単に説明すると、

<img width="300px" alt="Cross-validation" src="https://qiita-image-store.s3.amazonaws.com/0/211139/c9b78312-19ba-b6bf-ffc3-e9889ca8b2b9.png">

学習用のデータを整数k個で分割して、それぞれの試行で出た精度を平均化してスコアを出してくれます。

### 結局何を調節するの？
ソフトマージンの制約パラメータである`C`です

### 導入

実際に導入していきます。

```py3
# 学習データと評価データへ分割するライブラリの導入
from sklearn.model_selection import train_test_split
# グリッドサーチCVの導入
from sklearn.model_selection import GridSearchCV
# svmのインポート
from sklearn import svm

# todo: 学習用データを用意する

# 特徴量と正解ラベルを学習データと評価データへ分割(3割をテストデータに)
data_train, data_test, label_train, label_test = train_test_split(
        inputVec, outputVec, test_size=0.3)

# LinearSVCの取りうるモデルパラメータを設定
Standard = svm.LinearSVC(penalty='l2', loss='hinge', dual=True, tol=1e-3)
LossL2 = svm.LinearSVC(
        penalty='l1', loss='squared_hinge', dual=False, tol=1e-3)
PenaltyL1 = svm.LinearSVC(
        penalty='l2', loss='squared_hinge', dual=True, tol=1e-3)
model_set = [Standard, LossL2, PenaltyL1]

# グリッドサーチしたいパラメータCの値域をnp.logspaceで設定
tuned_parameters = [
        {'C': np.logspace(-1, 2, 30)}
]
# 各モデルを一つずつCに関してグリッドサーチを行う
for model in model_set:
    # 5-fold Cross-Validationで評価指標はprecision(適合率)を選択  
    clf = GridSearchCV(model, tuned_parameters, cv=5, scoring='precision')
    # 設定したパラメータで学習しつつ検証を行う
    clf.fit(data_train, label_train)
```
グリッドサーチには、一番良いスコアを返す`best_score_`や、
一番いいスコアのモデル設定条件を返す、`best_estimator_`などがあります。

*三種類のモデルパラメータで且つ一番いいパラメータCを保持して、
最終的なテストのスコアで最高のモデルを保存すればいいでしょう。*

感想
---

- 実装に関しては簡単ではあるものの、*GridSearchはあくまで特定範囲内での探索方法*なので、
実際はベイズ推定などのパラメータ推定法を用いるべきであると思いました。

- 学生の時は理論ばかりやっていたのでここまでpythonのコードを書けるまで随分成長したとしみじみ

参考
---

- [scikit.learn手法徹底比較！ SVM編](http://d.hatena.ne.jp/saket/20130212/1360656405)
- [C-SVMのチューニング(正則化パラメータの最適化)](https://qiita.com/yh0sh/items/d58a3e4850ec77b58be0)

---
最後に
--
**[エキサイト Advent Calendar 2017](https://qiita.com/advent-calendar/2017/excite)**も明日でとうとう最終日です。
最後のトリは【**3人できるユーザビリティテスト**】、気になります！
メリークリスマス！
![400761c3e3681b40c5b7e68df2573af5_400.jpg](https://qiita-image-store.s3.amazonaws.com/0/211139/d84fcf16-6997-ce3a-a8f9-6d0aa739bb8d.jpeg)