numpy in einsum (Einstein summation)

wedge

numpy has a einsum method, that is, we say, "Einstein sum", it has a very strong ability to express. Like the "dot product" matrix, "foreign plot", "transpose" and so can use the "Einstein summation" to achieve, and will be more convenient, but the drawback is the meaning of it takes some time to sign on behalf of understanding. Here we take a look at how to use:

Note: Einstein summation good use will be very convenient, and cool. But if not Einstein summation, we also can use other methods to achieve numpy.

Einstein summation biggest bright spot is that it can only use a few symbols can express a wealth of meaning, but do not use it is no problem, and this method can not to try not to use. For example, the transposed matrix directly arr.T can, like this there is no need to use Einstein's summation.

The last problem is the efficiency, Einstein summation of efficiency in some scenes is actually not so good, later we will specifically be tested to see if it and other methods to implement what the difference in efficiency. In fact, Einstein summation means for comparing (a + c) / 2

Einstein symbol

The first numpy einsum function takes a string (英文字符组成,即:符号)number of arrays and any number of arrays, and to pass the symbol matching. Let's see what this symbol in the end is?

We all know that there is an array of concept dimensions, such an array can be one, two, three, etc., we use English characters to represent the corresponding dimensions, how to understand it? For example, a three-dimensional array, then we use three English characters to represent. But three English characters which represent one dimension of it, is very simple, direct English characters in ascending order, then the corresponding dimension of the array.

For example: aMi, after the order is drained Mai, then M represents a first dimension, a second dimension representatives, i represents the third dimension.

But note that, although drained sequence corresponding dimensions are fixed, but the meaning aMi and aiM two symbols represent is not the same, it is easy to see in the demo code of the time.

Let's look at the code that actually demonstrate:

import numpy as np

arr = np.array([1, 2, 3, 4])
# 这个数组是一维的,那么只需要一个英文字符即可。
# einsum中的符号只需要一个英文字符
arr_view = np.einsum("a", arr)
print(arr_view)  # [1 2 3 4]

"""
这个会返回arr的一个视图,在视图上做修改会影响原来的数组
"""
arr_view[0] = 111
print(arr)  # [111   2   3   4]

# 另外我们说可以是任意的英文字符,而目前的arr只有一个维度
# 那么我们输入任意的英文字符都是可以的
print(np.einsum("k", arr))  # [111   2   3   4]
print(np.einsum("P", arr))  # [111   2   3   4]
print(np.einsum("T", arr))  # [111   2   3   4]
print(np.einsum("v", arr))  # [111   2   3   4]
print(np.einsum("q", arr))  # [111   2   3   4]

If you are new to einsum, then the current might be a bit ignorant, but it does not matter, a normal phenomenon. We look down slowly, and finally you will understand.

We show you the use of two-dimensional array

import numpy as np

arr = np.array([[1, 2, 3, 4]])
# 这个数组是二维的,那么需要两个英文字符。
print(np.einsum("ij", arr))  # [[1 2 3 4]]
"""
依旧返回arr的视图,我们在视图上进行修改会影响原来的数组本身
"""

# 我们说符号的英文字符是任意的,只是个数要和数组的维度进行匹配
# 我们这里的arr是二维的,那么符号里面的英文字符个数也要是两个,但是字符是什么英文字符则没有要求
print(np.einsum("av", arr))  # [[1 2 3 4]]

try:
    print(np.einsum("abc", arr))
except Exception as e:
    """
    这里就报错了,因为我们指定符号为"abc",三个字符
    但是数组只有两个维度,于是不匹配,所以报错
    """
    print(e)  # einstein sum subscripts string contains too many subscripts for operand 0


# 我们虽然说,英文字符个数只要对应就行,但是英文字符是什么则没有要求
# 但是排列之后的顺序还是要对应的
print(np.einsum("sm", arr))
"""
[[1]
 [2]
 [3]
 [4]]
"""
# 注意此时就变了,我们说"sm"和"ms"是不一样的,尽管它们都是m对应第一维、s对应第二维,因为排完序m在前s在后
# 原来数组的shape是(1, 4), m对应1,s对应4
# 但是符号如果是"sm",那么新返回的视图的shape变成(4, 1)。等于将数组转置了

So the meaning of ab, bc, xy, av these symbols represent the same, because they are still the original order after sorting.

For example: shape array is (2, 3), then the resulting shape of the array thereof or (2, 3)

