这个系列主要是写给自己看的,奉行拿来主义,对于学习过程中使用到的优质的学习资源和教程会直接把链接贴进来(省的自己看完忘了),也会附上我的一些学习笔记,同时也记录一下我在学习过程中踩过的各种坑(血泪史),希望能帮助到和我一样刚刚入门的人吧。这个系列称为“实践笔记”,是因为其主要面向编程实践而非理论学习。因为我的基础还是比较差,很多东西只是自己的理解,不一定对,供参考。
教程介绍和目录见此文:
季节:【从零开始的机器学习实践笔记】02-优质教程资源收录zhuanlan.zhihu.com教程10-13集讲了在PyTorch中对tensor的几种操作。
在Pytorch中对tensor的操作主要有以下四种类型:
这一集主要讲的是reshaping。
先创建一个具有浮点数数据类型的3x4的二阶张量方便举例:
>
张量 t 的 shape 或者 size 为:
>
前面在《教程5-7:tensor基本概念》中介绍过要注意reshape前后元素总量不变,而查看一个张量中的元素总量可以用以下两种方法:
① 计算各个 tensor 各个 axis 上的长度乘积:
>
② 或者直接用 numel() 函数(number of elements的缩写):
>
二阶张量 t 中一共有12个元素,那么对t进行 reshape 可能的结果是将 3x4 改成 1x12、2x6、3x4、4x3、6x2、12x1,当然也可以用 t.reshape(2,2,3) 这样的方式 reshape 为 2x2x3 的三阶张量。
另外PyTorch中还有一个 view() 函数和 reshape() 具有相同的作用。
在 reshape 的各种操作中,最常用的一种操作叫做 flatten 操作,就是把一个n阶的张量变形为只有一行的一阶张量,即变为 array 类型。这个操作通常在CNN中由卷积层传递到全连接层的时候发生。上一篇说过,卷积层的输出是很多的 feature map,每一个 feature map 都是一个二阶张量,但是全连接层的输入只能是数组,因此需要把卷积层输出的高阶张量压平变为一维数组,才能输入全连接层。
这里要说明的是,对二阶张量进行 flatten 的操作,是第一行数据不动,将第二行数据拼接至第一行数据末尾,再将第三行数据拼接到第二行数据的末尾,以此类推。
比如采用 reshape() 函数对张量 t 进行变换:
>
这里要注意的是,输出的张量有两层中括号,而且 shape 为“[1, 12]”,表明其在计算机中存储的形式仍然为二阶张量,只不过有一个 axis 的长度为 1 而已。要进一步将其转为一阶张量,需要用到 squeeze() 函数:
>
这时注意到输出结果变为了一阶张量,因此 squeeze() 函数的作用就是去掉高阶张量中所有长度为 1 的 axes,使高阶张量降低阶数为低阶张量。
类似的还有 unsqueeze() 函数,为张量添加一个长度为1的 axis,相当于 squeeze() 的逆操作:
>
可以定义一个 flatten() 函数包含上述 reshape 和 squeeze 两步:
def
这里在 reshape() 函数中的第二个参数填写了“-1”,是让计算机自己根据张量中的元素数量决定一维数组的长度。可以验证:
>
视频最后讲了一下张量的拼接,称为 concatenating,对应的函数是 cat()。
例如现在有两个二阶张量:
>
使用 cat() 函数同时指定拼接的方向(维数),可以得到一个拼接后的新张量。
沿第一个维度(行)拼接为4行2列:
>
沿第二个维度(列)拼接为2行4列:
>
这一集就讲了一个事情——如何只对一个高阶张量的部分维度进行 flatten 操作。
上一集只讲了对一张图片的二阶张量进行展平,但前面的章节说过,一次输入CNN的是不是一张而是一批图像,输入的是一个具有 [B, C, H, W] 形状的四阶张量。如果直接对这个张量进行展平,会把所有图像的所有颜色通道(或者 feature map)全都混在一起。而我们希望的是仅仅对一个 feature map 进行展平,而不把不同的图片、不同的颜色通道和不同的 feature map 都混合在一起。
所以我们要做的其实只是把 [B, C, H, W] 这个四阶张量的 H 和 W 两个维度展平。
为了方便讲解,举了一个 3x1x4x4 的四阶张量作为例子(灰度图片只有一个颜色通道)。
首先创建3个 4x4 的二阶张量代表3张 4x4 的图片,数字 i 只属于第 i 张图片:
t1
为了将这三个二阶张量堆栈起来组成一个 batch,可以用 torch.stach() 函数:
>
此时还缺一个中间的颜色通道维度,采用 reshape() 函数来添加:
>
最后创建的四阶张量 t 为(注释标明了每一个维度的含义):
tensor
因为灰度图片只有一个颜色通道,所以要把 [B, C, H, W] 中从第二个维度起的后三个维度 C、H、W 都展平,这里采用PyTorch自带的 torch.flatten() 添加一个 start_dim=1 的参数即可(上一集中为了讲解方便自己定义了 flatten() 函数,但PyTorch中其实自带了功能更强的 torch.flatten() 函数):
>
可以看到输出结果保留了 batch 的维度,而把后面的维度都展平了,这样同一 batch 中的不同图片数据就不会混在一起了。
对于有三个颜色通道的 RGB 图片,不同的颜色通道(或者 feature map)也不应该混淆在一起,这时候把 torch.flatten() 函数的参数改为 start_dim=2 即可。
这一集讲的是张量的 element-wise operations(按元素逐项操作)。
An element-wise operation operates on corresponding elements between tensors.
而所谓的 corresponding elements 是指在张量中的位置相同,就是在各个 axis 上的索引编号相同的元素。
举例来说,有两个tensor:
>
那么 1 和 9 就是 corresponding elements,因为索引的 indexes 是一样的:
>
所以通过这一点强调的是,所有的 element-wise operations 都只能在具有相同 shape 的张量之间进行(不然元素对应不上啊)。
这里介绍的 element-wise operations 分为三类,一类是 arithmetic operations(算术操作),一类是 comparison operations(比较操作),还有一类是 function operations(函数操作,其实也可以归到算术操作里)。
算数操作说的自然就是加减乘除之类的一些运算了呗,比如:
>
所以可以看到,PyTorch中张量间的加减乘除四则运算都是 element-wise 的,相当于 matlab 中加“.”前缀的运算符。
不过和 matlab 一样的是,PyTorch中张量和数的运算也是 element-wise 的,如下:
>
也可以用函数命令来运算,结果是一样的:
>
但 t1 是一个 2x2 的二阶张量,而数 2 是一个零阶的张量,没有 shape,和之前说的 element-wise operations 需要在 shape 一样的张量之间进行不相符。这是因为接下来要介绍的PyTorch中的一个重要概念:
Broadcasting 应该翻译成“增广”?我也不清楚,就这么叫吧。
Broadcasting tensor 就是把两个 shape 不一样的 tensor 匹配成 shape 一样的 sensor 的过程,匹配的方法是将 shape 小的 tensor 进行复制和拼接,使小的 tensor 变成和大的 tensor 具有一样的 shape。这是PyTorch在 shape 不同的张量之间进行操作之前会进行的一个步骤,即:
>
对于不同 shape 的 tensor 之间的操作也是如此:
>
t1 与 t2 相加时,PyTorch对 t2 进行了 broadcast 以匹配 t 的 shape:
>
在深度学习中,broadcasting 主要用在数据预处理(data preprocessing)和正则化(normalization routines)之中。
熟练掌握 broadcasting 的优势在于,在编程之中可以省略原本需要很多行代码进行手工张量增广和形状匹配的工作,而让PyTorch默认去完成,使代码更加简洁高效。
这篇教程中给出了有关 broadcasting 的更详细介绍:
Broadcasting Explained - deeplizarddeeplizard.com比较操作就是两个张量之间按元素进行比较了(比如比大小),返回值的数据类型是 torch.bool(布尔运算值),即 True 和 False。
比如有这么一个 tensor:
>
各项比较操作分别为:
>
可以发现PyTorch也是先对 number 类型的零维张量进行了 broadcasting 之后才进行比较操作。
下面这些常用函数操作也都是 element-wise 的:
>
最后补充一下,以下三个术语说的是同一个意思:
Just keep this in mind if you encounter any of these terms in the wild.
最后这个“in the wild”真是太逗了,你细品~
这一集讲PyTorch中的最后两种张量操作:
A reduction operation on a tensor is an operation that reduces the number of elements contained within the tensor.
前面提过,张量是深度学习中使用的数据结构,我们使用张量不仅是用来进行数据的存储,更重要的是进行数据的访问和管理。
Reshaping operations 让我们能够重新排列元素;element-wise operations 让我们能够在两个张量的元素之间执行运算,reduction operations 让我们能够对单个张量内的元素执行运算。
以一个 3x3 二阶张量举例:
>
所谓的 reduction operations 其实特别简单:
>
之所以称之为 reduction operations 是因为他们的输出从原来输入的二阶张量变为了零阶张量,张量中总的元素数量减少了:
>
上述的操作在不指定 axis 时都是对张量中所有元素进行操作的,也可以指定 reduction operations 作用的 axis,得到的输出就会是一个不是只包含一个元素的张量了。
以一个 3x4(3行4列)的张量为例:
>
可以看到用 dim = 0 指定沿第一个 axis 操作,也就是按行数索引的方向相加,其实就是求每列的和,所以输出有张量有4个元素(4列);与之相反 dim = 1 指定沿列索引的方向操作,就是求每行的和,所以输出有张量有3个元素(3行)。
这里要说明的是PyTorch中进行 reduction operations 之后会自动把输出的张量进行 squeeze() 操作,因此可以看到输出的直接就是一阶张量(都是一行,不区分行列),不再保留原来的行列信息,因此操作的时候需要自己想清楚。(可能是 matlab 惯的臭毛病吧)
然后这里要讲一个特别的 reduction operation 叫做 Argmax(其实数学上应该加个空格吧 Arg max)。
数学上,我们用 max y(x) 来表示求函数 y(x) 的最大值,而 Arg max y(x) 就是求当 y 取得最大值的时候,对应的 x 的取值是多少。反映在张量之中,就是求张量中某个最大的元素的位置上(就是对应的索引序号)。
比如这个张量,最大值 5 在第 3 行第 4 列:
t
可以看到 argmax() 函数输出的只有一个零阶张量,而非刚才说的第 3 行第 4 列两个值,这是因为PyTorch先对高阶张量进行了 flatten() 操作,然后才去比较大小:
> t.flatten()
tensor([1., 0., 0., 2., 0., 3., 3., 0., 4., 0., 0., 5.])
> t.flatten().argmax()
tensor(11)
同样 argmax() 函数也可以按指定的 axis 方向进行操作,比如:
> t.max(dim=0)
(tensor([4., 3., 3., 5.]), tensor([2, 1, 1, 2]))
> t.argmax(dim=0)
tensor([2, 1, 1, 2])
> t.max(dim=1)
(tensor([2., 3., 5.]), tensor([3, 1, 3]))
> t.argmax(dim=1)
tensor([3, 1, 3])
这里 max() 函数会返回两个张量,第一个是按指定的 axis 方向找出的最大元素值,第二个是这些最大元素在指定的 axis 方向上的索引号。
argmax() 在神经网络中的用处在于:图像分类神经网络的输出层是一个包含 k 个元素的一阶张量(其中 k 是图片的类别数),张量中的元素是对应各个类别的 prediction values(翻译成置信概率?),所有的 prediction values 之和为 1。我们会取置信概率最大的那一种作为最终预测的类别(比如 a 类预测值 0.05,b 类 0.95,那就认为是 b 类)。这样就需要使用 argmax() 函数来找出最大的 prediction value 对应的是哪一类。
最后就是讲一下如何访问和提取张量中的数据了。
比如求张量元素的平均值:
>
可以看到输出的数据类型仍然是一个张量(虽然是零阶张量),可以通过 item() 函数提取出零阶张量中的数据:
>
对于输出为含有多个元素的张量时,可以采用 tolist() 将其转为 Python list:
>
或者用 numpy() 将其装换为 numpy 数组:
>
关于 numpy 中高级索引的资料:
Advanced indexing and slicingdocs.scipy.org那么至此deeplizard教程的 Part I,就是关于 PyTorch 和 tensor 的介绍就结束了。接下来会进入 Part II 去实际搭建一个CNN来进行图像分类。下一集将从数据集的介绍开始。