前言
我认为,沐神的第七节课和第八节课十分的重要,是后面的基础。一定要掌握牢固了。
先处理几个问题:
1、图片不显示的问题
因为沐神使用的是jupyter,而我使用的是pycharm,所以在原本的d2l.plt…后面需要添加一行代码,比如
import matplotlib.pyplot as plt # 新添加的import
d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)
plt.show() # 新添加的代码
或者在 调用d2l的时候,后面添加d2l.plt.show()
2、ModuleNotFoundError: No module named ‘d2l’
解决办法是,下载d2l包
d2l包里面包含这几个文件,沐神的GitHub里面 下载地址
3、多线程的问题
报错原因是因为李沐用的linex系统,直接这样写没有问题,但是在window系统下,就出问题了,解决办法是,把return的值由4改为0,后面他直接调用的d2l.torch里面也需要改,遇到这个错误的时候,直接改就可以了。
def get_dataloader_workers(): # @save
"""使用4个进程来读取数据"""
return 0 # linux系统中可以使用多个子进程加载数据,windows系统里是不可以的
代码详细解释
沐神提供了两个版本,一个是从零开始实现,一个是简洁实现。所谓的简洁实现就是用一些torch自带的库和他自己的d2l封装在一起的,沐神很贴心的后面加上了 # @save。
1、
def synthetic_data(w, b, num_examples): # @save
"""生成y=Xw+b+噪声"""
X = torch.normal(0, 1, (num_examples, len(w))) # (a,b,c) a:均值,b方差,c:size
y = torch.matmul(X, w) + b # mul and * 是 点乘, matmul and mm 叉乘
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1)) # -1的意思是,总元素除以其他的元素
torch.normal(a,b,c) # a:均值、b方差、c是size
len(w) # 方法返回对象(字符、列表、元组等)长度或项目个数。
x1=[1,2,3] # 3 (长度)
x2=[[1,2,3],[2,3,4]] # 2 (数量)
x3='yy' # 2
x4=torch.tensor([1,2,3]) # 3 (数量)
x5=torch.tensor([[1,2,3]]) # 1 (数量)
x6=[[1,2,3],[2,3,4],[3,4,5],[5,6,7]] # 4 (数量)
print(len(x1),len(x2),len(x3),len(x4),len(x5))
torch.matmul的维度问题 ([1,2,3]是一维的,而[[1,2,3]]是二维的只不过只有一行而已)
一维乘以一维==》标量
二维乘以二维==》矩阵
一维乘以二维==》3×(3×2)》(1×3)×(3×2)》1×2==》2(例子)
一维乘以二维==》(3×2)×2==》(3×2)×(2×1)》3×1》3(例子)
2、
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples)) # 生成一个features长度的序列
# 这些样本是随机读取的,没有特定的顺序
random.shuffle(indices) # 打乱列表
for i in range(0, num_examples, batch_size): # 小批量的关键,需要理解
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)]) # min的作用是在不整除的时候,不至于报错
yield features[batch_indices], labels[batch_indices] # 新的东西
yield:作用就是使用一个固定大小的内存来处理不断生成的数据,从未达到减少内存的消耗。
yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 data_iter(1) 不会执行data_iter 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 data_iter函数内部的代码,执行到 yield 时,data_iter函数就返回一个迭代值,下次迭代时,代码从 yield 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。
也可以手动调用 data_iter的 next() 方法(因为data_iter 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 data_iter 的执行流程。
一言以蔽之:yield就像一个中断器,每次循环调用一次。
for X, y in data_iter(batch_size, features, labels):
在这个循环中,yield了一次,生成了一个batch_size的数据,进入循环体进行处理。处理完之后,执行for的时候又生成了一个batch_size的数据,再进行处理,直到所有的数据全部处理完成。
**3、**简洁实现
逐行解释
def load_array(data_arrays, batch_size, is_train=True): #@save
"""构造一个PyTorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays) # 将特征和标签放在一起,存在dataset中
return data.DataLoader(dataset, batch_size, shuffle=is_train) # 生成小批量的,无序的数据
batch_size = 10
data_iter = load_array((features, labels), batch_size)
# 可以看到三个next生成的数据都不一样,每次next都提出一组无序的数据,记得家print才能显示数据
print(next(iter(data_iter)))
print(next(iter(data_iter)))
print(next(iter(data_iter)))
from torch import nn
net = nn.Sequential(nn.Linear(2, 1)) # 网络,Sequential是一个容器
net[0].weight.data.normal_(0, 0.01) # 初始化
net[0].bias.data.fill_(0) # 初始化
loss = nn.MSELoss() # 损失函数
trainer = torch.optim.SGD(net.parameters(), lr=0.03) # 优化方式
num_epochs = 3 # 训练三轮
for epoch in range(num_epochs):
for X, y in data_iter: # 又开始yeild
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}')
**4、**回归问题
y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], []])
print(y_hat[[0, 1], y]) #[第0行中的第0个,第1行中的第2个]也就是[0.1,0.5]
理解:真实的值是0和2,第一次预测的0,1,2的概率分别为[0.1,0.3,0.6],第一次预测的0,1,2的概率分别为[0.3, 0.2, 0.5],那么两次预测都正确的概率为[0.1,0.5]