However, if a symbol is different sm, s represents the second dimension, m represents the first dimension, so that the shape of the array is equal to sm becomes (3, 2)

But note: sm from the shape (2, 3) becomes (3, 2) are not reshape into (3, 2), but rather a transposed

We compare the difference between:

import numpy as np

arr = np.arange(0, 12).reshape((3, 4))
print(arr)
"""
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
"""
# reshape成(4, 3)
print(arr.reshape((4, 3)))
"""
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
"""
# 等价于转置
print(np.einsum("sm", arr))
"""
[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]]
"""

# 因为转置之后,如果原来的shape为(a, b),那么会变成(b, a)
# 所以np.einsum("sm", arr)是将数组进行了转置
# 而reshape和转置是不一样的

So we are currently using Einstein summation achieve a transpose operation, how efficiencies between it and used directly transposed it?

import numpy as np

arr = np.arange(0, 12).reshape((3, 4))
print(arr)
"""
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
"""

print(arr.T)
"""
[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]]
"""

print(np.einsum("ba", arr))
"""
[[ 0  4  8]
 [ 1  5  9]
 [ 2  6 10]
 [ 3  7 11]]
"""

We note: The results of these two returned are the same, then we take a look at the difference in efficiency, let's test on jupyter notebook

>>> %timeit arr.T
122 ns ± 0.787 ns per loop
>>> %timeit np.einsum("ba", arr)
1.26 µs ± 91.3 ns per loop

1μs = 1000ns, so we see carried out using the Einstein summation is not arr.T go fast, you can speak slowly 10 times.

In addition, Einstein summation transposed on arr.T, and reshape the nature just create a view (view), the view is modified on will affect the original array.

A single array

By the example above, I believe you should have a general impression of Einstein sum up, let's take a look at its other operations.

We know that for a two-dimensional array, if the sign is "ab", then the view will get the original array, if it is "ba", although still get the view, but the shape will change to a two-dimensional array is equivalent to transpose.

However, if we are designated as "aa" of it?

import numpy as np

arr = np.arange(0, 12).reshape((3, 4))
print(arr)
"""
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
"""
try:
    print(np.einsum("aa", arr))
except Exception as e:
    print(e)  # dimensions in operand 0 for collapsing index 'a' don't match (3 != 4)
"""
我们看到报错了,我们知道a代表第一个维度,所以相当于reshape成(3, 3),但是维度不匹配
其实不是这样的,如果是两个字符都一样的话,那么相当于求主对角线的和,因此它要求必须是方阵
"""


arr = np.arange(0, 16).reshape((4, 4))
print(arr)
"""
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]
"""
# 需要arr是一个方阵,主对角线:0 5 10 15,所以加在一起是30
print(np.einsum("aa", arr))  # 30
# 求主对角线的和等价于np.trace,另外主对角线的和也叫作该矩阵的"迹"
print(np.trace(arr))  # 30

Speaking sum, Einstein summation is supported, after all, the name itself contains the sum

Summing

Einstein summed in summation symbols used ->denote

import numpy as np

arr = np.array([1, 2, 3, 4])
# 表示对返回的视图里面的元素进行求和
print(np.einsum("k->", arr), np.sum(arr))  # 10 10

But we saw above that "aa" does not include ->ah, Lord diagonal and we use "aa", in fact, aaand aa->is the same.

import numpy as np

arr = np.arange(0, 16).reshape((4, 4))
print(np.einsum("kk", arr))  # 30
print(np.einsum("kk->", arr))  # 30

Then the old rules, we look at two-dimensional array, a wave of good feelings.

import numpy as np

arr = np.arange(0, 12).reshape((3, 4))
print(arr)
"""
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
"""
print(np.einsum("ij->", arr), np.sum(arr))  # 66 66
# 我们看到对二维数组求和也是没有问题的,但是对于np.sum可以指定坐标轴
print(np.sum(arr, axis=0))  # [12 15 18 21]
print(np.sum(arr, axis=1))  # [ 6 22 38]

# 那么,对于爱因斯坦求和可不可以指定呢?答案是可以的
# 我们来看看怎么用
print(np.einsum("ij->i", arr))  # [ 6 22 38]
print(np.einsum("ij->j", arr))  # [12 15 18 21]

print(np.einsum("ji->i", arr))  # [12 15 18 21]
print(np.einsum("ji->j", arr))  # [ 6 22 38]

