デジタル・デザイン・ラボラトリーな日々

アラフィフプログラマーが数学と物理と英語を基礎からやり直す。https://qiita.com/yaju

機械学習の勉強再開 線形SVM

はじめに

2021年最初の記事となります。
昨年は機械学習の勉強に身が入らなかったため、はてな側に記事を書くことも少なったです。
今年は、競馬予想とか株価予想とかをやってみたいなと思うようになったので、改めて機械学習の勉強を再開することにしました。

手を動かす

競馬予想とか株価予想とかをやる前に、自分には先に片付けるものがあります。「サザエさんのじゃんけん データ分析」です。
サザエさんのじゃんけんをきちんとディープラーニングとかでやってみたいなと思ってはいたんですが、機械学習はこれまでやってきたパズル的にプログラムを組むと違って、理論的に理解した上でプログラムを組む必要があるわけです。本を読んだりしてもピンとこないんです。

ディープラーニングにこだわることはやめて、せっかく他の方法の機械学習でやり方を示して頂いたわけですから、まずこれを理解させていただきます。 r-std.hatenablog.com

この参考サイトは R言語で作成されていますので、これをPythonに移植することから始めることにしました。
データが2017年と古いですが学ぶ上では関係ありません。

R言語とR Studioのインストール

以前R言語をやったときはWindowsでしたが、MacになったのでMacにインストールします。
qiita.com

作業ディレクトリの変更

ファイルメニューのRStudio→PreferenceまたはTools→Global Optionで環境設定を開きます。
GeneralのDefault working directoryを初期値「~」から変更します。
Windows時に使用していたフォルダがあったので「~/workspace/Sazae_R」にしています。
作業フォルダを変更したら、R Studioを起動し直す必要があります。

ちなみにコマンドで「getwd()」とすると現在の作業フォルダが見れます。

線形SVM

R言語

###データの読み込み
sze_row=read.csv("2017sze.csv",header = T, stringsAsFactors=T)
sze=sze_row[,-9]
#1992年~2016年までを学習用、2017年分をテスト用とする
train=c(1:1254)
test=c(1255:1302)

###seedはサザエさん(3383)とする
set.seed(3383)

###線形SVM
library(e1071)
sze.svm=svm(X~.,sze[train,],method = "C-classification",
           kernel = "linear",
           cost = 2.5 )
test.svm=predict(sze.svm, sze[test,])
svm.tab=table(sze[test,1],test.svm)
svm.tab
sum(svm.tab[row(svm.tab)==col(svm.tab)])/sum(svm.tab) #識別率

結果

C G P
C 13 2 2
G 2 9 4
P 5 1 10

32勝11敗5分

苦労したところ

sze.svm=svm のところで、"Need numeric dependent variable for regression."のエラーが出て解決するまで時間がかかりました。
Rのバージョンを4にしたことで、read_csvのデフォルト設定が変わっていたことが原因でした。

qiita.com

Python

Google Colaboratoryで動作するようにしています。
Google Colaboratoryで複数行コメントする場合、コメントする行を選択して、[Command] + [/] を押しますと、"#" でコメントされます。外す場合も同様にします。
Pythonの複数行コメントにはダブルクォーテーション3つで挟むというのがあるのですが、Google Colaboratoryでは文字列として結果に表示されてしまうためです。

www.atmarkit.co.jp

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_row.loc[sze_row.X == "G", "X"] = 1
sze_row.loc[sze_row.X == "C", "X"] = 2
sze_row.loc[sze_row.X == "P", "X"] = 3
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

sze = sze_row.iloc[:, :-1]

#1992年~2016年までを学習用、2017年分をテスト用とする
train = range(0, 1253)
test = range(1254, 1301)

x_train = sze.iloc[train, 1:].to_numpy(dtype=int)
y_train = sze.iloc[train, 0].to_numpy(dtype=int)
x_test = sze.iloc[test, 1:].to_numpy(dtype=int)
y_test = sze.iloc[test, 0].to_numpy(dtype=int)

