机器学习入门 3-7 Numpy 中的矩阵运算

2022-05-25 14:02:11 浏览数 (1)

numpy.array 中的运算

给定一个向量,让向量中每一个数乘以 2

  • a = (0, 1, 2)
  • a * 2 = (0, 2, 4)

如何解决上面的问题呢?我们可以使用原生 Python 来实现。

代码语言:javascript复制
n = 10
L = [i for i in range(n)]

print(2 * L)
'''
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
'''

显然,在 Python 中,列表 * N 中的 * 运算符为重复操作,将列表中的每个元素重复 N 次。

为了让列表中的每一个元素都乘以 2,我们可以使用 for 循环实现。

代码语言:javascript复制
A = []
for e in L:
    A.append(2 * e)

print(A)
'''
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
'''

这种做法的效率是多少呢?为了测试效率,我们将列表中的元素个数设置的大一些。

代码语言:javascript复制
n = 1000000
L = [i for i in range(n)]

在 jupyter 中,可以使用 %%time 魔法方法来测试时间。

代码语言:javascript复制
%%time
A = []
for e in L:
    A.append(2 * e)

在我的电脑中用时:196 ms。众所周知,使用生成表达式的效率要比 for 循环高。

代码语言:javascript复制
%%time
A = [2 * e for e in L]

用时是 103 ms。

那在 NumPy 中如何实现呢?

代码语言:javascript复制
import numpy as np

n = 1000000
L = np.arange(n)
代码语言:javascript复制
%%time
A = np.array(2 * e for e in L)

用时为 12 ms。

在 NumPy 中可以直接对进行一些向量和矩阵的操作。

代码语言:javascript复制
%%time
A = 2 * L

用时为 2.03 ms。通过用时也可以看出 NumPy 能够显著地提升运算的效率。NumPy 会把数组当做向量或者矩阵来看待,并且支持很多向量和矩阵的运算操作。这些运算操作在 NumPy 中进行了非常好的优化,运行速度非常快。这种将数组看成向量或矩阵的运算有一个名字:Universal Functions。

Universal Functions

代码语言:javascript复制
X = np.arange(1, 16).reshape((3, 5))

X   1  # 矩阵加法
X - 1  # 矩阵减法
X * 2  # 矩阵数乘
X / 2  # 矩阵除法
X // 2 # 矩阵整除
X ** 2 # 幂运算
X % 2  # 求余运算
1 / X  # 矩阵运算取倒数

NumPy 同样也支持很多特殊的运算。

代码语言:javascript复制
np.abs(X) # 求绝对值
np.sin(X) # 正弦函数
np.cos(X) # 余弦函数
np.tan(X) # 正切函数
np.exp(X) # 取e^X次方
np.power(3, X) # 等价于 3 ** X
np.log(X) # 以e为底取对数
np.log2(X) # 以2为底取对数
np.log10(X) # 以10为底取对数

以上这些运算都是针对一个数组进行运算,并且这些运算作用在数组中的每一个元素。

矩阵运算

NumPy 还支持矩阵和矩阵之间的运算。

代码语言:javascript复制
A = np.arange(4).reshape(2, 2)
B = np.full((2, 2), 10)
代码语言:javascript复制
A   B # 矩阵的加法
A - B # 矩阵的减法
A * B # Hadamard乘积,对应元素相乘
A / B # 矩阵对应元素相除

A.dot(B) # 矩阵的乘法
A.T   # 矩阵的转置

向量和矩阵的运算

在机器学习中除了矩阵和矩阵的运算外,还有一种运算使用的也比较多,就是向量和矩阵之间的运算。

代码语言:javascript复制
v = np.array([1, 2]) # 向量
A = np.arange(4).reshape(2, 2) # 矩阵

print(v   A)
'''
array([[1, 3],
       [3, 5]])
'''

在线性代数中,向量和矩阵是没有办法相加的,不过在 NumPy 中,向量通过广播机制变成了矩阵相同的形状,进而进行运算。

我们可以显示的使用 vstack 函数,将向量 v 扩充到和矩阵 A 相同的形状。

代码语言:javascript复制
print(np.vstack([v] * A.shape[0]))
'''
array([[1, 2],
       [1, 2]])
'''

print(np.vstack([v] * A.shape[0])   A)
'''
array([[1, 3],
       [3, 5]])
'''

对于上面的操作,NumPy 还提供了更加简单的 tile 函数,tile 函数的第二个参数是一个数组类型,表示沿着每个维度重复的数量。比如,np.tile(v, (2, 1)) 第二个参数为 (2, 1),表示将 v 沿着第一个维度重复 2 次,沿着第二个维度重复 1 次。

代码语言:javascript复制
print(np.tile(v, (2, 1)))
'''
array([[1, 2],
       [1, 2]])
'''

print(np.tile(v, (2, 1))   A)
'''
array([[1, 3],
       [3, 5]])
'''

在 NumPy 中,向量和矩阵可以进行 Hadamard 乘积(对应元素相乘),这个同样是运用广播机制,将向量扩充成矩阵,然后再与矩阵进行 Hadamard 乘积。

代码语言:javascript复制
print(v * A)
'''
array([[0, 2],
       [2, 6]])
'''

向量和矩阵之间可以进行矩阵的乘法,此时是将向量看成是行向量或列向量。具体将向量看成行向量还是列向量,NumPy 可以自动帮助我们判断。

代码语言:javascript复制
print(v.dot(A)) # 将向量v看成行向量
print(A.dot(v)) # 将向量v看成列向量

矩阵的逆

代码语言:javascript复制
A = np.arange(4).reshape(2, 2)
invA = np.linalg.inv(A) # 计算矩阵A的逆矩阵

在线性代数中,原矩阵和逆矩阵(或逆矩阵和原矩阵)进行矩阵相乘的运算,结果为单位矩阵。

代码语言:javascript复制
print(A.dot(invA))
'''
array([[1., 0.],
       [0., 1.]])
'''

print(invA.dot(A))
'''
array([[1., 0.],
       [0., 1.]])
'''

不过需要注意,只有方阵才有可能有逆矩阵(未必任何方阵都有逆矩阵),不是方阵的矩阵肯定没有逆矩阵。

代码语言:javascript复制
X = np.arange(16).reshape((2, 8))
print(np.linalg.inv(X))
'''
LinAlgError: Last 2 dimensions of the array must be square
'''

可是在机器学习中,很多时候需要求矩阵的逆。对于这种非方阵,可以求伪逆矩阵。

代码语言:javascript复制
pinvX = np.linalg.pinv(X)
print(pinvX.shape)
'''
(8, 2)
'''

X 原矩阵的形状为 (2, 8),而 X 的伪逆矩阵 pinvX 为 (8, 2),它们之间满足矩阵 X 和 X 的伪逆矩阵进行矩阵相乘的运算,结果为单位矩阵。

代码语言:javascript复制
print(X.dot(pinvX))
'''
array([[ 1.00000000e 00, -2.49800181e-16],
       [ 6.66133815e-16,  1.00000000e 00]])
'''

References:

  1. Python3入门机器学习 经典算法与应用: https://coding.imooc.com/class/chapter/169.html#Anchor

0 人点赞