# 4.3.7~4.3.10
:::info
下面基本上都是將前面的function再增加一些功能
:::
### 接收多維資料
主要是利用將
(batch_size, num_features, width , height)
轉成(batch_size, num_features * width * height)
```python=
class Layer:
def __init__(self):
self.params = None
def forward(self, x):
raise NotImplementedError # 這個方法用於前向傳播,需要在子類別中實作
def backward(self, dZ):
raise NotImplementedError # 這個方法用於反向傳播,需要在子類別中實作
def reg_grad(self, reg):
pass # 這個方法用於計算正則化梯度,不是必要的
def reg_loss(self, reg):
return 0.0 # 這個方法用於計算正則化損失,通常初始化為0.0
class Dense(Layer):
def __init__(self, input_dim, out_dim, init_method=('random', 0.01)):
super().__init__()
random_method_name, random_value = init_method
if random_method_name == "random":
# 使用隨機值初始化權重矩陣和偏置項
self.W = np.random.rand(input_dim, out_dim) * random_value
self.b = np.random.rand(1, out_dim) * random_value
elif random_method_name == "he":
# 使用He初始化方法初始化權重矩陣,偏置項初始化為0
self.W = np.random.randn(input_dim, out_dim) * np.sqrt(2 / input_dim)
self.b = np.zeros((1, out_dim))
elif random_method_name == "xavier":
# 使用Xavier初始化方法初始化權重矩陣,偏置項初始化為隨機值
self.W = np.random.randn(input_dim, out_dim) * np.sqrt(1 / input_dim)
self.b = np.random.rand(1, out_dim) * random_value
elif random_method_name == "zeros":
# 將權重矩陣和偏置項初始化為零
self.W = np.zeros((input_dim, out_dim))
self.b = np.zeros((1, out_dim))
else:
# 默認情況下,使用隨機值初始化權重矩陣和偏置項
self.W = np.random.rand(input_dim, out_dim) * random_value
self.b = np.zeros((1, out_dim))
# 將參數和梯度儲存為列表
self.params = [self.W, self.b]
self.grads = [np.zeros_like(self.W), np.zeros_like(self.b)]
def forward(self, x):
self.x = x
# 將輸入資料 reshape 為二維形狀,執行前向傳播計算
x = x.reshape(x.shape[0], np.prod(x.shape[1:]))
Z = np.matmul(x, self.W) + self.b # 計算線性變換
return Z
def backward(self, dZ):
x = self.x
x = x.reshape(x.shape[0], np.prod(x.shape[1:]))
dW = np.dot(x.T, dZ) # 計算權重矩陣的梯度
db = np.sum(dZ, axis=0, keepdims=True) # 計算偏置項的梯度
dx = np.dot(dZ, np.transpose(self.W)) # 計算輸入資料的梯度
dx = dx.reshape(self.x.shape) # 將輸入資料的梯度 reshape 為與輸入相同的形狀
self.grads[0] += dW # 儲存權重梯度
self.grads[1] += db # 儲存偏置項梯度
return dx # 返回輸入資料的梯度
def reg_grad(self, reg):
# 計算正則化梯度,用於更新梯度
self.grads[0] += 2 * reg * self.W
def reg_loss(self, reg):
# 計算正則化損失,用於添加到總損失中
return reg * np.sum(self.W**2)
def reg_loss_grad(self, reg):
# 同時計算正則化梯度和正則化損失
self.grads[0] += 2 * reg * self.W
return reg * np.sum(self.W**2)
```
## 定義啟動函數
```python=
class Relu(Layer):
def __init__(self):
super().__init__()
def forward(self, x):
self.x = x
return np.maximum(0, x)
def backward(self, grad_output):
relu_grad = self.x > 0
return grad_output * relu_grad
class Sigmoid(Layer):
def __init__(self):
super().__init__()
def forward(self, x):
self.x = x
return 1.0 / (1.0 + np.exp(-x))
def backward(self, grad_output):
sigmoid_output = 1.0 / (1.0 + np.exp(-self.x))
return grad_output * sigmoid_output * (1 - sigmoid_output)
class Tanh(Layer):
def __init__(self):
super().__init__()
def forward(self, x):
self.x = x
self.a = np.tanh(x)
return self.a
def backward(self, grad_output):
d = 1 - np.square(self.a)
return grad_output * d
class LeakyRelu(Layer):
def __init__(self, leaky_slope):
super().__init__()
self.leaky_slope = leaky_slope
def forward(self, x):
self.x = x
return np.maximum(self.leaky_slope * x, x)
def backward(self, grad_output):
d = np.zeros_like(self.x)
d[self.x <= 0] = self.leaky_slope
d[self.x > 0] = 1
return grad_output * d
```
## Optimizer實作
### SGD
```python=
class SGD:
def __init__(self, model_params, learning_rate=0.01, momentum=0.9):
"""
初始化隨機梯度下降(SGD)優化器的類別。
參數:
- model_params(list of arrays):模型參數的列表,每個元素都是一個NumPy陣列。
- learning_rate(float,可選):學習率,控制每次參數更新的步長。預設為0.01。
- momentum(float,可選):動量,控制SGD的動量項。預設為0.9。
"""
# 初始化模型參數、學習率和動量
self.params = [(param, np.zeros_like(param)) for param in model_params]
self.lr = learning_rate
self.momentum = momentum
# 初始化速度(momentum的矢量)
self.vs = [np.zeros_like(param) for param in model_params]
def zero_grad(self):
"""
將所有梯度歸零。
此方法用於將所有模型參數的梯度初始化為零,以便進行下一輪的反向傳播。
"""
for param, grad in self.params:
grad.fill(0)
def step(self):
"""
執行一次SGD優化步驟。
此方法用於根據當前的梯度和速度執行一次SGD優化步驟,更新模型參數。
"""
for (param, grad), v in zip(self.params, self.vs):
# 計算動量項
v = self.momentum * v + self.lr * grad
# 更新模型參數
param -= v
def scale_learning_rate(self, scale):
"""
縮放學習率。
參數:
- scale(float):學習率的縮放因子,用於增加或減小學習率的大小。
此方法用於按照指定的比例縮放學習率,以便在訓練過程中調整學習率。
"""
self.lr *= scale
```
### Adam
```python=
class Adam:
def __init__(self, model_params, learning_rate=0.01, beta_1=0.9, beta_2=0.999, epsilon=1e-8):
"""
初始化Adam優化器的類別。
參數:
- model_params(list of pairs):模型參數和對應的梯度組成的列表。
- learning_rate(float,可選):學習率,控制每次參數更新的步長。預設為0.01。
- beta_1(float,可選):一階矩估計的衰減率(通常設置為0.9)。
- beta_2(float,可選):二階矩估計的衰減率(通常設置為0.999)。
- epsilon(float,可選):防止分母為零的小數(通常設置為1e-8)。
"""
# 初始化模型參數、學習率和超參數
self.params = model_params
self.lr = learning_rate
self.beta_1, self.beta_2, self.epsilon = beta_1, beta_2, epsilon
# 初始化一階和二階矩估計(m和v),以及時間步數(t)
self.ms = [np.zeros_like(p) for p, _ in self.params]
self.vs = [np.zeros_like(p) for p, _ in self.params]
self.t = 0
def zero_grad(self):
"""
將所有梯度歸零。
此方法用於將所有模型參數的梯度初始化為零,以便進行下一輪的反向傳播。
"""
for p, grad in self.params:
grad.fill(0)
def step(self):
"""
執行一次Adam優化步驟。
此方法用於根據當前的梯度和矩估計執行一次Adam優化步驟,更新模型參數。
"""
beta_1, beta_2, lr, t = self.beta_1, self.beta_2, self.lr, self.t
t += 1
self.t = t
for i, (p, grad) in enumerate(self.params):
m, v = self.ms[i], self.vs[i]
# 更新一階和二階矩估計
m = beta_1 * m + (1 - beta_1) * grad
v = beta_2 * v + (1 - beta_2) * (grad**2)
# 修正偏差
m1 = m / (1 - np.power(beta_1, t))
v1 = v / (1 - np.power(beta_2, t))
# 更新模型參數
p -= lr * m1 / (np.sqrt(v1) + self.epsilon)
def scale_learning_rate(self, scale):
"""
縮放學習率。
參數:
- scale(float):學習率的縮放因子,用於增加或減小學習率的大小。
此方法用於按照指定的比例縮放學習率,以便在訓練過程中調整學習率。
"""
self.lr *= scale
```
## 讀寫模型參數及自製神經網路
```python=
class NeuralNetwork:
def __init__(self):
"""
初始化神經網絡的類別。
此類別用於建立和操作神經網絡模型。它包含了神經網絡的各個層、參數、以及相關的方法。
"""
self._layers = [] # 儲存神經網絡的層
self._params = [] # 儲存神經網絡的參數和梯度
def add_layer(self, layer):
"""
添加層到神經網絡中。
參數:
- layer(Layer):要添加的神經網絡層。
此方法用於將一個神經網絡層添加到神經網絡中,同時也會將層的參數和梯度添加到參數列表中。
"""
self._layers.append(layer)
if layer.params:
for i, _ in enumerate(layer.params):
self._params.append([layer.params[i], layer.grads[i]])
def forward(self, x):
"""
前向傳播運算。
參數:
- x:輸入數據。
此方法用於執行神經網絡的前向傳播,從輸入數據到輸出預測的過程。
"""
for layer in self._layers:
x = layer.forward(x)
return x
def __call__(self, x):
"""
調用神經網絡模型。
此方法允許使用神經網絡實例像函數一樣調用,並執行前向傳播。
"""
return self.forward(x)
def predict(self, x):
"""
進行預測。
參數:
- x:輸入數據。
此方法用於根據輸入數據進行預測,返回預測結果。
"""
D = self.forward(x)
if D.ndim == 1:
return np.argmax(D)
return np.argmax(D, axis=1)
def backward(self, loss_grad, reg=0.):
"""
反向傳播運算。
參數:
- loss_grad:損失函數的梯度。
- reg:正則化參數,用於計算正則化梯度。
此方法用於執行神經網絡的反向傳播,從損失函數的梯度反向計算各層的梯度。
同時,如果有正則化項,也計算正則化梯度。
"""
for i in reversed(range(len(self._layers))):
layer = self._layers[i]
loss_grad = layer.backward(loss_grad)
layer.reg_grad(reg)
return loss_grad
def reg_loss(self, reg):
"""
計算正則化損失。
參數:
- reg:正則化參數,用於計算正則化損失。
此方法用於計算神經網絡模型的正則化損失。
"""
reg_loss = 0
for i in range(len(self._layers)):
reg_loss += self._layers[i].reg_loss(reg)
return reg_loss
def parameters(self):
"""
獲取模型參數和梯度。
此方法返回神經網絡模型的參數和梯度列表。
"""
return self._params
def zero_grad(self):
"""
將所有梯度歸零。
此方法用於將所有模型參數的梯度初始化為零,以便進行下一輪的反向傳播。
"""
for i, _ in enumerate(self._params):
self._params[i][1][:] = 0
def get_parameters(self):
"""
獲取模型參數。
此方法返回神經網絡模型的參數列表。
"""
return self._params
def save_parameters(self, filename):
"""
儲存模型參數到文件。
參數:
- filename:要保存參數的文件名。
此方法用於將神經網絡模型的參數儲存到文件,以便之後載入和使用。
"""
params = {}
for i in range(len(self._layers)):
if self._layers[i].params:
params[i] = self._layers[i].params
np.save(filename, params)
def load_parameters(self, filename):
"""
從文件載入模型參數。
參數:
- filename:包含要載入的參數的文件名。
此方法用於從文件載入之前保存的神經網絡模型的參數。
"""
params = np.load(filename, allow_pickle=True).item()
count = 0
for i in range(len(self._layers)):
if self._layers[i].params:
layer_params = params.get(i)
self._layers[i].params = layer_params
for j in range(len(layer_params)):
self._params[count][0] = layer_params[j]
count += 1
```
## 訓練模型
```python=
def data_iter(x,y,batch_size,if_shuffle=False):
m=len(x)
indices=list(range(m))
if if_shuffle:
np.random.shuffle(indices)
for i in range(0,m-batch_size+1,batch_size):
batch_indices=np.array(indices[i: min(i+batch_size,m)])
yield x.take(batch_indices,axis=0),y.take(batch_indices,axis=0)
def tran_nn(nn,X,Y,optimizer,loss_fun,epochs=2,batch_size=10,if_shuffle=False,print_n=100,reg=0.):
# iter_time=0
losses=[]
for epoch in range(epochs):
for x,y in data_iter(X,Y,batch_size,if_shuffle):
optimizer.zero_grad()
f=nn(x)
loss,loss_grad=loss_fun(f,y)
nn.backward(loss_grad,reg)
loss += nn.reg_loss(reg)
optimizer.step()
losses.append(loss)
if epoch%print_n==0:
f=nn(X)
loss,loss_grad=loss_fun(f,Y)
print("epoch=",epoch,"loss=",loss)
return losses
```
## 問題
NeuralNetwork._params 長什麼樣?
Ans:[[w dw],[b db]]