以回归和分类为例子进行搭建神经网络
1 回归
先搞几个数据:
a = torch.linspace(-1, 1, 100) # 返回一个一维张量 起始,终止,生成样本点
x = torch.unsqueeze(a, dim=1) # dim=0是一行,dim=1是一列,将一个一维tensor变成一个二维的数据的一列(torch不处理一维的数据)
y = x.pow(2) + 0.2*torch.rand(x.size()) # pow是求次幂,rand返回一组按照x的size的随机数 y = x^2+0.2*k,k相对于是个噪点
x, y = Variable(x), Variable(y)
生成的数据
从y的生成可以看出来了,是对y=x^2做一点噪声,然后现在对这个有噪声的数据做一个拟合。
- 首先,先搭建神经网络。定义Net类,其中需要继承nn.Module,并实现forward方法。nn.Module包含网络各层的定义及forward方法,继承nn.Module类之后,在构造函数中要调用Module的构造函数,并把网络中具有可学习参数的层放在构造函数。
class Net(nn.Module):
"""
这是一个最简单的一层的拟合的神经网络。hidden是隐藏层,predict是预测层
"""
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__() # 继承Net到nn.module
# 定义层,nn.Linear(in_features,out_features),Linear第一个参数都是层左边输入的特征数,第二个参数是层右边输出的特征数
self.hidden = nn.Linear(n_feature, n_hidden) # 一层隐藏层,n_feature个特征,n_hidden个神经元
self.predict = nn.Linear(n_hidden, n_output) # 预测层,n_hidden个神经元,n_output个特征的输出
def forward(self, x):
x = F.relu(self.hidden(x)) # x经过一个隐藏层,然后再被一个relu函数激活一下
x = self.predict(x)
return x
2.第二步,搭建训练步骤,通过反向传播不断调整Net的参数,从而使预测值接近训练数据的标签。
def run_Net():
net = Net(1, 10, 1) # 因为x是一维的,所以n_feature=1,神经元设为10个, y是一维的,所以输出为1维,n_output=1
# print(net) # 如果想看自己搭的神经元的层结构,直接print就行了
optimizer = torch.optim.SGD(net.parameters(), lr=0.5) # 优化器用来训练神经网络,设置为随机梯度下降,lr是学习效率
loss_func = nn.MSELoss() # 定义损失函数为均方误差
for t in range(100):
prediction = net(x)
loss = loss_func(prediction, y) # 计算预测值和真实值的误差,注意预测值在前
print(loss.data.numpy())
optimizer.zero_grad() # zero_grad将net中所有参数的梯度全部降为0(将梯度初始化为0),因为每次计算loss梯度都会保留在net和优化器中
loss.backward() # 反向传递,也是一个Variable
optimizer.step() # 用优化器优化梯度
最后训练好后,net会存储训练好的参数,再进行输出就可以了。图为:
训练结果
完整代码
import torch
import matplotlib.pyplot as plt
import torch.nn.functional as F
from torch import nn
from torch.autograd import Variable
a = torch.linspace(-1, 1, 100) # 返回一个一维张量 起始,终止,生成样本点
x = torch.unsqueeze(a, dim=1) # dim=0是一行,dim=1是一列,将一个一维tensor变成一个二维的数据的一列(torch不处理一维的数据)
y = x.pow(2) + 0.2*torch.rand(x.size()) # pow是求次幂,rand返回一组按照x的size的随机数 y = x^2+0.2*k,k相对于是个噪点
x, y = Variable(x), Variable(y)
# 训练数据示意图
# plt.scatter(x.data.numpy(), y.data.numpy())
# plt.show()
class Net(nn.Module):
"""
这是一个最简单的一层的拟合的神经网络。hidden是隐藏层,predict是预测层
"""
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__() # 继承Net到nn.module
# 定义层,nn.Linear(in_features,out_features),Linear第一个参数都是层左边输入的特征数,第二个参数是层右边输出的特征数
self.hidden = nn.Linear(n_feature, n_hidden) # 一层隐藏层,n_feature个特征,n_hidden个神经元
self.predict = nn.Linear(n_hidden, n_output) # 预测层,n_hidden个神经元,n_output个特征的输出
def forward(self, x):
x = F.relu(self.hidden(x)) # x经过一个隐藏层,然后再被一个relu函数激活一下
x = self.predict(x)
return x
def run_Net():
net = Net(1, 10, 1) # 因为x是一维的,所以n_feature=1,神经元设为10个, y是一维的,所以输出为1维,n_output=1
# print(net) # 如果想看自己搭的神经元的层结构,直接print就行了
optimizer = torch.optim.SGD(net.parameters(), lr=0.5) # 优化器用来训练神经网络,设置为随机梯度下降,lr是学习效率
loss_func = nn.MSELoss() # 定义损失函数为均方误差
for t in range(100):
prediction = net(x)
loss = loss_func(prediction, y) # 计算预测值和真实值的误差,注意预测值在前
print(loss.data.numpy())
optimizer.zero_grad() # zero_grad将net中所有参数的梯度全部降为0(将梯度初始化为0),因为每次计算loss梯度都会保留在net和优化器中
loss.backward() # 反向传递,也是一个Variable
optimizer.step() # 用优化器优化梯度
return net(x)
if __name__ == '__main__':
prediction = run_Net()
# 训练结果示意图
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-')
plt.show()
2 分类
同样的,先创建一些数据集。比如两个二次分布的数据, 不过他们的均值都不一样。然后用01来区分这两个数据簇。
n_data = torch.ones(100,2) # n_data
x0 = torch.normal(2*n_data, 1) # 输入
y0 = torch.zeros(100) # x0的类别是1
x1 = torch.normal(-2*n_data, 1)
y1 = torch.ones(100)
x = torch.cat((x0, x1), 0).type(torch.FloatTensor) # 按垂直方向拼接,FloatTensor32位浮点数
y = torch.cat((y0, y1), 0).type(torch.LongTensor) # LongTensor64位Integer
x, y = Variable(x), Variable(y)
plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=y.data.numpy(), s=100, lw=0, cmap='RdYlGn')
plt.show()
接下来仍然是定义类:
class Net(nn.Module):
"""
这是一个最简单的一层的拟合的神经网络。hidden是隐藏层,predict是预测层
"""
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__() # 继承Net到nn.module
# 定义层,nn.Linear(in_features,out_features),Linear第一个参数都是层左边输入的特征数,第二个参数是层右边输出的特征数
self.hidden = nn.Linear(n_feature, n_hidden) # 一层隐藏层,n_feature个特征,n_hidden个神经元
self.predict = nn.Linear(n_hidden, n_output) # 预测层,n_hidden个神经元,n_output个特征的输出
def forward(self, x):
x = F.relu(self.hidden(x)) # x经过一个隐藏层,然后再被一个relu函数激活一下
x = self.predict(x)
return x
然后进行训练。这里打印出第99次训练的损失函数值,然后再对net的output进行一次计算损失函数值,这两个值是相等的,说明net确实存储了训练好的参数:
def run_Net():
net = Net(2, 10, 2) # 第一个参数是输入特征,第二个特征是神经元数,第三个特征是输出的特征(y是两个特征0和1所以输出特征为2)
optimizer = torch.optim.SGD(net.parameters(), lr=0.2) # 学习速率慢一点效果更好
loss_func = nn.CrossEntropyLoss() # 交叉熵损失函数,判断期望输出与实际输出的接近程度,交叉熵值越小两个的概率分布越接近
for i in range(100):
prediction = net(x)
loss = loss_func(prediction, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if i == 99:
print(loss)
print(loss_func(net(x), y))
run_Net()
>> tensor(0.0065, grad_fn=<NllLossBackward>)
>> tensor(0.0065, grad_fn=<NllLossBackward>)
3 快速搭建与保存提取
以上面的回归为例,上面的回归采用的是通过一个class来定义一个Net类。torch中还可以使用其他方法来快速搭建一个神经网络。
# 上面的回归是:net = Net(1, 10, 1)
# 使用Sequential构造一个与net相同构造的net1
net1 = nn.Sequential(
nn.Linear(1, 10),
nn.ReLU(), # 额外升级一下在第一个层后再加一个ReLU
nn.Linear(10, 1)
)
# 如果想为每层起个名字:
model = nn.Sequential(
collections.OrderedDict([ # 有序字典 传入字典形式的话就需要是有序字典,得用OrderedDict
('hidden layer', nn.Linear(1, 10)),
('activating layer', nn.ReLU()),
('output layer', nn.Linear(10, 1)),
]))
对于训练好的神经网络,,有时候我们想保存它留到下次要用的时候直接提取使用。使用上面的快速搭建出来的net1进行训练并进行保存,torch提供了2种途径来保存,分别为:
def save():
# 建立一个和上面一样的神经网络,然后保存
# 如果想看自己搭的神经元的层结构,直接print就行了
optimizer = torch.optim.SGD(net1.parameters(), lr=0.5) # 优化器用来训练神经网络,设置为随机梯度下降,lr是学习效率
loss_func = nn.MSELoss() # 定义损失函数为均方误差
for t in range(100):
prediction = net1(x)
loss = loss_func(prediction, y) # 计算预测值和真实值的误差,注意预测值在前
print(loss.data.numpy())
optimizer.zero_grad() # zero_grad将net中所有参数的梯度全部降为0(将梯度初始化为0),因为每次计算loss梯度都会保留在net和优化器中
loss.backward() # 反向传递,也是一个Variable
optimizer.step() # 用优化器优化梯度
# torch提供的2种保存方法
torch.save(net1, 'net.pkl') # 保存net的整个计算结果包括计算图
torch.save(net1.state_dict(), 'net_params.pkl') # 保留了net的整个参数
对于直接保存计算图和保存模型参数两种保存结果,提取方法如下:
def restore_net():
# 提取保存的神经网络net
net2 = torch.load('net.pkl')
return net2(x)
def restore_params():
# 新建一个神经网络,然后提取保存的参数net_params
net3 = Net(1, 10, 1)
net3.load_state_dict(torch.load('net_params.pkl'))
return net3(x)
对上面的两种提取方式与直接训练的结果进行对比:
save()
prediction = run_Net()
prediction1 = restore_net()
prediction2 = restore_net()
plt.subplot(131)
plt.title('PREDICTION')
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)
plt.subplot(132)
plt.title('PREDICTION1')
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction1.data.numpy(), 'r-', lw=5)
plt.subplot(133)
plt.title('PREDICTION2')
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction2.data.numpy(), 'r-', lw=5)
plt.show()
得到结果如图,可以看出结果基本是一致的,当然也存在不一致的情况,因为毕竟输出的时候又走了一遍神经网络。
结果图