Some estimate will be depressed, which in the end is the sum along one axis, do not know how to judge. Of course, if you are very clear axis = 0 and the difference between the axis = 1, then you will easily understand.

How to understand this place, there are a variety of ways, presented here in a way I understand. First, we look ->to the right, we say i represents the first dimension, j represents the second dimension and shape of the original array is (3, 4). If yes ->i, then representative of the first dimension and the array length is consistent, ->jit indicates the length of the array and the second dimension consistent.

ijCorresponding to (3, 4), therefore ij->i, then the length of the array after addition be 3; and ij->j, then after the addition was complete length of the array must be 4.

As ji->iand ji->j, first of all ->the meaning of i and j represents the right and above, but jiit is equivalent to transpose the original array. The original shape is an array (3, 4), it is set after the completion of transfer (4, 3), so that ji->i, after addition of a length to be 4, since the length of the first dimension of the array into a 4 ji->j, after the addition was complete 3 should be of a length.

I believe at this point you should be very clear on the sum of an array, then let's look at how to obtain the main diagonal

import numpy as np

arr = np.arange(0, 16).reshape((4, 4))
print(arr)
"""
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]
"""
print(np.einsum("aa", arr))  # 30
print(np.einsum("aa->", arr))  # 30
# 我们说aa和aa->都是获取主对角线的和

# 而aa->a则是获取主对角线
print(np.einsum("aa->a", arr))  # [ 0  5 10 15]
"""
注意:我们这里的符号故意和之前写的不一样,因为aa->a、ii->i、BB->B都是一样的
"""
# 获取主对角线等价于
print(np.diag(arr))  # [ 0  5 10 15]

Finally, we have to look further than efficiency:

>>> %timeit np.sum(arr)
3.27 µs ± 417 ns per loop
>>> %timeit np.einsum("ab", arr)
1.23 µs ± 38.5 ns per loop

>>> %timeit np.sum(arr, axis=0)
3.09 µs ± 10.9 ns per loop
>>> %timeit np.einsum("ab->b", arr)  # ab->b等价于ij->j
1.72 µs ± 13.6 ns per loop

>>> %timeit np.sum(arr, axis=1)
3.97 µs ± 21.2 ns per loop
>>> %timeit np.einsum("ab->a", arr)  # ab->a等价于ij->i
1.81 µs ± 74.6 ns per loop

We can see that for summation, the Einstein summation is not a small advantage.

to sum up

For a single array, its operation we summarize.

A two-dimensional array

Let us look at how to operate between the two arrays, we einsum the above symbols are first introduced, and then explore other methods equivalent thereto.

Now we look at what operation between the two arrays, and then see how express einsum.

But before that we have some concepts you need to know.

Inner product vector cross product and outer product

Also known as the inner product of the dot product of two vectors for the product if you want, then the two components of the vector number must be equal. The component is then multiplied to the corresponding position, and then seeking the whole together, the results obtained vector dot product is a scalar.

假设存在两个向量α和β,其中\(α = [a_{1}, a_{2}, ..., a_{n}]\)\(β = [b_{1}, b_{2}, ..., b_{n}]\)

那么向量α和β进行点积的结果就是\(a_{1} * b_{1} + a_{2}  * b_{2} + ... + a_{n} * b_{n}\)

import numpy as np

arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])

# numpy中求两个向量的点积可以使用inner方法
print(np.inner(arr1, arr2))  # 32

向量的外积,看个公式和栗子就明白了

假设存在两个向量α和β,其中\(α = [a_{1}, a_{2}, ..., a_{m}]\)\(β = [b_{1}, b_{2}, ..., b_{n}]\)

那么向量α和β进行进行外积的结果就是:

\(\begin{bmatrix}a_{1}*b_{1},a_{1}*b_{2},...,a_{1}*b_{n}\\a_{2}*b_{1},a_{2}*b_{2},...,a_{2}*b_{n}\\a_{3}*b_{1},a_{3}*b_{2},...,a_{3}*b_{n}\\a_{m}*b_{1},a_{m}*b_{2},...,a_{m}*b_{n}\end{bmatrix}\)

import numpy as np

arr1 = np.array([2, 3, 4])
arr2 = np.array([3, 3, 3])
print(np.outer(arr1, arr2))
"""
[[ 6  6  6]
 [ 9  9  9]
 [12 12 12]]
"""

arr1 = np.array([2, 3, 4, 5, 6])
arr2 = np.array([3, 3, 3])
print(np.outer(arr1, arr2))
"""
[[ 6  6  6]
 [ 9  9  9]
 [12 12 12]
 [15 15 15]
 [18 18 18]]
"""

