分别用numpy,pytorch实现两层神经网络
在人工智能领域,深度学习,神经网络异军突起,成为了现在的主流研究对象。那么如果想从最基本的原理入手,我们可以先用numpy实现一个简单的两层神经网络,即只有一个中间层。
一个神经网络包括输入层,中间层,和输出层。
numpy
这一实现完全使用numpy来计算前向神经网络,损失函数,和反向传播。
首先导入numpy
import numpy as np
实现步骤:
1.创建输入、输出:
N, D_in, H, D_out = 64, 1000, 100, 10
#N表示输入数据个数,D_in表示输入神经元个数,H表示中间层个数,D_out表示输出神经元个数。
x = np.random.randn(N, D_in)#随机生成一个输入矩阵x
y = np.random.randn(N, D_out)#随机生成一个输出矩阵y
2.在创建好输入,输出后,进行神经网络的搭建:
设定学习率
learning_rate = 1e-6
由图可知,y=wX+b。
w为各个信号的权重参数,b叫做偏置,用于控制神经元被激活的容易程度。
说到这里,我们就不得不提到激活函数的概念:
若激活函数为h(x),则y可表示为h(wX+b)。常用激活函数有sigmoid函数,如图所示
激活函数的作用就是激活神经元:输出y达到某个阙值后,神经元激活。
根据公式搭建神经网络:
h = x.dot(w1) #dot为矩阵乘法
h_relu = np.maximum(h, 0) #激活函数relu
y_pred = h_relu.dot(w2) #输出
计算损失函数:
神经网络的学习通过某个指标表示现在的状态,然后,以这个指标为基准,寻找最优权重参数。这个指标成为损失函数。常用的损失函数有均方误差,和交叉熵误差。
设定损失函数为均方误差计算
loss = np.square(y_pred - y).sum()
反向计算误差,更新权重:
# loss = (y_pred - y) ** 2
grad_y_pred = 2.0 * (y_pred - y) #求y_pred的梯度
grad_w2 = h_relu.T.dot(grad_y_pred) #求第二层权重梯度
grad_h_relu = grad_y_pred.dot(w2.T)
grad_h = grad_h_relu.copy()
grad_h[h < 0] = 0
grad_w1 = x.T.dot(grad_h)
更新权重:
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
3.打印损失函数:
print(t, loss)
将以上流程循环500次,得到损失函数为:6.742494858156187e-06
附整体代码:
import numpy as np
N, D_in, H, D_out = 64, 1000, 100, 10
x = np.random.randn(N, D_in)
y = np.random.randn(N, D_out)
w1 = np.random.randn(D_in, H)
w2 = np.random.randn(H, D_out)
learning_rate = 1e-6
for t in range(500):
h = x.dot(w1)
h_relu = np.maximum(h, 0)
y_pred = h_relu.dot(w2)
loss = np.square(y_pred - y).sum()
print(t, loss)
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h_relu.T.dot(grad_y_pred)
grad_h_relu = grad_y_pred.dot(w2.T)
grad_h = grad_h_relu.copy()
grad_h[h < 0] = 0
grad_w1 = x.T.dot(grad_h)
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
pytorch
如果要用pytorch,首先要导入torch,没有的可以在官网上用pip install 下载
import torch
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # Uncomment this to run on GPU
# N 是 batch size; D_in 是 input dimension;
# H 是 hidden dimension; D_out 是 output dimension.
N, D_in, H, D_out = 64, 1000, 100, 10
# 创建随机的Tensor来保存输入和输出
# 设定requires_grad=False表示在反向传播的时候我们不需要计算gradient
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
# 创建随机的Tensor和权重。
# 设置requires_grad=True表示我们希望反向传播的时候计算Tensor的gradient
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
# 前向传播:通过Tensor预测y;这个和普通的神经网络的前向传播没有任何不同,
# 但是我们不需要保存网络的中间运算结果,因为我们不需要手动计算反向传播。
y_pred = x.mm(w1).clamp(min=0).mm(w2)
# 通过前向传播计算loss
# loss是一个形状为(1,)的Tensor
# loss.item()可以给我们返回一个loss的scalar
loss = (y_pred - y).pow(2).sum()
print(t, loss.item())
# PyTorch给我们提供了autograd的方法做反向传播。如果一个Tensor的requires_grad=True,
# backward会自动计算loss相对于每个Tensor的gradient。在backward之后,
# w1.grad和w2.grad会包含两个loss相对于两个Tensor的gradient信息。
loss.backward()
# 我们可以手动做gradient descent(后面我们会介绍自动的方法)。
# 用torch.no_grad()包含以下statements,因为w1和w2都是requires_grad=True,
# 但是在更新weights之后我们并不需要再做autograd。
# 另一种方法是在weight.data和weight.grad.data上做操作,这样就不会对grad产生影响。
# tensor.data会我们一个tensor,这个tensor和原来的tensor指向相同的内存空间,
# 但是不会记录计算图的历史。
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
# Manually zero the gradients after updating weights
w1.grad.zero_()
w2.grad.zero_()
在此基础上,使用pytorch的nn和optim库可以帮助我们更方便地构建神经网络和更新参数:
import torch
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
# 使用nn来定义前向神经网络以及损失函数
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),
)
loss_fn = torch.nn.MSELoss(reduction='sum')
# 使用optim来定义可以更新权重的optimizer,这里使用了Adam算法
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for t in range(500):
# 通过神经网络预测y
y_pred = model(x)
# 计算并输出损失函数
loss = loss_fn(y_pred, y)
print(t, loss.item())
#此步骤为清除累计的梯度
optimizer.zero_grad()
# Backward pass: compute gradient of the loss with respect to model
# parameters
loss.backward()
# Calling the step function on an Optimizer makes an update to its
# parameters
optimizer.step()