ディープラーニング入門コードを詳しく解説(コード付き)

目次

1. データ処理 

1.1 データの読み取り

1.2 データ形状変換 

1.3 データセットの分割 

1.4 データ正規化処理

1.5 ロードデータ関数へのカプセル化

2. モデル設計

2.1 トレーニング構成

2.2 トレーニングプロセス

2.3 勾配降下法

2.4 勾配の計算

2.5 勾配計算に Numpy を使用する

2.6 損失関数が小さくなる点を決定する

2.7 Train 関数のコードのカプセル化

2.8 すべてのパラメータに拡張されたトレーニング

3. 確率的勾配降下法

3.1 データ処理コードの修正

3.2 学習処理コードの修正

4. まとめ


ボストンの住宅価格予測は、プログラマーの世界の「Hello World」に似た古典的な機械学習タスクです。住宅価格については誰もが一般的に知っているように、ボストン地域の住宅価格は多くの要因の影響を受けます。このデータセットには、住宅価格とこのタイプの住宅の平均価格に影響を与える可能性のある 13 の要因が含まれており、図 1 に示す ように 。

データのダウンロード リンク: https://pan.baidu.com/s/1IEuef7z5EZropO-tJUnvbw 
抽出コード: dkmo

図1

予測問題の場合、予測出力の種類が連続実数値か離散ラベルかに応じて、回帰タスクと分類タスクに分けることができます。住宅価格は連続値であるため、住宅価格の予測は明らかに回帰タスクです。以下では、最も単純な線形回帰モデルを使用してこの問題を解決し、ニューラル ネットワークを使用してこのモデルを実装します。

線形回帰モデル

y=\sum_{j=1}^{M}x_jw_j+b

モデルの解決策は、各w_j合計をデータに適合させることですbこのうち、 、 、w_jbそれぞれ線形モデルの重みとバイアスを表します。1 次元では、w_j合計は b 直線の傾きと切片です。 

線形回帰モデルでは、平均二乗誤差を損失関数 (Loss) として使用し、予測住宅価格と実際の住宅価格の差を測定します。式は次のとおりです。

MSE=\frac{1}{n}\sum_{i=1}^{n}(\hat{Y}_i-Y)^{2}

考える:

なぜ損失関数として平均二乗誤差を使用するのでしょうか? サンプル全体の精度は、各トレーニング サンプルのモデルの予測誤差を合計することで測定されます。これは損失関数の設計において「合理性」だけでなく「解きやすさ」も考慮する必要があるためであり、この問題については以下で詳しく説明します。

線形回帰モデルのニューラルネットワーク構造

ニューラル ネットワークの標準的な構造では、各ニューロンは加重和と非線形変換で構成され、複数のニューロンが層状に配置および接続されてニューラル ネットワークを形成します。線形回帰モデルは、ニューラル ネットワーク モデルの最小限の特殊ケースと考えることができ、図 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 個 (このタイプの平均価格) が含まれていバツますY。家の)。)。 

# 读入之后的数据被转化成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の差を測定する何らかの指標が必要になります。回帰問題の場合、最も一般的に使用される測定方法は、モデルの品質を評価する指標として平均二乗誤差を使用することです。具体的な定義は次のとおりです。

損失=(yz)^{2}

上式の損失 (L と略記) は、通常、損失関数とも呼ばれ、モデルの品質を示す指標です。回帰問題では、平均二乗誤差が比較的一般的な形式であり、分類問題では通常、交差エントロピーが損失関数として使用されますが、これについては後続の章で詳しく説明します。サンプルの損失関数値を計算する実装は次のとおりです。

Loss = (y1 - z)*(y1 - z)
print(Loss)

印刷結果

[3.88644793]

損失関数を計算する際には各サンプルの損失関数値を考慮する必要があるため、単一サンプルの損失関数を合計し、それをサンプルの総数 N で割る必要があります。

損失=\frac{1}{N}\sum_{i=1}^{N}(y_i-z_i)^{2}

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 の最小値を取ると、次の連立方程式の解となるはずです。 

\frac{\partial L}{\partial w}=0

\frac{\partial L}{\partial b}=0

サンプル データ (x, y) を上記の連立方程式に取り込むと、w と b の値を求めることができますが、この方法は線形回帰などの単純なタスクにのみ有効です。モデルに非線形変換が含まれている場合、または損失関数が平均二乗誤差の単純な形式ではない場合、上記の式でそれを解くことは困難になります。この問題を解決するために、以下ではより汎用的な数値解法である勾配降下法を紹介します。

2.3 勾配降下法

実際には、一方向性関数と呼ばれる、順方向には解きやすいが、逆方向には解きにくい関数が数多く存在し、暗号学ではこのような関数が数多く応用されています。パスワードロックの特徴は、鍵が正しいかどうかを迅速に判断できること(xが分かればyを見つけるのは容易である)ですが、パスワードロックシステムを取得しても正しい鍵を解読することはできません(yがわかっている場合)。そして×は非常に難しい)。

