【白话机器学习系列】白话张量

白话张量

张量(Tensor)是向量和矩阵向 n n n 维的推广。向量是一维张量,矩阵是二维张量。张量作为数值容器,是机器学习,尤其是深度学习中最基础的操作对象,以至于 Google 的机器学习框架都以 TensorFlow 来命名,足见张量在机器学习中多么重要。不夸张地说,了解张量如何相互作用是机器学习的基本功。本文将用通俗的语言和图例,配合 PyTorch 中张量操作方法为大家深入浅出地讲解张量的必要知识。

在这里插入图片描述

概览

我们先从直观上对张量建立认识。尽管张量看起来很复杂,但我们可以将张量理解为向量和矩阵的集合。向量和矩阵对我们来说更熟悉一些,其实张量不过是向量和矩阵向 n n n 维的推广——向量是一维张量,矩阵是二维张量。理解向量和矩阵对于理解张量至关重要。

向量是元素的一维列表:
x ⃗ = [ x 0 , x 1 , … , x n ] \vec{x} = [x_0, x_1, \dots, x_n] x =[x0,x1,,xn]
矩阵是向量的二维列表:
[ [ x 0 , 0 , x 0 , 1 , x 0 , 2 ] [ x 1 , 0 , x 1 , 1 , x 1 , 2 ] [ x 2 , 0 , x 2 , 1 , x 2 , 2 ] ] = [ x 0 ⃗ x 1 ⃗ x 2 ⃗ ] = X \begin{bmatrix} [x_{0,0}, x_{0,1}, x_{0,2}] \\ [x_{1,0}, x_{1,1}, x_{1,2}] \\ [x_{2,0}, x_{2,1}, x_{2,2}] \\ \end{bmatrix} = \begin{bmatrix} \vec{x_0}\\ \vec{x_1}\\ \vec{x_2}\\ \end{bmatrix} = X [x0,0,x0,1,x0,2][x1,0,x1,1,x1,2][x2,0,x2,1,x2,2]=x0 x1 x2 =X
上面公式的下标表示(行,列)。矩阵的另一种方法是将向量作为元素的向量。矩阵通常用大写字母表示。

三维张量可以视为是矩阵的三维列表:
[ [ [ x 0 , 0 , x 0 , 1 , x 0 , 2 ] [ x 1 , 0 , x 1 , 1 , x 1 , 2 ] [ x 2 , 0 , x 2 , 1 , x 2 , 2 ] ] [ [ x 0 , 0 , x 0 , 1 , x 0 , 2 ] [ x 1 , 0 , x 1 , 1 , x 1 , 2 ] [ x 2 , 0 , x 2 , 1 , x 2 , 2 ] ] [ [ x 0 , 0 , x 0 , 1 , x 0 , 2 ] [ x 1 , 0 , x 1 , 1 , x 1 , 2 ] [ x 2 , 0 , x 2 , 1 , x 2 , 2 ] ] ] = [ [ x 0 ⃗ x 1 ⃗ x 2 ⃗ ] [ x 0 ⃗ x 1 ⃗ x 2 ⃗ ] [ x 0 ⃗ x 1 ⃗ x 2 ⃗ ] ] = [ X 0 X 1 X 2 ] = χ \begin{bmatrix} \begin{bmatrix} [x_{0,0}, x_{0,1}, x_{0,2}] \\ [x_{1,0}, x_{1,1}, x_{1,2}] \\ [x_{2,0}, x_{2,1}, x_{2,2}] \\ \end{bmatrix} \\ \begin{bmatrix} [x_{0,0}, x_{0,1}, x_{0,2}] \\ [x_{1,0}, x_{1,1}, x_{1,2}] \\ [x_{2,0}, x_{2,1}, x_{2,2}] \\ \end{bmatrix} \\ \begin{bmatrix} [x_{0,0}, x_{0,1}, x_{0,2}] \\ [x_{1,0}, x_{1,1}, x_{1,2}] \\ [x_{2,0}, x_{2,1}, x_{2,2}] \\ \end{bmatrix} \\ \end{bmatrix} = \begin{bmatrix} \begin{bmatrix} \vec{x_0}\\ \vec{x_1}\\ \vec{x_2}\\ \end{bmatrix} \\ \begin{bmatrix} \vec{x_0}\\ \vec{x_1}\\ \vec{x_2}\\ \end{bmatrix} \\ \begin{bmatrix} \vec{x_0}\\ \vec{x_1}\\ \vec{x_2}\\ \end{bmatrix} \\ \end{bmatrix} = \begin{bmatrix} X_0\\ X_1\\ X_2\\ \end{bmatrix} = \chi [x0,0,x0,1,x0,2][x1,0,x1,1,x1,2][x2,0,x2,1,x2,2][x0,0,x0,1,x0,2][x1,0,x1,1,x1,2][x2,0,x2,1,x2,2][x0,0,x0,1,x0,2][x1,0,x1,1,x1,2][x2,0,x2,1,x2,2]=x0 x1 x2 x0 x1 x2 x0 x1 x2 =X0X1X2=χ
三维张量的另一种方法是将矩阵作为元素的向量。

