在人工智能的研发中,其本质就是把一切问题转化为数学问题,所以数学运算非常重要。很多数学运算采用的都是numpy这个库,因为它提供了非常多的科学计算的方法,能让我们的工作变得非常便利,这一章我将从numpy的基本使用开始,逐渐解决掉那些数学问题,让Python与数学能够更紧密的结合在一起。
numpy的本质其实还是一个多维数组,虽然我们之前学习过数组对象(Python中的list或者tuple)和numpy的数据看似一样,但是数组是无法直接参与数值运算的,而numpy对象却可以。
import numpy as np
arr1 = np.array([1, 2, 3, 4, 5, 6])
arr2 = np.array([[1, 2, 3, 4, ], [5, 6, 7, 8, ]])
print(arr1, arr1.shape, arr1.dtype)
print(arr2, arr2.shape, arr1.dtype) # shape获取数组形状2行4列,dtype获取数组中元素类型
如果我们创建数组时,元素类型不一样,numpy会给我们自动处理成一样的。
arr3 = np.array([1, 2.5, 3]) # 只要数组元素中出现float类型,就会全部处理成float
print(arr3, arr3.dtype)
arr4 = np.array(['4', 5, 5.6]) # 只要数组元素中出现str,就会全部处理成str
print(arr4, arr4.dtype)
numpy的访问与Python中list或者tuple访问原理一样,方法也非常类似,只不过是加了一个纬度的概念。
# 首先我们定义了一个二维数组
arr5 = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]])
print(arr5[1]) # 第一行 [4 5 6]
print(arr5[1][0]) # 第一行第0个 4
print(arr5[:, 2]) # 所有行第2个 [ 3 6 9 12]
print(arr5[:2]) # 前2行 [[1 2 3] [4 5 6]]
print(arr5[1, :]) # 第1行所有列 [4 5 6]
print(arr5[:, 1:2]) # 所有行第2到第3列 [[ 2] [ 5] [ 8] [11]]
对于二维来说,如果有逗号,逗号前是行筛选,逗号后是列筛选。对于n维来说,第一个逗号前是第一维,后面依次是二维三维等。
numpy对象有一个shape属性,在Python基础中,对于形状并不敏感,而在科学计算中,形状却很重要,在后面的算法模型计算中,我们会使用地很频繁。
# 定义一个6行3列的numpy数组对象
arr6 = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12],
[13, 14, 15],
[16, 17, 18]])
print(arr6.shape) # 注意看每次打印的结果
print(arr6.reshape(2, 9)) # 得到一个新的形状,原来的对象不变
print(arr6.shape, arr6)
print(arr6.resize(2, 9)) # 无返回值,真正修改
print(arr6.shape, arr6)
我们在这段代码中,分别使用了reshape和resize两种方法来对数组进行形状上的改变。其中reshape只是返回改变形状后的预览状态,或者说如果我们要使用这个结果只能把结果赋值给一个单独的变量,然后再进行使用。resize方法的返回结果为空,但是它却真正的改变了组数的形状,仔细看打印结果你就能够发现这两种形状操作方法的区别了。
降维是人工智能算法中非常常用且重要的一个操作,原因是有时我们去描述一个事物的特征时,会有非常多的维度,但过多的维度会给我们的计算带来麻烦,这个时候我们就需要去降低它的维度,然后再进行计算。
arr7 = np.array([[1, 10, 100], [2, 20, 200], [3, 30, 300]])
# 按照数组的行顺序降至一维
print(arr7)
print(arr7.ravel())
print(arr7.reshape(-1))
print(arr7.flatten())
# 按照大小顺序降至一维
print(arr7.ravel(order="F"))
print(arr7.reshape(-1, order="F"))
print(arr7.flatten(order="F"))
降维后再进行修改
print('ravel:{}'.format(arr7.ravel()))
arr7.ravel()[1] = 1000
print(arr7)
print('reshape: {}'.format(arr7.reshape(-1)))
arr7.reshape(-1)[2] = 3000
print(arr7)
print('flatten: {}'.format(arr7.flatten()))
arr7.flatten()[0] = 2000
print(arr7)
从结果中,我们看到通过flatten方法实现的降维返回的是复制的操作,如果要用,那么只能把结果赋值给另外的变量了。它并没有影响原来数组的结果。通过ravel和reshape两个方法,返回的则是视图,也就是通过对视图的修改,是会直接影响到原数组中的值的。
arr8 = np.array([[1, 10, 100], [2, 20, 200], [3, 30, 300]])
arr9 = np.array([1, 2, 3])
arr10 = np.array([[5], [6], [7]])
# 纵向堆叠
print(np.vstack([arr8, arr9]))
print(np.row_stack([arr8, arr9]))
# 横向合并
print(np.hstack([arr8, arr10]))
print(np.column_stack([arr8, arr10]))
需要注意的是:多个数组横向堆叠时,要保证行数相同,纵向合并,则要保证列数相同。
在以前,我们如果要对两个同形状的数组进行对应位置的四则运算时,我们必须要对两个数组进行循环处理,代码量上来说并不少,并且容易出错。有了NumPy之后,这些运算将会变的非常的简单。
import numpy as np
arr1 = np.array([11, 12, 13])
arr2 = np.array([21, 22, 23])
arr3 = np.array([31, 32, 33])
print(arr1 + arr2)
print(arr1 + arr2 + arr3)
print(arr1 - arr2)
print(arr1 - arr2 - arr3)
print(arr1 * arr2)
print(arr1 * arr2 * arr3)
print(arr1 / arr2)
print(arr1 / arr2 / arr3)
print(arr1 // arr2)
print(arr1 % arr2)
print(arr1 ** arr2)
print(np.add(arr1, arr2))
print(np.add(np.add(arr1, arr2), arr3))
print(np.subtract(arr1, arr2))
print(np.multiply(arr1, arr2))
print(np.divide(arr1, arr2))
从代码的运行结果中我们可以看到,当我们使用符号进行四则运算的时候,是可以连续进行操作的。当我们使用对象的方法进行四则运算的时候,不可以连续进行操作,因为这个方法只接收两个参数。如果我们想要对多个数组对象进行操作的时候,我们必须使用方法嵌套的方式来进行操作。除了四则运算,在学习Python基础时,所学习的取余数、整除、幂运算等都是支持的。
print(arr1 <= arr2)
print(arr1 == arr2)
print(arr1 != arr2)
print(np.greater(arr1, arr2))
print(np.greater_equal(arr1, arr2))
print(np.less(arr1, arr2))
print(np.less_equal(arr1, arr2))
print(np.equal(arr1, arr2))
print(np.not_equal(arr1, arr2))
从结果上看,运用比较运算符可以返回布尔类型的值,也就是True和False。那我们什么时候会用到这样的运算呢?第一种情况是从数组中查询满足条件的元素,第二种情况是根据判断的结果执行不同的操作,示例代码如下:
arr3 = np.array([23, 12, 25])
arr4 = np.array([21, 15, 23])
print(arr3[arr3 > arr4]) # 取出arr3中元素大于arr4的
print(arr3[arr3 > 24]) # 取出arr3中元素大于24的
print(np.where(arr3 > 24, 0, arr3)) # 类似三元表达式,把大于24的修改成0,其他不变
print(list(0 if x > 24 else x for x in arr3))
print(np.where(arr4 > 16, 0, arr4))
上面我们所有的运算都是基于相同形状的数组,那么当数组形状不同时,能够让它们之间进行运算吗?答案是肯定的,但是有相应的规则,不能随意计算,这种计算就叫做广播运算。
# 1 广播运算,末尾的纬度值加上去
arr3 = np.arange(60).reshape(5, 4, 3)
arr4 = np.arange(12).reshape(4, 3)
print(arr3)
print(arr4)
print(arr3 + arr4)
# 2 纬度值有一个为1
arr5 = np.arange(60).reshape(5, 4, 3)
arr6 = np.arange(4).reshape(4, 1)
print(arr5)
print(arr6)
print(arr5 + arr6)
# 3 arr7会自动补齐,类似上面纬度值有一个为1
arr7 = np.arange(12).reshape(4, 3)
arr8 = np.array([1, 2, 3])
print(arr7)
print(arr8)
print(arr7 + arr8)
arr9 = np.arange(60).reshape(5, 4, 3)
arr10 = np.arange(8).reshape(4, 2)
print(arr9)
print(arr10)
# print(arr9 + arr10) # 不在上述三种讨论范围内,无法运算
其实,广播运算中的广播就是一对多,它的规律就是两者有相似的地方可以对应的上就能运算,缺少的部分,会自动用相同的部分补齐。
numpy提供给我们一些常见的函数,除了np.pi或者np.e这样的常量函数,numpy也提供给我们很多数学函数供我们直接调用。
import numpy as np
arr1 = np.array([1.3, 1.5, -1.8, 2.4, 3.2])
arr2 = np.array([1, 2, 3, 4, 5])
print(np.fabs(arr1)) # 绝对值
print(np.ceil(arr1)) # 向上取整
print(np.floor(arr1)) # 向下取整
print(np.round(arr1)) # 四舍五入
print(np.fmod(arr2, arr1)) # 余数
print(np.modf(arr1)) # 取小数部分和整数部分
print(np.sqrt(arr2)) # 算法平方根
print(np.square(arr2)) # 平方
print(np.exp(arr2)) # 以e为底的指数
print(np.power(arr2, 3)) # 各元素的3次方
print(np.log2(arr2)) # 以2为底的对数
print(np.log10(arr2)) # 以10为底的对数
print(np.log(arr2)) # 以e为底的对数
数组对象有几个维度就有几个轴,对于我们常见的二维数组来说,轴0是竖直方向,轴1是水平方向。
import numpy as np
arr1 = np.array([[3, 7, 25, 8, 15, 20],
[4, 5, 6, 9, 14, 21]])
print(arr1)
print(np.max(arr1)) # 所有数组元素最大值
print(np.max(arr1, axis=1)) # 轴1最大值
print(np.max(arr1, axis=0)) # 轴0最大值
numpy提供了很多可以按照轴方向来计算的函数。
print(np.min(arr1, axis=0)) # 最小值
print(np.min(arr1, axis=1))
print(np.mean(arr1, axis=0)) # 平均值
print(np.median(arr1, axis=0)) # 中位数
print(np.sum(arr1, axis=1)) # 求和