Tensor & autograd

Tensor

支持gpu加速,可以在ipython/notebook使用<function>? 查看帮助文档,也可以在代码中用help(function)这里的function就不用加小括号了,如

1
2
torch.ones? #仅用于notebook/ipython
#help(torch.ones)

基础操作

从接口,对tensor操作分为两类(torch.sum(a, b), a.sum(b)):

  • torch.function, 如torch.save (torch 是导入的模块名)
  • tensor.function, 如tensor.view (tensor代表的是创建的tensor对象名,)

从存储,对tensor操作分为两类:

  • 不会修改自身数据,如a.add(b),会返回一个新的tensor
  • 会修改自身数据,如a.add_(b)

创建tensor方式:

Tensor函数是最复杂多变的方式,可以接收list,可以指定形状,可以传入其他的tensor,或者torch.Size类别。

1
2
3
4
a = t.Tensor(2, 3)
b = t.Tensor([[1, 2, 3], [4, 2, 3]])
c = t.Tensor(a)
b.tolist() k#可以把tensor转会list

tensor.size() 返回torch.Size 对象,可查看tensor大小,也可以作为Tensor创建函数的参数。tensor.shape等同于tensor.size()

常用tensor操作

tensor.view

改变tensor的形状,返回的tensor与原tensor共享内存(相当于只是变了映射关系)

torch.squeeze(a, [N]),代表去除a中维度为一的维度,不输入N代表去除所有维度为一的维度,为具体数值表示如果该维度为1,则将其去掉。

torch.unsqueeze(a, [N])对数据进行扩充,在指定位置加上维度为一的维度

resize是另一种调整size的办法,但如果新尺寸超过了原尺寸,会分配新的空间,如果新尺寸小于了原尺寸,之前的数据会被保留

1
2
3
4
5
6
7
8
a = t.randn(2, 3)
a.resize_(3, 4) #貌似没见到a.resize()的用法,帮助文档也找不到,
print(a.size(), a)
a.resize_?
--------------------------------------------------
torch.Size([3, 4]) tensor([[ 1.1888e+00, -2.2413e-01, -6.4524e-01, 1.1506e+00],
[-6.9476e-02, -1.4372e+00, 1.8057e+28, 7.9050e+31],
[ 1.3236e-14, 3.9890e+03, 1.7033e+19, 1.0903e+27]])
索引操作

无特殊说明,索引出来的结果与原tensor共享内存,

一般来说可以这么用 a[1:2, 4:6, 1:3 ...]以逗号分割的代表不同维度,可以小于tensor的维度,以冒号分割的代表该维度上下限,左开又闭。

注意后两者区别

1
2
3
4
5
6
7
print(a.size())
print(a[0:1, :2].size())
print(a[0, :2].size())
-----------------------------------
torch.Size([3, 4])
torch.Size([1, 2])
torch.Size([2])

一个tensor,可以用 a > 1返回一个bytetensor,也可以作为tensor的索引,等同于a.mask_select(a>1)

1
2
3
4
5
6
7
8
a = t.randn(3, 4)
print(a)
print(a[a>1])
------------------------------
tensor([[ 1.3819, 1.4626, -0.0858, -1.2977],
[-0.1992, 0.1883, 0.5572, -2.4197],
[ 2.4179, -0.0833, 0.6245, -0.6284]])
tensor([1.3819, 1.4626, 2.4179])

其他的选择函数:

对一个二维gather,输出的每个元素如下

与gather对应的逆操作是scatter_。

高级索引

可以看成普通索引的拓展,但是结果一般不和原始Tensor共享内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
x = t.arange(0, 27).view(3, 3, 3)
print(x)
print(x[[1, 2],[1, 2], [2, 0]])
-------------------------------------
tensor([[[ 0, 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]]])
tensor([14, 24])

这与之前带 冒号的索引很相似,逗号分割的是不同维度,可以小于tensor的维度,只不过原来是i:j ,现在是[a, b],表示a或b,不同维度之间的匹配次数不愿意,原来是i*j,现在是各维度的元素数量(应该,有点不好描述)

