《动手学深度学习》笔记 1

文章目录[x]
  1. 1:线性回归
  2. 1.1:模型
  3. 1.2:数据集
  4. 1.3:损失函数
  5. 1.4:优化函数 - 随机梯度下降
  6. 1.5:矢量计算
  7. 1.6:线性回归模型的从0实现
  8. 1.7:线性回归模型使用pytorch的间接实现
  9. 1.8:两种实现方式的比较
  10. 1.9:课后作业
  11. 2:softmax和分类模型
  12. 2.1:基本概念
  13. 2.2:softmax函数操作
  14. 2.3:交叉熵损失函数
  15. 2.4:课后作业
  16. 3:多层感知机
  17. 3.1:表达公式
  18. 3.2:激活函数
  19. 3.3:关于激活函数的选择
  20. 3.4:多层感知机
  21. 3.5:多层感知机从零开始的实现
  22. 3.6:多层感知机pytorch实现
  23. 3.7:课后作业

本章学习内容

线性回归

softmax与分类模型

多层感知机

线性回归

模型

为了简单起见,这里我们假设价格只取决于房屋状况的两个因素,即面积(平方米)和房龄(年)。接下来我们希望探索价格与这两个因素的具体关系。线性回归假设输出与各个输入之间是线性关系:
price=?area⋅area+?age⋅age+?

数据集

我们通常收集一系列的真实数据,例如多栋房屋的真实售出价格和它们对应的面积和房龄。我们希望在这个数据上面寻找模型参数来使模型的预测价格与真实价格的误差最小。在机器学习术语里,该数据集被称为训练数据集(training data set)或训练集(training set),一栋房屋被称为一个样本(sample),其真实售出价格叫作标签(label),用来预测标签的两个因素叫作特征(feature)。特征用来表征样本的特点。

损失函数

在模型训练中,我们需要衡量价格预测值与真实值之间的误差。通常我们会选取一个非负数作为误差,且数值越小表示误差越小。一个常用的选择是平方函数。 它在评估索引为 ?i 的样本误差的表达式为
?(?)(?,?)=1/2(?̂(?)−?(?))2

优化函数 - 随机梯度下降

当模型和损失函数形式较为简单时,上面的误差最小化问题的解可以直接用公式表达出来。这类解叫作解析解(analytical solution)。本节使用的线性回归和平方误差刚好属于这个范畴。然而,大多数深度学习模型并没有解析解,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作数值解(numerical solution)。

在求数值解的优化算法中,小批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch)B,然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。

学习率: ?代表在每次优化中,能够学习的步长的大小

批量大小: B是小批量计算中的批量大小batch size

总结一下,优化函数的有以下两个步骤:
* (i)初始化模型参数,一般来说使用随机初始化;
* (ii)我们在数据上迭代多次,通过在负梯度方向移动参数来更新每个参数。

矢量计算

在模型训练或预测时,我们常常会同时处理多个数据样本并用到矢量计算。在介绍线性回归的矢量计算表达式之前,让我们先考虑对两个向量相加的两种方法。
  1. 向量相加的一种方法是,将这两个向量按元素逐一做标量加法。
  2. 向量相加的另一种方法是,将这两个向量直接做矢量加法。
import torch
import time

# init variable a, b as 1000 dimension vector
n = 1000
a = torch.ones(n)    #全1向量
b = torch.ones(n)    #全1向量

# define a timer class to record time
class Timer(object):    #记录运行时间
    """Record multiple running times."""
    def __init__(self):
        self.times = []
        self.start()

    def start(self):
        # start the timer
        self.start_time = time.time()

    def stop(self):
        # stop the timer and record time into a list
        self.times.append(time.time() - self.start_time)
        return self.times[-1]

    def avg(self):
        # calculate the average and return
        return sum(self.times)/len(self.times)

    def sum(self):
        # return the sum of recorded time
        return sum(self.times)

测试代码(for循环运算):

timer = Timer()
c = torch.zeros(n)
for i in range(n):
    c[i] = a[i] + b[i]
'%.5f sec' % timer.stop()

