Pytorch搭建神经网络
作者:ZZY
1. 搭建一个简单的神经网络
1.1 导入Pytorch
import torch
1.2 初始化参数
首先我们明确这次搭建的背景:希望将若干个二位平面的点分为两类。
对于平面上的点,我们将其x轴,y轴作为输入数据的特征。对于将要被分为的两类作为输出的节点。对于隐层,将其特征数量设置为50,为了将低维数据映射高维,便于分类的实现。(这里只是我自己的想法,欢迎纠正)
data_nums = 100 #输入训练集个数
input_data = 2 #每个训练集的特征数量
output_data = 2 #输出的分类数量
hidden_layer = 50 #隐层的特征数量
epoch_max = 50 #最大迭代次数
learning_rate = 1e-6 #学习速度(dx)
1.3 生成数据
根据神经网络的模型,我们得以得到四个需要的数据,输入数据集x,输出标签y,由输入层到隐层的参数w1,由隐层到输出层参数w2。根据四个数据的矩阵维度,随机化生成。
x = torch.randn(data_nums, input_data)
y = torch.randn(data_nums, output_data)
w1 = torch.randn(input_data, hidden_layer)
w2 = torch.randn(hidden_layer, output_data)
1.4 通过梯度下降优化神经网络
for epoch in range(0, epoch_max):
h1 = x.mm(w1)
h1.clamp_(min=0) #模拟激活函数RuLu
Y = h1.mm(w2)
loss = (Y-y).pow(2).sum()
print("Eopch:{}, Loss:{:.4f}".format(epoch+1, loss))
'''
h1 = x*w1,y = h1*w2,loss = f(y)
d(loss)/d(w2) = d(loss)/d(y) * d(y)/d(w2)
d(loss)/d(w1) = d(loss)/d(y) * d(y)/d(h1) * d(h1)/d(w1)
d(loss)/d(y) = 2*(Y-y)
d(y)/d(w2) = h1
d(y)/d(h1) = w2
d(h1)/d(w1) = x
'''
gray_Y = 2*(Y-y)
gray_w2 = h1.t().mm(gray_Y)
gray_h = gray_Y.clone()
gray_h = gray_h.mm(w2.t())
gray_h.clamp_(min=0)
gray_w1 = x.t().mm(gray_h)
w1 -= learning_rate*gray_w1
w2 -= learning_rate*gray_w2
上述代码中,通过FOR循环进行迭代模拟。
通过矩阵相乘模拟了前向传播,得到了预测结果。其中通过torch.clamp对矩阵进行裁剪,来模拟RuLe函数,将线性变成非线性。更有利于分类问题的实现。
其中loss函数定义为最常见的平方差和函数。然后根据链式反应写出对参数w1,w2的求导方程完成反向传播对参数的优化。对w1,w2求导的方法在代码注释部分已经给出。
1.5 打印结果
Eopch:1, Loss:11480.7578
Eopch:2, Loss:11365.5840
Eopch:3, Loss:11251.7920
Eopch:4, Loss:11139.1611
Eopch:5, Loss:11027.8447
Eopch:6, Loss:10917.8174
Eopch:7, Loss:10809.0947
Eopch:8, Loss:10701.6602
Eopch:9, Loss:10595.4980
Eopch:10, Loss:10490.5674
...
Eopch:45, Loss:7501.0620
Eopch:46, Loss:7432.1606
Eopch:47, Loss:7363.9067
Eopch:48, Loss:7296.3892
Eopch:49, Loss:7229.5928
Eopch:50, Loss:7163.5239
Process finishe d with exit code 0
通过上述打印结果我们可以发现,loss函数在不断的减小。说明根据神经网络优化参数后的模型,得到的预测值越来越准确。
1.6 完整代码
import torch
data_nums = 100 #输入训练集个数
input_data = 2 #每个训练集的特征数量
output_data = 2 #输出的分类数量
hidden_layer = 50 #隐层的特征数量
epoch_max = 50 #最大迭代次数
learning_rate = 1e-6 #学习速度(dx)
x = torch.randn(data_nums, input_data)
y = torch.randn(data_nums, output_data)
w1 = torch.randn(input_data, hidden_layer)
w2 = torch.randn(hidden_layer, output_data)
for epoch in range(0, epoch_max):
h1 = x.mm(w1)
h1.clamp_(min=0) #模拟激活函数RuLu
Y = h1.mm(w2)
loss = (Y-y).pow(2).sum()
print("Eopch:{}, Loss:{:.4f}".format(epoch+1, loss))
'''
h1 = x*w1,y = h1*w2,loss = f(y)
d(loss)/d(w2) = d(loss)/d(y) * d(y)/d(w2)
d(loss)/d(w1) = d(loss)/d(y) * d(y)/d(h1) * d(h1)/d(w1)
d(loss)/d(y) = 2*(Y-y)
d(y)/d(w2) = h1
d(y)/d(h1) = w2
d(h1)/d(w1) = x
'''
gray_Y = 2*(Y-y)
gray_w2 = h1.t().mm(gray_Y)
gray_h = gray_Y.clone()
gray_h = gray_h.mm(w2.t())
gray_h.clamp_(min=0)
gray_w1 = x.t().mm(gray_h)
w1 -= learning_rate*gray_w1
w2 -= learning_rate*gray_w2
2. 利用pytorch中的包对代码进行“减负”
相信写完上面的代码后,对于链式反应的部分觉得十分的麻烦,不但需要自己在草稿纸上推演,在代码实现中也需要小心翼翼。特别是当隐层的数量增加之后,复杂的数学方程式会大大减慢搬砖的速度。
Pytorch中的torch.autograd提供了自动计算梯度的方法,可以让模型参数自动计算在优化过程中需要的梯度值。大大提升了搬砖速度,减少了“重复造轮子”的冗余过程。
2.1 torch.autograd.Variable
通过Pytorch建立神经网络,获取梯度信息主要为以下几个阶段:
- 构建一个计算图,用Variable将Tensor包装,形成计算图中的节点。
- 通过.backward()来自动计算出所有需要的梯度
- 来针对某个变量x执行x.grad获得想要的梯度。
2.1.1 Variable
autograd.Variable 是autograd中最核心的类。 它包装了一个Tensor,并且几乎支持所有在其上定义的操作。一旦完成了你的运算,你可以调用 .backward()来自动计算出所有的梯度。
在Pytorch的一个重要特点就是动态计算图(Dynamic Computational Graphs)。计算图中每一个节点代表一个Variable,Variable之间的运算可以修改。
Variable用来构建一个计算图中的节点。将Tensor包装转换成Variable后,该Tensor就成了计算图中的一个节点。对于该节点,如上图中所示,有两个特性:
- .data:该节点的Tensor数据。
- .grad:获得该节点的梯度信息。
而对于Variable两个参数“requires_grad”和“grad_fn“,与创建人有关。
- requires_grad:表示该Variable是否需要计算梯度值,有True和Flase两个值。默认为False。
- grad_fn:得知该变量是否是一个计算结果,比如人为创建的Variable x中grad_fn = None,但是y=x+2中y的grad_fn是存在的,指向这个该函数y=x+2的相关对象。
来源:https://zhuanlan.zhihu.com/p/29904755
2.2 构建神经网络
2.2.1 导入库
对比开始,多从导入Variable
import torch
from torch.autograd import Variable
2.2.2 生成Variable
x = Variable(torch.randn(data_nums, input_data), requires_grad=False)
y = Variable(torch.randn(data_nums, output_data))
#y中没有初始化requires_gread参数,因为其默认值为False,效果和Variable x一样。
w1 = Variable(torch.randn(input_data, hidden_layer), requires_grad=True)
w2 = Variable(torch.randn(hidden_layer, output_data), requires_grad=True)
2.2.3 优化梯度下降代码
for epoch in range(0, epoch_max):
Y = x.mm(w1).clamp(min=0).mm(w2)
loss = (Y-y).pow(2).sum()
print("Eopch:{}, Loss:{:.4f}".format(epoch + 1, loss.data))
loss.backward()
w1.data -= learning_rate * w1.grad.data
w2.data -= learning_rate * w2.grad.data
w1.grad.data.zero_()
w2.grad.data.zero_()