1.1 定义数据batch大小,输入输出特征,神经网络特征大小
import torch
batch_n = 100 # 一个批次输入数据的数量
hidden_layer = 100 # 经过隐藏层后保留的特征个数
input_data = 1000 # 每个输入数据的特征个数
output_data = 10 # 每个输出数据的特征个数(分类结果值)
# 1000 -> 100 -> 10
1.2 定义数据集,网络权重
# 定义一个批次大小的输入和输出数据(真实数据)
x = torch.randn(batch_n, input_data) # 100*1000
y = torch.randn(batch_n, output_data) # 100*10
# 定义权重参数(随机初始化参数)
w1 = torch.randn(input_data, hidden_layer) # 1000*100
w2 = torch.randn(hidden_layer, output_data) # 100*10
1.3 定义学习次数和学习速率
epoch_n = 20 # 学习次数
learning_rate = 1e-6 # 学习速率
1.4 进行前向和后向传播计算
# 采用梯度下降优化参数
for epoch in range(epoch_n):
# 前向传播计算
h1 = x.mm(w1) # 100*1000 -> 100*100
h1 = h1.clamp(min=0) # ReLU激活函数作用
y_pred = h1.mm(w2) # 100*100 -> 100*10 (预测结果)
loss = (y_pred - y).pow(2).sum() # 均方误差损失函数(预测值与真实值之差)
print("Epoch:{}, Loss:{:.4f}".format(epoch, loss))
# 后向传播计算(梯度下降)
grad_y_pred = 2*(y_pred - y)
grea_w2 = h1.t().mm(grad_y_pred)
grad_h = grad_y_pred.clone()
grad_h = grad_h.mm(w2.t())
grad_h.clamp_(min=0)
grad_w1 = x.t().mm(grad_h)
# 权重更新
w1 -= learning_rate*grad_w1
w2 -= learning_rate*grea_w2
通过使用torch.autograd包,可以自动计算梯度值(主要是链式求导),降低代码复杂度
2.1 改动:引入一个新的包
import torch
from torch.autograd import Variable
batch_n = 100 # 一个批次输入数据的数量
hidden_layer = 100 # 经过隐藏层后保留的特征个数
input_data = 1000 # 每个输入数据的特征个数
output_data = 10 # 每个输出数据的特征个数(分类结果值)
# 1000 -> 100 -> 10
2.2 改动:Tensor数据转化为Variable类
# 定义一个批次大小的输入和输出数据(真实数据)
# 将Tensor数据封装成Variable类,requires_grad表示是否保留梯度值
x = Variable(torch.randn(batch_n, input_data), requires_grad=False) # 100*1000
y = Variable(torch.randn(batch_n, output_data), requires_grad=False) # 100*10
# 定义权重参数(随机初始化参数)
w1 = Variable(torch.randn(input_data, hidden_layer), requires_grad=True) # 1000*100
w2 = Variable(torch.randn(hidden_layer, output_data), requires_grad=True) # 100*10
2.3 保持与1.3相同
2.4 改动:使用自动梯度下降简化运算
# 采用梯度下降优化参数
for epoch in range(epoch_n):
# 前向传播计算
# h1 = x.mm(w1) # 100*1000 -> 100*100
# h1 = h1.clamp(min=0) # ReLU激活函数作用
# y_pred = h1.mm(w2) # 100*100 -> 100*10 (预测结果)
y_pred = x.mm(w1).clamp(min=0).mm(w2)
loss = (y_pred - y).pow(2).sum() # 均方误差损失函数(预测值与真实值之差)
print("Epoch:{}, Loss:{:.4f}".format(epoch, loss.item()))
# 后向传播计算(梯度下降)
loss.backward()
# 权重更新
w1.data -= learning_rate*w1.grad.data
w2.data -= learning_rate*w2.grad.data
# 梯度值如果不置零会一直累加
w1.grad.data.zero_()
w2.grad.data.zero_()
我们还可以通过重写torch.nn.Module中的forward和backward方法来实现自定义传播函数
3.1-3.3 保持与 2.1-2.3相同
3.4 建立继承torch.nn.Module的类
class Model(torch.nn.Module):
def __init__(self):
super(Model, self).__init__()
def forward(self, input, w1, w2):
x = torch.mm(input, w1)
x = torch.clamp(x, min=0)
x = torch.mm(x, w2)
return x
def backward(self):
pass
model = Model()
3.5 改动:前向传播函数改动
# 采用梯度下降优化参数
for epoch in range(epoch_n):
#y_pred = x.mm(w1).clamp(min=0).mm(w2)
y_pred = model(x, w1, w2)
loss = (y_pred - y).pow(2).sum() # 均方误差损失函数(预测值与真实值之差)
print("Epoch:{}, Loss:{:.4f}".format(epoch, loss.item()))
# 后向传播计算(梯度下降)
loss.backward()
# 权重更新
w1.data -= learning_rate*w1.grad.data
w2.data -= learning_rate*w2.grad.data
# 梯度值如果不置零会一直累加
w1.grad.data.zero_()
w2.grad.data.zero_()
四、模型搭建代码优化
4.1 torch.nn.Sequential
一种序列容器,可以直接嵌套或者通过传入orderdict有序字典的方式进行传入
前者模型名称按照序号排列,后者可以按照字典中键值指定名称
4.2 torch.nn.Linear
线性层,对应模型中的线性变换,三个参数:输入特征数、输出特征数、是否使用偏置(默认使用)
4.3 torch.nn.ReLU
ReLU非线性激活层,常用的有PReLU、LeakyReLU、Tanh、Sigmoid、Softmax
4.4 常用损失函数
torch.nn.MSELoss :均方误差损失函数
torch.nn.L1Loss :平均绝对误差函数
torch.nn.CrossEntropyLoss :交叉熵损失函数
4.5 使用上述函数简化后的代码
import torch
from torch.autograd import Variable
batch_n = 100 # 一个批次输入数据的数量
hidden_layer = 100 # 经过隐藏层后保留的特征个数
input_data = 1000 # 每个输入数据的特征个数
output_data = 10 # 每个输出数据的特征个数(分类结果值)
# 1000 -> 100 -> 10
# 定义一个批次大小的输入和输出数据(真实数据)
# 将Tensor数据封装成Variable类,requires_grad表示是否保留梯度值
x = Variable(torch.randn(batch_n, input_data), requires_grad=False) # 100*1000
y = Variable(torch.randn(batch_n, output_data), requires_grad=False) # 100*10
# 定义神经网络模型具体结构
models = torch.nn.Sequential(
torch.nn.Linear(input_data, hidden_layer), # 输入曾到隐藏层的线性变换
torch.nn.ReLU(), # 激活函数
torch.nn.Linear(hidden_layer, output_data) # 隐藏层到输出层的线性变换
)
epoch_n = 20000 # 学习次数(由于20轮太少无法看到效果,所以设为20000轮)
learning_rate = 1e-6 # 学习速率
loss_fn = torch.nn.MSELoss() # 计算均方误差
# 采用梯度下降优化参数
for epoch in range(epoch_n):
y_pred = models(x)
loss = loss_fn(y_pred, y) # 均方误差损失函数(预测值与真实值之差)
if(epoch % 1000 == 0):
print("Epoch:{}, Loss:{:.4f}".format(epoch, loss.item()))
# 梯度值如果不置零会一直累加
models.zero_grad()
# 后向传播计算(梯度下降)
loss.backward()
# 权重更新
for param in models.parameters():
param.data -= param.grad.data*learning_rate
结果如下:
Epoch:0, Loss:1.1477
Epoch:1000, Loss:1.1468
Epoch:2000, Loss:1.1459
Epoch:3000, Loss:1.1450
Epoch:4000, Loss:1.1441
Epoch:5000, Loss:1.1432
Epoch:6000, Loss:1.1422
Epoch:7000, Loss:1.1413
Epoch:8000, Loss:1.1404
Epoch:9000, Loss:1.1395
Epoch:10000, Loss:1.1386
Epoch:11000, Loss:1.1377
Epoch:12000, Loss:1.1368
Epoch:13000, Loss:1.1359
Epoch:14000, Loss:1.1350
Epoch:15000, Loss:1.1341
Epoch:16000, Loss:1.1332
Epoch:17000, Loss:1.1323
Epoch:18000, Loss:1.1314
Epoch:19000, Loss:1.1305
4.6 自动进行参数更新和优化——torch.optim
如SGD、AdaGrad、RMSProp、Adam,可以根据梯度更新自动使用学习率
代码更新如下:
...
epoch_n = 20 # 学习次数
learning_rate = 1e-4 # 学习速率(为了体现出自动优化函数的卓越性)
loss_fn = torch.nn.MSELoss() # 计算均方误差
optimzer = torch.optim.Adam(models.parameters(), lr=learning_rate) # 需要优化的参数、初始学习率
# 采用梯度下降优化参数
for epoch in range(epoch_n):
y_pred = models(x)
loss = loss_fn(y_pred, y) # 均方误差损失函数(预测值与真实值之差)
print("Epoch:{}, Loss:{:.4f}".format(epoch, loss.item()))
# 梯度值如果不置零会一直累加
optimzer.zero_grad()
# 后向传播计算(梯度下降)
loss.backward()
# 权重更新
optimzer.step()
运行结果:
Epoch:0, Loss:0.9943
Epoch:1, Loss:0.9739
Epoch:2, Loss:0.9539
Epoch:3, Loss:0.9344
Epoch:4, Loss:0.9153
Epoch:5, Loss:0.8966
Epoch:6, Loss:0.8783
Epoch:7, Loss:0.8604
Epoch:8, Loss:0.8429
Epoch:9, Loss:0.8258
Epoch:10, Loss:0.8091
Epoch:11, Loss:0.7928
Epoch:12, Loss:0.7769
Epoch:13, Loss:0.7615
Epoch:14, Loss:0.7464
Epoch:15, Loss:0.7316
Epoch:16, Loss:0.7172
Epoch:17, Loss:0.7032
Epoch:18, Loss:0.6895
Epoch:19, Loss:0.6761