深度学习 – 36.TF x Keras TF 常用矩阵计算方法大全

目录

一.引言

二.tf.multiply

1.常规乘法

2.乘以标量

3.不规则乘法

三.tf.matmul

1.常规矩阵相乘

2.多维矩阵相乘

四.tf.tensordot

1.axes=1

2.axes=N

3.axes=Tuple

4.axes=Array(Tuple())

五.K.dot

六.K.batch_dot

1.不指定 axes

2.指定 axes 为数字

3.指定 axes 为列表

4.二者维度不一致

七.tf.einsum

1.求和操作

2.矩阵乘法

3.向量对位相乘

4.向量 dot

5.全部求和


一.引言

开发深度学习任务时经常涉及到矩阵、向量的运算,例如 tf.multiply、tf.matmul、tf.tensordot、K.dot、K.batch_dot 以及 tf.einsum 等等,面对复杂的维度再加上五花八门的 API 时常处于眩晕状态,下面整理下常用矩阵计算方法示例。

二.tf.multiply

该方法用于计算哈达玛乘积,其实就是我们常说的对位相乘,其和 * 在一些场景下计算相同。给定两个向量,如果使用 multiply 就是对位相乘,如果是 dot 就是把对位相乘的结果加起来。其次如果维度的子集与待乘元素维度匹配,依旧可以对位相乘,所以该方法对维度要求较宽松。FM 的二阶项计算和平方与平方和就涉及到矩阵的对位相乘。

1.常规乘法

    mat1 = tf.reshape(tf.constant([1] * 12), [3, 4])
    mat2 = tf.reshape(tf.constant([2] * 12), [3, 4])

    print(mat1 * mat2)
    print(tf.multiply(mat1, mat2))

可以看到 multiply 与 * 结果相同,因为是对位相乘,所以 MxN * MxN => MxN 

tf.Tensor(
[[2 2 2 2]
 [2 2 2 2]
 [2 2 2 2]], shape=(3, 4), dtype=int32)
tf.Tensor(
[[2 2 2 2]
 [2 2 2 2]
 [2 2 2 2]], shape=(3, 4), dtype=int32)

2.乘以标量

    print(tf.multiply(mat1, 100))

标量直接作用与  MxN 的矩阵的每一个元素 Xij 

tf.Tensor(
[[100 100 100 100]
 [100 100 100 100]
 [100 100 100 100]], shape=(3, 4), dtype=int32)

3.不规则乘法

    mat2 = tf.reshape(tf.constant([2] * 12), [3, 4])
    mat3 = tf.constant([2] * 4)
    print(mat3)
    print(tf.multiply(mat2, mat3))

mat2 为 3x4,mat3 为 4,二者相乘的到 3x4。同理,如果前者为 MxNxK,后者为 NxK,multiply 会把 NxK 的部分对位相乘,然后得到 MxNxK。 

tf.Tensor([2 2 2 2], shape=(4,), dtype=int32)
tf.Tensor(
[[4 4 4 4]
 [4 4 4 4]
 [4 4 4 4]], shape=(3, 4), dtype=int32)

三.tf.matmul

matmul 矩阵常规计算语法

1.常规矩阵相乘

    mat1 = tf.reshape(tf.constant([1] * 12), [3, 4])
    mat2 = tf.reshape(tf.constant([2] * 12), [4, 3])
    print(tf.matmul(mat1, mat2))

matmul MxN NxK => MxK,前者的 dim[-1] 与后者的 dim[0] 匹配即可。

tf.Tensor(
[[8 8 8]
 [8 8 8]
 [8 8 8]], shape=(3, 3), dtype=int32)

2.多维矩阵相乘

    mat1 = tf.reshape(tf.constant([1] * 24), [2, 3, 4])
    mat2 = tf.reshape(tf.constant([2] * 24), [2, 4, 3])
    print(tf.matmul(mat1, mat2))

matmul MxNxK MxKxP => MxNxP,二者的首维相同,该方法主要用于两个 Batch 的样本执行矩阵计算,其中 M 看做 None,后面 matmul NxK KxP => NxP。FmFM 的 Fm 部门与 matrix 相乘就用到该方法。

tf.Tensor(
[[[8 8 8]
  [8 8 8]
  [8 8 8]]

 [[8 8 8]
  [8 8 8]
  [8 8 8]]], shape=(2, 3, 3), dtype=int32)

四.tf.tensordot

tf.tensordot 相对于前面的 multiply 或者 matmul 更加平凡或者说更加灵活,其提供 axes 参数,矩阵相乘只需 axes 指定的维度相等即可。

1.axes=1

默认 [-1,0],axes 代表前者的 dim[-1] 与后者的 dim[0] 对应,运算后对应维度消失。

    mat1 = tf.ones(shape=[2, 4, 3])
    mat2 = tf.ones(shape=[3, 2, 4])
    print(tf.tensordot(mat1, mat2, axes=1))

M x N x K * K x P x Q => M x N x P x Q,前后的 K 都消失。

