ラビットチャレンジレポート 機械学習後半

非線形回帰モデル

複雑な非線形構造を内在する現象に関しては、非線形モデルを適用する。

  • 規定展開法
    • 回帰関数として基底関数と呼ばれる非線形関数とパラメータベクトルの線形結合を使用する
    • よく使われる既定関数は、多項式関数やガウス型既定関数、スプライン関数

多項式型関数:  Φ_j=x^j
ガウス型基底:  Φ_j(x)=exp \left(\dfrac{(x-μ_j)^2}{2h_j}\right)

    • 規定展開法も、最小二乗法や最尤法など線形回帰と同じフレームワークで推定可能

説明変数  x_i=(x_{i1},x_{i2},...,x_{im})\in R^m
非線形関数ベクトル  Φ(x_i)=(Φ_1(x_i),Φ_2(x_i),.....,Φ_k(x_i))^T \in R^m
非線形関数の計画行列  Φ^{(train)}=(Φ_1(x_i),Φ_2(x_i),.....,Φ_k(x_n))^T \in R^{n×k}
最尤法による予測値  \hat{y}=Φ(Φ^{(train)T}Φ^{(train)})^{-1}Φ^{(train)T}y^{(train)}

未学習(underfitting)と過学習(overfitting)

  • 学習データに対して、十分小さな誤差が得られてないモデル→未学習
  • 対策:表現力の高いモデルを利用する
  • 学習データに対しては非常に小さな誤差を得られるが、テストデータに対する誤差が大きいモデル→過学習
    • 対策1:学習データの数を増やす
    • 対策2:不要な既定関数を削除
    • 対策3:正則化法を利用して表現力を抑止
  • 不要な基底関数の削除
    • 解きたい問題に対して多すぎる基底関数を用意してしまうと過学習の問題が起こりやすいため、適切な数の基底関数まで削除する(オッカムのカミソリ)
    • ただし、どの程度の基底関数が適切かの判断は難しい。

(参考)赤池情報基準(AIC)
統計モデルの良さを評価する基準。AICはモデルの複雑さとデータとの適合度のバランスをとるための指標として使われる。AIC最小のモデルを選択すれば、多くの場合良いモデルが作成できるとされる。
 AIC=-2lnL+2k
Lは最大尤度、kは自由パラメータの数

  • 正則化法(罰則化法)
    • 「モデルの複雑さに伴って、その値が大きくなる正則化項」を導入した関数を最小化

 S_ɤ=(y-Φw)^T(y-Φw)+ɤR(w)
ɤR(w):正則化

汎化性能の評価法

    • 学習に使用した入力だけでなく、新たな入力に対する予測性能
  • ホールドアウト法
    • 有限のデータを学習用とテスト用の2つに分割し、予測精度や誤り率を推定。
    • 手軽に出来る点が長所だが、手元に大量にデータが大量にない場合は、学習データと検証データで偏りが起きやすく、良い性能評価を与えないという欠点がある。
  • クロスバリデーション(交差検証)

 学習データをn分割し、1番目を検証用に割り当て、2番目以降を学習データに割り当てて精度評価を行い、次に2番目を検証用に割り当てて精度検証し、次に3番目を検証に割り当て..とn回繰り返し、精度の平均をとる方法。この平均精度をCV値と呼び、データの偏りが起きにくいため、汎化性能指標として広く用いられる。

パラメータチューニング

  • グリッドサーチ

すべてのチューニングパラメータの組み合わせで評価値を算出し、最も良い評価値を持つチューニングパラメータを持つ組み合わせを、「いいモデルのパラメータ」として採用する

非線形回帰モデルのハンズオン

まず環境設定をする。

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
#seaborn設定
sns.set()
#背景変更
sns.set_style("darkgrid", {'grid.linestyle': '--'})
#大きさ(スケール変更)
sns.set_context("paper")
n=100
def true_func(x):
    z = 1-48*x+218*x**2-315*x**3+145*x**4
    return z 
def linear_func(x):
    z = x
    return z 

データ生成

 # 真の関数からデータ生成
data = np.random.rand(n).astype(np.float32)
data = np.sort(data)
target = true_func(data)

#  ノイズを加える
noise = 0.5 * np.random.randn(n) 
target = target  + noise

# ノイズ付きデータを描画