测试代码(torch矢量加法):

timer.start()
d = a + b
'%.5f sec' % timer.stop()

测试结果:

for循环运算:'0.01234 sec'

torch矢量加法:'0.00023 sec'

可以看出矢量加法更加快速,所以在程序中应尽量使用矢量加法

线性回归模型的从0实现

# import packages and modules
%matplotlib inline
import torch
from IPython import display
from matplotlib import pyplot as plt
import numpy as np
import random

print(torch.__version__)


#生成数据集
# set input feature number 
num_inputs = 2
# set example number
num_examples = 1000

# set true weight and bias in order to generate corresponded label
true_w = [2, -3.4]
true_b = 4.2

features = torch.randn(num_examples, num_inputs,
                      dtype=torch.float32)
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()),
                       dtype=torch.float32)

#数据可视化
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);

#数据集读取
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    random.shuffle(indices)  # random read 10 samples
    for i in range(0, num_examples, batch_size):
        j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)]) # the last time may be not enough for a whole batch
        yield  features.index_select(0, j), labels.index_select(0, j)
batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break

#初始化模型参数
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)
b = torch.zeros(1, dtype=torch.float32)

w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)

#定义模型
def linreg(X, w, b):
    return torch.mm(X, w) + b

#定义损失函数
def squared_loss(y_hat, y): 
    return (y_hat - y.view(y_hat.size())) ** 2 / 2

#定义优化函数(使用随机梯度下降)
def sgd(params, lr, batch_size): 
    for param in params:
        param.data -= lr * param.grad / batch_size # ues .data to operate param without gradient track

#训练
# super parameters init
lr = 0.03    #学习率
num_epochs = 5    #训练轮数

net = linreg    #网络(单层线性网络)
loss = squared_loss    #损失函数(均方误差)

# training
for epoch in range(num_epochs):  # training repeats num_epochs times
    # in each epoch, all the samples in dataset will be used once
    
    # X is the feature and y is the label of a batch sample
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y).sum()  
        # calculate the gradient of batch sample loss 
        l.backward()      #反向传播
        # using small batch random gradient descent to iter model parameters
        sgd([w, b], lr, batch_size)  
        # reset parameter gradient
        w.grad.data.zero_()    #参数梯度清零
        b.grad.data.zero_()    #参数梯度清零
    train_l = loss(net(features, w, b), labels)
    print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))    #损失计算

print(w, true_w, b, true_b)

结果:

tensor([[ 0.2283, -1.3385],
        [-0.0638,  2.1262],
        [-0.5749,  0.1402],
        [ 1.3457,  1.8772],
        [ 2.2382,  0.4933],
        [-1.1108, -1.5794],
        [ 1.0630, -1.4738],
        [ 0.3280,  1.2516],
        [-2.1495,  0.2578],
        [ 0.1909,  1.3809]]) 
 tensor([ 9.2142, -3.1686,  2.5827,  0.5482,  7.0052,  7.3645, 11.3082,  0.6031,
        -0.9727, -0.1117])
epoch 1, loss 0.044495
epoch 2, loss 0.000191
epoch 3, loss 0.000053
epoch 4, loss 0.000053
epoch 5, loss 0.000053
tensor([[ 1.9996],
        [-3.4002]], requires_grad=True) [2, -3.4] tensor([4.1996], requires_grad=True) 4.2

线性回归模型使用pytorch的间接实现

import torch
from torch import nn
import numpy as np
torch.manual_seed(1)

print(torch.__version__)
torch.set_default_tensor_type('torch.FloatTensor')

#生成数据集(与上方相同)
num_inputs = 2
num_examples = 1000

true_w = [2, -3.4]
true_b = 4.2

features = torch.tensor(np.random.normal(0, 1, (num_examples, num_inputs)), dtype=torch.float)
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)

#数据集读取
import torch.utils.data as Data

batch_size = 10

# combine featues and labels of dataset
dataset = Data.TensorDataset(features, labels)

