【Python】Python/Scikit-learnで初めてのSVM

# はじめに
Pythonを触っていると機械学習のライブラリが豊富とはよく聞きます。
自分自身も存在は知っていましたが実際に自分で手を動かしてやったことがなかったです。
ただ、[この記事](https://qiita.com/ryu1104/items/06a71d8f6956da889bb8)を読んで簡単にできそう！と思ったのがきっかけで機械学習、特にSVM(Support Vector Machine)をやってみたので投稿します。

ここでは、天気の**データの取得からとても簡単なデータの処理、学習、可視化**までを行います。

主に参考にしたのは以下の2つの記事です。
[【Python】pythonで簡単に機械学習入門(SVM)](https://qiita.com/ryu1104/items/06a71d8f6956da889bb8)
[[python 機械学習初心者向け] scikit-learnでSVMを簡単に実装する](https://qiita.com/kazuki_hayakawa/items/18b7017da9a6f73eba77)

# 環境
Windows 10上のAnacondaで動かしています。

| 名前 | バージョン |
|:-:|:-:|
| Python | 3.7.3 |
| Scikit-learn | 0.23.1 |
| Pandas | 1.0.5 |
| Numpy | 1.18.5 |
| matplotlib | 3.2.2 |
| mlxtend | 0.17.3 |

それぞれ下記のようにpipでインストールできます。

```shell
$ pip install scikit-learn
$ pip isntall pandas
$ pip isntall numpy
$ pip install matplotlib
$ pip install mlxtend
```

# 対象読者
上に上げた2つの記事と同等レベルです。
 - Python、NumpyやPandasを扱える
 - 機械学習の存在くらいは知っている
 - SVMの実装の一連の流れを知りたい

# 機械学習、SVMとは
ここでは詳しいことは省きます。参考の記事などを参考にしてください。

機械学習の分類とは、
>分類
分類タスクでは、事前に定められた有限個のクラスが定められていて、各クラスには、「ネコ」、「イヌ」などのクラスラベル（もしくは単にラベル）と呼ばれるクラス名が割り振られている。分類タスクの目的は与えられた入力xがのいずれに属するかを当てる事である。
[機械学習 分類 - Wikipedia](https://ja.wikipedia.org/wiki/%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92#%E5%88%86%E9%A1%9E)

ここでは、機械学習で気温や降水量、雲量などのデータを使って、天気(ラベル)を当てます。

また、SVMとは以下になります。
>サポートベクターマシン（英: support vector machine, SVM）は、教師あり学習を用いるパターン認識モデルの一つである。分類や回帰へ適用できる。
[SVM - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%83%99%E3%82%AF%E3%82%BF%E3%83%BC%E3%83%9E%E3%82%B7%E3%83%B3)

# 流れ
ここでは、以下の流れに沿って説明します。
※コードだけみたいよ！って方は[こちら](https://qiita.com/koichi_baseball/items/6f4fbd8488b28819547c#%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E5%85%A8%E4%BD%93)です。
1. データの取得
2. データの加工
3. 学習
4. 可視化

# 1. データの取得
今回は天気のデータを扱うことにしたので[気象庁のこのページ](https://www.data.jma.go.jp/gmd/risk/obsdl/)からダウンロードしましょう。

地点、項目(気温や降水量など)、期間を選んでダウンロードしてください。気温や降水量などの項目は自由にダウンロードしてみてください。多い分には学習に使う際に取捨選択をすればいいので困らないと思います。data.csvという名前のcsvファイルがダウンロードできると思います。

※項目の、**天気概況**は絶対使うのでダウンロードしてください。

少し自分でやってみたところ、例えば2019年1年間のデータより2001年の10月-11月、2002年の10月-11月、2003年の10月-11月、、、の12か月のデータの方がうまく分類できるようです。(同じ時期のデータで学習した方がよさそうというのはなんとなくわかるかと思います)

# 2. データの加工
## 2-1 不要なデータの削除
ここから pandas等を使ってデータの加工をしていきます。
※説明が多くなるのですぐに試したいという方は[こちら](https://qiita.com/koichi_baseball/items/6f4fbd8488b28819547c#%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E5%85%A8%E4%BD%93)

ダウンロードしてきたファイルは1行目に
`ダウンロードした時刻：2020/11/16 18:18:28`
というデータがあるのでそれを避けて読み込むために `header=2`、また日本語を含むので`encoding="SHIFT-JIS"`としています。

```Python
import numpy as np
import pandas as pd

#csvファイルの読み込み (data.csvはご自身のディレクトリ/ファイル名に合わせてください)
df = pd.read_csv("data.csv", header = 2,encoding="SHIFT-JIS")
```

この時点のdfは以下のようになっていると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426354/14ef389b-868a-2bff-e858-3d401efc2225.png)

同じ列名があるので `「.1」`等の列名が存在しています。これは0行目にある品質番号、均質番号などの列があるためだとわかります。今回はいらないので削除しましょう。
少々強引ですが以下のようにしました。欠損値がある行も削除しておきます。

```Python
# 0行目をdrop
df = df.drop(df.index[[0]])

# ".1", ".2", ".3" が列名の末尾についている列をdrop
df = df.drop(df.loc[:, df.columns.str.endswith(".1")], axis = 1)
df = df.drop(df.loc[:, df.columns.str.endswith(".2")], axis = 1)
df = df.drop(df.loc[:, df.columns.str.endswith(".3")], axis = 1)

# 欠損値がある行を削除
df = df.dropna(how='all')
```

これできれいになったと思います！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426354/047c49b5-b7a7-f118-2c85-df2009457d15.png)

## 2-2 ラベルの整理
ここで、ラベル(天気概況)のユニーク数を見てみましょう。
自分のダウンロードしてきたデータはなんと64個でした。多すぎますね。

```Python
print(len(df["天気概況(昼：06時〜18時)"].unique().tolist()))
```

一部ですがこんな感じです。

```
['晴時々曇', '曇後一時雨', '曇', '晴後曇', '曇後雨', '晴後薄曇', '雨一時曇',
 '晴', '曇時々雨','曇一時晴', '雨', '曇後晴', '雨時々曇', '曇一時雨', '快晴',
 '雨後晴', '晴後一時曇', '薄曇','雨後一時晴', '雨後曇', '曇後一時晴',
'晴一時曇', '雨、雷を伴う', '晴後一時雨、雷を伴う', '大雨','曇一時雨後晴',
'曇一時霧', '薄曇一時晴', '曇時々晴', '雨後曇一時晴', '晴一時薄曇', '曇一時雨、雷を伴う']
```

**今回は、**分類を簡単にするために以下のような処置をしました。
①一文字目をとってくる。
②数値におきかえる

```Python
df["天気概況(昼：06時〜18時)"] = df["天気概況(昼：06時〜18時)"].str[:1]
df["天気No"] = df["天気概況(昼：06時〜18時)"].str.replace("曇","0").replace("晴", "1").replace("大", "3").replace("雨", "3").replace("薄", "0").replace("快", "1").replace("霧","0")
```

置き換えた数値と天気の一覧です。今回はとても簡略化しています。気に入らない方がいればもちろん自身で変えちゃってください。

| 天気(1文字目) | 数値 | 元の表記 |
|:-:|:-:| :-: |
| 曇 | 0 | 曇り(一時なんとか等) |
| 薄 | 0 | 薄曇(一時なんとか等) |
| 霧 | 0 | 霧(一時なんとか等) |
| 快 | 1 | 快晴(一時なんとか等) |
| 晴 | 1 | 晴(一時なんとか等) |
| 雨 | 2 | 雨(一時なんとか等) |
| 大 | 2 | 大雨(一時なんとか等) |

# 3. 学習
いよいよ学習です。今回はしっかり学習ができて訓練データにも、未知のデータにも予測ができているかを検証するため`train_test_split`という関数を使ってデータをトレーニングデータとテストデータに分けています。

学習自体はとても簡単で、

```Python
model.fit(x_train, y_train)
```

この行だけです。


```Python
from sklearn import svm
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 説明変数の格納
x = df.loc[1:, ["降水量の合計(mm)","平均雲量(10分比)"]]

#目的変数の格納
y = df.loc[1:,"天気No"].astype("int64")

# トレーニングデータとテストデータに分割。
# test_size=0.3 : テストデータは30%、トレーニングデータ：70%
# random_state=None：毎回異なるデータを生成
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=None )

# SVMを選択
model = svm.SVC()

# 学習
model.fit(x_train, y_train)

# トレーニングデータに対する精度
pred_train = model.predict(x_train)
accuracy_train = accuracy_score(y_train, pred_train)
print('トレーニングデータに対する正解率： %.2f' % accuracy_train)

# テストデータに対する精度
pred_test = model.predict(x_test)
accuracy_test = accuracy_score(y_test, pred_test)
print('テストデータに対する正解率： %.2f' % accuracy_test)
```

以下のように結果が出れば成功です！

```
トレーニングデータに対する正解率： 0.81
テストデータに対する正解率： 0.82
```

分類は`model.predict()`で行っているので、極論以下でも分類結果が返ってきますね。

```Python
model.predict([[1,1]])
```


# 4. 可視化
最後に可視化です。
`plot_decision_regions`で決定境界の可視化を行います。
`plot_decision_regions`も簡単に使うことができて、データとモデルを渡せば勝手にグラフを作ってくれます。
ただここで渡すxは2次元になります。結果のグラフのx,y軸にそれぞれが当てはまります。

```Python
# 決定境界の可視化
import matplotlib.pyplot as plt
from mlxtend.plotting import plot_decision_regions

x_combined = x_test.values
y_combined = y_test.values

fig = plt.figure(figsize=(13,8))
plot_decision_regions(x_combined, y_combined, clf=model,  res=0.02)
plt.show()
```

自分の場合は以下のような図が出ました。いけて、、、る？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426354/ff936fb9-accf-a83b-f8ea-1385160e8c6e.png)


# コードの全体

```Python
import numpy as np
import pandas as pd
from sklearn import svm
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
from mlxtend.plotting import plot_decision_regions

#csvファイルの読み込み (data.csvはご自身のディレクトリ/ファイル名に合わせてください)
df = pd.read_csv("data.csv", header = 2,encoding="SHIFT-JIS")

# 0行目をdrop
df = df.drop(df.index[[0]])

# ".1", ".2", ".3" が列名の末尾についている列をdrop
df = df.drop(df.loc[:, df.columns.str.endswith(".1")], axis = 1)
df = df.drop(df.loc[:, df.columns.str.endswith(".2")], axis = 1)
df = df.drop(df.loc[:, df.columns.str.endswith(".3")], axis = 1)

# 欠損値がある行を削除
df = df.dropna(how='all')

# ラベルの処理
df["天気概況(昼：06時〜18時)"] = df["天気概況(昼：06時〜18時)"].str[:1]
df["天気No"] = df["天気概況(昼：06時〜18時)"].str.replace("曇","0").replace("晴", "1").replace("大", "3").replace("雨", "3").replace("薄", "0").replace("快", "1").replace("霧","0")

# 説明変数の格納
x = df.loc[1:, ["降水量の合計(mm)","平均雲量(10分比)"]]

#目的変数の格納
y = df.loc[1:,"天気No"].astype("int64")

# トレーニングデータとテストデータに分割。
# test_size=0.3 : テストデータは30%、トレーニングデータ：70%
# random_state=None：毎回異なるデータを生成
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=None )

# SVMを選択
model = svm.SVC()

# 学習
model.fit(x_train, y_train)

# トレーニングデータに対する精度
pred_train = model.predict(x_train)
accuracy_train = accuracy_score(y_train, pred_train)
print('トレーニングデータに対する正解率： %.2f' % accuracy_train)

# テストデータに対する精度
pred_test = model.predict(x_test)
accuracy_test = accuracy_score(y_test, pred_test)
print('テストデータに対する正解率： %.2f' % accuracy_test)

# 決定境界の可視化
x_combined = x_test.values
y_combined = y_test.values

fig = plt.figure(figsize=(13,8))
plot_decision_regions(x_combined, y_combined, clf=model,  res=0.02)
plt.show()
```

# おわりに
ほぼ初めて機械学習に触りましたが意外と簡単でした！(色々雑な部分はありましたが、、)
自分も[この記事](https://qiita.com/ryu1104/items/06a71d8f6956da889bb8)に感化されてやってみたのでみなさんもぜひやってみてください。