第二章 PyTorch入门
第二章 PyTorch入门
1. 张量
1.1 张量
张量的概念其实就是高维数组
- 标量
- 向量
- 矩阵
- 张量
张量的数据类型
| 数据类型 | dtype | CPU tensor | GPU tensor |
|---|---|---|---|
| 32位浮点型 | torch.float | torch.FloatTensor | torch.cuda.FloatTensor |
| 64位浮点型 | torch.double | torch.DoubleTensor | torch.cuda.DoubleTensor |
| 16位浮点型 | torch.half | torch.HalfTensor | torch.cuda.HalfTensor |
| 8位无符号整型 | torch.uint8 | torch.ByteTensor | torch.cuda.ByteTensor |
| 8位有符号整型 | torch.int8 | torch.CharTensor | torch.cuda.CharTensor |
| 16位有符号整型 | torch.short | torch.ShortTensor | torch.cuda.ShortTensor |
| 32位有符号整型 | torch.int | torch.IntTensor | torch.cuda.IntTensor |
| 64位有符号整型 | torch.long | torch.LongTensor | torch.cuda.LongTensor |
常见的数据类型操作
1 | # torch中默认的数据类型是float32 |
常见的张量生成方式
-
使用**torch.tensor()**生成
Python中的列表和序列可以通过
torch.tensor()转化成张量,并且能够通过shape属性查看维度,size()方法计算形状,numel()方法计算元素个数。1
2
3
4
5
6
7# 注意只有浮点型的能计算梯度
b=torch.tensor([1,2,3],dtype=torch.DoubleTensor,requires_grad=True)
b.dim()
b.shape
b.size()
b.numel() -
使用**torch.Tensor()**生成
与torch.tensor()方法不同在于,前者只能接收现有数据,而后者能够接收维度形态。如:
1
b=torch.Tensor(2,3)
-
使用**Like()**方法生成
该方法能够生成维度与传入张量相同的新张量
1
2
3
4
5
6
7
8
9
10
11# 例如
c=torch.Tensor(2,3)
# 全一
torch.ones_like(c)
# 全零
torch.zeros_like(c)
# 随机
torch.rand_like(c) -
使用**new()**方法生成
该方法生成的张量将复制原始张量的数据类型,但维度可以不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# D.dtype --> torch.DoubleTensor
e=[[2,1],[7,8]]
# 注意new方法是从需要copy类型的张量上出发
# 而like则是从torch本身上出发
e=D.new_tensor(e)
# 做一个全1填充
D.new_full(e,fill_value=1)
D.new_zeros(e)
D.new_empty(e)
D.new_ones(e) -
张量和Numpy的互相转换
1
2
3
4
5
6
7
8
9
10F=np.ones((3,3))
# 方法一
torch.as_tensor(F)
# 方法二
torch.from_numpy()
# tensor转numpy
new=F.numpy() -
通过随机数生成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21# 设置随机数种子
# 该种子能够保证生成的随机数是可以重复出现的
torch.manual_seed(123)
# 生成一个正态分布的随机数
torch.normal(mean=0.0,std=torch.tensor(1.0))
# 指定mean和std的数量的话,就会生成许多个随机数
torch.normal(mean=torch.arange(1,5.0),std=torch.arange(1,5.0))
# 这样就能得到四个随机数
# 生成零一区间随机数
torch.rand(3,4)
# 生成标准正态分布
torch.randn(3,4)
torch.rand_like(3,4)
# 随机打散
torch.randperm(n)
# 会将0~n-1个数随机打散排列 -
其他生成张量的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 范围生成张量
torch.arange(start=0,end=10,step=2)
# 等间隔张量
torch.linspace(start=1,end=10,step=5)
# 对数间隔张量
torch.logspace(start=0.1,end=1.0,steps=5)
# 其他预定义的
torch.zeros()
torch.ones()
torch.eyes()
torch.full(,value)
torch.empty()
1.2 张量操作
1.2.1 改变张量形状
1 | # reshape方法能够设置张量形状大小 |
1.2.2 张量索引操作
tensor的切片索引非常大胆
1 | # 对一个四位数据进行操作 |
1.2.3 张量的拼接和拆分
1 | A=torch.arange(6.0).reshape(2,3) |
split是按长度分割
1 | a=torch.rand(4,32,8) |
chunk是等距分割,但不能指定大小
1 | # 分成两份 |
1.2.4 转置
.t
1 | a=torch.randn(3,4) |
transpose
1 | # transpose能交换任意两个维度 |
permute
1 | # permute可以多次交换 |
1.3 张量计算
1.3.1 大小比较
部分比较函数如下:
| 函数 | 功能 |
|---|---|
| torch.allclose() | 比较两个元素是否接近 |
| torch.eq() | 比较两个元素是否相等 |
| torch.equal() | 比较两个元素是否相等且数据类型一致 |
| torch.ge() | 大于等于 |
| torch.gt() | 大于 |
| torch.le() | 小于等于 |
| torch.lt() | 小于 |
| torch.ne() | 不等于 |
| torch.isnan() | 是否为空 |
allclose()函数非常有意思,其公式表示为:
1 | torch.allclose(A,B,rtol=1e-05,atol=1e-08,equal_nan=False) |
其他的用法基本上相同,都是逐元素比较
1 | torch.eq(A,B) |
注意所有的比较要求数据的维度相同
1.3.2 基本运算
张量的基本运算可以分为两种,一种是逐元素之间的计算,另一种则是矩阵之间的计算
-
加减乘除运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40# 元素乘法
A*B
# 元素除法
A/B
# 元素加法
A+B
# 元素减法
A-B
# 整除
A//B
# 幂运算
A**3
torch.pow(A,3)
# 指数运算
torch.exp(A)
# 对数运算
torch.log(A)
# 平方根
A**0.5
torch.sqrt(A)
# 平方根导数
1/(A**0.5)
torch.rsqrt(A)
# 元素限定裁剪
# 给定一个上下限用于限制元素大小
# 小于下限时会被重置为下限
# 大于上限则会被重置为上限
torch.clamp_max(A,4)
torch.clamp_min(A,3)
torch.clamp(A,2,5) -
矩阵间的运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 求转置
torch.t(A)
# 矩阵乘法
A.matmul(C)
# 注意矩阵乘法只计算最后两个维度的值
# 如元素A[2,3,2] B[2,2,3]
# 此时的矩阵乘法相当于stack((A[0].matmul(B[0])),(A[1].matmul(B[1]))
# 可以简写成torch.mm
# 计算矩阵的逆
torch.inverse() # 当然需要矩阵有逆矩阵
# 计算矩阵的迹
torch.trace()
1.3.3 统计运算
| 函数 | 功能 |
|---|---|
| torch.max(dim=n) | 计算张量对应维度下的最大值 |
| torch.argmax(dim=n) | 获得最大值位置 |
| torch.min(dim=n) | 最小值 |
| torch.argmin(dim=n) | 最小值位置 |
| torch.sort() | 排序并获得下标 |
| torch.argsort() | 序列下标 |
| torch.topk() | 取出第k大的值和下标 |
| torch.kthvalue() | 获得指定第k小的值和下标 |
| torch.mean() | 根据指定维度计算标准差 |
| torch.sum() | 根据指定维度求和 |
| torch.cumsum() | 根据指定维度计算累加和 |
| torch.median() | 根据指定维度计算中位数 |
| torch.cumprod() | 根据指定维度计算累乘 |
| torch.std() | 根据指定维度计算标准差 |
torch.max在不同维度下的运作:返回当前维度下的最大值,例如[2,3,4]在dim=0出取最大,返回一个[3,4],这个矩阵每个值都由dim=0的维度下取相应位置的最大值。如:
1 | a |
可以看到,返回值都是指定维度消失,保留其他维度
sort()方法
1 | val,idx=torch.sort(A,descending=True) |
topk()方法
1 | # 获取前4个大值 |
kthvalue()方法
1 | # 获得第k个小值 |
2. 自动微分
通过设置requires_grad即可实现自动微分。
1 | x=torch.Tensor([[1.,2.],[3.,4.]],requires_grad=True) |
3. torch.nn模块
3.1 卷积层
卷积可以看做是输入和卷积核之间的内积运算,是两个实值函数之间的一种数学运算。
卷积运算具有三个比较重要的特点,即卷积稀疏连接、参数共享、等变表示。
在CNN中,通过卷积核使得输入单元与输出单元呈现稀疏连接,这能减少需要训练的参数量,加快网络计算速度。
且模型中同一组参数将被相同的卷积核处理,只需要训练一个参数集即可,且卷积核大小远小于输入输出,减少了需要学习的参数数量。针对每个卷积层,可以使用多个卷积核获取输入的特征映射,对数据(尤其是图像)具有很强的特征提取和表示能力,并且在卷积运算后,使得卷积神经网络结构对输入的图像具有平移不变性。
PyTorch在卷积层提供了多种维度的卷积和逆卷积过程。
| API | 功能 |
|---|---|
| torch.nn.Conv1d() | 针对输入信号应用一维卷积 |
| torch.nn.Conv2d() | 针对输入信号应用二维卷积 |
| torch.nn.Conv3d() | 针对输入信号应用三维卷积 |
| torch.nn.ConvTranspose1d() | 针对输入信号应用一维转置卷积 |
| torch.nn.ConvTranspose2d() | 针对输入信号应用二维转置卷积 |
| torch.nn.ConvTranspose3d() | 针对输入信号应用三维转置卷积 |
以torch.nn.Conv2d()为例,其参数构造为:
1 | torch.nn.Conv2d( |
案例
-
首先读入图片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17path=r"C:\Users\落花雨\Desktop\01.jpg"
import numpy as np
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
from PIL import Image
# 读取图像并转化为灰度
mymin=Image.open(path)
myimgray=np.array(mymin.convert("L"),dtype=np.float32)
# 可视化图片
plt.figure(figsize=(20,8),dpi=300)
plt.imshow(myimgray,cmap=plt.cm.gray)
plt.axis("off")
plt.show() -
需要将图片转为张量
1
2
3
4# 将数组转化为张量
imh,imw=myimgray.shape
# 图片在torch中存储: batch channel height weight
myimgray_t=torch.from_numpy(myimgray.reshape((1,1,imh,imw))) -
接着呢,我们定义两个卷积核,其中核一是边缘检测核,而核二是随机核。
1
2
3
4
5
6
7
8
9
10# 我们用两个卷积核进行操作,第一个使用轮廓卷积核
# 第二个则采用随机数
kersize=5
ker=torch.ones(kersize,kersize,dtype=torch.float32)*-1
ker[2,2]=24
ker=ker.reshape((1,1,kersize,kersize))
# 定义卷积层
conv2d=nn.Conv2d(1,2,(5,5),bias=False)
# 通过conv2d的weight参数可以修改卷积核
conv2d.weight.data[0]=ker -
最后的卷积操作
1
2
3
4
5
6
7
8
9
10
11
12
13# 卷积操作
imconv2dout=conv2d(myimgray_t)
# 维度压缩
imconv2dout_im=imconv2dout.data.squeeze()
# 可视化
plt.figure(figsize=(12,6))
plt.subplot(1,2,1)
plt.imshow(imconv2dout_im[0],cmap=plt.cm.gray)
plt.axis("off")
plt.subplot(1,2,2)
plt.imshow(imconv2dout_im[1],cmap=plt.cm.gray)
plt.axis("off")
plt.show()
3.2 池化层
池化操作的一个重要目的是对卷积后得到的特征进行进一步处理(主要是降维),池化层可以起到对数据进一步浓缩的效果,从而缓解计算时内存的压力。
池化会将一定改大小的区域用一个代表元素表示,譬如将一个5*5范围的元素做平均,得到一个代表元素,该过程就称为平均池化。
API:
| API | FUC |
|---|---|
| nn.MaxPool1d() | 针对输入信号上应用1D最大值池化 |
| nn.MaxPool2d() | 针对输入信号上应用2D最大值池化 |
| nn.MaxPool3d() | 针对输入信号上应用3D最大值池化 |
| nn.MaxUnPool1d() | 1D最大池化的部分逆运算 |
| nn.MaxUnPool2d() | 2D最大池化的部分逆运算 |
| nn.MaxUnPool3d() | 3D最大池化的部分逆运算 |
| nn.AvgPool1d() | 1D平均池化 |
| nn.AvgPool2d() | 2D平均池化 |
| nn.AvgPool3d() | 3D平均池化 |
| nn.AdaptiveMaxPool1d() | 1D自适应最大池化 |
| nn.AdaptiveMaxPool2d() | 2D自适应最大池化 |
| nn.AdaptiveMaxPool3d() | 3D自适应最大池化 |
| nn.AdaptiveAvgPool1d() | 1D自适应平均池化 |
| nn.AdaptiveAvgPool2d() | 2D自适应平均池化 |
| nn.AdaptiveAvgPool3d() | 3D自适应平均池化 |
以nn.MaxPool2d()为例,其参数使用为
1 | nn.MaxPool2d( |
案例
-
我们对刚刚经过卷积的图像做最大池化处理:
1
2
3
4# 对卷积后的结果进行最大值池化
maxpool2=nn.MaxPool2d(2,2)
pool2_out=maxpool2(imconv2dout)
pool2_out_im=pool2_out.data.squeeze() -
平均池化处理
1
2
3
4# 平均值池化
avgpool2d=nn.AvgPool2d(2,2)
pool2_out=avgpool2d(imconv2dout)
pool2_out_im=pool2_out.data.squeeze() -
自适应池化处理
1
2
3
4
5# 自适应平均值池化
# 可以用output_size去指定特征映射尺寸
adaavgpool2=nn.AdaptiveAvgPool2d(output_size=(100,100))
pool2_out=adaavgpool2(imconv2dout)
pool2_out_im=pool2_out.data.squeeze()
补充
-
关于tensor.data和tensor.detach
1
2
3
4
5
6# 一般情况下Tensor会带有梯度信息、是否求导等动态图计算中需要使用的信息
# 那这些信息拿来做一些事情比如绘图就不方便
# 就需要从其中拿到数据
A.data # 取出数据本体,但共享内存,修改互相影响
A.detach # 同样,只不过在反向传播时,会判断数据是否被修改,修改会报错
3.3 激活函数
通常的激活函数为S型(sigmoid)激活函数、双曲正切(Tanh)激活函数、线性修正单元(ReLU)激活函数
| API | FUC |
|---|---|
| nn.Sigmoid | - |
| nn.Tanh | - |
| nn.ReLU | - |
| nn.Softplus | - |
-
Sigmoid
也称为logistic激活函数,其输出在(0,1)之间,作为早期的激活函数,其缺点比较明显。当输入远离坐标原点时,函数的梯度会变的很小,影响参数的更新速度。
-
Tanh
输出期间在(-1,1)之间,整个函数以0为中心,虽然梯度消失仍然存在,但其以0对称的特点使得其效果优于Sigmoid。
-
ReLU
ReLU只保留大于0的输出,当输入为正数时,不存在梯度饱和问题,计算速度很快。
-
Softplus
Softplus是平滑近似ReLU,默认取值为1,该函数对于任意位置都可导,且保留了ReLU的优点。
3.4 循环层
PyTorch提供了三种循环层的实现。
| API | FUC |
|---|---|
| nn.RNN | 多层RNN单元 |
| nn.LSTM | 多层长短期记忆LSTM单元 |
| nn.GRU | 多层门限循环GRU单元 |
| nn.RNNCell | 一个RNN单元 |
| nn.LSTMCell | - |
| nn.GRUCell | - |
以nn.RNN 为例,其参数信息为:
1 | nn.RNN( |
具体使用内容见后续循环神经网络。
3.5 全连接层
通常所说的全连接层是指一个由多个神经元所组成的层,其所有的输出和该层所有的输入都有连接,即每个输入都会影响所有神经元的输出。
在PyTorch中,nn.linear()表示线性变换,全连接层可以看做线性变换层加上一个激活函数。
其相关参数如下:
1 | nn.Linear( |
案例
现在利用PyTorch构建一个多层感知机。
1 | class MLP(nn.Module): |
4. 数据操作和预处理
在PyTorch中,torch.utils.data模块包含着一些常用的数据预处理函数。
| API | FUC |
|---|---|
| torch.utils.data.TensorDataset() | 将数据处理为张量 |
| torch.utils.data.ConcatDataset() | 连接多个数据集 |
| torch.utils.data.Subset() | 根据索引获取数据集的子集 |
| torch.utils.data.DataLoader() | 数据加载器 |
| torch.utils.data.random_split() | 随机将数据集拆分成给定长度的非重叠新数据集 |
4.1 高维数组
回归数据案例
-
预先加载数据
1
2
3
4
5
6
7import torch
import torch.utils.data as Data
from sklearn.datasets import load_boston,load_iris
# 读取波士顿回归数据
boston_x,boston_y=load_boston(return_X_y=True)
# 这两个是numpy数据,类型为float64 -
需要将numpy转化为tensor
1
2
3train_xt=torch.from_numpy(boston_x.astype(np.float32))
train_yt=torch.from_numpy(boston_y.astype(np.float32))
# 这样我们就得到了Tensor.float32数据啦 -
在训练全连接神经网络时,通常一次使用一个batch的数据进行权重更新。torch.utils.data.DataLoader()可以将输入的数据集打包成一个加载器,每次迭代可使用一个batch数据
1
2
3
4
5
6
7
8
9# 利用TD函数将XY整合到一起
train_data=Data.TensorDataset(train_xt,train_yt)
# 定义一个加载器,将训练数据集进行批量处理
train_loader=Data.DataLoader(
dataset=train_data, # 使用的数据集
batch_size=64, # 批处理样本大小
shuffle=True, # 每次迭代前打乱
num_workers=1, # 使用两个进程
) -
现在我们就能获取到一个batch的数据了
1
2
3
4
5for step,(b_x,b_y) in enumerate(train_loader):
b_x.shape
# torch.Size([64,13])
b_y.shape
# torch.Size([64])
4.2 图像数据
以文件夹路径数据为例
-
torchvision的datasets模块包含ImageFolder()函数,该函数可以读取如下格式的数据集:
- root/dog/001.png
- root/cat/026.png…
-
为了保证图像数据的一致性,需要先做一些变换
1
2
3
4
5
6
7
8
9
10
11import torchvision.transforms as transforms
train_data_transforms=transforms.Compose([
transforms.RandomResizedCrop(224), # 随机长宽比裁剪为224*224
transforms.RandomHorizontalFlip(), # 依概率p=0.5水平翻转
transforms.ToTensor() # 转化为张量并归一化至[0-1]
# 此过程还会将图像的形状从[H,W,C]转化为[C,H,W]
# 标准化处理
transforms.Normalize([0.485,0.456,0.406],
[0.229,0.224,0.225])
]) -
接着我们就能去读取图像了
1
2
3
4
5
6train_data_dir="data/chap2/imagedata/"
# 文件夹路径与变换信息
train_data=ImageFolder(train_data_dir,transform=train_data_transforms)
train_data_loader=Data.DataLoder(
train_data,batch_size=4,shuffle=True,num_works=1
)
4.3 文本数据
这一章内容因数据而异,暂且按下不表。





