3.2 线性回归从零开始实现
%matplotlib inline
import random
import torch
from d2l import torch as d2l
3.2.1 生成数据集
def synthetic_data(w,b,num_examples):
"""生成y=Xw+b+噪声"""
X=torch.normal(0, 1, (num_examples,len(w)))
y=torch.matmul(X, w)+b
"""加上噪声"""
y+=torch.normal(0,1,y.shape)
"""返回随机X以及形成的模拟y,y设置成一列"""
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)
# print('features:', features[0],'\nlabel:', labels[0])
d2l.set_figsize()#设置图表尺寸
"""使用d2l库中的绘图函数来创建散点图"""
"""三个参数,第一个参数代表x轴数据:是选择features(w)第2列所有的数据,
第二个参数代表y轴数据,选择了虚拟模型的所有结果;
第三个参数代表点的大小,可选"""
d2l.plt.scatter(features[:, 1].detach().numpy(),labels.detach().numpy(), 1)
3.2.2 读取数据集
def data_iter(batch_size,features,labels):
num_examples=len(features)
indices=list(range(num_examples)) #indices是【0-999】,共1000个数据
random.shuffle(indices) #把indices列表数据随机
for i in range(0, num_examples, batch_size): #从0到num_examples,步长为batch_size,例如1-6,步长为2,则i:1,3,6
batch_indices =torch.tensor(indices[i:min(i+batch_size,num_examples)]) #这里怕最后一个不满batch_size
yield features[batch_indices], labels[batch_indices] #print有batch_indices行的features和labels
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
# print(X, '\n', y)
break
3.2.3 初始化模型参数
w=torch.normal(0,1,size=(2,1),requires_grad=True)#requires_grad=True 表示当前的参数可以表示为梯度
b=torch.zeros(1,requires_grad=True) #b=tensor([0.], requires_grad=True)
3.2.4 定义模型
"""线性回归模型y=wx+b"""
def linreg(X,w,b):
return torch.matmul(X,w)+b
3.2.5 定义损失函数
"""损失函数0.5*(y_hat-y)^2 预测值y_hat 真实值y"""
def squared_loss(y_hat, y):
return (y_hat-y.reshape(y_hat.shape))**2/2
3.2.6 定义优化算法
"""小批量随机梯度下降法"""
def sgd(params, lr, batch_size): #params是【w】,【b】
with torch.no_grad(): #停止对梯度计算和存储,减少内存消耗,不会进行反向传播
for param in params:
param-=lr*param.grad/batch_size
param.grad.zero_() #清除param的梯度值
3.2.7 训练
lr = 0.01 #是学习率,也是超参数,控制参数每次更新的步幅
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() #反向传播,此时有了关于w和b中的梯度,w.grad,b,grad
sgd([w, b],lr, batch_size) #向梯度减小的方向移动,为了使得loss最小
with torch.no_grad():
train_l =loss(net(features,w,b),labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
3.3 线性回归的简洁实现
3.3.1 生成数据集
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)
3.3.2 读取数据集
def load_array(data_arrays, batch_size, is_train=True): #@save
"""构造一个PyTorch数据迭代器"""
#data_arrays相当于(features, labels),TensorDataset规定参数的第一维度必须相同
#data_arrays【0】=features,执行PyTotch提供的TensorDataset之后,dataset【0】是features【0】和labels【0】的元组
dataset = data.TensorDataset(*data_arrays)
#PyTorch提供的加载批量数据的迭代器
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size)
#iter-可迭代对象
next(iter(data_iter))
3.3.3 定义模型参数,训练
# nn是神经网络的缩写
from torch import nn
# nn.Linear设置全连接层,输入张量大小为2,输出张量大小为1
# nn.Sequential一个有序的容器
net = nn.Sequential(nn.Linear(2, 1))
# 用标准正态分布(均值为0,方差为1)填充data,也就是第一层全连接层的weight
net[0].weight.data.normal_(0, 0.01)
# 用0填充bias
net[0].bias.data.fill_(0)
# L2范式来充当损失函数
loss = nn.MSELoss()
# net.parameters()返回一个迭代器,可迭代该模型所有可训练的参数
# print(list(net.parameters()))输出参数列表
# [Parameter containing:
# tensor([[-0.0064, -0.0044]], requires_grad=True), Parameter containing:
# tensor([0.], requires_grad=True)]
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l =loss(net(X), y)
# 梯度不清零的话,PyTorch会将梯度累加
trainer.zero_grad()
l.backward()
# 更新参数
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
3.6 softmax回归的从零开始实现
3.6.1 初始化模型参数
%matplotlib inline
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
# 使用svg来显示图片,清晰度高一点
d2l.use_svg_display()
# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,
# 并除以255使得所有像素的数值均在0~1之间
trans = transforms.ToTensor()
mnist_train = torchvision.datasets.FashionMNIST(
root="../data", train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(
root="../data", train=False, transform=trans, download=True)
def get_fashion_mnist_labels(labels): # labels是数集,返回对应文本的集合
"""返回Fashion-MNIST数据集的文本标签"""
test_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [test_labels[int(i)] for i in labels]
"""绘制图像列表"""
def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5):
figsize = (num_rows*scale, num_cols*scale)
# _是一个Figure实例,为Figure(300x1350)
# axes是AxesSubplot实例
_, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
# enumerate遍历序列并追踪索引
# 设置arr1=['one','two'] arr2=['1','2']
# print(list(zip(arr1,arr2)))
# 输出 [('one','1'),('two','2')]
for i, (ax, img) in enumerate(zip(axes, imgs)):
if torch.is_tensor(img):
# 图片张量
ax.imshow(img.numpy())
else:
# PIL图片
ax.imshow(img)
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
if titles:
ax.set_title(titles[i])
return axes
3.6.2 定义softmax模型,损失函数
import torch
from IPython import display
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
num_input=784
num_output=10
# w使用正态分布初始化
w=torch.normal(0,0.01,size=(num_input,num_output),requires_grad=True)
b=torch.zeros(num_output,requires_grad=True)
# 这里演示sum的作用
X=torch.tensor([[1.0,2.0,3.0],[4.0,5.0,6.0]])
# keepdim表示保持原有的维度
# print (tensor([[5.0, 7.0, 9.0]]))
X.sum(0, keepdim=True)
# print (tensor([[6.], [15.]]))
X.sum(1, keepdim=True)
def softmax(X):
X_exp = torch.exp(X)
# X_exp.sum(1,keepdim=True)按行相加
# 这里直接相除用了广播机制
return X_exp/X_exp.sum(1,keepdim=True)
X=torch.normal(0,1,(2, 5))
X_prob = softmax(X)
# 每行之和为1,相当于概率
X_prob.sum(1)
# 定义模型
def net(X):
return softmax(torch.matmul(X.reshape(-1,w.shape[0]),w)+b)
# 获取预测概率的样例
y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
# 此处的意思是选择y_hat的第一个样本中的第一个概率以及第二个样本中的第三个概率
y_hat[[0, 1], y]
# 定义损失函数
def cross_entropy(y_hat, y):
# range(len(y_hat))相当于[0,1,...,行数-1],对比上面的样例理解
return -torch.log(y_hat[range(len(y_hat)),y])
cross_entropy(y_hat, y)
3.6.3 定义精度函数
# 评估正确的精度
def accuracy(y_hat, y):
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1: # y_hat是一个二维矩阵,此时行数列数大于1
y_hat = y_hat.argmax(axis=1) # 把y_hat每行最大的元素下标存到y_hat中
cmp = y_hat.type(y.dtype) == y # 比较预测的值和实际的值是否相同,cmp是个Boolean类型的tensor
return float(cmp.type(y.dtype).sum()) # 先转成和y一样的形状,然后求和,一般来说y_hat和y都是一列的tensor,这里试过把类型转化去掉结果一致
accuracy(y_hat ,y)/len(y)
def evaluate_accuracy(net, data_iter):
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module): # 判断模型是否是net的实例
net.eval() # 将模型设置为评估模式
metric = Accumulator(2) # 正确预测数、预测总数
with torch.no_grad():
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
3.6.4 定义Accumulator类用来保存数据
class Accumulator: #@save
"""在n个变量上累加"""
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
3.6.5 训练
# updater是更新参数的优化算法函数
def train_epoch_ch3(net, train_iter, loss, updater):
"""训练模型一个迭代周期(定义见第3章)"""
# 将模型设置为训练模式
if isinstance(net, torch.nn.Module):
net.train()
# 训练损失总和、训练准确度总和、样本数
metric = Accumulator(3)
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X)
l = loss(y_hat, y)
if isinstance(updater, torch.optim.Optimizer):
# 使用PyTorch内置的优化器和损失函数
updater.zero_grad()
l.mean().backward()
updater.step()
else:
# 使用定制的优化器和损失函数
l.sum().backward()
updater(X.shape[0])
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
return metric[0] / metric[2], metric[1] / metric[2]
动画略
预测略
3.7 softmax回归的简洁实现
import torch
from torch import nn
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# nn.Flatten()是将维度展平成张量,因为nn.Flatten()一般接收一维数据
# PyTorch不会隐式地调整输入的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整网络输入的形状
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights);
loss = nn.CrossEntropyLoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)