suzuzusu日記

(´・ω・`)

多項式カーネルに対応する特徴ベクトルを可視化する

多項式カーネルによって写像される特徴空間を可視化してカーネル法の挙動を把握します.

線形分離不可能なデータ

以下のように半径が異なる円周上に存在する2クラスのデータを生成します.

f:id:suzuzusu:20191204190233p:plain

import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np


def toy_dataset(n1, n2):
    # class 1 data set
    rs = np.random.random(n1)
    r = 1.0
    X1 = np.c_[r * np.cos(2*np.pi*rs), r * np.sin(2*np.pi*rs)]
    Y1 = ['class1' for _ in range(n1)]
    # class 2 data set
    rs = np.random.random(n2)
    r = 2.0
    X2 = np.c_[r * np.cos(2*np.pi*rs), r * np.sin(2*np.pi*rs)]
    Y2 = ['class2' for _ in range(n2)]
    # concat
    X = np.r_[X1, X2]
    Y = np.r_[Y1, Y2]
    x_df = pd.DataFrame(data = X, columns = ['x', 'y'])
    y_series = pd.Series(Y, name = 'class')
    df = pd.concat([x_df, y_series], axis=1)
    return df

# seed
np.random.seed(0)

# dataset
num = 100
df = toy_dataset(n1=num, n2=num)

# dataset scatter
c1_df = df[df['class'] == 'class1']
c2_df = df[df['class'] == 'class2']
sns.scatterplot(c1_df['x'], c1_df['y'], label='class1')
sns.scatterplot(c2_df['x'], c2_df['y'], label='class2')
plt.show()

上のような2クラスのデータは直線を描いて分離することができない線形分離不可能なデータです.このようなデータを識別するためには直線を描いて分離できるような空間に変換したほうが都合がよいです.次のカーネル法でその問題を解決します.

カーネル法

先程のデータを線形分離可能な特徴空間に写像するための写像関数を\phi(\boldsymbol{x})とします.カーネル法では明示的に\phi(\boldsymbol{x})を定義せずに特徴空間の内積カーネル関数 K(\boldsymbol{x}, \boldsymbol{y})=\phi(\boldsymbol{x}) ^T \phi(\boldsymbol{y})によって定義することで写像関数\phi(\boldsymbol{x})の特徴空間で分離して識別することが可能となる手法の総称です.今回はそのカーネル関数によってどのような特徴空間に写像されて識別しているのかを可視化します.

多項式カーネル

(式が揃っていないのは許して...分からなかった...)

多項式カーネルはパラメータc, dによって次の定義されるカーネル関数です.

 K(\boldsymbol{x}, \boldsymbol{y})=(\boldsymbol{x}^T \boldsymbol{y} + c)^d

\boldsymbol{x} = [ x_1, x_2 ]として,c=0,d=1のときの特徴ベクトルは次のようになります.


\begin{eqnarray}
K(\boldsymbol{x}, \boldsymbol{y}) = (\boldsymbol{x}^T \boldsymbol{y})^1 \\
= x_1 y_1 + x_2 y_2 \\
= [ x_1, x_2 ] ^T [ y_1, y_2 ] \\
= \phi(\boldsymbol{x}) ^T \phi(\boldsymbol{y})
\end{eqnarray}

よって特徴ベクトル\phi(\boldsymbol{x}) = [ x_1, x_2 ]となる.

同様に,c=0,d=2のときの特徴ベクトルは


\begin{eqnarray}
K(\boldsymbol{x}, \boldsymbol{y}) = (\boldsymbol{x}^T \boldsymbol{y})^2 \\
= x_1 ^2 y_1 ^2   + 2 x_1 x_2 y_1 y_2 + x_2 ^2 y_2 ^2 \\
= [ x_1 ^2  + \sqrt{2} x_1 x_2 + x_2 ^2 ] ^T [ y_1 ^2  + \sqrt{2} y_1 y_2 + y_2 ^2 ] \\
= \phi(\boldsymbol{x}) ^T \phi(\boldsymbol{y})
\end{eqnarray}

よって特徴ベクトル\phi(\boldsymbol{x}) = [ x_1 ^2 , \sqrt{2} x_1 x_2, x_2 ^2 ]となる.

 dが3以上の場合は同じように計算すればいいので省略します.

SVMによる識別

さきほどの線形分離不可能なデータをSVMによって識別してみます.その際に多項式カーネルのd(次数)を変えて識別性能を比較します.

レーニング,テストデータの準備

from sklearn.svm import SVC
from sklearn.metrics import accuracy_score
np.random.seed(0)

# train dataset
train_num = 100
df = toy_dataset(n1=train_num, n2=train_num)

# test dataset
test_num = 30
test_df = toy_dataset(n1=test_num, n2=test_num)

d(次数)が偶数の場合

print('even degree')
for d in range(2, 11, 2):
    clf = SVC(gamma = 'auto', kernel='poly', degree=d)
    clf.fit(df[['x', 'y']], df['class'])
    y_pred = clf.predict(test_df[['x', 'y']])
    print('degree =', d, 'acc:', accuracy_score(test_df['class'], y_pred))

出力

even degree
degree = 2 acc: 1.0
degree = 4 acc: 1.0
degree = 6 acc: 1.0
degree = 8 acc: 1.0
degree = 10 acc: 1.0

d(次数)が奇数の場合

print('odd degree')
for d in range(1, 10, 2):
    clf = SVC(gamma = 'auto', kernel='poly', degree=d)
    clf.fit(df[['x', 'y']], df['class'])
    y_pred = clf.predict(test_df[['x', 'y']])
    print('degree =', d, 'acc:', accuracy_score(test_df['class'], y_pred))

出力

odd degree
degree = 1 acc: 0.5166666666666667
degree = 3 acc: 0.6833333333333333
degree = 5 acc: 0.6833333333333333
degree = 7 acc: 0.6666666666666666
degree = 9 acc: 0.7

まとめ

横軸をd(次数),縦軸をAccuracyとしてプロットしてみます.

f:id:suzuzusu:20191204193527p:plain

dが偶数の場合は識別可能に対して,奇数の場合は識別不可能であることが分かります.なぜこのような結果になったのかを次に特徴ベクトルを可視化して調べてみます.

特徴ベクトルの可視化

特徴ベクトル

多項式カーネルの特徴ベクトルは次のように二項定理から計算することが可能です.

def poly_feature(x, d):
    n = x.shape[0]
    Z = np.zeros((n, d+1))
    for i in range(d+1):
        # 二項定理
        a = np.sqrt(comb(d, i, exact=True))
        Z[:,i] = a * (x[:,0]**(d-i)) * (x[:,1]**(i))
    return Z

これによって計算できた特徴ベクトルをseabornのペアプロット図を使って可視化してみます.

from scipy.special import comb
from itertools import combinations

for d in range(1, 11):
    z = poly_feature(df[['x', 'y']].values, d)
    columns = [ 'feature' + str(i) for i in range(d+1)]
    feature_df = pd.DataFrame(data = z, columns = columns)
    feature_df = pd.concat([feature_df, df['class']], axis=1)
    g = sns.pairplot(feature_df, hue='class', vars=feature_df.columns[:-1])
    g.fig.suptitle('degree = ' + str(d))
    plt.show()

d(次数)が偶数の場合

f:id:suzuzusu:20191204195346p:plain f:id:suzuzusu:20191204195349p:plain f:id:suzuzusu:20191204195354p:plain f:id:suzuzusu:20191204195359p:plain f:id:suzuzusu:20191204195407p:plain

偶数の場合は一部(右上,左下)の特徴空間を見ると線形分離可能な空間に写像されていることがよくわかります.

d(次数)が奇数の場合

f:id:suzuzusu:20191204195909p:plain f:id:suzuzusu:20191204195912p:plain f:id:suzuzusu:20191204195915p:plain f:id:suzuzusu:20191204195921p:plain f:id:suzuzusu:20191204195900p:plain

奇数の場合はどの写像した空間を見ても線形分離できないことが確認できます.

参考