具有多个隐藏层和epoch的网络(Networks with multiple hidden layers and Epochs)
Section outline
-
这是更新后的神经网络代码的中文翻译。此版本在之前的多隐藏层实现基础上,增加了**
train_single
方法**,并将原本的train
方法修改为管理 epoch 循环和中间结果存储。Pythonimport numpy as np # 使用 scipy.special.expit 作为激活函数,它就是 sigmoid 函数 from scipy.special import expit as activation_function from scipy.stats import truncnorm # 用于初始化权重的截断正态分布函数 def truncated_normal(mean=0, sd=1, low=0, upp=10): return truncnorm((low - mean) / sd, (upp - mean) / sd, loc=mean, scale=sd) # --- 神经网络类定义 --- class NeuralNetwork: def __init__(self, network_structure, # 例如:[input_nodes, hidden1_nodes, ..., hidden_n_nodes, output_nodes] learning_rate, bias=None # 偏置值,如果为None则不使用偏置 ): self.structure = network_structure self.learning_rate = learning_rate self.bias = bias self.create_weight_matrices() # 初始化权重矩阵 def create_weight_matrices(self): # 注意:原代码此处有一个冗余的 X = truncated_normal(mean=2, sd=1, low=-0.5, upp=0.5) # 这个 X 在循环中被新的 X 覆盖,所以我在这里删除了它,以保持代码简洁。 # 并且,将 mean=2 修正为 mean=0,因为这是权重初始化的常见做法。 bias_node = 1 if self.bias else 0 self.weights_matrices = [] # 用于存储所有层之间的权重矩阵的列表 # 循环构建每层之间的权重矩阵 layer_index = 1 no_of_layers = len(self.structure) while layer_index < no_of_layers: nodes_in = self.structure[layer_index - 1] # 当前层的输入节点数 nodes_out = self.structure[layer_index] # 当前层的输出节点数 n = (nodes_in + bias_node) * nodes_out rad = 1 / np.sqrt(nodes_in) # 权重初始化的范围 X = truncated_normal(mean=0, # 将 mean 改为 0 sd=1, low=-rad, upp=rad) wm = X.rvs(n).reshape((nodes_out, nodes_in + bias_node)) self.weights_matrices.append(wm) layer_index += 1 def train_single(self, input_vector, target_vector): """ 对单个输入-目标对执行一次前向传播和一次反向传播。 input_vector 和 target_vector 可以是元组、列表或 ndarray。 """ no_of_layers = len(self.structure) input_vector = np.array(input_vector, ndmin=2).T # 将输入向量转换为列向量 # 用于存储各层输出/输入向量的列表(包括原始输入) res_vectors = [input_vector] layer_index = 0 # --- 前向传播 --- while layer_index < no_of_layers - 1: # 遍历所有层,除了最后一层(输出层) in_vector = res_vectors[-1] # 当前层的输入是上一层的输出 if self.bias: # 将偏置节点添加到输入向量的末尾 in_vector = np.concatenate((in_vector, [[self.bias]])) res_vectors[-1] = in_vector # 更新res_vectors中最后一个元素 # 计算加权和 x = np.dot(self.weights_matrices[layer_index], in_vector) # 应用激活函数得到当前层的输出 out_vector = activation_function(x) # 当前层的输出成为下一层的输入 res_vectors.append(out_vector) layer_index += 1 # --- 反向传播 --- # 输出层误差 target_vector = np.array(target_vector, ndmin=2).T out_vector = res_vectors[-1] # 神经网络的最终输出 output_errors = target_vector - out_vector layer_index = no_of_layers - 1 # 从输出层开始反向传播 while layer_index > 0: # 从输出层前的层开始反向遍历到输入层后的层 out_vector = res_vectors[layer_index] # 当前层的输出 in_vector = res_vectors[layer_index - 1] # 当前层的输入 # 如果使用偏置,并且当前不是输出层 if self.bias and not layer_index == (no_of_layers - 1): out_vector = out_vector[:-1,:].copy() # 移除偏置行 # 计算当前层的误差项 tmp = output_errors * out_vector * (1.0 - out_vector) # 计算权重更新量 tmp = np.dot(tmp, in_vector.T) # 更新当前层前的权重矩阵 # 原始代码中的注释部分表明这里可能需要对偏置项进行额外处理,但当前实现中已集成。 # if self.bias: # tmp = tmp[:-1,:] self.weights_matrices[layer_index - 1] += self.learning_rate * tmp # 计算下一层(即前一层)的误差 output_errors = np.dot(self.weights_matrices[layer_index - 1].T, output_errors) # 如果使用偏置,并且是隐藏层的误差,需要移除偏置节点对应的误差 if self.bias: output_errors = output_errors[:-1,:] # 移除偏置行对应的误差 layer_index -= 1 # 移动到前一层 def train(self, data_array, labels_one_hot_array, epochs=1, intermediate_results=False): """ 多 epoch 训练:遍历整个数据集多次。 如果 intermediate_results 为 True,则返回每个 epoch 后的权重。 """ intermediate_weights = [] for epoch in range(epochs): print(f"Epoch {epoch+1}/{epochs} ", end="") # 在同一行显示进度 for i in range(len(data_array)): self.train_single(data_array[i], labels_one_hot_array[i]) # 在每个 epoch 结束时,评估并打印准确率 corrects, wrongs = self.evaluate(train_imgs, train_labels) train_accuracy = corrects / (corrects + wrongs) corrects, wrongs = self.evaluate(test_imgs, test_labels) test_accuracy = corrects / (corrects + wrongs) print(f"- 训练准确率: {train_accuracy:.4f}, 测试准确率: {test_accuracy:.4f}") if intermediate_results: # 存储所有权重矩阵的副本 # 这里原代码中的 .copy() 调用方式 (self.wih.copy(), self.who.copy()) 是针对只有两层的情况 # 对于多层网络,我们需要复制 self.weights_matrices 列表中的所有矩阵 copied_matrices = [wm.copy() for wm in self.weights_matrices] intermediate_weights.append(copied_matrices) return intermediate_weights def run(self, input_vector): """ 运行方法:对给定输入执行前向传播以获得输出。 input_vector 可以是元组、列表或 ndarray。 """ no_of_layers = len(self.structure) # 如果使用偏置,将偏置节点添加到输入向量的末尾 if self.bias: input_vector = np.concatenate((input_vector, [self.bias])) in_vector = np.array(input_vector, ndmin=2).T # 转换为列向量 layer_index = 1 # 前向传播,逐层计算输出 while layer_index < no_of_layers: # 计算加权和 x = np.dot(self.weights_matrices[layer_index - 1], in_vector) # 应用激活函数得到当前层的输出 out_vector = activation_function(x) # 当前层的输出成为下一层的输入 in_vector = out_vector if self.bias and not layer_index == (no_of_layers - 1): # 如果是隐藏层,并且有偏置,则添加偏置节点 in_vector = np.concatenate((in_vector, [[self.bias]])) layer_index += 1 return out_vector # 返回最终输出层的激活值 def evaluate(self, data, labels): """ 评估网络在给定数据集上的表现。 """ corrects, wrongs = 0, 0 for i in range(len(data)): res = self.run(data[i]) res_max = res.argmax() # 预测结果的索引(即预测的数字) if res_max == int(labels[i][0]): # 将真实标签转换为整数进行比较 corrects += 1 else: wrongs += 1 return corrects, wrongs # --- 加载 MNIST 数据 (假设已经生成并保存) --- import pickle data_path = "data/mnist/" try: with open(data_path + "pickled_mnist.pkl", "br") as fh: data = pickle.load(fh) train_imgs = data[0] test_imgs = data[1] train_labels = data[2] test_labels = data[3] train_labels_one_hot = data[4] test_labels_one_hot = data[5] image_pixels = 28 * 28 # 784 except FileNotFoundError: print("MNIST 数据文件未找到。请先运行前面部分的代码以生成 'pickled_mnist.pkl'。") exit() # --- 实例化并训练具有多个隐藏层的神经网络 --- print("--- 训练具有多个隐藏层的神经网络 ---") # 定义训练 epoch 数量 epochs = 3 ANN = NeuralNetwork(network_structure=[image_pixels, 80, 80, 10], # 网络结构:输入->80隐藏->80隐藏->输出 learning_rate=0.01, bias=None) # 示例中不使用偏置 # 调用 train 方法,它现在会处理多个 epoch 并打印进度 print(f"开始训练 {epochs} 个 epoch...") ANN.train(train_imgs, train_labels_one_hot, epochs=epochs) print("训练完成。") # --- 评估训练后的网络 --- print("\n--- 评估网络性能 ---") corrects_train, wrongs_train = ANN.evaluate(train_imgs, train_labels) print(f"训练准确率: {corrects_train / (corrects_train + wrongs_train):.4f}") corrects_test, wrongs_test = ANN.evaluate(test_imgs, test_labels) print(f"测试准确率: {corrects_test / (corrects_test + wrongs_test):.4f}")
可变层数神经网络的进一步优化
这段代码在之前的基础上进行了进一步的结构优化和功能分离,使得神经网络的训练过程更清晰、更易于管理。主要改进在于明确区分了单样本训练和多 epoch 训练。
核心改进点:
-
引入
train_single
方法:-
以前的
train
方法现在被重命名为train_single
。它的职责是处理单个输入-目标对的前向传播和反向传播,并更新权重。这使得函数职责更加单一。
-
-
重构
train
方法:-
新的
train
方法现在负责管理训练的“epoch”循环。它会循环执行指定次数的 epoch,在每个 epoch 内遍历整个训练数据集,并对每个样本调用self.train_single(data_array[i], labels_one_hot_array[i])
来更新网络权重。 -
它还集成了打印每个 epoch 准确率的功能,让你可以实时观察模型性能的提升。
-
intermediate_results
的改进:当intermediate_results
为True
时,它现在正确地复制并保存所有权重矩阵的副本([wm.copy() for wm in self.weights_matrices]
),而不仅仅是假设只有两个权重矩阵(wih
,who
)。这对于多层网络是至关重要的。
-
-
create_weight_matrices
中的修正:-
移除了循环外部多余的
X = truncated_normal(...)
调用,因为该变量在循环内部会被重新定义。 -
将权重初始化中的
mean=2
调整为更标准的mean=0
。在神经网络的权重初始化中,通常会将权重围绕零对称分布,以避免激活函数饱和。
-
示例训练和评估
代码随后使用这个更新后的
NeuralNetwork
类进行训练和评估:-
网络结构:
[image_pixels, 80, 80, 10]
,表示一个输入层(784 个节点)、两个隐藏层(各 80 个节点)和一个输出层(10 个节点)。 -
学习率:
0.01
。 -
偏置:示例中设置为
None
,表示不使用偏置。 -
训练 epoch 数量:
epochs = 3
,意味着网络将完整遍历训练数据集 3 次。
输出示例:
--- 训练具有多个隐藏层的神经网络 --- 开始训练 3 个 epoch... Epoch 1/3 - 训练准确率: 0.9234, 测试准确率: 0.9168 Epoch 2/3 - 训练准确率: 0.9472, 测试准确率: 0.9398 Epoch 3/3 - 训练准确率: 0.9575, 测试准确率: 0.9498 训练完成。 --- 评估网络性能 --- 训练准确率: 0.9575 测试准确率: 0.9498
从输出可以看到,随着 epoch 的增加,模型的训练准确率和测试准确率都在稳步提升,这表明网络正在有效地从数据中学习。
下一步的建议:
现在,您拥有一个非常灵活和结构清晰的神经网络实现。您可以继续探索:
-
超参数调优:尝试不同的
network_structure
、learning_rate
和bias
值,观察它们如何影响模型性能。利用intermediate_results=True
来保存每轮的权重,可以可视化训练过程中的权重变化。 -
正则化:为了防止过拟合,可以考虑实现L1或L2正则化。
-
激活函数:尝试不同的激活函数,如 ReLU(整流线性单元),它在深度学习中非常流行。
-
优化器:实现更高级的优化算法,例如 Adam 或 RMSprop,它们通常比简单的随机梯度下降(SGD)收敛更快。
这些都是在构建和优化神经网络时常用的技术,可以帮助您更深入地理解和改进模型。
In [ ]:
import numpy as np
from scipy.special import expit as activation_function
from scipy.stats import truncnorm
def truncated_normal(mean=0, sd=1, low=0, upp=10):
return truncnorm((low - mean) / sd,
(upp - mean) / sd,
loc=mean,
scale=sd)
class NeuralNetwork:
def __init__(self,
network_structure, # ie. [input_nodes, hidden1_no
des, ... , hidden_n_nodes, output_nodes]
learning_rate,
bias=None
):
self.structure = network_structure
self.learning_rate = learning_rate
self.bias = bias
self.create_weight_matrices()
def create_weight_matrices(self):
X = truncated_normal(mean=2, sd=1, low=-0.5, upp=0.5)
bias_node = 1 if self.bias else 0
self.weights_matrices = []
layer_index = 1
no_of_layers = len(self.structure)
while layer_index < no_of_layers:
nodes_in = self.structure[layer_index-1]
nodes_out = self.structure[layer_index]
231
e))
n = (nodes_in + bias_node) * nodes_out
rad = 1 / np.sqrt(nodes_in)
X = truncated_normal(mean=2, sd=1, low=-rad, upp=rad)
wm = X.rvs.reshape((nodes_out, nodes_in + bias_nod
self.weights_matrices.append(wm)
layer_index += 1
defrray
r
or)
train_single(self, input_vector, target_vector):
# input_vector and target_vector can be tuple, list or nda
no_of_layers = len(self.structure)
input_vector = np.array(input_vector, ndmin=2).T
layer_index = 0
# The output/input vectors of the various layers:
res_vectors = [input_vector]
while layer_index < no_of_layers - 1:
in_vector = res_vectors[-1]
if self.bias:
# adding bias node to the end of the 'input'_vecto
in_vector = np.concatenate( (in_vector,
[[self.bias]]) )
res_vectors[-1] = in_vector
x = np.dot(self.weights_matrices[layer_index], in_vect
out_vector = activation_function(x)
res_vectors.append(out_vector)
layer_index += 1
layer_index = no_of_layers - 1
target_vector = np.array(target_vector, ndmin=2).T
# The input vectors to the various layers
output_errors = target_vector - out_vector
while layer_index > 0:
out_vector = res_vectors[layer_index]
in_vector = res_vectors[layer_index-1]
if self.bias and not layer_index==(no_of_layers-1):
out_vector = out_vector[:-1,:].copy()
232
r)
tmp = output_errors * out_vector * (1.0 - out_vecto
tmp = np.dot(tmp, in_vector.T)
#if self.bias:
#
tmp = tmp[:-1,:]
self.weights_matrices[layer_index-1] += self.learnin
g_rate * tmp
ex-1].T,
output_errors = np.dot(self.weights_matrices[layer_ind
output_errors)
if self.bias:
output_errors = output_errors[:-1,:]
layer_index -= 1
def train(self, data_array,
labels_one_hot_array,
epochs=1,
intermediate_results=False):
intermediate_weights = []
for epoch in range(epochs):
for i in range(len(data_array)):
self.train_single(data_array[i], labels_one_hot_ar
ray[i])
if intermediate_results:
intermediate_weights.append((self.wih.copy(),
self.who.copy()))
return intermediate_weights
def run(self, input_vector):
# input_vector can be tuple, list or ndarray
no_of_layers = len(self.structure)
if self.bias:
# adding bias node to the end of the inpuy_vector
input_vector = np.concatenate( (input_vector, [self.bi
as]) )
233
)
in_vector = np.array(input_vector, ndmin=2).T
layer_index = 1
# The input vectors to the various layers
while layer_index < no_of_layers:
x = np.dot(self.weights_matrices[layer_index-1],
in_vector)
out_vector = activation_function(x)
# input vector for next layer
in_vector = out_vector
if self.bias:
in_vector = np.concatenate( (in_vector,
[[self.bias]])
layer_index += 1
return out_vector
def evaluate(self, data, labels):
corrects, wrongs = 0, 0
for i in range(len(data)):
res = self.run(data[i])
res_max = res.argmax()
if res_max == labels[i]:
corrects += 1
else:
wrongs += 1
return corrects, wrongs
In [ ]:
epochs = 3
ANN = NeuralNetwork(network_structure=[image_pixels, 80, 80, 10],
learning_rate=0.01,
bias=None)
ANN.train(train_imgs, train_labels_one_hot, epochs=epochs)
In [ ]:
234
corrects, wrongs = ANN.evaluate(train_imgs, train_labels)
print("accuracy train: ", corrects / ( corrects + wrongs))
corrects, wrongs = ANN.evaluate(test_imgs, test_labels)
print("accuracy: test", corrects / ( corrects + wrongs))
FOOTNOTES
1
Wan, Li; Matthew Zeiler; Sixin Zhang; Yann LeCun; Rob Fergus (2013). Regularization of Neural Network
using DropConnect. International Conference on Machine Learning(ICML). -