tf.Tensor(
[[[[3. 3. 3. 3.]
   [3. 3. 3. 3.]]

  [[3. 3. 3. 3.]
   [3. 3. 3. 3.]]

  [[3. 3. 3. 3.]
   [3. 3. 3. 3.]]

  [[3. 3. 3. 3.]
   [3. 3. 3. 3.]]]


 [[[3. 3. 3. 3.]
   [3. 3. 3. 3.]]

  [[3. 3. 3. 3.]
   [3. 3. 3. 3.]]

  [[3. 3. 3. 3.]
   [3. 3. 3. 3.]]

  [[3. 3. 3. 3.]
   [3. 3. 3. 3.]]]], shape=(2, 4, 2, 4), dtype=float32)

2.axes=N

前者的后两维与后者的前两维相同,相乘后对应维度消失。

    mat1 = tf.ones(shape=[2, 4, 3])
    mat2 = tf.ones(shape=[3, 4, 4])
    print(tf.tensordot(mat1, mat2, axes=2))

看了 axes=1、2 的示例,这里也可以推广至 axes=N,大家可以自己尝试。 

tf.Tensor(
[[12. 12. 12. 12.]
 [12. 12. 12. 12.]], shape=(2, 4), dtype=float32)

3.axes=Tuple

Tuple(_.1,_.2) axes 为元组时,._1 代表前者维度索引,._2 代表后者维度索引,要求 _.1 = _.2,相乘后对应维度消失。

    mat1 = tf.ones(shape=[2, 4, 3])
    mat2 = tf.ones(shape=[3, 2, 4])
    print(tf.tensordot(mat1, mat2, axes=(1, 2)))

2x4x3 的索引1维度为 4,3x2x4的索引2维度为4,二者相乘对应维度消失其余保留得到 2x3x3x2。 

tf.Tensor(
[[[[4. 4.]
   [4. 4.]
   [4. 4.]]

  [[4. 4.]
   [4. 4.]
   [4. 4.]]

  [[4. 4.]
   [4. 4.]
   [4. 4.]]]


 [[[4. 4.]
   [4. 4.]
   [4. 4.]]

  [[4. 4.]
   [4. 4.]
   [4. 4.]]

  [[4. 4.]
   [4. 4.]
   [4. 4.]]]], shape=(2, 3, 3, 2), dtype=float32)

4.axes=Array(Tuple())

这个其实和 axes=1 推广至 axes=N 比较类似,这里要求前者的 ._1 * ._1 = ._2 * ._2 

    mat1 = tf.ones(shape=[2, 4, 3])
    mat2 = tf.ones(shape=[3, 2, 4])
    print(tf.tensordot(mat1, mat2, axes=((1, 2), (0, 2))))

上面说的比较抽象,下面玩点真实的,(1, 2)、(0, 2) 前者维度 4*3 = 12 后者维度 3*4 = 12,对应维度消失,得到 2*2。虽然理解用法了,但是这里维度太多还是不好想象出来真实变换场景。axes 方法在 DCN 构建 CrossLayer 时用到。

tf.Tensor(
[[12. 12.]
 [12. 12.]], shape=(2, 2), dtype=float32)

五.K.dot

广义矩阵相乘,这里限制较多且无法指定维度,要求前者 dim[-1] 与 后者 dim[-2] 相等,可以看做是 tf.tensordot 的特例,即 tf.tensordot(mat1, mat2, axes=(-1, -2))。通过其定义我们也可以看出其主要用于 TF lookup 得到向量后与参数矩阵相乘使用,第一个维度看做是 None,然后用后面的矩阵相乘。

    mat1 = tf.ones(shape=[2, 4, 3])
    mat2 = tf.ones(shape=[6, 3, 4])
    print(K.dot(mat1, mat2))
    print(tf.tensordot(mat1, mat2, axes=(-1, -2)))

2x4x3 的最后一维是3,6x3x4 的倒数第二维是3,相乘然后对位维度消失得到 2x4x6x4,大家可以用 tf.tensordot axes=(-1,-2) 验证数据是否一致。

tf.Tensor(
[[[[3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]]

  [[3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]]

  [[3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]]

  [[3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]]]


 [[[3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]]

  [[3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]]

  [[3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]]

  [[3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]
   [3. 3. 3. 3.]]]], shape=(2, 4, 6, 4), dtype=float32)

六.K.batch_dot

批量广义矩阵相乘,可以通过 axes 指定维度,两个输入的维度相等即可。如果不指定 axes,则类似 K.dot 要求 x_dim - 1 与 y_dim - 2 即前者倒1等于后者倒2。这里同样可以将 y_dim - 2 前面的维度看做 batch_size 的 None 从而实现 emb 与参数矩阵相乘,在 FwFM Fw 加权部分中用到了该方法。

1.不指定 axes

    mat1 = tf.ones(shape=[8, 4, 3])
    mat2 = tf.ones(shape=[8, 3, 4])
    print(K.batch_dot(mat1, mat2))

需要注意的是,这里相乘后不再是对位消失其余保留了即 8x4x8x4,而是8保留,4x3 与 3x4 执行矩阵计算,最后得到 8 x 4 x 4。换成符号语言就是 MxNxK 与 MxKxP 相乘的到 M x N x P,对应维度消失,首维度不变。