plt.scatter(data, target)

plt.title('NonLinear Regression')
plt.legend(loc=2)

f:id:tibet:20211113225328p:plain

まず、線形回帰モデルを使用してみる。

from sklearn.linear_model import LinearRegression
clf = LinearRegression()
data = data.reshape(-1,1)
target = target.reshape(-1,1)
clf.fit(data, target)

p_lin = clf.predict(data)
plt.scatter(data, target, label='data')
plt.plot(data, p_lin, color='darkorange', marker='', linestyle='-', linewidth=1, markersize=6, label='linear regression')
plt.legend()
print(clf.score(data, target))

f:id:tibet:20211113225658p:plain
データに対して、うまく学習出来ていない様子がわかる。

次に、L2制約(Ridge推定)が付いたガウス型基底関数を用いた非線形回帰モデル(KernelRidge)を用いて推定する。

from sklearn.kernel_ridge import KernelRidge
clf = KernelRidge(alpha=0.0002, kernel='rbf')
clf.fit(data, target)
p_kridge = clf.predict(data)
plt.scatter(data, target, color='blue', label='data')
plt.plot(data, p_kridge, color='orange', linestyle='-', linewidth=3, markersize=6, label='kernel ridge')
plt.legend()
#plt.plot(data, p, color='orange', marker='o', linestyle='-', linewidth=1, markersize=6)

f:id:tibet:20211113230325p:plain
上手くフィッティングしている様子がわかる。

ロジスティック回帰

  • 分類問題(クラス分類)
    • ある入力からクラスに分類する問題
    • 2値分類問題(0or1)

説明変数:  x=(x_1,x_2,..,x_m)^T\in R^m
目的変数:  y\in {0,1}

 σ(x)=\dfrac{1}{1+exp(-a)}

 シグモイド関数微分シグモイド関数で表現できる。
 \dfrac{\partial σ(x)}{\partial x}=aσ(x)(1-σ(x))

ロジスティック回帰モデル

 P(Y=1|x)=σ(w_0+\displaystyle  \sum_{k=1}^n (w_kx_k))

最尤推定

  • 尤度関数:データを固定し、パラメータを変化させる
  • 尤度関数を最大化するようなパラメータを選ぶ推定方法を最尤推定という
  • ベルヌーイ分布:数学において確率pで1、確率1-pで0をとる2値の離散分布。ロジスティック回帰のベースの確率分布。

 P(y)=p^y(1-p)^{1-y}

  • n回のベルヌーイ試行の同時確率による尤度関数

 P(y_1,y_2,...,y_n;p)=\prod_{i=1}^np^{y_i}(1-p)^{1-y_i}

ロジスティック回帰モデルの最尤推定

 P(y_1,y_2,...,y_n|w_0,w_1,...,w_m)=\prod_{i=1}^np^{y_i}(1-p)_{1-y_i}
                                                          =\prod_{i=1}^nσ(w^Tx_i)^{y_i}(1-σ(w^Tx_i))^{1-y_i}
                                                          =L(w)

  • ロジスティック回帰モデルの尤度関数は、通常対数をとる

 理由としては、積を和に変換できる、確率の掛け算は多重に行うと極小で桁落ちになり実務上取り扱いが出来ないなど。
  E(w)=-logL(w)=-\displaystyle \sum_{i=1}^{n}{y_ilogp_i+(1-y_i)log(1-p_i)}

勾配降下法

  • 反復学習によりパラメータを逐次的に更新するアプローチ
  • 対数尤度関数をパラメータで微分してゼロになる値を求める必要があるが、解析的にこの値を求めることは不可能。

 w(k+1)=w^k-η\dfrac{\partial E(w)}{\partial w}

  • ロジスティック回帰モデルでは、下記のように書ける。

 w(k+1)=w^k-η\displaystyle \sum_{i=1}^{n}(y_i-p_i)x_i]

 勾配降下法では、パラメータを更新するのにN個すべてのデータを求める必要がある。
 そのため、メモリの問題や、計算時間の問題が発生する。
 確率的勾配降下法では、データをランダムに選んでパラメータを更新する。
 勾配降下法でパラメータを一回更新更新するのと同じ計算量でパラメータをn回更新できる。

機械学習の精度指標

  • 混合行列(confusion matrix)
