Geminiとのやり取りを元に、構成されています。画像も生成しています。
2層ニューラルネットワークの実装 入力データは乱数
参考書:
ゼロから作るDeepLearning Pythonで学ぶディープラーニングの理論と実践 斎藤康毅 著
開発環境:
VScode + 拡張機能Python(microsoft) + anaconda(統計処理、参考書の推薦ライブラリ)
この記事は、ゼロから作るDeep Learning 第4章の学習記録と、補足知識の記録になります。
2層ニューラルネットワーク 実装概要
「1層ニューラルネットワーク(simpleNet)で勾配を求める仕組みを構築した次のステップとして、重みとバイアスを増やした『2層ニューラルネットワーク』の実装を行います。
1. アルゴリズムの拡張と実装の課題
アルゴリズムの根幹は1層の拡張であるため、理論的な飛躍よりも、PythonとNumPyを用いた『多次元配列の管理術』 の習得がメインの作業となります。層が増えることでパラメータ(重み・バイアス)の数が増大するため、これらをいかにシステマチックに扱うかが重要になります。
2. モジュール化による設計の整理
1層のコードでは全処理を1つのファイルに収める『モノリシック』な構成でしたが、2層ではプロジェクトをモジュール化し、ネットワークの構造(TwoLayerNet クラス)や共通関数(layers.py や gradient.py 等)を別ファイルに切り出しました。これにより、コードの保守性と再利用性が向上します。
3. 多層化の反映と計算の共通化
層が1層増えたことによる変化は、主に推論(predict)時の行列の内積計算が一段増える点にあります。勾配の算出については、増えた各層のパラメータを順次『数値微分関数』に投入するだけなので、1層の時と手法自体は変わりません。
結論として、2層ネットワークの実装は、増大したパラメータをNumPyでどう整理し、Pythonの文法を駆使していかに効率的に扱うかという、エンジニアリング視点での作業が中心となります。」
※参考書とはちがい、MNISTのデータセットを使用していません。入力バッチデータx_trainは、乱数で生成しています。
フォルダ構成
myProject/
├── dataset/ # データセット読み込み用のフォルダ
│ ├──
│ └──
├── common/ # プロジェクト全体で再利用する共通モジュール
│ ├── myFunctions.py # 活性化関数(sigmoid, softmax)や損失関数など
│ ├── myGradient.py # 数値微分(numerical_gradient)の関数
│ ├──
│ └──
├── main/ # 【実行用フォルダ】
│ ├── myTwo_layer_net.py # 2層ニューラルネットワークのクラス定義
│ ├── myTrain_net.py # 学習を実行するメインスクリプト
│ └──
└──
※ファイル名が参考書とちがい、my*.pyになっています。
※実行するときは、VScodeのターミナルで、カレントディレクトリを実行ファイルのある場所に移動してください。cd myProject/mainのようなコマンドを打ち込みます。
common/myfuctions.py
import numpy as np
def sigmoid(x):
return 1/(1 + np.exp(-x))
#rectified linear unit正規化線形関数
def relu(x):
return np.maximum(0,x)
#2つの配列(または配列とスカラ値)を比較し、
# 大きい方の数値を取って新しい配列を作ります。
#ソフトマックス
def softmax(x):
if x.ndim == 2: #2次元ならば、
x = x.T #行列を転置
x = x - np.max(x, axis = 0) #すべての値<=0
y = np.exp(x)/np.sum(np.exp(x),axis = 0)
return y.T
x = x - np.max(x)
return np.exp(x)/np.sum(np.exp(x))
#交差エントロピー誤差
def cross_entropy_error(y,t):
if y.ndim == 1:
t = t.reshape(1,t.size)
y = y.reshape(1,y.size)
if t.size == y.size:
t = t.argmax(axis=1)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size),t]+1e-7))/batch_size
common/myGradient.py
import numpy as np
def numerical_gradient(f,x):
h = 1e-4
grad = np.zeros_like(x)
it = np.nditer(x,flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
idx = it.multi_index
tmp_val = x[idx]
x[idx] = tmp_val + h
fxh1 = f(x)
x[idx] = tmp_val - h
fxh2 = f(x)
grad[idx] = (fxh1-fxh2)/(2*h)
x[idx] = tmp_val
it.iternext()
return grad
main/myTwo_layer_net.py
import sys,os
sys.path.append(os.pardir)
from common.myFunctions import *
from common.myGradient import numerical_gradient
import numpy as np
#self は、一言で言うと 「生成されたインスタンス(自分自身)を指すラベル」 です。
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size, weight_init_sdt=0.01):
self.params = {}
self.params['W1']=weight_init_sdt*np.random.rand(input_size,hidden_size)
self.params['b1']=np.zeros(hidden_size)
self.params['W2']=weight_init_sdt*np.random.rand(hidden_size,output_size)
self.params['b2']=np.zeros(output_size)
#タプル・アンパック
#この書き方の背景には、Pythonの「タプル」という概念があります。
#パッキング: 右辺の self.params['W1'], self.params['W2'] は、暗黙的に一つの**タプル(データのセット)**としてまとめられます。
#アンパック: そのセットが、左辺の W1, W2 という2つの変数に「荷ほどき(アンパック)」されて代入されます。
def predict(self, x):
W1,W2 = self.params['W1'], self.params['W2']
b1,b2 = self.params['b1'], self.params['b2']
a1 = np.dot(x,W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1,W2) + b2
y = softmax(a2)
return y
def loss(self, x, t):
y = self.predict(x)
return cross_entropy_error(y,t)
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x,t)
grads = {}
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
return grads
- {} (辞書型)とは何か?
Pythonの辞書型(dictionary)は、**「キー(Key)」と「値(Value)」**をペアにしてデータを保存するデータ構造です。
イメージ: 本の「索引」のようなものです。「単語(キー)」を探せば、その「説明(値)」が見つかる仕組みです。
初期化: {} と書くと、中身が何もない「空の辞書」が作成されます。
main/myTrain_neuralnet.py
import sys, os
sys.path.append(os.pardir)
import numpy as np
np.set_printoptions(linewidth = 200)
from myTwo_layer_net import TwoLayerNet
train_size = 100 #データ数
input_size = 100 #入力ノード数
hidden_size = 10 #隠れ層のニューロン数
output_size = 10
np.random.seed(42)#再現性のため固定
#入力データ:正規分布で生成
x_train = np.random.randn(train_size, input_size)
#正解ラベル:one-hot表現をランダム生成
t_train = np.zeros((train_size, output_size))
for i in range(train_size):
rand_label = np.random.randint(0,output_size)#最小値、最大値
t_train[i,rand_label] = 1
# --- ネットワークの初期化 ---
net = TwoLayerNet(input_size=input_size, hidden_size = hidden_size, output_size = output_size)
# --- ハイパーパラメータ ---
iters_num = 300 #繰り返し回数(処理が重いので、少なめに設定)
batch_size = 10 #ミニバッチサイズ
learning_rate = 0.1 #学習率
print("学習開始(数値微分のため時間がかかります...)")
for i in range(iters_num):
#1.ミニバッチの取得
#第一引数に整数 n を渡すと、range(n) (0からn-1まで)の中から選んでくれます。
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
#2.勾配の計算
grad = net.numerical_gradient(x_batch,t_batch)
#3.パラメータの更新
for key in ('W1', 'b1', 'W2', 'b2'):
net.params[key] -= learning_rate * grad[key]
#4.学習経過の記録
if(i+1) % 100==0:
current_softmax = net.predict(x_batch)
print("current_softmax:\n",current_softmax)
loss = net.loss(x_batch, t_batch)
print(f"Iteration {i+1}: Loss = {loss:.4f}")
print("-"*30)
print("学習終了")
print(t_batch)
newA = current_softmax*t_batch
print(newA)