# put dataset into DataLoader
data_iter = Data.DataLoader(
    dataset=dataset,            # torch TensorDataset format
    batch_size=batch_size,      # mini batch size
    shuffle=True,               # whether shuffle the data or not
    num_workers=2,              # read data in multithreading
)

for X, y in data_iter:
    print(X, '\n', y)
    break

#定义模型
class LinearNet(nn.Module):
    def __init__(self, n_feature):
        super(LinearNet, self).__init__()      # call father function to init 
        self.linear = nn.Linear(n_feature, 1)  # function prototype: `torch.nn.Linear(in_features, out_features, bias=True)`

    def forward(self, x):
        y = self.linear(x)
        return y
    
net = LinearNet(num_inputs)
print(net)

# ways to init a multilayer network
# method one
net = nn.Sequential(
    nn.Linear(num_inputs, 1)
    # other layers can be added here
    )

# method two
net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1))
# net.add_module ......

# method three
from collections import OrderedDict
net = nn.Sequential(OrderedDict([
          ('linear', nn.Linear(num_inputs, 1))
          # ......
        ]))

print(net)
print(net[0])

#初始化模型参数
from torch.nn import init

init.normal_(net[0].weight, mean=0.0, std=0.01)
init.constant_(net[0].bias, val=0.0)  # or you can use `net[0].bias.data.fill_(0)` to modify it directly

for param in net.parameters():
    print(param)

#定义损失函数
loss = nn.MSELoss()    # nn built-in squared loss function
                       # function prototype: `torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')`

#定义优化函数

optimizer = optim.SGD(net.parameters(), lr=0.03)   # built-in random gradient descent function
print(optimizer)  # function prototype: `torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False)`

#训练
num_epochs = 3
for epoch in range(1, num_epochs + 1):
    for X, y in data_iter:
        output = net(X)
        l = loss(output, y.view(-1, 1))
        optimizer.zero_grad() # reset gradient, equal to net.zero_grad()
        l.backward()
        optimizer.step()
    print('epoch %d, loss: %f' % (epoch, l.item()))

# result comparision
dense = net[0]
print(true_w, dense.weight.data)
print(true_b, dense.bias.data)

两种实现方式的比较

  1. 从零开始的实现(推荐用来学习)能够更好的理解模型和神经网络底层的原理
  2. 使用pytorch的简洁实现能够更加快速地完成模型的设计与实现

课后作业

选择题

1.假如你正在实现一个全连接层,全连接层的输入形状是7×8,输出形状是7×1,其中7是批量大小,则权重参数w和偏置参数b的形状分别是____和____(C)

2.

课程中的损失函数定义为:

def squared_loss(y_hat, y):
    return (y_hat - y.view(y_hat.size())) ** 2 / 2