検証用データの結果
positive negative
モデルの予測結果 positive 真陽性 偽陰性 ]
negative 偽陽性 真陰性
  • 精度(accuracy)

 \dfrac {TP+TF}{TP+FN+FP+TN}

  • 再現率(Recall)

 全陽性群の中で、真陽性のデータの割合
 適用例:病変診断
 \dfrac{TP}{TP+FN}

  • 適合率(Precision)

モデルが陽性と予測したものの中で、真陽性のデータの割合
 適用例:スパムフィルタ
 \dfrac{TP}{TP+FP} 

 PresitionとRecallの調和平均
  \dfrac{2Recall\cdot Precision}{Recall+Precision}

ロジスティック回帰ハンズオン

  • テーマ:タイタニック
  • 課題:年齢が30歳で男の乗客は生き残れるか

環境設定、データ読み込み

import pandas as pd
import numpy as np
train=pd.read_csv('train.csv')
train_df=pd.DataFrame(train)

学習に使うデータのみ選定し、nullを中央値で補完

train_df=train_df.drop(["PassengerId","Name","Ticket","Fare","Cabin"], axis=1,inplace=True)
train_df['Age']=train_df['Age'].fillna(train_df['Age'].median())

性別で、maleを1、Femaleを0に数値化

train_df['Sex2']=train_df['Sex'].apply(lambda x:1 if x=='male' else 0 )

ターゲットデータとラベルを作成

data=train_df.loc[:,['Sex2','Age']]
label=train_df.loc[:,['Survived']]

ロジスティック回帰モデルを作成

from sklearn.linear_model import LogisticRegression as lr
model=lr()
model.fit(data,label)

30歳男性(Sex2=1, Age=30)を予測

model.predict([[1,30]])

array([0])
結果は、0つまり死亡の予測だった。
probaで(死亡(0)の予測確率、生存(1)の予測確率)を算出する。

model.predict_proba([[1,30]])

array(0.80695278, 0.19304722)
それぞれ80.7%、19.3%の予測結果となった。

主成分分析(PCA)

  • 多変量データの持つ構造をより少数個の指標に圧縮する次元削減の手法
    • 情報の量を分散の大きさととらえる
    • 線形返還後の変数の分散が最大となる射影軸を探索する
  • 以下の制約付き最適化問題を解く

目的関数:  arg max_{a\in{R^m}} a_j^TVar(\bar{X})a_j
制約条件 :  a_j^Ta_j=1

ラグランジュ関数を最大にする係数ベクトルを探索する(微分して0になる点を探索)
ラグランジュ関数
 E(a_j)=a_j^TVar(bar{X}a_j-λ(a_j^Ta_j-1)

    • 実務的な解法

1.分散共分散行列を計算
2.固有値問題を解く
3.mこの固有値固有ベクトルのペアを算出
4.寄与率から削減する次元数を決定

      • 寄与率

第k主成分の分散の全文さんに対する割合

      • 累積寄与率

第1-k主成分まで圧縮した時の情報損失量の割合

主成分分析のハンズオン

  • 設定:乳がん検査データ
  • 課題:32次元のデータを2次元上に次元圧縮した際に、うまく判別できるか

環境設定

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegressionCV
from sklearn.metrics import confusion_matrix
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
%matplotlib inline

cancer_df=pd.read_csv(cancer.csv)

目的変数をdiagnosis、説明変数を3列目以降として、ロジスティック回帰で分類
>|python}
cancer_df.drop('Unnamed: 32', axis=1, inplace=True)
y = cancer_df.diagnosis.apply(lambda d: 1 if d == 'M' else 0)
X = cancer_df.loc[:, 'radius_mean':]
# 学習用とテスト用でデータを分離
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

# 標準化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# ロジスティック回帰で学習
logistic = LogisticRegressionCV(cv=10, random_state=0)
logistic.fit(X_train_scaled, y_train)

# 検証
print('Train score: {:.3f}'.format(logistic.score(X_train_scaled, y_train)))
print('Test score: {:.3f}'.format(logistic.score(X_test_scaled, y_test)))
print('Confustion matrix:\n{}'.format(confusion_matrix(y_true=y_test, y_pred=logistic.predict(X_test_scaled))))
|

