lab
Neural Network
日本語

ニューラルネットワークをゼロから構築してトレーニングする

概要

これまでに多くの理論的知識が語られてきましたが、今こそ実践を始める時です。ニューラルネットワークを最初から構築し、プロセス全体をつなぎ合わせるようにトレーニングしてみましょう。


より直感的で理解しやすくするために、次の原則に従います。

  1. ロジックを単純化するためにサードパーティのライブラリを使用しないでください。
  2. パフォーマンスの最適化なし:追加の概念や手法の導入を避け、複雑さを増します。

データセット

まず、データセットが必要です。視覚化を容易にするために、目的関数として 2 値関数を使用し、それをサンプリングしてデータセットを生成します。


注:実際のエンジニアリングプロジェクトでは、目的関数は不明ですが、サンプリングすることができます。

目的関数

o(x,y)={1x2+y2<10otherwiseo(x, y) = \begin{cases} 1 & x^2 + y^2 < 1 \\ 0 & \text{otherwise}\end{cases}

コードは次のように表示されます。

def o(x, y): return 1.0 if x*x + y*y < 1 else 0.0

データセットを生成する

sample_density = 10 xs = [ [-2.0 + 4 * x/sample_density, -2.0 + 4 * y/sample_density] for x in range(sample_density+1) for y in range(sample_density+1) ] dataset = [ (x, y, o(x, y)) for x, y in xs ]

生成されるデータセットは次のとおりです:[[-2.0,-2.0,0.0],[-2.0,-1.6,0.0],...]

データセット画像

ニューラルネットワークを構築する

活性化関数

import math def sigmoid(x): return 1 / (1 + math.exp(-x))

ニューロン

from random import seed, random seed(0) class Neuron: def __init__(self, num_inputs): self.weights = [random()-0.5 for _ in range(num_inputs)] self.bias = 0.0 def forward(self, inputs): # z = wx + b z = sum([ i * w for i, w in zip(inputs, self.weights) ]) + self.bias return sigmoid(z)

ニューロンの発現は次のとおりです。

sigmoid(wx+b)\text{sigmoid}(\mathbf w \mathbf x + b)
  • w\mathbf w:ベクトル、コード内の重み配列に対応
  • bb:コードのバイアスに対応します

注:ニューロンのパラメーターはランダムに初期化されます。ただし、再現性のある実験を確実にするために、ランダムシードが設定されます(seed(0))

ニューラルネットワーク

class MyNet: def __init__(self, num_inputs, hidden_shapes): layer_shapes = hidden_shapes + [1] input_shapes = [num_inputs] + hidden_shapes self.layers = [ [ Neuron(pre_layer_size) for _ in range(layer_size) ] for layer_size, pre_layer_size in zip(layer_shapes, input_shapes) ] def forward(self, inputs): for layer in self.layers: inputs = [ neuron.forward(inputs) for neuron in layer ] #最後のニューロンの出力を返す return inputs[0]

次のようにニューラルネットワークを構築します。

net = MyNet(2, [4])

この時点で、ニューラルネットワーク関数を呼び出すことができるニューラルネットワーク(net)ができました。

print(net.forward([0, 0]))

関数値 0.55 ...を取得します。この時点のニューラルネットワークは、トレーニングされていないネットワークです。

初期ニューラルネットワーク機能画像

ニューラルネットワークをトレーニングする

損失関数

まず、損失関数を定義します。

def square_loss(predict, target): return (predict-target)**2

勾配を計算する

勾配の計算は、特に深いニューラルネットワークの場合は複雑です。 バックプロパゲーションアルゴリズムは、ニューラルネットワークの勾配を計算するために特別に設計されたアルゴリズムです。


その複雑さのため、ここでは説明しません。興味のある方は、以下の詳細なコードを参照してください。さらに、現在の深層学習フレームワークには、勾配を自動的に計算する機能があります。


微分関数を定義します。

def sigmoid_derivative(x): _output = sigmoid(x) return _output * (1 - _output) def square_loss_derivative(predict, target): return 2 * (predict-target)

偏導関数を見つけます(導関数を容易にするために、データの一部がフォワード関数にキャッシュされます)。

class Neuron: ... def forward(self, inputs): self.inputs_cache = inputs # z = wx + b self.z_cache = sum([ i * w for i, w in zip(inputs, self.weights) ]) + self.bias return sigmoid(self.z_cache) def zero_grad(self): self.d_weights = [0.0 for w in self.weights] self.d_bias = 0.0 def backward(self, d_a): d_loss_z = d_a * sigmoid_derivative(self.z_cache) self.d_bias += d_loss_z for i in range(len(self.inputs_cache)): self.d_weights[i] += d_loss_z * self.inputs_cache[i] return [d_loss_z * w for w in self.weights] class MyNet: ... def zero_grad(self): for layer in self.layers: for neuron in layer: neuron.zero_grad() def backward(self, d_loss): d_as = [d_loss] for layer in reversed(self.layers): da_list = [ neuron.backward(d_a) for neuron, d_a in zip(layer, d_as) ] d_as = [sum(da) for da in zip(*da_list)]
  • 偏導関数はそれぞれd_weightsd_biasに格納されます
  • zero_grad関数は、各偏導関数を含む勾配をクリアするために使用されます
  • backward関数は、偏導関数を計算し、その値を累積的に格納するために使用されます

パラメータを更新する

最急降下法を使用してパラメーターを更新します。

class Neuron: ... def update_params(self, learning_rate): self.bias -= learning_rate * self.d_bias for i in range(len(self.weights)): self.weights[i] -= learning_rate * self.d_weights[i] class MyNet: ... def update_params(self, learning_rate): for layer in self.layers: for neuron in layer: neuron.update_params(learning_rate)

トレーニングを実行する

def one_step(learning_rate): net.zero_grad() loss = 0.0 num_samples = len(dataset) for x, y, z in dataset: predict = net.forward([x, y]) loss += square_loss(predict, z) net.backward(square_loss_derivative(predict, z) / num_samples) net.update_params(learning_rate) return loss / num_samples def train(epoch, learning_rate): for i in range(epoch): loss = one_step(learning_rate) if i == 0 or (i+1) % 100 == 0: print(f"{i+1} {loss:.4f}")

2000 ステップのトレーニング:

train(2000, learning_rate=10)

注:ここでは、プロジェクトの状況に関連して、比較的高い学習率が使用されます。実際のプロジェクトでの学習率は通常非常に小さいです

トレーニング後のニューラルネットワーク機能画像
log y\text{log y}
損失曲線

推論

トレーニング後、モデルを推論に使用できます。

def inference(x, y): return net.forward([x, y]) print(inference(1, 2))

完全なコードを参照してください:nn_from_scratch.py

概要

この練習の手順は次のとおりです。

  1. 仮想目的関数を作成します: o(x,y)o(x, y);
  2. o(x,y)o(x, y)をサンプリングしてデータセット、つまりデータセット関数を取得します: d(x,y)d(x, y)
  3. 隠れ層を持つ完全に接続されたニューラルネットワーク、つまりニューラルネットワーク関数を構築しました: f(x,y)f(x, y)
  4. 勾配降下法を使用して、 f(x,y)f(x, y)d(x,y)d(x, y)に近似するようにニューラルネットワークをトレーニングします。

最も複雑な部分は、バックプロパゲーションアルゴリズムを使用する勾配を見つけることです。実際のプロジェクトでは、開発に主流の深層学習フレームワークを使用すると、勾配のコードを保存し、しきい値を下げることができます。


ラボの3D 分類実験では、2 番目のデータセットはこのプラクティスのデータセットと非常に似ているため、アクセスして操作できます。