1.tensorflow 有一种 kernels 名为 BinaryOp(二元操作), 像加法, 减法, 哈达玛积这种都属于二元操作(都是简单的数学操作), 所有的二元操作逻辑都是相同的, 唯独中间的运算符不同, 在实现上将这个操作符作为模板参数传入, 本质也是调用的 Eigen 的 API(但是像矩阵乘这样的感觉也是二元操作, 但是实现上却不同与二元操作, 主要是调用了不同的 Eigen API).
二元操作要求两个参数有相同的维度, 比如矩阵相加, 相减. 但是深度学习中有一种典型的应用: 梯度更新的时候, 以 SGD 为例, w-grad_w*lr, 其中 w 一般都是多维的, 而 lr 都是常数, 相当于一个常数乘以一个多维张量. 这个操作也属于二元操作, 看似很简单, 但是却需要二元操作有足够的支持, 这种维数不匹配的解决方法称为 broadcast, 简单理解就是维数扩展.
tensorflow 在实现 broadcast 的时候, 借鉴了 numpy 的 broadcast 方法, 也不是借鉴, 就是直接拿过来用了. 其 broadcast 的实现文件在 core/util/bcast.h 中.
broadcast 的原理很简单, 就是维数不匹配的时候, 将两个张量的其中之一或者两个都进行维数的扩展, 举个例子:
- a = [2 3 4]
- b = 2
c = a*b 等价于 c = [2 3 4]*[2 2 2]
也就是说看似 a 是和 2 相乘, 实际上是和 [2 2 2] 相乘的, 也就是说要事先对 b 进行维数扩展, 扩展成和 a 相同维数才能进行哈达玛积的操作 .
注: 在具体实现扩展的时候, 绝不是将 2 变为[2 2 2], 这样浪费内存效率差, 实际在内存中还是只有一份 2 存在, 只不过我像上面那样讲可以方便理解. 像 tensorflow 在实现 broadcast 的时候调用了 Eigen 的 broadcast 方法, 其内部实现用到了线程池或者 cuda 实现并发.
- a = [[0 1 2]]
- b = a'c = a*a'
- 0 1 2 0 0 0 0 0 0
等价于 c = 0 1 2 * 1 1 1 = 0 1 2
0 1 2 2 2 2 0 2 4
正常来说 a 的维数是 [1,3],b 的维数是[3,1], 二者是不能做 * 操作的. 但是由于 broadcast 的存在, 先将两个输入都变成了[3,3] 维度的, 二者就可以运算了.
具体的 broadcast 规则可以参考 https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html .
其核心主要有两点:
后缀匹配: 对两个输入的维数从后向前匹配, 二者若兼容, 就对其中一个输入的该维度进行 broadcast, 不兼容就表示二者不能做二元操作.
什么情况算作维数兼容: 维数相等或者其中一个是 1. 并且如果其中一个是 1, 那么最终的输出在该维度上的值就是另一个输入的维度.
以下是官方的例子:
- A (2d array): 5 x 4
- B (1d array): 1
- Result (2d array): 5 x 4
- A (2d array): 5 x 4
- B (1d array): 4
- Result (2d array): 5 x 4
- A (3d array): 15 x 3 x 5
- B (3d array): 15 x 1 x 5
- Result (3d array): 15 x 3 x 5
- A (3d array): 15 x 3 x 5
- B (2d array): 3 x 5
- Result (3d array): 15 x 3 x 5
- A (3d array): 15 x 3 x 5
- B (2d array): 3 x 1
- Result (3d array): 15 x 3 x 5
不兼容的例子:
- A (1d array): 3
- B (1d array): 4 # trailing dimensions do not match
- A (2d array): 2 x 1
- B (3d array): 8 x 4 x 3 # second from last dimensions mismatched
来源: http://www.bubuko.com/infodetail-2681455.html