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
魔法方法来测试时间。
%%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 次。
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:
- Python3入门机器学习 经典算法与应用: https://coding.imooc.com/class/chapter/169.html#Anchor