将返回结果替换为下面的哪一个会导致会导致模型无法训练:(B)(阅读材料:https://pytorch.org/docs/stable/notes/broadcasting.html)

A.(y_hat.view(-1) - y) ** 2 / 2

B.(y_hat - y.view(-1)) ** 2 / 2

C.(y_hat - y.view(y_hat.shape)) ** 2 / 2

D.(y_hat - y.view(-1, 1)) ** 2 / 2

填空题

1.在线性回归模型中,对于某个大小为3的批量,标签的预测值和真实值如下表所示:

y
2.33 3.14
1.07 0.98
1.23 1.32

该批量的损失函数的平均值为:(0.112)(参考“线性回归模型从零开始的实现”中的“定义损失函数”一节,结果保留三位小数)


softmax和分类模型

基本概念

分类问题

一个简单的图像分类问题,输入图像的高和宽均为2像素,色彩为灰度。

图像中的4像素分别记为?1,?2,?3,?4x1,x2,x3,x4

假设真实标签为狗、猫或者鸡,这些标签对应的离散值为?1,?2,?3y1,y2,y3

我们通常使用离散的数值来表示类别,例如?1=1,?2=2,?3=3y1=1,y2=2,y3=3

权重矢量

神经网络图

下图用神经网络图描绘了上面的计算。softmax回归同线性回归一样,也是一个单层神经网络。由于每个输出?1,?2,?3o1,o2,o3的计算都要依赖于所有的输入?1,?2,?3,?4x1,x2,x3,x4,softmax回归的输出层也是一个全连接层。

softmax函数操作

交叉熵损失函数

平方损失估计:

交叉熵:

假设训练数据集的样本数为n,交叉熵损失函数定义为:

课后作业

选择题

1.softmax([100, 101, 102])的结果等于以下的哪一项(C)

A.softmax([10.0, 10.1, 10.2])

B.softmax([-100, -101, -102])

C.softmax([-2 -1, 0])

D.softmax([1000, 1010, 1020])

2.对于本节课的模型,在刚开始训练时,训练数据集上的准确率低于测试数据集上的准确率,原因是(C)

B.训练集的样本容量更大,要提高准确率更难

C.训练集上的准确率是在一个epoch的过程中计算得到的,测试集上的准确率是在一个epoch结束后计算得到的,后者的模型参数更优

多层感知机

下图展示了一个多层感知机的神经网络图,它含有一个隐藏层,该层中有5个隐藏单元。

表达公式

具体来说,给定一个小批量样本X∈Rn×d,其批量大小为n,输入个数为d。假设多层感知机只有一个隐藏层,其中隐藏单元个数为h。记隐藏层的输出(也称为隐藏层变量或隐藏变量)为H,有H∈Rn×h。因为隐藏层和输出层均是全连接层,可以设隐藏层的权重参数和偏差参数分别为Wh∈Rd×h和 bh∈R1×h,输出层的权重和偏差参数分别为Wo∈Rh×qbo∈R1×q

我们先来看一种含单隐藏层的多层感知机的设计。其输出O∈Rn×q的计算为

也就是将隐藏层的输出直接作为输出层的输入。如果将以上两个式子联立起来,可以得到

从联立后的式子可以看出,虽然神经网络引入了隐藏层,却依然等价于一个单层神经网络:其中输出层权重参数为WhWo,偏差参数为bhWo+bo。不难发现,即便再添加更多的隐藏层,以上设计依然只能与仅含输出层的单层神经网络等价。

激活函数

上述问题的根源在于全连接层只是对数据做仿射变换(affine transformation),而多个仿射变换的叠加仍然是一个仿射变换。解决问题的一个方法是引入非线性变换,例如对隐藏变量使用按元素运算的非线性函数进行变换,然后再作为下一个全连接层的输入。这个非线性函数被称为激活函数(activation function)。

下面是几个常用的激活函数:

ReLU函数

ReLU(rectified linear unit)函数提供了一个很简单的非线性变换。给定元素x,该函数定义为

ReLU(x)=max(x,0).

可以看出,ReLU函数只保留正数元素,并将负数元素清零。

Sigmoid函数

sigmoid函数可以将元素的值变换到0和1之间:

依据链式法则,sigmoid函数的导数

tanh函数

tanh(双曲正切)函数可以将元素的值变换到-1和1之间:

当输入接近0时,tanh函数接近线性变换。虽然该函数的形状和sigmoid函数的形状很像,但tanh函数在坐标系的原点上对称。

依据链式法则,tanh函数的导数:

关于激活函数的选择

ReLu函数是一个通用的激活函数,目前在大多数情况下使用。但是,ReLU函数只能在隐藏层中使用。

用于分类器时,sigmoid函数及其组合通常效果更好。由于梯度消失问题,有时要避免使用sigmoid和tanh函数。

在神经网络层数较多的时候,最好使用ReLu函数,ReLu函数比较简单计算量少,而sigmoid和tanh函数计算量大很多。

在选择激活函数的时候可以先选用ReLu函数如果效果不理想可以尝试其他激活函数。

多层感知机

多层感知机就是含有至少一个隐藏层的由全连接层组成的神经网络,且每个隐藏层的输出通过激活函数进行变换。多层感知机的层数和各隐藏层中隐藏单元个数都是超参数。以单隐藏层为例并沿用本节之前定义的符号,多层感知机按以下方式计算输出:

其中ϕ表示激活函数。

多层感知机从零开始的实现

import torch
import numpy as np
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
print(torch.__version__)

#获取训练集
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size,root='/home/kesci/input/FashionMNIST2065')

