李沐《动手学深度学习》预备知识 张量操作及数据处理
李沐《动手学深度学习》预备知识 线性代数及微积分
教材:李沐《动手学深度学习》
随机梯度下降的公式表示:
(
w
,
b
)
←
(
w
,
b
)
?
η
∣
B
∣
∑
i
∈
B
?
(
w
,
b
)
l
(
i
)
(
w
,
b
)
(w,b)\leftarrow(w,b)-\frac{\eta}{|B|}\sum_{i\in B}\partial_{(w,b)}l^{(i)}(w,b)
(w,b)←(w,b)?∣B∣η?i∈B∑??(w,b)?l(i)(w,b)
在每次迭代中,先随机抽样一个小批量 B B B(由固定数量的训练样本组成),然后计算小批量的平均损失的导数(梯度),最后将梯度乘以一个预定的正数 η \eta η(学习率),并从当前参数的值中减掉。
批量大小和学习率的值通常是手动预先指定,而不是通过模型训练得到的。 这些可以调整但不在训练过程中更新的参数称为超参数。 调参(hyperparameter tuning)是选择超参数的过程。 超参数通常是我们根据训练迭代结果来调整的, 而训练迭代结果是在独立的验证数据集(validation dataset)上评估得到的。
在训练模型时,会利用线性代数库对计算进行矢量化,从而实现整个小批量样本的同时处理。
对比矢量化和for循环两种计算方法,会发现矢量化方法的计算时间短很多,代码实现如下:
相关库的准备:
%matplotlib inline
import math
import time
import numpy as np
import torch
from d2l import torch as d2l
定义一个计时器:
class Timer: #@save
"""记录多次运行时间"""
def __init__(self):
self.times = []
self.start()
def start(self):
"""启动计时器"""
self.tik = time.time()
def stop(self):
"""停止计时器并将时间记录在列表中"""
self.times.append(time.time() - self.tik)
return self.times[-1]
def avg(self):
"""返回平均时间"""
return sum(self.times) / len(self.times)
def sum(self):
"""返回时间总和"""
return sum(self.times)
def cumsum(self):
"""返回累计时间"""
return np.array(self.times).cumsum().tolist()
数据准备:
n=10000
a=torch.ones([n])
b=torch.ones([n])
用python for循环进行计算:
该方法花费的时间为’0.16749 sec’
c=torch.zeros(n)
timer=Timer()
for i in range(n):
c[i]=a[i]+b[i]
f'{timer.stop():.5f} sec'
用矢量化进行计算:
该方法花费的时间为’0.00042 sec’
timer.start()
d=a+b
f'{timer.stop():.5f} sec'
结果很明显,第二种方法比第一种方法快得多。 矢量化代码通常会带来数量级的加速。 另外,可以将更多的数学运算放到库中,而无须自己编写那么多的计算,从而减少了出错的可能性。
我们可以将线性回归模型视为仅由单个人工神经元组成的神经网络,或称为单层神经网络:
对于线性回归,每个输入都与每个输出(在本例中只有一个输出)相连, 我们将这种变换( 图中的输出层) 称为全连接层(fully-connected layer)。
根据带有噪声的线性模型构造一个人造数据集:
def synthetic_data(w,b,num_examples):
X=torch.normal(0,1,(num_examples,len(w)))
y=torch.matmul(X,w)+b
y+=torch.normal(0,0.01,y.shape)#噪声
return X,y.reshape((-1,1))
true_w=torch.tensor([2,-3.4])
true_b=4.2
features,labels=synthetic_data(true_w,true_b,1000)
定义一个函数data_iter, 该函数能打乱数据集中的样本并以小批量方式获取数据
def data_iter(batch_size,features,labels):
num_examples=len(features)
indices=list(range(num_examples))
random.shuffle(indices)#随机读取样本
for i in range(0,num_examples,batch_size):
batch_indices=torch.tensor(
indices[i:min(i+batch_size,num_examples)])
yield features[batch_indices],labels[batch_indices]
通常,我们利用GPU并行运算的优势,处理合理大小的“小批量”。 每个样本都可以并行地进行模型计算,且每个样本损失函数的梯度也可以被并行计算。 GPU可以在处理几百个样本时,所花费的时间不比处理一个样本时多太多。
从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0:
w=torch.normal(0,0.01,size=(2,1),requires_grad=True)
b=torch.zeros(1,requires_grad=True)
def linreg(X,w,b):
return torch.matmul(X,w)+b
使用 平方损失函数。 在实现中,我们需要将真实值y的形状转换为和预测值y_hat的形状相同。
def squared_loss(y_hat,y):
return (y_hat-y.reshape(y_hat.shape))**2/2
使用小批量随机梯度下降法进行优化。
def sgd(params,lr,batch_size):
with torch.no_grad():
for param in params:
param -= lr*param.grad/batch_size
param.grad.zero_()
在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。 计算完损失后,我们开始反向传播,存储每个参数的梯度。 最后,我们调用优化算法sgd来更新模型参数。
在每个迭代周期(epoch)中,使用data_iter函数遍历整个数据集, 并将训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。 这里的迭代周期个数num_epochs和学习率lr都是超参数,分别设为3和0.03。 设置超参数很棘手,需要通过反复试验进行调整。
lr=0.03
num_epochs=3
net=linreg
loss=squared_loss
for epoch in range(num_epochs):
for X,y in data_iter(batch_size,features,labels):
l=loss(net(X,w,b),y)
l.sum().backward()
sgd([w,b],lr,batch_size)
with torch.no_grad():
train_l=loss(net(features,w,b),labels)
print(f'epoch {epoch+1},loss {float(train_l.mean()):f}')
根据带有噪声的线性模型构造一个人造数据集:
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)
调用框架中现有的API来读取数据:
def load_array(data_arrays,batch_size,is_train=True):
#构造一个数据迭代器
# TensorDataset 将输入的特征数据和标签数据打包成一个数据集
dataset=data.TensorDataset(*data_arrays)
# DataLoader 用于批量加载数据,可以选择是否在每个 epoch 时随机打乱数据
return data.DataLoader(dataset,batch_size,shuffle=is_train)
batch_size=10
data_iter=load_array((features,labels),batch_size)
对于标准深度学习模型,我们可以使用框架的预定义好的层。这使我们只需关注使用哪些层来构造模型,而不必关注层的实现细节。
torch.nn.Sequential 是一个容器模块,它按顺序包含了其他模块(layers)。这个容器允许将一系列的神经网络层按照顺序组合在一起,形成一个更大的网络模型。Sequential 类提供了一种简单的方式来构建和组织神经网络模型,尤其适用于顺序堆叠的层结构。
将两个参数传递到nn.Linear中。 第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。
from torch import nn
net=nn.Sequential(nn.Linear(2,1))
在使用net之前,我们需要初始化模型参数。 如在线性回归模型中的权重和偏置。 深度学习框架通常有预定义的方法来初始化参数。 在这里,我们指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样, 偏置参数将初始化为零。
通过net[0]选择网络中的第一个图层, 然后使用weight.data和bias.data方法访问参数。 我们还可以使用替换方法normal_和fill_来重写参数值。
net[0].weight.data.normal_(0,0.01)
net[0].bias.data.fill_(0)
使用 平方损失函数。 在实现中,我们需要将真实值y的形状转换为和预测值y_hat的形状相同。
loss=nn.MSELoss()
使用小批量随机梯度下降法进行优化。
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。 计算完损失后,我们开始反向传播,存储每个参数的梯度。 最后,我们调用优化算法sgd来更新模型参数。
在每个迭代周期(epoch)中,使用data_iter函数遍历整个数据集, 并将训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。 这里的迭代周期个数num_epochs和学习率lr都是超参数,分别设为3和0.03。 设置超参数很棘手,需要通过反复试验进行调整。
num_epochs=3
for epoch in range(num_epochs):
for X,y in data_iter:
l=loss(net(X),y)
trainer.zero_grad()
l.backward()
trainer.step()
l=loss(net(features),labels)
print(f'epoch {epoch+1}, loss {l:f}')