PythonでLIBSVMのカスタムカーネルを使う方法

デフォルトのカーネルとはデータの扱い方が少し異なるので、備忘録も兼ねて

開発環境 : Windows 10 Pycharm 2018.1
LIBSVMは以下のページからダウンロードできます。インストール方法は他の記事を参考にしてください。
https://www.csie.ntu.edu.tw/~cjlin/libsvm/

# カーネル
LIBSVMでは__"linear"__, __"polynomial"__, __"RBF"__, __"sigmoid"__, __"precomputed"__の５つのカーネルがパラメータ指定で使用できます。
precomputedを指定することで自分で定義したカーネルを使うことができます。
今回はカーネルとして__一般化ガウス関数(GGD : Generalized Gaussian Distribution)__使います。
GGDは形状が変化させられるガウス関数みたいな感じ, 詳しくはご自分でお願いします。
$\sigmaと\gamma$はパラメータです。

```math
\begin{align}
K(x, y; \sigma, \gamma)&=\frac{g(\gamma)\gamma}{2\sigma\Gamma(1/\gamma)}\exp\left[\left(\frac{g(\gamma)||x-y||^2}{2\sigma^2}\right)^\gamma\right] \\
g(\gamma)&=\sqrt{\frac{\Gamma(3/\gamma)}{\Gamma(1/\gamma)}}
\end{align}
```
```py   
def ggd_kernel(x, y):
    sigma = パラメータ
    r = パラメータ
    beta = sigma * math.sqrt(math.gamma(1 / r) / math.gamma(3 / r))
    ggd = (r / (2 * beta * math.gamma(1 / r))) * np.exp(-(distance.cdist(x, y, metric="euclidean") / beta) ** r)
    return ggd
```
# トレーニングデータ読み込み
１列目がラベル, ２列目以降が特徴量となるcsvなどを用意してください。
yにラベル, xに特徴量が入ります。

```py
y, x = svm_read_problem("ファイル名")
```

LIBSVMを使う上でトレーニングデータの扱い方に注意が必要です。
__ラベルがi個でprecomputedを使う場合__は以下のようなフォーマットに変換しないとエラーが出ます。
この記事ではリストの中に辞書を入れています。
※GGDの計算結果はi×iの行列になるので下のようなフォーマットになります。

|0|1|2|・・・|i|
|:--:|:--:|:--:|:--:|:--:|
|0:1|1:data01|2:data02|・・・|i:data0i|
|0:1|1:data11|2:data12|・・・|i:data1i|
|・|・|・|・|・|
|・|・|・|・|・|
|・|・|・|・|・|
|0:1|1:datai1|2:datai2|・・・|i:dataii|

svm_read_problemで読み込んだ特徴量はリストの中に辞書が入った形で下の表のようになる。
フォーマットを合わせるために全てのデータの列を１つずらして, ０列に0:1を追加する。

|0|1|・・・|i|
|:--:|:--:|:--:|:--:|
|0:data01|1:data02|・・・|i:data0i|
|0:data11|1:data12|・・・|i:data1i|
|・|・|・|・|
|・|・|・|・|
|・|・|・|・|
|0:datai1|1:datai2|・・・|i:dataii|

トレーニングデータのみが入ったCSVファイルから上記のフォーマットに変換するプログラム

```py    
# 学習データ読み込み
y, x = svm_read_problem("hoge.csv")   
# svm_read_problemで読み込んだxはリストの中に辞書が入った形で[{0:data01, 1:data02, ..., i-1:data0i}]となるので, 表のフォーマットに変換する必要がある
# リスト[辞書]からデータフレームに変換
x = pd.DataFrame(x)

# データフレームから行列に変換
x_matrix = np.matrix(x)

# 学習データのGGDを計算
x_ggd = ggd_kernel(x_matrix, x_matrix)

# GGDの計算結果の格納リスト
x_list = []

# 行数分だけ回す
for i in range(x_ggd.shape[0]):
    # GGDの辞書(キーをつけるため)
    x_dict = {}
    # 列数分だけ回す
    for j in range(x_ggd.shape[1]):
        # keyに列数, valueにGGDのデータ
        x_dict[0] = i+1                 
        x_dict[j+1] = x_ggd[i][j]
    # 格納リストに辞書を追加
    x_list.append(x_dict)
```

# 学習
precomputedを使用するため, svm_problemのisKernelにTrue, SVMのパラメータに"-t 4"を指定する。

```py
# 学習データ登録 isKernel=Trueでprecomputed
# yはラベル, x_listはGGDの計算結果（precomputed以外のカーネルの場合はトレーニングデータをそのまま入れる）
prob = svm_problem(y, x_list, isKernel=True)

# パラメータ設定
# SVCやSVRなど使う手法によって設定パラメータが異なるので注意
param = svm_parameter("-t 4")

# トレーニング
model = svm_train(prob, param)

# モデル保存
svm_save_model("model.txt", model)
```

# 分類・推定
分類・推定を行う際もカーネルの計算とデータフォーマットをそろえる必要がある。
※ソースコードではSVRを使用していたのでSVCなどではsvm_predictの部分が動かないかもしれません

```py
# test_dataと特徴量のGGDを計算
ggd_predict = ggd_kernel(test_data, x_matrix)
    
ggd_predict_list = []

# 行数分だけ回す
for i in range(test_data.shape[0]):
    # GGDの辞書(キーをつけるため)
    test_data_dict = {}
    # 列数分だけ回す
    for j in range(x_ggd.shape[1]):
        # keyに列数, valueにGGDのデータ
        test_data_dict[0] = i+1           
        test_data_dict[j+1] = ggd_predict[i][j]
    # 格納リストに辞書を追加
    ggd_predict_list.append(test_data_dict)

predict_label, accuracy, dec_values = svm_predict(test_label, ggd_predict_list, model)
```

# 最後に
今回は使い方だけで例題を出していないので, 時間があれば例題を使って分類・推定をしたいと思います。