はじめに
なんか他の確率的降下法のサイト色々見てみたけど、
単純パーセプトロンとか、バッチ式勾配降下法とかと違いが全然分からん。
class AdalineSGD(object):
def __init__(self, eta=0.01, n_iter=10, shuffle=True, random_state=None):
self.eta = eta
self.n_iter = n_iter
self.w_initialized = False
self.shuffle = shuffle
self.random_state = random_state
shuffle??
それ以外はADALINEと同じ。
def fit(self, X, y):
self._initialize_weights(X.shape[1])
self.cost_ = []
for i in range(self.n_iter): #エポックをn_iter回まわす
if self.shuffl: #Trueの場合は循環を回避する為シャッフル
X, y = self._shuffle(X, y)
#各サンプルのコストを格納するリストの生成
cost = []
for xi, target in zip(X, y):
#特徴量xi と目的変数y を用いた重みの更新とコストの計算
cost.append(self._update_weights(xi, target))
#トレーニングサンプルの平均コストとしてエポック毎のコストの計算
avg_cost = sum(cost) / len(y)
#平均コストの格納
self.cost_.append(avg_cost)
return self
5,6行目:
指定された場合はシャッフルを行う。
ランダムに取ってきて、重みを更新するアルゴリズムなので、
循環を回避する為にシャッフル機能が存在する。
トレーニングサンプルごとに重みを更新する。
def partial_fit(self, X, y):
"""Fit training data without reinitializing the weights"""
if not self.w_initialized:
self._initialize_weights(X.shape[1])
if y.ravel().shape[0] > 1:
for xi, target in zip(X, y):
self._update_weights(xi, target)
else:
self._update_weights(X, y)
return self
partial_fitメソッドは、fitメソッドと違い、オンライン学習に合わせて、重みの初期化を行わない
オンライン学習用のメソッド
def _shuffle(self, X, y):
"""Shuffle training data"""
r = self.rgen.permutation(len(y))
return X[r], y[r]
各エポックの前にトレーニングデータをシャッフルする。
これはコスト関数を最適化する時の循環を避ける為。
numpy.randomのpermutation関数を使って0以上99以下の乱数を順列で生成。
それらの乱数は、特徴行列とクラスラベルベクトルをシャッフルする為のインデックスとして使用する。
def _initialize_weights(self, m):
"""Initialize weights to small random numbers"""
self.rgen = np.random.RandomState(self.random_state)
self.w_ = self.rgen.normal(loc=0.0, scale=0.01, size=1 + m)
self.w_initialized = True
今までのADALINEの初期化部分が関数化された。
self.rgen = np.random.RandomState(self.random_state)
npクラスのrandomクラスの、RandomStateクラスは、乱数を自動発生させる。
self.rgenには乱数が代入される。
self.w_ = self.rgen.normal(loc=0.0, scale=0.01, size = 1+m)
self.w_ の重み配列を初期化する行。
normalメソッドで平均0,標準偏差0.01の正規分布からsize個のデータを引っ張ってくる。
def _update_weights(self, xi, target):
"""Apply Adaline learning rule to update the weights"""
output = self.activation(self.net_input(xi))
error = (target - output)
self.w_[1:] += self.eta * xi.dot(error)
self.w_[0] += self.eta * error
cost = 0.5 * error**2
return cost
update_weigthsはコスト関数を計算して返すメソッド。
3行目:
net_inputで総出力zが返ってきて、activationは恒等関数なのでzのまま
4行目:
target訓練用ラベル - output総出力z = error
7行目:
コスト関数J(w)を計算している。
\begin{align}
J(w) &= \frac{1}{2}\sum_{i}(y^{(i)}-φ(y^{(i)}))^2
\end{align}
def net_input(self, X):
"""Calculate net input"""
return np.dot(X, self.w_[1:]) + self.w_[0]
net_inputは総入力zを返す関数。
np.dot関数は行列の積や、ベクトルの内積を計算する。
z = w0 + w1 * x1 + w2みたいなやつを分解します。
ここではXはがく片の長さ*花びらの長さの二次元。
self.w_[1:]は、w1,w2の二次元。
self.w_[0]はw0の一次元。
なので行列の積が求まる。
よって、
z = np.dot(X,self.w_[1:]) + self.w_[0]
これが総入力!これをreturn
def activation(self, X):
"""Compute linear activation"""
return X
def predict(self, X):
"""Return class label after unit step"""
return np.where(self.activation(self.net_input(X)) >= 0.0, 1, -1)
X_std = np.copy(X)
X_std[:, 0] = (X[:, 0] - X[:, 0].mean()) / X[:, 0].std()
X_std[:, 1] = (X[:, 1] - X[:, 1].mean()) / X[:, 1].std()
ada = AdalineSGD(n_iter=15, eta=0.01, random_state=1)
ada.fit(X_std, y)
#境界領域のプロット
plot_decision_regions(X_std, y, classifier=ada)
plt.title('Adaline - Stochastic Gradient Descent')
plt.xlabel('sepal length [standardized]')
plt.ylabel('petal length [standardized]')
plt.legend(loc='upper left')
plot_decision_regiosは分類機の境界線をプロットする関数。
plt.tight_layout()
# plt.savefig('images/02_15_1.png', dpi=300)
plt.show()
plt.plot(range(1, len(ada.cost_) + 1), ada.cost_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Average Cost')
plt.tight_layout()
# plt.savefig('images/02_15_2.png', dpi=300)
plt.show()
ada.partial_fit(X_std[0, :], y[0])
# # Summary
# ...
# ---
#
# Readers may ignore the following cell