每小节都有对应的可执行notebook文件。
由于本人能力有限,不可能将所有Pytorch的操作都进行讲解。因此强烈建议读者遇到问题时候查阅Pytorch的官方文档和参与一些论坛社区的讨论。
对Pytorch的安装,这里也不做过多的展开介绍。可以来看沐神的视频来进行学习。
为此我们首先导入torch
import torch # 为了后续方便我顺便将下面这些库也导入 import numpy as np
可以通过我们熟悉的List或者Numpy来进行创建
list_form = [[1, -1], [2, -2]] x1 = torch.tensor(list_form) # 从list中创建 x2 = torch.from_numpy(np.array(list_form)) # 从numpy中创建 x1, x2
当然tensor也可以转换为numpy
x = x1.numpy() x
其他类型tensor的创建
x = torch.arange(12) print(x) x.shape, x.numel() # 形状,数量
x = torch.empty(3, 4) x
x = torch.rand(3, 4) # 元素在(0, 1)之间 x
x = torch.ones(3, 4) x
x = torch.ones(3, 4, dtype=torch.long) # 指定long类型 x, x.dtype
x = torch.randn_like(x, dtype=torch.float) # 正态分布,size与x一致 x
x = x.new_ones(3, 4, dtype=torch.float) # size为(3, 4)的单位tensor x
x = x.new_ones(3, 4, dtype=torch.float) y = torch.rand(3, 4) x + y
z = torch.add(x, y) z
add_代表inplace版本。pytorch其他函数也类似如x.copy_(y), x.t_()
y.add_(x) y == z
y = x[0, :] y += 1 y == x[0, :] # 结果为True。证明源tensor也会改变
view和reshape是常用的改变tensor.shape的函数
y = x.view(12) z = x.view(-1, 6) # -1所指的维度可以根据其他维度的值推出来 x.size(), y.size(), z.size() # x.size开始时候为(3, 4)
深拷贝
x += 1 x, y # True, y的值也会跟着改变, 即使他们的shape不同。
因此如果我们想得到一个真正的副本而不是像上边那样共享内存,可以考虑使用reshape()函数。还有另外一个解决方案就是使用clone创建一个副本再使用view
x_cp = x.clone().view(12) x -= 1 x, x_cp # x_cp不会跟着x改变
x = torch.zeros([1, 2, 3]) print(f'former shape:', x.shape) # (1, 2, 3) x.squeeze_(0) # 可以指定维度,也可以不指定 print(f'shape after squeeze:', x.shape)
x = torch.zeros([2, 3]) print(f'former shape:', x.shape) x = x.unsqueeze(1) # 在维度为1处添加 print(f'shape after unsqueeze:', x.shape)
x = torch.zeros([2, 3]) x = x.transpose(0, 1) # 转置的维度 x.shape
x = torch.zeros([2, 1, 3]) y = torch.zeros([2, 2, 3]) z = torch.zeros([2, 3, 3]) a = torch.cat([x, y, z], dim=1) # 根据维度1来进行连接 a.shape # (2, 6, 3)
即先适当复制元素使这两个Tensor形状相同后再按元素运算。
x = torch.arange(1, 3).view(1, 2) print(x) y = torch.arange(1, 4).view(3, 1) y, x + y
使用pytorch自带的id函数:
x = torch.tensor([1, 2]) y = torch.tensor([3, 4]) id_before = id(y) y = y + x id(y) == id_before # False
如果想指定结果到原来的y的内存,我们可以使用前面介绍的索引来进行替换操作。
我们把x + y的结果通过[:]写进y对应的内存中
x = torch.tensor([1, 2]) y = torch.tensor([3, 4]) id_before = id(y) y[:] = y + x # 仅仅改写元素 id(y) == id_before # True
还可以使用运算符全名函数中的out参数或者自加运算符+=(也即add_())达到上述效果,如:
torch.add(x, y, out=y)
y.add_(x)
y += x
x = torch.tensor([1, 2]) y = torch.tensor([3, 4]) id_before = id(y) torch.add(x, y, out=y) # y += x, y.add_(x) # 仅仅改写元素 id(y) == id_before # True
需要注意的是,虽然view返回的Tensor与源Tensor是共享data的,但是依然是一个新的Tensor(因为Tensor除了包含data外还有一些其他属性),二者id(内存地址)并不一致。
a = torch.arange(20).reshape(5, 4) print(f'a:', a) b = a.sum(axis=0) print(f'b:', b) c = a.sum(axis=0, keepdim=True) # 可以用广播机制,保留那个求和的维度 print(f'c:', c) print(f'a/c:', a/c) # 累加求和 print(f'a累加求和:', a.cumsum(axis=0))
mv
函数A = torch.rand(5, 4) x = torch.rand(4) A, x, A.shape, x.shape, torch.mv(A, x)
mm
函数B = torch.ones(4, 3) torch.mm(A, B)
u = torch.tensor([3., -4.]) torch.norm(u)
u = torch.tensor([3., -4.]) torch.abs(u).sum()
torch.norm(torch.ones(4, 9))
首先,你需要确保你的Win/Linux机器拥有英伟达(NVIDIA)的显卡。[cuda的安装地址](https://developer.nvidia.cn/zh-
cn/cuda-toolkit)
在后面章节的模型训练中,不要频繁出现tensor在gpu和cpu之间跳转,否则训练时间会大大增加。
if torch.cuda.is_available(): # 查看是否有cuda的设备 device = torch.device("cuda") # GPU y = torch.ones_like(x, device=device) # 直接创建一个在GPU上的Tensor x = x.to(device) # 等价于 .to("cuda") z = x + y print(z) print(z.to("cpu", torch.double)) # to()将tensor转移回去cpu,同时可以更改数据类型。
为了能用深度学习来解决现实世界的问题,我们经常从预处理原始数据开始,
而不是从那些准备好的张量格式数据开始。在Python中常用的数据分析工具中,我们通常使用pandas软件包。
像庞大的Python生态系统中的许多其他扩展包一样,pandas可以与张量兼容。
这里,将简要介绍使用pandas预处理原始数据,并将原始数据转换为张量格式。当然数据处理的方法还有很多,可以自行找相关资料,版块也会在后面对相关内容进行扩展。
我们首先创建一个数据集csv文件,存放在本地文件夹,以其他格式存储的数据也可以通过类似的方式进行处理。
下面我们将数据集按行写入CSV文件中。
import os os.makedirs(os.path.join('../..', 'data'), exist_ok=True) data_file = os.path.join('../..', 'data', 'house_price.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')
下面使用pandas进行相关数据处理
# 下面一行代码可以帮助你的机器安装pandas # !pip install pandas import pandas as pd import numpy as np data = pd.read_csv(data_file) data
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2] inputs = inputs.fillna(inputs.mean()) inputs
下面将NaN视为一个类别。 由于巷子类型(Alley)列只接受两种类型的类别值Pave和NaN,
pandas可以自动将此列转换为两列Alley_Pave和Alley_nan。
巷子类型为Pave的行会将Alley_Pave的值设置为1,Alley_nan的值设置为0。
缺少巷子类型的行会将Alley_Pave和Alley_nan分别设置为0和1。
inputs_fillna = pd.get_dummies(inputs, dummy_na=True) print(inputs_fillna)
# 删除有空的那一整行 drops = inputs.isnull().any(axis=1) inputs_dropna = inputs[~drops] inputs_dropna
interpolate
为插值函数,其相关参数可以查阅pandas官方文档
# 插值 inputs_interpolate = inputs.interpolate(limit_direction='both') inputs_interpolate # 如果是str类型的仍然是无法填充的
import torch X, y = torch.tensor(inputs_fillna.values), torch.tensor(outputs.values) X, y
为了后续使用pytorch求梯度更加方便
from torch.utils.data import Dataset, DataLoader # 继承Dataset类 class MyDataset(Dataset): def __init__(self, data): """读取数据和其他预处理操作""" self.data = data def __getitem__(self, index): """每次获取一个样本""" return self.data[index] def __len__(self): """返回数据集的size""" return len(self.data)
dataset = MyDataset((X, y)) # shuffle在训练期间一般为True,测试时候为false dataloader = DataLoader(dataset, batch_size=1, shuffle=True) # shuffle参数为True代表先扰乱数据集顺序 # 转成python的iter next(iter(dataloader))
# 模型定义 from torch import nn # 单层线性神经网络 net_single = nn.Sequential(nn.Linear(2, 1)) # input数位2,output数位1 # 定义多层感知机 net_multi = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 10)) # Relu为激活函数,接下来会讲解到
import torch.nn.functional as F import matplotlib.pyplot as plt # 给定数据 x = torch.linspace(-3, 3, 100) x_np = x.data.numpy()
y_relu = torch.relu(x).data.numpy() y_sigmoid = torch.sigmoid(x).data.numpy() y_tanh = torch.tanh(x).data.numpy() y_softplus = F.softplus(x).data.numpy()
plt.figure(1, figsize=(8, 6)) plt.subplot(221) plt.plot(x_np, y_relu, c='red', label='relu') plt.ylim(-1, 5) plt.legend(loc='best') plt.subplot(222) plt.plot(x_np, y_sigmoid, c='red', label='sigmoid') plt.ylim(-0.2, 1.2) plt.legend(loc='best') plt.subplot(223) plt.plot(x_np, y_tanh, c='red', label='tanh') plt.ylim(-1.2, 1.2) plt.legend(loc='best') plt.subplot(224) plt.plot(x_np, y_softplus, c='red', label='softplus') plt.ylim(-0.2, 6) plt.legend(loc='best') plt.show()
rmse:
r
m
s
e
=
1
m
∑
i
=
1
m
(
y
i
−
y
i
)
2
rmse = \sqrt{\frac{1}{m} \sum_{i=1}^{m}\left(y_{i}-y_{i}\right)^{2}}
rmse=m1∑i=1m(yi−yi)2
crossentropy:
H
(
p
,
q
)
=
−
∑
x
(
p
(
x
)
log
q
(
x
)
H(p, q)=-\sum_{x}(p(x) \log q(x)
H(p,q)=−∑x(p(x)logq(x)
rmse = nn.MSELoss() ce = nn.CrossEntropyLoss()
import torch.nn as nn class MyModel(nn.Module): def __init__(self): """初始化你的模型,并且定义你的层""" super().__init__() self.net = nn.Sequential( nn.Linear(10, 32), nn.Sigmoid(), nn.Linear(32, 1), ) def forward(self, X): """前向传播计算你的神经网络""" return X - X.sum() device = torch.device("cpu") # 默认使用cpu if torch.cuda.is_available(): # 查看是否有cuda的设备 device = torch.device("cuda") # GPU model = MyModel().to(device=device) model(torch.ones(2, 10)) print('parameters is on:', model.net[0].weight.data.device)
path = '../../data/model_pa' # 保存 torch.save(model.state_dict(), path)
# 加载 loading = torch.load(path) model.load_state_dict(loading)
# 无参数自定义层 class CenteredLayer(nn.Module): def __init__(self): super().__init__() def forward(self, X): return X - X.sum() 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.shape
# 带参数自定义层 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 return F.relu(linear) dense = MyLinear(5, 3) print(f'dense weight:', dense.weight)
net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1)) Y = net(torch.rand(2, 64)) Y.shape
使用下面代码展示:
在1.1当中已经引入过自动求导的相关代码实现。在深度学习框架当中,会根据我们设计的模型,系统会构建一个计算图(computational graph),
来跟踪计算是哪些数据通过哪些操作组合起来产生输出。 自动微分使系统能够随后反向传播梯度。意味着跟踪整个计算图,填充关于每个参数的偏导数。
下面我们通过pytorch来实现一个简单的实例:
import torch x = torch.arange(4.0) x
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True) x.grad # 默认值为None
计算 y = 2 X T X y = 2 X^TX y=2XTX
y = 2 * torch.dot(x, x) y
下面通过调用反向传播函数来自动计算 y y y关于 X X X每个分量的梯度
y.backward() x.grad
显然结果和我们的数学推导 ∂ y ∂ X = ∂ ( 2 X T X ) ∂ X = 4 X \frac{\partial y}{\partial X} = \frac{\partial (2X^TX)}{\partial X} = 4X ∂X∂y=∂X∂(2XTX)=4X是一致的
x.grad == 4 * x # True
当计算关于
X
X
X的另一个函数的梯度时候,在默认情况下,PyTorch会累积梯度,我们需要使用x.grad_zero_()
清除之前的值。
x.grad.zero_() y = x.sum() y.backward() x.grad
当然,对非标量的变量也可以进行反向传播
x.grad.zero_() y = x * x y
# 等价于y.backward(torch.ones(len(x))) y.sum().backward() x.grad
有时候,我们希望将某些计算移动到记录的计算图之外。
例如,假设
y
y
y是作为
x
x
x的函数计算的,而
z
z
z则是作为
y
y
y和
x
x
x的函数计算的。我们想计算
z
z
z关于
x
x
x的梯度,但由于某种原因,我们希望将
y
y
y视为一个常数,
并且只考虑到
x
x
x在
y
y
y被计算后发挥的作用。
下面例子在反向传播过程中将 u u u当做一个常数进行处理:
x.grad.zero_() y = x * x u = y.detach() z = u * x z.sum().backward() x.grad == u
简单验证一下:
x.grad.zero_() y.sum().backward() x.grad == 2 * x
即使在控制流语句下,梯度计算仍然可以正常工作:
d = f ( a ) = k ∗ a d = f(a) = k * a d=f(a)=k∗a
def f(a): b = a * 2 while b.norm() < 1000: b = b * 2 if b.sum() > 0: c = b else: c = 100 * b return c a = torch.randn(size=(), requires_grad=True) d = f(a) d.backward() a.grad == d / a # 验证一下
下面以简单线性回归为例子:
import torch from torch.utils import data true_w = torch.tensor([2, -3.4]) true_b = 4.2 # 生成数据 def synthetic_data(w, b, num_examples): """生成带噪音的数据集 y = Xw + b + noise.""" 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)) features, labels = synthetic_data(true_w, true_b, 1000)
def load_array(data_arrays, batch_size, is_train=True): #@save """构造一个PyTorch数据迭代器""" dataset = data.TensorDataset(*data_arrays) return data.DataLoader(dataset, batch_size, shuffle=is_train) batch_size = 10 data_iter = load_array((features, labels), batch_size) # 转成python的iter next(iter(data_iter))
# 模型定义 from torch import nn # 单层神经网络 net = nn.Sequential(nn.Linear(2, 1))
# 初始化模型参数 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: l = loss(net(X), y) # 自带模型参数,不需要w和b放进去了 trainer.zero_grad() # 优化器梯度清零 l.backward() # 自动帮你求sum了 trainer.step() # 模型更新 l = loss(net(features), labels) print(f'epoch {epoch + 1}, loss {l:f}')