Train score: 0.988
Test score: 0.972
検証スコア97%で分類できることを確認
目的変数と不要な変数1つを除いた30次元のデータについて、寄与率を観察

pca = PCA(n_components=30)
pca.fit(X_train_scaled)
plt.bar([n for n in range(1, len(pca.explained_variance_ratio_)+1)], pca.explained_variance_ratio_)

f:id:tibet:20211114212728p:plain
上位2変数の寄与度が非常に大きいため、2次元に圧縮しても分類情報が保たれる可能性は高い。
2次元まで圧縮する。

pca = PCA(n_components=2)
X_train_pca = pca.fit_transform(X_train_scaled)
print('X_train_pca shape: {}'.format(X_train_pca.shape))
# X_train_pca shape: (426, 2)

# 寄与率
print('explained variance ratio: {}'.format(pca.explained_variance_ratio_))
# explained variance ratio: [ 0.43315126  0.19586506]

# 散布図にプロット
temp = pd.DataFrame(X_train_pca)
temp['Outcome'] = y_train.values
b = temp[temp['Outcome'] == 0]
m = temp[temp['Outcome'] == 1]
plt.scatter(x=b[0], y=b[1], marker='o') # 良性は○でマーク
plt.scatter(x=m[0], y=m[1], marker='^') # 悪性は△でマーク
plt.xlabel('PC 1') # 第1主成分をx軸
plt.ylabel('PC 2') # 第2主成分をy軸

f:id:tibet:20211114212923p:plain

k近傍法

  • 分類問題のための機械学習手法
  • 最近棒のデータをk個とってきて、それらが最も多く所属するクラスに識別する
  • kを変化させると結果も変わる

k-means

1.各クラスタ中心の初期値を設定する。
2.各データ点に対して、各クラスタ中心との最も距離の近いクラスタを割り当てる
3.各クラスタの重心を計算する
4.収束するまで2,3の処理を繰り返す
中心の初期値を変えると、クラスタリングの結果も変わりえる

SVM

サポートベクトルマシン(SVM)は1990年代に提案され、2000年代前半に急速に発展した手法で、データ分析の場において近年最も注目を集めているモデルである。
オリジナルのSVMは、2クラス分類問題を対象としている。その後、回帰問題や教師無し問題などへも応用されている。

  • 決定境界と分類境界

2クラス分類問題では、特徴ベクトルxがどちらのクラスに属するか判定するために、次の決定関数が使われる。
 f(x)=w^Tx+b
データを分類するには、ある入力データxに対して決定関数f(x)を計算し、その符号によって2つのクラスに分類する。
一般に、x1-x2-..-xm平面とf(x)の平面の交線f(x)=0が特徴ベクトルxを2つのクラスに分ける境界線になっている。一般にこのような境界を分類境界と呼ぶ。

  • 線形サポートベクトル分類(ハードマージン)

 一般に訓練データを分離できる分類境界は複数存在する。SVMでは、それぞれのクラスのデータが分類境界からなるべく離れるようにして「最適な」分類境界を決定する。分類境界を挟んで2つのクラスがどのくらい離れているかをマージンと呼ぶ。なるべく大きなマージンを持つ分類境界を探す考え方をマージン最大化と呼ぶ。
訓練データを完全に分類できる分離境界があることを分離可能と呼ぶ。分離可能性を仮定したSV分類のことを、一般にハードマージンと呼ぶ。
分類境界の決定にかかわるのは分類境界に最も近いデータxiのみのため、xiをサポートベクトルと呼ぶ。

  • 線形サポートベクトル分類(ソフトマージン)

 ハードマージンでは訓練データを完璧に分類できる決定関数f(x)が存在すると仮定をおいたが、現実の多くの問題にとってこの仮定は強すぎる。そこで、分離可能でない問題までSV分類を拡張したものを、ソフトマージンと呼ぶ。マージン内に入るデータやご分類されたデータに対する誤差を表す変数をスラック変数と呼ぶ。

 線形分離ではうまく分類できないデータに対して、特徴ベクトルを非線形変換してその空間での線形分類を行う「カーネルトリック」と呼ばれる手法で非線形分離を行うことも可能である。
 非線形分離をするためには、写像Φ(x)で高次元空間へ変換する必要がある。しかし、最適化問題をそのまま解くことは不可能である。なぜならΦ(x)の内積部分の計算量が莫大になるからである。
