前阵子540实验室的前辈们给我们出了一些题目,督促我们学习,以下是题目要求:
1.用pytorch 实现两层神经网络拟合,不使用model。题目:一个全连接的ReLU神经网络,一个隐藏层,没有bias,用来从x预测y。
(可自动求导)
2.用numpy 实现两层神经网络。题目:一个全连接的ReLU神经网络,一个隐藏层,没有bias,用来从x预测y。(不可自动求导)
3.用pytorch 实现两层神经网络,不使用model。题目:一个全连接的ReLU神经网络,一个隐藏层,没有bias,用来从x预测y。(不可自动求导)
这个三道好像是经典例题,算法和思想是一致的,题目之间的区别就只是所用工具,直接先去b站看吴恩达先生的视频,P44这里,重点看一下P51那个反向传播算法。
这是这个模型的图片
第一层是输入层X,样本数量为 N=64,样本维度为 D_in=1000;第二层为隐藏层H(因为在机器跑代码的时候我们看不到这个层上面的数据变化,看不见=隐藏。。。。。。),维度为 h=100;第三层为输出层Y,输出维度为 D_out=10.
输出层和隐藏层个点之间连线上的权为W1,矩阵的大小为(D_in,H),隐藏层和输出层之间的权为W2,大小为(H,D_out)
首先是数据准备,在torch使用.randn()生成,标准正态分布。
#数据准备
X = torch.randn(N,D_in)#输入
y = torch.randn(N,D_out)#输出
W1 = torch.randn(D_in,H,requires_grad=True)#权值
W2 = torch.randn(H,D_out,requires_grad=True)#权值
#自动求导才需要下面这段
W1 = Variable(W1,requires_grad=True)#封装
W2 = Variable(W2,requires_grad=True)#封装
#参与自动求导发生改变的数据封装后才能进行自动求导
设定学习率 LEARNING_RATE = 1e-6,训练轮次 EPOCH = 500。
先进行前向传播
h = X * W1#隐藏层输入为 NH
h_relu = relu(h)#激活函数,大于0的数字表述神经元兴奋,小于等于0表示神经元抑制
y_pred = h_relu * W2#输出维度为 ND_in
#接下来的操作使用了BP算法的思想(根据预测值与真实值之间的误差来反向改变权重,神经元若有阈值也可以通过计算被改变,进而得到误差率更小的模型参数,优化模型)
计算损失值
loss = (y_pred - y)^2
计算反向传播
// 反向传播代码段及各函数意义
grad_y_pred = 2.0 * (y_pred-y) # 64*10
grad_W2 = h_relu.t().mm(grad_y_pred)#torch.t() 矩阵转置 100*10
grad_h_relu = grad_y_pred.mm(W2.t()) # 64*100
grad_h = grad_h_relu.clone()#torch.clone() 复制一份tensor(新的内存空间) 64*100
grad_h[h<0] = 0#grad_h[h < 0] 小于0的值置为0 64*100
grad_W1 = X.t().mm(grad_h) #1000 *100
最后便是权重的更新,使用随机梯度下降
//update of weight
W1 = W1 - LEARNING_RATE * grad_W1
W2 = W2 - LEARNING_RATE * grad_W2
题目一的代码
// pytorch + 自动求导
import numpy
import torch
from torch.autograd import Variable
N,D_in,H,D_out = 64,1000,100,10 # N代表的是样本个数,D_in是样本的维度,H是隐藏层的维度,D_out是输出层的维度\n",
#数据的准备
X = torch.randn(N,D_in)#输入
y = torch.randn(N,D_out)#输出
W1 = torch.randn(D_in,H,requires_grad=True)#权值
W2 = torch.randn(H,D_out,requires_grad=True)#权值
W1 = Variable(W1,requires_grad=True)#封装
W2 = Variable(W2,requires_grad=True)#封装
#学习率和训练轮次
LEARNING_RATE = 1e-6
EPOCH = 500
for t in range(EPOCH):
y_pred = X.mm(W1).clamp(min=0).mm(W2)#torch.mm() 矩阵相乘;torch.clamp() 将元素限制在【0,max】范围里,返回的是张量
loss = (y_pred - y).pow(2).sum()#torch.pow() 每个元素平方;torch.sum() 所有元素的和
print(t,loss.item())#torch.item() 将张量转换成标量
loss.backward()#反向传播,计算当前梯度
with torch.no_grad():#接下来的计算不被记录,不需要创建计算图
W1 -= LEARNING_RATE * W1.grad
W2 -= LEARNING_RATE * W2.grad
W1.grad.zero_()#梯度清零,不然会累加
W2.grad.zero_()#梯度清零,不然会累加
题目二的代码
// numpy + 手动求导
import numpy
N,D_in,H,D_out = 64,1000,100,10 # N代表的是样本个数,D_in是样本的维度,H是隐藏层的维度,D_out是输出层的维度\n",
#数据准备
X = numpy.random.randn(N, D_in)#输入
y = numpy.random.randn(N, D_out)#输出
W1 = numpy.random.randn(D_in, H)#权值
W2 = numpy.random.randn(H, D_out)#权值
#学习率和训练轮次
LEARNING_RATE = 1e-6
EPOCH = 500
for t in range(EPOCH):
h = X.dot(W1)# h = W1 * X numpy.dot() 矩阵相乘
h_relu = numpy.maximum(0, h)# h_relu = relu(h) numpy.maximu() 把元素限制在【0,max】范围内
y_pred = h_relu.dot(W2)# y_pred = W2 * h_relu
loss = numpy.square(y_pred - y).sum()#numpy.square() 元素平方
print(t, loss)
grad_y_pred = 2.0 * (y_pred - y)
grad_W2 = h_relu.T.dot(grad_y_pred)#numpy.T() 矩阵转置
grad_h_relu = grad_y_pred.dot(W2.T)
grad_h = grad_h_relu.copy()#numpy.copy() 复制一份ndarray(新的内存空间)
grad_h[h < 0] = 0#grad_h[h < 0] 小于0的值置为0
grad_W1 = X.T.dot(grad_h)
W1 = W1 - LEARNING_RATE * grad_W1
W2 = W2 - LEARNING_RATE * grad_W2
题目三的代码
// pytorch + 手动求导
import torch
N,D_in,H,D_out = 64,1000,100,10 # N代表的是样本个数,D_in是样本的维度,H是隐藏层的维度,D_out是输出层的维度\n",
#数据准备
X = torch.randn(N,D_in)#输入
y = torch.randn(N,D_out)#输出
W1 = torch.randn(D_in,H,requires_grad=True)#权值
W2 = torch.randn(H,D_out,requires_grad=True)#权值
#学习率和训练轮次
LEARNING_RATE = 1e-6
EPOCH = 500
for t in range(EPOCH):
h = X.mm(W1)# h = W1 * X
h_relu = h.clamp(min=0)# h_relu = relu(h)
y_pred = h_relu.mm(W2)# y_pred = W2 * h_relu
loss = (y_pred-y).pow(2).sum().item()#平方求和再转换成标量
print(t,loss)
grad_y_pred = 2.0 * (y_pred-y) # 64*10
grad_W2 = h_relu.t().mm(grad_y_pred)#torch.t() 矩阵转置 100*10
grad_h_relu = grad_y_pred.mm(W2.t()) # 64*100
grad_h = grad_h_relu.clone()#torch.clone() 复制一份tensor(新的内存空间) 64*100
grad_h[h<0] = 0#grad_h[h < 0] 小于0的值置为0 64*100
grad_W1 = X.t().mm(grad_h) #1000 *100
#随机梯度下降
W1 = W1 - LEARNING_RATE * grad_W1
W2 = W2 - LEARNING_RATE * grad_W2
没有解决的问题
1.发现题目一梯度更新这一块其实可以写成
#梯度更新
#with torch.no_grad():#接下来的计算不被记录,不需要创建计算图
W1.data -= LEARNING_RATE * W1.grad.data
W2.data -= LEARNING_RATE * W2.grad.data
W1.grad.data.zero_() # 梯度清零,不然会累加
W2.grad.data.zero_() # 梯度清零,不然会累加
网上的解释为将variable参数从网络中隔离开,不参与参数更新,个人的理解是为了防止权重更新步骤进行反向传播。
心得
手动求导还行,自动求导这里磨了半天,我觉得不行