1 实验2 前馈神经网络 BJTU
实验要求:
(1) 手动生成实现前馈神经网络解决回归、二分类、多分类任务
(2) 利用torch.nn实现前馈神经网络解决回归、二分类、多分类任务
(3) 在多分类任务中使用至少三种不同的激活函数
(4) 分析多分类任务中的模型评估隐藏层层数和隐藏单元个数对实验结果的影响
运行环境说明:我的运行环境是jupiter notebook.
如果需要咨询诸如安装GPU版本的torchvision、创建虚拟环境并在虚拟环境中下载深度学习等需要的库,并最终创建一个kernal供jupternotebook使用、jupiter notebook实用小技巧等操作,可以查看我这个专栏的其他文章。
2 Pytorch和Torchvision的安装与版本检验
import torch
print(torch.__version__)
print(torch)
将这个代码块运行后会打印出你的torch是否可用以及版本号。
import torchvision
print(torchvision.__version__)
这个代码块会打印出的你的torchvision使用的版本
下面是我的打印的样子,仅供参考:
3 手动实现前馈神经网络解决回归、二分类、多分类问题
3.1 辅助绘图函数说明
-
np.linspace
:np.linspace(start, stop, num)
用于创建一个等间隔的数字序列。- 参数
start
是起始值,stop
是结束值,num
是生成的样本数量。 np.linspace(0, len(train_loss), len(test_loss))
用于生成 x 轴的值,确保 x 轴与训练损失和测试损失的长度相同。
-
plt.plot
:plt.plot(x, y, label, linewidth)
用于绘制二维数据图形。- 它被用来绘制训练损失和测试损失曲线。
x
是 x 轴的值,y
是 y 轴的值,label
是图例标签,linewidth
是线的宽度。
-
plt.xlabel
:plt.xlabel(label)
用于为 x 轴添加标签。- 它被用来为 x 轴添加标签,标明 x 轴表示的是迭代次数(Epoch)。
-
plt.legend
:plt.legend()
用于在图形中添加图例,以便区分不同的数据系列。- 它被用来添加训练损失和测试损失的图例,使得图形中的两条曲线可以被正确标识。
-
plt.show
:plt.show()
用于显示图形。- 在这个代码中,它被用来显示绘制的训练损失和测试损失曲线图。
这些函数结合在一起,使得可以绘制损失曲线图,并通过标签、轴标签和图例等元素使图形更加清晰易懂。
3.2 前馈神经网络解决回归问题
3.2.1 引入库
import time # 引入时间模块
import matplotlib.pyplot as plt # 引入matplotlib库用于绘图
import numpy as np # 引入numpy库进行数值操作
import torch # 引入PyTorch深度学习框架
import torch.nn as nn # 引入PyTorch中的神经网络模块
import torchvision # 引入PyTorch的计算机视觉库
from torch.nn.functional import cross_entropy, binary_cross_entropy
# 从PyTorch的functional模块中引入交叉熵和二元交叉熵函数
from torch.nn import CrossEntropyLoss
# 引入PyTorch中的多分类交叉熵损失函数
from torchvision import transforms
# 引入PyTorch的图像转换模块
from sklearn import metrics
# 引入scikit-learn库中的评估指标模块
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
说明:运行这个代码块,定义了你的device。
如果你的torchvision是GPU版本,那么你的打印结果是’cuda’;如果你的torchvision是CPU版本,那么打印的就是cpu。
这里我推荐使用GPU版本的torchvision,训练的时候电脑风扇转得慢一点,而且一般情况下训练的也比较快。
关于如何安装GPU版本的torchvision我在专栏的其他文章专门写了,欢迎查找!
3.2.2 手动生成回归任务的数据集
我们的要求如下:
从这里可以看到
回归任务的数据集手动生成的要求总共有三点
生成单个数据集
数据记得大小
数据集的样本特征维度
# 设置样本数量
data_num, train_num, test_num = 10000, 7000, 3000
# 设置线性回归模型的真实权重和偏差
regression_weight, regression_bias = 0.0056 * torch.ones(500, 1), 0.028
# 别名,简化后续代码
r_w, r_b = regression_weight, regression_bias
# 生成随机特征数据,形状为 (10000, 500)
features = torch.randn(10000, 500)
# 使用线性回归模型生成标签
labels = torch.matmul(features, r_w) + r_b
# 在标签上添加高斯噪声,模拟真实世界情况
labels = labels + torch.tensor(np.random.normal(0, 0.01, size=labels.size()),dtype=torch.float32)
声明三个变量,data_num.train_num,test_num,初始化并分别赋予10000,7000,3000的初值
我们因为regression_weight,regression_bias的变量名字过于详尽且不好书写,简化为r_w,r_b
最后添加了高斯噪声,更能模拟真实情况
np.random.normal(0, 0.01, size=labels.size()): 这部分使用NumPy生成了一个形状与 labels 相同的张量,其中的元素是从均值为0,标准差为0.01的高斯分布中随机抽样得到的。这样生成的数组模拟了一些高斯噪声。
torch.tensor(…, dtype=torch.float32): 将上述生成的NumPy数组转换为PyTorch张量,并确保其数据类型为 torch.float32。
labels =lables+ …: 将上述生成的噪声张量加到原始的 labels 中。这一步是为了在标签上引入一些随机性,模拟真实世界中的噪声或不确定性。
# 划分训练集和测试集特征数据
train_feat_0, test_feat_0 = features[:train_num, :], features[train_num:, :]
# 划分训练集和测试集标签数据
train_lab_0, test_lab_0 = labels[:train_num], labels[train_num:]
# 设置批处理大小
batch_size = 128
# 创建训练集的 PyTorch 数据集
train_dataset_0 = torch.utils.data.TensorDataset(train_feat_0, train_lab_0)
#创建测试集的 PyTorch数据集
test_dataset_0 = torch.utils.data.TensorDataset(test_feat_0, test_lab_0)
# 创建训练集的 PyTorch 数据加载器,设置批处理大小和打乱数据顺序
train_dataloader_0 = torch.utils.data.DataLoader(dataset=train_dataset_0,
batch_size=batch_size,
shuffle=True)
# 创建测试集的 PyTorch 数据加载器,设置批处理大小和打乱数据顺序
test_dataloader_0 = torch.utils.data.DataLoader(dataset=test_dataset_0,
batch_size=batch_size,
shuffle=True)
3.2.3 变量名字以及一些函数运用的解释
针对划分数据集和测试集特征数据的句式解释:
因为features是一个二维的向量或者在我们看来是矩阵,逗号的作用是隔开选择的维度,拿features[:train_num,:]举例子,逗号的前面是选择的行,意思是选择第一列到train_num-1行,逗号后面的冒号的意思是选择这些行的每一列
在NumPy(或类似的数组操作库)中,数据通常是多维数组。每个维度都是一个轴(axis),而每个轴上的索引对应于该维度上的一个位置。在这种情况下,逗号,
用于分隔不同维度的切片范围,也就是说,它用于同时指定多个维度上的切片。
逗号分割举例
考虑一个二维数组的情况,其中有两个维度,分别是行和列。逗号,
就是用来分隔这两个维度的。例如,对于一个数组arr
:
arr = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
我们可以使用逗号,
来选择数组的不同部分。例如:
# 选择所有行的第一个列
column_1 = arr[:, 0]
# 选择第一行的所有列
row_1 = arr[0, :]
# 选择前两行和前两列的交叉部分
top_left_subarray = arr[:2, :2]
在这里,逗号,
分隔了行和列的切片范围。对于arr[:, 0]
,:
表示选择所有行,然后,
后面的0
表示选择第一个列。对于arr[0, :]
,0
表示选择第一行,,
后面的:
表示选择该行的所有列。在arr[:2, :2]
中,:2
表示选择前两行,,
后面的:2
表示选择前两列。
这种方式可以推广到更高维度的数组,其中逗号,
用于分隔每个维度上的切片范围。
python切片举例:
切片(slicing)是对数组或列表等序列类型数据进行选取子集的一种灵活的方式。在Python中,切片的基本规则如下:
-
基本切片语法:
start:stop:step
start
:起始位置,表示切片开始的索引(包含在切片内)。stop
:结束位置,表示切片结束的索引(不包含在切片内)。step
:步长,表示每次跳跃的索引数。
-
默认值:
- 如果未指定
start
,默认为序列的起始位置。 - 如果未指定
stop
,默认为序列的结束位置。 - 如果未指定
step
,默认为1。
- 如果未指定
下面通过例子来说明:
# 创建一个示例列表
my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 基本切片示例
subset = my_list[2:7] # 从索引2到索引6(不包含7)
print(subset) # 输出: [2, 3, 4, 5, 6]
# 使用步长示例
subset_step = my_list[1:8:2] # 从索引1到索引7,每隔2个取一个
print(subset_step) # 输出: [1, 3, 5, 7]
# 默认值示例
subset_default = my_list[::3] # 从头到尾,每隔3个取一个
print(subset_default) # 输出: [0, 3, 6, 9]
对于多维数组,切片规则可以应用到每一个维度。之前提到的[:2, :2]
的例子就是这种情况。
import numpy as np
# 创建一个示例二维数组
my_array = np.array([
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
])
# 二维数组切片示例
subarray = my_array[:2, :2] # 选择前两行和前两列的交叉部分
print(subarray)
# 输出:
# [[0 1]
# [3 4]]
这里的[:2, :2]
表示在第一个维度(行)上选择前两行,在第二个维度(列)上选择前两列。
3.2.4验证数据集
print(train_feat_0[0][:5])
print(train_lab_0[0])
这是我的结果,仅供参考
3.2.5 主体函数
#函数主体
class Net_regression():
def __init__(self):
#设置隐藏层和输出的节点数
num_inputs, num_outputs, num_hiddens = 500, 1, 256
w_1 = torch.tensor(np.random.normal(0, 0.01,
(num_hiddens, num_inputs)),
dtype=torch.float32,
requires_grad=True)
b_1 = torch.zeros(num_hiddens, dtype=torch.float32, requires_grad=True)
w_2 = torch.tensor(np.random.normal(0, 0.01,
(num_outputs, num_hiddens)),
dtype=torch.float32,
requires_grad=True)
b_2 = torch.zeros(num_outputs, dtype=torch.float32, requires_grad=True)
self.params = [w_1, b_1, w_2, b_2]
#这句代码确保在训练过程中能够计算和更新神经网络参数的梯度,以便通过梯度下降等优化算法进行模型参数的调整。
for param in self.params:
param.requires_grad = True
#定义模型结构
self.input_layer = lambda x: x.view(x.shape[0], -1)
self.hidden_layer = lambda x: self.my_Relu(
torch.matmul(x, w_1.t()) + b_1)
self.output_layer = lambda x: torch.matmul(x, w_2.t()) + b_2
def my_Relu(self, x):
return torch.max(input=x, other=torch.tensor(0.0))
def forward(self, x):
flatten_input = self.input_layer(x)
hidden_output = self.my_Relu(self.hidden_layer(flatten_input))
final_output = self.output_layer(hidden_output)
return final_output
def mse(pred, true):
ans = torch.sum((true - pred)**2) / len(pred)
return ans
def mySGD(params, lr, batchsize):
for param in params:
param.data -= lr * param.grad / batchsize
下面是进行训练的代码块
# 训练
model_regression = Net_regression()
criterion = CrossEntropyLoss() # 损失函数
lr = 0.05 # 学习率
batchsize = 128
epochs = 100 #训练轮数
train_all_loss1 = [] # 记录训练集上得loss变化
test_all_loss1 = [] #记录测试集上的loss变化
begintime1 = time.time()
for epoch in range(epochs):
train_l = 0
for data, labels in train_dataloader_0:
pred = model_regression.forward(data)
train_each_loss = mse(pred.view(-1, 1), labels.view(-1, 1)) #计算每次的损失值
train_each_loss.backward() # 反向传播
mySGD(model_regression.params, lr, batchsize) # 使用小批量随机梯度下降迭代模型参数
# 梯度清零
train_l += train_each_loss.item()
for param in model_regression.params:
param.grad.data.zero_()
# print(train_each_loss)
train_all_loss1.append(train_l) # 添加损失值到列表中
with torch.no_grad():
test_loss = 0
for data, labels in train_dataloader_0:
pred = model_regression.forward(data)
test_each_loss = mse(pred, labels)
test_loss += test_each_loss.item()
test_all_loss1.append(test_loss)
if epoch == 0 or (epoch + 1) % 1 == 0:
print('Epoch: %d | Train Loss:%.2f | Test Loss:%.2f' %
(epoch + 1, train_all_loss1[-1], test_all_loss1[-1]))
endtime1 = time.time()
print("前馈网络完成回归实验 %d轮 总用时: %.3fs" % (epochs, endtime1 - begintime1))
3.2.6 绘图辅助函数
#定义辅助函数
def draw_loss(train_loss, test_loss):
x = np.linspace(0, len(train_loss), len(test_loss))
plt.plot(x, train_loss, label="Train Loss", linewidth=1.5)
plt.plot(x, test_loss, label="Test Loss", linewidth=1.5)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.show()
3.2.7绘出图形
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体避免乱码
plt.title('前缀神经网络回归任务Loss曲线')
draw_loss(train_all_loss1, test_all_loss1)
下面是我绘出的图形,仅供参考:
3.3 前缀神经网络手动实现二分类任务
3.2.1 引入库
import time # 引入时间模块
import matplotlib.pyplot as plt # 引入matplotlib库用于绘图
import numpy as np # 引入numpy库进行数值操作
import torch # 引入PyTorch深度学习框架
import torch.nn as nn # 引入PyTorch中的神经网络模块
import torchvision # 引入PyTorch的计算机视觉库
from torch.nn.functional import cross_entropy, binary_cross_entropy
# 从PyTorch的functional模块中引入交叉熵和二元交叉熵函数
from torch.nn import CrossEntropyLoss
# 引入PyTorch中的多分类交叉熵损失函数
from torchvision import transforms
# 引入PyTorch的图像转换模块
from sklearn import metrics
# 引入scikit-learn库中的评估指标模块
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
3.2.2 手动生成二分类任务的数据集
二分类任务数据集在前面我们就已经有图说明了,这里我们直接上代码
# 定义数据集大小
data_num, train_num, test_num = 10000, 7000, 3000
# 重置二分类数据集合的数值是个好习惯,减少代码的连贯性,避免查错误的时候跑很远
# 生成两组二分类特征和标签
binary_feat_0 = torch.normal(mean=0.2, std=2, size=(data_num, 200), dtype=torch.float32)
binary_labels_0 = torch.ones(data_num)
binary_feat_1 = torch.normal(mean=-0.2, std=2, size=(data_num, 200), dtype=torch.float32)
binary_lables_1 = torch.zeros(data_num)
# 划分训练集和测试集
binary_train_feats = torch.cat((binary_feat_0[:train_num], binary_feat_1[:train_num]), dim=0)
binary_train_lables = torch.cat((binary_labels_0[:train_num], binary_lables_1[:train_num]), dim=-1)
binary_test_feats = torch.cat((binary_feat_0[train_num:], binary_feat_1[train_num:]), dim=0)
binary_test_labels = torch.cat((binary_labels_0[train_num:], binary_lables_1[train_num:]), dim=-1)
# 定义批处理大小
batch_size = 128
# 创建数据加载器
binary_train_dataset = torch.utils.data.TensorDataset(binary_train_feats, binary_train_lables)
binary_test_dataset = torch.utils.data.TensorDataset(binary_test_feats, binary_test_labels)
bl_train_loader = torch.utils.data.DataLoader(dataset=binary_train_dataset, batch_size=batch_size, shuffle=True)
bl_test_loader = torch.utils.data.DataLoader(dataset=binary_test_dataset, batch_size=batch_size, shuffle=True)
3.2.3 变量注释
bc=binary class
feat=features
lab=labels
dl=dataloader
3.2.4 部分函数解释
第一组(样本标签为0)均值为0.5,标准差为1,样本特征的维度为200
第二组(样本标签为1)均值为-0.5,标准差为1,样本特征的维度为200
torch.normal正态分布
torch.ones张量全部设置为1
torch.zero张量全部设置成0
torch.cat
torch.cat
是 PyTorch 中的一个张量拼接函数,用于沿指定维度拼接多个张量。cat
是 concatenate(拼接)的缩写。通过 torch.cat
,你可以在指定的维度上将多个张量连接起来,生成一个新的张量。
其基本语法为:
torch.cat(tensors, dim=0, *, out=None) -> Tensor
tensors
是一个包含待拼接张量的列表或元组。dim
是指定拼接的维度。默认是dim=0
,表示在第一个维度上拼接。
示例:
import torch
# 创建两个张量
tensor1 = torch.tensor([[1, 2], [3, 4]])
tensor2 = torch.tensor([[5, 6]])
# 在第一个维度上拼接
result_tensor = torch.cat([tensor1, tensor2], dim=0)
print(result_tensor)
在上述示例中,tensor1
和 tensor2
在第一个维度上进行了拼接,生成了一个新的张量 result_tensor
。输出结果为:
tensor([[1, 2],
[3, 4],
[5, 6]])
注意:在进行拼接时,除了指定维度的大小外,其他维度的大小必须一致。torch.cat
是一个常用的操作.
3.2.5 主体函数
# 定义二分类神经网络模型
class Net_binary_classify():
def __init__(self):
# 设置隐藏层和输出层的节点数
num_inputs, num_hiddens, num_outputs = 200, 256, 1
w_1 = torch.tensor(np.random.normal(0, 0.01,
(num_hiddens, num_inputs)),
dtype=torch.float32,
requires_grad=True)
b_1 = torch.zeros(num_hiddens, dtype=torch.float32, requires_grad=True)
w_2 = torch.tensor(np.random.normal(0, 0.01,
(num_outputs, num_hiddens)),
dtype=torch.float32,
requires_grad=True)
b_2 = torch.zeros(num_outputs, dtype=torch.float32, requires_grad=True)
self.params = [w_1, b_1, w_2, b_2]
# 定义模型结构
self.input_layer = lambda x: x.view(x.shape[0], -1)
self.hidden_layer = lambda x: self.my_Relu(
torch.matmul(x, w_1.t()) + b_1)
self.output_layer = lambda x: torch.matmul(x, w_2.t()) + b_2
self.fn_logistic = self.logistic
def my_Relu(self, x):
return torch.max(input=x, other=torch.tensor(0.0))
def logistic(self, x): # 定义logistic函数
x = 1.0 / (1.0 + torch.exp(-x))
return x
# 定义前向传播
def forward(self, x):
x = self.input_layer(x)
x = self.my_Relu(self.hidden_layer(x))
x = self.fn_logistic(self.output_layer(x))
return x
# 定义优化函数
def mySGD(params, lr):
for param in params:
param.data -= lr * param.grad
# 实例化二分类神经网络模型
model_binary_classify = Net_binary_classify()
lr = 0.01
binary_Epoch = 100
train_binary_loss = []
test_binary_loss = []
train_binary_acc, test_binary_acc = [], []
binary_begin = time.time()
# 训练循环
for epoch in range(binary_Epoch):
train_each, train_epoch_count = 0, 0
for data, labels in bl_train_loader:
pred = model_binary_classify.forward(data)
train_each_loss = binary_cross_entropy(pred.view(-1), labels.view(-1))
train_each += train_each_loss.item()
train_each_loss.backward()
mySGD(model_binary_classify.params, lr)
for param in model_binary_classify.params:
param.grad.data.zero_()
train_epoch_count += (torch.tensor(np.where(
pred > 0.5, 1, 0)).view(-1) == labels).sum()
train_binary_acc.append(
(train_epoch_count / len(binary_train_dataset)).item())
train_binary_loss.append(train_each)
with torch.no_grad():
test_each, test_epoch_count = 0, 0
for data, labels in bl_test_loader:
pred = model_binary_classify.forward(data)
test_each_loss = binary_cross_entropy(pred.view(-1),
labels.view(-1))
test_each += test_each_loss.item()
test_epoch_count += (torch.tensor(np.where(
pred > 0.5, 1, 0)).view(-1) == labels.view(-1)).sum()
test_binary_acc.append(
(test_epoch_count / len(binary_test_dataset)).item())
test_binary_loss.append(test_each)
if epoch == 0 or (epoch + 1) % 1 == 0:
print(
'Epoch: %d | Train Loss:%.2f | Test Loss:%.2f | TrainAcc:%.2f | TestAcc:%.2f'
% (epoch + 1, train_binary_loss[-1], test_binary_loss[-1],
train_binary_acc[-1], test_binary_acc[-1]))
binary_end = time.time()
print("Binary Classify: %d轮 总用时: %.3f" %
(binary_Epoch, binary_end - binary_begin))
3.2.6 绘图呈现
#定义辅助函数
def draw_loss(train_loss, test_loss):
x = np.linspace(0, len(train_loss), len(test_loss))
plt.plot(x, train_loss, label="Train Loss", linewidth=1.5)
plt.plot(x, test_loss, label="Test Loss", linewidth=1.5)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.show()
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体避免乱码
plt.title('前缀神经网络二分类任务Loss曲线')
draw_loss(train_binary_loss, test_binary_loss)
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体避免乱码
plt.title('前缀神经网络二分类任务Loss曲线')
draw_loss(train_binary_loss, test_binary_loss)
下面是我运行后产生的图形,仅供参考:
在图中我们其实可以看到,训练轮数多了出现了过拟合的现象,我们下回遇见这种问题需要再次基础上适当减少训练轮数
3.4 手动实现前馈神经网络实现多分类任务
3.4.1导入库
import time # 引入时间模块
import matplotlib.pyplot as plt # 引入matplotlib库用于绘图
import numpy as np # 引入numpy库进行数值操作
import torch # 引入PyTorch深度学习框架
import torch.nn as nn # 引入PyTorch中的神经网络模块
import torchvision # 引入PyTorch的计算机视觉库
from torch.nn.functional import cross_entropy, binary_cross_entropy
# 从PyTorch的functional模块中引入交叉熵和二元交叉熵函数
from torch.nn import CrossEntropyLoss
# 引入PyTorch中的多分类交叉熵损失函数
from torchvision import transforms
# 引入PyTorch的图像转换模块
from sklearn import metrics
# 引入scikit-learn库中的评估指标模块
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
3.4.2 多分类数据集下载
# multiful classification
mc_train_dataset=torchvision.datasets.MNIST(root="./Datasets/MNIST",train=True,transform=transforms.ToTensor(),download=True)
mc_test_dataset=torchvision.datasets.MNIST(root="./Datasets/MNIST",train=False,transform=transforms.ToTensor())
mc_train_loader=torch.utils.data.DataLoader(mc_train_dataset,batch_size=32,shuffle=True)
mc_test_loader=torch.utils.data.DataLoader(mc_test_dataset,batch_size=32,shuffle=False)
for X,y in mc_train_loader:
print(X.shape,y.shape)
break
代码结果展示:
3.4.3 主体函数
# 定义自己的前馈神经网络
class Net_multiple():
def __init__(self):
num_inputs, num_hiddens, num_outputs = 784, 256, 10 # 十分类问题
w_1 = torch.tensor(np.random.normal(0, 0.01,
(num_hiddens, num_inputs)),
dtype=torch.float32,
requires_grad=True)
b_1 = torch.zeros(num_hiddens, dtype=torch.float32, requires_grad=True)
w_2 = torch.tensor(np.random.normal(0, 0.01,
(num_outputs, num_hiddens)),
dtype=torch.float32,
requires_grad=True)
b_2 = torch.zeros(num_outputs, dtype=torch.float32, requires_grad=True)
self.params = [w_1, b_1, w_2, b_2]
self.input_layer = lambda x: x.view(x.shape[0], -1)
self.hidden_layer = lambda x: self.my_Relu(
torch.matmul(x, w_1.t()) + b_1)
self.output_layer = lambda x: torch.matmul(x, w_2.t()) + b_2
def my_Relu(self, x):
return torch.max(input=x, other=torch.tensor(0.0))
# 定义前向传播
def forward(self, x):
x = self.input_layer(x)
x = self.hidden_layer(x)
x = self.output_layer(x)
return x
def mySGD(params, lr, batchsize):
for param in params:
param.data -= lr * param.grad / batchsize
# 实例化多分类前馈神经网络模型
modle_multiple = Net_multiple()
crit = cross_entropy
lr = 0.10
multipleEpoch = 80
train_multiple_loss = []
test_multiple_loss = []
train_multiple_acc, test_multiple_acc = [], []
multiple_begin = time.time()
# 多分类训练循环
for epoch in range(multipleEpoch):
train_each, train_acc_num = 0, 0
for data, labels in mc_train_loader:
pred = modle_multiple.forward(data)
train_each_loss = crit(pred, labels)
train_each += train_each_loss.item()
train_each_loss.backward()
mySGD(modle_multiple.params, lr, 128)
train_acc_num += (pred.argmax(dim=1) == labels).sum().item()
for param in modle_multiple.params:
param.grad.data.zero_()
train_multiple_loss.append(train_each)
train_multiple_acc.append(train_acc_num / len(mc_train_loader))
with torch.no_grad():
test_each, test_acc_num = 0, 0
for data, labels in mc_test_loader:
pred = modle_multiple.forward(data)
test_each_loss = crit(pred, labels)
test_each += test_each_loss.item()
test_acc_num += (pred.argmax(dim=1) == labels).sum().item()
test_multiple_loss.append(test_each)
test_multiple_acc.append(test_acc_num / len(mc_test_loader))
if epoch == 0 or (epoch + 1) % 1 == 0:
print(
'Epoch: %d | Train Loss:%.2f | Test Loss:%.2f | TrainAcc: %.2f | TestAcc: %.2f'
% (epoch + 1, train_each, test_each, train_multiple_acc[-1],
test_multiple_acc[-1]))
multiple_end = time.time()
print("前馈神经网络-多(十)分类实验 %d轮 总用时: %.2f" %
(multipleEpoch, multiple_end - multiple_begin))
3.4.4 绘图呈现
#定义辅助函数
def draw_loss(train_loss, test_loss):
x = np.linspace(0, len(train_loss), len(test_loss))
plt.plot(x, train_loss, label="Train Loss", linewidth=1.5)
plt.plot(x, test_loss, label="Test Loss", linewidth=1.5)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.show()
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体避免乱码
plt.title('前缀神经网络多分类任务Loss曲线')
draw_loss(train_multiple_loss, test_multiple_loss)
结果展示:
4 利用torch.nn实现前馈神经网络
4.1 torch.nn解决回归问题
4.1.0 引入库
import time # 引入时间模块
import matplotlib.pyplot as plt # 引入matplotlib库用于绘图
import numpy as np # 引入numpy库进行数值操作
import torch # 引入PyTorch深度学习框架
import torch.nn as nn # 引入PyTorch中的神经网络模块
import torchvision # 引入PyTorch的计算机视觉库
from torch.nn.functional import cross_entropy, binary_cross_entropy
# 从PyTorch的functional模块中引入交叉熵和二元交叉熵函数
from torch.nn import CrossEntropyLoss
# 引入PyTorch中的多分类交叉熵损失函数
from torchvision import transforms
# 引入PyTorch的图像转换模块
from sklearn import metrics
# 引入scikit-learn库中的评估指标模块
from torch.optim import SGD
from torch.nn import MSELoss
# 导入 PyTorch 中的神经网络模块和优化器
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
注释:这次和上面稍微有些变化,我们因为torch.nn需要增加一些引入的库。
我们可以看到,经过我们的conda install torchvision-GPU类似的操作,成功在pytorch的虚拟环境下安装了GPU版本的TORCHVISION
接下来我们需要将数据和dataloader等等进行一个to device的操作即可。
4.1.1 生成数据集(跟前面相同)
# 设置样本数量
data_num, train_num, test_num = 10000, 7000, 3000
# 设置线性回归模型的真实权重和偏差
regression_weight, regression_bias = 0.0056 * torch.ones(500, 1), 0.028
# 别名,简化后续代码
r_w, r_b = regression_weight, regression_bias
# 生成随机特征数据,形状为 (10000, 500)
features = torch.randn(10000, 500)
# 使用线性回归模型生成标签
labels = torch.matmul(features, r_w) + r_b
# 在标签上添加高斯噪声,模拟真实世界情况
labels = labels + torch.tensor(np.random.normal(0, 0.01, size=labels.size()),dtype=torch.float32)
# 划分训练集和测试集特征数据
train_feat_0, test_feat_0 = features[:train_num, :], features[train_num:, :]
# 划分训练集和测试集标签数据
train_lab_0, test_lab_0 = labels[:train_num], labels[train_num:]
# 设置批处理大小
batch_size = 128
# 创建训练集的 PyTorch 数据集
train_dataset_0 = torch.utils.data.TensorDataset(train_feat_0, train_lab_0)
#创建测试集的 PyTorch数据集
test_dataset_0 = torch.utils.data.TensorDataset(test_feat_0, test_lab_0)
# 创建训练集的 PyTorch 数据加载器,设置批处理大小和打乱数据顺序
train_dataloader_0 = torch.utils.data.DataLoader(dataset=train_dataset_0,
batch_size=batch_size,
shuffle=True)
# 创建测试集的 PyTorch 数据加载器,设置批处理大小和打乱数据顺序
test_dataloader_0 = torch.utils.data.DataLoader(dataset=test_dataset_0,
batch_size=batch_size,
shuffle=True)
4.1.2变量设置查询
train_feat_0 训练特征数据(回归任务)
test_feat_0 测试特征数据(回归任务)
train_lab_0 训练标签(回归任务)
test_lab_0 测试标签(回归任务)
train_dataset_0 训练集数据集(回归任务)
test_dataset_0 测试集数据集(回归任务)
train_dataloader_0 训练数据加载器(回归任务)
test_dataloader_0 测试数据加载器(回归任务)
4.1.3 主体函数(定义前馈神经网络)
class Net_nn_regression(nn.Module):
def __init__(self):
super(Net_nn_regression, self).__init__()
# 我们依然设置inputs,hiddens,outputs和上面的回归实验相同的变量不变
num_inputs, num_hiddens, num_outputs = 500, 256, 1
# 定义模型结构
self.input_layer = nn.Flatten()
self.hidden_layer = nn.Linear(num_inputs, num_hiddens)
self.output_layer = nn.Linear(num_hiddens, num_outputs)
self.relu = nn.ReLU()
# 定义前向传播
def forward(self, x):
flatten_input = self.input_layer(x)
hidden_output = self.relu(self.hidden_layer(flatten_input))
final_output = self.output_layer(hidden_output)
return final_output
4.1.4 主体函数(训练)
# 导入神经网络模型和相关库
model_nn_regression = Net_nn_regression()
model_nn_regression = model_nn_regression.to(device) # 将模型移动到GPU上
# 定义损失函数和优化器
criterion = MSELoss()
criterion = criterion.to(device) # 将损失函数移动到指GPU上
optimizer = SGD(model_nn_regression.parameters(), lr=0.1)
# 定义训练轮次和记录训练和测试损失的列表
epochs = 100
train_nn_loss_regression = []
test_nn_loss_regression = []
# 记录训练开始时间
b_time_nn = time.time()
# 开始训练循环
for epoch in range(epochs):
train_l = 0
# 遍历训练数据集
for data, labels in train_dataloader_0:
data, labels = data.to(device=device), labels.to(device)
# 前向传播
pred = model_nn_regression(data)
# 计算每个样本的训练损失
train_each_loss = criterion(pred.view(-1, 1), labels.view(-1, 1))
# 梯度清零
optimizer.zero_grad()
# 反向传播
train_each_loss.backward()
# 更新模型参数
optimizer.step()
# 累加训练损失
train_l += train_each_loss.item()
# 记录训练损失
train_nn_loss_regression.append(train_l)
# 在测试集上进行验证
with torch.no_grad():
test_loss = 0
# 遍历测试数据集
for data, labels in test_dataloader_0:
data, labels = data.to(device), labels.to(device)
# 前向传播
pred = model_nn_regression(data)
# 计算每个样本的测试损失
test_each_loss = criterion(pred, labels)
# 累加测试损失
test_loss += test_each_loss.item()
# 记录测试损失
test_nn_loss_regression.append(test_loss)
# 每10轮次打印一次训练和测试损失
if epoch == 0 or (epoch + 1) % 1 == 0:
print('Epoch: %d | Train Loss:%.2f | Test Loss:%.2f' % (epoch + 1, train_nn_loss_regression[-1], test_nn_loss_regression[-1]))
# 记录训练结束时间
e_time_nn = time.time()
# 打印总用时
print("torch.nn实现回归 %d轮次 总用时: %.2fs" % (epochs, e_time_nn - b_time_nn))
4.1.5 绘出图像
# 定义辅助函数
def draw_loss(train_loss, test_loss):
x = np.linspace(1, len(train_loss[1:]), len(train_loss[1:])) #第一个数据实在是太离谱了,所以我们索性抛弃了第一个数据
plt.plot(x, train_loss[1:], label="Train Loss", linewidth=1.5)
plt.plot(x, test_loss[1:], label="Test Loss", linewidth=1.5)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.show()
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体避免乱码
plt.title('torch.nn回归任务Loss曲线')
draw_loss(train_nn_loss_regression, test_nn_loss_regression)
从这个图像看还是过拟合了
4.2 torch.nn解决二分类问题
4.2.1引入库
import time # 引入时间模块
import matplotlib.pyplot as plt # 引入matplotlib库用于绘图
import numpy as np # 引入numpy库进行数值操作
import torch # 引入PyTorch深度学习框架
import torch.nn as nn # 引入PyTorch中的神经网络模块
import torchvision # 引入PyTorch的计算机视觉库
from torch.nn.functional import cross_entropy, binary_cross_entropy
# 从PyTorch的functional模块中引入交叉熵和二元交叉熵函数
from torch.nn import CrossEntropyLoss
# 引入PyTorch中的多分类交叉熵损失函数
from torchvision import transforms
# 引入PyTorch的图像转换模块
from sklearn import metrics
# 引入scikit-learn库中的评估指标模块
from torch.optim import SGD
from torch.nn import MSELoss
# 导入 PyTorch 中的神经网络模块和优化器
from torch.optim import SGD
from torch.nn.functional import binary_cross_entropy
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
4.2.2 手动生成二分类任务的数据集
# 定义数据集大小
data_num, train_num, test_num = 10000, 7000, 3000
# 重置二分类数据集合的数值是个好习惯,减少代码的连贯性,避免查错误的时候跑很远
# 生成两组二分类特征和标签
binary_feat_0 = torch.normal(mean=0.2, std=2, size=(data_num, 200), dtype=torch.float32)
binary_labels_0 = torch.ones(data_num)
binary_feat_1 = torch.normal(mean=-0.2, std=2, size=(data_num, 200), dtype=torch.float32)
binary_lables_1 = torch.zeros(data_num)
# 划分训练集和测试集
binary_train_feats = torch.cat((binary_feat_0[:train_num], binary_feat_1[:train_num]), dim=0)
binary_train_lables = torch.cat((binary_labels_0[:train_num], binary_lables_1[:train_num]), dim=-1)
binary_test_feats = torch.cat((binary_feat_0[train_num:], binary_feat_1[train_num:]), dim=0)
binary_test_labels = torch.cat((binary_labels_0[train_num:], binary_lables_1[train_num:]), dim=-1)
# 定义批处理大小
batch_size = 128
# 创建数据加载器
binary_train_dataset = torch.utils.data.TensorDataset(binary_train_feats, binary_train_lables)
binary_test_dataset = torch.utils.data.TensorDataset(binary_test_feats, binary_test_labels)
bl_train_loader = torch.utils.data.DataLoader(dataset=binary_train_dataset, batch_size=batch_size, shuffle=True)
bl_test_loader = torch.utils.data.DataLoader(dataset=binary_test_dataset, batch_size=batch_size, shuffle=True)
4.2.3 主体函数(修改上面的即可)
# 定义一个用于二分类的前馈神经网络模型
class nn_binary(nn.Module):
def __init__(self):
super(nn_binary, self).__init__()
# 设置输入层、隐藏层和输出层的节点数
num_inputs, num_hiddens, num_outputs = 200, 256, 1
# 定义模型结构
self.input_layer = nn.Flatten() # 将输入数据展平
self.hidden_layer = nn.Linear(num_inputs, num_hiddens) # 隐藏层,线性变换
self.output_layer = nn.Linear(num_hiddens, num_outputs) # 输出层,线性变换
self.relu = nn.ReLU() # 使用ReLU激活函数
def logistic(self, x):
# 定义逻辑函数,将输入值转换到(0, 1)范围
x = 1.0 / (1.0 + torch.exp(-x))
return x
# 定义前向传播
def forward(self, x):
x = self.input_layer(x)
x = self.relu(self.hidden_layer(x))
x = self.logistic(self.output_layer(x))
return x
# 训练过程
modelnn_binary = nn_binary() # 创建二分类神经网络模型
modelnn_binary = modelnn_binary.to(device) # 将模型移动到GPU上
op = SGD(modelnn_binary.parameters(), lr=0.001) # 使用随机梯度下降优化器
nn_binary_epochs = 100 # 设置训练轮次
nn_train_binary_loss = [] # 记录每轮训练损失
nn_test_binary_loss = [] # 记录每轮测试损失
train_acc_nn, test_acc_nn = [], [] # 记录每轮训练和测试准确率
nn_binary_begin = time.time() # 记录训练开始时间
# 开始训练循环
for epoch in range(nn_binary_epochs):
train_each, train_epoch_count, test_epoch_count = 0, 0, 0
# 遍历训练数据集
for data, labels in bl_train_loader:
data, labels = data.to(device), labels.to(device)
pred = modelnn_binary(data)
# 计算每个样本的训练损失
train_each_loss = binary_cross_entropy(pred.view(-1), labels.view(-1))
op.zero_grad() # 梯度清零
train_each_loss.backward() # 反向传播
op.step() # 更新模型参数
train_each += train_each_loss.item()
# 计算每个批次的训练准确率
pred = torch.tensor(np.where(pred.cpu() > 0.5, 1, 0))
each_count = (pred.view(-1) == labels.cpu()).sum()
train_epoch_count += each_count
# 记录每轮训练损失和准确率
train_acc_nn.append(train_epoch_count / len(bl_train_loader))
nn_train_binary_loss.append(train_each)
# 在测试集上进行验证
with torch.no_grad():
test_loss, each_count = 0, 0
# 遍历测试数据集
for data, labels in bl_test_loader:
data, labels = data.to(device), labels.to(device)
pred = modelnn_binary(data)
# 计算每个样本的测试损失
test_each_loss = binary_cross_entropy(pred.view(-1), labels)
test_loss += test_each_loss.item()
# 计算每个批次的测试准确率
pred = torch.tensor(np.where(pred.cpu() > 0.5, 1, 0))
each_count = (pred.view(-1) == labels.cpu().view(-1)).sum()
test_epoch_count += each_count
# 记录每轮测试损失和准确率
nn_test_binary_loss.append(test_loss)
test_acc_nn.append(test_epoch_count / len(bl_test_loader))
# 打印每轮次的训练和测试损失以及准确率
if epoch == 0 or (epoch + 1) % 1 == 0:
print('Epoch: %d | Train Loss:%.2f Test Loss:%.2f | TrainAcc:%.2f | TestAcc:%.2f' % (
epoch + 1, nn_train_binary_loss[-1], nn_test_binary_loss[-1], train_acc_nn[-1], test_acc_nn[-1]))
nn_end_binary = time.time()
print("torch.nn实现前馈网络-二分类实验 %d轮 总用时: %.3fs" % (nn_binary_epochs, nn_end_binary - nn_binary_begin))
4.2.4 绘图呈现
#定义辅助函数
def draw_loss(train_loss, test_loss):
x = np.linspace(0, len(train_loss), len(test_loss))
plt.plot(x, train_loss, label="Train Loss", linewidth=1.5)
plt.plot(x, test_loss, label="Test Loss", linewidth=1.5)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.show()
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体避免乱码
plt.title('前缀神经网络二分类任务Loss曲线')
draw_loss(nn_train_binary_loss, nn_test_binary_loss)
4.3 torch.nn解决多分类问题
4.3.1 引入库
import time # 引入时间模块
import matplotlib.pyplot as plt # 引入matplotlib库用于绘图
import numpy as np # 引入numpy库进行数值操作
import torch # 引入PyTorch深度学习框架
import torch.nn as nn # 引入PyTorch中的神经网络模块
import torchvision # 引入PyTorch的计算机视觉库
from torch.nn.functional import cross_entropy, binary_cross_entropy
# 从PyTorch的functional模块中引入交叉熵和二元交叉熵函数
from torch.nn import CrossEntropyLoss
# 引入PyTorch中的多分类交叉熵损失函数
from torchvision import transforms
# 引入PyTorch的图像转换模块
from sklearn import metrics
# 引入scikit-learn库中的评估指标模块
from torch.optim import SGD
from torch.nn import MSELoss
# 导入 PyTorch 中的神经网络模块和优化器
from collections import OrderedDict
from torch.nn import CrossEntropyLoss
from collections import OrderedDict
from torch.nn import CrossEntropyLoss
from torch.optim import SGD
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
4.3.2 下载数据集
# multiful classification
mc_train_dataset=torchvision.datasets.MNIST(root="./Datasets/MNIST",train=True,transform=transforms.ToTensor(),download=True)
mc_test_dataset=torchvision.datasets.MNIST(root="./Datasets/MNIST",train=False,transform=transforms.ToTensor())
mc_train_loader=torch.utils.data.DataLoader(mc_train_dataset,batch_size=32,shuffle=True)
mc_test_loader=torch.utils.data.DataLoader(mc_test_dataset,batch_size=32,shuffle=False)
for X,y in mc_train_loader:
print(X.shape,y.shape)
break
4.3.3 主体函数(激活函数为relu)
class nn_multiple(nn.Module):
def __init__(self,
num_hiddenlayer=1,
num_inputs=28 * 28,
num_hiddens=[256],
num_outs=10):
super(nn_multiple, self).__init__()
self.num_inputs, self.num_hiddens, self.num_outputs = num_inputs, num_hiddens, num_outs
self.input_layer = nn.Flatten()
# 设置隐藏层
if num_hiddenlayer == 1:
self.hidden_layers = nn.Linear(self.num_inputs,
self.num_hiddens[-1])
else:
self.hidden_layers = nn.Sequential()
self.hidden_layers.add_module(
"hidden_layer1", nn.Linear(self.num_inputs,
self.num_hiddens[0]))
for i in range(0, num_hiddenlayer - 1):
name = str('hidden_layer' + str(i + 2))
self.hidden_layers.add_module(
name,
nn.Linear(self.num_hiddens[i], self.num_hiddens[i + 1]))
self.output_layer = nn.Linear(self.num_hiddens[-1], self.num_outputs)
self.act = nn.ReLU()
def logistic(self, x): # 定义logistic函数
x = 1.0 / (1.0 + torch.exp(-x))
return x
# 定义前向传播
def forward(self, x):
x = self.input_layer(x)
x = self.act(self.hidden_layers(x))
x = self.output_layer(x)
return x
# 训练
# 使用默认的参数即: num_inputs=28*28, num_hiddens=256, num_outs=10
model_nn_multiple = nn_multiple()
model_nn_multiple = model_nn_multiple.to(device)
def train_and_test(model=model_nn_multiple):
nn_model = model
optimizer = SGD(nn_model.parameters(), lr=0.01) # 优化函数
nn_epoch_multiple = 80
criterion = CrossEntropyLoss()
train_nn_multiple = []
test_nn_multiple = []
train_nn_acc, test_nn_acc = [], []
nn_multiple_begin = time.time()
for epoch in range(nn_epoch_multiple):
train_l, train_epoch_count, test_epoch_count = 0, 0, 0
for data, labels in mc_train_loader:
data, labels = data.to(device), labels.to(device)
pred = nn_model(data)
train_each_loss = criterion(pred, labels.view(-1)) # 计算每次的损失值
optimizer.zero_grad() # 梯度清零
train_each_loss.backward() # 反向传播
optimizer.step() # 梯度更新
train_l += train_each_loss.item()
train_epoch_count += (pred.argmax(dim=1) == labels).sum()
train_nn_acc.append(train_epoch_count.cpu() / len(mc_train_loader))
train_nn_multiple.append(train_l) # 添加损失值到列表中
with torch.no_grad():
test_loss, test_epoch_count = 0, 0
for data, labels in mc_test_loader:
data, labels = data.to(device), labels.to(device)
pred = nn_model(data)
test_each_loss = criterion(pred, labels)
test_loss += test_each_loss.item()
test_epoch_count += (pred.argmax(dim=1) == labels).sum()
test_nn_multiple.append(test_loss)
test_nn_acc.append(test_epoch_count.cpu() / len(mc_test_loader))
if epoch == 0 or (epoch + 1) % 1 == 0:
print(
'Epoch: %d | Train Loss:%.2f | Test Loss:%.2f | TrainAcc:%5f TestAcc:%.2f:'
% (epoch + 1, train_nn_multiple[-1], test_nn_multiple[-1],
train_nn_acc[-1], test_nn_acc[-1]))
nn_multiplr_end = time.time()
print("torch.nn实现前馈网络-多分类任务 %d轮 总用时: %.2fs" %
(nn_epoch_multiple, nn_multiplr_end - nn_multiple_begin))
return train_nn_multiple, test_nn_multiple, train_nn_acc, test_nn_acc
# 调用函数进行训练和测试
train_nn_multiple, test_nn_multiple, train_nn_acc, test_nn_acc = train_and_test(
model=model_nn_multiple)
4.3.4 绘图呈现(激活函数为relu)
#定义辅助函数
def draw_loss(train_loss, test_loss):
x = np.linspace(0, len(train_loss), len(test_loss))
plt.plot(x, train_loss, label="Train Loss", linewidth=1.5)
plt.plot(x, test_loss, label="Test Loss", linewidth=1.5)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.show()
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体避免乱码
plt.title('nn.ReLu多(十)分类任务Loss曲线')
draw_loss(train_nn_multiple, test_nn_multiple)
def draw_acc(train_acc, test_acc):
x = np.linspace(0, len(train_acc), len(test_acc))
plt.plot(x, train_acc, label="Train Acc", linewidth=1.5)
plt.plot(x, test_acc, label="Test Acc", linewidth=1.5)
plt.xlabel("Epoch")
plt.ylabel("Acc")
plt.legend()
plt.show()
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体避免乱码
plt.title('nn.ReLu多(十)分类任务Acc曲线')
draw_acc(train_nn_acc, test_nn_acc)
4.3.5 主体函数(激活函数为sigmoid)
class nn_multiple(nn.Module):
def __init__(self,
num_hiddenlayer=1,
num_inputs=28 * 28,
num_hiddens=[256],
num_outs=10):
super(nn_multiple, self).__init__()
self.num_inputs, self.num_hiddens, self.num_outputs = num_inputs, num_hiddens, num_outs
self.input_layer = nn.Flatten()
# 设置隐藏层
if num_hiddenlayer == 1:
self.hidden_layers = nn.Linear(self.num_inputs,
self.num_hiddens[-1])
else:
self.hidden_layers = nn.Sequential()
self.hidden_layers.add_module(
"hidden_layer1", nn.Linear(self.num_inputs,
self.num_hiddens[0]))
for i in range(0, num_hiddenlayer - 1):
name = str('hidden_layer' + str(i + 2))
self.hidden_layers.add_module(
name,
nn.Linear(self.num_hiddens[i], self.num_hiddens[i + 1]))
self.output_layer = nn.Linear(self.num_hiddens[-1], self.num_outputs)
self.act = nn.Sigmoid()
def logistic(self, x): # 定义logistic函数
x = 1.0 / (1.0 + torch.exp(-x))
return x
# 定义前向传播
def forward(self, x):
x = self.input_layer(x)
x = self.act(self.hidden_layers(x))
x = self.output_layer(x)
return x
# 训练
# 使用默认的参数即: num_inputs=28*28, num_hiddens=256, num_outs=10
model_nn_multiple = nn_multiple()
model_nn_multiple = model_nn_multiple.to(device)
def train_and_test(model=model_nn_multiple):
nn_model = model
optimizer = SGD(nn_model.parameters(), lr=0.01) # 优化函数
nn_epoch_multiple = 80
criterion = CrossEntropyLoss()
train_nn_multiple = []
test_nn_multiple = []
train_nn_acc, test_nn_acc = [], []
nn_multiple_begin = time.time()
for epoch in range(nn_epoch_multiple):
train_l, train_epoch_count, test_epoch_count = 0, 0, 0
for data, labels in mc_train_loader:
data, labels = data.to(device), labels.to(device)
pred = nn_model(data)
train_each_loss = criterion(pred, labels.view(-1)) # 计算每次的损失值
optimizer.zero_grad() # 梯度清零
train_each_loss.backward() # 反向传播
optimizer.step() # 梯度更新
train_l += train_each_loss.item()
train_epoch_count += (pred.argmax(dim=1) == labels).sum()
train_nn_acc.append(train_epoch_count.cpu() / len(mc_train_loader))
train_nn_multiple.append(train_l) # 添加损失值到列表中
with torch.no_grad():
test_loss, test_epoch_count = 0, 0
for data, labels in mc_test_loader:
data, labels = data.to(device), labels.to(device)
pred = nn_model(data)
test_each_loss = criterion(pred, labels)
test_loss += test_each_loss.item()
test_epoch_count += (pred.argmax(dim=1) == labels).sum()
test_nn_multiple.append(test_loss)
test_nn_acc.append(test_epoch_count.cpu() / len(mc_test_loader))
if epoch == 0 or (epoch + 1) % 1 == 0:
print(
'Epoch: %d | Train Loss:%.2f | Test Loss:%.2f | TrainAcc:%5f TestAcc:%.2f:'
% (epoch + 1, train_nn_multiple[-1], test_nn_multiple[-1],
train_nn_acc[-1], test_nn_acc[-1]))
nn_multiplr_end = time.time()
print("torch.nn实现前馈网络-多分类任务 %d轮 总用时: %.2fs" %
(nn_epoch_multiple, nn_multiplr_end - nn_multiple_begin))
return train_nn_multiple, test_nn_multiple, train_nn_acc, test_nn_acc
# 调用函数进行训练和测试
train_nn_multiple, test_nn_multiple, train_nn_acc, test_nn_acc = train_and_test(
model=model_nn_multiple)
4.3.6绘图呈现(激活函数为sigmoid)
#定义辅助函数
def draw_loss(train_loss, test_loss):
x = np.linspace(0, len(train_loss), len(test_loss))
plt.plot(x, train_loss, label="Train Loss", linewidth=1.5)
plt.plot(x, test_loss, label="Test Loss", linewidth=1.5)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.show()
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体避免乱码
plt.title('nn.Sigmoid多(十)分类任务Loss曲线')
draw_loss(train_nn_multiple, test_nn_multiple)
def draw_acc(train_acc, test_acc):
x = np.linspace(0, len(train_acc), len(test_acc))
plt.plot(x, train_acc, label="Train Acc", linewidth=1.5)
plt.plot(x, test_acc, label="Test Acc", linewidth=1.5)
plt.xlabel("Epoch")
plt.ylabel("Acc")
plt.legend()
plt.show()
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体避免乱码
plt.title('nn.Sigmoid多(十)分类任务Acc曲线')
draw_acc(train_nn_acc, test_nn_acc)
4.3.7主体函数(激活函数为tanh)
class nn_multiple(nn.Module):
def __init__(self,
num_hiddenlayer=1,
num_inputs=28 * 28,
num_hiddens=[256],
num_outs=10):
super(nn_multiple, self).__init__()
self.num_inputs, self.num_hiddens, self.num_outputs = num_inputs, num_hiddens, num_outs
self.input_layer = nn.Flatten()
# 设置隐藏层
if num_hiddenlayer == 1:
self.hidden_layers = nn.Linear(self.num_inputs,
self.num_hiddens[-1])
else:
self.hidden_layers = nn.Sequential()
self.hidden_layers.add_module(
"hidden_layer1", nn.Linear(self.num_inputs,
self.num_hiddens[0]))
for i in range(0, num_hiddenlayer - 1):
name = str('hidden_layer' + str(i + 2))
self.hidden_layers.add_module(
name,
nn.Linear(self.num_hiddens[i], self.num_hiddens[i + 1]))
self.output_layer = nn.Linear(self.num_hiddens[-1], self.num_outputs)
self.act = nn.Tanh()
def logistic(self, x): # 定义logistic函数
x = 1.0 / (1.0 + torch.exp(-x))
return x
# 定义前向传播
def forward(self, x):
x = self.input_layer(x)
x = self.act(self.hidden_layers(x))
x = self.output_layer(x)
return x
# 训练
# 使用默认的参数即: num_inputs=28*28, num_hiddens=256, num_outs=10
model_nn_multiple = nn_multiple()
model_nn_multiple = model_nn_multiple.to(device)
def train_and_test(model=model_nn_multiple):
nn_model = model
optimizer = SGD(nn_model.parameters(), lr=0.01) # 优化函数
nn_epoch_multiple = 80
criterion = CrossEntropyLoss()
train_nn_multiple = []
test_nn_multiple = []
train_nn_acc, test_nn_acc = [], []
nn_multiple_begin = time.time()
for epoch in range(nn_epoch_multiple):
train_l, train_epoch_count, test_epoch_count = 0, 0, 0
for data, labels in mc_train_loader:
data, labels = data.to(device), labels.to(device)
pred = nn_model(data)
train_each_loss = criterion(pred, labels.view(-1)) # 计算每次的损失值
optimizer.zero_grad() # 梯度清零
train_each_loss.backward() # 反向传播
optimizer.step() # 梯度更新
train_l += train_each_loss.item()
train_epoch_count += (pred.argmax(dim=1) == labels).sum()
train_nn_acc.append(train_epoch_count.cpu() / len(mc_train_loader))
train_nn_multiple.append(train_l) # 添加损失值到列表中
with torch.no_grad():
test_loss, test_epoch_count = 0, 0
for data, labels in mc_test_loader:
data, labels = data.to(device), labels.to(device)
pred = nn_model(data)
test_each_loss = criterion(pred, labels)
test_loss += test_each_loss.item()
test_epoch_count += (pred.argmax(dim=1) == labels).sum()
test_nn_multiple.append(test_loss)
test_nn_acc.append(test_epoch_count.cpu() / len(mc_test_loader))
if epoch == 0 or (epoch + 1) % 1 == 0:
print(
'Epoch: %d | Train Loss:%.2f | Test Loss:%.2f | TrainAcc:%5f TestAcc:%.2f:'
% (epoch + 1, train_nn_multiple[-1], test_nn_multiple[-1],
train_nn_acc[-1], test_nn_acc[-1]))
nn_multiplr_end = time.time()
print("torch.nn实现前馈网络-多分类任务 %d轮 总用时: %.2fs" %
(nn_epoch_multiple, nn_multiplr_end - nn_multiple_begin))
return train_nn_multiple, test_nn_multiple, train_nn_acc, test_nn_acc
# 调用函数进行训练和测试
train_nn_multiple, test_nn_multiple, train_nn_acc, test_nn_acc = train_and_test(
model=model_nn_multiple)
4.3.8 绘图呈现(激活函数为tanh)
#定义辅助函数
def draw_loss(train_loss, test_loss):
x = np.linspace(0, len(train_loss), len(test_loss))
plt.plot(x, train_loss, label="Train Loss", linewidth=1.5)
plt.plot(x, test_loss, label="Test Loss", linewidth=1.5)
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.show()
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体避免乱码
plt.title('nn.Tanh多(十)分类任务Loss曲线')
draw_loss(train_nn_multiple, test_nn_multiple)
def draw_acc(train_acc, test_acc):
x = np.linspace(0, len(train_acc), len(test_acc))
plt.plot(x, train_acc, label="Train Acc", linewidth=1.5)
plt.plot(x, test_acc, label="Test Acc", linewidth=1.5)
plt.xlabel("Epoch")
plt.ylabel("Acc")
plt.legend()
plt.show()
plt.rcParams["font.sans-serif"]=["SimHei"] #设置字体避免乱码
plt.title('nn.Tanh多(十)分类任务Acc曲线')
draw_acc(train_nn_acc, test_nn_acc)
4.3.9 总结
三种激活函数的作用基本相同,在训练时间上也没有太多的差异。但是可以看见的是relu所需的训练时间最少,在80轮的训练轮数中节省了将近100s的时间。
此外,三种激活函数在Epoch=80时,LOSS都是逐渐减少,ACC都是逐渐提高的。
隐藏层层数 ):
增加隐藏层的数量通常可以提高模型的表达能力,使其能够学习更复杂的特征和关系。然而,层数增加也可能导致过拟合
隐藏层的增加会导致模型的计算复杂性增加,训练和推理的时间也可能变长
隐藏单元个数
隐藏单元的增加可能导致模型的训练速度变慢,因为需要更多的参数进行更新。这也可能使得需要更多的训练数据来避免过拟合。
过多的隐藏单元可能导致模型对训练数据过于敏感,从而降低了其在未见过的数据上的泛化能力。适当的正则化技术可以帮助提高泛化性能