it-roy-ru.com

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 не подходит. 

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

32
yoki

Операция matmul работает только с матрицами (2D-тензорами). Вот два основных подхода к этому, оба предполагают, что U является 2D-тензором.

  1. Разрежьте embed на 2D тензоры и умножьте каждый из них на U по отдельности. Это, вероятно, проще всего сделать с помощью tf.scan() вот так:

    h = tf.scan(lambda a, x: tf.matmul(x, U), embed)
    
  2. С другой стороны, если важна эффективность, может быть лучше изменить форму 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.

15
Styrke

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

Входные данные должны быть матрицами (или тензорами ранга> 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)

67
Salvador Dali

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 . Например, первое решение для 3.2 можно записать просто как

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

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

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

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

11
P-Gn

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

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

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

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

Кажется, что в TensorFlow 1.11.0 docs для tf.matmul неправильно говорят, что это работает для ранга> = 2.

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

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

0
James Fletcher