arr1 = np.array([2])
arr2 = np.array([3, 3, 3])
print(np.outer(arr1, arr2))
"""
[[6 6 6]]
"""

arr1 = np.array([2, 3, 4])
arr2 = np.array([3])
print(np.outer(arr1, arr2))
"""
[[ 6]
 [ 9]
 [12]]
"""
# 我们看到两个向量进行外积的话,不要求两个向量的分量个数相等
# 并且两个向量进行外积得到的是一个二维数组,也就是矩阵
# 在numpy中,我们把一维数组称之为向量,二维数组称之为矩阵。

向量的叉积,同样要求两个向量的分量个数必须一样。

假设存在两个向量α和β,其中\(α = [a_{1}, a_{2}, ..., a_{n}]\)\(β = [b_{1}, b_{2}, ..., b_{n}]\)

那么向量α和向量β进行叉积的结果就是下面

\(\begin{bmatrix}1 ,1,1,...,1\\a_{1},a_{2},a_{3},...,a_{n}\\b_{1},b_{2},b_{3},...,b_{n}\end{bmatrix}\)

这个矩阵的第一行的每个元素的代数余子式组成的向量。

import numpy as np

arr1 = np.array([2, 3, 4])
arr2 = np.array([4, 5, 3])
print(np.cross(arr1, arr2))
"""
[-11  10  -2]
"""

# 我们来看看是怎么计算的
# 我们说结果就是下面
"""
1, 1, 1
2, 3, 4
4, 5, 3
"""
# 这个矩阵的第一行的每个元素的代数余子式组成的向量
# 如果不知道代数余子式是什么,可以去百度一下,这里不提了
# 3 * 3 - 5 * 4, -1 * (2 * 3 - 4 * 4), 2 * 5 - 3 * 4
# 所以结果是(-11, 10, -2)

矩阵的相乘和点乘

下面解释一下矩阵的相乘和点乘,相乘就是对应位置的元素直接相乘即可;而点乘则是矩阵间的运算,如果两个矩阵A和B,想要进行A点乘B,那么需要满足A的列数等于B的行数,然后运算得到矩阵的行数等于矩阵A的行数、列数等于矩阵B的列数。

import numpy as np

arr1 = np.array([[1, 2, 3], [2, 3, 4]])
arr2 = np.array([[3, 1, 2], [2, 5, 1]])
print(arr1)
"""
[[1 2 3]
 [2 3 4]]
"""
print(arr2)
"""
[[3 1 2]
 [2 5 1]]
"""
print(arr1 * arr2)
"""
[[ 3  2  6]
 [ 4 15  4]]
"""
# 说白了相乘就是对应位置的元素进行相乘
# 当然向量也是可以进行相乘的,如果相乘完了再把所有的元素相加就等价于两个向量的内积

# 而向量间的点乘,在python中使用@表示
try:
    print(arr1 @ arr2)
except Exception:
    print("error occurred")  # error occurred

所以矩阵之间相乘是很好理解的,就是对应位置的元素相乘。但是点乘不一样,点乘就是要求第一个矩阵的列数等于第二个矩阵的行数。arr1和arr2的shape都是(2, 3),所以arr1的列数为3,arr2的行数为2,它们不相等,所以报错了

import numpy as np

arr1 = np.array([[1, 2, 3], [2, 3, 4]])
arr2 = np.array([[3, 1], [2, 5], [1, 3]])
print(arr1)
"""
[[1 2 3]
 [2 3 4]]
"""
print(arr2)
"""
[[3 1]
 [2 5]
 [1 3]]
"""
try:
    print(arr1 * arr2)
except Exception:
    # 我们说相乘是对应位置的元素进行相乘,但是这两个矩阵的形状不一样
    # 元素无法一一对应,所以报错了
    print("error occurred")  # error occurred

print(arr1 @ arr2)
"""
[[10 20]
 [16 29]]
"""
# 但是点乘可以,点乘就是arr1的每一行和arr2的每一列进行内积
# 所以才要arr1的列数和arr2的行数相同,只有这样arr1的一行才可以arr2的一列进行内积
"""
arr1:
    [[1 2 3]
    [2 3 4]]
arr2:
    [[3 1]
     [2 5]
     [1 3]]

arr1 @ arr2:
    [[1 * 3 + 2 * 2 + 3 * 1, 1 * 1 + 2 * 5 + 3 * 3]
     [2 * 3 + 3 * 2 + 4 * 1, 2 * 1 + 3 * 5 + 4 * 3]]

==>
    [[10, 20]
     [16, 29]]       
"""

