# 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]]