原来线性代数这么有用!
请注意,本文编写于 980 天前,最后修改于 980 天前,其中某些信息可能已经过时。
Numpy(Numerical Python)是一个开源的Python科学计算库,用于快速处理任意维度的数组。
Numpy支持常见的数组和矩阵操作。对于同样的数值计算任务,使用Numpy比直接使用Python要简洁的多。
Numpy使用ndarray对象来处理多维数组,该对象是一个快速而灵活的大数据容器。
NumPy provides an N-dimensional array type, the ndarray,
which describes a collection of “items” of the same type.
NumPy提供了一个N维数组类型ndarray,它描述了相同类型的“items”的集合。
学生成绩数据
用ndarray进行存储:
import numpy as np
# 创建ndarray
score = np.array(
[[80, 89, 86, 67, 79],
[78, 97, 89, 67, 81],
[90, 94, 78, 67, 74],
[91, 91, 90, 67, 69],
[76, 87, 75, 67, 86],
[70, 79, 84, 67, 84],
[94, 92, 93, 67, 64],
[86, 85, 83, 67, 80]])
score
返回结果:
array([[80, 89, 86, 67, 79],
[78, 97, 89, 67, 81],
[90, 94, 78, 67, 74],
[91, 91, 90, 67, 69],
[76, 87, 75, 67, 86],
[70, 79, 84, 67, 84],
[94, 92, 93, 67, 64],
[86, 85, 83, 67, 80]])
提问:
使用Python列表可以存储一维数组,通过列表的嵌套可以实现多维数组,那么为什么还需要使用Numpy的ndarray呢?
在这里我们通过一段代码运行来体会到ndarray的好处
import random
import time
import numpy as np
a = []
for i in range(100000000):
a.append(random.random())
# 通过%time魔法方法, 查看当前行的代码运行一次所花费的时间
%time sum1=sum(a)
b=np.array(a)
%time sum2=np.sum(b)
其中第一个时间显示的是使用原生Python计算时间,第二个内容是使用numpy计算时间:
CPU times: user 852 ms, sys: 262 ms, total: 1.11 s
Wall time: 1.13 s
CPU times: user 133 ms, sys: 653 µs, total: 133 ms
Wall time: 134 ms
从中我们看到ndarray的计算速度要快很多,节约了时间。
机器学习的最大特点就是大量的数据运算,那么如果没有一个快速的解决方案,那可能现在python也在机器学习领域达不到好的效果。
计算量大
Numpy专门针对ndarray的操作和运算进行了设计,所以数组的存储效率和输入输出性能远优于Python中的嵌套列表,数组越大,Numpy的优势就越明显。
思考:
ndarray为什么可以这么快?
ndarray到底跟原生python列表有什么不同呢,请看一张图:
numpy内存地址
Python原生list中可以存放数字也可以存放字符串,但是numpy的list中只能存放同一种类型的元素。
从图中我们可以看出ndarray在存储数据的时候,数据与数据的地址都是连续的,这样就给使得批量操作数组元素时速度更快。
这是因为ndarray中的所有元素的类型都是相同的,而Python列表中的元素类型是任意的,所以ndarray在存储元素时内存可以连续,而python原生list就只能通过寻址方式找到下一个元素,这虽然也导致了在通用性能方面Numpy的ndarray不及Python原生list,但在科学计算中,Numpy的ndarray就可以省掉很多循环语句,代码使用方面比Python原生list简单的多。
numpy内置了并行运算功能,当系统有多个核心时,做某种计算时,numpy会自动做并行计算
Numpy底层使用C语言编写,内部解除了GIL(全局解释器锁),其对数组的操作速度不受Python解释器的限制,所以,其效率远高于纯Python代码。
数组属性反映了数组本身固有的信息。
属性名字 | 属性解释 |
---|---|
ndarray.shape | 数组维度的元组 |
ndarray.ndim | 数组维数 |
ndarray.size | 数组中的元素数量 |
ndarray.itemsize | 一个数组元素的长度(字节) |
ndarray.dtype | 数组元素的类型 |
import numpy as np
score_data = np.array([[80, 89, 86, 67, 79],
[78, 97, 89, 67, 81],
[90, 94, 78, 67, 74],
[91, 91, 90, 67, 69],
[76, 87, 75, 67, 86],
[70, 79, 84, 67, 84],
[94, 92, 93, 67, 64],
[86, 85, 83, 67, 80]])
print(score_data.shape)
# 输出(8, 5),说明该多维数组为8行5列
print(score_data.ndim)
# 输出2,多维数组的维度
print(score_data.size)
# 输出40,多维数组中元素的数量
print(score_data.itemsize)
# 4 一个数组元素的长度(字节)
print(score_data.dtype)
# int32 数组元素的类型
首先创建一些数组。
# 创建不同形状的数组
>>> a = np.array([[1,2,3],[4,5,6]])
>>> b = np.array([1,2,3,4])
>>> c = np.array([[[1,2,3],[4,5,6]],[[1,2,3],[4,5,6]]])
分别打印出形状
>>> a.shape
>>> b.shape
>>> c.shape
(2, 3) # 二维数组
(4,) # 一维数组
(2, 2, 3) # 三维数组
如何理解数组的形状?
二维数组
三维数组
>>> type(score.dtype)
<type 'numpy.dtype'>
dtype是numpy.dtype类型,先看看对于数组来说都有哪些类型
名称 | 描述 | 简写 |
---|---|---|
np.bool | 用一个字节存储的布尔类型(True或False) | 'b' |
np.int8 | 一个字节大小,-128 至 127 | 'i' |
np.int16 | 整数,-32768 至 32767 | 'i2' |
np.int32 | 整数,-2^31 至 2^32 -1 | 'i4' |
np.int64 | 整数,-2^63 至 2^63 - 1 | 'i8' |
np.uint8 | 无符号整数,0 至 255 | 'u' |
np.uint16 | 无符号整数,0 至 65535 | 'u2' |
np.uint32 | 无符号整数,0 至 2^32 - 1 | 'u4' |
np.uint64 | 无符号整数,0 至 2^64 - 1 | 'u8' |
np.float16 | 半精度浮点数:16位,正负号1位,指数5位,精度10位 | 'f2' |
np.float32 | 单精度浮点数:32位,正负号1位,指数8位,精度23位 | 'f4' |
np.float64 | 双精度浮点数:64位,正负号1位,指数11位,精度52位 | 'f8' |
np.complex64 | 复数,分别用两个32位浮点数表示实部和虚部 | 'c8' |
np.complex128 | 复数,分别用两个64位浮点数表示实部和虚部 | 'c16' |
np.object_ | python对象 | 'O' |
np.string_ | 字符串 | 'S' |
np.unicode_ | unicode类型 | 'U' |
创建数组的时候指定类型
>>> a = np.array([[1, 2, 3],[4, 5, 6]], dtype=np.float32)
>>> a.dtype
dtype('float32')
>>> arr = np.array(['python', 'tensorflow', 'scikit-learn', 'numpy'], dtype = np.string_)
>>> arr
array([b'python', b'tensorflow', b'scikit-learn', b'numpy'], dtype='|S12')
In [1]: import numpy as np
In [2]: ones = np.ones([4,8])
In [3]: ones
Out[3]:
array([[1., 1., 1., 1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1., 1., 1., 1.]])
In [4]: np.zeros_like(ones)
Out[4]:
array([[0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0.]])
两种方式的对比:
In [5]: a = np.array([[1,2,3],[4,5,6]])
In [6]: a1 = np.array(a) # 类似于深拷贝
In [7]: a2 = np.asarray(a) # 类似于浅拷贝
In [8]: a[0, 0] = 100 # 对a中的[0, 0]位置元素赋值
In [9]: a1
Out[9]:
array([[1, 2, 3], # a1未改变
[4, 5, 6]])
In [10]: a2
Out[10]:
array([[100, 2, 3], # a2改变
[ 4, 5, 6]])
# 生成等间隔的数组
np.linspace(0, 100, 11)
返回结果:
array([ 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100.])
np.arange(10, 50, 2)
返回结果:
array([10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42,
44, 46, 48])
# 生成10^x
np.logspace(0, 2, 3)
返回结果:
array([ 1., 10., 100.])
正态分布是一种概率分布。正态分布是具有两个参数μ和σ的连续型随机变量的分布,第一参数μ是服从正态分布的随机变量的均值,第二个参数σ是此随机变量的方差,所以正态分布记作N(μ,σ )。
正态分布函数图像
生活、生产与科学实验中很多随机变量的概率分布都可以近似地用正态分布来描述。
μ决定了其位置,其标准差σ决定了分布的幅度。当μ = 0,σ = 1时的正态分布是标准正态分布。
标准差如何来?
其中M为平均值,n为数据总个数,σ 为标准差,σ ^2可以理解一个整体为方差。
np.random.randn(d0, d1, …, dn)
功能:从标准正态分布中返回一个或多个样本值
np.random.normal(*loc=0.0*, *scale=1.0*, *size=None*)
loc:float
此概率分布的均值(对应着整个分布的中心centre)
scale:float
此概率分布的标准差(对应于分布的宽度,scale越大越矮胖,scale越小,越瘦高)
size:int or tuple of ints
输出的shape,默认为None,只输出一个值
np.random.standard_normal(size=None)
返回指定形状的标准正态分布的数组。
x1 = np.random.normal(1.75, 1, 100000000)
返回结果:
array([2.90646763, 1.46737886, 2.21799024, ..., 1.56047411, 1.87969135,
0.9028096 ])
# 生成均匀分布的随机数
x1 = np.random.normal(1.75, 1, 100000000)
# 画图看分布状况
# 1)创建画布
plt.figure(figsize=(20, 10), dpi=100)
# 2)绘制直方图
plt.hist(x1, 1000)
# 3)显示图像
plt.show()
# 生成均匀分布的随机数
x2 = np.random.uniform(-1, 1, 100000000)
返回结果:
array([ 0.22411206, 0.31414671, 0.85655613, ..., -0.92972446,
0.95985223, 0.23197723])
画图看分布状况:
import matplotlib.pyplot as plt
# 生成均匀分布的随机数
x2 = np.random.uniform(-1, 1, 100000000)
# 画图看分布状况
# 1)创建画布
plt.figure(figsize=(10, 10), dpi=100)
# 2)绘制直方图
plt.hist(x=x2, bins=1000) # x代表要使用的数据,bins表示要划分区间数
# 3)显示图像
plt.show()
均匀分布
一维、二维、三维的数组如何索引?
二维数组索引方式:
# 二维的数组,两个维度
stock_change[0, 0:3]
返回结果:
array([-0.03862668, -1.46128096, -0.75596237])
# 三维
a1 = np.array([ [[1,2,3],[4,5,6]], [[12,3,34],[5,6,7]]])
# 返回结果
array([[[ 1, 2, 3],
[ 4, 5, 6]],
[[12, 3, 34],
[ 5, 6, 7]]])
# 索引、切片
>>> a1[0, 0, 1] # 输出: 2
# 在转换形状的时候,一定要注意数组的元素匹配
stock_change.reshape([5, 4])
stock_change.reshape([-1,10]) # 数组的形状被修改为: (2, 10), -1: 表示通过待计算
stock_change.resize([5, 4])
# 查看修改后结果
stock_change.shape
(5, 4)
stock_change.T.shape
(4, 5)
stock_change.astype(np.int32)
arr = np.array([[[1, 2, 3], [4, 5, 6]], [[12, 3, 34], [5, 6, 7]]])
arr.tostring()
如果遇到
IOPub data rate exceeded.
The notebook server will temporarily stop sending output
to the client in order to avoid crashing it.
To change this limit, set the config variable
`--NotebookApp.iopub_data_rate_limit`.
这个问题是在jupyer当中对输出的字节数有限制,需要去修改配置文件
创建配置文件
jupyter notebook --generate-config
vi ~/.jupyter/jupyter_notebook_config.py
取消注释,多增加
## (bytes/sec) Maximum rate at which messages can be sent on iopub before they
# are limited.
c.NotebookApp.iopub_data_rate_limit = 10000000
但是不建议这样去修改,jupyter输出太大会崩溃
temp = np.array([[1, 2, 3, 4],[3, 4, 5, 6]])
>>> np.unique(temp)
array([1, 2, 3, 4, 5, 6])
# 生成10名同学,5门功课的数据
>>> score = np.random.randint(40, 100, (10, 5))
# 取出最后4名同学的成绩,用于逻辑判断
>>> test_score = score[6:, 0:5]
# 逻辑判断, 如果成绩大于60就标记为True 否则为False
>>> test_score > 60
array([[ True, True, True, False, True],
[ True, True, True, False, True],
[ True, True, False, False, True],
[False, True, True, True, True]])
# BOOL赋值, 将满足条件的设置为指定的值-布尔索引
>>> test_score[test_score > 60] = 1
>>> test_score
array([[ 1, 1, 1, 52, 1],
[ 1, 1, 1, 59, 1],
[ 1, 1, 44, 44, 1],
[59, 1, 1, 1, 1]])
# 判断前两名同学的成绩[0:2, :]是否全及格
>>> np.all(score[0:2, :] > 60)
False
# 判断前两名同学的成绩[0:2, :]是否有大于90分的
>>> np.any(score[0:2, :] > 80)
True
通过使用np.where能够进行更加复杂的运算
# 判断前四名学生,前四门课程中,成绩中大于60的置为1,否则为0
temp = score[:4, :4]
np.where(temp > 60, 1, 0)
# 判断前四名学生,前四门课程中,成绩中大于60且小于90的换为1,否则为0
np.where(np.logical_and(temp > 60, temp < 90), 1, 0)
# 判断前四名学生,前四门课程中,成绩中大于90或小于60的换为1,否则为0
np.where(np.logical_or(temp > 90, temp < 60), 1, 0)
如果想要知道学生成绩最大的分数,或者做小分数应该怎么做?
在数据挖掘/机器学习领域,统计指标的值也是我们分析问题的一种方式。常用的指标如下:
axis=1
按行输出每行的最值,axis=0
按列输出每列的最值。
进行统计的时候,axis 轴的取值并不一定,Numpy中不同的API轴的值都不一样,在这里,axis 0代表列, axis 1代表行去进行统计
# 接下来对于前四名学生,进行一些统计运算
# 指定列 去统计
temp = score[:4, 0:5]
print("前四名学生,各科成绩的最大分:{}".format(np.max(temp, axis=0)))
print("前四名学生,各科成绩的最小分:{}".format(np.min(temp, axis=0)))
print("前四名学生,各科成绩波动情况:{}".format(np.std(temp, axis=0)))
print("前四名学生,各科成绩的平均分:{}".format(np.mean(temp, axis=0)))
结果:
前四名学生,各科成绩的最大分:[96 97 72 98 89]
前四名学生,各科成绩的最小分:[55 57 45 76 77]
前四名学生,各科成绩波动情况:[16.25576821 14.92271758 10.40432602 8.0311892 4.32290412]
前四名学生,各科成绩的平均分:[78.5 75.75 62.5 85. 82.25]
如果需要统计出某科最高分对应的是哪个同学?
print("前四名学生,各科成绩最高分对应的学生下标:{}".format(np.argmax(temp, axis=0)))
结果:
前四名学生,各科成绩最高分对应的学生下标:[0 2 0 0 1]
需要了解基础的矩阵知识!!!官方教材:《工程数学 · 线性代数(同济 · 第六版)》
学生成绩计算
计算公式:
矩阵运算:
PS C:\Users\myxc> ipython
Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.17.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import numpy as np
In [2]: a = np.array([[80, 86],
...: [82, 80],
...: [85, 78],
...: [90, 90],
...: [86, 82],
...: [82, 90],
...: [78, 80],
...: [92, 94]])
In [3]: b = np.array([[0.7], [0.3]])
In [4]: c = np.matmul(a, b)
In [5]: c
Out[5]:
array([[81.8],
[81.4],
[82.9],
[90. ],
[84.8],
[84.4],
[78.6],
[92.6]])
In [6]: d = np.dot(a,b)
In [7]: d
Out[7]:
array([[81.8],
[81.4],
[82.9],
[90. ],
[84.8],
[84.4],
[78.6],
[92.6]])
In [8]: c == d
Out[8]:
array([[ True],
[ True],
[ True],
[ True],
[ True],
[ True],
[ True],
[ True]])
np.matmul和np.dot的区别:
二者都是矩阵乘法。 np.matmul中禁止矩阵与标量的乘法。 在矢量乘矢量的內积运算中,np.matmul与np.dot没有区别。对比如下:
In [1]: import numpy as np
In [2]: matrix_01 = np.array([[2, 3, 4, 3, 2, 5],
...: [6, 7, 2, 3, 4, 2]])
In [3]: np.matmul(matrix_01, 10)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-3-84098b37479e> in <module>
----> 1 np.matmul(matrix_01, 10)
ValueError: matmul: Input operand 1 does not have enough dimensions (has 0, gufunc core with signature (n?,k),(k,m?)->(n?,m?) requires 1)
In [4]: np.dot(matrix_01, 10)
Out[4]:
array([[20, 30, 40, 30, 20, 50],
[60, 70, 20, 30, 40, 20]])
In [1]: import numpy as np
In [2]: arr = np.array([[1, 2, 3, 2, 1, 4], [5, 6, 1, 2, 3, 1]])
In [3]: arr + 1
Out[3]:
array([[2, 3, 4, 3, 2, 5],
[6, 7, 2, 3, 4, 2]])
In [4]: arr / 2
Out[4]:
array([[0.5, 1. , 1.5, 1. , 0.5, 2. ],
[2.5, 3. , 0.5, 1. , 1.5, 0.5]])
In [5]: a = [1, 2, 3, 4, 5]
In [6]: a * 3
Out[6]: [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
对比Python原生list于数字的运算和numpy数组与数字的运算的差别。
In [1]: import numpy as np
In [2]: arr1 = np.array([[1, 2, 3, 2, 1, 4], [5, 6, 1, 2, 3, 1]])
...: arr2 = np.array([[1, 2, 3, 4], [3, 4, 5, 6]])
In [3]: np.matmul(arr1, arr2)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-3-c392db06761e> in <module>
----> 1 np.matmul(arr1, arr2)
ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 2 is different from 6)
In [4]: arr3 = arr1.T
In [5]: np.matmul(arr1, arr3)
Out[5]:
array([[35, 31],
[31, 76]])
矩阵的运算要符合规则!!!
数组在进行矢量化运算时,要求数组的形状是相等的。当形状不相等的数组执行算术运算的时候,就会出现广播机制,该机制会对数组进行扩展,使数组的shape属性值一样,这样,就可以进行矢量化运算了。下面通过一个例子进行说明:
In [1]: import numpy as np
In [2]: arr1 = np.array([[0],[1],[2],[3]])
...: arr1.shape
Out[2]: (4, 1)
In [3]: arr2 = np.array([1,2,3])
...: arr2.shape
Out[3]: (3,)
In [4]: arr1+arr2
Out[4]:
array([[1, 2, 3],
[2, 3, 4],
[3, 4, 5],
[4, 5, 6]])
上述代码中,数组arr1是4行1列,arr2是1行3列。这两个数组要进行相加,按照广播机制会对数组arr1和arr2都进行扩展,使得数组arr1和arr2都变成4行3列。
下面通过一张图来描述广播机制扩展数组的过程:
广播机制实现了时两个或两个以上数组的运算,即使这些数组的shape不是完全相同的,只需要满足如下任意一个条件即可。
广播机制需要扩展维度小的数组,使得它与维度最大的数组的shape值相同,以便使用元素级函数或者运算符进行运算。
如果是下面这样,则不匹配:
A (1d array): 10
B (1d array): 12
A (2d array): 2 x 1
B (3d array): 8 x 4 x 3
思考:下面两个ndarray是否能够进行运算?
arr1 = np.array([[1, 2, 3, 2, 1, 4], [5, 6, 1, 2, 3, 1]])
arr2 = np.array([[1], [3]])
可以,代码如下:
In [1]: import numpy as np
In [2]: arr1 = np.array([[1, 2, 3, 2, 1, 4], [5, 6, 1, 2, 3, 1]])
...: arr2 = np.array([[1], [3]])
In [3]: arr1 + arr2
Out[3]:
array([[2, 3, 4, 3, 2, 5],
[8, 9, 4, 5, 6, 4]])
----- END -----