【机器学习】熟练使用 NumPy

版权声明:欢迎转载 https://blog.csdn.net/antony1776/article/details/84141586

1 引言

Python 用列表(list)保存一组值,可当做数组使用,但由于列表的元素可以是任何对象,因此列表中保存的是对象的指针(引用),对于数值运算来说,这种结构显然是浪费内存和 CPU 时间的。

Python 的 array 模块,与 list 不同,能直接保存数值,但是不支持多维数组,没有各种运算函数,因此也不适合做数值运算。

NumPy 是为数值计算而生的,为 Python 带来了真正的多维数组功能,并且提供了十分丰富的对数组进行处理和运算的函数集。其对常用的数学函数进行数组化,使这些数学函数能直接对数组进行运算,将本来需要在 Python 中进行的循环运算,转移到高效的库函数中,充分利用这些函数能明显地提高程序的运算速度。

其提供了两种基本对象:ndarray 和 ufunc。ndarray 是存储单一数据类型的多维数组,ufunc 是能够对数组进行处理的函数。

2 ndarray

demo = np.array([[0,1,2],[3,4,5],[6,7,8]], dtype=np.float32)

下图为数组 demo 的内存结构:

(dtype, dim count, dimensions, strides, data)

在这里插入图片描述

strides: 每个轴上相邻两个元素的地址差

通过切片下标得到的新数组是原始数组的视图,data 是共享的,但是新数组的 strides 属性和 data 属性会发生变化。

数据存储区域(data)保存着数组中所有元素的二进制数据,dtype 对象定义了如何将二进制数据转化为可用的值。

2.1 shape

可以通过 shape 获取数组的形状,也就是数组各个轴的大小。
shape 是一个描述数组各个轴长度的元组(tuple),更改 shape 属性并不是对数组进行转置,而只是改变每个轴的大小,数组元素在内存中的位置并没有改变。

import numpy as np

a = np.array([[1,2,3,4], [4,6,7,8], [9,10,11,12]])

# =========== shape =============
a.shape == (3,4)        # (3,4)
a.shape = 4,3           # 更改轴属性,并不是对数组进行转置,而只是改变每个轴的大小,数组元素在内存中的位置并没有改变。
a.shape = 2, -1         # -1 表示自动计算此轴的长度,为 (2,,)
b = a.reshape((2, -1))  # a.reshape(2, -1), 创建新的数组,a 保持不变;
a[0][0] = 100           # 内存数据并没有发生变更!
b[0][0] == 100          # true
a.dtype == 'int32'      # true

# =========== typeDict =============
np.typeDict['int32'] == np.int32        # true
np.typeDict['d'] == np.float64          # true
np.typeDict['double'] == np.float64     # true
np.typeDict['float'] == np.float64      # true

set(np.typeDict.values())               # 所有的数据类型

2.2 创建数组

可以通过传入特定的 [] 来创建 ndarray

a = np.array([1, 2, 3, 4])
b = np.array((5, 6, 7, 8))
c = np.array([[1, 2, 3, 4],[4, 5, 6, 7], [7, 8, 9, 10]])

除此之外,还有其他更高效的创建数组的方式。

  • arange() 类似于 range(), 通过制定 (start, stop, step) 创建表示等差数列的一维数组,不包含终值。
  • linspace() 通过制定 (start, stop, num) 创建表示等差数列的一维数组,可以通过 endpoint 参数指定是否包含终值。
  • logspace() 与 linespace() 相似,创建等比数列数组。
  • empty(),zeros(),ones(): 创建指定形状和类型的数组
  • fromstring(),frombuffer(),fromfile(): 从字节流或文件中创建数组
  • fromfunction() : 通过特定函数生成特定形状的数组
# =========== arange,logspace,linespace =============
np.arange(0, 1, 0.1)            # [ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 ]
np.linspace(0, 1, 10)           # [ 0. , 0.11111111, 0.22222222, 0.33333333, 0.44444444, 0.55555556, 0.66666667, 0.77777778, 0.88888889, 1. ]
np.linspace(0, 1, 10, endpoint=False) # [ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 ]
np.logspace(0, 2, 5, base=10)   # [ 1. , 3.16227766, 10. , 31.6227766 , 100. ]

# =========== empty,zeros,ones =============
np.empty((2,3),np.int)
np.zeros(4, np.float)
np.ones((3,4))                  # defalut is np.float

# =========== fromstring,frombuffer,fromfile =============
s = "abcdefgh"
np.fromstring(s, dtype=np.int8) # [ 97, 98, 99, 100, 101, 102, 103, 104]

# =========== fromfunction =============
np.fromfunction(lambda x, y: x*y, (2,2)) # [[0, 0, 0], [0, 1, 2], [0, 2, 4]]

2.3 元素存取与切片