使用爱因斯坦求和

使用einsum对两个一维数组相乘

import numpy as np

arr1 = np.array([1, 2, 3])
arr2 = np.array([3, 4, 5])
print(arr1 * arr2)  # [ 3  8 15]
"""
两个数组相乘,就是将里面的对应元素相乘
如果再将所有元素相加就是内积
"""
# 我们看到此时有两个数组,所以"->"的左侧出现了用于分隔的逗号
print(np.einsum("i,i->i", arr1, arr2))  # [ 3  8 15]

使用einsum对两个一维数组内积

import numpy as np

arr1 = np.array([1, 2, 3])
arr2 = np.array([3, 4, 5])
print(np.inner(arr1, arr2))  # 26
"""
就是矩阵内的对应元素彼此相乘,然后再将所有元素加在一起
1 * 3 + 2 * 4 + 3 * 5 = 26
"""
print(np.einsum("i,i->", arr1, arr2))  # 26

使用einsum对两个一维数组外积

import numpy as np

arr1 = np.array([1, 2, 3])
arr2 = np.array([3, 4, 5])
print(np.outer(arr1, arr2))
"""
[[ 3  4  5]
 [ 6  8 10]
 [ 9 12 15]]
"""
print(np.einsum("i,j->ij", arr1, arr2))
"""
[[ 3  4  5]
 [ 6  8 10]
 [ 9 12 15]]
"""

估计这里可能有人会懵,我们来解释一下。首先两个一维数组,那么它们的符号都只能是一个英文字符,然后使用逗号分割。如果是i,i->i,那么表示两个向量里面的元素进行相乘,乘完之后的元素个数和原来一样。

如果是i,i->,那么很好理解,则是乘完之后就相加。

最后是i,j->ij,可能有点难理解。我们说单个二维数组ij,那么等于返回一个视图。但如果类似i,j,是两个数组,那么就表示两个数组相乘,至于怎么相乘,则取决于符号本身。而i,j->ij表示进行外积,因为->ij表示要返回一个二维数组,shape为(i, j)。

import numpy as np

arr1 = np.array([1, 2, 3])
arr2 = np.array([3, 4, 5])
print(np.outer(arr1, arr2))
"""
[[ 3  4  5]
 [ 6  8 10]
 [ 9 12 15]]
"""
print(np.sum(np.outer(arr1, arr2)))  # 72
print(np.einsum("i,j->", arr1, arr2))  # 72
"""
i,j->ij: 返回一个shape为(i, j)的二维数组
i,j->: 返回一个shape为(i, j)的二维数组的所有元素的和

i,i->i: 返回一个shape为(i,)的一维数组
i,i->: 返回一个shape为(i,)的一维数组的所有元素的和
"""

然后来看个稍微拐个弯的

import numpy as np

arr1 = np.array([1, 2, 3])
arr2 = np.array([3, 4, 5])
print(np.outer(arr1, arr2))
"""
[[ 3  4  5]
 [ 6  8 10]
 [ 9 12 15]]
"""
print(np.einsum("i,j->i", arr1, arr2))  # [12 24 36]
print(np.einsum("i,j->j", arr1, arr2))  # [18 24 30]
"""
我们说i,j可以看成是一个二维数组,shape为(i, j)
如果是i,j->ij,那么就返回这个shape为(i, j)的二维数组
如果是i,j->,那么就返回这个shape为(i, j)的二维数组的所有元素的和

但如果是i,j->i的话,显然是在某一个方向上进行求和。至于在哪一个方向,可以看我们上面说的单个二维数据的求和
"""

print(np.einsum("j,i->i", arr1, arr2))  # [18 24 30]
print(np.einsum("j,i->j", arr1, arr2))  # [12 24 36]
"""
我们说i,j可以看成是返回一个shape为(i, j)的二维数组,而j,i则还是可以看成是返回一个shape为(i, j)的二维数组
所以结果是相反的
"""

