不知不觉我写了5篇公众号了,这个系列也写到了第三章了,我发现做什么事情还是自驱力很重要,如果觉得有意思,就能一直坚持下去,如果我公众号能写到50篇,我就去起点开一个号写仙侠小说去,我觉得小说可能我也能坚持写下去。
继续写基础,这东西之所以学起来很麻烦就是基础太多
上一篇文章我们介绍了,可以查中括号来确定张量是几维的,也能通过shape数量看到,同时shape也提供了每个维度的与元素个数,像上图前面每个维度都是1维,只有最后一维,也就是列包含2个元素
另外除了shape以外也可以用size来查看Tensor的形状
size这个方法还可以直接提取对应维度的元素个数
今天的重点就是Tesnor的变型
为什么要变型呢,就是方便不同Tensor进行乘加计算,因为深度学习的核心其实说白了,就是这些东西
简单的改变Tensor的形状, view方法
我们先建立一个1行6列的Tensor t1
然后我们将它改变成2行3列,这个时候我们调用view方法
已经从之前的1行6列变成现在的2行3列
这里有个需要注意的地方,view方法其实并没有改变Tensor的物理形状(没改变存储的方式,只是改变了头的展现形式),我们用代码验证一下,现在有t1,t2两个Tensor,t2是t1.view之后的变体
我们来打印一下t1和t2的实际存储的位置,可以看到它的物理位置并没有改变,是相同的地址
我们再打印一下t1和t2的头区域
可以看到,这两者是不同的。
我们用下图说明也许就好理解一点,不管view如何对原Tensor进行变化,但是实际上原Tensor的物理存储位置都没有变化,改变的是对操作人员或程序展现的视图
这里其实不可避免的引入了另一个概念,Tensor的连续性(contiguous),那么什么是Tensor的连续性,我们用下图说明
orch的很多操作都会导致Tensor不连续,比如tensor.transpose()、tensor.narrow()、tensor.expand()、tensor.view(),即虽然这些操作的结果是新的Tensor,但pytorch并不会真正开辟新空间来存储这些新Tenor,而是记录新Tensor元素在原Tensor中的位置,说白了,就是改变映射位置,那自然会产生很多不连续的Tensor。
如果本身你操作的Tensor正好是不连续的Tensor,那么是没有办法进行view的,判断是不是连续的Tensor,用is_contiguous方法
如果不是连续的Tensor,需要改变形状的话怎么办? 有多种方法,但是实际项目里我就推荐一种,reshape,对就是和numpy的reshape一样,不过它除了有reshape改view的功能,还集成了contiguous()方法,属于一步到位了
contiguous()再做view(), 这个时候就会产生一个新的Tensor,占用新的存储区域。
view经常的用法是确认一个或者几个维度,其他都用-1来自动生成,参加下图,我们先设定一个包含随机值,形状为[2,2,3,5]的4维Tensor
比如我想生成一个2维矩阵,那么我设定好了行的数量为2,其他的列的数量它自己生成
只想快速确认Tensor的维度,别的不关心,可以使用dim()
想求出Tensor中的最大最小的值,可以直接用Max,Min来给出答案
绝对值平均值也可以直接求得,这些方法就没必要一一罗列了,自己可以看一下方法的description
Transpose和Permute
不同于view的只是更改头区域的操作,这两个方法都是直接把Tensor给进行了轴的交换,或者叫转置,生成了完全不同的Tensor, 也会出现不连续的Tensor。Transpose和Permute的区别就是交换2个维度还是交换多个维度,为了省事,我们下面的实验就采用Transpose来演示。
首先我们定义一个2行三列的矩阵a,2维Tesnor也比较容易理解
然后我们对a先进行一个view的3行2列变换,得到矩阵b
最后我们对a进行Transpose的矩阵转置让行和列互换一下得到矩阵c
细心的读者其实已经发现了区别,同样是把一个2行3列的矩阵进行3行2列的变换,但是值的分布几乎完全不同,下图是Transpose的操作逻辑即轴交换,把x和y,即行和列完全颠倒生成新的Tensor
而view的操作逻辑(以2维矩阵为例子比较好理解),你可以认为,近似于先把Tensor码平,成为一行若干列的Tensor(参见上面的图9),然后按要求的形状,逐行去填写元素
我们刚才讲过Transpose和Permute会生成不连续的Tensor,我们现在验证一下
打印的结果证明了我们之前的观点,在Transpose之后生成的Tensor已经是不连续的了,至于为什么,这篇文章前面的章节讲过了原因,大家可以看一下
同样的这里可以用reshape()或者contiguous()方法,让Tensor变得连续(代价是存储占用变大,因为新生成了Tensor),以便后面的view操作。
关于最最基础的理论,就是通用定义部分到这篇截止就应该结束了,然而后面还有训练/推理时的理论和一些数学基础。有读者反映写得慢,有几个原因吧:
第一,我确实不是职业码字的,平时有正常工作要做,这是基础的时间占用问题,另外我手本来就慢,平均码个一两千字的文章可能要3天,还要demo和画图,又想认真写,边写边想,就会溜号想加点新的东西进来,经常改,所以就会慢。
第二,好饭不怕晚,而且我尽量写的细点,要不后面的东西实话说,真的很抽象,比如算Transformer 进来个embbeding的sequence,加上自注意力后,生成的多头QKV,是怎么转置相乘的(要是今天这个章节没看懂,那这块就是读不下去了),乘出来的东西又怎么进残差,怎么做Layer Normal,为什么要再进2层FFN,又为什么只有第一层有激活,第二层不激。我用这么复杂的排比句(可能算不上排比,我语文不好)不是为了吓退大家,坦率说,没有基础确实很难理解进去。如果要再结合硬件部分一起讲,那真就直接放弃了,而我们的Slogon就是入门到不想放弃,我不能砸了自己的招牌,所以基础这块内容付出多点时间也不过分。