# 分類器svm seedはサザエさん(3383)とする
model = svm.LinearSVC(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

結果

1 2 3
1 13 0 2
2 8 4 5
3 3 0 13

30勝8敗10分

R言語版と結果が違いますが、機械学習なので多少違いは出るのは仕方ない。
R言語版は、Cが13勝です。Python版は、1=Gが13勝になっています。

結果の見方

f:id:Yaju3D:20210104100225p:plain

行(たて)が正解(実際に出された手)、列(よこ)が予測となります。
機械学習では出す手を予測するので勝ち手を選んだことにして勝敗を出しています。※勝ち手を選んだとこまでは現状でプログラムを組んでいません。

  • 黄色セルは、例えばチョキ(C)を出すと予測して勝ち手のグー(G)出した、実際に出された手はチョキ(C)なので勝ちになります。
  • 赤色セルは、例えばチョキ(C)が出ると予測して勝ち手のグー(G)出した、実際に出された手はパー(P)なので負けとなります。
  • 白色セルは、例えばチョキ(C)が出ると予測して勝ち手のグー(G)出した、実際に出された手はグー(G)なので引き分けとなります。

CGPに順序変更

R言語版は文字型が自動的に因子型になっているので、アルファベット順のCGPになっています。
Python版でも、1=G、2=C、3=Pに変換していましたが、これを 1=C、2=G、3=Pに修正して結果を出したところ勝率が減ってしまいました。これなら変更しない方がいいですよね。

1 2 3
1 11 4 2
2 4 6 5
3 4 2 10

27勝13敗8分

苦労したところ

これだけなのに、ものすごく苦労しています。
エラーが幾つか出たのですが、それを解決させるまでが大変。アルゴリズムが悪いわけではないのでデバッグするわけではない。地道にエラーで検索して解決方法を探っていく。
比較として、Irisのdatasetを使用して正しく分類ができることで何故正しく動くのかを理解する。

aiacademy.jp

model.fit で幾つかエラーになりました。

model.fit(sze.iloc[train, 1:], sze.iloc[train, 0]) 

ValueError: could not convert string to float: 'G'

PythonのSVMでは文字列は使えないと判断して、数値に変換するようにしました。しかし、この方法では下記の警告メッセージが表示されます。

sze_row.X[sze_row.X == "G"] = 1

/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:13: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  del sys.path[0]

qiita.com

下記コードに変更することで警告メッセージが表示されなくなりました。

sze_row.X[sze_row.X == "G"] = 1
              ↓
sze_row.loc[sze_row.X == "G", "X"] = 1

文字型は無くしたのですが、次に下記のエラーがでました。

model.fit(sze.iloc[train, 1:], sze.iloc[train, 0])

ValueError: Unknown label type: 'unknown'

fit には、データフレーム型は使えないようで Array型に変換する必要があります。
ただto_numpy()で Array型に変換しても同じエラーになったため、数値型と判断されるように to_numpy(dtype=int) で型を指定することでようやっとエラーが解消されました。

x_train = sze.iloc[train, 1:].to_numpy(dtype=int)
y_train = sze.iloc[train, 0].to_numpy(dtype=int)

model.fit(x_train, y_train) 

識別率のところで、警告が出てました。

print(accuracy_score(y_test, pred))

0.625
/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)

neko-py.com

「 ConvergenceWarning:Liblinearは収束に失敗しました。反復回数を増やしてください。 「反復回数」、ConvergenceWarning) 」 テスト回数が少なくて設定値を上げろと言っているっぽい 引用元のライブラリの説明をみてみると「 max_iter 」がデフォルト値だと小さいみたいなので値を上げてみたら警告が解消されました。

LinearSVCのとことにmax_iter=5000 を追加することで、警告が表示がされなくなりました。

model = svm.LinearSVC(C=2.5, random_state=3383)
                                                       ↓
model = svm.LinearSVC(C=2.5, max_iter=5000, random_state=3383)

R言語版のtable関数がクロス集計で Pythonだと pandas.crosstab()関数を使えばクロス集計が出来るとわかりました。

tab = pd.crosstab(y_test, pred)
# 識別率
print(accuracy_score(y_test, pred))

tab

col_0   1  2  3
row_0           
1  13 0  2
2  8  4  5
3  3  0  13

R言語版と同じにするなら、予測値をGCPの文字に変換した上でクロス集計をすればいいです。

最後に

R言語からのPythonへの移植は簡単にできそうだと思ったのに、エラーと警告が表示されて結構苦労しまくりでした。
R言語だと文字型というか factor型(因子型)がサポートされているので、GCPの文字でも問題なく動くのは便利ですね。
Pythonでは factor型の代替として、pandasで Categoricalの型(dtype)がサポートされているようです。

qiita.com

線形SVM(サポートベクターマシン)のアルゴリズムの理解は置いておいた、どういう動きで学習と予測を求めているかの理解は出来ました。
次の方法も順々にやっていきます。

  • RBFカーネルを用いたSVM
  • randomForest
  • 決定木
  • naive bayes

最終的には2018年と2019年と2020年もやっていって、理解が進めばオリジナルに挑戦していきます。

スポンサーリンク