そのため、この部分の計算を簡略化するためのテクニックはカーネルトリックと呼ばれ、Φの内積部分をと呼ばれる関数で置き換える方法である。
 K(x_i,X_j)=Φ(x_i)^TΦ(x_j)
決定関数f(x)はカーネル関数を用いて
[tex: f(x)=w^TΦ(x)+b=\displaystyle \sum_{i=1}^{n}α_iy_iK(x_i,x)+b
カーネル関数さえ決めれば、最適化や決定係数の計算にはΦ(x)は一切必要なくなる。
代表的なカーネル関数は次の3つ
多項式カーネル
ガウスカーネル
・シグモイドカーネル

SVMハンズオン

環境設定

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
  • 線形分離可能な訓練データ生成
def gen_data():
    x0 = np.random.normal(size=50).reshape(-1, 2) - 2.
    x1 = np.random.normal(size=50).reshape(-1, 2) + 2.
    X_train = np.concatenate([x0, x1])
    ys_train = np.concatenate([np.zeros(25), np.ones(25)]).astype(np.int)
    return X_train, ys_train
X_train, ys_train = gen_data()
plt.scatter(X_train[:, 0], X_train[:, 1], c=ys_train)

f:id:tibet:20211114223222p:plain
学習
>|python}
t = np.where(ys_train == 1.0, 1.0, -1.0)

n_samples = len(X_train)
# 線形カーネル
K = X_train.dot(X_train.T)

eta1 = 0.01
eta2 = 0.001
n_iter = 500

H = np.outer(t, t) * K

a = np.ones(n_samples)
for _ in range(n_iter):
grad = 1 - H.dot(a)
a += eta1 * grad
a -= eta2 * a.dot(t) * t
a = np.where(a > 0, a, 0)
|

予測

index = a > 1e-6
support_vectors = X_train[index]
support_vector_t = t[index]
support_vector_a = a[index]

term2 = K[index][:, index].dot(support_vector_a * support_vector_t)
b = (support_vector_t - term2).mean()
xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100))
xx = np.array([xx0, xx1]).reshape(2, -1).T

X_test = xx
y_project = np.ones(len(X_test)) * b
for i in range(len(X_test)):
    for a, sv_t, sv in zip(support_vector_a, support_vector_t, support_vectors):
        y_project[i] += a * sv_t * sv.dot(X_test[i])
y_pred = np.sign(y_project)
# 訓練データを可視化
plt.scatter(X_train[:, 0], X_train[:, 1], c=ys_train)
# サポートベクトルを可視化
plt.scatter(support_vectors[:, 0], support_vectors[:, 1],
                    s=100, facecolors='none', edgecolors='k')