四维张量可以被认为是三维张量的四维列表。将四维张量展开写出来会非常展版面,这里我只写出矩阵向量的向量和三维张量向量形式。
[ [ X 0 X 1 X 2 ] [ X 0 X 1 X 2 ] [ X 0 X 1 X 2 ] ] = [ χ 0 χ 1 χ 2 ] = T 4 D \begin{bmatrix} \begin{bmatrix} X_0\\ X_1\\ X_2\\ \end{bmatrix} \\ \begin{bmatrix} X_0\\ X_1\\ X_2\\ \end{bmatrix} \\ \begin{bmatrix} X_0\\ X_1\\ X_2\\ \end{bmatrix} \\ \end{bmatrix} = \begin{bmatrix} \chi_0\\ \chi_1\\ \chi_2\\ \end{bmatrix} = T_{4D} X0X1X2X0X1X2X0X1X2=χ0χ1χ2=T4D
运用类似的原理,我们可以递归的写出任意维张量。

向量运算

设有两个长度为 i i i 的向量 x ⃗ , y ⃗ \vec{x}, \vec{y} x ,y
x ⃗ = [ x 0 , x 1 , … , x i ] y ⃗ = [ y 0 , y 1 , … , y i ] \vec{x} = [x_0, x_1, \dots, x_i]\\ \vec{y} = [y_0, y_1, \dots, y_i] x =[x0,x1,,xi]y =[y0,y1,,yi]
我们可以 x ⃗ , y ⃗ \vec{x}, \vec{y} x ,y 上定义加法、减法、标量乘法、点积、哈达玛积等一系列运算。这些运算主要针对向量中的元素进行的,也就是说两个向量中的对应元素之间进行计算。

加法

向量加法定义如下:
x ⃗ + y ⃗ = [ x 0 , x 1 , … , x i ] + [ y 0 , y 1 , … , y i ] = [ x 0 + y 0 , x 1 + y 1 , … , x i + y i ] \vec{x} + \vec{y} = [x_0, x_1, \dots, x_i] + [y_0, y_1, \dots, y_i] = [x_0+y_0, x_1+y_1, \dots, x_i+y_i] x +y =[x0,x1,,xi]+[y0,y1,,yi]=[x0+y0,x1+y1,,xi+yi]
直白的说就是将向量中的对应元素相加。

在 PyTorch 中可以直接用 + 实现张量相加:

import torch

x = torch.tensor([1, 3, 5])
y = torch.tensor([3, 7, 4])

x + y
# Output: tensor([ 4, 10,  9])

减法

向量加法定义如下:
x ⃗ − y ⃗ = [ x 0 , x 1 , … , x i ] − [ y 0 , y 1 , … , y i ] = [ x 0 − y 0 , x 1 − y 1 , … , x i − y i ] \vec{x} - \vec{y} = [x_0, x_1, \dots, x_i] - [y_0, y_1, \dots, y_i] = [x_0-y_0, x_1-y_1, \dots, x_i-y_i] x y =[x0,x1,,xi][y0,y1,,yi]=[x0y0,x1y1,,xiyi]
跟向量加法类似,向量减法就是向量中的对应元素相减。

在 PyTorch 中可以直接用 - 实现张量相加:

x = torch.tensor([1, 3, 5])
y = torch.tensor([3, 7, 4])

x - y
# Output: tensor([-2, -4,  1])

标量乘法

