Python Pytorch框架使用 – dawsonenjoy – Python量化投资

Python Pytorch框架使用 – dawsonenjoy

介绍

相比TensorFlow的静态图开发,Pytorch的动态图特性使得开发起来更加人性化,选择Pytorch的理由可以参考:https://www.jianshu.com/p/c1d9cdb52548

安装

分为CPU和GPU版本:

简单示例

import torch
import matplotlib.pyplot as plt
w = 2
b = 1
noise = torch.rand(100, 1)
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1)
# 因为输入层格式要为(-1, 1),所以这里将(100)的格式转成(100, 1)
y = w*x + b + noise
# 拟合分布在y=2x+1上并且带有噪声的散点
model = torch.nn.Sequential(
    torch.nn.Linear(1, 16),
    torch.nn.Tanh(),
    torch.nn.Linear(16, 1),
    )
# 自定义的网络,带有2个全连接层和一个tanh层
loss_fun = torch.nn.MSELoss()
# 定义损失函数为均方差
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 使用adam作为优化器更新网络模型的权重,学习率为0.01
plt.ion()
# 图形交互
for _ in range(1000):
    ax = plt.axes()
    output = model(x)
    # 数据向后传播(经过网络层的一次计算)
    loss = loss_fun(output, y)
    # 计算损失值
    # print("before zero_grad:{}".format(list(model.children())[0].weight.grad))
    # print("-"*100)
    model.zero_grad()
    # 优化器清空梯度
    # print("before zero_grad:{}".format(list(model.children())[0].weight.grad))
    # print("-"*100)
    # 通过注释地方可以对比发现执行zero_grad方法以后倒数梯度将会被清0
    # 如果不清空梯度的话,则会不断累加梯度,从而影响到当前梯度的计算
    loss.backward()
    # 向后传播,计算当前梯度,如果这步不执行,那么优化器更新时则会找不到梯度
    optimizer.step()
    # 优化器更新梯度,如果这步不执行,那么因为梯度没有发生改变,loss会一直计算最开始的那个梯度
    if _ % 100 == 0:
        plt.cla()
        plt.scatter(x.data.numpy(), y.data.numpy())
        plt.plot(x.data.numpy(), output.data.numpy(), 'r-', lw=5)
        plt.text(0.5, 0, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color':  'red'})
        plt.pause(0.1)
        # print("w:", list(model.children())[0].weight.t() @ list(model.children())[-1].weight.t())
        # 通过这句可以查看权值变化,可以发现最后收敛到2附近
plt.ioff()
plt.show()

数据类型

标量/张量

pytorch里的基本单位,个人理解是0维、没有方向的(比如单个数字那样)称为标量,有方向的称为张量(比如一串数字),通过torch.tensor定义,举例:

>>> torch.tensor(1.)
tensor(1.)

pytorch中还提供了以下数据类型的张量:

通用类型  Tensor
float型  torch.FloatTensor
double型  torch.DoubleTensor
int型  torch.IntTensor
long型  torch.LongTensor
byte型  torch.ByteTensor

这些数据类型的张量使用方法和tensor有点不同,tensor是自定义数据,而标定数据类型的将会随机生成一个该类型的数据,举例:

>>> torch.tensor(1.)
tensor(1.)
# 生成值为1的张量
>>> torch.FloatTensor(1)
tensor([-3.0147e-21])
# 生成1个随机的float型张量
>>> torch.FloatTensor(2,3)
tensor([[-7.3660e-21,  4.5914e-41,  9.6495e+20],
        [ 8.2817e-43,  0.0000e+00,  0.0000e+00]])
# 生成随机指定尺寸的float型张量

并且可以发现如果是大写开头的张量都是传入形状生成对应尺寸的张量,只有tensor是传入自己定义的数据,举例:

>>> torch.Tensor(2,3)
tensor([[-7.3660e-21,  4.5914e-41,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00,  0.0000e+00]])
# 创建一个格式为2行3列的张量
>>> torch.tensor(3)
tensor(3)
# 创建一个值为3的张量

当然如果希望大写开头的张量传入自己定义的数据,则传入一个列表或者数组,举例:

>>> torch.Tensor([2,3])
tensor([2., 3.])
# 创建一个张量,值为自定义的

注1:
上面提供的张量类型是在CPU下的,如果是在GPU下的,则在torch.cuda下,举例:

float型  torch.cuda.FloatTensor
double型  torch.cuda.DoubleTensor
int型  torch.cuda.IntTensor
long型  torch.cuda.LongTensor
byte型  torch.cuda.ByteTensor

注2:
pytorch框架里没有提供string这样的数据类型,所以为了表示某个标记之类的,我们可以使用one-hot编码,或者使用Embedding(常用的Word2vec/glove

张量属性/方法
索引

张量可以像数组那样进行索引,举例:

>>> a = torch.rand(2,3)
# 生成2行3列随机数
>>> a
tensor([[0.2027, 0.7210, 0.2423],
        [0.8315, 0.3196, 0.5687]])
>>> a[0]
tensor([0.2027, 0.7210, 0.2423])
# 索引第一行
>>> a[0,1]
tensor(0.7210)
# 索引第一行第二列

也可以使用内置的index_select方法进行索引,使用该方法可以索引多个自定义的行列(比如取第1/3/4列),该方法传入两个参数分别为张量维度以及张量中的索引(传入类型为张量),举例:

>>> a = torch.rand(2,3)
>>> a
tensor([[0.7470, 0.8258, 0.5929],
        [0.7803, 0.7016, 0.4281]])
>>> a.index_select(0, torch.tensor([1]))
tensor([[0.7803, 0.7016, 0.4281]])
# 对数据第1维(整体)取第2个数据(第二行)
# 第二个参数为张量
>>> a.index_select(1, torch.tensor([1, 2]))
tensor([[0.8258, 0.5929],
        [0.7016, 0.4281]])
# 对数据第1维取2/3列
切片

张量可以像数组那样进行切片,举例:

>>> a = torch.rand(2,3)
>>> a
tensor([[0.7470, 0.8258, 0.5929],
        [0.7803, 0.7016, 0.4281]])
>>> a[0, :2]
tensor([0.7470, 0.8258])
# 第一行前两列
>>> a[0, ::2]
tensor([0.7470, 0.5929])
# 第一行从第一个起隔两个取一个,因为这里只有3个,所以取第一列和第三列

还有...代表对这部分取全部,举例:

>>> a = torch.rand(2,2,2,2)
>>> a
tensor([[[[0.0882, 0.8744],
          [0.9916, 0.6415]],
         [[0.7247, 0.4012],
          [0.5703, 0.9776]]],
        [[[0.1076, 0.0710],
          [0.1275, 0.5045]],
         [[0.7833, 0.6519],
          [0.3394, 0.2560]]]])
>>> a[1, ..., 1]
tensor([[0.0710, 0.5045],
        [0.6519, 0.2560]])
# 相当于a[1, :, :, 1]
逻辑操作

可以像数组那样进行逻辑操作,举例:

>>> a = torch.rand(2,3)
>>> a
tensor([[0.7679, 0.1081, 0.3601],
        [0.0661, 0.8539, 0.3079]])
>>> a>0.5
tensor([[1, 0, 0],
        [0, 1, 0]], dtype=torch.uint8)
# 大于0.5的变成1,否则变成0
# 在数组里是变成True或False
shape

获取张量形状,举例:

>>> a = torch.tensor(1.)
>>> a.shape
torch.Size([])
>>> a = torch.tensor((1., 2.))
>>> a.shape
torch.Size([2])
>>> a = torch.tensor(((1., 2.), (3, 4)))
>>> a
tensor([[1., 2.],
        [3., 4.]])
>>> a.shape
torch.Size([2, 2])
>>> a.shape[0]
2
# 一维的尺寸

也可以通过内置方法size()来获取,举例:

>>> a = torch.tensor(((1., 2.), (3, 4)))
>>> a.size()
torch.Size([2, 2])
>>> a.size(0)
2
# 一维的尺寸
>>> a.size(1)
2
# 二维的尺寸
dim()

获取张量维度,举例:

>>> a = torch.tensor(1.)
>>> a.dim()
0
# 标量数据,0维
>>> a = torch.tensor([1.])
>>> a.dim()
1
# 一维张量
>>> a = torch.tensor(((1., 2.), (3, 4)))
>>> a.dim()
2
numel()

获取张量大小,举例:

>>> a = torch.tensor(((1., 2.), (3, 4)))
>>> a.numel()
4
# a里总共有4个数
type()

获取张量类型,举例:

>>> x = torch.IntTensor(1)
>>> x
tensor([1065353216], dtype=torch.int32)
>>> x.type()
'torch.IntTensor'
>>> type(x)
<class 'torch.Tensor'>
# 使用内置的type函数只能知道是个torch下的张量
cuda()

CPU数据转GPU,举例:

>>> x = torch.IntTensor(1)
>>> x = x.cuda()
# 转成GPU数据,这条语句得在GPU环境下才能运行

注:
判断是否可用GPU可以通过torch.cuda.is_available()判断,举例:

>>> torch.cuda.is_available()
False

下面是一个GPU常用操作:

>>> torch.cuda.device_count()
1
# 获取可使用GPU数量
>>> torch.cuda.set_device(0)
# 使用编号为0 的GPU
cpu()

GPU数据转CPU,举例:

>>> a = torch.cuda.FloatTensor(1)
>>> a.cpu()
tensor(1)

注:
上面的GPU和CPU数据互换是在低版本的pytorch上使用的,使用起来可能不太方便,之后的版本推出了简单切换的版本:先通过torch.device()方法选择一个GPU/CPU设备,然后对需要使用该设备的数据通过to()方法调用,举例:

>>> device = torch.device('cuda:0')
# 选择GPU设备
>>> net = torch.nn.Linear(10, 100).to(device)
# 这里定义了一个全连接网络层,并使用该GPU设备
view()/reshape()

这两个方法一样,修改张量形状,举例:

>>> a = torch.rand(2,3)
>>> a
tensor([[0.7824, 0.0911, 0.5798],
        [0.4280, 0.2592, 0.4978]])
>>> a.reshape(3, 2)
tensor([[0.7824, 0.0911],
        [0.5798, 0.4280],
        [0.2592, 0.4978]])
>>> a.view(3, 2)
tensor([[0.7824, 0.0911],
        [0.5798, 0.4280],
        [0.2592, 0.4978]])
t()

转置操作,举例:

>>> a = torch.rand(2, 3)
>>> a
tensor([[0.4620, 0.9787, 0.3998],
        [0.4092, 0.1320, 0.5631]])
>>> a.t()
tensor([[0.4620, 0.4092],
        [0.9787, 0.1320],
        [0.3998, 0.5631]])
>>> a.t().shape
torch.Size([3, 2])
pow()

对张量进行幂运算,也可以用**代替,举例:

>>> a = torch.full([2, 2], 3)
>>> a
tensor([[3., 3.],
        [3., 3.]])
>>> a.pow(2)
tensor([[9., 9.],
        [9., 9.]])
>>> a**2
tensor([[9., 9.],
        [9., 9.]])
sqrt()

对张量取平方根,举例:

>>> a = torch.full([2, 2], 9)
>>> a
tensor([[9., 9.],
        [9., 9.]])
>>> a.sqrt()
tensor([[3., 3.],
        [3., 3.]])
>>> a**(0.5)
tensor([[3., 3.],
        [3., 3.]])
# 可以看出结果一样
rsqrt()

取平方根的倒数,举例:

>>> a = torch.full([2, 2], 9)
>>> a
tensor([[9., 9.],
        [9., 9.]])
>>> a.rsqrt()
tensor([[0.3333, 0.3333],
        [0.3333, 0.3333]])
exp()

e为底的幂次方,举例:

>>> a = torch.full([2, 2], 2)
>>> a.exp()
tensor([[7.3891, 7.3891],
        [7.3891, 7.3891]])
log()

log以e为底的对数,举例:

>>> a = torch.full([2, 2], 2)
>>> a.exp()
tensor([[7.3891, 7.3891],
        [7.3891, 7.3891]])
>>> a.log()
tensor([[0.6931, 0.6931],
        [0.6931, 0.6931]])

对应的还有以2为底的log2、以10为底的log10等,举例:

>>> a = torch.full([2, 2], 2)
>>> a.log2()
tensor([[1., 1.],
        [1., 1.]])
round()/floor()/ceil()

四舍五入、向下取整和向上取整

trunc()/frac()

取整数/小数部分,举例:

>>> a = torch.tensor(1.2)
>>> a.trunc()
tensor(1.)
>>> a.frac()
tensor(0.2000)
max()/min()/median()/mean()

取最大值、最小值、中位数和平均值,举例:

>>> a = torch.tensor([1., 2., 3., 4., 5.])
>>> a.max()
tensor(5.)
>>> a.min()
tensor(1.)
>>> a.median()
tensor(3.)
>>> a.mean()
tensor(3.)

注意的是该方法默认会将全部数据变成一维的,并取整个数据里的最大/最小之类的值,因此可以通过dim参数设置取值维度,举例:

>>> a = torch.rand(2, 3)
>>> a
tensor([[0.0042, 0.5913, 0.0104],
        [0.1673, 0.9443, 0.1303]])
>>> a.max()
tensor(0.9443)
# 默认返回总体的最大值
>>> a.max(dim=0)
(tensor([0.1673, 0.9443, 0.1303]), tensor([1, 1, 1]))
# 在第1维选择,返回每一列最大值,并且对应索引为1,1,1
>>> a.max(dim=1)
(tensor([0.5913, 0.9443]), tensor([1, 1]))
# 在第2维选择,返回每一行最大值,并且对应索引为1,1

还有一个keepdim参数,可以控制返回的格式和之前相同,举例:

>>> a = torch.rand(2, 3)
>>> a.max(dim=0, keepdim=True)
(tensor([[0.1673, 0.9443, 0.1303]]), tensor([[1, 1, 1]]))
>>> a.max(dim=1, keepdim=True)
(tensor([[0.5913],
        [0.9443]]), tensor([[1],
        [1]]))
# 和之前的对比,可以发现设置该参数后格式变成一样的了
sum()/prod()

求累加、累乘,举例:

>>> a = torch.tensor([1., 2., 3., 4., 5.])
>>> a.sum()
tensor(15.)
>>> a.prod()
tensor(120.)

该方法也可以使用dim参数对某维度进行运算

argmax()/argmin()

返回最大/小值的索引,举例:

>>> a = torch.tensor([1., 2., 3., 4., 5.])
>>> a.argmax()
tensor(4)
>>> a.argmin()
tensor(0)

该方法同样默认会将全部数据变成一维的,并计算整个数据里最大值的索引,举例:

>>> a = torch.rand(2, 3)
>>> a
tensor([[0.2624, 0.2925, 0.0866],
        [0.0545, 0.8841, 0.9959]])
>>> a.argmax()
tensor(5)
# 可以看出所有数据默认转到1维上,最大值在第6个

但可以通过输入维度来控制索引的判断基准,举例:

>>> a = torch.rand(2, 3)
>>> a
tensor([[0.7365, 0.4280, 0.6650],
        [0.6988, 0.9839, 0.8990]])
>>> a.argmax(dim=0)
tensor([0, 1, 1])
# 在第1维下判断每一列的最大值索引,第一列是0.7365索引为0,第二列是0.9839索引为1,第三列是0.8990索引为1
>>> a.argmax(dim=1)
tensor([0, 1])
# 在第2维下判断每一行的最大值索引,第一行是0.7365索引为0,第二行是0.9839索引为1
argsort()

返回从小到大(默认,可以通过descending参数设置)的索引,举例:

>>> a.argsort()
tensor([0, 1, 2, 3, 4])
>>> a = torch.tensor([1., 5., 4., 2., 5., 3.])
>>> a.argsort()
tensor([0, 3, 5, 2, 1, 4])
# 从小到大的索引
>>> a.argsort(descending=True)
tensor([1, 4, 2, 5, 3, 0])
# 从大到小的索引
topk()

返回前几大的值(取前几小的值设置参数largestFalse就行),并且还是按从大到小(取前几小就是从小到大)排序好的,举例:

>>> a = torch.rand(2, 3)
>>> a
tensor([[0.0831, 0.3135, 0.2989],
        [0.2959, 0.6371, 0.9715]])
>>> a.topk(3)
(tensor([[0.3135, 0.2989, 0.0831],
        [0.9715, 0.6371, 0.2959]]), tensor([[1, 2, 0],
        [2, 1, 0]]))
# 取前三大的,可以看到结果也从大到小排序好
>>> a.topk(3, dim=1)
(tensor([[0.3135, 0.2989, 0.0831],
        [0.9715, 0.6371, 0.2959]]), tensor([[1, 2, 0],
        [2, 1, 0]]))
# 在第2维取前大三的数
>>> a.topk(3, largest=False)
(tensor([[0.0831, 0.2989, 0.3135],
        [0.2959, 0.6371, 0.9715]]), tensor([[0, 2, 1],
        [0, 1, 2]]))
# 取前三小的,可以看到结果也从小到大排序好
kthvalue()

返回第几小的数,举例:

>>> a = torch.rand(2, 3)
>>> a
tensor([[0.2052, 0.1159, 0.8533],
        [0.3335, 0.3922, 0.7414]])
>>> a.sort()
(tensor([[0.1159, 0.2052, 0.8533],
        [0.3335, 0.3922, 0.7414]]), tensor([[1, 0, 2],
        [0, 1, 2]]))
>>> a
tensor([[0.2052, 0.1159, 0.8533],
        [0.3335, 0.3922, 0.7414]])
>>> a.kthvalue(2, dim=0)
(tensor([0.3335, 0.3922, 0.8533]), tensor([1, 1, 0]))
# 在第1维返回第二小的数,可以看到每一列第二小的数被返回
>>> a.kthvalue(2, dim=1)
(tensor([0.2052, 0.3922]), tensor([0, 1]))
# 在第2维返回第二小的数,可以看到每一行第二小的数被返回
norm()

计算张量的范数(可以理解成张量长度的模),默认是l2范数(即欧氏距离),举例:

>>> a = torch.tensor((-3, 4), dtype=torch.float32)
>>> a.norm(2)
tensor(5.)
# l2范数:((-3)**2 + 4**2)**0.5
>>> a.norm(1)
tensor(7.)
# l1范数(曼哈顿距离):|-3| + |4|
>>> a.norm(2, dim=0)
>>> a = torch.tensor(((-3, 4), (6, 8)), dtype=torch.float32)
>>> a
tensor([[-3.,  4.],
        [ 6.,  8.]])
>>> a.norm(2, dim=0)
tensor([6.7082, 8.9443])
# 在第1维算范数:((-3)**2 + 6**2)**0.5,  (4**2 + 8**2)**0.5
>>> a.norm(2, dim=1)
tensor([ 5., 10.])
# 在第2维算范数:((-3)**2 + 4**2)**0.5,  (6**2 + 8**2)**0.5
clamp()

设置张量值范围,举例:

>>> a = torch.tensor([1, 2, 3, 4, 5])
>>> a.clamp(3)
tensor([3, 3, 3, 4, 5])
# 范围控制在3~
>>> a.clamp(3, 4)
tensor([3, 3, 3, 4, 4])
# 范围控制在3~4
transpose()

将指定维度对调,如果在2维情况,就相当于是转置,举例:

>>> a = torch.rand(2, 3, 1)
>>> a
tensor([[[0.8327],
         [0.7932],
         [0.7497]],
        [[0.2347],
         [0.7611],
         [0.5529]]])
>>> a.transpose(0, 2)
tensor([[[0.8327, 0.2347],
         [0.7932, 0.7611],
         [0.7497, 0.5529]]])
# 将第1维和第3维对调
>>> a.transpose(0, 2).shape
torch.Size([1, 3, 2])
permute()

transpose类似也是对调维度,但使用不太一样,举例:

>>> a = torch.rand(2, 3, 1)
>>> a
tensor([[[0.6857],
         [0.4819],
         [0.3992]],
        [[0.7477],
         [0.8073],
         [0.1939]]])
>>> a.permute(2, 0, 1)
tensor([[[0.6857, 0.4819, 0.3992],
         [0.7477, 0.8073, 0.1939]]])
# 将a修改成原来第3个维度放在第1个维度,第1个维度放在第2个维度,第2个维度放在第3个维度
>>> a.permute(2, 0, 1).shape
torch.Size([1, 2, 3])
squeeze()

在指定索引位置删减维度,如果不传入索引,将会把所有能删减的维度(值为1)都删减了,举例:

>>> a = torch.rand(1,2,1,1)
>>> a
tensor([[[[0.3160]],
         [[0.5993]]]])
>>> a.squeeze()
tensor([0.3160, 0.5993])
>>> a.squeeze().shape
torch.Size([2])
# 可以看出删减了所有能删减的维度
>>> a.squeeze(0)
tensor([[[0.3160]],
        [[0.5993]]])
>>> a.squeeze(0).shape
torch.Size([2, 1, 1])
# 删减了第1维
>>> a.squeeze(1)
tensor([[[[0.3160]],
         [[0.5993]]]])
>>> a.squeeze(1).shape
torch.Size([1, 2, 1, 1])
# 第2维因为无法删减,所以没有变化
unsqueeze()

在指定索引位置增加维度,举例:

>>> a = torch.rand(2,3)
>>> a
tensor([[0.8979, 0.5201, 0.2911],
        [0.8355, 0.2032, 0.9345]])
>>> a.unsqueeze(0)
tensor([[[0.8979, 0.5201, 0.2911],
         [0.8355, 0.2032, 0.9345]]])
# 在第1维增加维度
>>> a.unsqueeze(0).shape
torch.Size([1, 2, 3])
# 可以看出(2, 3)->(1,2,3)
>>> a.unsqueeze(-1)
tensor([[[0.8979],
         [0.5201],
         [0.2911]],
        [[0.8355],
         [0.2032],
         [0.9345]]])
# 在最后1维增加维度
>>> a.unsqueeze(-1).shape
torch.Size([2, 3, 1])
# 可以看出(2, 3)->(2,3,1)
expand()

扩展数据,但仅限于维度是1的地方,举例:

>>> a = torch.rand(1,2,1)
>>> a
tensor([[[0.5487],
         [0.9694]]])
>>> a.expand([2,2,1])
tensor([[[0.5487],
         [0.9694]],
        [[0.5487],
         [0.9694]]])
# 扩展了第1维度的数据
>>> a.expand([2,2,2])
tensor([[[0.5487, 0.5487],
         [0.9694, 0.9694]],
        [[0.5487, 0.5487],
         [0.9694, 0.9694]]])
# 扩展了第1/3维度的数据
>>> a.expand([1,4,1])
Traceback (most recent call last):
  File "<pyshell#242>", line 1, in <module>
    a.expand([1,4,1])
RuntimeError: The expanded size of the tensor (4) must match the existing size (2) at non-singleton dimension 1.  Target sizes: [1, 4, 1].  Tensor sizes: [1, 2, 1]
# 因为第2维度不为1,所以不能扩展
repeat()

复制数据,对指定维度复制指定倍数,举例:

>>> a = torch.rand(1,2,1)
>>> a.repeat([2,2,1]).shape
torch.Size([2, 4, 1])
# 将1/2维变成原来2倍,第3维不变
>>> a.repeat([2,2,2]).shape
torch.Size([2, 4, 2])
split()

根据长度切分数据,举例:

>>> a = torch.rand(2,2,3)
>>> a
tensor([[[0.6913, 0.3448, 0.5107],
         [0.5714, 0.1821, 0.2043]],
        [[0.9937, 0.4512, 0.8015],
         [0.9622, 0.3952, 0.6199]]])
>>> a.split(1, dim=0)
(tensor([[[0.6913, 0.3448, 0.5107],
         [0.5714, 0.1821, 0.2043]]]), tensor([[[0.9937, 0.4512, 0.8015],
         [0.9622, 0.3952, 0.6199]]]))
# 可以看出根据第1维将数据按长度1切分成了2/1份(第1维长度是2)
>>> a.split(1, dim=0).shape
>>> x, y = a.split(1, dim=0)
>>> x.shape, y.shape
(torch.Size([1, 2, 3]), torch.Size([1, 2, 3]))
chunk

根据数量切分数据,也就是自定义要切成多少份,举例:

>>> a = torch.rand(3,3)
>>> a
tensor([[0.3355, 0.0770, 0.1840],
        [0.0844, 0.4452, 0.8723],
        [0.9296, 0.4290, 0.4051]])
>>> a.chunk(3, dim=0)
(tensor([[0.3355, 0.0770, 0.1840]]), tensor([[0.0844, 0.4452, 0.8723]]), tensor([[0.9296, 0.4290, 0.4051]]))
# 把数据切成3份
>>> a.chunk(2, dim=0)
(tensor([[0.3355, 0.0770, 0.1840],
        [0.0844, 0.4452, 0.8723]]), tensor([[0.9296, 0.4290, 0.4051]]))
# 切成2份,可以看到最后一个被独立出来了

常用方法

数组/张量转换
torch.from_numpy

数组转张量,举例:

>>> a = np.array([1, 2, 3])
>>> torch.from_numpy(a)
tensor([1, 2, 3], dtype=torch.int32)

张量转数组则通过data.numpy()转,若为GPU数据,则先转成CPU的,也可以通过np.array(tensor)强行转成数组,举例:

>>> a = torch.tensor([1., 2., 3.])
>>> a
tensor([1., 2., 3.])
>>> a.cpu().data.numpy()
array([1., 2., 3.], dtype=float32)
# 转成CPU数据,然后转数组
>>> np.array(a)
array([1., 2., 3.], dtype=float32)
# 强制转成数组
基本运算
torch.add

加法运算,一般情况下也可以用+号代替,举例:

>>> a = torch.rand(2, 2)
>>> a
tensor([[0.5643, 0.4722],
        [0.5939, 0.6289]])
>>> torch.add(a, b)
tensor([[1.5643, 1.4722],
        [1.5939, 1.6289]])
>>> a + b
tensor([[1.5643, 1.4722],
        [1.5939, 1.6289]])
# 可以看出结果一样
torch.sub

张量减法

torch.mul

张量乘法

torch.div

张量除法

torch.matmul

矩阵乘法,举例:

>>> a = torch.tensor([[1,1], [1,1]])
>>> b = torch.tensor([[1,1], [1,1]])
>>> torch.matmul(a, b)
tensor([[2, 2],
        [2, 2]])
>>> a*b
tensor([[1, 1],
        [1, 1]])
# 别把数组乘法和矩阵乘法弄混了

矩阵乘法还可以用@代替,举例:

>>> a@b
tensor([[2, 2],
        [2, 2]])
# 结果和前面矩阵乘法一样
逻辑操作
torch.equal

判断两个张量是否完全相等,返回True或者False,而==符号返回的只是一个由0和1组成的张量,举例:

>>> a = torch.rand(2, 2)
>>> a
tensor([[0.8146, 0.1331],
        [0.6715, 0.4594]])
>>> b = a
>>> a == b
tensor([[1, 1],
        [1, 1]], dtype=torch.uint8)
# 判断两个张量每个元素是否相等,并用0和1来表示是否相等
>>> torch.equal(a, b)
True
# 判断两个张量是否相等并返回结果
torch.all

逻辑与操作,判断是否全为1,举例:

>>> a = torch.tensor([1,2,3])
>>> b = torch.tensor([1,2,4])
>>> a
tensor([1, 2, 3])
>>> b
tensor([1, 2, 4])
>>> torch.all(a==b)
tensor(0, dtype=torch.uint8)
>>> a==b
tensor([1, 1, 0], dtype=torch.uint8)
torch.any

逻辑或操作,判断是否存在1

torch.where

有三个参数a, b, c,对数据a进行逻辑判断,为1的取数据b上对应位置的值,为0取c上对应值,举例:

>>> a = torch.tensor([[1., 2.], [3., 4.]])
>>> b = torch.tensor([[5., 6.], [7., 8.]])
>>> c = torch.rand(2, 2)
>>> c
tensor([[0.3821, 0.6138],
        [0.2323, 0.2675]])
>>> torch.where(c>0.3, a, b)
tensor([[1., 2.],
        [7., 8.]])
# 位置(0,0)为1,所以取b上(0,0)的值,即1
# 位置(0,1)为1,所以取b上(0,1)的值,即2
# 位置(1,0)为0,所以取c上(1,0)的值,即7
# 位置(1,1)为0,所以取c上(1,1)的值,即8
数据生成
torch.zeros

生成固定尺寸的全0张量,举例:

>>> torch.zeros(2,2)
tensor([[0., 0.],
        [0., 0.]])
torch.ones

生成固定尺寸的全1张量,举例:

>>> torch.ones(2,2)
tensor([[1., 1.],
        [1., 1.]])
torch.full

生成固定尺寸的值全为指定值的张量,举例:

>>> torch.full([2, 3], 2)
tensor([[2., 2., 2.],
        [2., 2., 2.]])
# 指定格式且值全为2
torch.eye

生成对角线上值全为1的张量,举例:

>>> torch.eye(3)
tensor([[1., 0., 0.],
        [0., 1., 0.],
        [0., 0., 1.]])
# 生成3*3张量
>>> torch.eye(3,3)
tensor([[1., 0., 0.],
        [0., 1., 0.],
        [0., 0., 1.]])
# 和上面一样
>>> torch.eye(3,4)
tensor([[1., 0., 0., 0.],
        [0., 1., 0., 0.],
        [0., 0., 1., 0.]])
>>> torch.eye(3,2)
tensor([[1., 0.],
        [0., 1.],
        [0., 0.]])
torch.arange

生成指定等差数列的张量,举例:

>>> torch.arange(10)
tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# 只传一个参数n则默认为从0-n-1的n个数
>>> torch.arange(-1, 1, 0.1)
tensor([-1.0000, -0.9000, -0.8000, -0.7000, -0.6000, -0.5000, -0.4000, -0.3000,
        -0.2000, -0.1000,  0.0000,  0.1000,  0.2000,  0.3000,  0.4000,  0.5000,
         0.6000,  0.7000,  0.8000,  0.9000])
# 生成-1到1,且距离为0.1的等差数列
torch.linspace

也是生成等差数列的张量,用法和arrange稍有不同,举例:

>>> torch.linspace(-1, 1, steps=21)
tensor([-1.0000, -0.9000, -0.8000, -0.7000, -0.6000, -0.5000, -0.4000, -0.3000,
        -0.2000, -0.1000,  0.0000,  0.1000,  0.2000,  0.3000,  0.4000,  0.5000,
         0.6000,  0.7000,  0.8000,  0.9000,  1.0000])
# 生成一个长度为21的等差数列,且值为-1到1
# 可以看出和上面等价,但是这个方便设置数据量,上面的方便设置距离
torch.logspace

linspace用法相似,可以理解成再linspace的基础上对其求10的n次方值,举例:

>>> torch.logspace(0, 10, steps=11)
tensor([1.0000e+00, 1.0000e+01, 1.0000e+02, 1.0000e+03, 1.0000e+04, 1.0000e+05,
        1.0000e+06, 1.0000e+07, 1.0000e+08, 1.0000e+09, 1.0000e+10])
# 10的0次方、1次方、2次方、...
torch.rand

随机生成一个固定尺寸的张量,并且数值范围都在0~1之间,举例:

>>> torch.rand((2, 3))
tensor([[0.6340, 0.4699, 0.3745],
        [0.5066, 0.3480, 0.7346]])
torch.randn

也是随机生成固定尺寸的张量,数值符合正态分布

torch.randint

随机生成一个固定尺寸的张量,并且数值为自定义范围的整数,举例:

>>> torch.randint(0, 10, (2, 3))
tensor([[4, 3, 1],
        [6, 3, 9]])
# 0~9的固定格式整数
torch.randperm

生成指定个数的张量(范围为0~个数-1)并打乱,举例:

>>> torch.randperm(10)
tensor([0, 4, 9, 5, 1, 7, 3, 6, 2, 8])
# 生成0~9的数,并打乱
torch.rand_like

传入一个张量,并根据该张量shape生成一个新的随机张量,举例:

>>> a = torch.rand(2, 3)
>>> a
tensor([[0.4079, 0.9071, 0.9304],
        [0.0641, 0.0043, 0.0429]])
>>> torch.rand_like(a)
tensor([[0.2936, 0.4585, 0.7674],
        [0.4049, 0.0707, 0.0456]])
# 生成一个和a格式相同的张量

注:
有好多xxx_like的方法,原理都是一样的:传入一个张量,根据张量的shape生成新的张量

数据处理
torch.masked_select

取出指定条件数据,举例:

>>> a = torch.tensor([0, 0.5, 1, 2])
>>> torch.masked_select(a, a>0.5)
tensor([1., 2.])
# 取出所有大于0.5的数据
torch.cat

在指定维度合并数据,但要求两个数据维度相同,并且指定维度以外的维度尺寸相同,举例:

>>> a = torch.rand(1,2,3)
>>> b = torch.rand(2,2,3)
>>> torch.cat((a,b))
tensor([[[0.0132, 0.4118, 0.5814],
         [0.8034, 0.8765, 0.8404]],
        [[0.7860, 0.6115, 0.4745],
         [0.0846, 0.4158, 0.3805]],
        [[0.9454, 0.3390, 0.3802],
         [0.6526, 0.0319, 0.7155]]])
>>> torch.cat((a,b)).shape
torch.Size([3, 2, 3])
# 可以看出默认在第1维合并数据,合并过程可以看成:[1,2,3] + [2,2,3] = [3,2,3]
>>> torch.cat((a,b), dim=2)
Traceback (most recent call last):
  File "<pyshell#42>", line 1, in <module>
    torch.cat((a,b), dim=2)
RuntimeError: invalid argument 0: Sizes of tensors must match except in dimension 2. Got 1 and 2 in dimension 0 at d:\build\pytorch\pytorch-1.0.1\aten\src\th\generic\thtensormoremath.cpp:1307
# 在第3维合并数据时因为1维(非3维部分)不一样所以报错
torch.stack

在指定维度创建一个新维度合并两个数据,举例:

>>> a = torch.rand(1,2,3)
>>> b = torch.rand(1,2,3)
>>> a
tensor([[[0.5239, 0.0540, 0.0213],
         [0.9713, 0.5983, 0.1413]]])
>>> b
tensor([[[0.3397, 0.0976, 0.3744],
         [0.5080, 0.7520, 0.1759]]])
>>> torch.stack((a, b))
tensor([[[[0.5239, 0.0540, 0.0213],
          [0.9713, 0.5983, 0.1413]]],
        [[[0.3397, 0.0976, 0.3744],
          [0.5080, 0.7520, 0.1759]]]])
>>> torch.stack((a, b)).shape
torch.Size([2, 1, 2, 3])
# 合并过程可以看成:[1,1,2,3] + [1,1,2,3] = [1+1,1,2,3] = [2,1,2,3]
>>> torch.stack((a, b), dim=2)
tensor([[[[0.5239, 0.0540, 0.0213],
          [0.3397, 0.0976, 0.3744]],
         [[0.9713, 0.5983, 0.1413],
          [0.5080, 0.7520, 0.1759]]]])
>>> torch.stack((a, b), dim=2).shape
torch.Size([1, 2, 2, 3])
# 合并过程可以看成:[1,2,1,3] + [1,2,1,3] = [1,2,1+1,3] = [1,2,2,3]
带下划线的方法

在pytorch里面可以看到很多方法有两种版本:带下划线和不带下划线的,这两者的区别就是:不带下划线的方法操作数据会先新建一个相同的数据,然后对其进行操作后返回;带下划线的则直接对该数据进行操作并返回(声明该tensor是个in-place类型),举例:

>>> a = torch.tensor((1., -2.))
>>> a.abs()
tensor([1., 2.])
>>> a
tensor([ 1., -2.])
# 不带下划线的取绝对值后,查看原来的数据,发现没有变
>>> a.abs_()
tensor([1., 2.])
>>> a
tensor([1., 2.])
# 带下划线的取绝对值后,查看原来的数据,发现已经变了

更多关于in-place类型的参考:
https://blog.csdn.net/hbhhhxs/article/details/93886525

工具集

torch.utils下提供了很多API工具方便我们的使用

随机切分数据集

通过torch.utils.data.random_split()方法随机切分数据集,然后通过torch.utils.data.DataLoader来载入数据,举例:

>>> a = torch.rand(100)
>>> a
tensor([0.8579, 0.6903, 0.8042, 0.6803, 0.5619, 0.4721, 0.3132, 0.6476, 0.6644,
        0.1822, 0.8333, 0.6207, 0.5666, 0.3410, 0.9760, 0.1522, 0.5908, 0.4049,
        0.8710, 0.3284, 0.7598, 0.1615, 0.2269, 0.7273, 0.5658, 0.7861, 0.4562,
        0.4225, 0.0466, 0.2845, 0.2759, 0.0649, 0.7345, 0.7406, 0.0044, 0.2111,
        0.5922, 0.1108, 0.8785, 0.5843, 0.3432, 0.1751, 0.8386, 0.8131, 0.5848,
        0.3727, 0.4079, 0.3207, 0.7192, 0.5415, 0.2176, 0.3019, 0.9200, 0.1222,
        0.1771, 0.7479, 0.1213, 0.7306, 0.7951, 0.6702, 0.4286, 0.6684, 0.4392,
        0.5319, 0.8701, 0.5307, 0.0664, 0.6950, 0.8652, 0.8842, 0.1940, 0.5079,
        0.1927, 0.3511, 0.6232, 0.5951, 0.7436, 0.3113, 0.8578, 0.6422, 0.6670,
        0.5569, 0.4681, 0.3848, 0.5463, 0.2438, 0.7747, 0.2718, 0.8766, 0.3523,
        0.1736, 0.9693, 0.6800, 0.6727, 0.9430, 0.5596, 0.7665, 0.8402, 0.3828,
        0.6339])
>>> m, n = torch.utils.data.random_split(a, [10, 90])
# 将数据随机分成10和90个
>>> list(m)
[tensor(0.6803), tensor(0.8386), tensor(0.4079), tensor(0.8652), tensor(0.8333), tensor(0.8402), tensor(0.7861), tensor(0.8710), tensor(0.7306), tensor(0.5848)]
# 查看数据集m
>>> m.indices
tensor([ 3, 42, 46, 68, 10, 97, 25, 18, 57, 44])
# 可以看到其存放的是随机的10个索引,然后寻找对应下标数据
>>> data_m = torch.utils.data.DataLoader(m, batch_size=2)
# 载入m数据
>>> data_n = torch.utils.data.DataLoader(n, batch_size=2)

函数式API和类API

在pytorch当中,大部分神经网络层、激活函数、损失函数等都提供了两种API调用方式,分别是函数式API和类API,前者基本都在torch.nn.functional下,后者基本都在torch.nn下,前者一般直接调用即可,适合函数式编程;后者一般是先实例化,然后通过其内置的方法进行调用,适合面向对象编程。当然这些API功能基本都可以自定义实现,只是这里提供了API简化了操作,并且还提供了GPU加速等功能

网络层

全连接层

说白了就是单纯的矩阵相乘然后有偏置则加上偏置(公式:input@w + b),函数式API:torch.nn.functional.linear,类API:torch.nn.Linear,类API举例:

>>> layer1 = torch.nn.Linear(100, 10)
# 这里使用类API
# 定义一个全连接层,输入100个单元,输出10个,可以理解成初始化的一个(100, 10)的矩阵
>>> layer2 = torch.nn.Linear(10, 1)
>>> x = torch.rand(1,100)
# 定义一个(1, 100)的矩阵
>>> x = layer1(x)
# x经过layer1全连接层的运算
>>> x
tensor([[-0.1354,  0.1530,  0.1946, -0.1349,  0.6149, -0.0482,  0.1025, -0.8483,
         -1.0567, -0.5853]], grad_fn=<AddmmBackward>)
>>> x.shape
torch.Size([1, 10])
# 可以发现乘完以后变成了(1, 10)的矩阵
>>> x = layer2(x)
# x再经过layer2层运算
>>> x
tensor([[-0.2182]], grad_fn=<AddmmBackward>)
>>> x.shape
torch.Size([1, 1])
>>> layer1.weight.shape
torch.Size([10, 100])
# 可以通过weight属性查看当前层的权值,计算的时候会将权值矩阵进行转置后才进行运算,所以是(10, 100)而不是(100, 10)
>>> layer1.bias
Parameter containing:
tensor([ 0.0049, -0.0081, -0.0541, -0.0301,  0.0320, -0.0621,  0.0072, -0.0024,
        -0.0339,  0.0456], requires_grad=True)
# 可以通过bias属性查看当前层的偏置值

函数式API举例:

>>> x = torch.rand(1,100)
>>> w = torch.rand(10, 100)
>>> x = torch.nn.functional.linear(x, w)
# 可以看出函数式API需要我们自己定义初始化权值,然后直接调用即可
>>> x
tensor([[25.9789, 23.4787, 24.2929, 25.8615, 22.0681, 23.1044, 22.0457, 22.0386,
         23.0654, 24.6127]])

通过上面对比我们可以发现对于类API,我们只需实例化对应的类,然后在其的__init__方法里会对权值之类的数据进行初始化,然后我们传入自己的数据进行调用运算;而在函数式API当中,首先我们需要自己定义初始化的权值,然后通过往API接口传入数据和权值等数据进行运算
注:
通过上面我们可以看出每一层操作的时候可以实时打印查看其权值之类的变化,以供我们观察,这也是pytorch作为动态图和TensorFlow最大的区别

Dropout

随机选取一部分节点使用,忽略一部分节点,函数式API:torch.nn.functional.dropout,类API:torch.nn.Dropout,举例:

>>> a = torch.rand(20)
>>> torch.nn.functional.dropout(a, 0.2)
tensor([1.2178, 1.0375, 0.0555, 0.0307, 0.3235, 0.0000, 0.5209, 0.0000, 0.3346,
        1.2383, 0.3606, 1.0937, 0.0000, 0.2957, 0.9463, 0.2932, 0.8088, 0.4445,
        0.5565, 0.0241])
# 随机将百分之20的节点转成0
批标准化层

函数式API:torch.nn.functional.batch_norm,类API:torch.nn.BatchNorm2d(对应的有1d、2d等等),类API举例:

>>> x1 = torch.rand(1, 3, 784)
# 3通道的1d数据
>>> layer1 = torch.nn.BatchNorm1d(3)
# 1d批标准化层,3通道
>>> layer1(x1)
tensor([[[-0.0625, -0.1859, -0.3823,  ...,  0.6668, -0.7487,  0.8913],
         [ 0.0115, -0.1149,  0.1470,  ..., -0.1546,  0.3012,  0.2472],
         [ 1.5185, -0.4740, -0.8664,  ...,  0.6266,  0.2797, -0.2975]]],
       grad_fn=<NativeBatchNormBackward>)
# 可以看到数据都被标准化了
>>> layer1(x1).shape
torch.Size([1, 3, 784])
>>> x2 = torch.rand(1, 3, 28, 28)
# 3通道的2d数据
>>> layer2 = torch.nn.BatchNorm2d(3)
>>> layer2(x2)
tensor([[[[-0.0378, -0.3922,  0.2255,  ..., -0.1469, -0.3016,  0.2384],
          [-0.3901, -0.0220, -0.3118,  ..., -0.2492,  0.1705, -0.0599],
          [-0.1309, -0.3064, -0.2001,  ..., -0.0613, -0.1838,  0.1335],
          ...,
          [ 0.9022, -0.3031,  1.0695,  ..., -0.8257, -0.6438, -0.2672],
          [-0.1015,  1.1482,  1.0834,  ...,  0.6641, -0.8632, -0.2418],
          [-1.2068, -0.7443,  0.8346,  ...,  0.1213,  0.4528, -0.5756]]]],
       grad_fn=<NativeBatchNormBackward>)
>>> layer2(x2).shape
torch.Size([1, 3, 28, 28])
卷积层

函数式API:torch.nn.functional.conv2d,类API:torch.nn.Conv2d,类API举例:

>>> x = torch.rand(1, 1, 28, 28)
>>> layer = torch.nn.Conv2d(1, 3, kernel_size=3, stride=1, padding=0)
# 设置输入通道为1,输出通道为3,filter大小为3x3,步长为1,边框不补0
>>> layer.weight
Parameter containing:
tensor([[[[-0.1893,  0.1177, -0.2837],
          [ 0.1116,  0.0348,  0.3011],
          [-0.1871, -0.0722, -0.1843]]],
          ...,
        [[[ 0.0083, -0.0784,  0.1592],
          [-0.1896,  0.0082, -0.0146],
          [-0.2069, -0.0147, -0.1899]]]], requires_grad=True)
# 可以查看初始化权值
>>> layer.weight.shape
torch.Size([3, 1, 3, 3])
# 格式分别代表输出通道3,输入通道1,尺寸为3x3
>>> layer.bias.shape
torch.Size([3])
# 查看初始化偏置
>>> layer(x)
tensor([[[[-0.0494, -0.1396, -0.0690,  ..., -0.1382, -0.0539, -0.1876],
          [-0.2185, -0.0116, -0.1287,  ...,  0.1233, -0.0091,  0.0407],
          [-0.0648,  0.0506, -0.1971,  ..., -0.2013,  0.1151, -0.0026],
          ...,
          [-0.4974, -0.5449, -0.4583,  ..., -0.7153, -0.1890, -0.7381],
          [-0.4254, -0.6051, -0.2578,  ..., -0.4957, -0.4128, -0.4875],
          [-0.5392, -0.4214, -0.5671,  ..., -0.2785, -0.6113, -0.3150]]]],
       grad_fn=<ThnnConv2DBackward>)
# 进行一次卷积运算,实际是魔法方法__call__里调用了forward方法
>>> layer(x).shape
torch.Size([1, 3, 26, 26])
# 可以看到计算后由于边框不补0,而滤波器大小为3x3,所以结果的长宽就变成了(height-3+1, weight-3+1)
>>> layer1 = torch.nn.Conv2d(1, 3, kernel_size=3, stride=1, padding=1)
# 这里边缘补0
>>> layer1(x).shape
torch.Size([1, 3, 28, 28])
# 可以看到由于边缘补0,所以大小没变
>>> layer2 = torch.nn.Conv2d(1, 3, kernel_size=3, stride=2, padding=0)
# 这里步长改成2
>>> layer2(x).shape
torch.Size([1, 3, 13, 13])
# 结果的长宽就变成了((height-3+1)/2, (weight-3+1)/2)
>>> layer3 = torch.nn.Conv2d(1, 3, kernel_size=3, stride=2, padding=1)
# 这里边缘补0,且步长改成2
>>> layer3(x).shape
torch.Size([1, 3, 14, 14])
# 可以看到结果的长宽就变成了(height/2, weight/2)

函数式API举例:

>>> x = torch.rand(1, 1, 28, 28)
>>> w = torch.rand(3, 1, 3, 3)
# 输出3通道,输入1通道,尺寸3x3
>>> b = torch.rand(3)
# 偏置长度要和通道数一样
>>> layer = torch.nn.functional.conv2d(x, w, b, stride=1, padding=1)
>>> layer
tensor([[[[2.1963, 2.6321, 3.4186,  ..., 3.2495, 3.1609, 2.5473],
          [2.5637, 3.4892, 4.0079,  ..., 4.1167, 4.4497, 3.1637],
          [2.7618, 3.2788, 3.2314,  ..., 4.7185, 4.3128, 2.6393],
          ...,
          [1.3735, 2.3738, 1.8388,  ..., 2.9912, 2.6638, 1.5941],
          [2.1967, 2.0466, 2.0095,  ..., 3.3192, 2.9521, 2.2673],
          [1.6091, 2.1341, 1.5108,  ..., 2.1684, 2.4585, 1.7931]]]])
>>> layer.shape
torch.Size([1, 3, 28, 28])
池化层

函数式API:torch.nn.functional.max_pool2d,类API:torch.nn.MaxPool2d,类API举例:

>>> x = torch.rand(1, 1, 28, 28)
>>> layer = torch.nn.MaxPool2d(3, stride=2)
# 尺寸3x3,步长为2
>>> layer(x)
tensor([[[[0.9301, 0.9342, 0.9606, 0.9922, 0.9754, 0.9055, 0.7142, 0.9882,
           0.9803, 0.8054, 0.9903, 0.9903, 0.9426],
          ...,
          [0.8873, 0.8873, 0.9324, 0.9876, 0.9566, 0.9225, 0.9673, 0.9675,
           0.9977, 0.9977, 0.9552, 0.9552, 0.8689]]]])
>>> layer(x).shape
torch.Size([1, 1, 13, 13])

还有个avgpool(函数式API:torch.nn.functional.avg_pool2d,类API:torch.nn.AvgPool2d),和maxpool不一样的是:maxpool是取最大值,而avgpool取的是平均值,举例:

>>> torch.nn.functional.avg_pool2d(x, 3, stride=2)
tensor([[[[0.5105, 0.6301, 0.5491, 0.4691, 0.5788, 0.4525, 0.3903, 0.5718,
           0.6259, 0.3388, 0.4169, 0.6122, 0.4760],
          ...,
          [0.4705, 0.5332, 0.4150, 0.5000, 0.5686, 0.5325, 0.6241, 0.4926,
           0.4646, 0.3121, 0.2975, 0.5203, 0.5701]]]])
>>> torch.nn.functional.avg_pool2d(x, 3, stride=2).shape
torch.Size([1, 1, 13, 13])
向上取样

将图片放大/缩小成原来的几倍,函数式API:torch.nn.functional.interpolate,举例:

>>> x = torch.rand(1, 1, 2, 2)
>>> x
tensor([[[[0.9098, 0.7948],
          [0.0670, 0.3906]]]])
>>> torch.nn.functional.interpolate(x, scale_factor=2, mode='nearest').shape
torch.Size([1, 1, 4, 4])
# 可以看到数据被放大了一倍
>>> torch.nn.functional.interpolate(x, scale_factor=2, mode='nearest')
tensor([[[[0.9098, 0.9098, 0.7948, 0.7948],
          [0.9098, 0.9098, 0.7948, 0.7948],
          [0.0670, 0.0670, 0.3906, 0.3906],
          [0.0670, 0.0670, 0.3906, 0.3906]]]])
# 可以看到是往横纵向都复制成原来的对应倍数
>>> torch.nn.functional.interpolate(x, scale_factor=0.5, mode='nearest')
tensor([[[[0.9098]]]])
# 将数据缩小一倍,可以看到取那一部分的第一个数据
嵌入层

一般在对数据进行升/降维时使用,函数式API:torch.nn.functional.embedding,类API:torch.nn.Embedding,类API举例:

>>> x = torch.eye(5, dtype=torch.long)
>>> x
tensor([[1, 0, 0, 0, 0],
        [0, 1, 0, 0, 0],
        [0, 0, 1, 0, 0],
        [0, 0, 0, 1, 0],
        [0, 0, 0, 0, 1]])
>>> layer = torch.nn.Embedding(5, 1)
>>> layer(x).shape
torch.Size([5, 5, 1])
RNN层

类API:torch.nn.RNN,类API举例:

>>> x = torch.randn(10, 3, 100)
# 模拟句子序列:有10个单词,共3句话,每个单词用100维向量表示
>>> layer = torch.nn.RNN(input_size=100, hidden_size=20, num_layers=4)
>>> layer
RNN(100, 20, num_layers=4)
>>> out, h = layer(x)
>>> out.shape
torch.Size([10, 3, 20])
# 所有时间戳上的状态
>>> h.shape
torch.Size([4, 3, 20])
# 最后一个时间戳上所有的状态
LSTM层

类API:torch.nn.LSTM

序列化模型

即用来定义神经网络模型(每一层都要是继承于torch.nn下的网络),类API:torch.nn.Sequential(该模型就是针对面向对象模式编写,因此不提供函数式API),举例:

>>> net = torch.nn.Sequential(
    torch.nn.Linear(100, 10),
    torch.nn.Dropout(0.7),
    torch.nn.ReLU(),
    torch.nn.Linear(10, 1)
    )
>>> net
Sequential(
  (0): Linear(in_features=100, out_features=10, bias=True)
  (1): Dropout(p=0.7)
  (2): ReLU()
  (3): Linear(in_features=10, out_features=1, bias=True)
# 可以直接查看网络结构
自定义神经网络

当需要自己定义神经网络层的时候,首先需要继承于torch.nn.Module,并在初始化时调用父类的初始化方法,同时也要在forward方法里实现数据的前向传播,举例:

import torch
class Dense(torch.nn.Module):
    # 实现一个自定义全连接+relu层,继承torch.nn.Module
    def __init__(self, input_shape, output_shape):
        super(Dense, self).__init__()
        # 首先初始化时执行父类的初始化,这句话可以看
        # 在父类初始化中会初始化很多变量
        self.w = torch.nn.Parameter(torch.randn(output_shape, input_shape))
        # 初始化权重和偏置参数
        # 使用Parameter其会自动将参数设置为需要梯度信息,并且可以通过内置的parameters方法返回这些参数
        self.b = torch.nn.Parameter(torch.rand(output_shape))
        self.relu = torch.nn.ReLU()
        # 初始化relu层
    def forward(self, x):
        # 定义前向传播方法
        x = x @ self.w.t() + self.b
        # 全连接层的功能就是矩阵相乘计算
        x = self.relu(x)
        # 进行relu层计算
        return x
    def __call__(self, x):
        # 调用该类对象执行时,调用前向传播方法
        # 这个可以不写,直接通过调用forward方法也一样
        return self.forward(x)
layer = Dense(10, 1)
x = torch.rand(2, 10)
output = layer(x)
print(output)
# 输出结果:
# tensor([[0.1780],
#         [0.0000]], grad_fn=<ThresholdBackward0>)
保存和载入网络

对于所有继承自torch.nn.Module下的网络,保存时首先通过内置的方法state_dict()返回所有的当前类的状态,然后通过torch.save()方法保存成文件;载入时通过torch.load()方法载入文件,并通过内置的load_state_dict()方法载入所有的参数,举例:

>>> layer = torch.nn.Linear(10, 1)
>>> layer.state_dict()
OrderedDict([('weight', tensor([[-0.1597,  0.0573,  0.0976, -0.1028, -0.1264, -0.0400,  0.0308,  0.2192,
         -0.0150, -0.3148]])), ('bias', tensor([0.0557]))])
# 可以看到layer里定义的参数配置
>>> torch.save(layer.state_dict(), "ckpt.mdl")
# 现在保存这个网络参数
>>> layer1 = torch.nn.Linear(10, 1)
# 新建一个网络
>>> layer1.state_dict()
OrderedDict([('weight', tensor([[-0.2506, -0.2960, -0.3083,  0.0629,  0.1707,  0.3018,  0.2345, -0.1922,
         -0.0527, -0.1894]])), ('bias', tensor([-0.0069]))])
# 显然layer1参数和layer的不一样
>>> layer1.load_state_dict(torch.load("ckpt.mdl"))
# layer1载入前面的layer网络参数
>>> layer1.state_dict()
OrderedDict([('weight', tensor([[-0.1597,  0.0573,  0.0976, -0.1028, -0.1264, -0.0400,  0.0308,  0.2192,
         -0.0150, -0.3148]])), ('bias', tensor([0.0557]))])
# 可以发现layer1的参数变得和layer保存的参数一样

激活函数

sigmoid

公式:

1 / ( 1 + e^-x )
注:该公式可以将值控制在0~1之间,适合概率之类的问题,但因为当x特别大时,导数几乎为0,容易发生梯度弥散(梯度长时间得不到更新,损失值不下降)之类的问题

函数式API:torch.nn.functional.sigmoid,类API:torch.nn.Sigmoid,pytorch自带:torch.sigmoid,举例:

>>> a = torch.linspace(-100, 100, 10)
>>> a
tensor([-100.0000,  -77.7778,  -55.5556,  -33.3333,  -11.1111,   11.1111,
          33.3333,   55.5555,   77.7778,  100.0000])
>>> torch.sigmoid(a)
tensor([0.0000e+00, 1.6655e-34, 7.4564e-25, 3.3382e-15, 1.4945e-05, 9.9999e-01,
        1.0000e+00, 1.0000e+00, 1.0000e+00, 1.0000e+00])
# 可以看到值都被映射在0~1之间
tanh

公式:

(e^x – e^-x) / (e^x + e^-x)
注:该公式可以将值控制在-1~1之间

函数式API:torch.nn.functional.tanh,类API:torch.nn.Tanh,pytorch自带:torch.tanh

relu

公式:

0, x <= 0
x, x > 0
注:该公式可以将值控制在0~+∞之间

函数式API:torch.nn.functional.relu,类API:torch.nn.ReLU

leakyrelu

公式:

ax, x <= 0
x, x > 0
注:a是一个很小的参数值,该公式在relu的基础上,使负区间的数不为0,而是保持在一个很小的梯度上

函数式API:torch.nn.functional.leaky_relu,类API:torch.nn.LeakyReLU

softmax

公式:

e^xi / Σe^xi
注:该公式可以将值控制在0~1之间,并且所有的值总和为1,适合分类之类的问题,并且可以发现通过e阶函数,其还会把大的值(特征)放大,小的缩小

函数式API:torch.nn.functional.softmax,类API:torch.nn.Softmax,pytorch自带:torch.softmax

损失函数

均方差

就是计算的y与实际y之差的平方取平均值,函数式API:torch.nn.functional.mse_loss,类API:torch.nn.MSELoss,第一个参数是y,第二个参数是y对应的公式,举例:

>>> x = torch.tensor(1.)
>>> w = torch.tensor(2.)
>>> b = torch.tensor(0.)
>>> y = torch.tensor(1.)
>>> mse = torch.nn.functional.mse_loss(y, w*x+b)
# 计算y=wx+b在点(1, 1)时的均方差:(1 - (2*1+0))^2 = 1
>>> mse
tensor(1.)
交叉熵

通过含有的信息量大小来进行判断,函数式API:torch.nn.functional.cross_entropy,类API:torch.nn.CrossEntropyLoss

求导

torch.autograd.grad

定义了自动求导,传入第一个参数是对应的公式,第二个参数是一个列表,里面存放所有要求导的变量,并且在求导前的变量需要通过require_grad()方法来声明该公式的某个变量是需要求导的(或者在定义时就设置requires_grad参数为True),返回一个元组,里面是对应每个变量的求导信息,举例:

>>> x = torch.tensor(1.)
>>> w = torch.tensor(2.)
>>> b = torch.tensor(0.)
>>> y = torch.tensor(1.)
>>> mse = torch.nn.functional.mse_loss(y, w*x+b)
# 模拟一个y=wx+b的函数
>>> mse
tensor(1.)
>>> torch.autograd.grad(mse, [w])
# 此时没有变量声明过是需要求导的
>>> w.requires_grad_()
tensor(2., requires_grad=True)
# 声明w需要求导,可以看到一开始就定义w = torch.tensor(2., requires_grad=True)效果也是一样的
# 加下划线代表为in-place类型,直接对w进行修改,也可以替换成:w = w.requires_grad()
>>> torch.autograd.grad(mse, [w])
# 报错,因为mse里的w还是之前的w,需要更新一下mse里的w
>>> mse = torch.nn.functional.mse_loss(y, w*x+b)
# 更新mse里的w为声明了需要求导的w
>>> torch.autograd.grad(mse, [w])
(tensor(2.),)
# 可以看出mse对第一个变量w求偏导的结果为2,计算过程:
# mse为:(y-(wx+b))^2
# 对w求偏导为:-2x(y-(wx+b))
# 代入数值:-2*1*(1-(2*1+0)) = 2
torch.backward

向后传播,也能实现求导,通过设置该tensor允许向后传播,那么其会对所有声明了求导信息的变量进行求导,然后在对应的变量上通过grad属性获取求导结果,举例:

>>> x = torch.tensor(1.)
>>> b = torch.tensor(0.)
>>> w = torch.tensor(2., requires_grad=True)
>>> y = torch.tensor(1.)
>>> mse = torch.nn.functional.mse_loss(y, w*x+b)
>>> mse.backward()
# 对mse公式里需要求导的变量都进行求导
>>> w.grad
tensor(2.)
# w的求导结果为2
>>> x.grad
# 因为x没有声明需要求导,所以为空

优化器

torch.optim

定义了各种优化器,将优化器实例化后(传入需要求导的参数和学习率),通过step()方法进行梯度下降

  • Adam
  • SGD

可视化操作

tensorboardX

在TensorFlow里有tensorboard可以进行训练过程的可视化,而在Pytorch里也提供了tensorboardX几乎和tensorboard一样,适合习惯了tensorboard的使用者,具体使用可以参考:
https://www.jianshu.com/p/713c1bc4cf8a
https://www.jianshu.com/p/46eb3004beca

visdom

pytorch提供的另一个可视化,个人更喜欢这种界面样式(tensorboard的黄色底色看着不太习惯)

安装
pip install visdom
使用步骤
  1. 通过命令python -m visdom.server启动visdom服务器进行监听
  2. 导入可视化类:from visdom import Visdom
  3. 实例化可视化类,并通过line/bar/text等提供的API进行绘图

具体使用可以参考:
https://blog.csdn.net/wen_fei/article/details/82979497
https://ptorch.com/news/77.html

© 著作权归作者所有,转载或内容合作请联系作者
https://www.jianshu.com/p/5460b7fa3ec4

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
Python
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论