Tensor类型

tensor有不同类型,每种类型分别对应有CPU和GPU版本的,默认tensor是floattensor类型,可以通过t.set_default_tensor_type 修改tensor类型,(如果默认类型为gpu tensor,那么所有操作都将在GPU上进行),

各类型之间可以进行转换,**type(new_type)**是通用的做法,除此之外,还有float,long,half等快捷方式。GPU tensor 和 CPU tensor之间的相互转换可以通过 tensor.cuda 和 tensor.cpu的方法实现,Tensor还有一个new 方法,可以创建该tensor类型的tensor

1
2
3
4
a = t.Tensor(2, 3)
b = a.long()
c = a.type_as(b)
d = c.new(3, 4)
逐元素操作

element-wise,操作输入与输出形状一致。

归并操作

使输出形状小于输入形状,并可以沿着某一维度指定操作。如加法sum,可以计算整个tensor的和,也可以计算每一行/每一列的和。

以上函数都有一个参数dim,用来指定是在那个维度上操作的,另外一个注意点:(wuenda视频里也经常有keepdims=True)

比较

有些是逐元素比较,有些类似于归并操作。常用函数如下:

第一行实现了运算符重载可以用a>=b, a!=b 等,返回结果是一个bytetensor。max/min有三种用法,

  • t.max(tensor) 返回tensor中最大的一个数
  • t.max(tensor, dim) 指定维度上最大的数,返回tensor和下标(如二维tenser,dim=1,代表每一行的最大值)
  • t.max(tensor, tensor) 比较两个tensor比较大的元素

维度,整个多维矩阵可以看出一棵树,从第一维到第n维,max的第二种用法需要指定dim为第几维度,对应到树上就是第几层树,然后在该层间找最大值(同一个父节点)

例子:

x = numpy.array([[[1, 2], [3, 4]], [[4, 3], [2, 1]]]) (pytorch也一样)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
print(numpy.max(x, axis=0))
print(all(numpy.max(x, axis=0)==numpy.max(x, axis=-3)))
----------------------------------
[[4, 3],
[3, 4]]
True
print(numpy.max(x, axis=1))
print(all(numpy.max(x, axis=1)==numpy.max(x, axis=-2)))
-----------------------------------
[[3, 4],
[4, 3]]
True
print(numpy.max(x, axis=2))
print(all(numpy.max(x, axis=2)==numpy.max(x, axis=-1)))
------------------------------------
[[2, 4],
[4, 2]]
True
线性代数

pytorch的线性函数主要封装了blas和lapack,常用的函数如下表所示

Tensor & Numpy

两者相似性很高,之间共享内存。可以相互转换再使用。

虽然pytorch有自动广播,还是可以通过以下函数手动实现,不易出错

  • unsqueeze或view,为某一维度补上1
  • expand或expand_as, 重复数组,将单维度扩大。(不会复制数组,所有不会占用额外空间,repeat有类似的功能,但会复制数据,会占用额外空间)

内部结构

tensor分为头信息区和存储区。信息区主要保存着tensor形状,步长,数据类型等信息。真正的数据连续成组,信息区占用内存较少,主要内存占用取决于tensor中元素的数目,即存储区的大小。

可以通过对象的storage函数访问,将其传入id中获取对象id, 可以通过对象的data_ptr 返回tensor的首元素的内存地址。

1
2
print(b.storage())
print(id(b.storage()))

other

持久化

tensor加载和保存很简单,使用t.save和t.load,在save/load时还可以指定使用到pickle模块,load时可将gpu tensor映射到其他cpu或gpu上。(不太理解,之后看吧)

向量化

避免原生for循环,使用向量化的数值计算。

疑问,pytorch 的tensor变量在被赋值时可以自动更改类型吗

1
2
3
4
5
6
7
8
# 创建一个整数类型的张量
a = t.tensor([1, 2, 3])

# 尝试将浮点数赋值给整数类型的张量
a = t.tensor([1.0, 2.0, 3.0])

