传送门:https://transformers.run/c2/2021-12-14-transformers-note-3/
pytorch基础知识
tensor : 张量。
需要知道的内容:
- 张量构建
- 张量计算
- 自动微分
- 形状调整
- 广播机制
- 索引与切片
- 升降维度
Tensor
张量:理解成高纬度的向量就完事。
构造向量:
- 使用torch.tensor()
- torch.from_numpy进行构建
>>> array = [[1.0, 3.8, 2.1], [8.6, 4.0, 2.4]]
>>> torch.tensor(array)
tensor([[1.0000, 3.8000, 2.1000],
[8.6000, 4.0000, 2.4000]])
>>> import numpy as np
>>> array = np.array([[1.0, 3.8, 2.1], [8.6, 4.0, 2.4]])
>>> torch.from_numpy(array)
tensor([[1.0000, 3.8000, 2.1000],
[8.6000, 4.0000, 2.4000]], dtype=torch.float64)
张量计算:
- 支持简单的加减乘除(针对的张量里面的对应单元),同时也支持点积计算与矩阵相乘。
e.g.
>>> x = torch.tensor([1, 2, 3], dtype=torch.double)
>>> y = torch.tensor([4, 5, 6], dtype=torch.double)
>>> print(x + y)
tensor([5., 7., 9.], dtype=torch.float64)
>>> print(x - y)
tensor([-3., -3., -3.], dtype=torch.float64)
>>> print(x * y)
tensor([ 4., 10., 18.], dtype=torch.float64)
>>> print(x / y)
tensor([0.2500, 0.4000, 0.5000], dtype=torch.float64)
>>> x.dot(y)
tensor(32., dtype=torch.float64)
>>> x.sin()
tensor([0.8415, 0.9093, 0.1411], dtype=torch.float64)
>>> x.exp()
tensor([ 2.7183, 7.3891, 20.0855], dtype=torch.float64)
除了数学运算,Pytorch 还提供了多种张量操作函数,如聚合 (aggregation)、拼接 (concatenation)、比较、随机采样、序列化等,详细使用方法可以参见 Pytorch 官方文档。
自动微分
pytorch可以进行梯度的自动计算,根据反向传播算法可以计算出来。微分计算的是特定表达式与特定的自变量值。
具体步骤:
- 设置自变量tensor,并且requires_grad=True
- 构造因变量表达式。
- 调用tensor.backward()
- 这时候计算的梯度就在${自变量}.grad里面
e.g.
>>> x = torch.tensor([2.], requires_grad=True)
>>> y = torch.tensor([3.], requires_grad=True)
>>> z = (x + y) * (y - 2)
>>> print(z)
tensor([5.], grad_fn=<MulBackward0>)
>>> z.backward()
>>> print(x.grad, y.grad)
tensor([1.]) tensor([6.])
形状调整
形状调整三种:
- 形状转换
- 转置
- 交换维度
形状转换一般用{tensor}.reshape({想要的shape})
剩下的转置跟交换维度,看的不是很懂?暂时放弃。
广播机制
当在计算的过程之中,发现两个张量的形状问题导致无法计算的时候,torch会自动将张量进行广播完成计算。
当然输出的最后的结果也会是最终进行广播的结果。
索引与切片
与python数组之类的类似, 直接看代码
>>> x = torch.arange(12).view(3, 4)
>>> x
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> x[1, 3] # element at row 1, column 3
tensor(7)
>>> x[1] # all elements in row 1
tensor([4, 5, 6, 7])
>>> x[1:3] # elements in row 1 & 2
tensor([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> x[:, 2] # all elements in column 2
tensor([ 2, 6, 10])
>>> x[:, 2:4] # elements in column 2 & 3
tensor([[ 2, 3],
[ 6, 7],
[10, 11]])
>>> x[:, 2:4] = 100 # set elements in column 2 & 3 to 100
>>> x
tensor([[ 0, 1, 100, 100],
[ 4, 5, 100, 100],
[ 8, 9, 100, 100]])
升降维度
直接贴图。
>>> a = torch.tensor([1, 2, 3, 4])
>>> a.shape
torch.Size([4])
>>> b = torch.unsqueeze(a, dim=0)
>>> print(b, b.shape)
tensor([[1, 2, 3, 4]]) torch.Size([1, 4])
>>> b = a.unsqueeze(dim=0) # another way to unsqueeze tensor
>>> print(b, b.shape)
tensor([[1, 2, 3, 4]]) torch.Size([1, 4])
>>> c = b.squeeze()
>>> print(c, c.shape)
tensor([1, 2, 3, 4]) torch.Size([4])
数据加载
首先对数据的大概处理现有一个基本的顺序流程
对于数据的大概处理是:加载数据 ---->> shuffle —>> 分为一个个minibatch —>> 丢进模型训练。
pytorch 在数据载入主要使用两个主要的数据结构:
- Dataset:主要用来存储数据,并且给出映射关系,可以简单理解成最后能够给出一个数组类似的数据结构。能够进行arr[idx] 访问。
- DataLoaders:主要用来训练遍历数据与完成丢进模型训练之前的操作。
dataset再细说一下,根据加载的数据类别主要分为两种(以下为个人理解):
- 迭代性数据集:这种类型本身就具有一定的映射关系,所以只需要给出迭代器就完事了。
- 映射类数据集:说白了就类似map结构,key可以是任何的东西。那么这时候就需要给出根据特定的key返回的数据到底是什么。如果这个KEY是类似整数的结构,那么系统会自身构造一个映射关系,就能够完成像上面迭代型的任务。如果这个key非整数型,那么还需要手动添加一个映射法则,将这个key映射成一个可以遍历访问的结构。也就是sampler
模型构建
- pytorch 中所有的模型都是nn.Module的子类
所以,自己构建模型需要满足以下准则:
- 是nn.Module的子类
- 写一下自己网络的内部结构,实际上就是构造一下内部变量,将已有的pytorch模块加载进自己的类里
- 重写forward函数,forward函数就是用来推理整个过程的函数,也是决定了这个模型的形状。
必要类补充:
nn.Sequiential()实际上就是一个装在nn.Module的容器。
e.g.
device = 'cuda' if torch.cuda.is_available() else 'cpu'
class MyNeuralNetwork(nn.Module):
def __init__(self):
super(MyNeuralNetwork, self).__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28*28, 512),
nn.ReLU(),
nn.Linear(512, 256),
nn.ReLU(),
nn.Linear(256, 10),
nn.Dropout(p=0.2)
)
def forward(self, x):
x = self.flatten(x),
logits = self.linear_relu_stack(x)
return logits
model = MyNeuralNetwork().to(device)
print(model)
整体训练
首先先将神经网络理解为一个函数,这个通过这个函数能够获得结果,但里面同时有参数。
丢进去一个东西能够获得一个结果,但目标是让这个神经网络F获得最优秀的结果。那么这时候就需要一个评判机制,也就是损失函数(LOSS)。
通过损失函数,就可以知道我这个结果是好是坏。那么根据好坏我就可以针对这个网络进行修改,希望往好的方向进行修改。至于这个修改机制,就称为优化器(optimizer)。大多数优化器修改机制都是通过进行梯度计算,获得导数信息来针对进行修改。
所以训练过程主要如下:
- 针对训练集进行分为几个batch
- 分完之后丢到网络中推理得到结果
- 利用损失函数计算获得结果
- 这时候损失函数肯定是跟关于输入的一个表达式,可以根据这个损失函数计算梯度
- 计算完了之后就交给优化器进行优化参数
需要使用到的大概函数:
- optimizer.zero_grad(): 将导数结果归零,避免之前训练带来的错误
- loss.backward(): 损失函数梯度计算
- optimizer.step(): 优化一步
# step1 加载数据
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor()
)
learning_rate = 1e-3
batch_size = 64
epochs = 3
train_dataloader = DataLoader(training_data, batch_size=batch_size, shuffle=True)
# step2 构建训练
def train_loop(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
model.train()
for batch, (X, y) in enumerate(dataloader, start=1):
X, y = X.to(device), y.to(device)
pred = model(X)
loss = loss_fn(pred, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 100 == 0 :
loss, current = loss.item(), batch * len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
# step3 run
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train_loop(train_dataloader, model, loss_fn, optimizer)
# test_loop(test_dataloader, model, loss_fn)
print("Done!")