# 仔细感受一下下面几个式子的不同
print(np.einsum("i,j->ij", arr1, arr2))
"""
[[ 3  4  5]
 [ 6  8 10]
 [ 9 12 15]]
"""
print(np.einsum("i,j->ji", arr1, arr2))
"""
[[ 3  6  9]
 [ 4  8 12]
 [ 5 10 15]]
"""
print(np.einsum("j,i->ji", arr1, arr2))
"""
[[ 3  4  5]
 [ 6  8 10]
 [ 9 12 15]]
"""
print(np.einsum("j,i->ij", arr1, arr2))
"""
[[ 3  6  9]
 [ 4  8 12]
 [ 5 10 15]]
"""
# i,j->ij:arr1的长度为i,arr2的长度为j,返回的数组shape为(i, j)
# j,i->ji:arr1的长度为j,arr2的长度为i,返回的数组shape为(j, i)
# 所以i,j->ij和j,i->ji是一样的
# 但是如果是->i或者->j,需要对某一方向进行求和,那么无论是i,j->i还是j,i->,默认返回的都是二维数组的shape都是(i,j)

两个二维数组

这里介绍两个二维数组之间的爱因斯坦求和,关于一维数组和二维数组之间由于涉及到广播运算,我们就不说了

实现两个矩阵的相乘

import numpy as np

arr1 = np.array([[1, 2, 3], [2, 3, 4]])
arr2 = np.array([[3, 4, 5], [5, 1, 2]])
print(arr1)
"""
[[1 2 3]
 [2 3 4]]
"""
print(arr2)
"""
[[3 4 5]
 [5 1 2]]
"""
print(arr1 * arr2)
"""
[[ 3  8 15]
 [10  3  8]]
"""
print(np.einsum("ij,ij->ij", arr1, arr2))
"""
[[ 3  8 15]
 [10  3  8]]
"""
# 我们说两个一维数组、也就是向量相乘是"i,i->i",表示两个向量对应元素相乘,返回shape为(i,)的向量
# 而矩阵相乘,则是"ij,ij->ij",表示两个矩阵对应位置的元素相乘,返回shape为(i,j)的矩阵
# 所以如果想对应位置的元素相乘,那么就用相同的符号表示就可以了

# 同理"ij,ji->ij"表示arr1和arr2的转置进行相乘,当然这里会报错,因为arr2转置之后和arr1就不能一一对应了

实现两个矩阵的点乘

import numpy as np

arr1 = np.array([[1, 2, 3], [2, 3, 4]])
arr2 = np.array([[3, 1], [2, 5], [1, 3]])
print(arr1)
"""
[[1 2 3]
 [2 3 4]]
"""
print(arr2)
"""
[[3 1]
 [2 5]
 [1 3]]
"""
print(arr1 @ arr2)
"""
[[10 20]
 [16 29]]
"""
# 点乘,arr1的每一行要和arr2的每一列内积,最终计算之后的矩阵的行数为arr1的行数,列数为arr2的列数
# 并且arr1的第二个维度要和arr2的第一个维度的数组长度一致
# 所以"ij,jk->ik",
print(np.einsum("ij,jk->ik", arr1, arr2))
"""
[[10 20]
 [16 29]]
"""

带上转置

import numpy as np

arr1 = np.array([[1, 2, 3], [2, 3, 4]])
arr2 = np.array([[3, 4, 5], [5, 1, 2]])
# 我们说arr1和arr2之间可以相乘,但是无法点乘
# 但如果将arr2转置一下不就可以了吗?
print(np.einsum("ij,kj->ik", arr1, arr2))
"""
[[26 13]
 [38 21]]
"""
print(arr1 @ arr2.T)
"""
[[26 13]
 [38 21]]
"""
# "ij,kj",因为j是第一个维度、k是第二个维度,所以"ij,kj->ik"表示将arr1和转置之后的arr2进行点乘
import numpy as np

arr1 = np.array([[1, 2, 3], [2, 3, 4]])
arr2 = np.array([[3, 1], [2, 5], [1, 3]])

print(arr1 * arr2.T)
print(np.einsum("ij,ji->ij", arr1, arr2))
"""
[[ 3  4  3]
 [ 2 15 12]]
"""
# "ij,ij->ij"表示对应元素相乘
# "ij,ji->ij"表示将arr2转置之后,再和arr1对应元素相乘

总结

我个人觉得这个爱因斯坦算法确实有点太难理解了,个人建议没事的话还是不要乱用,其实用普通的numpy的方法完全可以轻松的实现。目前介绍的算是比较浅显的了,更复杂的用法可以参考官网。

Guess you like

Origin www.cnblogs.com/traditional/p/12635516.html