标量乘法是将一个标量 k k k 与向量中的每一个元素相乘,其定义如下:
k x ⃗ = k [ x 0 , x 1 , … , x i ] = [ k x 0 , k x 1 , … , k x i ] k\vec{x} = k[x_0, x_1, \dots, x_i] = [kx_0, kx_1, \dots, kx_i] kx =k[x0,x1,,xi]=[kx0,kx1,,kxi]
标量乘法有点像乘法分配律,其中标量 k k k 一般为实数。

在 PyTorch 中可以直接用 * 实现标量与张量相乘:

k = 5
x = torch.tensor([1, 3, 5])

k * x
# Output: tensor([ 5, 15, 25])

点积(Dot Product)

点积我在上一篇文章 《白话点积》 中有详细介绍,这里不过多赘述。其定义为:
x ⃗ ⋅ y ⃗ = [ x 0 , x 1 , … , x i ] ⋅ [ y 0 , y 1 , … , y i ] = x 0 y 0 + x 1 y 1 + ⋯ + x i y i = ∑ n = 0 i x n y n \vec{x} \sdot \vec{y} = [x_0, x_1, \dots, x_i] \sdot [y_0, y_1, \dots, y_i] = x_0y_0+ x_1y_1+ \dots + x_iy_i = \sum_{n=0}^ix_ny_n x y =[x0,x1,,xi][y0,y1,,yi]=x0y0+x1y1++xiyi=n=0ixnyn
在 PyTorch 中可以用 dot 方法求两个张量的点积:

x = torch.tensor([1, 3, 5])
y = torch.tensor([3, 7, 4])

torch.dot(x, y) # 1*3 + 3*7 + 5*4 = 3 + 21 + 20 = 44
# Output: tensor(44)

PyTorch 中点积还可以用更直观的 @ 运算符来计算,因此上面的代码还可以写成

x @ y # 与 torch.dot(x, y) 等价

注意,点积的结果是一个标量。

哈达玛积(Hadamard Product)

哈达玛积与点积类似,将向量各对应元素相乘,与点积不同的是,哈达玛积不将对应元素的相乘结果累计,而是返回对应元素相乘结果的向量。哈达玛积的定义如下:
x ⃗ ⊙ y ⃗ = [ x 0 , x 1 , … , x i ] ⊙ [ y 0 , y 1 , … , y i ] = [ x 0 y 0 , x 1 y 1 , … , x i y i ] \vec{x} \odot \vec{y} = [x_0, x_1, \dots, x_i] \odot [y_0, y_1, \dots, y_i] = [x_0y_0, x_1y_1, \dots, x_iy_i] x y =[x0,x1,,xi][y0,y1,,yi]=[x0y0,x1y1,,xiyi]
在 PyTorch 中可以用 * 计算两个张量的哈达玛积:

x = torch.tensor([1, 3, 5])
y = torch.tensor([3, 7, 4])

x * y
# Output: tensor([ 3, 21, 20])

矩阵乘法

矩阵是向量的集合,因此矩阵的加法、减法、标量乘法和哈达玛积与向量是一样的,都是对应元素计算即可。不同的是点积。前面强调过向量的点积是一个标量,而矩阵的点积不是这样。

