文章目录
1. 线性回归从零开始实现
1.1 生成数据集
我们根据带有噪声的线性模型构造一个人造数据集,我们将使用有限的数据集来恢复这个模型的参数。在下面代码中,我们生成一个包含1000个样本的数据集,每个样本包含从标准正态分布中采样的两个特征。我们使用线性模型参数 w = [ 2 , − 3.4 ] T w = [2,-3.4]^T w=[2,−3.4]T、 b = 4.2 b=4.2 b=4.2和噪声项 ε \varepsilon ε生成数据集及其标签。( y = X w + b + ε y = \mathbf{X}\mathbf{w} + b + \varepsilon y=Xw+b+ε)
def synthetic_data(w, b, num_examples):
"""生成 y=Xw + b + 噪声"""
X = torch.normal(0, 1, (num_examples, len(w))) # 正态分布(均值为0,标准差为1)
y = torch.matmul(X, w) + b # 矩阵相乘
y += torch.normal(0, 0.01, y.shape) # 加入噪声项
# 得到的y为行向量的形式,为了使其变为一列的形式需要进行reshape
return X, y.reshape((-1, 1))
1.2 读取数据集
我们要实现每次读取一个小批量,并且使用他们来更新我们的模型。因此我们定义一个函数,该函数可以打乱数据集中的样本,并以小批量方式获取数据。
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]
注意:1)该函数的作用类似于生成迭代器 ,yield
相当于return
,调用函数时每for
循环一次返回一个值。
1.3 初始化模型参数
在使用小批量梯度优化我们的模型参数之前,我们需要首先初始化模型参数,有了初始化的模型参数,我们就可以更新这些参数,直到这些参数足够拟合我们的数据。因为每次更新参数的时候都需要计算损失函数关于模型参数的梯度,以便向减小损失函数的方向更新模型参数,所以我们引入自动微分requires_grad
来计算梯度。
# 初始化模型参数
w = torch.normal(0,0.01,(2,1),requires_grad=True)
b = torch.zeros(1,requires_grad=True)
1.4 定义模型
到此,我们需要定义一个模型,将模型的输入和参数同模型的输出关联起来
def linear(X,w,b):
"""定义模型"""
return torch.matmul(X,w)+b
1.5定义损失函数
采用之前文章中线性回归问题中较为常用的损失函数:平方损失函数。
def squared_loss(y_hat,y):
return (y_hat-y.reshape(y_hat.shape))**2/2
1.6 定义优化算法
接下来,我们将朝着减小损失函数的方向更新我们的参数。以下函数实现小批量随机梯度更新。因为我们定义的损失函数是一个批量的总和,所以我们使用batch_size
归一化步长,这样使得步长大小不会取决于我们对批量大小的选择。
def sgd(params, lr, batch_size):
"""小批量梯度下降"""
with torch.no_grad():
for param in params: # 参数b和w
param -= lr*param.grad/batch_size
param.grad.zero_()
注:1)torch.no_grad()
表示对以下语句不生成计算图,即无法其下表达式求解参数的导数。这么做可以节省计算机的计算量。
2)param.grad.zero_()
对参数的梯度值归零,不然下一次反向传播求解的梯度会累加到本次的梯度值上。
1.7 训练
训练过程如下:在每次迭代中我们选取一小批量训练样本,并通过我们定义的模型来获得一组预测。计算完损失后,我们进行反向传播,存储每个参数的梯度。最后,我们使用自定义的sgd
优化算法来更新模型的参数。
每次取一个小的batch_size,直到所有样本采集结束,定义为完成一个epoch.我们定义epoch的个数不停循环上述过程。
for epoch in range(num_epochs):
for X, y in data_iter(batch_size,features,labels):
y_hat = linear(X,w,b)
loss = squared_loss(y_hat,y)
loss.sum().backward() # 进行反向传播得到梯度
sgd((w,b),lr,batch_size)
with torch.no_grad():
train_l = squared_loss(net(features,w,b),labels)
# print(train_l)
print(f'epoch{epoch+1},loss{float(train_l.mean()):f}')
注:1)得到梯度时一定要先进行反向传播,l.sum().backward()
2) tensor形式变量可以使用tensor.sum()
,tensor.mean()
求均值和求和。
2. 线性回归简洁实现
在从零开始实现线性回归中,我们只依赖了torch
框架为我们提供的两个功能:1.使用张量来存储数据和进行线性代数。2.通过自动微分来计算梯度。由于数据迭代器,损失函数,优化器和神经网络很常用,所以深度学习库,已经为我们实现了这些组件。
2.1 生成数据集
-
生成数据集
true_w = torch.tensor([2, -3.4]) true_b = 4.2 features,labels = d2l.synthetic_data(true_w,true_b,10000)
2.2 读取数据集
def load_array(data_arrays, batch_size, is_train=True):
"""构建一个pytorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
说明:
-
data.DataLoader()
为pytorch中重要的数据读取类,其传入参数dataset
需要满足特定的数据结构(映射形(map-style
)或可迭代形(Iterable-style
)),batch_size
为每次迭代读取样本的大小,shuffle
表示是否打乱数据集。 -
data.TensorDataset
类需要传入n个第一个维度相同的张量,便可将第一个维度的索引为数据和对应标签的索引。由此建立map-style
类型数据集。
data_iter = load_array((features,labels),batch_size)
print(next(iter(data_iter)))
>>> [output]
[tensor([[ 0.1447, -0.2136],
[-1.0608, 0.5082],
[ 1.7026, 0.0333],
[ 1.8841, 0.2664],
[ 0.0773, -0.1409],
[-1.3836, -0.6195],
[ 0.7591, -0.2314],
[-0.1942, -0.4251],
[ 0.4559, -1.2682],
[ 0.1983, -0.6629]]),
tensor([[5.2203],
[0.3465],
[7.5039],
[7.0671],
[4.8408],
[3.5432],
[6.5057],
[5.2781],
[9.4210],
[6.8420]])]
创建python迭代器,观察数据集,注意iter
中传入的一定要是可迭代的数据,通过next
可以以此从迭代器中获取数据。
2.3 定义模型
对于标准操作,我们可以使用框架预定义好的层。这使我们只需要关注使用哪些层来构造模型,而不必关心层的实现细节。
Sequential
类为串联在一起的多个层定义了一个容器。当给定输入数据,Sequential
实例将数据传入到第一层,然后第一层的输出作为第二层的输入,以此类推。
线性网络为全连接层,即每一个输入都通过矩阵向量乘法连接到它的每一个输出。全连接层在Linear
类中定义,其需要传入两个参数,第一个为输入特征的形状(本例中两个特征即
y
=
w
1
∗
x
1
+
w
2
∗
x
2
+
b
y = w_1*x_1+ w_2*x_2+b
y=w1∗x1+w2∗x2+b中的
x
1
x
2
x_1 \quad x_2
x1x2),第二个为输出特征的形状(本例为输出单个标量)
net = nn.Sequential(nn.Linear(2,1))
2.4 初始化模型参数
上一步中我们已经定义了好了模型,并实例化为net
,我们可以通过net[index]
选择容器中任一层,并通过net[index].weight.data
和net[index].bias.data
访问线性层的参数。此后便可以使用替换方法,重写参数值。
net[0].weight.data.normal_(0,0.01)
net[0].bias.data.fill_(0)
2.5 定义损失函数
计算均方误差使用MSELoss类,其默认返回所有样本损失的平均值。
loss = nn.MSELoss()
2.6 定义优化算法
小批量随机梯度下降(SGD)算法是一种优化神经网络的标准工具。在Pytorch的optim
模块中实现了该算法许多变种。当我们实例化SGD时,我们需要指定需要优化的参数和优化算法所需要的超参数字典。小批量随机梯度下降算法只需要设置lr
值。这里设置为0.03.
trainer = torch.optim.SGD(net.parameters(),0.03)
注:可以通过net.parameters()
获取网络层中所有参数。
2.7 训练
定义好损失函数,优化算法等基本组件后,模型的训练思路于上文从零实现基本一致。
num_epoch = 3
for epoch in range(num_epoch):
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}')
注:1)trainer.zero_grad()
可以归零参数梯度。
2)trainer.step()
为按照规定方法进行参数优化。其一定要在反向传播得到梯度后进行。
3. 完整程序代码
注:线性回归简洁实现及从零实现的python完整程序代码见d2l_线性回归完整python程序代码