# 領域を可視化
#plt.contourf(xx0, xx1, y_pred.reshape(100, 100), alpha=0.2, levels=np.linspace(0, 1, 3))
# マージンと決定境界を可視化
plt.contour(xx0, xx1, y_project.reshape(100, 100), colors='k',
                     levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--'])

# マージンと決定境界を可視化
plt.quiver(0, 0, 0.1, 0.35, width=0.01, scale=1, color='pink')

f:id:tibet:20211114223511p:plain

  • 線形分離不可能な訓練データ生成
factor = .2
n_samples = 50
linspace = np.linspace(0, 2 * np.pi, n_samples // 2 + 1)[:-1]
outer_circ_x = np.cos(linspace)
outer_circ_y = np.sin(linspace)
inner_circ_x = outer_circ_x * factor
inner_circ_y = outer_circ_y * factor

X = np.vstack((np.append(outer_circ_x, inner_circ_x),
               np.append(outer_circ_y, inner_circ_y))).T
y = np.hstack([np.zeros(n_samples // 2, dtype=np.intp),
               np.ones(n_samples // 2, dtype=np.intp)])
X += np.random.normal(scale=0.15, size=X.shape)
x_train = X
y_train = y
plt.scatter(x_train[:,0], x_train[:,1], c=y_train)

f:id:tibet:20211114223656p:plain

学習
カーネルとしてRBFカーネルを利用する

def rbf(u, v):
        sigma = 0.8
        return np.exp(-0.5 * ((u - v)**2).sum() / sigma**2)
    
X_train = x_train
t = np.where(y_train == 1.0, 1.0, -1.0)

n_samples = len(X_train)
# RBFカーネル
K = np.zeros((n_samples, n_samples))
for i in range(n_samples):
    for j in range(n_samples):
        K[i, j] = rbf(X_train[i], X_train[j])

eta1 = 0.01
eta2 = 0.001
n_iter = 5000

H = np.outer(t, t) * K

a = np.ones(n_samples)
for _ in range(n_iter):
    grad = 1 - H.dot(a)
    a += eta1 * grad
    a -= eta2 * a.dot(t) * t
    a = np.where(a > 0, a, 0)

予測

index = a > 1e-6
support_vectors = X_train[index]
support_vector_t = t[index]
support_vector_a = a[index]

term2 = K[index][:, index].dot(support_vector_a * support_vector_t)
b = (support_vector_t - term2).mean()
xx0, xx1 = np.meshgrid(np.linspace(-1.5, 1.5, 100), np.linspace(-1.5, 1.5, 100))
xx = np.array([xx0, xx1]).reshape(2, -1).T

X_test = xx
y_project = np.ones(len(X_test)) * b
for i in range(len(X_test)):
    for a, sv_t, sv in zip(support_vector_a, support_vector_t, support_vectors):
        y_project[i] += a * sv_t * rbf(X_test[i], sv)
y_pred = np.sign(y_project)
# 訓練データを可視化
plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train)
# サポートベクトルを可視化
plt.scatter(support_vectors[:, 0], support_vectors[:, 1],
                    s=100, facecolors='none', edgecolors='k')
# 領域を可視化
plt.contourf(xx0, xx1, y_pred.reshape(100, 100), alpha=0.2, levels=np.linspace(0, 1, 3))
# マージンと決定境界を可視化
plt.contour(xx0, xx1, y_project.reshape(100, 100), colors='k',
                     levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--'])

f:id:tibet:20211114223835p:plain

  • ソフトマージンSVM

重なりのある訓練データの生成

x0 = np.random.normal(size=50).reshape(-1, 2) - 1.
x1 = np.random.normal(size=50).reshape(-1, 2) + 1.
x_train = np.concatenate([x0, x1])
y_train = np.concatenate([np.zeros(25), np.ones(25)]).astype(np.int)
plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train)

f:id:tibet:20211114224014p:plain
学習
スラック変数を導入する

X_train = x_train
t = np.where(y_train == 1.0, 1.0, -1.0)

n_samples = len(X_train)
# 線形カーネル
K = X_train.dot(X_train.T)

C = 1
eta1 = 0.01
eta2 = 0.001
n_iter = 1000

H = np.outer(t, t) * K

a = np.ones(n_samples)
for _ in range(n_iter):
    grad = 1 - H.dot(a)
    a += eta1 * grad
    a -= eta2 * a.dot(t) * t
    a = np.clip(a, 0, C)

予測

index = a > 1e-8
support_vectors = X_train[index]
support_vector_t = t[index]
support_vector_a = a[index]

term2 = K[index][:, index].dot(support_vector_a * support_vector_t)
b = (support_vector_t - term2).mean()
xx0, xx1 = np.meshgrid(np.linspace(-4, 4, 100), np.linspace(-4, 4, 100))
xx = np.array([xx0, xx1]).reshape(2, -1).T

X_test = xx
y_project = np.ones(len(X_test)) * b
for i in range(len(X_test)):
    for a, sv_t, sv in zip(support_vector_a, support_vector_t, support_vectors):
        y_project[i] += a * sv_t * sv.dot(X_test[i])
y_pred = np.sign(y_project)
# 訓練データを可視化
plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train)
# サポートベクトルを可視化
plt.scatter(support_vectors[:, 0], support_vectors[:, 1],
                    s=100, facecolors='none', edgecolors='k')
# 領域を可視化
plt.contourf(xx0, xx1, y_pred.reshape(100, 100), alpha=0.2, levels=np.linspace(0, 1, 3))
# マージンと決定境界を可視化
plt.contour(xx0, xx1, y_project.reshape(100, 100), colors='k',
                     levels=[-1, 0, 1], alpha=0.5, linestyles=['--', '-', '--'])

f:id:tibet:20211114224202p:plain