目录
一、初步认识全连接神经网络
1、神经元
2、网络结构
3、正向传播算法
二、反向传播算法
1、理解
2、迭代流程
三、构建神经网络模型的基本步骤
四、线性回归神经网络结构
4.1 数据处理
1、数据导入
2、数据归一化处理
3、数据集划分
4、数据形状变换
4.2 模型设计
4.3 训练配置
4.4 训练过程
4.5 模型保存
完整代码
五、全连接神经网络实现手写数字识别代码
1、手写数字识别原理
2、MINST 数据集
3、代码实现
3.1 方法概括
3.2 数据处理
一、初步认识全连接神经网络
在多层神经网络中, 第 N 层的每个神经元都分别与第 N-1 层的神经元相互连接。
1、神经元
这个神经元接收的输入信号为向量 , 向量为输入向量的组合权重, 为偏置项, 是一个标量。
神经元的作用是对输入向量进行加权求和, 并加上偏置项 b, 最后经过激活函数f 变换产生输出: ===>
2、网络结构
在分类问题中, 神经网络一般有多层:
- 第一层为输入层, 对应输入向量, 神经元的数量等于特征向量的维数, 输入层不对数据进行处理, 只是将输入向量送入下一层中进行计算。
- 中间层为隐含层, 可能有多个。
- 最后是输出层, 神经元的数量等于要分类的类别数, 输出层的输出值被用来做分类预测
- 第一层是输入层,对应的输入向量为 , 有 3 个神经元, 输入层不对数据做任何处理, 直接原样送入下一层。
- 中间层有 4 个神经元, 接收的数据为 , 输出向量为 。
- 第三层为输出层, 接收的数据为向量 , 输出向量为
神经网络通过激活函数而具有非线性, 通过调整权重形成不同的映射函数。现实应用中要拟合的函数一般是非线性的, 线性函数无论怎样复合最终还是线性函数, 因此, 必须使用非线性激活函数。
3、正向传播算法
算法公式:,
假设神经网络有 m 层, 正向传播算法的流程为:
二、反向传播算法
1、理解
假设神经网络有 层, 第 层的神经元个数为 。 第 层从第 层接收的输入向量为 , 第 层的权重矩阵为 , 偏置向量为, 输出向量为 。 该层的输出可以写成如下形式:
(已有推导结论: , )
损失函数对权重和偏置的梯度:
发现和中都存在,因此定义误差项为:
即得到递推公式为:
通过递推公式知由计算得到,因此需要从后往前计算,当计算出来时和也可计算出来,这就称为反向传播
2、迭代流程
均按照一个样本迭代,若有m个样本,公式要除以m,以求平均梯度
- 正向传播, 利用当前权重和偏置值, 计算每一层的输出值--确定
- 计算输出层的误差项 。
- 反向传播, 计算隐藏层各层的误差项:
⊙ 表示元素乘法,前边表示矩阵乘法- 计算损失函数对权重和偏置的梯度:
- 用梯度下降法更新权重和梯度:
如果采用所有样本进行迭代, 根据单个样本损失函数的梯度计算总梯度, 然后求均值即可。
3、常见激活函数
根据上边公式:
是激活函数的导数,因此若激活函数倒数好计算,误差项计算就会容易。
下面是几种常用的激活函数及它们的导数:
三、构建神经网络模型的基本步骤
四、线性回归神经网络结构
线性回归模型可以认为是神经网络模型的一种极简特例, 是一个神经元,其只有加权和、没有非线性变换。
4.1 数据处理
1、数据导入
从本地文件或 URL 中读取数据。
data_list = pd.read_csv(filepath)
2、数据归一化处理
使用Z-Score 归一化
data_list = (data_list - data_list.mean()) / data_list.std()
3、数据集划分
将数据集划分成训练集和测试集, 其中训练集用于确定模型的参数, 测试集用于评判模型的效果。
train_size = int(len(data_list) * ratio)
# 生成一个随机排列的整数数组
random_indices = np.random.permutation(len(data_list))
# 使用随机排列的索引列表重新设定 DataFrame 的行顺序
data_list = data_list.iloc[random_indices]
trainset = data_list[:train_size]
4、数据形状变换
如果读取的数据维数与我们的模型输入的输入维数不一致, 我们就需要进行数据形状的变换
X_train = X_train.values
y_train = trainset["MEDV"]
y_train = y_train.values.reshape(-1, 1) #1维变2维,-1表示自动计算行数,1表示列数
X_test = testset.drop("MEDV", axis=1)
X_test = X_test.values
y_test = testset["MEDV"]
y_test = y_test.values.reshape(-1, 1)
4.2 模型设计
如果将输入特征和输出预测值均以向量表示, 输入特征x有 13 个分量,y有 1 个分量, 那么参数权重的形状是 13× 1:
class Network(object):def __init__(self, num_of_weights):# 随机产生 w 的初始值# 为了保持程序每次运行结果的一致性, 此处设置固定的随机数种子np.random.seed(0)self.w = np.random.randn(num_of_weights, 1) #数学中向量数据是按列存储的self.b = 0def forward(self, x):z = np.dot(x, self.w) + self.breturn z
4.3 训练配置
通过损失函数平衡模型的好坏
def loss(self, z, y):error = z - ycost = error * errorcost = np.mean(cost)return cost
4.4 训练过程
使用梯度下降法寻找最优W和b
def gradient(self, x, y):z = self.forward(x)gradient_w = (z - y) * x #(z - y)是m*1 x是m*ngradient_w = np.mean(gradient_w, axis=0) #批量梯度下降,对各个样本取得权重求均值gradient_w = gradient_w[:, np.newaxis] #变成列向量gradient_b = (z - y)gradient_b = np.mean(gradient_b)return gradient_w, gradient_bdef update(self, gradient_w, gradient_b, alpha=0.01):self.w = self.w - alpha * gradient_wself.b = self.b - alpha * gradient_b
训练模型
def train(self, x, y, iterations=100, alpha=0.01):losses = []for i in range(iterations):z = self.forward(x)L = self.loss(z, y)gradient_w, gradient_b = self.gradient(x, y)self.update(gradient_w, gradient_b, alpha)losses.append(L)if (i + 1) % 10 == 0:print('iter {}, loss {}'.format(i, L))return losses
调用函数进行模型训练
# 创建网络
net = Network(13)
num_iterations = 1000
# 启动训练
losses = net.train(X_train, y_train, iterations=num_iterations,
alpha=0.01)
# 画出损失函数的变化趋势
plot_x = np.arange(num_iterations)
plot_y = np.array(losses)
plt.plot(plot_x, plot_y)
plt.show()
4.5 模型保存
将训练好的模型保存用于后续的预测过程。 当需要进行预测时使用训练好的模型进行预测。
# 模型保存
params = {"w": net.w.tolist(),"b": net.b
} #将参数保存到文件
with open('params.json', 'w') as f:json.dump(params, f, indent=4)# 使用模型进行预测
net = Network(13)
with open('params.json', 'r') as f:params = json.load(f)net.w = np.array(params["w"])
net.b = params["b"]z = net.forward(X_test)
绘制图像
# 绘制预测值与真实值的关系图
x = np.arange(len(y_test))
fig, ax = plt.subplots()
ax.plot(x, z, label='predic', color='red')
ax.plot(x, y_test, label='label', color='blue')
ax.legend()
plt.show()
完整代码
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import json
import random
# pip install pandas matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple# 1.数据处理
# 载入数据集
def loadData(filepath):""":param filepath: csv:return: X, y"""data_list = pd.read_csv(filepath)# 使用Z-score对数据进行归一化处理data_list = (data_list - data_list.mean()) / data_list.std()return data_listdata_list = loadData('housing.csv')# 划分训练集与测试集
def splitData(data_list, ratio):train_size = int(len(data_list) * ratio)# 生成一个随机排列的整数数组random_indices = np.random.permutation(len(data_list))# 使用随机排列的索引列表重新设定 DataFrame 的行顺序data_list = data_list.iloc[random_indices]trainset = data_list[:train_size]testset = data_list[train_size:]X_train = trainset.drop("MEDV", axis=1)X_train = X_train.valuesy_train = trainset["MEDV"]y_train = y_train.values.reshape(-1, 1)X_test = testset.drop("MEDV", axis=1)X_test = X_test.valuesy_test = testset["MEDV"]y_test = y_test.values.reshape(-1, 1)return X_train, X_test, y_train, y_testX_train, X_test, y_train, y_test = splitData(data_list, 0.8)class Network(object):def __init__(self, num_of_weights):# 随机产生w的初始值# 为了保持程序每次运行结果的一致性,此处设置固定的随机数种子np.random.seed(0)self.w = np.random.randn(num_of_weights, 1)self.b = 0.def forward(self, x):z = np.dot(x, self.w) + self.breturn zdef loss(self, z, y):error = z - ynum_samples = error.shape[0]cost = error * errorcost = np.sum(cost) / num_samplesreturn costdef gradient(self, x, y):z = self.forward(x)gradient_w = (z - y) * xgradient_w = np.mean(gradient_w, axis=0)gradient_w = gradient_w[:, np.newaxis]gradient_b = (z - y)gradient_b = np.mean(gradient_b)return gradient_w, gradient_bdef update(self, gradient_w, gradient_b, alpha=0.01):self.w = self.w - alpha * gradient_wself.b = self.b - alpha * gradient_bdef train(self, x, y, iterations=100, alpha=0.01):losses = []for i in range(iterations):z = self.forward(x)L = self.loss(z, y)gradient_w, gradient_b = self.gradient(x, y)self.update(gradient_w, gradient_b, alpha)losses.append(L)if (i + 1) % 10 == 0:print('iter {}, loss {}'.format(i, L))return lossesdef SGD_train(self, x, y, num_epochs, batch_size=64, alpha=0.01):indices = list(range(len(x)))random.shuffle(indices) # 打乱数据索引,以便随机选择样本losses = []for epoch_id in range(num_epochs):# 在每轮迭代开始之前,将训练数据的顺序随机打乱random.shuffle(indices) # 打乱数据索引,以便随机选择样本# 将训练数据进行拆分,每个mini_batch包含batch_size条的数据loss = 0for start in range(0, len(x), batch_size):end = min(start + batch_size, len(x))batch_indices = indices[start:end]X_batch = x[batch_indices]y_batch = y[batch_indices]a = self.forward(X_batch)L = self.loss(a, y_batch)loss += Lgradient_w, gradient_b = self.gradient(X_batch, y_batch)self.update(gradient_w, gradient_b, alpha)print('Epoch {} / iter {}, loss = {}'.format(epoch_id, start+1, L))losses.append(loss)return losses# 创建网络
net = Network(13)
num_iterations = 1000
# 启动训练
losses = net.train(X_train, y_train, iterations=num_iterations, alpha=0.01)
# losses = net.SGD_train(X_train, y_train, num_epochs=num_iterations, batch_size=64, alpha=0.01)# 画出损失函数的变化趋势
plot_x = np.arange(num_iterations)
plot_y = np.array(losses)
plt.plot(plot_x, plot_y)
plt.show()# 模型保存
params = {"w": net.w.tolist(),"b": net.b
}# 将参数保存到文件
with open('params.json', 'w') as f:json.dump(params, f, indent=4)# 使用模型进行预测
net = Network(13)
with open('params.json', 'r') as f:params = json.load(f)net.w = np.array(params["w"])
net.b = params["b"]z = net.forward(X_test)# 绘制预测值与真实值的关系图
x = np.arange(len(y_test))
fig, ax = plt.subplots()
ax.plot(x, z, label='predic', color='red')
ax.plot(x, y_test, label='label', color='blue')
ax.legend()
plt.show()
五、全连接神经网络实现手写数字识别代码
1、手写数字识别原理
计算机存储数据的最小单位是 bit, 它只有 0 和 1 两种二进制状态, 所以任何数据在计算机中都是以 0 和 1 组成的一堆数字。 那如何让计算机认出图片中的数字是几呢? 灰度图像是一种只包含灰度级别而不包含彩色信息的图像, 灰度级别通常被分为 256 个等级, 即从全黑(灰度值 0) 到全白(灰度值 255), 使用 8位二进制数可以表示从 0 到 255 的 256 个不同的数值, 因此用一个 8 位二进制数表示一个像素。 如下图所示把 0 以外的点连接起来就能看出图片中的是数字 4。
2、MINST 数据集
MNIST 数据集是美国国家标准与技术研究院收集整理的大型手写数字数据库,包含 60,000 个示例的训练集以及 10,000 个示例的测试集。 其中的图像的尺寸为 28*28。 采样数据显示如下:
MNIST 图像文件有一个特定的文件头, 其中包含一些元数据, 这个文件头由 16 字节组成, 它们分别表示:
- magic_number: 一个魔数, 用于标识文件格式。 对于图像文件, 这个值通常是 0x00000803;
- num_images: 文件中的图像数量;
- num_rows: 每张图像的行数(即高度);
- num_cols: 每张图像的列数(即宽度)。
3、代码实现
3.1 方法概括
使用一个 3 层的全连接神经网络实现手写数字识别, 输入层神经原个数为 784(也就是图片的像素数 28× 28), 隐藏层的神经元个数为 128, 输出层的神经元个数为 10(数字从 0~9 的个数)。 隐藏层的激活函数使用 ReLU, 输出层的激活函数为 softmax。 分别使用批量梯度下降法和随机梯度下降法进行参数优化。
3.2 数据处理
# 读取MNIST数据集
train_images = idx2numpy.convert_from_file('./MNIST/raw/train-images-idx3-ubyte')
train_labels = idx2numpy.convert_from_file('./MNIST/raw/train-labels-idx1-ubyte')
test_images = idx2numpy.convert_from_file('./MNIST/raw/t10k-images-idx3-ubyte')
test_labels = idx2numpy.convert_from_file('./MNIST/raw/t10k-labels-idx1-ubyte')# 将图像数据转换为一维向量,并归一化
X_train = train_images.reshape(train_images.shape[0], -1) / 255.0
X_test = test_images.reshape(test_images.shape[0], -1) / 255.0# 将标签进行one-hot编码
num_classes = 10
y_train = np.eye(num_classes)[train_labels]
y_test = np.eye(num_classes)[test_labels]
one-hot讲解
3.3 模型设计
反向传播初始误差项跟所选损失函数有关
- 使用均方误差(MSE)损失函数对应初始误差项 : 是输出层的激活值, 是真实标签
- 使用交叉熵损失函数对应初始误差项 : 是真实标签的one-hot编码
class NeuralNetwork:def __init__(self, input_size, hidden_size, output_size):self.input_size = input_sizeself.hidden_size = hidden_sizeself.output_size = output_size# 初始化权重self.W1 = np.random.randn(self.input_size, self.hidden_size)self.b1 = np.zeros((1, self.hidden_size))self.W2 = np.random.randn(self.hidden_size, self.output_size)self.b2 = np.zeros((1, self.output_size))def forward(self, X):# 前向传播self.z1 = np.dot(X, self.W1) + self.b1self.a1 = np.maximum(0, self.z1) # ReLU激活函数self.z2 = np.dot(self.a1, self.W2) + self.b2exp_scores = np.exp(self.z2) # softmaxself.probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)return self.probsdef backward(self, X, y, learning_rate=0.01):# 反向传播m = X.shape[0]delta3 = self.probs - ydW2 = np.dot(self.a1.T, delta3) / mdb2 = np.sum(delta3, axis=0, keepdims=True) / mdelta2 = np.dot(delta3, self.W2.T) * (self.a1 > 0) #(self.a1 > 0)表示Relu激活函数求导结果,要么是0要么是1dW1 = np.dot(X.T, delta2) / mdb1 = np.sum(delta2, axis=0, keepdims=True) / m# 更新权重self.W1 -= learning_rate * dW1self.b1 -= learning_rate * db1self.W2 -= learning_rate * dW2self.b2 -= learning_rate * db2
3.4 训练配置
定义损失函数
- 在回归问题中常用均方误差作为损失函数;
- 在分类问题中常用采用交叉熵(Cross-Entropy) 作为损失函数。
def calculate_loss(self, X, y):# 计算softmax交叉熵损失log_probs = -np.log(self.probs[range(X.shape[0]), np.argmax(y, axis=1)])return np.mean(log_probs)
常用损失函数
3.5 训练过程
# 梯度下降法训练def train(self, X, y, num_epochs=1000, learning_rate=0.01):for epoch in range(num_epochs):# 前向传播和反向传播probs = self.forward(X)self.backward(X, y, learning_rate)loss = self.calculate_loss(X, y)print(f"Epoch {epoch}, Loss: {loss}")# 随机梯度下降法训练def train_SGD(self, X, y, num_epochs=1000, learning_rate=0.01, batch_size=64): # 增加了batch_size参数indices = list(range(len(X)))random.shuffle(indices) # 打乱数据索引,以便随机选择样本for epoch in range(num_epochs):# 使用随机顺序遍历数据集for start in range(0, len(X), batch_size):end = min(start + batch_size, len(X))batch_indices = indices[start:end]X_batch = X[batch_indices]y_batch = y[batch_indices]# 前向传播和反向传播probs = self.forward(X_batch)self.backward(X_batch, y_batch, learning_rate)if (start // batch_size) % 100 == 0:loss = self.calculate_loss(X_batch, y_batch)print(f"Epoch [{epoch + 1}/{num_epochs}], step {start + 1}/{len(X)}, Loss: {loss}")
随机梯度下降算法
- 梯度下降法计算一步梯度需要对所有样本进行计算
- 随机梯度下降法(SGD)每次迭代都不使用整个数据集, 而是只选择小批量随机训练样本来计算梯度并更新模型参数。核心概念如下:
1)minibatch: 每次迭代时抽取出来的一批数据被称为一个 minibatch。
2)batch_size: 每个 minibatch 所包含的样本数目称为 batch_size。
3)Epoch: 当程序迭代的时候, 按 minibatch 逐渐抽取出样本, 当把整个数据集都遍历到了的时候, 则完成了一轮训练, 也叫一个 Epoch(轮次)。 启动训练时,可以将训练的轮数 num_epochs 和 batch_size 作为参数传入。
随机梯度下降算法的过程如下:
3.6 模型保存
# 模型保存
params = {"W1": nn.W1.tolist(),"b1": nn.b1.tolist(),"W2": nn.W2.tolist(),"b2": nn.b2.tolist()
}
# 将参数保存到文件
with open('fcn_params.json', 'w') as f:json.dump(params, f, indent=4)
4、手动实现代码(完整版)
import numpy as np
import idx2numpy # 用于读取MNIST数据集
from sklearn.metrics import accuracy_score
import random
import json
import matplotlib.pyplot as plt# 读取MNIST数据集
train_images = idx2numpy.convert_from_file('./MNIST/raw/train-images-idx3-ubyte')
train_labels = idx2numpy.convert_from_file('./MNIST/raw/train-labels-idx1-ubyte')
test_images = idx2numpy.convert_from_file('./MNIST/raw/t10k-images-idx3-ubyte')
test_labels = idx2numpy.convert_from_file('./MNIST/raw/t10k-labels-idx1-ubyte')# num_images_to_display = 5
# # for i in range(num_images_to_display):
# # plt.imshow(train_images[i], cmap='gray')
# # plt.title(f"Label: {train_labels[i]}")
# # plt.axis('off')
# # plt.show()# 将图像数据转换为一维向量,并归一化
#第二个维度 -1 表示将所有剩余的维度(即 height 和 width)展平成一维。因此,每个图像将被转换为一个长度为 height * width 的一维数组。
X_train = train_images.reshape(train_images.shape[0], -1) / 255.0
X_test = test_images.reshape(test_images.shape[0], -1) / 255.0# 将标签进行one-hot编码
num_classes = 10
y_train = np.eye(num_classes)[train_labels]
y_test = np.eye(num_classes)[test_labels]class NeuralNetwork:def __init__(self, input_size, hidden_size, output_size):self.input_size = input_sizeself.hidden_size = hidden_sizeself.output_size = output_size# 初始化权重self.W1 = np.random.randn(self.input_size, self.hidden_size)self.b1 = np.zeros((1, self.hidden_size))self.W2 = np.random.randn(self.hidden_size, self.output_size)self.b2 = np.zeros((1, self.output_size))def forward(self, X):# 前向传播self.z1 = np.dot(X, self.W1) + self.b1self.a1 = np.maximum(0, self.z1) # ReLU激活函数self.z2 = np.dot(self.a1, self.W2) + self.b2exp_scores = np.exp(self.z2) # softmaxself.probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)return self.probsdef backward(self, X, y, learning_rate=0.01):# 反向传播m = X.shape[0]delta3 = self.probs - ydW2 = np.dot(self.a1.T, delta3) / mdb2 = np.sum(delta3, axis=0, keepdims=True) / mdelta2 = np.dot(delta3, self.W2.T) * (self.a1 > 0) #(self.a1 > 0)表示Relu激活函数求导结果,要么是0要么是1dW1 = np.dot(X.T, delta2) / mdb1 = np.sum(delta2, axis=0, keepdims=True) / m# 更新权重self.W1 -= learning_rate * dW1self.b1 -= learning_rate * db1self.W2 -= learning_rate * dW2self.b2 -= learning_rate * db2def calculate_loss(self, X, y):# 计算softmax交叉熵损失log_probs = -np.log(self.probs[range(X.shape[0]), np.argmax(y, axis=1)])return np.mean(log_probs)# 梯度下降法训练def train(self, X, y, num_epochs=1000, learning_rate=0.01):for epoch in range(num_epochs):# 前向传播和反向传播probs = self.forward(X)self.backward(X, y, learning_rate)loss = self.calculate_loss(X, y)print(f"Epoch {epoch}, Loss: {loss}")# 随机梯度下降法训练def train_SGD(self, X, y, num_epochs=1000, learning_rate=0.01, batch_size=64): # 增加了batch_size参数indices = list(range(len(X)))random.shuffle(indices) # 打乱数据索引,以便随机选择样本for epoch in range(num_epochs):# 使用随机顺序遍历数据集for start in range(0, len(X), batch_size):end = min(start + batch_size, len(X))batch_indices = indices[start:end]X_batch = X[batch_indices]y_batch = y[batch_indices]# 前向传播和反向传播probs = self.forward(X_batch)self.backward(X_batch, y_batch, learning_rate)if (start // batch_size) % 100 == 0:loss = self.calculate_loss(X_batch, y_batch)print(f"Epoch [{epoch + 1}/{num_epochs}], step {start + 1}/{len(X)}, Loss: {loss}")# 定义网络结构和参数
input_size = 784 # 28*28像素
hidden_size = 128
output_size = 10
batch_size = 64# 创建神经网络实例
nn = NeuralNetwork(input_size, hidden_size, output_size)# 训练模型
nn.train_SGD(X_train, y_train, num_epochs=1, learning_rate=0.01, batch_size=batch_size)
# nn.train(X_train, y_train, num_epochs=1, learning_rate=0.01)# 模型保存
params = {"W1": nn.W1.tolist(),"b1": nn.b1.tolist(),"W2": nn.W2.tolist(),"b2": nn.b2.tolist()
}
# 将参数保存到文件
with open('fcn_params.json', 'w') as f:json.dump(params, f, indent=4)# 使用模型进行预测
nn = NeuralNetwork(input_size, hidden_size, output_size)
with open('fcn_params.json', 'r') as f:params = json.load(f)nn.W1 = np.array(params["W1"])
nn.b1 = np.array(params["b1"])
nn.W2 = np.array(params["W2"])
nn.b2 = np.array(params["b2"])# 预测并评估模型
test_predictions = np.argmax(nn.forward(X_test), axis=1)test_accuracy = accuracy_score(np.argmax(y_test, axis=1), test_predictions)print(f"Test Accuracy: {test_accuracy:.4f}")
5、pytorch实现代码(完整版)
# python --version 3.8.10
# PyTorch --version 2.3.1
# torchvision --version 0.18.1
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms# 定义超参数
input_size = 784 # 28x28
hidden_size = 128
num_classes = 10
num_epochs = 1
batch_size = 64
learning_rate = 0.001# 加载和预处理数据
train_dataset = datasets.MNIST(root='./', train=True, transform=transforms.ToTensor(), download=False)
test_dataset = datasets.MNIST(root='./', train=False, transform=transforms.ToTensor(), download=False)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)# 定义全连接神经网络
class NeuralNet(nn.Module):def __init__(self, input_size, hidden_size, num_classes):super(NeuralNet, self).__init__()self.fc1 = nn.Linear(input_size, hidden_size)self.relu = nn.ReLU()self.fc2 = nn.Linear(hidden_size, num_classes)def forward(self, x):out = self.fc1(x)out = self.relu(out)out = self.fc2(out)return out# 训练模型
def train(model, train_loader, optimizer, criterion, num_epochs):for epoch in range(num_epochs):for i, (images, labels) in enumerate(train_loader):# 前向传播outputs = model.forward(images.reshape(-1, 28 * 28))loss = criterion(outputs, labels)# 反向传播和优化optimizer.zero_grad()loss.backward()optimizer.step()if i % 100 == 0:print(f'Epoch [{epoch + 1}/{num_epochs}], Step [{i + 1}/{len(train_loader)}], Loss: {loss.item()}')# 测试模型
def predict(model, test_loader):model.eval() # 设置为评估模式correct = 0total = 0with torch.no_grad():for images, labels in test_loader:outputs = model.forward(images.reshape(-1, 28 * 28))_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()print(f'Accuracy of the network on the 10000 test images: {100 * correct / total} %')model = NeuralNet(input_size, hidden_size, num_classes)
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
# optim.Adam
optimizer = optim.Adam(model.parameters(), lr=learning_rate) # SGD
# 训练模型
train(model, train_loader, optimizer, criterion, num_epochs=num_epochs)# 保存模型
torch.save(model.state_dict(), 'fcn_state_dict.pth')# 加载模型
model.load_state_dict(torch.load('fcn_state_dict.pth'))# 测试模型
predict(model, test_loader)