Tensorflow - матрица входной матрицы с пакетными данными

У меня есть некоторые данные, представленные 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, и я не могу понять, как это сделать.

Сначала я попытался использовать 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 не подходит.

Как правильно выполнить умножение матриц на входе тензорной матрицы (для неизвестного размера партии)?

Ответ 1

Операция tf.scan() следующим образом:

h = tf.scan(lambda a, x: tf.matmul(x, U), embed)

С другой стороны, если эффективность важна, может быть лучше изменить форму t21 на 2D тензор, поэтому умножение может быть выполнено с помощью одного matmul следующего вида:

embed = tf.reshape(embed, [-1, m])
h = tf.matmul(embed, U)
h = tf.reshape(h, [-1, n, c])

где c - количество столбцов в U. Последнее изменение будет гарантировать, что h является трехмерным тензором, где 0-е измерение соответствует партии, как оригинальные x_input и embed.

Ответ 2

Предыдущие ответы устарели. В настоящее время tf.matmul() поддерживают тензоры с рангом > 2:

Входы должны быть матрицами (или тензорами рангa > 2, представляющими партии матриц), с соответствующими внутренними размерами, возможно, после транспозиции.

Также был удален tf.batch_matmul(), а tf.matmul() - правильный способ пакетного умножения. Основная идея может быть понята из следующего кода:

import tensorflow as tf
batch_size, n, m, k = 10, 3, 5, 2
A = tf.Variable(tf.random_normal(shape=(batch_size, n, m)))
B = tf.Variable(tf.random_normal(shape=(batch_size, m, k)))
tf.matmul(A, B)

Теперь вы получите тензор формы (batch_size, n, k). Вот что здесь происходит. Предположим, что у вас есть batch_size матриц nxm и batch_size матриц mxk. Теперь для каждой пары из них вы вычисляете nxm X mxk, который дает вам матрицу nxk. У вас будет batch_size из них.

Обратите внимание, что что-то вроде этого также верно:

A = tf.Variable(tf.random_normal(shape=(a, b, n, m)))
B = tf.Variable(tf.random_normal(shape=(a, b, m, k)))
tf.matmul(A, B)

и даст вам форму (a, b, n, k)

Ответ 3

1. Я хочу умножить партию матриц на партию матриц одинаковой длины попарно

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)

2. Я хочу умножить партию матриц на партию векторов одинаковой длины попарно

Мы вернемся к случаю 1, добавив и удалив измерение в v.

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)

3. Я хочу, чтобы умножить одну матрицу с партией матриц

В этом случае мы не можем просто добавить пакетное измерение 1 в одну матрицу, потому что tf.matmul не передает в пакетном измерении.

3.1. Единственная матрица находится на правой стороне

В этом случае мы можем рассматривать матричную партию как одну большую матрицу, используя простое изменение формы.

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.2. Единственная матрица находится на левой стороне

Этот случай сложнее. Мы можем вернуться к случаю 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

Профилирование покажет, какой вариант лучше подходит для данной комбинации проблемы/оборудования.

4. Я хочу умножить одну матрицу на пакет векторов

Это похоже на случай 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 швейцарского армейского ножа tf.einsum. Например, первое решение для 3.2 можно записать просто как

MN = tf.einsum('nm,bmp->bnp', M, N)

Тем не менее, обратите внимание, что einsum в конечном счете полагается на tranpose и matmul для вычислений.

Таким образом, хотя einsum является очень удобным способом записи умножения матриц, он скрывает сложность операций под ней - например, не просто догадаться, сколько раз выражение einsum будет транспонировать ваши данные, и, следовательно, насколько дорогостоящей будет операция, Кроме того, он может скрывать тот факт, что может быть несколько альтернатив для одной и той же операции (см. Случай 3.2) и может не обязательно выбирать лучший вариант.

По этой причине я бы лично использовал явные формулы, подобные приведенным выше, чтобы лучше передать их сложность. Хотя, если вы знаете, что делаете, и вам нравится простота синтаксиса einsum, то обязательно einsum это.

Ответ 4

Как ответил @Stryke, есть два способа добиться этого: 1. Сканирование и 2. Изменение формы

  • tf.scan требует лямбда-функций и обычно используется для рекурсивных операций. Некоторые примеры для них приведены здесь: https://rdipietro.github.io/tensorflow-scan-examples/

  • Я лично предпочитаю переделывать, поскольку он более интуитивно понятен. Если вы пытаетесь матрицей умножить каждую матрицу в трехмерном тензоре на матрицу, являющуюся 2D-тензором, например Cijl = Aijk * Bkl, вы можете сделать это с простой перестановкой.

    A' = tf.reshape(Aijk,[i*j,k])
    C' = tf.matmul(A',Bkl)
    C = tf.reshape(C',[i,j,l])
    

Ответ 5

Похоже, что в TensorFlow 1.11.0 документы для tf.matmul неправильно говорят, что он работает для ранга> = 2.

Вместо этого, лучшая чистая альтернатива, которую я нашел, это использовать tf.tensordot(a, b, (-1, 0)) (docs).

Эта функция получает произведение точек любой оси массива a и любой оси массива b в общем виде tf.tensordot(a, b, axis). Предоставление axis как (-1, 0) получает стандартное произведение точек двух массивов.