この状況は、山の頂上から斜面の谷まで歩きたい視覚障害者に特に似ています。彼は斜面の谷がどこにあるのかを見ることはできません (損失導関数が 0 の場合、パラメータ値を逆に解くことはできません)。彼の足を使って周囲の斜面を探索します (現在、点の微分値。勾配とも呼ばれます)。次に、損失関数の最小値を次のように解くことができます。現在のパラメータから値を取得し、最低点に到達するまで下り坂方向に段階的に下っていきます。著者はこの方法を「ブラインドダウンヒル法」と呼んでいます。いや、もっと正式な用語として「勾配降下法」というものがあります。

トレーニングの鍵は、損失関数 L が最小値を取るようなグループ (w, b) を見つけることです。まず、解を見つけるアイデアを得るために、損失関数 L が 2 つのパラメーター w5 と w9 によってのみ変化する単純な状況を見てみましょう。

L=L(w_5,w_9)

ここでは、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 勾配の計算

損失関数の計算方法については上で説明しましたが、ここで少し書き直し、勾配の計算をより簡潔にするために、係数を導入し、\frac{1}{2}損失関数を次のように定義します。

L=\frac{1}{2N}\sum_{i=1}^{N}(y_i-z_i)^{2}

ここで、zi は iii 番目のサンプルに対するネットワークの予測値です。 

z_i=\sum_{j=0}^{12}x_i^{j}\cdot w_j+b

勾配の定義

gradient=({\frac{\partial L}{\partial w_0}},{\frac{\partial L}{\partial w_1}},\cdots ,{\frac{\partial L}{\partial w_{12 }}},{\frac{\partial L}{\partial b}})

w と b に関する L の偏導関数は次のように計算できます。 

\frac{\partial L}{\partial w_j}=\frac{1}{N}\sum_{i=1}^{N}(z_i-y_i)\frac{\partial z_i}{\partial w_j}= \frac{1}{N}\sum_{i=1}^{N}(z_i-y_i)x_i^{j}

\frac{\partial L}{\partial b}=\frac{1}{N}\sum_{i=1}^{N}(z_i-y_i)\frac{\partial z_i}{\partial b}= \frac{1}{N}\sum_{i=1}^{N}(z_i-y_i)

\frac{1}{2}導関数の計算過程から係数が消去されていることが分かりますが、これは二次関数を導出する際に係数2が発生するためであり、損失関数を書き換える理由でもあります

次に、サンプルが 1 つだけある場合を考慮し、勾配を計算します。

L=\frac{1}{2}(y_i-z_i)^{2}

z_1=x_1^{0}\cdot w_0+x_1^{1}\cdot w_1+\cdots +x_1^{12}\cdot w_{12}+b

それは次のように計算できます。

L=\frac{1}{2}(x_1^{0}\cdot w_0+x_1^{1}\cdot w_1+\cdots +x_1^{12}\cdot w_{12}+b-y_1)^{2 }

w と b に関する L の偏導関数は次のように計算できます。 

 \frac{\partial L}{\partial w_0}=(x_1^{0}\cdot w_0+x_1^{1}\cdot w_1+\cdots +x_1^{12}\cdot w_{12}+b-y_1) \cdot x_0^{1}=(z_1-y_1)\cdot x_0

 \frac{\partial L}{\partial b}=(x_1^{0}\cdot w_0+x_1^{1}\cdot w_1+\cdots +x_1^{12}\cdot w_{12}+b-y_1) \cdot 1=z_1-y_1

各変数のデータとディメンションは、特定の手順で表示できます。 

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 の各行は、勾配に対するサンプルの寄与を表します。勾配の計算式によれば、合計勾配は、勾配に対する各サンプルの寄与の平均値です。 

\frac{\partial L}{\partial w_j}=\frac{1}{N}\sum_{i=1}^{N}(z_i-y_i)\frac{\partial z_i}{\partial w_j}= \frac{1}{N}\sum_{i=1}^{N}(z_i-y_i)x_i^{j}

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_ww は同じ形状を維持する必要があります。したがって、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の傾きを計算する処理をgradientNetworkクラスの関数として記述すると、以下のような実装方法となります。

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関数にカプセル化しtrainupdate実装方法は以下のようになります。

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 に分割され、 \frac{404}{10}+1=41 最初の 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 アルゴリズムを実装するコードのこの部分をtrainNetwork クラスの関数に統合します。最終的な完全なコードは次のとおりです。 

# 获取数据
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 層のループです。

  1. ループの最初のレベルは、サンプル セットがトレーニングおよびトラバースされる回数を表します (「エポック」と呼ばれます)。コードは次のとおりです。

for epoch_id in range(num_epochs):

  1. ループの 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 つの部分をtrainNetwork クラスの関数に統合すると、最終的な実装は次のようになります。 

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 として抽出し、勾配を計算してパラメータを更新し、損失関数がほとんど減少しなくなるまで繰り返します。

おすすめ

転載: blog.csdn.net/weixin_43734080/article/details/121577271#comments_28362174