X , Y X, Y X,Y 是 2 个 4 × 3 4 \times 3 4×3 的矩阵:
X = [ [ x 0 , 0 , x 0 , 1 , x 0 , 2 ] [ x 1 , 0 , x 1 , 1 , x 1 , 2 ] [ x 2 , 0 , x 2 , 1 , x 2 , 2 ] [ x 3 , 0 , x 3 , 1 , x 3 , 2 ] ] Y = [ [ y 0 , 0 , y 0 , 1 , y 0 , 2 ] [ y 1 , 0 , y 1 , 1 , y 1 , 2 ] [ y 2 , 0 , y 2 , 1 , y 2 , 2 ] [ y 3 , 0 , y 3 , 1 , y 3 , 2 ] ] X = \begin{bmatrix} [x_{0,0}, x_{0,1}, x_{0,2}] \\ [x_{1,0}, x_{1,1}, x_{1,2}] \\ [x_{2,0}, x_{2,1}, x_{2,2}] \\ [x_{3,0}, x_{3,1}, x_{3,2}] \\ \end{bmatrix}\\ Y = \begin{bmatrix} [y_{0,0}, y_{0,1}, y_{0,2}] \\ [y_{1,0}, y_{1,1}, y_{1,2}] \\ [y_{2,0}, y_{2,1}, y_{2,2}] \\ [y_{3,0}, y_{3,1}, y_{3,2}] \\ \end{bmatrix}\\ X=[x0,0,x0,1,x0,2][x1,0,x1,1,x1,2][x2,0,x2,1,x2,2][x3,0,x3,1,x3,2]Y=[y0,0,y0,1,y0,2][y1,0,y1,1,y1,2][y2,0,y2,1,y2,2][y3,0,y3,1,y3,2]
矩阵乘法定义如下:
X Y = [ x 0 , 0 x 0 , 1 x 0 , 2 x 1 , 0 x 1 , 1 x 1 , 2 x 2 , 0 x 2 , 1 x 2 , 2 x 3 , 0 x 3 , 1 x 3 , 2 ] [ y 0 , 0 y 0 , 1 y 0 , 2 y 1 , 0 y 1 , 1 y 1 , 2 y 2 , 0 y 2 , 1 y 2 , 2 y 3 , 0 y 3 , 1 y 3 , 2 ] T = [ x 0 , 0 x 0 , 1 x 0 , 2 x 1 , 0 x 1 , 1 x 1 , 2 x 2 , 0 x 2 , 1 x 2 , 2 x 3 , 0 x 3 , 1 x 3 , 2 ] [ y 0 , 0 y 1 , 0 y 2 , 0 y 3 , 0 y 0 , 1 y 1 , 1 y 2 , 1 y 3 , 1 y 0 , 2 y 1 , 2 y 2 , 2 y 3 , 2 ] = [ x 0 , 0 y 0 , 0 + x 0 , 1 y 0 , 1 + x 0 , 2 y 0 , 2 x 0 , 0 y 1 , 0 + x 0 , 1 y 1 , 1 + x 0 , 2 y 1 , 2 x 0 , 0 y 2 , 0 + x 0 , 1 y 2 , 1 + x 0 , 2 y 2 , 2 x 0 , 0 y 3 , 0 + x 0 , 1 y 3 , 1 + x 0 , 2 y 3 , 2 x 1 , 0 y 0 , 0 + x 1 , 1 y 0 , 1 + x 1 , 2 y 0 , 2 x 1 , 0 y 1 , 0 + x 1 , 1 y 1 , 1 + x 1 , 2 y 1 , 2 x 1 , 0 y 2 , 0 + x 1 , 1 y 2 , 1 + x 1 , 2 y 2 , 2 x 1 , 0 y 3 , 0 + x 1 , 1 y 3 , 1 + x 1 , 2 y 3 , 2 x 2 , 0 y 0 , 0 + x 2 , 1 y 0 , 1 + x 2 , 2 y 0 , 2 x 2 , 0 y 1 , 0 + x 2 , 1 y 1 , 1 + x 2 , 2 y 1 , 2 x 2 , 0 y 2 , 0 + x 2 , 1 y 2 , 1 + x 2 , 2 y 2 , 2 x 2 , 0 y 3 , 0 + x 2 , 1 y 3 , 1 + x 2 , 2 y 3 , 2 x 3 , 0 y 0 , 0 + x 3 , 1 y 0 , 1 + x 3 , 2 y 0 , 2 x 3 , 0 y 1 , 0 + x 3 , 1 y 1 , 1 + x 3 , 2 y 1 , 2 x 3 , 0 y 2 , 0 + x 3 , 1 y 2 , 1 + x 3 , 2 y 2 , 2 x 3 , 0 y 3 , 0 + x 3 , 1 y 3 , 1 + x 3 , 2 y 3 , 2 ] \begin{aligned} XY &= \begin{bmatrix} x_{0,0}& x_{0,1}& x_{0,2} \\ x_{1,0}& x_{1,1}& x_{1,2} \\ x_{2,0}& x_{2,1}& x_{2,2} \\ x_{3,0}& x_{3,1}& x_{3,2} \\ \end{bmatrix}\begin{bmatrix} y_{0,0}& y_{0,1}& y_{0,2} \\ y_{1,0}& y_{1,1}& y_{1,2} \\ y_{2,0}& y_{2,1}& y_{2,2} \\ y_{3,0}& y_{3,1}& y_{3,2} \\ \end{bmatrix}^T\\ &=\begin{bmatrix} x_{0,0}& x_{0,1}& x_{0,2} \\ x_{1,0}& x_{1,1}& x_{1,2} \\ x_{2,0}& x_{2,1}& x_{2,2} \\ x_{3,0}& x_{3,1}& x_{3,2} \\ \end{bmatrix}\begin{bmatrix} y_{0,0}& y_{1,0}& y_{2,0} & y_{3,0} \\ y_{0,1}& y_{1,1}& y_{2,1} & y_{3,1} \\ y_{0,2}& y_{1,2}& y_{2,2} & y_{3,2} \\ \end{bmatrix}\\ &=\begin{bmatrix} x_{0,0}y_{0,0}+x_{0,1}y_{0,1}+x_{0,2}y_{0,2}&x_{0,0}y_{1,0}+x_{0,1}y_{1,1}+x_{0,2}y_{1,2} & x_{0,0}y_{2,0}+x_{0,1}y_{2,1}+x_{0,2}y_{2,2}& x_{0,0}y_{3,0}+x_{0,1}y_{3,1}+x_{0,2}y_{3,2} \\ x_{1,0}y_{0,0}+x_{1,1}y_{0,1}+x_{1,2}y_{0,2}&x_{1,0}y_{1,0}+x_{1,1}y_{1,1}+x_{1,2}y_{1,2} & x_{1,0}y_{2,0}+x_{1,1}y_{2,1}+x_{1,2}y_{2,2}& x_{1,0}y_{3,0}+x_{1,1}y_{3,1}+x_{1,2}y_{3,2} \\ x_{2,0}y_{0,0}+x_{2,1}y_{0,1}+x_{2,2}y_{0,2}&x_{2,0}y_{1,0}+x_{2,1}y_{1,1}+x_{2,2}y_{1,2} & x_{2,0}y_{2,0}+x_{2,1}y_{2,1}+x_{2,2}y_{2,2}& x_{2,0}y_{3,0}+x_{2,1}y_{3,1}+x_{2,2}y_{3,2} \\ x_{3,0}y_{0,0}+x_{3,1}y_{0,1}+x_{3,2}y_{0,2}&x_{3,0}y_{1,0}+x_{3,1}y_{1,1}+x_{3,2}y_{1,2} & x_{3,0}y_{2,0}+x_{3,1}y_{2,1}+x_{3,2}y_{2,2}& x_{3,0}y_{3,0}+x_{3,1}y_{3,1}+x_{3,2}y_{3,2} \end{bmatrix} \end{aligned} XY=x0,0x1,0x2,0x3,0x0,1x1,1x2,1x3,1x0,2x1,2x2,2x3,2y0,0y1,0y2,0y3,0y0,1y1,1y2,1y3,1y0,2y1,2y2,2y3,2T=x0,0x1,0x2,0x3,0x0,1x1,1x2,1x3,1x0,2x1,2x2,2x3,2y0,0y0,1y0,2y1,0y1,1y1,2y2,0y2,1y2,2y3,0y3,1y3,2=x0,0y0,0+x0,1y0,1+x0,2y0,2x1,0y0,0+x1,1y0,1+x1,2y0,2x2,0y0,0+x2,1y0,1+x2,2y0,2x3,0y0,0+x3,1y0,1+x3,2y0,2x0,0y1,0+x0,1y1,1+x0,2y1,2x1,0y1,0+x1,1y1,1+x1,2y1,2x2,0y1,0+x2,1y1,1+x2,2y1,2x3,0y1,0+x3,1y1,1+x3,2y1,2x0,0y2,0+x0,1y2,1+x0,2y2,2x1,0y2,0+x1,1y2,1+x1,2y2,2x2,0y2,0+x2,1y2,1+x2,2y2,2x3,0y2,0+x3,1y2,1+x3,2y2,2x0,0y3,0+x0,1y3,1+x0,2y3,2x1,0y3,0+x1,1y3,1+x1,2y3,2x2,0y3,0+x2,1y3,1+x2,2y3,2x3,0y3,0+x3,1y3,1+x3,2y3,2
上面的过程虽然复杂,其实如果我们将矩阵看作是由向量组成的,上面的运算过程其实就是将组成矩阵的向量两两相乘,运算结果就是结果矩阵的一个元素。我们定义矩阵乘法的运算规则为用第一个矩阵的行乘以第二个矩阵的列,因此只有当第一个矩阵中的行数与第二个矩阵的列数匹配时,才能进行乘法运算。为了满足运算规则,所以我们将矩阵 Y Y Y 转置了一下( T ^T T 是转置运算符),使其变成 3 × 4 3 \times 4 3×4 矩阵,这样两个矩阵的行列元素才能对应上。
X Y = ( 4 , 3 ) × ( 4 , 3 ) T = ( 4 , 3 ) × ( 3 , 4 ) = ( 4 , 4 ) \begin{aligned} XY &= (4,3) \times (4,3)^T\\ &=(4,3) \times (3,4)\\ &=(4,4) \end{aligned} XY=(4,3)×(4,3)T=(4,3)×(3,4)=(4,4)
因此我们可以总结出矩阵乘法的一半要求:
( m , n ) × ( n , r ) = ( m , r ) (m, n) \times (n, r) = (m, r) (m,n)×(n,r)=(m,r)
在 PyTorch 中可以用 matmul 方法对两个矩阵进行乘法运算:

