我有一些input_x
代表的数据。它是一个未知大小的张量(应该是批量输入),每个项目的大小为n
。 input_x
经历了tf.nn.embedding_lookup
,因此embed
现在具有维度[?, n, m]
,其中m
是嵌入大小,?
是指未知的批量大小。
这里描述:
input_x = tf.placeholder(tf.int32, [None, n], name="input_x")
embed = tf.nn.embedding_lookup(W, input_x)
我现在正试图将我的输入数据中的每个样本(现在通过嵌入维度扩展)乘以矩阵变量U
,我似乎无法知道如何做到这一点。 / p>
我首先尝试使用tf.matmul
,但由于形状不匹配而导致错误。然后我通过扩展U
的维度并应用batch_matmul
来尝试以下内容(我也尝试了tf.nn.math_ops.
中的函数,结果是相同的):
U = tf.Variable( ... )
U1 = tf.expand_dims(U,0)
h=tf.batch_matmul(embed, U1)
这会传递初始编译,但是当应用实际数据时,我会收到以下错误:
In[0].dim(0) and In[1].dim(0) must be the same: [64,58,128] vs [1,128,128]
我也知道为什么会发生这种情况 - 我复制了U
的维度,现在是1
,但是小批量大小64
不合适。
如何正确地对张量矩阵输入进行矩阵乘法(对于未知的批量大小)?
答案 0 :(得分:21)
M = tf.random_normal((batch_size, n, m))
N = tf.random_normal((batch_size, m, p))
# python >= 3.5
MN = M @ N
# or the old way,
MN = tf.matmul(M, N)
# MN has shape (batch_size, n, p)
我们通过向v
添加和删除维度来回到情况1。
M = tf.random_normal((batch_size, n, m))
v = tf.random_normal((batch_size, m))
Mv = (M @ v[..., None])[..., 0]
# Mv has shape (batch_size, n)
在这种情况下,我们不能简单地将1
的批量维度添加到单个矩阵中,因为tf.matmul
不会以批量维度进行广播。
在这种情况下,我们可以使用简单的整形将矩阵批处理视为单个大矩阵。
M = tf.random_normal((batch_size, n, m))
N = tf.random_normal((m, p))
MN = tf.reshape(tf.reshape(M, [-1, m]) @ N, [-1, n, p])
# MN has shape (batch_size, n, p)
这种情况更加复杂。通过转置矩阵,我们可以回到情况3.1。
MT = tf.matrix_transpose(M)
NT = tf.matrix_transpose(N)
NTMT = tf.reshape(tf.reshape(NT, [-1, m]) @ MT, [-1, p, n])
MN = tf.matrix_transpose(NTMT)
但是,换位可能是一项代价高昂的操作,在此情况下,它在整批矩阵上执行两次。最好简单地复制M
以匹配批次尺寸:
MN = tf.tile(M[None], [batch_size, 1, 1]) @ N
分析将告诉您哪个选项对给定的问题/硬件组合更有效。
这看起来与情况3.2类似,因为单个矩阵位于左侧,但实际上更简单,因为转置向量本质上是无操作的。我们最终以
M = tf.random_normal((n, m))
v = tf.random_normal((batch_size, m))
MT = tf.matrix_transpose(M)
Mv = v @ MT
einsum
呢?以前所有的乘法都可以用tf.einsum
瑞士军刀来写。例如,第一个3.2的解决方案可以简单地写为
MN = tf.einsum('nm,bmp->bnp', M, N)
但是,请注意,einsum
最终是relying on tranpose
and matmul
进行计算。
因此,即使einsum
是编写矩阵乘法的一种非常方便的方法,但它掩盖了其下面运算的复杂性-例如,猜测einsum
表达式将转置多少次并不容易您的数据,以及操作的成本。同样,它可能掩盖了一个事实,即同一操作可能有多种选择(请参阅案例3.2),而不一定选择更好的选择。
由于这个原因,我个人将使用上述明确的公式来更好地传达它们各自的复杂性。尽管如果您知道自己在做什么,并且喜欢einsum
语法的简单性,那么一定要这么做。
答案 1 :(得分:17)
matmul operation仅适用于矩阵(2D张量)。以下是执行此操作的两种主要方法,均假设U
是2D张量。
将embed
切片到2D张量中,并将每个张量分别与U
相乘。这可能是最容易使用这样的tf.scan()
:
h = tf.scan(lambda a, x: tf.matmul(x, U), embed)
另一方面,如果效率很重要,最好将embed
重塑为2D张量,这样可以使用单个matmul
进行乘法运算:
embed = tf.reshape(embed, [-1, m])
h = tf.matmul(embed, U)
h = tf.reshape(h, [-1, n, c])
其中c
是U
中的列数。最后一次重塑将确保h
是一个3D张量,其中第0维对应于批次,就像原始x_input
和embed
一样。
答案 2 :(得分:4)
正如@Stryke所说,有两种方法可以达到这个目的:1。扫描,和2.重塑
tf.scan需要lambda函数,通常用于递归操作。这里有一些例子:https://rdipietro.github.io/tensorflow-scan-examples/
我个人更喜欢重塑,因为它更直观。如果你试图通过2D张量矩阵(如Cijl = Aijk * Bkl)将3D张量中的每个矩阵进行矩阵乘法,则可以通过简单的重塑来实现。
A' = tf.reshape(Aijk,[i*j,k])
C' = tf.matmul(A',Bkl)
C = tf.reshape(C',[i,j,l])
答案 3 :(得分:0)
在TensorFlow 1.11.0中,tf.matmul
的{{3}}似乎错误地认为它适用于等级> = 2。
相反,我发现最好的替代方法是使用tf.tensordot(a, b, (-1, 0))
(docs)。
此函数以常规形式a
获取数组b
的任何轴和数组tf.tensordot(a, b, axis)
的任何轴的点积。将axis
设置为(-1, 0)
可获得两个数组的标准点积。