# 查看最终张量的类型
print(a.dtype)

小试牛刀-线性回归

%matplotlib inline 将使得 matplotlib 的图表直接嵌入到 notebook 中的输出单元格中,而不是弹出一个新的图形窗口。这对于在 notebook 中进行数据分析和可视化非常方便。

autograd

方便用户使用,专门开发的自动求导引擎,根据输入和前向传播过程自动构建计算图,执行反向传播。了解基本计算图知识很有必要(Christopher Olah)

Variable(部分可能是老版本的)

Pytorch在autograd模块中实现了计算图的相关功能,autograd中的核心计算结构是Variable,Variable封装了tensor。Variable包含三个属性,data(保存variable包含的tensor),grad(保存data对应的梯度),grad_fn(指向一个function,记录variable的操作历史),

Variable的构造参数需要传入tensor,同时有两个可选参数。

requires_grad(bool) ,是否需要对该variable进行求导。

vaolatile(bool) ,设置为true,构建在该variable上的图都不会求导。

Variable支持大部分tensor支持的函数,但不支持部分inplace函数,因为这些函数会修改tensor本身,在反向传播中,variable需要缓存之前的tensor来计算梯度。要计算各个Variable的梯度,只需要调用根节点variable的backward方法。autograd会自动沿着计算图反向传播,计算每一个叶子结点的梯度。

计算图

由于中间变量的梯度计算后会被清空,可以使用autograd.grad和hook来获取中间变量的梯度

1
t.autograd.grad(z, y) #代表求z对y的梯度,隐式调用backward
1
2
3
4
def variable_hook(grad):
print("y 的梯度", grad)
hook_handle = y.register_hook(variable_hook) #注册,backward时可以自动调用
hook_handle.remove()#去除

假如y = x**2 + x*2, z = y.sum()可以从z开始backward,也可以从y开始,只是需要传入y当前的梯度,之所以z不用传,是因为z对自己的梯度就是1,被省略了。

1
2
3
4
5
6
7
8
9
10
11
12
x = t.arange(0.0, 3.0, requires_grad=True)
y = x**2 + x * 2
z = y.sum()
z.backward()
x.grad
--------------------------------------------------
x = t.arange(0.0, 3.0, requires_grad=True)
y = x**2 + x * 2
z = y.sum()
y_grad_var = t.Tensor([1.0, 1.0, 1.0])
y.backward(y_grad_var)
x.grad

对variable的操作才可以使用autograd,如果对variable的data直接操作,将无法使用反向传播。一般不会修改data的值。

神经网络工具箱nn

针对前向传播网络,每次都写很复杂的forward函数含麻烦,有两种简化方式,ModuleList和Sequential。Sequential是一个特殊的Module,它包含几个module,前向传播会将输入一层一层的传递下去,Module也是一个特殊的Module,可以包含几个module,向使用list一样使用。

两种创建

优化器

常用优化方法封装到torch.optim中,所有优化方法都是继承基类optim.Optimizer,

nn.module 和nn.fucntional, 模型有可学习的参数,最好用第一个,否则都可。激活函数(relu,sigmoid,tanh,)池化没有可学习的参数,可以用对应的functional函数代替。卷积,全连接等网络建议用nn.module。有参数也可以用funtional,只是需要手动定义参数,如。

常用工具

数据,可视化,GPU加速

数据

数据加载,通过自定义数据集实现,数据集对象被抽象为dataset类,实现自定义的数据集需要继承dataset,并实现两个魔法方法。

  • __getitem__, 返回一个数据
  • len ,返回样本的数量

此外,pytorch提供了torchvision,是一个视觉工具包,提供了很多视觉图像处理的工具。transforms模块提供了对PIL Image对象和Tensor对象的常用操作。

对PIL iMAGE的常用操作:

  • Resize
  • centerCrop
  • pad
  • toTensor,并且自动归一化[0,1]
  • normalize
  • topilimage

torchvision 视觉工具包,常用模型,数据集加载方式,预处理操作

可视化工具,Tensorboard和visdom