#定义模型参数
W1 = torch.tensor(np.random.normal(0, 0.01, (num_inputs, num_hiddens)), dtype=torch.float)
b1 = torch.zeros(num_hiddens, dtype=torch.float)
W2 = torch.tensor(np.random.normal(0, 0.01, (num_hiddens, num_outputs)), dtype=torch.float)
b2 = torch.zeros(num_outputs, dtype=torch.float)

params = [W1, b1, W2, b2]
for param in params:
    param.requires_grad_(requires_grad=True)

#定义激活函数
def relu(X):
    return torch.max(input=X, other=torch.tensor(0.0))

#定义网络
def net(X):
    X = X.view((-1, num_inputs))
    H = relu(torch.matmul(X, W1) + b1)
    return torch.matmul(H, W2) + b2

#定义损失函数
loss = torch.nn.CrossEntropyLoss()

#训练
num_epochs, lr = 5, 100.0
# def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size,
#               params=None, lr=None, optimizer=None):
#     for epoch in range(num_epochs):
#         train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
#         for X, y in train_iter:
#             y_hat = net(X)
#             l = loss(y_hat, y).sum()
#             
#             # 梯度清零
#             if optimizer is not None:
#                 optimizer.zero_grad()
#             elif params is not None and params[0].grad is not None:
#                 for param in params:
#                     param.grad.data.zero_()
#            
#             l.backward()
#             if optimizer is None:
#                 d2l.sgd(params, lr, batch_size)
#             else:
#                 optimizer.step()  # “softmax回归的简洁实现”一节将用到
#             
#             
#             train_l_sum += l.item()
#             train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
#             n += y.shape[0]
#         test_acc = evaluate_accuracy(test_iter, net)
#         print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
#               % (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))

d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, params, lr)

结果:

epoch 1, loss 0.0030, train acc 0.712, test acc 0.806
epoch 2, loss 0.0019, train acc 0.821, test acc 0.806
epoch 3, loss 0.0017, train acc 0.847, test acc 0.825
epoch 4, loss 0.0015, train acc 0.856, test acc 0.834
epoch 5, loss 0.0015, train acc 0.863, test acc 0.847

多层感知机pytorch实现

import torch
from torch import nn
from torch.nn import init
import numpy as np
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l

print(torch.__version__)

#初始化模型各个参数
num_inputs, num_outputs, num_hiddens = 784, 10, 256
    
net = nn.Sequential(
        d2l.FlattenLayer(),
        nn.Linear(num_inputs, num_hiddens),
        nn.ReLU(),
        nn.Linear(num_hiddens, num_outputs), 
        )
    
for params in net.parameters():
    init.normal_(params, mean=0, std=0.01)

#训练
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size,root='/home/kesci/input/FashionMNIST2065')
loss = torch.nn.CrossEntropyLoss()

optimizer = torch.optim.SGD(net.parameters(), lr=0.5)

num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)

结果:

epoch 1, loss 0.0031, train acc 0.701, test acc 0.774
epoch 2, loss 0.0019, train acc 0.821, test acc 0.806
epoch 3, loss 0.0017, train acc 0.841, test acc 0.805
epoch 4, loss 0.0015, train acc 0.855, test acc 0.834
epoch 5, loss 0.0014, train acc 0.866, test acc 0.840

课后作业

选择题

1.关于激活函数,以下说法中错误的是(B)

A.在多层感知机中引入激活函数的原因是,将多个无激活函数的线性层叠加起来,其表达能力与单个线性层相同

B.tanh可以由sigmoid平移伸缩得到,所以两者没有区别

C.相较于sigmoid和tanh,Relu的主要优势是计算效率高且不会出现梯度消失问题

D.如果我们需要网络输出范围是[0, 1],可以考虑使用sigmoid函数

填空题

对于只含有一个隐藏层的多层感知机,输入是的图片,隐藏单元个数是1000,输出类别个数是10,则模型的所有权重矩阵W_{i}的元素数量之和是(65546000)(256

点赞

发表评论