目录
2.预备知识
2.1数据操作
torch.arange()
x.shape
x.numel()
x.reshape(m, n)
torch.zeros((2, 3, 4)) torch.ones((2, 3, 4))
torch.randn(3, 4)
torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
x + y, x - y, x * y, x / y, x ** y, x == y
torch.exp(x)
torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
X.sum()
torch.zeros_like(Y)
id()
X[:] = X + Y 或X += Y
A = X.numpy() , B = torch.tensor(A)
a.item(), float(a), int(a)
2.2. 数据预处理
os.makedirs(name, mode=0o777, exist_ok=False)
os.path.join()
with open() as f
pd.read_csv()
.iloc[ , ]
.fillna()
pd.get_dummies()
.values
2.3. 线性代数
len(x)
A.T
.clone()
.sum()
.mean()
.cumsum(axis=0)
torch.dot(x, y) 与 torch.sum(x * y)相同(点积)
torch.mv(A, x) (矩阵-向量积)
torch.mm(A, B)(矩阵-矩阵乘法)
torch.norm()
torch.abs()
2.4. 微积分
arange(start=0, end, step=1, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) -> Tensor
>>> torch.arange(5) # 默认以 0 为起点
tensor([ 0, 1, 2, 3, 4])
>>> torch.arange(1, 4) # 默认间隔为 1
tensor([ 1, 2, 3])
>>> torch.arange(1, 2.5, 0.5) # 指定间隔 0.5
tensor([ 1.0000, 1.5000, 2.0000])
可以通过张量的shape属性来访问张量(沿每个轴的长度)的形状 。
如果只想知道张量中元素的总数(标量),即形状的所有元素乘积,可以检查它的大小(size)。
改变一个张量的形状而不改变元素数量和元素值。不需要通过手动指定每个维度来改变形状。 也就是说,如果我们的目标形状是(高度,宽度), 那么在知道宽度后,高度会被自动计算得出,不必我们自己做除法。 为了获得一个3行的矩阵,我们手动指定了它有3行和4列。 幸运的是,我们可以通过-1来调用此自动计算出维度的功能。 即我们可以用x.reshape(-1, 4)或x.reshape(3 , -1)来取代x.reshape(3 ,4)。
可以创建一个形状为(2,3,4)的张量,其中所有元素都设置为0/1。
创建一个形状为(3,4)的张量。 其中的每个元素都从均值为0、标准差为1的标准高斯分布(正态分布)中随机采样。
提供包含数值的Python列表(或嵌套列表),来为所需张量中的每个元素赋予确定值。 在这里,最外层的列表对应于轴0,内层的列表对应于轴1。
x = torch.tensor([1.0, 2, 4, 8]) # **运算符是求幂运算
y = torch.tensor([2, 2, 2, 2])
对于任意具有相同形状的张量, 常见的标准算术运算符(+
、-
、*
、/
和**
)都可以被升级为按元素运算。
x == y: 对于每个位置,如果x和y在该位置相等,则新张量中相应项的值为1。意味着该位置处为真(True),否则该位置为0(False)。
对张量作指数运算
沿行(轴-0,形状的第一个元素) 和按列(轴-1,形状的第二个元素)连结两个矩阵时,dim=0,增加行数;dim=1,增加列数。
对张量中的所有元素进行求和,会产生一个单元素张量。
继承了Y的shape,将里面的值全部替换成0。
id()它给我们提供了内存中引用对象的确切地址。
运行一些操作可能会导致为新结果分配内存。 例如,如果我们用Y = X + Y,我们将取消引用Y指向的张量,而是指向新分配的内存处的张量。可以使用X[:] = X + Y 或X += Y来减少操作的内存开销。
将深度学习框架定义的张量转换为NumPy张量(numpy)很容易,反之也同样容易。 torch张量和numpy数组将共享它们的底层内存,就地操作更改一个张量也会同时更改另一个张量。
要将大小为1的张量转换为Python标量,我们可以调用item函数或Python的内置函数。
padans软件包是Python中常用的数据分析工具中,padans可以与张量兼容。
import os
os.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', 'data', 'house_tiny.csv')
with open(data_file, 'w') as f:
f.write('NumRooms,Alley,Price\n') # 列名
f.write('NA,Pave,127500\n') # 每行表示一个数据样本
f.write('2,NA,106000\n')
f.write('4,NA,178100\n')
f.write('NA,NA,140000\n')
参数说明:
name:要创建的目录路径。
mode:目录权限,默认为0o777,表示该目录所有用户都有读写执行权限。
exist_ok:如果设置为True,则在目录已经存在的情况下不会抛出异常,否则会抛出FileExistsError异常。
总之,os.mkdir()和os.makedirs()都是Python中用于创建目录的函数(创建文件夹),其中os.makedirs()具有递归创建目录的功能,使用时需要注意它们的区别和语法。
Python中的os.path.join是用来合并路径的函数。它可以把多个路径组合在一起,并正确处理斜杠和反斜杠。
print(os.path.join('C:\\', 'Users', 'username', 'Desktop'))
# 输出: 'C:\\Users\\username\\Desktop'
文件的读写操作:
content = f.read(f) #文件的读操作
f.write('hello world') #文件的写操作
pd.read_csv是pandas库中的一个函数,用于读取CSV文件并将其转换为DataFrame对象。加载原始数据集。
(index location),iloc[a:b,c:d]:取行索引从a到b-1,列索引从c到d-1的数据。
fillna() 是 DataFrame 中的一个方法,用于将数据中缺失的值填充为指定的值或方法所返回的值。填充的数值可以是标量,Series,DataFrame 等。该方法有多个参数,包括 value,method,axis 等。其中,value 参数用于填充缺失值的常数值;method 参数可以采用 ffill 或 bfill 方法进行前向或后向填充;axis 参数表示在哪个维度上填充缺失值,默认为 0,即在行上填充。
用独热编码来替换离散值特征。
函数中还有一个参数是dummy_na,其作用是:是否将“na”(缺失值)视为有效的特征值,并为其创建指示符特征。dummy_na = false,就是不将“na”视为有效特征值,等于true就是将“na”视为一个特征值,并为其建立指示符特征。
获取当前字典中所有键值对中的值( value )。
返回对象(字符、列表、元组等)长度或者项目个数。我们也可以通过.shape属性访问向量的长度。(返回轴0长度)
矩阵的转置。
B = A.clone() # 通过分配新内存,将A的一个副本分配给B
调用求和函数会沿所有的轴降低张量的维度,使它变为一个标量。
以矩阵为例,为了通过求和所有行的元素来降维(轴0),可以在调用函数时指定axis=0。 由于输入矩阵沿0轴降维以生成输出向量,因此输入轴0的维数在输出形状中消失。
A_sum_axis0 = A.sum(axis=0)
A.sum(axis=[0, 1]) # 结果和A.sum()相同
sum_A = A.sum(axis=1, keepdims=True) #保持轴数不变
mean() 是计算每组平均值的方法。同样,计算平均值的函数也可以沿指定轴降低张量的维度。
A.mean(axis=0), A.sum(axis=0) / A.shape[0] #两个结果相同
沿某个轴计算A
元素的累积总和, 比如axis=0(按行计算),可以调用cumsum函数。 此函数不会沿任何轴降低输入张量的维度。
点积:两向量相同位置的按元素乘积的和。
A的列维数(沿轴1的长度)必须与x的维数(其长度)相同。其结果是一个向量。
A的列数要等于B的行数。
是对输入的tensor求对应的范数。
默认为p=‘fro’,计算矩阵的Frobenius norm (Frobenius 范数),就是矩阵各项元素的绝对值平方的总和的平方根。
返回输入参数的绝对值作为输出,输入参数必须是一个Tensor 数据类型的变量。
torch.abs(u).sum() #求L1范数
用于循环遍历一个指定范围内的数字。(前闭后开)
for i in range(start, end, step):
# Do something
自动求导,如果y值是个标量,对向量x求导,那么直接使用;否则y不是标量,直接使用会报错。
y.backward()
x.grad
# 在默认情况下,PyTorch会累积梯度,我们需要清除之前的值
x.grad.zero_()
# 线性回归从零开始实现
%matplotlib inline
import random
import torch
from d2l import torch as d2l
# 生成数据集
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)
print('feature:', features[0], 'labelss:', labels[0])
d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1); #detach:pytoch里一些计算图需要detach出来,然后才能转到numpy里去
# 读取数据集
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples)) #range()表示从0 - n-1,list()表示转成list格式
# # 这些样本是随机读取的,没有特定的顺序,把下标随机打乱
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] #产生随机顺序的特征以及标号
batch_size = 10
#展示一组批量
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
# 定义初始化模型参数
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
# 定义损失函数
def squared_loss(y_hat, y):
#均方损失
return (y_hat-y.reshape(y_hat.shape))**2/2
# 定义优化算法
def sgd(params, lr, batch_size): #params是list,里面包含了w和b
#小批量随机梯度下降
with torch.no_grad(): #更新的时候不需要参与梯度计算
for param in params:
param -= lr * param.grad / batch_size #梯度存在.grad里面
param.grad.zero_() #把梯度设成0,不影响下一次梯度计算
# 训练过程
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}')
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
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):
#构造一个pytorch数据迭代器
dataset = data.TensorDataset(*data_arrays) #已经有features, labels,把它做成list,传到dataset里面
return data.DataLoader(dataset, batch_size, shuffle=is_train) #得到打乱的批量数据
batch_size = 10
data_iter = load_array((features, labels), batch_size)
next(iter(data_iter)) #把data_iter转成iterator,通过next获得第一批数据
#使用框架预定义好的层
# 'nn'是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2,1)) #线性层,即全连接层,唯一要指定输入维度和输出维度,Sequential是list of layers
#初始化模型参数
net[0].weight.data.normal_(0, 0.01) #net可以通过0访问到w,再通过data访问到它的真实值
net[0].bias.data.fill_(0)
#计算均方误差
loss = nn.MSELoss()
#实例化SGD
trainer = torch.optim.SGD(net.parameters(), lr=0.03) #传入net所有参数,包括w和b
#训练
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() #pytorch已经做了sum了
trainer.step() #进行模型更新
l = loss(net(features), labels)
print(f'epoch{epoch + 1}, loss{l:f}')
# 多层感知机
import torch
from torch import nn
from torch.nn import functional as F
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
X = torch.rand(2, 20)
net(X)
# 自定义块
class MLP(nn.Module):
# 用模型参数声明层。这里,我们声明两个全连接的层
def __init__(self):
# 调用MLP的父类Module的构造函数来执行必要的初始化。
# 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍)
super().__init__()
self.hidden = nn.Linear(20, 256) # 隐藏层
self.out = nn.Linear(256, 10) # 输出层
# 定义模型的前向传播,即如何根据输入X返回所需的模型输出
def forward(self, X):
# 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。
return self.out(F.relu(self.hidden(X)))
net = MLP()
net(X)
# 顺序块
class MySequential(nn.Module):
def __init__(self, *args): # *args是收集参数,相当于把若干个参数打包成一个来传入
super().__init__()
for block in args:
self._modules[block] = block
def forward(self, X):
for block in self._modules.values():
X = block(X)
return X
net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
net(X)
# 在前向传播函数中执行代码
class FixedHiddenMLP(nn.Module):
def __init__(self):
super().__init__()
# 不计算梯度的随机权重参数。因此其在训练期间保持不变
self.rand_weight = torch.rand((20, 20), requires_grad=False)
self.linear = nn.Linear(20, 20)
def forward(self, X):
X = self.linear(X)
# 使用创建的常量参数以及relu和mm函数
X = F.relu(torch.mm(X, self.rand_weight) + 1)
# 复用全连接层。这相当于两个全连接层共享参数
X = self.linear(X)
# 控制流
while X.abs().sum() > 1: # 绝对值求和
X /= 2
return X.sum()
net = FixedHiddenMLP()
net(X)
# 混合搭配各种组合块的方法
class NestMLP(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(), nn.Linear(64, 32), nn.ReLU())
self.linear = nn.Linear(32, 16)
def forward(self, X):
return self.linear(self.net(X))
chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP())
chimera(X)
import torch
from torch import nn
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))
net(X)
# 参数访问
print(net[2].state_dict())
# 目标参数
print(type(net[2].bias))
print(net[2].bias)
print(net[2].bias.data)
net[2].weight.grad == None # 这里还没有做反向计算
# 一次性访问所有参数
print(*[(name, param.shape) for name, param in net[0].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()]) # 整个网络
net.state_dict()['2.bias'].data
# 从嵌套块收集参数
def block1():
return nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
nn.Linear(8, 4), nn.ReLU())
def block2():
net = nn.Sequential()
for i in range(4):
# 在这里嵌套
net.add_module(f'block {i}', block1()) # 插入四个block1
return net
rgnet = nn.Sequential(block2(), nn.Linear(4, 1))
rgnet(X)
print(rgnet)
# 内置初始化
def init_normal(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, mean=0, std=0.01)
nn.init.zeros_(m.bias)
net.apply(init_normal)
net[0].weight.data[0], net[0].bias.data[0]
# 将所有参数初始化为给定的常数,比如初始化为1(不可以这样做)
def init_constant(m):
if type(m) == nn.Linear:
nn.init.constant_(m.weight, 1)
nn.init.zeros_(m.bias)
net.apply(init_constant)
net[0].weight.data[0], net[0].bias.data[0]
# 对某些块应用不同的初始化方法
def init_xavier(m):
if type(m) == nn.Linear:
nn.init.xavier_uniform_(m.weight)
def init_42(m):
if type(m) == nn.Linear:
nn.init.constant_(m.weight, 42)
net[0].apply(init_xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)
# 自定义初始化
def my_init(m):
if type(m) == nn.Linear:
print("Init", *[(name, param.shape)
for name, param in m.named_parameters()][0])
nn.init.uniform_(m.weight, -10, 10) # 均匀初始化
m.weight.data *= m.weight.data.abs() >= 5 # 保留绝对值大于5的权重,不是的话就设成0
net.apply(my_init)
net[0].weight[:2]
# 可以直接设置参数
net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
net[0].weight.data[0]
# 参数绑定,希望在多个层间共享参数: 我们可以定义一个稠密层,然后使用它的参数来设置另一个层的参数。
# 我们需要给共享层一个名称,以便可以引用它的参数
shared = nn.Linear(8, 8)
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
shared, nn.ReLU(),
shared, nn.ReLU(),
nn.Linear(8, 1))
net(X)
# 检查参数是否相同
print(net[2].weight.data[0] == net[4].weight.data[0])
net[2].weight.data[0, 0] = 100
# 确保它们实际上是同一个对象,而不只是有相同的值
print(net[2].weight.data[0] == net[4].weight.data[0])
#构造一个没有任何参数的自定义层
import torch
import torch.nn.functional as F
from torch import nn
class CenteredLayer(nn.Module):
def __init__(self):
super().__init__()
def forward(self, X):
return X - X.mean()
layer = CenteredLayer()
layer(torch.FloatTensor([1, 2, 3, 4, 5]))
# 将层作为组件合并到更复杂的模型中
net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())
Y = net(torch.rand(4, 8))
Y.mean()
# 带参数的层
class MyLinear(nn.Module):
def __init__(self, in_units, units):
super().__init__()
self.weight = nn.Parameter(torch.randn(in_units, units))
self.bias = nn.Parameter(torch.randn(units,))
def forward(self, X):
linear = torch.matmul(X, self.weight.data) + self.bias.data # 通过.data访问对应的参数
return F.relu(linear)
linear = MyLinear(5, 3)
linear.weight
# 使用自定义层直接执行前向传播计算
linear(torch.rand(2, 5))
# 使用自定义层构建模型
net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))
net(torch.rand(2, 64))
# 加载和保存张量
import torch
from torch import nn
from torch.nn import functional as F
x = torch.arange(4)
torch.save(x, 'x-file')
x2 = torch.load("x-file")
x2
# 存储一个张量列表,然后把它们读回内存
y = torch.zeros(4)
torch.save([x, y], 'x-file')
x2, y2 = torch.load("x-file")
(x2, y2)
# 写入或读取从字符串映射到张量的字典
mydict = {'x': x, 'y': y}
torch.save(mydict, 'mydict')
mydict2 = torch.load("mydict")
mydict2
# 加载和保存模型参数
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.hidden = nn.Linear(20, 256)
self.output = nn.Linear(256, 10)
def forward(self, x):
return self.output(F.relu(self.hidden(x)))
net = MLP()
X = torch.randn(size=(2, 20))
Y = net(X)
# 将模型的参数存储在一个叫做“mlp.params”的文件中
torch.save(net.state_dict(), 'mlp.params') # state_dict()得到所有的parameter
# 实例化了原始多层感知机模型的一个备份,直接读取文件中存储的参数。
clone = MLP() # 先声明一下,把网络生成出来
clone.load_state_dict(torch.load('mlp.params')) # 从字典读出,overwrite初始化的参数
clone.eval()
# 由于两个实例具有相同的模型参数,在输入相同的X时, 两个实例的计算结果应该相同。 让我们来验证一下
Y_clone = clone(X)
Y_clone == Y
import torch
from torch import nn
torch.device('cpu'), torch.device('cuda'), torch.device('cuda:1')
# 查询可用gpu的数量
torch.cuda.device_count()
# 定义了两个方便的函数, 这两个函数允许我们在不存在所需所有GPU的情况下运行代码
def try_gpu(i=0): #@save
"""如果存在,则返回gpu(i),否则返回cpu()"""
if torch.cuda.device_count() >= i + 1:
return torch.device(f'cuda:{i}')
return torch.device('cpu')
def try_all_gpus(): #@save
"""返回所有可用的GPU,如果没有GPU,则返回[cpu(),]"""
devices = [torch.device(f'cuda:{i}')
for i in range(torch.cuda.device_count())]
return devices if devices else [torch.device('cpu')]
try_gpu(), try_gpu(10), try_all_gpus()
# 查询张量所在的设备
x = torch.tensor([1, 2, 3])
x.device
# 存储在GPU上
X = torch.ones(2, 3, device=try_gpu())
X
Y = torch.rand(2, 3, device=try_gpu(1))
Y
# 它在同一设备上找不到数据会导致失败。 由于Y位于第二个GPU上,所以我们需要将X移到那里, 然后才能执行相加运算。
Z = X.cuda(1)
print(X)
print(Z)
# 现在数据在同一个GPU上(Z和Y都在),我们可以将它们相加
Y + Z
# 假设变量Z已经存在于第二个GPU上。 如果我们还是调用Z.cuda(1)会发生什么? 它将返回Z,而不会复制并分配新内存。
Z.cuda(1) is Z
# 神经网络与GPU
# 神经网络模型可以指定设备。 下面的代码将模型参数放在GPU上
net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=try_gpu()) # 把它挪到GPU上
# 当输入为GPU上的张量时,模型将在同一GPU上计算结果
net(X)
# 确认模型参数存储在同一个GPU上
net[0].weight.data.device
# 互相关运算
import torch
from torch import nn
from d2l import torch as d2l
def corr2d(X, K): # K是核矩阵
"""计算二维互相关运算"""
h, w = K.shape # 行数和列数
Y = torch.zeros(X.shape[0] - h + 1, X.shape[1] - w + 1) # Y输出的大小
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i:i + h, j:j + w] * K).sum()
return Y
# 验证上述二维互相关运算的输出
X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
corr2d(X, K)
# 实现二维卷积层
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super().__init__()
self.weight = nn.Parameter(torch.rand(kernel_size)) # 可学
self.bias = nn.Parameter(torch.zeros(1))
def forward(self, x):
return corr2d(x, self.weight) + self.bias
# 图像中目标的边缘检测
X = torch.ones(6, 8)
X[:, 2:6] = 0
X
K = torch.tensor([[1.0, -1.0]])
Y = corr2d(X, K)
Y
corr2d(X.t(), K) # 这个卷积核K只可以检测垂直边缘,无法检测水平边缘
# 学习由X生成Y的卷积核
conv2d = nn.Conv2d(1, 1, kernel_size=(1, 2), bias=False) # 输入输出通道都为1
X = X.reshape((1, 1, 6, 8)) # 批量大小数,通道数
Y = Y.reshape((1, 1, 6, 7))
for i in range(10): # 迭代十次
Y_hat = conv2d(X)
l = (Y_hat - Y)**2
conv2d.zero_grad()
l.sum().backward()
conv2d.weight.data[:] -= 3e-2 * conv2d.weight.grad # 学习率3e-2
if (i + 1) % 2 == 0:
print(f'batch {i + 1}, loss {l.sum():.3f}')
# 所学的卷积核的权重张量
conv2d.weight.data.reshape((1, 2))
# 在所有侧边填充1个像素
import torch
from torch import nn
def comp_conv2d(conv2d, X):
X = X.reshape((1, 1) + X.shape) # (1, 1)是批量大小数和通道数
Y = conv2d(X)
return Y.reshape(Y.shape[2:])
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1) # 先填充再卷积
X = torch.rand(size=(8, 8))
comp_conv2d(conv2d, X).shape
# 填充不同的高度和宽度
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))
comp_conv2d(conv2d, X).shape
# 将高度和宽度的步幅设置为2,从而将输入的高度和宽度减半
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape
# 一个稍微复杂的例子
conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
comp_conv2d(conv2d, X).shape
# 实现一下多输入通道互相关运算
import torch
from d2l import torch as d2l
def corr2d_multi_in(X, K): # X,K都是3d
return sum(d2l.corr2d(x, k) for x, k in zip(X, K)) # 对X,K zip起来,每次zip起来都会对最外面的那个通道做遍历,即输入通道
# 验证互相关运算的输出
X = torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])
corr2d_multi_in(X, K)
# 计算多个通道的输出的互相关函数
def corr2d_multi_in_out(X, K): # X是3d的,K是4d的,其最外维是输出通道
return torch.stack([corr2d_multi_in(X, k) for k in K], 0) # 把每个输出通道做多输入通道互相关运算
K = torch.stack((K, K + 1, K + 2), 0)
K.shape
corr2d_multi_in_out(X, K)
# 1x1卷积层, 等价于一个全连接
def corr2d_multi_in_out_1x1(X, K):
c_i, h, w = X.shape
c_o = K.shape[0]
X = X.reshape((c_i, h * w)) # h * w把高宽拉成一条向量
K = K.reshape((c_o, c_i))
Y = torch.matmul(K, X)
return Y.reshape((c_o, h, w))
X = torch.normal(0, 1, (3, 3, 3))
K = torch.normal(0, 1, (2, 3, 1, 1))
Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
# 当执行1x1卷积运算时,上述函数相当于先前实现的互相关函数corr2d_multi_in_out
assert float(torch.abs(Y1 - Y2).sum()) < 1e-6
# 实现汇聚层的前向传播
import torch
from torch import nn
from d2l import torch as d2l
def pool2d(X, pool_size, mode='max'):
p_h, p_w = pool_size
Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1)) # 单通道
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
if mode == 'max':
Y[i, j] = X[i:i + p_h, j:j + p_w].max()
elif mode == 'avg':
Y[i, j] = X[i:i + p_h, j:j + p_w].mean()
return Y
# 验证二维最大汇聚层的输出
X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
pool2d(X, (2, 2))
# 验证平均汇聚层
pool2d(X, (2, 2), 'avg')
# 填充和步幅
X = torch.arange(16, dtype=torch.float32).reshape((1, 1, 4, 4))
X
# 深度学习框架中的步幅与汇聚窗口的大小相同,窗口没有重叠
pool2d = nn.MaxPool2d(3) # 3x3窗口
pool2d(X)
# 手动设定填充和步幅
pool2d = nn.MaxPool2d(3, padding=1, stride=2)
pool2d(X)
# 设定一个任意大小的矩形汇聚窗口,并分别设定填充和步幅的高度和宽度
pool2d = nn.MaxPool2d((2, 3), stride=(2, 3), padding=(0, 1))
pool2d(X)
# 池化层在每个输入通道上单独运算
X = torch.cat((X, X + 1), 1) # 双通道
X
pool2d = nn.MaxPool2d(3, padding=1, stride=2)
pool2d(X)
# LeNet(LeNet-5)由两个部分组成:卷积编码器和全连接层密集块
import torch
from torch import nn
from d2l import torch as d2l
class Reshape(torch.nn.Module): # 可以放在nn.Sequential()中
def forward(self, x):
return x.view(-1, 1, 28, 28)
net = torch.nn.Sequential(Reshape(),
nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Flatten(), # 批量保持,后面全拉成一个向量
nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
nn.Linear(120, 84), nn.Sigmoid(),
nn.Linear(84, 10))
# 检查模型
X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)
for layer in net:
X = layer(X)
print(layer.__class__.__name__, 'output shape: \t', X.shape)
# 模型训练,LeNet在Fashion-MNIST数据集上的表现
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
# 对evaluate_accuracy函数进行轻微的修改
def evaluate_accuracy_gpu(net, data_iter, device=None): #@save
"""使用GPU计算模型在数据集上的精度"""
if isinstance(net, nn.Module):
net.eval() # 设置为评估模式
if not device:
device = next(iter(net.parameters())).device
# 正确预测的数量,总预测的数量
metric = d2l.Accumulator(2)
with torch.no_grad():
for X, y in data_iter:
if isinstance(X, list):
# BERT微调所需的(之后将介绍)
X = [x.to(device) for x in X]
else:
X = X.to(device)
y = y.to(device)
metric.add(d2l.accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
# 为了使用GPU,我们还需要一点小改动
#@save
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
"""用GPU训练模型(在第六章定义)"""
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
print('training on', device)
net.to(device)
optimizer = torch.optim.SGD(net.parameters(), lr=lr)
loss = nn.CrossEntropyLoss()
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
timer, num_batches = d2l.Timer(), len(train_iter)
for epoch in range(num_epochs):
# 训练损失之和,训练准确率之和,样本数
metric = d2l.Accumulator(3)
net.train()
for i, (X, y) in enumerate(train_iter):
timer.start()
optimizer.zero_grad()
X, y = X.to(device), y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
l.backward()
optimizer.step()
with torch.no_grad():
metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
timer.stop()
train_l = metric[0] / metric[2]
train_acc = metric[1] / metric[2]
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches,
(train_l, train_acc, None))
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
f'test acc {test_acc:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
f'on {str(device)}')
# 训练和评估LeNet-5模型
lr, num_epochs = 0.9, 10
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
import torch
from torch import nn
from d2l import torch as d2l
net = nn.Sequential(
# 这里使用一个11*11的更大窗口来捕捉对象。
# 同时,步幅为4,以减少输出的高度和宽度。
# 另外,输出通道的数目远大于LeNet
nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
# 使用三个连续的卷积层和较小的卷积窗口。
# 除了最后的卷积层,输出通道的数量进一步增加。
# 在前两个卷积层之后,汇聚层不用于减少输入的高度和宽度
nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(),
nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(),
nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Flatten(),
# 这里,全连接层的输出数量是LeNet中的好几倍。使用dropout层来减轻过拟合
nn.Linear(6400, 4096), nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096), nn.ReLU(),
nn.Dropout(p=0.5),
# 最后是输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10))
X = torch.randn(1, 1, 224, 224)
for layer in net:
X=layer(X)
print(layer.__class__.__name__,'output shape:\t',X.shape)
# Fashion-MNIST图像的分辨率(28x28像素)低于ImageNet图像。我们将它们增加到224x224(通常来讲这不是一个明智的做法,但在这里这样做是为了有效使用AlexNet架构)
batch_size = 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
# 训练
lr, num_epochs = 0.01, 10
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
import torch
from torch import nn
from d2l import torch as d2l
# 定义了一个名为vgg_block的函数来实现一个VGG块
def vgg_block(num_convs, in_channels, out_channels): # num_convs卷积层的个数
layers = []
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels,
kernel_size=3, padding=1))
layers.append(nn.ReLU())
in_channels = out_channels
layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
return nn.Sequential(*layers)
# VGG网络
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512)) # 一共五块,(卷积层数,输出通道数),块数不能变,但可以设置每块多少层卷积和通道数
# VGG-11
def vgg(conv_arch):
conv_blks = []
in_channels = 1
# 卷积层部分
for (num_convs, out_channels) in conv_arch:
conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
in_channels = out_channels
return nn.Sequential(
*conv_blks, nn.Flatten(),
# 全连接层部分
nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 10))
net = vgg(conv_arch)
# 观察每个层输出的形状
X = torch.randn(size=(1, 1, 224, 224))
for blk in net:
X = blk(X)
print(blk.__class__.__name__,'output shape:\t',X.shape)
# 由于VGG-11比AlexNet计算量更大,因此我们构建了一个通道数较少的网络
ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch] # 所有通道数除以4
net = vgg(small_conv_arch)
# 模型训练
lr, num_epochs, batch_size = 0.05, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
import torch
from torch import nn
from d2l import torch as d2l
# NiN块
def nin_block(in_channels, out_channels, kernel_size, strides, padding):
return nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding), # 一个普通卷积层开始,后面是两个1x1的卷积层
nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU())
# NiN模型
net = nn.Sequential(
nin_block(1, 96, kernel_size=11, strides=4, padding=0),
nn.MaxPool2d(3, stride=2),
nin_block(96, 256, kernel_size=5, strides=1, padding=2),
nn.MaxPool2d(3, stride=2),
nin_block(256, 384, kernel_size=3, strides=1, padding=1),
nn.MaxPool2d(3, stride=2),
nn.Dropout(0.5),
# 标签类别数是10
nin_block(384, 10, kernel_size=3, strides=1, padding=1),
nn.AdaptiveAvgPool2d((1, 1)), # 全局平均池化层,高宽都变成1
# 将四维的输出转成二维的输出,其形状为(批量大小,10)
nn.Flatten())
# 查看每个块的输出形状
X = torch.rand(size=(1, 1, 224, 224))
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape:\t', X.shape)
# 训练模型
lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
# Inception块
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
class Inception(nn.Module):
# c1--c4是每条路径的输出通道数
def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
super(Inception, self).__init__(**kwargs)
# 线路1,单1x1卷积层
self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
# 线路2,1x1卷积层后接3x3卷积层
self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
# 线路3,1x1卷积层后接5x5卷积层
self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
# 线路4,3x3最大汇聚层后接1x1卷积层
self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)
def forward(self, x):
p1 = F.relu(self.p1_1(x))
p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
p4 = F.relu(self.p4_2(self.p4_1(x)))
# 在通道维度上连结输出
return torch.cat((p1, p2, p3, p4), dim=1) # dim=0是批量数
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
nn.ReLU(),
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32),
Inception(256, 128, (128, 192), (32, 96), 64),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64),
Inception(512, 160, (112, 224), (24, 64), 64),
Inception(512, 128, (128, 256), (24, 64), 64),
Inception(512, 112, (144, 288), (32, 64), 64),
Inception(528, 256, (160, 320), (32, 128), 128),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
Inception(832, 384, (192, 384), (48, 128), 128),
nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten())
net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10))
X = torch.rand(size=(1, 1, 96, 96))
for layer in net:
X = layer(X)
print(layer.__class__.__name__,'output shape:\t', X.shape)
lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
import torch
from torch import nn
from d2l import torch as d2l
# 从零实现
def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum): # gamma, beta可学的
# 通过is_grad_enabled来判断当前模式是训练模式还是预测模式
if not torch.is_grad_enabled():
# 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
else:
assert len(X.shape) in (2, 4)
if len(X.shape) == 2:
# 使用全连接层的情况,计算特征维上的均值和方差
mean = X.mean(dim=0)
var = ((X - mean) ** 2).mean(dim=0)
else:
# 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
# 这里我们需要保持X的形状以便后面可以做广播运算
mean = X.mean(dim=(0, 2, 3), keepdim=True)
var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True)
# 训练模式下,用当前的均值和方差做标准化
X_hat = (X - mean) / torch.sqrt(var + eps)
# 更新移动平均的均值和方差,全局的
moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
moving_var = momentum * moving_var + (1.0 - momentum) * var
Y = gamma * X_hat + beta # 缩放和移位
return Y, moving_mean.data, moving_var.data
class BatchNorm(nn.Module):
# num_features:完全连接层的输出数量或卷积层的输出通道数。
# num_dims:2表示完全连接层,4表示卷积层
def __init__(self, num_features, num_dims):
super().__init__()
if num_dims == 2:
shape = (1, num_features)
else:
shape = (1, num_features, 1, 1)
# 参与求梯度和迭代的拉伸和偏移参数,分别初始化成1和0
self.gamma = nn.Parameter(torch.ones(shape))
self.beta = nn.Parameter(torch.zeros(shape))
# 非模型参数的变量初始化为0和1
self.moving_mean = torch.zeros(shape)
self.moving_var = torch.ones(shape)
def forward(self, X):
# 如果X不在内存上,将moving_mean和moving_var
# 复制到X所在显存上
if self.moving_mean.device != X.device:
self.moving_mean = self.moving_mean.to(X.device)
self.moving_var = self.moving_var.to(X.device)
# 保存更新过的moving_mean和moving_var
Y, self.moving_mean, self.moving_var = batch_norm(
X, self.gamma, self.beta, self.moving_mean,
self.moving_var, eps=1e-5, momentum=0.9)
return Y
# 使用批量规范化层的 LeNet
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
nn.Linear(16 * 4 * 4, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(),
nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(),
nn.Linear(84, 10))
# 训练
lr, num_epochs, batch_size = 1.0, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,))
# 简明实现
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(),
nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(),
nn.Linear(84, 10))
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
import torch
from torch import nn
from d2l import torch as d2l
from matplotlib import pyplot as plt
# 注意力汇聚:Nadaraya-Watson 核回归
n_train = 50 # 训练样本数
x_train, _ = torch.sort(torch.rand(n_train) * 5) # 排序后的训练样本,0-5之间随机生成50个数,然后排序
def f(x):
return 2 * torch.sin(x) + x**0.8
y_train = f(x_train) + torch.normal(0.0, 0.5, (n_train,)) # 训练样本的输出,label
x_test = torch.arange(0, 5, 0.1) # 测试样本
y_truth = f(x_test) # 测试样本的真实输出
n_test = len(x_test) # 测试样本数
n_test
def plot_kernel_reg(y_hat):
d2l.plot(x_test, [y_truth, y_hat], 'x', 'y', legend=['Truth', 'Pred'],
xlim=[0, 5], ylim=[-1, 5])
d2l.plt.plot(x_train, y_train, 'o', alpha=0.5);
# 平均汇聚
y_hat = torch.repeat_interleave(y_train.mean(), n_test)
plot_kernel_reg(y_hat)
# 黄色的点是训练的点
# 非参数注意力汇聚
# x_test是query,x_train包含着键
# X_repeat的形状:(n_test,n_train),
# 每一行都包含着相同的测试输入(例如:同样的查询)
X_repeat = x_test.repeat_interleave(n_train).reshape((-1, n_train)) # repeat_interleave控制重复多少次
# x_train包含着键。attention_weights的形状:(n_test,n_train),
# 每一行都包含着要在给定的每个查询的值(y_train)之间分配的注意力权重
attention_weights = nn.functional.softmax(-(X_repeat - x_train)**2 / 2, dim=1)
# y_hat的每个元素都是值的加权平均值,其中的权重是注意力权重
y_hat = torch.matmul(attention_weights, y_train)
plot_kernel_reg(y_hat)
# 注意力的权重
# 以后调用该函数来显示att权重
def show_heatmaps(matrices, xlabel, ylabel, titles=None, figsize=(2.5, 2.5),
cmap='Reds'):
d2l.use_svg_display()
num_rows, num_cols = matrices.shape[0], matrices.shape[1]
fig, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize,
sharex=True, sharey=True, squeeze=False)
for i, (row_axes, row_matrices) in enumerate(zip(axes, matrices)):
for j, (ax, matrix) in enumerate(zip(row_axes, row_matrices)):
pcm = ax.imshow(matrix.detach().numpy(), cmap=cmap)
if i == num_rows - 1:
ax.set_xlabel(xlabel)
if j == 0:
ax.set_ylabel(ylabel)
if titles:
ax.set_title(titles[j])
fig.colorbar(pcm, ax=axes, shrink=0.6);
show_heatmaps(attention_weights.unsqueeze(0).unsqueeze(0),
xlabel='Sorted training inputs',
ylabel='Sorted testing inputs')
plt.show()
# 批量矩阵乘法
X = torch.ones((2, 1, 4))
Y = torch.ones((2, 4, 6))
torch.bmm(X, Y).shape
# 带参数注意力汇聚
class NWKernelRegression(nn.Module):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.w = nn.Parameter(torch.rand((1,), requires_grad=True))
def forward(self, queries, keys, values):
# queries和attention_weights的形状为(查询个数,“键-值”对个数)
queries = queries.repeat_interleave(keys.shape[1]).reshape((-1, keys.shape[1]))
self.attention_weights = nn.functional.softmax(
-((queries - keys) * self.w)**2 / 2, dim=1)
# values的形状为(查询个数,“键-值”对个数)
return torch.bmm(self.attention_weights.unsqueeze(1),
values.unsqueeze(-1)).reshape(-1)
# 训练
# X_tile的形状:(n_train,n_train),每一行都包含着相同的训练输入
X_tile = x_train.repeat((n_train, 1))
# Y_tile的形状:(n_train,n_train),每一行都包含着相同的训练输出
Y_tile = y_train.repeat((n_train, 1))
# keys的形状:('n_train','n_train'-1)
keys = X_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train, -1))
# values的形状:('n_train','n_train'-1)
values = Y_tile[(1 - torch.eye(n_train)).type(torch.bool)].reshape((n_train, -1))
# 使用平方损失函数
net = NWKernelRegression()
loss = nn.MSELoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=0.5)
animator = d2l.Animator(xlabel='epoch', ylabel='loss', xlim=[1, 5])
for epoch in range(5):
trainer.zero_grad()
l = loss(net(x_train, keys, values), y_train)
l.sum().backward()
trainer.step()
print(f'epoch {epoch + 1}, loss {float(l.sum()):.6f}')
animator.add(epoch + 1, float(l.sum()))
# keys的形状:(n_test,n_train),每一行包含着相同的训练输入(例如,相同的键)
keys = x_train.repeat((n_test, 1))
# value的形状:(n_test,n_train)
values = y_train.repeat((n_test, 1))
y_hat = net(x_test, keys, values).unsqueeze(1).detach()
plot_kernel_reg(y_hat)
show_heatmaps(net.attention_weights.unsqueeze(0).unsqueeze(0),
xlabel='Sorted training inputs',
ylabel='Sorted testing inputs')
# w使窗口变窄,每一个测试样本分的权重相对集中
# 注意力打分函数
import math
import torch
from torch import nn
from d2l import torch as d2l
from matplotlib import pyplot as plt
# 遮蔽softmax操作
#@save
def masked_softmax(X, valid_lens):
"""通过在最后一个轴上掩蔽元素来执行softmax操作"""
# X:3D张量,valid_lens:1D或2D张量
if valid_lens is None:
return nn.functional.softmax(X, dim=-1) # 没给valid_lens,就是正常的softmax
else:
shape = X.shape
if valid_lens.dim() == 1:
valid_lens = torch.repeat_interleave(valid_lens, shape[1])
else:
valid_lens = valid_lens.reshape(-1)
# 最后一轴上被掩蔽的元素使用一个非常大的负值替换,从而其softmax输出为0
X = d2l.sequence_mask(X.reshape(-1, shape[-1]), valid_lens,
value=-1e6)
return nn.functional.softmax(X.reshape(shape), dim=-1)
# 演示此函数是如何工作
# 假设给了(2, 2, 4),第一个2是batch_size,[2, 3]表示第一个样本前面两列是有效的,第二个前三列是有效的
masked_softmax(torch.rand(2, 2, 4), torch.tensor([2, 3]))
masked_softmax(torch.rand(2, 2, 4), torch.tensor([[1, 3], [2, 4]]))
#@save
class AdditiveAttention(nn.Module):
"""加性注意力"""
def __init__(self, key_size, query_size, num_hiddens, dropout, **kwargs):
super(AdditiveAttention, self).__init__(**kwargs)
self.W_k = nn.Linear(key_size, num_hiddens, bias=False)
self.W_q = nn.Linear(query_size, num_hiddens, bias=False)
self.w_v = nn.Linear(num_hiddens, 1, bias=False)
self.dropout = nn.Dropout(dropout)
def forward(self, queries, keys, values, valid_lens): # valid_lens表示有多少对k-v是需要的
queries, keys = self.W_q(queries), self.W_k(keys)
# 在维度扩展后,
# queries的形状:(batch_size,查询的个数,1,num_hidden)
# key的形状:(batch_size,1,“键-值”对的个数,num_hiddens)
# 使用广播方式进行求和
features = queries.unsqueeze(2) + keys.unsqueeze(1)
features = torch.tanh(features)
# self.w_v仅有一个输出,因此从形状中移除最后那个维度。
# scores的形状:(batch_size,查询的个数,“键-值”对的个数)
scores = self.w_v(features).squeeze(-1)
self.attention_weights = masked_softmax(scores, valid_lens)
# values的形状:(batch_size,“键-值”对的个数,值的维度)
return torch.bmm(self.dropout(self.attention_weights), values)
# 演示上面的AdditiveAttention类
queries, keys = torch.normal(0, 1, (2, 1, 20)), torch.ones((2, 10, 2)) # (2, 1, 20) batch_size为2,1个query,长度为20
# values的小批量,两个值矩阵是相同的,是(2, 10, 4)
values = torch.arange(40, dtype=torch.float32).reshape(1, 10, 4).repeat( 2, 1, 1)
valid_lens = torch.tensor([2, 6]) # 查看键值对
attention = AdditiveAttention(key_size=2, query_size=20, num_hiddens=8,
dropout=0.1)
attention.eval()
attention(queries, keys, values, valid_lens)
# 注意力权重
def show_heatmaps(matrices, xlabel, ylabel, titles=None, figsize=(2.5, 2.5),
cmap='Reds'):
d2l.use_svg_display()
num_rows, num_cols = matrices.shape[0], matrices.shape[1]
fig, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize,
sharex=True, sharey=True, squeeze=False)
for i, (row_axes, row_matrices) in enumerate(zip(axes, matrices)):
for j, (ax, matrix) in enumerate(zip(row_axes, row_matrices)):
pcm = ax.imshow(matrix.detach().numpy(), cmap=cmap)
if i == num_rows - 1:
ax.set_xlabel(xlabel)
if j == 0:
ax.set_ylabel(ylabel)
if titles:
ax.set_title(titles[j])
fig.colorbar(pcm, ax=axes, shrink=0.6);
show_heatmaps(attention.attention_weights.reshape((1, 1, 2, 10)),
xlabel='Keys', ylabel='Queries')
plt.show()
# 缩放点积注意力
#@save
class DotProductAttention(nn.Module):
"""缩放点积注意力"""
def __init__(self, dropout, **kwargs):
super(DotProductAttention, self).__init__(**kwargs)
self.dropout = nn.Dropout(dropout)
# queries的形状:(batch_size,查询的个数,d)
# keys的形状:(batch_size,“键-值”对的个数,d)
# values的形状:(batch_size,“键-值”对的个数,值的维度)
# valid_lens的形状:(batch_size,)或者(batch_size,查询的个数)
def forward(self, queries, keys, values, valid_lens=None):
d = queries.shape[-1]
# 设置transpose_b=True为了交换keys的最后两个维度
scores = torch.bmm(queries, keys.transpose(1,2)) / math.sqrt(d) # key要做转置
self.attention_weights = masked_softmax(scores, valid_lens)
return torch.bmm(self.dropout(self.attention_weights), values)
# 演示上述的DotProductAttention类
queries = torch.normal(0, 1, (2, 1, 2))
attention = DotProductAttention(dropout=0.5)
attention.eval()
attention(queries, keys, values, valid_lens)
show_heatmaps(attention.attention_weights.reshape((1, 1, 2, 10)),
xlabel='Keys', ylabel='Queries')