Хатринское произведение матриц с использованием np.tensordot

Я пытаюсь разложить тензор (m, n, o) на матрицы A(m, r), B (n, r) и C (k, r). Это известно как разложение PARAFAC. Тензорно уже делает этот вид разложения.

Важным шагом является умножение A, B и C, чтобы получить тензор формы (m, n, o).

Тензорно делает это следующим образом:

def kt_to_tensor(A, B, C):
    factors = [A, B, C]
    for r in range(factors[0].shape[1]):
        vecs = np.ix_(*[u[:, r] for u in factors])
        if r:
            res += reduce(np.multiply, vecs)
        else:
            res = reduce(np.multiply, vecs)
    return res

Однако используемый мной пакет (Autograd) не поддерживает np.ix_ операции. Таким образом, я написал более простое определение следующим образом:

def new_kt_to_tensor(A, B, C):
    m, n, o = A.shape[0], B.shape[0], C.shape[0]
    out = np.zeros((m, n, o))
    k_max = A.shape[1]
    for alpha in range(0, m):
        for beta in range(0, n):
            for delta in range(0, o):
                for k in range(0, k_max):
                    out[alpha, beta, delta]=out[alpha, beta, delta]+ A[alpha, k]*B[beta, k]*C[delta, k]
    return out

Однако оказывается, что эта реализация также имеет некоторые аспекты, которые не поддерживает Autograd. Тем не менее, Autogra действительно поддерживает np.tensordot,

Мне было интересно, как использовать np.tensordot чтобы получить это умножение. Я думаю, что Tensorflow's tf.tensordot также будет иметь аналогичную функциональность.

Предполагаемое решение должно быть что-то вроде:

def tensordot_multplication(A, B, C):
    """
    use np.tensordot
    """

2 ответа

Решение

Не думай np.tensordot здесь вам помогут, так как нужно распределить оси, которые не участвуют в уменьшении суммы, так как у нас есть требование выравнивания, чтобы последняя ось была выровнена между тремя входами при выполнении умножения. Таким образом, с tensordot, вам потребуется дополнительная обработка и больше требований к памяти.

Я бы предложил два метода - один с broadcasting и еще один с np.einsum,

Подход № 1: с broadcasting -

(A[:,None,None,:]*B[:,None,:]*C).sum(-1)

Пояснение:

  • простираться A в 4D, вводя новые оси в axis=(1,2) с None/np.newaxis.

  • Аналогичным образом расширить B в 3D, введя новую ось в axis=(1),

  • Держать C как есть и выполнять поэлементное умножение, в результате чего 4D массив.

  • Наконец, уменьшение суммы происходит вдоль последней оси 4D массив.

Схематично поставил -

A        : m        r
B        :    n     r
C        :       k  r

=> A*B*C : m  n  k  r
=> out   : m  n  k    # (sum-reduction along last axis)

Подход № 2: с np.einsum -

np.einsum('il,jl,kl->ijk',A,B,C)

Идея здесь такая же, как и с предыдущей broadcasting один, но с строковыми обозначениями, помогающими нам в передаче информации об осях в более сжатой форме.

Broadcasting конечно доступно на tensorflow так как у него есть инструменты для expand dimensions, в то время как np.einsum вероятно нет.

Код, на который вы ссылаетесь, на самом деле не то, как TensorLy реализует его, а просто альтернативная реализация, приведенная в документе.

Фактический код, используемый в TensorLy:

def kruskal_to_tensor(factors):
    shape = [factor.shape[0] for factor in factors]
    full_tensor = np.dot(factors[0], khatri_rao(factors[1:]).T)
    return fold(full_tensor, 0, shape)

где khatri_rao реализован с использованием numpy.einsum способом, который обобщает то, что предложил Дивакар.

Другие вопросы по тегам