一、按原理手动搭建神经网络

 

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