医工荟萃,不是萝卜开会,融合创新才是硬道理!
预计阅读时间: 6 分钟
上一讲荟荟讲了一下张量切片、逐元素运算、广播,这一讲再说说张量点积和张量变形,集齐这五种装备,基本可以开始愉快地玩耍了。
张量点积
前面我们说过逐元素运算,要将两个张量进行逐元素相乘,我们使用“*“运算符,比如将两个矩阵A和B进行逐元素相乘
而在神经网络中,经常需要类似图1的乘法方式,我们将输入向量和权值向量的对应元素进行相乘后再求和,这种方式我们称之为张量点积。
如图2所示,如果有多个神经元需要输出,则可以把输入X写成一个列向量,它的转置为
输入权值矩阵可写为
该矩阵的每一列,代表输入到中间层某一个神经元的权值向量,如下图中输入到z2的权值正是上述矩阵的第二列。从图中可以看出,我们得到的中间层神经元的输出(这里为了简单起见省略了非线性激活函数,如ReLU)也是一个列向量,如下:
其中的第n个元素正是输入的每个元素和W的第n列对应元素一一相乘并相加的结果。如果向更高维度扩展,输入不是一个向量,而是一个矩阵,比如我们将100个输入样本向量输入刚才讨论的神经网络,那么XT的形状就是(100,3),ZT的形状就是(100,4)。
看到这里,学过线性代数的童鞋可能已经看出,这不就是我们学过的矩阵乘法嘛,没错,这里说的张量点积,如果是2d张量(矩阵),就是我们最常见的矩阵乘法,语法如下。
只不过它还可以继续拓展,比如要在神经网络里处理视频数据,形状通常是(帧,长,宽,颜色),如果需要进行点积(对应元素相乘相加),这时候如果有一个底层优化过的张量点积运算,我们就可以保持其他维度不变,直接用一次dot把所有帧和颜色的张量都进行相同的点积运算,而按照传统方法我们则需要进行多重循环。
在Numpy中,如果A为M维张量,B为N为张量,两个张量的点积就是将A张量的最后一个轴中的所有元素,与B张量中倒数第二个轴的所有元素对应相乘后相加的结果,也就是dot(a, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m])。有点晕菜对不对,下面用矩阵来解释一下,如图3所示,矩阵x的第一个轴是行,第二个轴是列,矩阵一共就两个轴,所以x的最后一个轴就是列,同理y的倒数第二个轴是行。可见矩阵x和y的点积就是将x第i行中的所有列(x的最后一个轴)和y第j列中的所有行(y的倒数第二个轴)对应相乘并相加的结果返回给z(i,j)。从这个例子中也可以发现,这就要求x的列数必须等于y的行数,也就是A张量最后一个轴的元素数量必须等于B张量倒数第二个轴中的元素数量。
那么张量点积后的形状和输入张量形状的关系又是什么呢,以一个例子来说明一下,如果A的形状为(a,b,c,d),B的形状为(d,e),那么numpy.dot(A,B)的形状为(a,b,c,e)。如果A的形状为(a,b,c,d), B的形状为(c,d),那么就呵呵了,因为A的最后一个轴元素数量为d,而B的倒数第二个轴元素数量为c,不匹配,会报错。
下面给出了一个矩阵乘法的实际案例,小伙伴们去仔细研究一下吧。
此外,Numpy还提供了一种更为灵活的张量点乘函数numpy.tensordot(a,b,axes),可以指定需要和并的轴,但是不太容易理解,这里就不写出来混淆试听了,有兴趣的朋友们可以看看Chenxiao Ma的博客,写的还挺清楚的https://www.machenxiao.com/blog/tensordot
对于初学者来说知道numpy.dot就是求矩阵乘法也基本够用了。
张量变形
- reshape
张量变形其实咱们在第二讲中已经见过,当时我们需要把一个形状为(60000,28,28)的手写字符图像张量输入一个中间层为784个神经元的密集连接型神经网络。所以需要将这个张量变形为(60000,28*28)的形状,才好和后面的全连接神经网络进行加权点乘。当时我们的预处理代码是:
train_images =train_images.reshape((60000, 28 * 28))
reshape函数很简单,输入参数就是你想要的新形状,只要新张量中的元素个数和原始张量中的元素个数是一样的即可。元素的安排顺序默认和c语言是一致的,即依次进行排列。下面两个例子可以让你清楚地理解这一操作。
- 转置Transpose
另一种常见的张量变形就是转置了,即把矩阵的行换成列,列换成行,也就是x[i,:]变为x[:,i],语法为np.transpose(x)。
到这里,张量最常见的几种常见的操作和运算算是告一段落,总结一下本讲的内容:1. 张量点积(相乘相加,2D张量就是矩阵乘法);2. 张量变形(张量在神经网络逐层传播,每一层的输入和输出形状有可能发生变化,需要变形加以适应)。下一讲我们会从卷积神经网络开始正式进入深度学习的实际操作,过程中我们会反复操练这些张量操作方法。