本代码基于《动手学深度学习》Pytorch版,第四章多层感知机,第五节权重衰减,第二部分高维线性回归。对代码进行修改,增加注释,供学习使用。
导入相关库
import matplotlib_inline
import matplotlib.pyplot as plt
import IPython
import torch
设置JupyterNotebook中Matplotlib默认图像显示格式为SVG,让图表更易嵌入网页,并可在各种分辨率和设备上呈现出高质量效果
# SVG(ScalableVectorGraphics,可缩放矢量图形)基于XML的矢量图形格式,可缩放和调整大小而不失真
# SVG图像具有更高的质量和可扩展性,在需更清晰的图像或需要缩放图像时使用
# 在JupyterNotebook中,默认Matplotlib生成的图像会以PNG格式显示
def use_svg_display():
matplotlib_inline.backend_inline.set_matplotlib_formats('svg')
# backend_inline是JupyterNotebook设置Matplotlib后端的模块,使生成的图像直接嵌入笔记本中,而不是在单独窗口中显示
# set_matplotlib_formats()设置Matplotlib图像显示格式,允许为Matplotlib生成的图像指定所需的输出格式
# 可接受其他参数,quality(设置JPEG输出格式图像质量),dpi(设置图像每英寸点数)等
设置Matplotlib图形的轴属性,并在轴上启用网格线
def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):
axes.set_xlabel(xlabel)
axes.set_ylabel(ylabel)
axes.set_xscale(xscale)
axes.set_yscale(yscale)
axes.set_xlim(xlim)
axes.set_ylim(ylim)
# legend如果为None或空列表,则不显示
if legend:
axes.legend(legend)
axes.grid()
# grid(b = None, which = 'major', linestyle = '-', linewidth = 0.5, color = 'gray', alpha = 0.5)
# 创建网格线,可帮助在绘制图形时更好地定位数据点
# b可选,布尔值,是否显示网格线,默认为None,根据其他参数自动确定是否显示网格线
# which可选,指定要显示的网格线类型
# 可是major(主要网格线),minor(次要网格线),both(主要和次要网格线),默认为major
# linestyle可选,指定网格线线型,可是-(实线),--(虚线),-.(点划线),:(点线),默认为-
# linewidth可选,指定网格线线宽,默认为0.5
# color可选,指定网格线颜色,可是任何有效的Matplotlib颜色字符串或颜色代码,默认为gray
# alpha可选,指定网格线透明度,默认为0.5
在动画中绘制图表,在训练过程中实时显示数据
# 在动画中绘制图表指在计算机动画或交互式图形应用程序中实时显示数据变化过程
class Animator:
def __init__(self, xlabel = None, ylabel = None, legend = None, xlim = None, ylim = None, xscale = 'linear', yscale = 'linear', fmts = ('-', 'm--', 'g-.', 'r:'), nrows = 1, ncols = 1, figsize = (3.5, 2.5)):
# xlabel:x轴标签
# ylabel:y轴标签
# legend图例,显示每条线的标签
# xlim:x轴范围
# ylim:y轴范围
# xscale:x轴刻度类型,默认为线性
# yscale:y轴刻度类型,默认为线性
# fmts绘制线条的格式
# nrows子图行数
# ncols子图列数
# figsize图形大小,包含两个元素的元组,表示整个图表的宽度和高度(以英寸为单位)
# 检查legend是否为None,如果是,则将其设置为空列表
if legend is None:
legend = []
# 设置图像显示格式
use_svg_display()
# 创建包含nrows行和ncols列的子图网格
self.fig, self.axes = plt.subplots(nrows, ncols, figsize = figsize)
# 如果只有一个子图,将其放入列表中
if nrows * ncols == 1:
self.axes = [self.axes, ]
# 将单一的Axes对象转换为包含该对象的列表
# 使用lambda函数配置子图的轴
self.config_axes = lambda: set_axes(self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts
# 向图表中添加数据点,并在添加数据点后更新图形
def add(self, x, y):
# x:x轴的数据点
# y:y轴的数据点
# 检查变量是否具有__len__属性,如果有,说明变量是可迭代对象,如果没有,说明变量不是可迭代对象
# 将y转换为包含单个元素的列表,计算y的长度,即元素数量,将其存储在变量n中,将x转换为长度为n的列表
if not hasattr(y, '__len__'):
y = [y]
n = len(y)
if not hasattr(x, '__len__'):
x = [x] * n
# 检查变量是否为空,如果为空,将其初始化为包含n个空列表的列表
if not self.X:
# 检查是否为空,如果为空,则条件为真,执行if语句块中的代码,否则,条件为假,跳过if语句块
self.X = [[] for _ in range(n)]
# 使用列表推导式创建包含n个空列表的列表
if not self.Y:
self.Y = [[] for _ in range(n)]
# 遍历x和y,如果不为None,则添加到X和Y
for i, (a, b) in enumerate(zip(x, y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
# 配置子图属性,并显示图形后清除当前输出区域
self.config_axes()
IPython.display.display(self.fig)
IPython.display.clear_output(wait = True)
累加数值
class Accumulator:
# 初始化长度为n的浮点数列表self.data存储累加的值,所有元素初始值为0.0
def __init__(self, n):
# n要累加的值的数量
self.data = [0.0] * n
# 接收任意数量的数值参数,并与self.data中的对应元素相加并存储
def add(self, *args):
# args可变数量参数
self.data = [a + float(b) for a, b in zip(self.data, args)]
# 重置累加器,将self.data中的所有元素重置为0.0
def reset(self):
self.data = [0.0] * len(self.data)
# 实现类实例的索引访问,允许使用索引操作符[]来访问Accumulator对象中的元素
def __getitem__(self, idx):
# __getitem__允许对象像字典或列表一样进行索引操作
# 当尝试使用索引访问对象时,Python会自动调用该对象的__getitem__方法
# __getitem__通常与__setitem__一起使用,以实现对象的索引操作
# __setitem__用于设置值,__getitem__用于获取值
# idx要访问的元素索引
return self.data[idx]
评估给定数据集上模型的损失
def evaluate_loss(net, data_iter, loss):
metric = Accumulator(2)
for x, y in data_iter:
out = net(x)
y = y.reshape(out.shape)
l = loss(out, y)
metric.add(l.sum(), l.numel())
# numel(input)计算张量中所有元素的数量,接受张量作为输入,返回整数,张量中元素总数
# input必需,输入张量
return metric[0] / metric[1]
生成带有噪声的线性模型数据集
def dataset(w, b, n):
x = torch.normal(0, 1, (n, len(w)))
# normal(mean, std, out = None, dtype = None, layout = torch.strided, device = None, requires_grad = False)
# 从正态分布中抽取样本,在模拟数据,生成随机噪声,其他需要随机数的任务时非常有用
# mean必需,正态分布均值,可是标量,可是与输出张量形状相同的张量
# std必需,正态分布标准差,与mean类似,可是标量或张量
# out可选,指定输出张量存储位置,如果想将结果存储在已有张量中,可传递此参数
# dtype可选,指定输出张量数据类型,默认为float32
# layout可选,指定张量布局,默认为strided,表示张量是连续存储的
# device可选,指定输出张量应该存储在的设备,默认在CPU上,可指定GPU设备
# requires_grad可选,指定输出张量是否需要计算梯度,默认为False,不需要计算梯度
y = torch.matmul(x, w) + b
# torch.matmul(input, other, *, out = None)
# 执行矩阵乘法,可处理一维(向量),二维(矩阵),更高维度张量间的矩阵乘法
# input输入张量,可是任意形状的张量
# other输入张量,形状应与input兼容以进行矩阵乘法
# out可选,指定输出张量的存储位置,如果想将结果存储在已有张量中,可传递此参数
# 矩阵乘法规则
# 对于一维张量(向量),执行点积运算
# 对于二维张量(矩阵),执行标准矩阵乘法
# 对于更高维度张量,会沿着最后一个维度执行矩阵乘法
# 在进行矩阵乘法时,请确保输入张量的形状是兼容的,如不兼容,将抛出错误
y += torch.normal(0, 0.01, y.shape)
return x, y.reshape((-1, 1))
构建数据迭代器
def iterate(dataset, n, shuffle = False):
data = torch.utils.data.TensorDataset(*dataset)
# TensorDataset(*tensors)创建数据集,其中每个样本由一组张量表示
# tensors一系列张量,每个张量表示一个样本的一个特征,所有张量的长度(即第一个维度大小)必须相同,表示数据集中的样本数量
# TensorDataset类常用于将多个张量组合成一个数据集,以便在训练神经网络时使用
# 可与其他PyTorch数据加载器(如DataLoader)结合使用,以实现数据的批量加载和并行处理
# 在Python中,使用*作为前缀可将元组或列表中的元素解包并作为单独的参数传递给函数
return torch.utils.data.DataLoader(data, n, shuffle = shuffle)
# DataLoader(dataset, batch_size = 1, shuffle = False, sampler = None, batch_sampler = None, num_workers = 0,
# collate_fn = None, pin_memory = False, drop_last = False, timeout = 0, worker_init_fn = None)
# 创建数据加载器,数据加载器用于在训练神经网络时高效地加载和处理数据
# dataset要加载的数据集,可是PyTorch中定义的任何数据集类(如TensorDataset,ImageFolder等)的实例
# batch_size可选,每个批次的数据量大小,默认为1
# shuffle可选,布尔值,是否在每个epoch开始时打乱数据集,默认为False
# sampler可选,自定义采样器,决定从数据集中抽取哪些样本,如果提供,则shuffle将被忽略
# batch_sampler可选,自定义批量采样器,决定从数据集中抽取哪些样本组成批次
# 如果提供了,则batch_size,shuffle,sampler将被忽略
# num_workers可选,加载数据的子进程数量,默认为0,使用主进程加载数据
# collate_fn可选,自定义函数,将一批样本组合成一个批次,默认为None,使用默认的组合方式
# pin_memory可选,布尔值,是否将数据加载到固定内存中,默认为False
# 如果设为True,则数据加载器会将数据加载到固定内存中,可提高数据传输到GPU的速度
# drop_last可选,布尔值,是否丢弃最后一个不完整的批次,默认为False
# timeout可选,等待子进程加载数据的超时时间(秒),默认为0,无限制等待
# worker_init_fn可选,自定义函数,初始化每个子进程,默认为None
定义线性回归模型
def linearRegression(x, w, b):
return torch.matmul(x, w) + b
初始化模型参数
def init_param():
w = torch.normal(0, 1, size = (n_input, 1), requires_grad = True)
b = torch.zeros(1, requires_grad = True)
# torch.zeros(*size, out = None, dtype = None, layout = torch.strided, device = None, requires_grad = False)
# 创建全为0的张量,在初始化权重,偏置项,创建占位符矩阵时非常有用
# *size必需,指定输出张量形状,可传递整数序列来定义二维张量,或传递多个整数来定义更高维度的张量
# out可选,指定输出张量存储位置,如果想将结果存储在已有张量中,可传此参数
# dtype可选,指定输出张量数据类型,默认为float32
# layout可选,指定张量布局,默认为strided,表示张量是连续存储的
# device可选,指定输出张量应该存储的设备,默认在CPU上,可指定GPU设备
# requires_grad可选,指定输出张量是否需要计算梯度,默认为False,不需要计算梯度
return w, b
定义平方损失函数
def squaredLoss(y_hat, y):
return (y_hat - y) ** 2 / 2
定义L2范数惩罚
def l2_penalty(w):
return torch.sum(w.pow(2)) / 2
# torch.sum(input, dim = None, keepdim = False, dtype = None, out = None)
# 计算张量中所有元素的和,在计算损失函数的梯度,统计信息,其他需要求和的操作时非常有用
# input必需,要计算和的张量
# dim可选,指定沿着哪个维度计算和,如果是整数,沿着指定维度计算和,如果是元组,沿着多个维度计算和
# 如果是None(默认值),计算所有元素的和
# keepdim可选,指定是否保持计算和后的维度,如果为True,计算和后的张量将保留原始张量的维度
# 如果为False(默认值),计算和后的张量将降低维度
# dtype可选,指定输出张量数据类型,默认输出张量的数据类型与输入张量的数据类型相同,可指定其他数据类型
# out可选,指定输出张量存储位置,如果想将结果存储在已有张量中,可传递此参数
# 在进行求和操作时,请确保输入张量形状和数据类型是合适的,如果输入包含NaN或无穷大值,可能会产生不正确的结果
# pow(base, exponent, modulus)计算数的幂,返回底数的指数次幂
# base必需,要计算幂的底数
# exponent必需,要计算的指数
# modulus可选,指定模数,当提供模数时,将计算(base ** exponent) % modulus
# 当使用模数时,底数、指数和模数都应该是整数
定义优化算法梯度下降更新参数
def optimize(params, lr, n):
with torch.no_grad():
# with异常处理,封装了try…except…finally编码范式,提高了易用性,使代码清晰,更具可读性,简化了文件流等公共资源的管理
# with语句实现原理建立在上下文管理器之上,上下文管理器是实现__enter__和__exit__方法的类
# 使用with语句确保在嵌套块的末尾调用__exit__方法,类似try...finally块的使用
# with简化资源管理,在with语句中,可创建上下文管理器对象,当代码块执行完毕后,会自动调用该对象的__exit__()来清理资源
# torch.no_grad()是上下文管理器,禁用PyTorch的梯度计算
# torch.no_grad()禁止梯度计算,被调用时,模型参数的梯度不会被跟踪和计算,在进行推断或测试模型时有用,不需反向传播
# with torch.no_grad():是上下文管理器,禁用PyTorch的梯度计算
# 在这个代码块中进行的计算不会跟踪梯度,当代码块执行完毕后,梯度计算会自动恢复
for param in params:
param -= lr * param.grad / n
# 在PyTorch中,每个参数都有梯度属性grad,存储参数梯度值,在反向传播过程中,每个参数的梯度都会被计算并存储
# 在进行梯度更新时,需使用梯度值来更新参数值
param.grad.zero_()
# 将参数的梯度清零,在进行梯度更新时,需先将梯度值清零,避免累积之前的梯度影响当前梯度的计算
训练
def train(lambd):
w, b = init_param()
net, loss = lambda x: linearRegression(x, w, b), squaredLoss
# lambda arguments: expression
# lambda关键字,定义匿名函数,匿名函数是一种简洁的,没有名称的函数,通常用于一次性,临时性的简单操作
# lambda函数的语法简单,接受任意数量的参数,但只能有一个表达式
# arguments传递给lambda函数的参数列表,可有零个或多个,参数之间用逗号分隔
# expression返回值表达式,lambda函数将计算这个表达式的值并返回
# lambda函数的使用场景相对有限,因为只能包含一个表达式,不能包含复杂的逻辑
n_epoch, lr = 100, 0.003
animator = Animator(xlabel = 'epochs', ylabel = 'loss', yscale = 'log', xlim = [5, n_epoch], legend = ['train',
'test'])
for epoch in range(n_epoch):
for x, y in train_iterate:
l = loss(net(x), y) + lambd * l2_penalty(w)
l.sum().backward()
optimize([w, b], lr, batch_size)
if (epoch + 1) % 5 == 0:
# !!!问题:当shuffle = True时,图像无法与书本吻合,图像并不圆润,震荡频繁,波动较大!!!
animator.add(epoch + 1, (evaluate_loss(net, train_iterate, loss), evaluate_loss(net, test_iterate,
loss)))
print('w的L2范数是:', torch.norm(w).item())
# torch.norm(input, p = 'fro', dim = None, keepdim = False, dtype = None, out = None, eps = 1e-12)
# 计算张量的范数,范数是衡量张量大小的量,可衡量向量的长度,矩阵的奇异值等
# norm()支持多种范数类型,包括L1范数,L2范数,无穷范数等
# input必需,要计算范数的张量
# p可选,指定范数类型,默认为fro,表示Frobenius范数
# inf无穷范数(最大元素的绝对值)
# 1,L1 范数(各元素绝对值之和)
# 2,L2 范数(欧几里得范数,即向量长度)
# 一个浮点数p,Lp范数(各元素绝对值的p次方之和的1/p次方)
# dim可选,指定沿着哪个维度计算范数,如果是整数,沿着指定的维度计算范数,如果是元组,沿着多个维度计算范数
# 如果是None(默认值),计算整个张量的范数
# keepdim可选,指定是否保持计算范数后的维度,如果为True,计算范数后的张量将保留原始张量的维度
# 如果为False(默认值),计算范数后的张量将降低维度
# dtype可选,输出张量数据类型,默认输出张量数据类型与输入张量数据类型相同,可指定其他数据类型
# out可选,输出张量存储位置,如果想将结果存储在已有张量中,可传递此参数
# eps可选,指定数值稳定性的容差,默认为1e-12
# 在进行范数计算时,请确保输入张量形状和数据类型是合适的,如果输入包含NaN或无穷大值,可能会产生不正确的结果
# tensor.item()将张量中的单个元素提取为Python标量,在需从张量中获取单个值(例如损失函数的值)并将其用于Python代码中时非常有用
# tensor必需,要提取元素的张量
# 只能用于包含单个元素的张量,如果尝试从包含多个元素的张量中调用,将引发RuntimeError异常
# 返回的是Python标量,因此无法用于需要张量作为输入的PyTorch操作
# 如果需要将张量转换为Python列表或其他数据结构,可使用其他方法,如tolist()
初始化参数
n_train, n_test, n_input, batch_size = 20, 100, 200, 5
true_w, true_b = torch.ones((n_input, 1)) * 0.01, 0.05
# ones(*size, out = None, dtype = None, layout = torch.strided, device = None, requires_grad = False)
# 创建全为1的张量,在初始化权重或创建掩码矩阵时非常有用
# size必需,指定输出张量形状,可传递整数序列来定义二维张量,或传递多个整数来定义更高维度的张量
# out可选,指定输出张量存储位置,如果想将结果存储在已有的张量中,可传递此参数
# dtype可选,指定输出张量数据类型,默认输出张量的数据类型是float32
# layout可选,指定张量布局,默认布局是strided,表示张量是连续存储的
# device可选,指定输出张量应该存储的设备,默认会存储在CPU上,可指定GPU设备
# requires_grad可选,指定输出张量是否需要计算梯度,默认为False,不需要计算
生成带有噪声的高维线性模型数据集迭代器
train_dataset = dataset(true_w, true_b, n_train)
train_iterate = iterate(train_dataset, batch_size)
test_dataset = dataset(true_w, true_b, n_test)
test_iterate = iterate(test_dataset, batch_size, shuffle = False)
禁用权重衰减
train(lambd = 0)
运行结果
w的L2范数是: 13.286377906799316
启用权重衰减
train(lambd = 3)
运行结果
w的L2范数是: 0.3635611832141876