1.数据类型
1.1 数据类型基础
python的int类型,在PyTorch里就可以用IntTensor来表示,只不过它的维度是0,称之为标量,标量经常用来计算Loss函数的值;python的int数组,可以用维度为1的IntTensor来表示。string的表达可以用one-hot,但是one-hot不能反应字符串之间的关系,在NLP领域有Embedding相关的内容可以表示。不属于本部分内容,后面有机会再描述。
有一些重要的表述需要区分一下Dimension, Size和Tensor。一般Dimension就是说这个变量的维度,比如二维矩阵的Dimension就是2,Size是具体指每个维度的长度,比如[2, 2],Tension就是一个具体的值。
常用的类型IntTensor,FloatTensor, ByteTensor。注意,同一个数据是在CPU上计算还是GPU上计算,PyTorch可能会用不同类型来表示,例如上图的torch.FloatTensor和torch.cuda.FloatTensor的区别。
# 类型推断
import torch
import numpy as np
# 初始化一个2行3列的tensor
# 生成一个Dimension为2 的tensor
a = torch.randn(2, 3)
# 返回torch.FloatTensor
print(a.type())
# 检验类型, 返回True
print(isinstance(a, torch.FloatTensor))
#下面示例在CPU和GPU的类型
#返回False
print(isinstance(a, torch.cuda.FloatTensor))
a = a.cuda()
#返回True
print(isinstance(a, torch.cuda.FloatTensor))
#dimension为0的标量,一般用于loss函数
b = torch.tensor(1.0)
# 返回torch.FloatTensor
print(b.type())
# 返回torch.Size([])
print(b.shape)
#返回0
print(b.ndimension())
#返回torch.Size([])
print(b.size())
#dimension为1,长度为1的张量
#一般用于Bias
c = torch.tensor([1.1])
#返回tensor([1.1000])
print(c)
#直接指定长度为1的张量,会有一个随机数
d = torch.FloatTensor(1)
#使用numpy来初始化
d = np.ones(2)
再来看一个Dimension为4的例子:
import torch
# 生成一个Dimension为2的张量
# Dimension为2的张量一般用于表示Linear Input batch
# 比如mnist照片[4, 784]就表示4张照片,每张的像素点是784个
a = torch.randn(2, 3)
# 打印 tensor([[-0.1650, 1.7424, 0.9895],
# [ 0.4871, 1.1065, 1.7918]])
print(a)
# torch.Size([2, 3])
print(a.shape)
# 返回2
print(a.size(0))
# 返回3
print(a.shape(1))
1.2 数据创建
1.2.1 从numpy中创建
import numpy as np
import torch
a = np.array([2, 3.3])
b = torch.from_numpy(a)
# 返回tensor([2.0000, 3.3000], dtype=torch.float64)
print(b)
a = np.ones([2,3])
b = torch.from_numpy(a)
# tensor([[1., 1., 1.],
# [1., 1., 1.]], dtype=torch.float64)
print(b)
1.2.2 使用torch api创建
# 从List导入
import torch
# tensor是接收现有的数据
a = torch.tensor([2., 3.2])
print(a)
# FloatTensor传值
b = torch.FloatTensor([2., 3.2])
# FloatTensor传shape
c = torch.FloatTensor(2, 3)
# 返回tensor([2.0000, 3.2000])
print(b)
# 返回tensor([[1.3563e-19, 1.3563e-19, 1.3563e-19],
# [1.3563e-19, 1.2708e+31, 1.7182e+25]])
print(c)
# tensor([[ 2.0000, 3.2000],
# [ 1.0000, 22.3000]])
a = torch.tensor([[2., 3.2], [1., 22.3]])
print(a)
1.2.3 设置默认推断类型
import torch
a = torch.tensor([1.2, 3]).type()
#torch.FloatTensor
print(a)
torch.set_default_tensor_type(torch.DoubleTensor)
a = torch.tensor([1.2, 3]).type()
#torch.DoubleTensor
print(a)
1.2.4 随机初始化
import torch
# rand从N(0,1)分布来随机读取数据
a = torch.randn(3, 3)
print(a)
# rand_like接收一个tensor为输入,返回一个类型一样的tensor
b = torch.randn_like(a)
print(b)
# 也可以自己设置取值的范围
# 第一个是开始值,第二个是最大值,取值包含开始值小于最大值
# 第三个参数是shape
a = torch.randint(1, 10, [3, 3])
# tensor([[2, 6, 9],
# [8, 7, 8],
# [7, 4, 8]])
print(a)
1.3 索引与切片
1.3.1 直接选择维度
import torch
a = torch.rand(4, 3, 28, 28)
# 对第一个维度的第一个元素进行索引, 并打印shape
# 打印结果 torch.Size([3, 28, 28])
print(a[0].shape)
# 对第一个维度的第二个元素进行索引, 并打印shape
# 打印结果 torch.Size([3, 28, 28])
print(a[1].shape)
# 对两个维度进行索引
# 打印结果 torch.Size([28, 28])
print(a[0, 0].shape)
# 对四个维度进行索引,返回最终的一个标量
# 打印结果 tensor(0.4392)
print(a[0, 0, 2, 4])
1.3.2 从前/后开始选择
# select first/last N
a = torch.rand(4, 3, 28, 28)
print(a.shape)
# [num1]:[num2] 表示大于等于num1,小于num2
# 选择第一个维度的前2个元素
# 打印结果 torch.Size([2, 3, 28, 28])
print(a[:2].shape)
# :前后都没有值表示所有元素
# 打印结果 torch.Size([2, 1, 28, 28])
print(a[:2, :1, :, :].shape)
# [num1]: 表示从num1开始到最后一个元素
# 打印结果 torch.Size([2, 2, 28, 28])
print(a[:2, 1:, :, :].shape)
1.3.3 按步长选择
# select by steps
# 按步长选择元素
# 语法 num1:num2:num3 表示 开始值:结束值:步长
a = torch.rand(4, 3, 28, 28)
print(a.shape)
# 打印结果 torch.Size([4, 3, 14, 14])
print(a[:, :, ::2, ::2].shape)
1.3.4 指定index选择
import torch
a = torch.rand(4, 3, 28, 28)
# 指定索引进行选择
# 第一个参数指定维度,第二个参数给定一个数组,指定要采样的索引号
b = a.index_select(0, torch.tensor([1]))
# 打印结果 torch.Size([1, 3, 28, 28])
print(b.shape)
c = a.index_select(2, torch.arange(0, 8))
# 打印结果 torch.Size([4, 3, 8, 28])
print(c.shape)
1.4 维度变换
维度变换是机器学习、深度学习过程中非常常用的方法,PyTorch中一般包含四类操作:view/reshape, squeeze/unsequeeze, transpose/t/permute, expand/repeat。
1.4.1 view/reshape
view和reshape基本是同样的API,只不过是不同版本的PyTorch导致的,主要作用是将张量的shape进行变换,需要满足变换前的张量变换前不同维度的乘积和变换后不同维度的乘积相同,即prod值相等。实际中要注意,每种维度的变换应该具有其物理意义,不能无目的变换,变换会失去张量的物理意义。比如有张量tensor.Size([4,1,28,28]),假设我们可以理解为有4张图片,每张图片有1个通道,图片长和宽都是由28个像素点组成的。
1.4.2 squeeze/unsqueeze
有时候,两个张量操作时候,由于维度不一样,无法直接操作。一般,就会使用squeeze/unsqueeze来减少/增加对应的维度,以同步两个张量的维度。
1.4.3 t/transpose/permute
t是矩阵的转置操作,只适用于维度为2的张量。transpose是将张量的2个维度进行调换,要注意调换后物理内存上可能没有完成数据调换,所以要搭配contiguous()函数使用。在使用transpose和view搭配使用时候,一定要注意不能破坏张量的结构。详见示例。transpose一次操作只能调换两个维度,如a.transpose(1,3),如果需要一次重排整个张量的结构,用permute更简单,如果a是torch.Size([4, 28, 28, 3]), 可以直接使用permute和维度索引来调换顺序,a.permute([0,2,3,1])详见示例。
1.4.4 expand/repeat
expand和repeat都可以将对应的维度的长度进行扩展,区别是Expand是逻辑扩展,不会复制数据,repeat是物理扩展,会占用内存,复制数据。详见示例。
# view/reshape
# Squeeze/unsqueeze
# Transpose/t/permute
# Expand/repeat
import torch
# view/reshape 通用,同numpy reshape
a = torch.rand(4, 1, 28, 28)
print(a.shape)
# 物理意义相当于是把照片数量和通道合并在一起,把照片的行和高合并在一起
b = a.view(4, 28 * 28)
print(b.shape)
# Squeeze 挤压 unsqueeze 展开
# 在第一个维度前增加一个维度, 相当于增加一个group概念的物理意义
# torch.Size([1, 4, 1, 28, 28])
print(a.unsqueeze(0).shape)
c = torch.tensor([1, 2])
c = c.unsqueeze(0)
print(c)
c = c.view(2, 1)
print(c)
# 解决变换维度实现维度同步的例子
b = torch.rand(32)
f = torch.rand(4, 32, 14, 14)
b = b.unsqueeze(1).unsqueeze(2).unsqueeze(0)
# 返回torch.Size([1, 32, 1, 1])
print(b.shape)
# 后面就可以实现b和f 相加了
# squeeze 维度删减
# 不给参数,把所有能挤压的维度全部挤压
# torch.Size([32])
print(b.squeeze().shape)
# 挤压掉第0维度, 返回 torch.Size([32,1,1])
print(b.squeeze(0).shape)
# 维度扩展Expand/repeat
# 区别是Expand是逻辑扩展,不会复制数据,repeat是物理扩展,会占用内存,复制数据
# torch.Size([1, 32, 1, 1])
print(b.shape)
# torch.Size([4, 32, 14, 14])
print(b.expand(4, 32, 14, 14).shape)
# 如果不想扩展其中某些维度,填-1就行了
# 返回torch.Size([1, 32, 14, 1])
print(b.expand(-1, 32, 14, -1).shape)
# .t 适用于矩阵转置
a = torch.randn(3, 4)
print(a)
a = a.t()
print(a)
# transpose 接收两个参数,包含要交换的两个维度
# 需要配合contiguous()使用
a = torch.randn(4, 3, 32, 32)
print(a.shape)
# 注意a1 破坏了结构
a1 = a.transpose(1, 3).contiguous().view(4, 3 * 32 * 32).view(4, 3, 32, 32)
a2 = a.transpose(1, 3).contiguous().view(4, 3 * 32 * 32).view(4, 32, 32, 3).transpose(1, 3)
# tensor(False)
print(torch.all(torch.eq(a, a1)))
# tensor(True)
print(torch.all(torch.eq(a, a2)))
另外再给一个综合一点的例子:
import torch
# 假设有一个学校,有5个班级,1班,2班, 3班,4班, 5班每个班级的同学都学语文,数学,英语, 物理4门科目
# 每个班有3个学生,每个同学各科目分数分别是80,90,60,85
a = torch.tensor([[[80, 90, 60, 85], [80, 90, 60, 85], [80, 90, 60, 85]],
[[80, 90, 60, 85], [80, 90, 60, 85], [80, 90, 60, 85]],
[[80, 90, 60, 85], [80, 90, 60, 85], [80, 90, 60, 85]]])
print(a.shape)
# 现在校长认为所有分数都比较低,要求每个同学所有分数增加5分
b = torch.tensor(5)
print(b.shape)
# 我们需要将标量5进行变换
b = b.unsqueeze(0).unsqueeze(0).expand_as(a)
print(b.shape)
c = a + b
#tensor([[[85, 95, 65, 90],
# [85, 95, 65, 90],
# [85, 95, 65, 90]],
#
# [[85, 95, 65, 90],
# [85, 95, 65, 90],
# [85, 95, 65, 90]],
#
# [[85, 95, 65, 90],
# [85, 95, 65, 90],
# [85, 95, 65, 90]]])
print(c)
# 现在校长认为英语分数比较低,要求为每个同学英语分数再增加20分
d = torch.tensor([0, 0, 20, 0])
print(d.shape)
d = d.unsqueeze(0).unsqueeze(0).expand_as(a)
c = c + d
#tensor([[[85, 95, 85, 90],
# [85, 95, 85, 90],
# [85, 95, 85, 90]],
#
# [[85, 95, 85, 90],
# [85, 95, 85, 90],
# [85, 95, 85, 90]],
#
# [[85, 95, 85, 90],
# [85, 95, 85, 90],
# [85, 95, 85, 90]]])
print(c)
1.5 合并和分割
2. 数学基础
2.1 数学运算
2.2 统计属性
常见的统计属性,详见示例:
- norm 求范数
- mean sum 均值 求和
- prod
- max, min, argmin, argmax
- kvthvalue, topk
2.2.1 范数
norm 不是normalize(正则化)
一范数是所有元素的绝对值求和
二范数是所有的元素的平方和,再开二次方