tf.Tensor(
[[[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]], shape=(8, 4, 4), dtype=float32)

2.指定 axes 为数字

    mat1 = tf.ones(shape=[8, 3, 4])
    mat2 = tf.ones(shape=[8, 3, 4])
    print(K.batch_dot(mat1, mat2, axes=1))

这里要 Dot 的 axes 会消失,即 3 消失 变成 8 x 4 x 4。

tf.Tensor(
[[[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]], shape=(8, 4, 4), dtype=float32)

3.指定 axes 为列表

   mat1 = tf.ones(shape=[8, 4, 3])
   mat2 = tf.ones(shape=[8, 3, 4]) 
   print(K.batch_dot(mat1, mat2, axes=[2, 1]))

结果以及计算思路与上面不指定 axes 相同。 

tf.Tensor(
[[[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]

 [[3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]
  [3. 3. 3. 3.]]], shape=(8, 4, 4), dtype=float32)

4.二者维度不一致

    mat1 = tf.ones(shape=[8, 4, 8])
    mat2 = tf.ones(shape=[8, 8])
    print(K.batch_dot(mat1, mat2))

mat1 看做是 lookup 的批次样本 None x Field x EmdDim 后者为参数矩阵 EmdDim x EmdDim,再次计算时保留 mat1 的 batch_size 即 None,这里会做 sumed 处理,所以最后得到 8 x 4。

tf.Tensor(
[[8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]], shape=(8, 4), dtype=float32)

这里不好理解的话也可以看下官方给的示例:

七.tf.einsum

爱因斯坦求和约定,这里使用更加 common 的表达式方式制定维度。

1.求和操作

    mat1 = tf.ones(shape=[8, 4, 8])
    mat2 = tf.einsum("ijk->ij", mat1)
    print(mat2)

相当于 reduce_sum,将给定矩阵的第三维累加起来,如果换做数学语言:

B_{ij} = sum_{k}^{}Aijk

即将 K 维的数字累加,如果换做是程序语言:

    i, j, k = mat1.shape[0], mat1.shape[1], mat1.shape[2]
    mat1 = np.ones(shape=[8, 4, 8])
    mat2 = np.zeros(shape=[i, j])
    for i_ in range(i):
        for j_ in range(j):
            for k_ in range(k):
                mat2[i_][j_] += mat1[i_][j_][k_]
    print(mat2)

对哪个 axis 求和,哪个维度消失,所以 8x4x8 对 ijk 的 k 即第三维求和所以剩 ij 即 8x4。

[[8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]
 [8. 8. 8. 8.]]

2.矩阵乘法

    mat1 = tf.ones(shape=[2, 10, 8])
    mat2 = tf.ones(shape=[8, 8])
    mat3 = tf.einsum("bij,mn->bin", mat1, mat2)
    print(mat3)

矩阵乘法 None[b] x Filed[i] x EmdDim[j] EmbDim[j] x EmdDim[k] => None x Filed x EmdDim [bik]

相当于 matmul,数学语言:

C_{bin}=sum_{j} sum_{m} A_{bij} * B_{mn}

消失的维度放到求和符号中,剩下的维度保留,最终维度 2 x 10 x 8,程序语言:

    b, i, j = mat1.shape[0], mat1.shape[1], mat1.shape[2]
    m, n = mat2.shape[0], mat2.shape[1]
    mat3 = np.zeros(shape=[b, i, j])
    for b_ in range(b):
        for i_ in range(i):
            for j_ in range(j):
                for m_ in range(m):
                    for n_ in range(n):
                        mat3[b_][i_][j_] += mat1[b_][i_][j_] * mat2[m_][n_]
    print(mat3)
tf.Tensor(
[[[64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]]

 [[64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]
  [64. 64. 64. 64. 64. 64. 64. 64.]]], shape=(2, 10, 8), dtype=float32)

3.向量对位相乘

    vec1 = tf.constant([1] * 12)
    vec2 = tf.constant([2] * 12)
    vec3 = tf.einsum("i,i->i", vec1, vec2)
    print(vec3)

 i,i -> i 实现 mutiply 对位相乘

tf.Tensor([2 2 2 2 2 2 2 2 2 2 2 2], shape=(12,), dtype=int32)

4.向量 dot

    vec1 = tf.constant([1] * 12)
    vec2 = tf.constant([2] * 12)
    vec3 = tf.einsum("i,i->", vec1, vec2)
    print(vec3)

i,i -> 实现向量内积

tf.Tensor(24, shape=(), dtype=int32)

5.全部求和

    mat1 = tf.ones(shape=[2, 10, 8])
    sum_ = tf.einsum("ijk->", mat1)
    print(sum_)

不管多少维,-> 后面均不指定,最终得到求和结果。

tf.Tensor(160.0, shape=(), dtype=float32)

Tips:

上面展示了一些常规的使用方法,具体到实战中可以使用不同语法实现相同功能,更完整的功能介绍与维度变换打击可以直接看官方源码,都有完整的解释与示例:

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>