目次
ボストンの住宅価格予測は、プログラマーの世界の「Hello World」に似た古典的な機械学習タスクです。住宅価格については誰もが一般的に知っているように、ボストン地域の住宅価格は多くの要因の影響を受けます。このデータセットには、住宅価格とこのタイプの住宅の平均価格に影響を与える可能性のある 13 の要因が含まれており、図 1 に示す ように 。
データのダウンロード リンク: https://pan.baidu.com/s/1IEuef7z5EZropO-tJUnvbw
抽出コード: dkmo
図1
予測問題の場合、予測出力の種類が連続実数値か離散ラベルかに応じて、回帰タスクと分類タスクに分けることができます。住宅価格は連続値であるため、住宅価格の予測は明らかに回帰タスクです。以下では、最も単純な線形回帰モデルを使用してこの問題を解決し、ニューラル ネットワークを使用してこのモデルを実装します。
線形回帰モデル
モデルの解決策は、各合計をデータに適合させることです。このうち、 、 、はそれぞれ線形モデルの重みとバイアスを表します。1 次元では、合計は 直線の傾きと切片です。
線形回帰モデルでは、平均二乗誤差を損失関数 (Loss) として使用し、予測住宅価格と実際の住宅価格の差を測定します。式は次のとおりです。
考える:
なぜ損失関数として平均二乗誤差を使用するのでしょうか? サンプル全体の精度は、各トレーニング サンプルのモデルの予測誤差を合計することで測定されます。これは損失関数の設計において「合理性」だけでなく「解きやすさ」も考慮する必要があるためであり、この問題については以下で詳しく説明します。
線形回帰モデルのニューラルネットワーク構造
ニューラル ネットワークの標準的な構造では、各ニューロンは加重和と非線形変換で構成され、複数のニューロンが層状に配置および接続されてニューラル ネットワークを形成します。線形回帰モデルは、ニューラル ネットワーク モデルの最小限の特殊ケースと考えることができ、図 2 に示す ように 。
図2
ボストンの住宅価格予測タスク用のニューラル ネットワーク モデルの構築
ディープラーニングはモデルのエンドツーエンド学習を実現するだけでなく、人工知能を工業的な量産段階に押し上げ、標準化、自動化、モジュール化のための普遍的なフレームワークを生み出します。さまざまなシナリオのディープ ラーニング モデルにはある程度の汎用性があり 、 図 3 に示すように、モデルの構築とトレーニングは 5 つのステップで完了できます。
図 3: ニューラル ネットワーク/ディープ ラーニング モデルを構築するための基本手順
ディープ ラーニングのモデリングとトレーニング プロセスの普遍性があるからこそ、さまざまなモデルを構築する場合、モデルの 3 つの要素のみが異なり、他のステップは基本的に同じであるため、ディープ ラーニング フレームワークは役立ちます。
1. データ処理
データ処理は、データインポート、データ形状変換、データセット分割、データ正規化処理、パッケージ化load data
機能の5つの部分から構成されます。データの前処理後にのみ、モデルから呼び出すことができます。
例証します:
- このチュートリアルのコードは AI Studio で直接実行でき、印刷結果はプログラムの実際の実行結果に基づいています。
- これは実際のケースであるため、コード間には依存関係があるため、リーダーはコードを 1 つずつ実行する必要があります。そうしないと、コマンドの実行中にエラーが報告されます。
1.1 データの読み取り
ボストンの住宅価格のデータ セット構造を理解するには、次のコードを通じてデータを読み取ります。データは、ローカル ディレクトリの Housing.data ファイルに保存されます。
# 导入需要用到的package
import numpy as np
import json
# 读入训练数据
datafile = 'D:/NoteBook/housing.data'
data = np.fromfile(datafile, sep=' ')
data
印刷結果
配列([6.320e-03, 1.800e+01, 2.310e+00, ..., 3.969e+02, 7.880e+00, 1.190e+01])
1.2 データ形状変換
読み込んだ元のデータは1次元なので、全てのデータが繋がっています。したがって、データの形状を変換して 2 次元行列を形成する必要があります。各行にはデータ サンプル (14 個の値) が含まれています。各データ サンプルには、13 個 (住宅価格に影響を与える特徴) と 1 個 (このタイプの平均価格) が含まれています。家の)。)。
# 读入之后的数据被转化成1维array,其中array的第0-13项是第一条数据,第14-27项是第二条数据,以此类推....
# 这里对原始数据做reshape,变成N x 14的形式
feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE','DIS',
'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
feature_num = len(feature_names)
data = data.reshape([data.shape[0]//feature_num, feature_num])
# 查看数据
x = data[0]
print(x.shape)
print(x)
印刷結果
(14,) [6.320e-03 1.800e+01 2.310e+00 0.000e+00 5.380e-01 6.575e+00 6.520e+01 4.090e+00 1.000e+00 2.960e+02 1.530e+01 3.969 e+02 4.980e+00 2.400e+01]
1.3 データセットの分割
データセットはトレーニングセットとテストセットに分かれており、トレーニングセットはモデルのパラメータを決定するために使用され、テストセットはモデルの効果を評価するために使用されます。なぜデータセットを分割し、モデルトレーニングに直接適用しない必要があるのでしょうか? これは、図 4 に示す ように 。
私が学生の頃は、真面目に勉強しない頭の良い生徒が必ずいて、試験前に詰め込み勉強をしたり、練習問題を暗記したりしましたが、成績が良くないことが多かったです。なぜなら、学校は生徒が演習そのものだけでなく、知識を習得することを期待しているからです。新しいテスト問題を導入すると、生徒は演習の背後にある原則を理解するために熱心に取り組むようになります。同様に、モデルがトレーニング データ自体ではなく、タスクの本質的なルールを学習することを期待しています。モデルの効果をより正確に評価できるのは、モデルのトレーニングに使用されていないデータのみです。
この場合、データの 80% をトレーニング セット、20% をテスト セットとして使用し、実装コードは次のようになります。トレーニング セットの形状を出力すると、合計 404 個のサンプルがあり、各サンプルには 13 個の特徴と 1 個の予測値が含まれていることがわかります。
ratio = 0.8
offset = int(data.shape[0] * ratio)
training_data = data[:offset]
training_data.shape
プリントアウト (404, 14)
1.4 データ正規化処理
各特徴の値が 0 から 1 の間にスケールされるように、各特徴は正規化されます。これには 2 つの利点があります: 1 つ目は、モデルのトレーニングがより効率的であること、2 つ目は、特徴の前の重みで予測結果に対する変数の寄与を表すことができることです (各特徴値自体の範囲が同じであるため)。
# 计算train数据集的最大值,最小值,平均值
maximums, minimums, avgs = \
training_data.max(axis=0), \
training_data.min(axis=0), \
training_data.sum(axis=0) / training_data.shape[0]
# 对数据进行归一化处理
for i in range(feature_num):
#print(maximums[i], minimums[i], avgs[i])
data[:, i] = (data[:, i] - minimums[i]) / (maximums[i] - minimums[i])
1.5 ロードデータ関数へのカプセル化
上記のデータ処理操作をload data
関数にカプセル化して、次のステップのモデル呼び出しを実行します。実装方法は次のとおりです。
def load_data():
# 从文件导入数据
datafile = 'D:/NoteBook/housing.data'
data = np.fromfile(datafile, sep=' ')
# 每条数据包括14项,其中前面13项是影响因素,第14项是相应的房屋价格中位数
feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', \
'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
feature_num = len(feature_names)
# 将原始数据进行Reshape,变成[N, 14]这样的形状
data = data.reshape([data.shape[0] // feature_num, feature_num])
# 将原数据集拆分成训练集和测试集
# 这里使用80%的数据做训练,20%的数据做测试
# 测试集和训练集必须是没有交集的
ratio = 0.8
offset = int(data.shape[0] * ratio)
training_data = data[:offset]
# 计算训练集的最大值,最小值,平均值
maximums, minimums, avgs = training_data.max(axis=0), training_data.min(axis=0), \
training_data.sum(axis=0) / training_data.shape[0]
# 对数据进行归一化处理
for i in range(feature_num):
#print(maximums[i], minimums[i], avgs[i])
data[:, i] = (data[:, i] - minimums[i]) / (maximums[i] - minimums[i])
# 训练集和测试集的划分比例
training_data = data[:offset]
test_data = data[offset:]
return training_data, test_data
# 获取数据
training_data, test_data = load_data()
x = training_data[:, :-1]
y = training_data[:, -1:]
# 查看数据
print(x[0])
print(y[0])
印刷結果
[0. 0.18 0.07344184 0. 0.31481481 0.57750527 0.64160659 0.26920314 0. 0.22755741 0.28723404 1. 0.08967991] [0.42222222]
2. モデル設計
モデル設計は深層学習モデルの重要な要素の一つで、ネットワーク構造設計とも呼ばれ、モデルの仮説空間、つまり入力から出力までの「順計算」を実現するプロセスに相当します。 )モデルの。
入力特徴と出力予測値の両方がベクトルとして表現され、入力特徴 x の成分が 13 個、y の成分が 1 個の場合、パラメーターの重みの形状は 13×1 になります。次のようにパラメータを任意の数値で初期化するとします。
w=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,−0.1,−0.2,−0.3,−0.4,0.0]
w = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, -0.1, -0.2, -0.3, -0.4, 0.0]
w = np.array(w).reshape([13, 1])
最初のサンプル データを取り出し、サンプルの特徴ベクトルとパラメーター ベクトルを乗算した結果を観察します。
x1=x[0]
t = np.dot(x1, w)
print(t)
印刷結果
[0.69474855]
完全な線形回帰式では、オフセット b を初期化し、初期値 -0.2 を任意に割り当てる必要もあります。線形回帰モデルの完全な出力は z=t+b になります。特徴とパラメータから出力値を計算するこのプロセスは、「順計算」と呼ばれます。
b = -0.2
z = t + b
print(z)
印刷結果
[0.49474855]
上記の予測出力を計算するプロセスは「クラスとオブジェクト」の形式で記述されており、クラスのメンバ変数にはパラメータ w と b があります。特徴量とパラメータから予測値を出力するまでの上記の計算プロセスを関数(「順計算」を表す)を書くことで完成させるforward
コードは以下のとおりです。
class Network(object):
def __init__(self, num_of_weights):
# 随机产生w的初始值
# 为了保持程序每次运行结果的一致性,
# 此处设置固定的随机数种子
np.random.seed(0)#每次随机数保持不变
self.w = np.random.randn(num_of_weights, 1)
self.b = 0.
def forward(self, x):
z = np.dot(x, self.w) + self.b
return z
Network クラスの定義に基づいて、モデルの計算プロセスは次のようになります。
net = Network(13)
x1 = x[0]
y1 = y[0]
z = net.forward(x1)
print(z)
印刷する
[2.39362982]
2.1 トレーニング構成
モデル設計が完了したら、トレーニング構成を通じてモデルの最適値を見つける、つまり損失関数を通じてモデルの品質を測定する必要があります。トレーニング構成も深層学習モデルの重要な要素の 1 つです。
モデルを通じて計算された x1 で表される影響要因に対応する住宅価格は z であるはずですが、実際のデータは住宅価格が y であることを示しています。このとき、予測値zと実際の値yの差を測定する何らかの指標が必要になります。回帰問題の場合、最も一般的に使用される測定方法は、モデルの品質を評価する指標として平均二乗誤差を使用することです。具体的な定義は次のとおりです。
上式の損失 (L と略記) は、通常、損失関数とも呼ばれ、モデルの品質を示す指標です。回帰問題では、平均二乗誤差が比較的一般的な形式であり、分類問題では通常、交差エントロピーが損失関数として使用されますが、これについては後続の章で詳しく説明します。サンプルの損失関数値を計算する実装は次のとおりです。
Loss = (y1 - z)*(y1 - z)
print(Loss)
印刷結果
[3.88644793]
損失関数を計算する際には各サンプルの損失関数値を考慮する必要があるため、単一サンプルの損失関数を合計し、それをサンプルの総数 N で割る必要があります。
Network クラスに損失関数を追加する計算プロセスは次のとおりです。
class Network(object):
def __init__(self, num_of_weights):
# 随机产生w的初始值
# 为了保持程序每次运行结果的一致性,此处设置固定的随机数种子
np.random.seed(0)
self.w = np.random.randn(num_of_weights, 1)
self.b = 0.
def forward(self, x):
z = np.dot(x, self.w) + self.b
return z
def loss(self, z, y):
error = z - y
cost = error * error
cost = np.mean(cost)
return cost
定義された Network クラスを使用して、予測値と損失関数を簡単に計算できます。クラス内の変数 x、w、b、z、error などはすべてベクトルであることに注意してください。変数 x を例にとると、特徴量 (値は 13) とサンプル数の 2 つの次元があり、コードは次のようになります。
net = Network(13)
# 此处可以一次性计算多个样本的预测值和损失函数
x1 = x[0:3]
y1 = y[0:3]
z = net.forward(x1)
print('predict: ', z)
loss = net.loss(z, y1)
print('loss:', loss)
印刷結果
予測: [[2.39362982][2.46752393][2.02483479]] 損失: 3.384496992612791
2.2 トレーニングプロセス
上記の計算プロセスでは、ニューラル ネットワークを構築し、ニューラル ネットワークを通じて予測値と損失関数の計算を完了する方法を説明します。次に、パラメータ w と b の値を解決する方法を紹介します。このプロセスはモデルのトレーニング プロセスとも呼ばれます。トレーニング プロセスは、深層学習モデルの重要な要素の 1 つであり、その目標は、定義された損失関数 Loss をできるだけ小さくすること、つまり、損失関数が最小値を取得するようにパラメーターの解 w と b を見つけることです。価値。
最初に小さなテストを行ってみましょう 。 図 5 に示すように、微積分の知識に基づくと、特定の点での曲線の傾きは、その点での関数の微分値に等しくなります。考えてみてください、曲線の極点にあるとき、その点の傾きはいくらでしょうか?
図 5: 曲線の傾きは微分値に等しい
この質問に答えるのは難しくありません。曲線の極点での傾きは 0、つまり極点での関数の導関数は 0 です。次に、損失関数に w と b の最小値を取ると、次の連立方程式の解となるはずです。
サンプル データ (x, y) を上記の連立方程式に取り込むと、w と b の値を求めることができますが、この方法は線形回帰などの単純なタスクにのみ有効です。モデルに非線形変換が含まれている場合、または損失関数が平均二乗誤差の単純な形式ではない場合、上記の式でそれを解くことは困難になります。この問題を解決するために、以下ではより汎用的な数値解法である勾配降下法を紹介します。
2.3 勾配降下法
実際には、一方向性関数と呼ばれる、順方向には解きやすいが、逆方向には解きにくい関数が数多く存在し、暗号学ではこのような関数が数多く応用されています。パスワードロックの特徴は、鍵が正しいかどうかを迅速に判断できること(xが分かればyを見つけるのは容易である)ですが、パスワードロックシステムを取得しても正しい鍵を解読することはできません(yがわかっている場合)。そして×は非常に難しい)。
この状況は、山の頂上から斜面の谷まで歩きたい視覚障害者に特に似ています。彼は斜面の谷がどこにあるのかを見ることはできません (損失導関数が 0 の場合、パラメータ値を逆に解くことはできません)。彼の足を使って周囲の斜面を探索します (現在、点の微分値。勾配とも呼ばれます)。次に、損失関数の最小値を次のように解くことができます。現在のパラメータから値を取得し、最低点に到達するまで下り坂方向に段階的に下っていきます。著者はこの方法を「ブラインドダウンヒル法」と呼んでいます。いや、もっと正式な用語として「勾配降下法」というものがあります。
トレーニングの鍵は、損失関数 L が最小値を取るようなグループ (w, b) を見つけることです。まず、解を見つけるアイデアを得るために、損失関数 L が 2 つのパラメーター w5 と w9 によってのみ変化する単純な状況を見てみましょう。
ここでは、w5,w9を除くw0,w1,...,w12のパラメータとbを固定し、L(w5,w9)の形を絵で描くことができます。
net = Network(13)
losses = []
#只画出参数w5和w9在区间[-160, 160]的曲线部分,以及包含损失函数的极值
w5 = np.arange(-160.0, 160.0, 1.0)
w9 = np.arange(-160.0, 160.0, 1.0)
losses = np.zeros([len(w5), len(w9)])
#计算设定区域内每个参数取值所对应的Loss
for i in range(len(w5)):
for j in range(len(w9)):
net.w[5] = w5[i]
net.w[9] = w9[j]
z = net.forward(x)
loss = net.loss(z, y)
losses[i, j] = loss
#使用matplotlib将两个变量和对应的Loss作3D图
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = Axes3D(fig)
w5, w9 = np.meshgrid(w5, w9)
ax.plot_surface(w5, w9, losses, rstride=1, cstride=1, cmap='rainbow')
plt.show()
この単純なケースでは、上記のプログラムを使用して、3 次元空間でのパラメーターの変化に応じた損失関数の表面プロットを描画できます。図から、一部の領域の関数値が周囲の点よりも大幅に小さいことがわかります。
説明する必要があるのは、なぜここで絵を描くために w5 と w9 を選択するのかということです。これは、これら 2 つのパラメータを選択すると、損失関数の曲面グラフから極値の存在が比較的直観的にわかるためです。他のパラメーターの組み合わせの場合、損失関数の極値をグラフで観察するのは直感的ではありません。w2 と w6 を選択して絵を描く場合、上の図ほど直感的ではありません。
上の曲線が「滑らかな」傾きを示していることに注目してください。これが、損失関数として平均二乗誤差を選択した理由の 1 つです。図 6 は、パラメーター 次元が 1 つしかない場合の平均二乗誤差と絶対値誤差 (各サンプルの誤差のみが二乗処理なしで累積されます) の損失関数曲線図を示しています。
平均二乗誤差で表される「滑らかな」傾きには 2 つの利点があることがわかります。
- 曲線の最低点は微分可能です。
- 最下点に近づくほど曲線の傾きが徐々に緩やかになるため、現在の勾配で最下点に近づく度合い(最下点を見逃さないようにステップサイズを徐々に小さくするかどうか)を判断するのに役立ちます。
絶対値誤差にはこの 2 つの特徴がないため、損失関数の設計では「合理性」だけでなく「解きやすさ」も追求する必要があります。
次に、損失関数を最小化する [w5,w9] 値のセットを見つける必要があります。勾配降下法を実装するための解決策は次のとおりです。
- ステップ 1: 一連の初期値をランダムに選択します。例: [w5,w9]=[−100.0,−100.0]
- ステップ 2: L(w5',w9')<L(w5,w9) となるように次の点 [w5',w9'] を選択します。
- ステップ 3: 損失関数の減少がほぼ止まるまでステップ 2 を繰り返します。
[w5′, w9′] をどのように選択するかが重要であり、第一に L が減少していることを確認する必要があり、第二に、その減少傾向をできるだけ早くする必要があります。微積分の基礎知識によれば、図 7 に示す ように 。簡単に理解すると、ある点における関数の勾配の方向は、曲線の傾きが最も大きい方向になりますが、勾配の方向は上向きであるため、最も速く下降するのは勾配の反対方向です。
図 7: 勾配降下方向の概略図
2.4 勾配の計算
損失関数の計算方法については上で説明しましたが、ここで少し書き直し、勾配の計算をより簡潔にするために、係数を導入し、損失関数を次のように定義します。
ここで、zi は iii 番目のサンプルに対するネットワークの予測値です。
勾配の定義
w と b に関する L の偏導関数は次のように計算できます。
導関数の計算過程から係数が消去されていることが分かりますが、これは二次関数を導出する際に係数2が発生するためであり、損失関数を書き換える理由でもあります。
次に、サンプルが 1 つだけある場合を考慮し、勾配を計算します。
それは次のように計算できます。
w と b に関する L の偏導関数は次のように計算できます。
各変数のデータとディメンションは、特定の手順で表示できます。
x1 = x[0]
y1 = y[0]
z1 = net.forward(x1)
print('x1 {}, shape {}'.format(x1, x1.shape))
print('y1 {}, shape {}'.format(y1, y1.shape))
print('z1 {}, shape {}'.format(z1, z1.shape))
印刷結果
×1 [0. 0.18 0.07344184 0. 0.31481481 0.57750527 0.64160659 0.26920314 0. 0.22755741 0.28723404 1. 0.08967991]、形状 (13,) y1 [0.422222 22]、形状 (1、) z1 [115.40486738]、形状 (1、)
上式によれば、サンプルが 1 つしかない場合、ある wj (w0 など) の傾きを計算できます。
gradient_w0 = (z1 - y1) * x1[0]
print('gradient_w0 {}'.format(gradient_w0))
印刷結果
勾配_w0 [0.]
同様に、w1 の勾配を計算できます。
gradient_w1 = (z1 - y1) * x1[1]
print('gradient_w1 {}'.format(gradient_w1))
印刷結果
グラデーション_w1 [20.69687613]
順番に w2 の勾配を計算します。
gradient_w2= (z1 - y1) * x1[2]
print('gradient_w1 {}'.format(gradient_w2))
印刷結果
グラデーション_w1 [8.44453726]
賢明な読者は、for ループを作成すれば、w0 から w12 までのすべての重みの勾配を計算できると考えているかもしれませんが、読者はこのメソッドを自分で実装できます。
2.5 勾配計算に Numpy を使用する
Numpy ブロードキャスト メカニズム (ベクトルおよび行列の計算は単一変数の計算と同じです) に基づいて、勾配計算をより迅速に実装できます。勾配を計算するコードでは、(z1−y1)⋅x1 が直接使用され、結果は 13 次元のベクトルになり、各成分はその次元の勾配を表します。
gradient_w = (z1 - y1) * x1
print('gradient_w_by_sample1 {}, gradient.shape {}'.format(gradient_w, gradient_w.shape))
印刷結果
gradient_w_by_sample1 [ 0. 20.69687613 8.44453726 0. 36.19824014 66.40308345 73.773623 30.95368902 0. 26.16515307 33.02692999 114.98 264516 10.31163346]、gradient.shape (13,)
入力データには複数のサンプルがあり、それぞれが勾配に寄与します。上記のコードは、サンプル 1 のみがある場合の勾配値を計算します。同じ計算方法で、サンプル 2 とサンプル 3 の勾配への寄与を計算することもできます。
x2 = x[1]
y2 = y[1]
z2 = net.forward(x2)
gradient_w = (z2 - y2) * x2
print('gradient_w_by_sample2 {}, gradient.shape {}'.format(gradient_w, gradient_w.shape))
印刷結果
gradient_w_by_sample2 [3.94626939e-02 0.00000000e+00 4.38925272e+01 0.00000000e+00 2.89108135e+01 9.16634146e+01 1.30921707e+02 5.83 707680e+01 7.27259594e+00 1.92063337e +01 9.25321781e+01 1.67269707e+02 3.42016701e+01]、gradient.shape (13,)
読者の中には、for ループを使用して各サンプルの勾配への寄与を計算し、それを平均できると再び考える人もいるかもしれません。しかし、これを行う必要はありません。3 つのサンプルの場合など、Numpy の行列演算を使用して演算を簡素化することができます。
# 注意这里是一次取出3个样本的数据,不是取出第3个样本
x3samples = x[0:3]
y3samples = y[0:3]
z3samples = net.forward(x3samples)
print('x {}, shape {}'.format(x3samples, x3samples.shape))
print('y {}, shape {}'.format(y3samples, y3samples.shape))
print('z {}, shape {}'.format(z3samples, z3samples.shape))
印刷結果
x [[0.00000000e+00 1.80000000e-01 7.34418420e-02 0.00000000e+00 3.14814815e-01 5.77505269e-01 6.41606591e-01 2.69203139e-01 0.00000000e+00 2.27557411e-01 2.87234043e-01 1.00000000e+ 00 8.96799117e-02] [2.35922539e-04 0.00000000e+00 2.62405717e-01 0.00000000e+00 1.72839506e-01 5.47997701e-01 7.82698249e- 01 3.48961980e-01 4.34782609e-02 1.14822547e -01 5.53191489e- 01 1.00000000e+00 2.04470199e-01] [2.35697744e-04 0.00000000e+00 2.62405717e-01 0.00000000e+00 1.72839506e-01 6.94385898e- 01 5.99382080e-01 3.48961980e-01 4.34782609e- 02 1.14822547e- 01 5.53191489e-01 9.87519166e-01 6.34657837e-02]]、形状 (3, 13) y [[0.42222222] [0.36888889] [0.66 ]]、形状 (3, 1) z [[115.40486738] [167.63859551] [138.22280208]]、形状 (3, 1)
上記の x3samples、y3samples、z3samples の最初の次元のサイズはすべて 3 であり、サンプルが 3 つあることを示しています。これら 3 つのサンプルの勾配への寄与は以下で計算されます。
gradient_w = (z3samples - y3samples) * x3samples
print('gradient_w {}, gradient.shape {}'.format(gradient_w, gradient_w.shape))
印刷結果
gradient_w [[0.00000000e+00 2.06968761e+01 8.44453726e+00 0.00000000e+00 3.61982401e+01 6.64030834e+01 7.37736230e+01 3.09536890 e+01 0.00000000e+00 2.61651531e+01 3.30269300e+01 1.14982645e+ 02 1.03116335e+01] [3.94626939e-02 0.00000000e+00 4.38925272e+01 0.00000000e+00 2.89108135e+01 9.16634146e+01 1.30921707e+ 02 5.83707680e+01 7.27259594e+00 1.92063337e +01 9.25321781e+ 01 1.67269707e+02 3.42016701e+01] [3.24232421e-02 0.00000000e+00 3.60972657e+01 0.00000000e+00 2.37762868e+01 9.55216698e+ 01 8.24526785e+01 4.80041878e+01 5.98099139e +00 1.57953113e+ 01 7.60985714e+01 1.35845904e+02 8.73053104e+00]]、gradient.shape (3, 13)
gradient_w
ここで、計算された勾配の次元は 3×133 で、その最初の行は上記の最初のサンプルで計算された勾配 gradient_w_by_sample1 と一致し、2 行目は上記の 2 番目のサンプルで計算された勾配 gradient_w_by_sample2 と一致していることがわかります。3 行目は上記と一致しており、3 番目のサンプル gradient_w_by_sample3 によって計算された勾配は一致しています。ここで行列演算を使用すると、3 つのサンプルのそれぞれの勾配への寄与を計算する方が便利です。
N サンプルの場合、Numpy ライブラリのブロードキャスト機能を使用すると、次の方法ですべてのサンプルの勾配への寄与を直接計算できます。ここで、Numpy ライブラリを使用したブロードキャスト関数を要約しましょう。
- 一方で、パラメータの次元を拡張して、for ループを置き換えて、w0 から w12 までのすべてのパラメータに対する 1 つのサンプルの勾配を計算することができます。
- 一方、サンプルの次元を拡張し、for ループを使用してサンプル 0 からサンプル 403 までのパラメーターの勾配を計算することができます。
z = net.forward(x)
gradient_w = (z - y) * x
print('gradient_w shape {}'.format(gradient_w.shape))
print(gradient_w)
印刷結果
gradient_w 形状 (404, 13) [[0.00000000e+00 2.06968761e+01 8.44453726e+00 ... 3.30269300e+01 1.14982645e+02 1.03116335e+ 01] [3.94626939e-0 2 0.00000000e+00 4.38925272e+01 ... 9.25321781e+01 1.67269707e+02 3.42016701e+01] [3.24232421e-02 0.00000000e+00 3.60972657e+01 ... 7.60985714e+01 1.35845904e+0 2 8.73053104e+00] ... [4.37440950 e+01 0.00000000e+00 1.91527710e+02 ... 2.21129631e+02 2.73502438e+02 1.40298298e+02] [2.94457614e+01 0.00000000e+00 1.91313328 e+02 ... 2.20882114e+02 2.55779093e+ 02 1.40065873e+02] [7.44978133e+01 0.00000000e+00 1.87191522e+02 ... 2.16123255e+02 2.67310342e+02 1.33065082e+02]]
上記の gradient_w の各行は、勾配に対するサンプルの寄与を表します。勾配の計算式によれば、合計勾配は、勾配に対する各サンプルの寄与の平均値です。
Numpy の平均関数を使用してこのプロセスを実行することもできます。
# axis = 0 表示把每一行做相加然后再除以总的行数
gradient_w = np.mean(gradient_w, axis=0)
print('gradient_w ', gradient_w.shape)
print('w ', net.w.shape)
print(gradient_w)
print(net.w)
印刷結果
Gradient_W(13、) W(13、1) [5.53780571 10.64543906 75.67827617 17.61268347 63.65268301 80.73011068 123.00673216 31.660931151.21588871 6844792 96.0444792 .59539397 51.95466115] [[ 1.76405235E+00] [4.00157208E-01] [1.59000000E+02] [2.24089320E+ 00+00 ] [ 1.86755799e+00] [-9.77277880e-01] [ 1.59000000e+02] [-1.51357208e-01] [-1.03218852e-01] [ 4.10598502e-01] [ 1.44043571e-01] [ 1.45427351e+ 00] [7.61037725e-01]]
Numpy の行列演算を使用して勾配の計算を簡単に完了できますが、問題が発生します。gradient_w
の形状は (13,)、w の次元は (13, 1) です。この問題は、np.mean
関数を使用するときに 0 番目の次元が削除されることが原因で発生します。加算、減算、乗算、除算などの計算を容易にするために、gradient_w
w は同じ形状を維持する必要があります。したがって、gradient_w
次元も (13,1) に設定し、コードは次のようになります。
gradient_w = gradient_w[:, np.newaxis]
print('gradient_w shape', gradient_w.shape)
上記の分析に基づいて、勾配を計算するコードは次のようになります。
z = net.forward(x)
gradient_w = (z - y) * x
gradient_w = np.mean(gradient_w, axis=0)
gradient_w = gradient_w[:, np.newaxis]
gradient_w
印刷結果
array([[ 4.6555403 ], [ 19.35268996], [ 55.88081118], [ 14.00266972], [ 47.98588869], [ 76.87210821], [ 94.8555119 ], [ 36.07579608], [ 45.44575958]、[ 59.65733292]、 [ 83.65114918 ]、 [134.80387478] 、 [ 38.93998153]])
上記のコードは、w の勾配計算を非常に簡潔に完了します。同様に、b の勾配を計算するコードも同様の原理に基づいています。
gradient_b = (z - y)
gradient_b = np.mean(gradient_b)
# 此处b是一个数值,所以可以直接用np.mean得到一个标量
gradient_b
印刷結果
159.47184398759848
上記のwとbの傾きを計算する処理をgradient
Networkクラスの関数として記述すると、以下のような実装方法となります。
class Network(object):
def __init__(self, num_of_weights):
# 随机产生w的初始值
# 为了保持程序每次运行结果的一致性,此处设置固定的随机数种子
np.random.seed(0)
self.w = np.random.randn(num_of_weights, 1)
self.b = 0.
def forward(self, x):
z = np.dot(x, self.w) + self.b
return z
def loss(self, z, y):
error = z - y
num_samples = error.shape[0]
cost = error * error
cost = np.sum(cost) / num_samples
return cost
def gradient(self, x, y):
z = self.forward(x)
gradient_w = (z-y)*x
gradient_w = np.mean(gradient_w, axis=0)
gradient_w = gradient_w[:, np.newaxis]
gradient_b = (z - y)
gradient_b = np.mean(gradient_b)
return gradient_w, gradient_b
# 调用上面定义的gradient函数,计算梯度
# 初始化网络
net = Network(13)
# 设置[w5, w9] = [-100., -100.]
net.w[5] = -100.0
net.w[9] = -100.0
z = net.forward(x)
loss = net.loss(z, y)
gradient_w, gradient_b = net.gradient(x, y)
gradient_w5 = gradient_w[5][0]
gradient_w9 = gradient_w[9][0]
print('point {}, loss {}'.format([net.w[5][0], net.w[9][0]], loss))
print('gradient {}'.format([gradient_w5, gradient_w9]))
ポイント [-100.0, -100.0]、損失 7873.345739941161 勾配 [-45.87968288123223, -35.50236884482904]
2.6 損失関数が小さくなる点を決定する
次に、勾配を更新する方法の検討を開始します。まず、勾配の反対方向に小さなステップを移動し、次の点 P1 を見つけて、損失関数の変化を観察します。
# 在[w5, w9]平面上,沿着梯度的反方向移动到下一个点P1
# 定义移动步长 eta
eta = 0.1
# 更新参数w5和w9
net.w[5] = net.w[5] - eta * gradient_w5
net.w[9] = net.w[9] - eta * gradient_w9
# 重新计算z和loss
z = net.forward(x)
loss = net.loss(z, y)
gradient_w, gradient_b = net.gradient(x, y)
gradient_w5 = gradient_w[5][0]
gradient_w9 = gradient_w[9][0]
print('point {}, loss {}'.format([net.w[5][0], net.w[9][0]], loss))
print('gradient {}'.format([gradient_w5, gradient_w9]))
印刷結果
ポイント [-95.41203171187678, -96.4497631155171]、損失 7214.694816482369 勾配 [-43.883932999069096, -34.019273908495926]
上記のコードを実行すると、勾配の反対方向に小さなステップを踏むことで、次の点での損失関数が実際に減少することがわかります。興味があれば、上のコード ブロックをクリックして、損失関数が小さくなり続けるかどうかを確認してみてください。
上記のコードでは、パラメーターを更新するために毎回ステートメントが使用されます。 net.w[5] = net.w[5] - eta * gradient_w5
- 減算: パラメータをグラデーションの反対方向に移動する必要があります。
- eta: 勾配の反対方向の各パラメータ値の変化のサイズ、つまり、学習率とも呼ばれる各動作のステップ サイズを制御します。
考えてみてください。スケールの一貫性を保つために、なぜ入力特徴を正規化する必要があったのでしょうか? これは、均一なステップ サイズをより適切にするためです。
図 8 に示すように 、特徴入力が正規化された後、さまざまなパラメータによる損失出力は比較的規則的な曲線となり、学習率は統一された値に設定できます。特徴入力が正規化されていない場合、異なるパラメータに対応するパラメータは異なります。ステップ サイズが矛盾しています。スケールが大きいパラメータには大きなステップ サイズが必要であり、スケールが小さいパラメータには小さいステップ サイズが必要であるため、統一された学習率を設定できません。
図 8: 正規化されていないフィーチャは、フィーチャの寸法ごとに異なる理想的なステップ サイズをもたらします。
2.7 Train 関数のコードのカプセル化
上記のループ計算処理をsum関数にカプセル化しtrain
、update
実装方法は以下のようになります。
class Network(object):
def __init__(self, num_of_weights):
# 随机产生w的初始值
# 为了保持程序每次运行结果的一致性,此处设置固定的随机数种子
np.random.seed(0)
self.w = np.random.randn(num_of_weights,1)
self.w[5] = -100.
self.w[9] = -100.
self.b = 0.
def forward(self, x):
z = np.dot(x, self.w) + self.b
return z
def loss(self, z, y):
error = z - y
num_samples = error.shape[0]
cost = error * error
cost = np.sum(cost) / num_samples
return cost
def gradient(self, x, y):
z = self.forward(x)
gradient_w = (z-y)*x
gradient_w = np.mean(gradient_w, axis=0)
gradient_w = gradient_w[:, np.newaxis]
gradient_b = (z - y)
gradient_b = np.mean(gradient_b)
return gradient_w, gradient_b
def update(self, gradient_w5, gradient_w9, eta=0.01):
net.w[5] = net.w[5] - eta * gradient_w5
net.w[9] = net.w[9] - eta * gradient_w9
def train(self, x, y, iterations=100, eta=0.01):
points = []
losses = []
for i in range(iterations):
points.append([net.w[5][0], net.w[9][0]])
z = self.forward(x)
L = self.loss(z, y)
gradient_w, gradient_b = self.gradient(x, y)
gradient_w5 = gradient_w[5][0]
gradient_w9 = gradient_w[9][0]
self.update(gradient_w5, gradient_w9, eta)
losses.append(L)
if i % 50 == 0:
print('iter {}, point {}, loss {}'.format(i, [net.w[5][0], net.w[9][0]], L))
return points, losses
# 获取数据
train_data, test_data = load_data()
x = train_data[:, :-1]
y = train_data[:, -1:]
# 创建网络
net = Network(13)
num_iterations=2000
# 启动训练
points, losses = net.train(x, y, iterations=num_iterations, eta=0.01)
# 画出损失函数的变化趋势
plot_x = np.arange(num_iterations)
plot_y = np.array(losses)
plt.plot(plot_x, plot_y)
plt.show()
印刷結果
iter 0、ポイント [-99.54120317118768、-99.64497631155172]、損失 7873.345739941161 iter 50、ポイント [-78.9761810944732、-83.65939206734069]、損失 5131.4807 04109405 iter 100、ポイント [-62.4493631356931、-70.67918223434114]、損失 3346.754494352463 iter 150、ポイント [-49.17799206644332、 -60.12620415441553], loss 2184.906016270654 iter 200, point [-38.53070194231174, -51.533984751788346], loss 1428.4172504483342 iter 250, point [-29.998249130283174, -44.52613603923428], loss 935.7392894242679 iter 300, point [-23.169901624519575, -38.79894318028118], loss 614.7592258739251 iter 350 、ポイント [-17.71439280083778、-34.10731848231335]、損失 405.53408184471505 iter 400、ポイント [-13.364557220746388、-30.253470630210863]、損失 269.055 1396220099 iter 450、ポイント [-9.904936677384967、-27.077764259976597]、損失 179.9364750604248 iter 500、ポイント [-7.161782280775628、-24.451346444229817]、損失 121 .65711285489998 iter 550、ポイント [-4.994989383373879、-22.270198517465555]、 損失 83.46491706360901 iter 600、ポイント [-3.2915916915280783、 -20.450337700789422], loss 58.36183370758033 iter 650, point [-1.9605131425212885, -18.923946252536773], loss 41.792808952534 iter 700, point [-0.9283343968114077, -17.636248840494844], loss 30.792614998570482 iter 750, point [-0.13587780041668718, -16.542993494033716], loss 23.43065354742935 iter 800 、ポイント [0.4645474092373408、-15.60841945615185]、損失 18.449664464381506 iter 850, point [0.9113672926170796, -14.803617811655524], loss 15.030615923519784 iter 900,ポイント [1.2355357562745004、-14.105208963393421]、損失 12.639705730905764 iter 950, point [1.4619805189121953, -13.494275706622066], loss 10.928795653764196 iter 1000, point [1.6107694974712377, -12.955502492189021], loss 9.670616807081698 iter 1050, point [1.6980516626374353, -12.476481020835202], loss 8.716602071285436 iter 1100, point [1.7368159644039771, -12.04715001603925], loss 7.969442965176621 iter 1150, point [1.7375034995020395, -11.659343238414994], loss 7.365228465612388 iter 1200, point [1.7085012931271857, -11.306424818680442], loss 6.861819342703047 iter 1250, point [1.6565405824483015, -10.982995030930885 ]、損失6.431280353078019 iter 1300, point [1.5870180647823104, -10.684652890749808], loss 6.054953198278096 iter 1350, point [1.5042550040699705, -10.407804594738165 ]、損失 5.720248083137862 iter 1400, point [1.4117062100403601, -10.14950894127009], loss 5.418553777303124 iter 1450, point [1.3121285818148223, -9.907352585055445], loss 5.143875665274019 iter 1500, point [1.2077170340724794, -9.67934935975478], loss 4.891947653805328 iter 1550, point [1.1002141124777076, -9.463859017459276], loss 4.659652555766873 iter 1600, point [0.990998385834045, -9.259521632951046], loss 4.444643323159747 iter 1650, point [0.8811557188942747, -9.065204645952335], loss 4.245095084874306 iter 1700, point [0.7715367363576023, -8.87996009965401], loss 4.059542401818773 iter 1750、ポイント [0.662803148565214, -8.702990105791185]、損失 3.88677206759292 iter 1800, point [0.5554650931141796, -8.533618947271485 ]、損失 3.7257521401326525 iter 1850、ポイント [0.4499112301277286, -8.371270536496699]、損失 3.5755846299900256 iter 1900、ポイント [0.3464329929523944、-8.215450195281456]、損失 3.435473657404253 iter 1950、ポイント [0.24524412503452966、-8.065729922139326]、損失 3。 3047037451160453
2.8 すべてのパラメータに拡張されたトレーニング
読者に直観的な感覚を与えるために、上で示した勾配降下プロセスには 2 つのパラメーター w5 と w9 のみが含まれていますが、住宅価格予測の完全なモデルはすべてのパラメーター w と b を解決する必要があります。これには、 Network のupdate
および関数をtrain
変更する必要があります。計算に含まれるパラメーターが制限されなくなったため (すべてのパラメーターが計算に含まれる)、変更されたコードはより簡潔になります。実装ロジック:「出力の順計算、出力と実数値に基づく損失の計算、損失と入力に基づく勾配の計算、勾配に基づくパラメータ値の更新」の4つの部分が損失関数が完成するまで繰り返し実行されます。最小化されます。具体的なコードは以下の通りです。
class Network(object):
def __init__(self, num_of_weights):
# 随机产生w的初始值
# 为了保持程序每次运行结果的一致性,此处设置固定的随机数种子
np.random.seed(0)
self.w = np.random.randn(num_of_weights, 1)
self.b = 0.
def forward(self, x):
z = np.dot(x, self.w) + self.b
return z
def loss(self, z, y):
error = z - y
num_samples = error.shape[0]
cost = error * error
cost = np.sum(cost) / num_samples
return cost
def gradient(self, x, y):
z = self.forward(x)
gradient_w = (z-y)*x
gradient_w = np.mean(gradient_w, axis=0)
gradient_w = gradient_w[:, np.newaxis]
gradient_b = (z - y)
gradient_b = np.mean(gradient_b)
return gradient_w, gradient_b
def update(self, gradient_w, gradient_b, eta = 0.01):
self.w = self.w - eta * gradient_w
self.b = self.b - eta * gradient_b
def train(self, x, y, iterations=100, eta=0.01):
losses = []
for i in range(iterations):
z = self.forward(x)
L = self.loss(z, y)
gradient_w, gradient_b = self.gradient(x, y)
self.update(gradient_w, gradient_b, eta)
losses.append(L)
if (i+1) % 10 == 0:
print('iter {}, loss {}'.format(i, L))
return losses
# 获取数据
train_data, test_data = load_data()
x = train_data[:, :-1]
y = train_data[:, -1:]
# 创建网络
net = Network(13)
num_iterations=1000
# 启动训练
losses = net.train(x,y, iterations=num_iterations, eta=0.01)
# 画出损失函数的变化趋势
plot_x = np.arange(num_iterations)
plot_y = np.array(losses)
plt.plot(plot_x, plot_y)
plt.show()
印刷結果
iter 9, loss 5.143394325795511 iter 19, loss 3.097924194225988 iter 29, loss 2.082241020617026 iter 39, loss 1.5673801618157397 iter 49, loss 1.296620473507743 iter 59, loss 1.1453399043319765 iter 69, loss 1.0530155717435201 iter 79, loss 0.9902292156463153 iter 89, loss 0.9426576903842502 iter 99, loss 0.9033048096880774 iter 109、損失 0.868732003041364 iter 119、損失 0.837229250968144 iter 129、損失 0.807927474161227 iter 139、損失 0.7803677341465796 iter 149、損失 0.754292 0908532763 iter 159、 損失 0.7295420168915829 iter 169、損失 0.7060090054240883 iter 179、損失 0.6836105084697766 iter 189, loss 0.6622781710179414 iter 359、損失 0.4179034044959738 iter 199, loss 0.6419520361168637 iter 209, loss 0.6225776517869489 iter 219, loss 0.6041045903195837 iter 229, loss 0.5864856570315078 iter 239, loss 0.5696764374763879 iter 249, loss 0.5536350125932016 iter 259, loss 0.5383217588525027 iter 269, loss 0.5236991929680566 iter 279, loss 0.5097318413761649 iter 289, loss 0.4963861247069634 iter 299、損失 0.48363025234390233 iter 309、損失 0.4714341245401978 iter 319、損失 0.45976924072044867 iter 329、損失 0.44860861316591 iter 339、 損失 0.437 92668556597936 iter 349、損失 0.4276992560632111 iter 369、 損失 0.40851742358635523 iter 379、損失 0.39952075384787633 iter 389, loss 0.39089392200622347 iter 399, loss 0.3826184827405131 iter 409, loss 0.37467696356451247 iter 419, loss 0.36705281267772816 iter 429, loss 0.35973034962581096 iter 439, loss 0.35269471861856694 iter 449, loss 0.3459318443621334 iter 459, loss 0.3394283902696658 iter 469, loss 0.3331717189222164 iter 479、損失 0.3271498546584252 iter 489、損失 0.32135144817819605 iter 499、損失 0.31576574305173283 iter 509、損失 0.3103825440311681 iter 519、損失 0.3 0519218706757245 iter 529、損失 0.30018551094136725 iter 539、 損失 0.29535383041913843 iter 549、損失 0.29068891085453674 iter 559, loss 0.28618294415539336 iter 569、損失 0.28182852604338504 iter 579, loss 0.2776186345365534 iter 589, loss 0.27354660958874766 iter 599, loss 0.2696061338236152 iter 609, loss 0.265791214304132 iter 619, loss 0.262096165281848 iter 629, loss 0.258515591873034 iter 639, loss 0.25504437461176843 iter 649, loss 0.2516776548326958 iter 659, loss 0.24841082083874047 iter 669、損失 0.24523949481147192 iter 679、損失 0.2421595204240984 iter 689、損失 0.23916695111922887 iter 699、損失 0.23625803901558054 iter 709、損失 0。 2334292244097483 iter 719、損失 0.23067712584097294 iter 729、 損失 0.22799853068858242 iter 739、損失 0.22539038627340988 iter 749, loss 0.22284979143604464 iter 759、損失 0.22037398856623475 iter 769, loss 0.2179603560591435 iter 779, loss 0.2156064011754777 iter 789, loss 0.2133097532837386 iter 799, loss 0.2110681574640261 iter 809, loss 0.2088794684539304 iter 819, loss 0.20674164491810018 iter 829, loss 0.20465274402406475 iter 839, loss 0.20261091630783168 iter 849, loss 0.20061440081366638 iter 859、損失 0.1986615204933024 iter 869、損失 0.19675067785062839 iter 879、損失 0.19488035081864621 iter 889、損失 0.19304908885621125 iter 899、損失 0。 19125550925273513 iter 909、損失 0.1894982936296714 iter 919、 損失 0.18777618462820622 iter 929、損失 0.18608798277314595 0.18443254350353405 iter 949、損失 0.18280877436103968 iter 959、損失 0.18121563232764162 iter 969、損失 0.1796521213045923 iter 979、損失 0.1781172897250724 iter 989、損失 0.1 7661022829336184 iter 999、損失 0.17513006784373505
3. 確率的勾配降下法
上記のプログラムでは、各損失関数と勾配の計算はデータセット内の全データに基づいています。ボストンの住宅価格予測タスク データ セットの場合、サンプル数は比較的少なく、わずか 404 です。しかし、実際の問題では、データセットが非常に大きい場合が多く、毎回全量のデータを計算に使用すると効率が非常に低く、平たく言えば「牛のナイフで鶏を殺すようなもの」です。パラメーターは毎回グラデーションの反対方向に少しだけ更新されるため、方向はそれほど正確である必要はありません。合理的な解決策は、全体のデータセットから毎回ランダムにデータの一部を抽出して全体を表し、この部分のデータに基づいて勾配と損失を計算してパラメータを更新することです。この方法は確率的勾配降下法と呼ばれます。 . SGD)、中心となる概念は次のとおりです。
- ミニバッチ: 各反復中に抽出されたデータのバッチはミニバッチと呼ばれます。
- バッチサイズ: ミニバッチに含まれるサンプルの数は、バッチサイズと呼ばれます。
- エポック: プログラムが反復されると、サンプルはミニバッチに従って徐々に抽出され、データセット全体が走査されると、トレーニングのラウンドが完了します (エポックとも呼ばれます)。トレーニングを開始するときに、トレーニング ラウンドの数 num_epochs とbatch_size をパラメーターとして渡すことができます。
データ処理およびトレーニング プロセスにおけるコードの変更を含む、具体的な実装プロセスをプログラムと併せて以下に紹介します。
3.1 データ処理コードの修正
データ処理には、データ バッチの分割とサンプルの並べ替え (ランダム サンプリングの効果を実現するため) という 2 つの機能が必要です。
# 获取数据
train_data, test_data = load_data()
train_data.shape
印刷結果
(404、14)
train_data には合計 404 個のデータが含まれています。batch_size=10 の場合、最初の 0 ~ 9 サンプルが最初のミニバッチとして取得され、train_data1 という名前が付けられます。
train_data1 = train_data[0:10]
train_data1.shape
(10、14)
train_data1 のデータ (サンプル 0 ~ 9) を使用して勾配を計算し、ネットワーク パラメーターを更新します。
net = Network(13)
x = train_data1[:, :-1]
y = train_data1[:, -1:]
loss = net.train(x, y, iterations=1, eta=0.01)
loss
[4.497480200683046]
次に、サンプル No. 10 ~ 19 を 2 番目のミニバッチとして取得し、勾配を計算してネットワーク パラメーターを更新します。
train_data2 = train_data[10:20]
x = train_data2[:, :-1]
y = train_data2[:, -1:]
loss = net.train(x, y, iterations=1, eta=0.01)
loss
[5.849682302465982]
この方法によれば、新しいミニバッチが継続的に取り出され、ネットワークパラメータが徐々に更新される。
次に、次のコードに示すように、train_data は、batch_size のサイズの複数の mini_batch に分割されます。 train_data は mini_batch に分割され、 最初の 40 個の mini_batch にはそれぞれ 10 個のサンプルが含まれ、最後の mini_batch には 4 個のサンプルのみが含まれます。
batch_size = 10
n = len(train_data)
mini_batches = [train_data[k:k+batch_size] for k in range(0, n, batch_size)]
print('total number of mini_batches is ', len(mini_batches))
print('first mini_batch shape ', mini_batches[0].shape)
print('last mini_batch shape ', mini_batches[-1].shape)
ミニバッチの総数は 41 です 最初のミニバッチ形状 (10, 14) 最後のミニバッチ形状 (4, 14)
さらに、ここでは mini_batch が順番に読み取られますが、SGD ではサンプルの一部がランダムに選択されて母集団を表します。ランダム サンプリングの効果を実現するために、まず train_data 内のサンプルの順序をランダムに崩してから、mini_batch を抽出します。サンプルの順序をランダムに崩すにはnp.random.shuffle
関数を使う必要があるので、まずは使い方を紹介します。
例証します:
多数の実験を通じて、モデルは最後に表示されるデータにより強い印象を与えることがわかりました。トレーニング データがインポートされた後、モデル トレーニングの終わりに近づくほど、モデル パラメーターに対する最後の数バッチのデータの影響が大きくなります。モデルのメモリがトレーニング効果に影響を与えるのを防ぐために、サンプルをシャッフルする必要があります。
# 新建一个array
a = np.array([1,2,3,4,5,6,7,8,9,10,11,12])
print('before shuffle', a)
np.random.shuffle(a)
print('after shuffle', a)
シャッフル前 [ 1 2 3 4 5 6 7 8 9 10 11 12] シャッフル後 [ 7 2 11 3 8 6 12 1 4 5 10 9]
上記のコードを複数回実行すると、シャッフル関数実行後の数値の順序が毎回異なることがわかります。上の例は 1 次元配列が狂った場合ですが、2 次元配列が狂った場合の影響を見てみましょう。
# 新建一个array
a = np.array([1,2,3,4,5,6,7,8,9,10,11,12])
a = a.reshape([6, 2])
print('before shuffle\n', a)
np.random.shuffle(a)
print('after shuffle\n', a)
シャッフル前 [[ 1 2] [ 3 4] [ 5 6] [ 7 8] [ 9 10] [11 12]] シャッフル後 [[ 1 2] [ 3 4] [ 5 6] [ 9 10] [11 12 ] [ 7 8]]
実行結果を観察すると、配列の要素が 0 次元でランダムにシャッフルされていますが、1 次元の順序は変わっていないことがわかります。たとえば、数値 2 は数値 1 のすぐ後ろにあり、数値 8 は数値 7 のすぐ後ろにあり、2 番目の次元 [3, 4] は [1, 2] の後に来ません。SGD アルゴリズムを実装するコードのこの部分をtrain
Network クラスの関数に統合します。最終的な完全なコードは次のとおりです。
# 获取数据
train_data, test_data = load_data()
# 打乱样本顺序
np.random.shuffle(train_data)
# 将train_data分成多个mini_batch
batch_size = 10
n = len(train_data)
mini_batches = [train_data[k:k+batch_size] for k in range(0, n, batch_size)]
# 创建网络
net = Network(13)
# 依次使用每个mini_batch的数据
for mini_batch in mini_batches:
x = mini_batch[:, :-1]
y = mini_batch[:, -1:]
loss = net.train(x, y, iterations=1)
3.2 学習処理コードの修正
ランダムに選択された各ミニバッチ データは、パラメーター トレーニング用のモデルに入力されます。トレーニング プロセスの中核は 2 層のループです。
- ループの最初のレベルは、サンプル セットがトレーニングおよびトラバースされる回数を表します (「エポック」と呼ばれます)。コードは次のとおりです。
for epoch_id in range(num_epochs):
- ループの 2 番目のレベルは、各走査中にサンプル セットが分割される複数のバッチを表し、それらすべてをトレーニングする必要があります。これは「iter (反復)」と呼ばれます。コードは次のとおりです。
for iter_id,mini_batch in emumerate(mini_batches):
2 層ループの内部には、古典的な 4 ステップのトレーニング プロセスがあります: 前方計算 -> 損失の計算 -> 勾配の計算 -> パラメータの更新。これは、以前に学習した内容と一致しています。コードは次のとおりです。
x = mini_batch[:, :-1]
y = mini_batch[:, -1:]
a = self.forward(x) #前向计算
loss = self.loss(a, y) #计算损失
gradient_w, gradient_b = self.gradient(x, y) #计算梯度
self.update(gradient_w, gradient_b, eta) #更新参数
書き換えられたコードの 2 つの部分をtrain
Network クラスの関数に統合すると、最終的な実装は次のようになります。
import numpy as np
class Network(object):
def __init__(self, num_of_weights):
# 随机产生w的初始值
# 为了保持程序每次运行结果的一致性,此处设置固定的随机数种子
#np.random.seed(0)
self.w = np.random.randn(num_of_weights, 1)
self.b = 0.
def forward(self, x):
z = np.dot(x, self.w) + self.b
return z
def loss(self, z, y):
error = z - y
num_samples = error.shape[0]
cost = error * error
cost = np.sum(cost) / num_samples
return cost
def gradient(self, x, y):
z = self.forward(x)
N = x.shape[0]
gradient_w = 1. / N * np.sum((z-y) * x, axis=0)
gradient_w = gradient_w[:, np.newaxis]
gradient_b = 1. / N * np.sum(z-y)
return gradient_w, gradient_b
def update(self, gradient_w, gradient_b, eta = 0.01):
self.w = self.w - eta * gradient_w
self.b = self.b - eta * gradient_b
def train(self, training_data, num_epochs, batch_size=10, eta=0.01):
n = len(training_data)
losses = []
for epoch_id in range(num_epochs):
# 在每轮迭代开始之前,将训练数据的顺序随机打乱
# 然后再按每次取batch_size条数据的方式取出
np.random.shuffle(training_data)
# 将训练数据进行拆分,每个mini_batch包含batch_size条的数据
mini_batches = [training_data[k:k+batch_size] for k in range(0, n, batch_size)]
for iter_id, mini_batch in enumerate(mini_batches):
#print(self.w.shape)
#print(self.b)
x = mini_batch[:, :-1]
y = mini_batch[:, -1:]
a = self.forward(x)
loss = self.loss(a, y)
gradient_w, gradient_b = self.gradient(x, y)
self.update(gradient_w, gradient_b, eta)
losses.append(loss)
print('Epoch {:3d} / iter {:3d}, loss = {:.4f}'.
format(epoch_id, iter_id, loss))
return losses
# 获取数据
train_data, test_data = load_data()
# 创建网络
net = Network(13)
# 启动训练
losses = net.train(train_data, num_epochs=50, batch_size=100, eta=0.1)
# 画出损失函数的变化趋势
plot_x = np.arange(len(losses))
plot_y = np.array(losses)
plt.plot(plot_x, plot_y)
plt.show()
エポック 0 / イター 0、損失 = 1.0281 エポック 0 / イター 1、損失 = 0.5048 エポック 0 / イター 2、損失 = 0.6382 エポック 0 / イター 3、損失 = 0.5168 エポック 0 / イター 4、損失 = 0.1951 エポック 1 / イター 0 、損失 = 0.6281 エポック 1 / イター 1、損失 = 0.4611 エポック 1 / イター 2、損失 = 0.4520 エポック 1 / イター 3、損失 = 0.3961 エポック 1 / イター 4、損失 = 0.1381 エポック 2 / イター 0、損失 = 0.5642 エポック2 / イター 1、損失 = 0.4250 エポック 2 / イター 2、損失 = 0.4480 エポック 2 / イター 3、損失 = 0.3881 エポック 2 / イター 4、損失 = 0.1884 エポック 3 / イター 0、損失 = 0.3921 エポック 3 / イター 1、損失 = 0.5582 エポック 3 / イター 2、損失 = 0.3759 エポック 3 / イター 3、損失 = 0.3849 エポック 3 / イター 4、損失 = 0.1425 エポック 4 / イター 0、損失 = 0.3821 エポック 4 / イター 1、損失 = 0.4382 エポック 4 / イター 2、損失 = 0.3864 エポック 4 / イター 3 、損失 = 0.4314 エポック 4 / イター 4、損失 = 0.0471 エポック 5 / イター 0、損失 = 0.4264 エポック 5 / イター 1、損失 = 0.3829 エポック 5 / イター 2、損失 = 0.3179 エポック 5 / イター 3、損失 = 0.4149 エポック5 / iter 4、損失 = 0.1581 Epoch 6 / iter 0、損失 = 0.3148 Epoch 6 / iter 1、損失 = 0.3532 Epoch 6 / iter 2、損失 = 0.4195 Epoch 7 / iter 0、損失 = 0.3166 エポック 6 / イター 3、損失 = 0.3272 エポック 6 / イター 4、損失 = 1.2465 エポック 7 / イター 1、損失 = 0.2810 エポック 7 / イター 2、損失 = 0.4126 エポック 7 / イター 3、損失 = 0.3309 エポック 7 / イター 4、損失 = 0.2255 エポック 8 / イター 0 、損失 = 0.2555 エポック 8 / イター 1、損失 = 0.3678 エポック 8 / イター 2、損失 = 0.3342 エポック 8 / イター 3、損失 = 0.3806 エポック 8 / イター 4、損失 = 0.0570 エポック 9 / イター 0、損失 = 0.3532 エポック9 / iter 1、損失 = 0.3973 Epoch 9 / iter 2、損失 = 0.1945 Epoch 9 / iter 3、損失 = 0.2839 Epoch 9 / iter 4、損失 = 0.1604 Epoch 10 / iter 0、損失 = 0.3414 Epoch 10 / iter 1、損失 = 0.2774 エポック 10 / イター 2、損失 = 0.3439 Epoch 10 / iter 3、損失 = 0.2103 エポック 10 / イター 4、損失 = 0.0959 エポック 11 / イター 0、損失 = 0.3004 エポック 11 / イター 1、損失 = 0.2497 エポック 11 / イター 2、損失 = 0.2827 エポック 11 / イター 3 、損失 = 0.2987 エポック 11 / イター 4、損失 = 0.0316 エポック 12 / イター 0、損失 = 0.2509 エポック 12 / イター 1、損失 = 0.2535 エポック 12 / イター 2、損失 = 0.2944 エポック 12 / イター 3、損失 = 0.2889 エポック12 / iter 4、損失 = 0.0547 Epoch 13 / iter 0、損失 = 0.2792 Epoch 13 / iter 1、損失 = 0.2137 Epoch 13 / iter 2、損失 = 0.2427 Epoch 13 / iter 3、損失 = 0.2986 Epoch 13 / iter 4、損失 = 0.3861 Epoch 14 / iter 0、損失 = 0.3261 Epoch 14 / iter 1、損失 = 0.2123 エポック 14 / イター 2、損失 = 0.1837 エポック 14 / イター 3、損失 = 0.2968 エポック 14 / イター 4、損失 = 0.0620 エポック 15 / イター 0、損失 = 0.2402 エポック 15 / イター 1、損失 = 0.2823 エポック 15 / イター 2 、損失 = 0.2574 エポック 15 / イター 3、損失 = 0.1833 エポック 15 / イター 4、損失 = 0.0637 エポック 16 / イター 0、損失 = 0.1889 エポック 16 / イター 1、損失 = 0.1998 エポック 16 / イター 2、損失 = 0.2031 エポック16 / iter 3、損失 = 0.3219 Epoch 16 / iter 4、損失 = 0.1373 Epoch 17 / iter 0、損失 = 0.2042 Epoch 17 / iter 1、損失 = 0.2070 Epoch 17 / iter 2、損失 = 0.2651 Epoch 17 / iter 3、損失 = 0.2137 Epoch 17 / iter 4、損失 = 0.0138 エポック 18 / イター 0、損失 = 0.1794 エポック 18 / イター 1、損失 = 0.1575 エポック 18 / イター 2、損失 = 0.2554 エポック 18 / イター 3、損失 = 0.2531 エポック 18 / イター 4 、損失 = 0.2192 エポック 19 / イター 0、損失 = 0.1779 エポック 19 / イター 1、損失 = 0.2072 エポック 19 / イター 2、損失 = 0.2140 エポック 19 / イター 3、損失 = 0.2513 エポック 19 / イター 4、損失 = 0.0673 エポック20 / iter 0、損失 = 0.1634 Epoch 20 / iter 1、損失 = 0.1887 Epoch 20 / iter 2、損失 = 0.2515 Epoch 20 / iter 3、損失 = 0.1924 Epoch 20 / iter 4、損失 = 0.0926 Epoch 21 / iter 0、損失 = 0.1583 Epoch 21 / iter 1、損失 = 0.2319 Epoch 21 / iter 2、損失 = 0.1550 エポック 21 / イター 3、損失 = 0.2092 エポック 21 / イター 4、損失 = 0.1959 エポック 22 / イター 0、損失 = 0.2414 エポック 22 / イター 1、損失 = 0.1522 エポック 22 / イター 2、損失 = 0.1719 エポック 22 / イター 3 、損失 = 0.1829 エポック 22 / イター 4、損失 = 0.2748 エポック 23 / イター 0、損失 = 0.1861 エポック 23 / イター 1、損失 = 0.1830 エポック 23 / イター 2、損失 = 0.1606 エポック 23 / イター 3、損失 = 0.2351 エポック23 / iter 4、損失 = 0.1479 Epoch 24 / iter 0、損失 = 0.1678 Epoch 24 / iter 1、損失 = 0.2080 Epoch 24 / iter 2、損失 = 0.1471 Epoch 25 / iter 0、損失 = 0.1162 Epoch 24 / iter 3、損失 = 0.1747 エポック 24 / イター 4、損失 = 0.1607 Epoch 28 / iter 1、損失 = 0.1224 エポック 25 / イター 1、損失 = 0.2067 エポック 25 / イター 2、損失 = 0.1692 エポック 25 / イター 3、損失 = 0.1757 エポック 25 / イター 4、損失 = 0.0125 エポック 26 / イター 0、損失 = 0.1707 エポック 26 / イター 1 、損失 = 0.1898 エポック 26 / イター 2、損失 = 0.1409 エポック 26 / イター 3、損失 = 0.1501 エポック 26 / イター 4、損失 = 0.1002 エポック 27 / イター 0、損失 = 0.1590 エポック 27 / イター 1、損失 = 0.1801 エポック27 / iter 2、損失 = 0.1578 Epoch 27 / iter 3、損失 = 0.1257 Epoch 27 / iter 4、損失 = 0.7750 Epoch 28 / iter 0、損失 = 0.1573 Epoch 28 / iter 2、損失 = 0.1353 Epoch 28 / iter 3、損失 = 0.1862 エポック 28 / イター 4、損失 = 0.5305 エポック 29 / イター 0、損失 = 0.1981 エポック 29 / イター 1、損失 = 0.1114 エポック 29 / イター 2、損失 = 0.1414 エポック 29 / イター 3 、損失 = 0.1856 エポック 29 / イター 4、損失 = 0.0268 エポック 30 / イター 0、損失 = 0.0984 エポック 30 / イター 1、損失 = 0.1528 エポック 30 / イター 2、損失 = 0.1637 エポック 30 / イター 3、損失 = 0.1532 エポック30 / iter 4、損失 = 0.0846 Epoch 31 / iter 0、損失 = 0.1433 Epoch 31 / iter 1、損失 = 0.1643 Epoch 31 / iter 2、損失 = 0.1202 Epoch 31 / iter 3、損失 = 0.1215 Epoch 31 / iter 4、損失 = 0.2182 Epoch 32 / iter 0、損失 = 0.1567 Epoch 32 / iter 1、損失 = 0.1420 エポック 32 / イター 2、損失 = 0.1073 エポック 32 / イター 3、損失 = 0.1496 エポック 32 / イター 4、損失 = 0.0846 エポック 33 / イター 0、損失 = 0.1420 エポック 33 / イター 1、損失 = 0.1369 エポック 33 / イター 2 、損失 = 0.0962 エポック 33 / イター 3、損失 = 0.1480 エポック 33 / イター 4、損失 = 0.0687 エポック 34 / イター 0、損失 = 0.1234 エポック 34 / イター 1、損失 = 0.1028 エポック 34 / イター 2、損失 = 0.1407 エポック34 / iter 3、損失 = 0.1528 Epoch 34 / iter 4、損失 = 0.0390 Epoch 35 / iter 0、損失 = 0.1113 Epoch 35 / iter 1、損失 = 0.1289 Epoch 35 / iter 2、損失 = 0.1733 Epoch 35 / iter 3、損失 = 0.0892 Epoch 35 / iter 4、損失 = 0.0456 エポック 36 / イター 0、損失 = 0.1358 エポック 36 / イター 1、損失 = 0.0782 エポック 36 / イター 2、損失 = 0.1475 エポック 36 / イター 3、損失 = 0.1294 エポック 36 / イター 4、損失 = 0.0442 エポック 37 / イター 0 、損失 = 0.1136 エポック 37 / イター 1、損失 = 0.0954 エポック 37 / イター 2、損失 = 0.1542 エポック 37 / イター 3、損失 = 0.1262 エポック 37 / イター 4、損失 = 0.0452 エポック 38 / イター 0、損失 = 0.1277 エポック38 / iter 1、損失 = 0.1361 Epoch 38 / iter 2、損失 = 0.1103 Epoch 38 / iter 3、損失 = 0.0920 Epoch 38 / iter 4、損失 = 0.4119 Epoch 39 / iter 2、損失 = 0.1334 Epoch 39 / iter 0、損失 = 0.1054 エポック 39 / イター 1、損失 = 0.1165 エポック 39 / イター 3、損失 = 0.1240 エポック 39 / イター 4、損失 = 0.0672 エポック 40 / イター 0、損失 = 0.1218 エポック 40 / イター 1、損失 = 0.0982 エポック 40 / イター 2 、損失 = 0.1077 エポック 40 / イター 3、損失 = 0.1062 エポック 40 / イター 4、損失 = 0.4781 エポック 41 / イター 0、損失 = 0.1541 エポック 41 / イター 1、損失 = 0.1049 エポック 41 / イター 2、損失 = 0.0979 エポック41 / iter 3、損失 = 0.1042 Epoch 41 / iter 4、損失 = 0.0397 Epoch 42 / iter 0、損失 = 0.0996 Epoch 42 / iter 1、損失 = 0.1031 Epoch 42 / iter 2、損失 = 0.1294 Epoch 42 / iter 3、損失 = 0.0980 エポック 42 / イター 4、損失 = 0.1135 Epoch 43 / iter 0、損失 = 0.1521 エポック 43 / イター 1、損失 = 0.1088 エポック 43 / イター 2、損失 = 0.1089 エポック 43 / イター 3、損失 = 0.0775 エポック 43 / イター 4、損失 = 0.1444 エポック 44 / イター 0 、損失 = 0.0827 エポック 44 / イター 1、損失 = 0.0875 エポック 44 / イター 2、損失 = 0.1428 エポック 44 / イター 3、損失 = 0.1002 エポック 44 / イター 4、損失 = 0.0352 エポック 45 / イター 0、損失 = 0.0917 エポック45 / iter 1、損失 = 0.1193 Epoch 45 / iter 2、損失 = 0.0933 Epoch 45 / iter 3、損失 = 0.1044 Epoch 45 / iter 4、損失 = 0.0064 Epoch 46 / iter 0、損失 = 0.1020 Epoch 46 / iter 1、損失 = 0.0913 エポック 46 / イター 2、損失 = 0.0882 Epoch 46 / iter 3、損失 = 0.1170 エポック 46 / イター 4、損失 = 0.0330 エポック 47 / イター 0、損失 = 0.0696 エポック 47 / イター 1、損失 = 0.0996 エポック 47 / イター 2、損失 = 0.0948 エポック 47 / イター 3 、損失 = 0.1109 エポック 47 / イター 4、損失 = 0.5095 エポック 48 / イター 0、損失 = 0.0929 エポック 48 / イター 1、損失 = 0.1220 エポック 48 / イター 2、損失 = 0.1150 エポック 48 / イター 3、損失 = 0.0917 エポック48 / iter 4、損失 = 0.0968 Epoch 49 / iter 0、損失 = 0.0732 Epoch 49 / iter 1、損失 = 0.0808 Epoch 49 / iter 2、損失 = 0.0896 Epoch 49 / iter 3、損失 = 0.1306 Epoch 49 / iter 4、損失 = 0.1896
上記の損失の変化を観察すると、確率的勾配降下法はトレーニング プロセスを高速化しますが、パラメーターが更新され、毎回少数のサンプルのみに基づいて損失が計算されるため、損失減少曲線は振動します。
例証します:
住宅価格予測はデータ量が少ないため、確率的勾配降下法による性能向上を実感しにくい。
4. まとめ
このセクションでは、Numpy を使用して勾配降下アルゴリズムを実装し、ボストンの住宅価格を予測するための単純な線形モデルを構築およびトレーニングする方法を詳細に紹介します。ニューラル ネットワークを使用して住宅をモデル化するには 3 つの重要なポイントがあると結論付けることができます。価格予測:
- ネットワークを構築し、パラメータ w と b を初期化し、予測関数と損失関数の計算方法を定義します。
- 初期点をランダムに選択し、勾配の計算方法とパラメータの更新方法を確立します。
- 全体のデータセットから一部のデータを mini_batch として抽出し、勾配を計算してパラメータを更新し、損失関数がほとんど減少しなくなるまで繰り返します。