X = torch.tensor([[1, 3, 5],
                  [3, 6, 1],
                  [6, 8, 5],
                  [7, 3, 2]])

Y = torch.tensor([[6, 3, 2],
                  [8, 5, 4],
                  [3, 1, 7],
                  [1, 8, 3]])

X.matmul(Y.T)
# Output: tensor([[ 25,  43,  41,  40],
#        		  [ 38,  58,  22,  54],
#        		  [ 70, 108,  61,  85],
#        		  [ 55,  79,  38,  37]])

其中 Y.T 是转置操作。矩阵乘法也可以用 X @ Y.T 来实现。

张量乘法

理解了矩阵乘法后,我们再来看一下更高维度的张量如何相乘。

矩阵乘法我们可以简单地总结为行乘以列,放到更高维的张量中,除了行与列还有其他维度,甚至何为行、何为列都很模糊。那么张量应该如何相乘呢?

其实运用类比思维,这个问题也好理解。我们将矩阵视为向量的集合,三维张量视为矩阵的集合。因此对于三维张量来说,就是张量中的矩阵两两相乘,矩阵相乘我们前面已经介绍过,只需按照矩阵乘法的运算规则进行计算即可。下图可以更清晰地展示这个过程:

在这里插入图片描述

矩阵运算需要满足 ( m , n ) × ( n , r ) = ( m , r ) (m, n) \times (n, r) = (m, r) (m,n)×(n,r)=(m,r) ,三维张量视为矩阵的集合,我们只需要将张量中的矩阵转置然后两两相乘即可,因此三维张量相乘只需要保证第一个维度不变,让剩下的两个维度满足矩阵乘法的运算要求即可:
( z , m , n ) × ( z , n , r ) = ( z , m , r ) (z, m, n) \times (z, n, r) = (z, m, r) (z,m,n)×(z,n,r)=(z,m,r)
类似的思想可以扩展到四维张量上,四维张量是三维张量的集合,因此四维张量相乘就是将三维张量两两相乘,三维张量相乘再视为其包含的矩阵两两相乘。从某种意义上说,张量相乘就像一个嵌套的点积。
( c , z , m , n ) × ( c , z , n , r ) = ( c , z , m , r ) (c, z, m, n) \times (c, z, n, r) = (c, z, m, r) (c,z,m,n)×(c,z,n,r)=(c,z,m,r)
这个思想递归下去可以推出任意维张量乘法。

代码上,我们可以用 transpose() 方法传入要转置的2个维度

X.matmul(Y.transpose(1,2))

总结

张量运算是机器学习中最常用到的操作,理解张量对每一个机器学习工程师都直观重要。上面内容为了简单易懂可能在数学上有失严谨,全面严谨的讲解建议大家系统性地学习一下线性代数,这里推荐 Gilbert Strang 老爷子的 Introduction to Linear Algebra,这本书是我读过对初学者最友好的线性代数书。

猜你喜欢

转载自blog.csdn.net/jarodyv/article/details/131002259