はじめに
前回、R言語の線形SVMをPythonに移植しました。 yaju3d.hatenablog.jp
Pythonやscikit-learn に慣れていなかったので、もう少しいい方法があるのではないかと再調査しました。
SVMでは文字列は使えるのか
結論では、正解ラベル側には文字列が使えますが、学習データ側は文字列が使えないため数値型に変換する必要があります。
長くなったのでQiitaに別記事としました。
qiita.com
変更前の数値型変換ですが、イケてないですよね。
sze_row.loc[sze_row.X1 == "G", "X1"] = 1 sze_row.loc[sze_row.X1 == "C", "X1"] = 2 sze_row.loc[sze_row.X1 == "P", "X1"] = 3 sze_row.loc[sze_row.X2 == "G", "X2"] = 1 sze_row.loc[sze_row.X2 == "C", "X2"] = 2 sze_row.loc[sze_row.X2 == "P", "X2"] = 3 sze_row.loc[sze_row.X3 == "G", "X3"] = 1 sze_row.loc[sze_row.X3 == "C", "X3"] = 2 sze_row.loc[sze_row.X3 == "P", "X3"] = 3
これを改善しました。
# ラベルエンコーディング rating = {'G' : 1, 'C' : 2, 'P' : 3} sze_row.X1 = sze_row.X1.map(lambda x : rating[x]) sze_row.X2 = sze_row.X2.map(lambda x : rating[x]) sze_row.X3 = sze_row.X3.map(lambda x : rating[x])
SVC(kernel='linear')とsvm.LinearSVCの違い
線形SVMですが、kernel='linear' を指定する方法とLinearSVCを方法の2種類あります。
これが同じかというと微妙に違うの結果も変わってきます。
When should one use LinearSVC or SVC? - StackOverflow
LinearSVCがLIBLINEARライブラリーを基にしているに対し、SVC(kernel='linear')はLIBSVMライブラリーを基にしている。
LinearSVCの場合、デフォルトはヒンジ損失の2乗(loss='squared_hinge')を最小化するように設定されています。
LIBSVMはカーネル法を用いたサポートベクターマシン (SVM) の学習に使うSMOアルゴリズムを実装しており、分類と回帰に対応している[1]。 LIBLINEARは線形SVMと、座標降下法(英語版)アルゴリズムを用いて学習するロジスティック回帰を実装している LIBSVM - wikipedia
SVC(kernel='linear')
model = svm.SVC(kernel='linear', C=2.5, max_iter=5000, random_state=3383)
結果
0.6458333333333334
C | G | P | |
---|---|---|---|
C | 8 | 6 | 3 |
G | 0 | 13 | 2 |
P | 3 | 3 | 10 |
31勝10敗8分けとなります。
LinearSVC
デフォルトは、loss='squared_hinge' なので指定しなくても同じになります。
#model = svm.LinearSVC(C=2.5, max_iter=5000, random_state=3383) model = svm.LinearSVC(loss='squared_hinge', C=2.5, max_iter=5000, random_state=3383)
結果
0.625
C | G | P | |
---|---|---|---|
C | 5 | 8 | 4 |
G | 0 | 13 | 2 |
P | 1 | 3 | 12 |
30勝11敗7分け
LinearSVCの補足
loss='hinge' に変更すると結果がかなり悪くなります。
model = svm.LinearSVC(loss='hinge', C=2.5, max_iter=20000, random_state=3383)
max_iter=5000 では下記の警告が発生します。Google翻訳すると「Liblinearは収束に失敗しました。反復回数を増やしてください。 」ということなので、max_iter=20000 まで上げたところ警告が出なくなりました。
/usr/local/lib/python3.6/dist-packages/sklearn/svm/_base.py:947: ConvergenceWarning: Liblinear failed to converge, increase the number of iterations. "the number of iterations.", ConvergenceWarning)
結果
0.5
C | G | P | |
---|---|---|---|
C | 4 | 6 | 7 |
G | 0 | 6 | 9 |
P | 0 | 2 | 14 |
24勝15敗9分け
ソースコード
import pandas as pd import numpy as np import urllib.request from io import StringIO from sklearn import svm from sklearn.metrics import accuracy_score import random url = "https://raw.githubusercontent.com/yaju/Sazae_R/master/2017sze.csv" # データの読み込み res = urllib.request.urlopen(url) res = res.read().decode("utf-8") sze_row = pd.read_csv(StringIO(res), header=0) # 末尾の日付削除 sze = sze_row.iloc[:, :-1] # ラベルエンコーディング rating = {'G' : 1, 'C' : 2, 'P' : 3} sze.X1 = sze.X1.map(lambda x : rating[x]) sze.X2 = sze.X2.map(lambda x : rating[x]) sze.X3 = sze.X3.map(lambda x : rating[x]) #1992年~2016年までを学習用、2017年分をテスト用とする train = range(0, 1253) test = range(1254, 1302) x_train = sze.iloc[train, 1:] y_train = sze.iloc[train, 0] x_test = sze.iloc[test, 1:] y_test = sze.iloc[test, 0] # 分類器svm #seedはサザエさん(3383)とする model = svm.SVC(kernel='linear', C=2.5, max_iter=5000, random_state=3383) # 学習 model.fit(x_train, y_train) pred = model.predict(x_test) tab = pd.crosstab(y_test, pred) # 識別率 print(accuracy_score(y_test, pred)) tab #print(y_test) #print(pred)
調査
同じ列の優先順序
同じ列に異なる数値があるとモデルのデータによってはある種の順序(0 < 1 < 2)であると誤解するとのことで、値の順番を下記のように変更してみました。
結果は31勝10敗8分けと変わらなかったです。
rating = {'G' : 1, 'C' : 2, 'P' : 3} ↓ rating = {'G' : 3, 'C' : 2, 'P' : 1}
One Hot表現に変更
説明変数側(学習データ)は回帰でも分類でも基本的 One Hot表現を使用するのが一般的なようです。こうすると優先順序が関係なくなるからです。
# ラベルエンコーディング rating = {'G' : 1, 'C' : 2, 'P' : 3} sze.X1 = sze.X1.map(lambda x : rating[x]) sze.X2 = sze.X2.map(lambda x : rating[x]) sze.X3 = sze.X3.map(lambda x : rating[x])
上記の部分を下記に変更します。
# One-hotエンコーディング sze = pd.get_dummies(sze, columns=['X1', 'X2', 'X3'])
pandasのget_dummiesを使うとカラム指定で、One-hotエンコーディングができます。
変換元の列は削除され、末尾にOne-hotの列が追加されます。
変換前
X | X1 | X2 | X3 | Q | Grate | Crate | Prate |
---|---|---|---|---|---|---|---|
C | G | P | G | 1 | 0.333333 | 0.333333 | 0.333333 |
変換後
X | Q | Grate | Crate | Prate | X1_C | X1_G | X1_P | X2_C | X2_G | X2_P | X3_C | X3_G | X3_P |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
C | 1 | 0.333333 | 0.333333 | 0.333333 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
結果
0.6666666666666666
C | G | P | |
---|---|---|---|
C | 13 | 2 | 2 |
G | 2 | 9 | 4 |
P | 5 | 1 | 10 |
32勝11敗5分け
OneHotEncoderの使用
pandasのget_dummiesは便利だけど用途によっては使用を控えた方がいいようなので、OneHotEncoderの使い方も知っておく必要がある。
get_dummiesと同じことを、OneHotEncoderで実現してみました。
ColumnTransformerで複数列を変換できますが、列名ではなくインデックス番号の指定する。
numpy.ndarray で変換されるので一旦データフレームに変換、元のデータフレームで変換前の文字列の列を削除して変換されたデータフレームと結合させた。
from sklearn.preprocessing import OneHotEncoder from sklearn.compose import ColumnTransformer ct = ColumnTransformer([('onehot', OneHotEncoder(sparse=False), [1,2,3])]) columns = ['X1_C', 'X1_G', 'X1_P', 'X2_C', 'X2_G', 'X2_P', 'X3_C', 'X3_G', 'X3_P'] df = pd.DataFrame(ct.fit_transform(sze), columns=columns) sze = sze.drop(['X1', 'X2', 'X3'], axis=1) sze = pd.concat([sze, df], axis=1)
参照
- scikit-learn — カテゴリー機能に関するOneHotEncoderの問題
- OneHotEncoderで「categorical_features」のTypeErrorが発生した時の対処法 | 30代未経験ネットワークエンジニアのblog
- OneHotEncoder – TauStation
最後に
結果からすれば学習データはOne Hot表現にして問題ないですね、実際に結果を良かったですし。
One Hot表現にすることでクロス集計(tab)のところを修正する必要があるのかと気にしていたのですが、全く修正なしでした。
これからは、One Hot表現でやっていきます。
これでようやっと次の段階に入ります。
- RBFカーネルを用いたSVM
- randomForest
- 決定木
- naive bayes