1.准备可复现的随机数据
可复现是指:一个程序在相同设备上多次运行,可以得到相同的结果。在调试程序过程中,常使用这种方式来复现问题,方便地找出上一次运行时所出现的问题。
具体做法是,在程序开始时固定torch的随机数种子,并固定numpy的随机数种子。
import torch
import numpy as np
torch.manual_seed(0)
torch.cuda.manual_seed_all(0)
torch.backends.cudnn.deterministic=True
torch.backends.cudnn.benchmark=False
np.random.seed(0)
这部分代码必须放在程序的最开始位置。这样才可以保证每次生成的随机模拟数据,以及模型的初始权重相同。
在sklearn库的datasets模块中,通过调用make_moons()函数可以生成两组半圆形数据。
import sklearn.datasets
import matplotlib.pyplot as plt
X, Y = sklearn.datasets.make_moons(200, noise=0.2)
data1 = []
data2 = []
for i in range(200):
if Y[i] == 0:
data1.append(X[i])
else:
data2.append(X[i])
X1, X2, Y1, Y2 = [], [], [], []
for i in data1:
X1.append(i[0])
Y1.append(i[1])
for j in data2:
X2.append(j[0])
Y2.append(j[1])
plt.scatter(X1, Y1, c='b', marker='+', label="data1")
plt.scatter(X2, Y2, c='r', marker='.', label="data2")
plt.legend()
plt.show()
2.实现并训练模型
2.1定义模型
import torch.nn as nn
class LogicNet(nn.Module): # 继承nn.Module类,构建模型
def __init__(self, inputdim, hiddendim, outputdim): # 初始化网络结构
super(LogicNet, self).__init__()
self.Linear1 = nn.Linear(inputdim, hiddendim) # 定义全连接层
self.Linear2 = nn.Linear(hiddendim, outputdim) # 定义全连接层
self.criterion = nn.CrossEntropyLoss() # 定义交叉熵函数
def forward(self, x): # 搭建用两层全连接组成的模型
x = self.Linear1(x) # 将输入数据传入第一层
x = torch.tanh(x) # 对第一层的结果进行非线性变换
x = self.Linear2(x) # 将网络数据传入第二层
return x
def predict(self, x): # 实现LogicNet类的预测接口
# 调用自身模型,并对结果进行softmax处理,分别得出预测数据属于哪一类的概率
pred = torch.softmax(self.forward(x), dim=1)
return torch.argmax(pred, dim=1) # 返回每组预测概率中最大的索引
def getloss(self, x, y): # 实现LogicNet类的损失值计算接口
y_pred = self.forward(x)
loss = self.criterion(y_pred, y) # 计算损失值的交叉熵
return loss
2.2搭建并训练模型
只需要将定义好的模型类LogicNet实例化,即可完成模型的真正搭建。待模型搭建完成后,还需要定义训练模型所需的优化器,优化器会在训练模型时的反向传播过程中使用。
model = LogicNet(inputdim=2, hiddendim=3, outputdim=2) # 实例化模型
optimizer = torch.optim.Adam(model.parameters(), lr=0.01) # 定义优化器
xt = torch.from_numpy(X).type(torch.FloatTensor) # 将numpy数据转换为张量
yt = torch.from_numpy(Y).type(torch.LongTensor)
epochs = 1000 # 定义迭代次数
losses = [] # 定义列表,用于接收每一步的损失值
for i in range(epochs):
loss = model.getloss(xt, yt)
losses.append(loss.item()) # 保存中间状态的损失值
optimizer.zero_grad() # 清空之前的梯度
loss.backward() # 反向传播损失值
optimizer.step() # 更新参数
- inputdim:输入数据的维度。因为本实例中输入的数据是一个具有x和y两个坐标值的数据,所以维度是2。
- hiddendim:隐藏层的节点个数。这个值可以随意定义,节点个数越大,则网络的拟合效果越好。但太大的节点个数会为网络带来训练困难、泛化性差的问题。
- outputdim:模型输出的维度,这个参数具有一定的规律。在分类模型中,模型的最终结果是几类,该参数就设成几。
plt.plot(range(1000), losses, '-')
plt.xlabel('step number')
plt.ylabel('Loss')
plt.title('step number vs. Training loss')
plt.show()
2.3评估及使用模型
import sklearn.metrics as metrics
print(metrics.accuracy_score(model.predict(xt), yt)) # 输出0.975
注意:在实际运行时,神经网络节点的初始值时随机的。而在训练过程中,它是基于网络节点中的原始值进行调节的。所以,每次训练完的模型在进行评估时都会得到不同的分数。但是总体的评估值会在一个范围内浮动,不会有太大的差异。