切片:(start, stop, step)

  1. 与列表不同,切片后的数组只是原数组的视图,共享内部数据与列表不同,切片后的数组只是原数组的视图,共享内部数据
  2. 支持整数列表、整数数组和布尔数组等高级下标存取方法,使用列表作为下标得到的数组不和原始数组共享数据
  3. 多维数组与一维数组在数据存储上没有区别
#  =========== slice =============
a = np.arange(10)   # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
a[3:5]              # a[3], a[4] 不包括 a[5]
a[:-1]              # 排除最后一个数据
a[1:3] = 22,22      # [0, 22, 22, 3, 4, 5, 6, 7, 8, 9]
a[::2]              # [0, 2, 4, 6, 8]
a[8::-2]            # [8, 6, 4, 2, 0]

# =========== view =============

b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

a1 = a[3:5]
a1[0] = 88
a1[0] == a[3] # True

b1 = b[3:5]
b1[0] = 88
b1[0] == b[3] # False

# =================== 整数列表,整数数组,布尔数组 ============
c = np.arange(10, 1, -1)    # [10, 9, 8, 7, 6, 5, 4, 3, 2]
c[[1,1,1,1]]                # [10, 10, 10, 10]
c[[0,1,2]] = 99,99,99       # [99, 99, 99, 7, 6, 5, 4, 3, 2]

# =================== 多维数组切片 ===============
d = np.fromfunction(lambda x,y: 10*x + y, (6,6)) # d = np.arange(0, 60, 10).reshape(-1, 1) + np.arange(0, 6)
"""
array([[  0.,   1.,   2.,   3.,   4.,   5.],
       [ 10.,  11.,  12.,  13.,  14.,  15.],
       [ 20.,  21.,  22.,  23.,  24.,  25.],
       [ 30.,  31.,  32.,  33.,  34.,  35.],
       [ 40.,  41.,  42.,  43.,  44.,  45.],
       [ 50.,  51.,  52.,  53.,  54.,  55.]])
"""
d[0, 3:5]           # [3, 4] 
d[4:, 4:]           # [[44, 45], [54, 55]]
d[:, 2]             # [2, 12, 22, 32, 42, 52]
d[2::2, ::2]        # [[20, 22, 24], [40, 42, 44]]

在这里插入图片描述

2.4 slice 对象

在[]中可以使用以冒号隔开的两个或三个整数表示切片,但是单独生成切片对象时需要使用 slice(start, stop, step) 创建,省略的参数使用 None,如 slice(None, None, 2)。

NumPy 提供了 s_ 对象来创建下标切片

np.s_[::2, 2:] # (slice(None, None, 2), slice(2, None, None))

2.5 结构数组

NumPy 数组中可以存储自定义的结构,首先要通过 np.dtype 定义结构的类型。

personttype = np.dtype({
    'names': ['name', 'age'],
    'formats': ['S32', 'i']
}, align=True)

a = np.array([("Zhang",32),("Wang",24)], dtype=persontype)

类似于 c 语言中的 struct

struct person
{
	char name[32];
	int age;
}

3 ufunc 运算

ufunc :univeral function,它是一种能对数组中每个元组进行操作的函数。NumPy 内置的许多 ufunc 函数都是 c 语言级别实现的,因此它们的计算速度非常快。

3.1 四则运算

在这里插入图片描述

a = np.arange(5) 			# [0, 1, 2, 3, 4]
b = np.arange(5, 0, -1)		# [4, 3, 2, 1, 0]
c1 = a + b 					# [5, 5, 5, 5, 5]
c2 = np.empty(5)			# [0, 0, 0, 0, 0]
c2 = np.add(a, b, c2)		# c2 == [5, 5, 5, 5, 5]

对于复杂的运算表达式,会因为产生大量的中间结果而降低程序的运算效率。比如 x=a*b+c, 相当于:

t = a * b
x = t + c # 产生临时数组 t
del t

可以通过分解为两个表达式,来减少一次内存分配:

x = a * b
x += c

3.2 比较与布尔运算

在这里插入图片描述

python 中原生的布尔运算使用 and, or, not 等关键字,无法被重载!因此数组的布尔运算只能通过相应的 ufunc 函数进行,这些函数的名称都以 “logical_” 开头。

np.logical_and, np.logical_not, np.logical_or, np.logical_xor

np.any(), np.all()

3.3 自定义 ufunc 函数

通过 frompyfunc() 将一个计算单个元素的函数转换为 ufunc 函数,这样就可以方便地进行数组计算了。

def mfun(v, arg1, arg2):
	return v*arg1 + arg2
	
a = np.arange(5) 	# [0, 1, 2, 3, 4]
u_mfun = np.frompyfunc(mfun, 3, 1)
u_mfun(a, 2, 2) 	# [2, 4, 6, 8, 10]

frompyfunc(func, nin, nout) : func 为原函数,nin 为 func 函数的参数个数,nout 为 func 函数的返回值个数

猜你喜欢

转载自blog.csdn.net/antony1776/article/details/84141586
今日推荐