再次走进 Numpy(1)

179 阅读12分钟

这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战

今天是农历的大年初一,首先在这里给大家拜个年,祝大家新年快乐!身体健康!事事顺心!万事如意!

在新的一年里,希望自己的表达能力、用于语言描述技术能力会有所提高。开篇是一篇关于 Numpy 的文章。那么今天会谈到那些 Numpy 方面内容,首先会聊一聊使用 numpy 如何创建一个数组。

数组的创建

import numpy as np

Python是一种比较慢的语言,在复杂的数学运算方面比较吃力。在 Python 中,数值是对象,不是数字类型或任何特定的数据类型,而是指向内存中实际数字的指针。NumPy 操作对象主要是同一类型的多维数组。是一个元素(通常是数字)的表格,所有的元素都是相同的类型,由非负整数的元组来索引。在Numpy中,维度被称为轴。numpy数组是一个单一的对象,用于收集数组中的所有数字。这些数字在存储上非常有效,在计算时也更快。

a = np.arange(30)
a = a.reshape(3,10)
a
array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]])
print(f'size:{a.size}')
print(f'dimension:{a.ndim}')
print(f'shape:{a.shape}')
print(f'dtype:{a.dtype}')
print(f'flags:{a.flags}')
print(f'strides:{a.strides}')

size:30
dimension:2
shape:(3, 10)
dtype:int64
flags:  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

strides:(80, 8)

numpy_001.png

  • size 表示元素数量
  • OWNDATA 表示
  • C_CONTIGUOUS 表示按行连续在内存
  • F_CONTIGUOUS 表示按列连续在内存
  • strides 80 和 8 因为 3 行 100 列 2 维矩阵,这是 64 维操作系统,所以每一个整数占 8 位,所以列移动一个位置需要在内存移动 8 位,移动一行 8 * 10 等于 80
b = np.array([[.4,.5,.6],[.4,.5,.6]])
print(f'size:{b.size}')
print(f'dimension:{b.ndim}')
print(f'shape:{b.shape}')
print(f'dtype:{b.dtype}')
print(f'flags:{b.flags}')
print(f'strides:{b.strides}')

size:6
dimension:2
shape:(2, 3)
dtype:float64
flags:  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

strides:(24, 8)

在 Numpy 中提供多种多样方式来创建数组,也可以利用既有数组通过 slice 或者 reshape 操作来得到一个新数组,同时 Numpy 来贴心准备了一些我们在线性代数中常用的矩阵形式。这里我们就从这些方法挑选几个给大家介绍一下,如果大家想要了解更多 Numpy 创建数组的方法,请查看 Numpy 官方文档。如果您在实际工作中遇到那些情况需要以什么形式创建数组好的经验,也希望你多多留言。

zeros

调用 zeros 方法,然后传入你要创建矩阵的形状就可以得到一个全部元素为 0 的你想要形状的矩阵。也有 ones 是创建元素全部为 1 的矩阵。

np.zeros((2,3))
array([[0., 0., 0.],

[0., 0., 0.]])

在 Numpy 中也有 arange 方法,有点类似 python 的 range 方法,会返回一个 numpy 数组,从 0 开始向后以 1 间隔取 10 个整数,因为通常我们需要这样数列来对其研究

np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

tile 和 repeat

np.tile(2,(5,3))

tile 创建 2 有点像贴瓷砖,例如上面 tile 就是 2 ,然后我们 5 x 3 大小开始贴满 2

array([[2, 2, 2],[2, 2, 2],[2, 2, 2],[2, 2, 2],[2, 2, 2]])

上面例子,有点简单不足以表现 tile 的用法,下面方法,我们首先将序列在重复 3 次,然后将得到序列重复 5 次。

np.tile(np.arange(10),(5,3))
array([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1,2, 3, 4, 5, 6, 7, 8, 9],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1,2, 3, 4, 5, 6, 7, 8, 9],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1,2, 3, 4, 5, 6, 7, 8, 9],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1,2, 3, 4, 5, 6, 7, 8, 9],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1,2, 3, 4, 5, 6, 7, 8, 9]])

repeat 重复数组中元素次数,也可以指定重复的维度。

np.repeat([1,2],5)
array([1, 1, 1, 1, 1, 2, 2, 2, 2, 2])

也可以为每个元素指定重复次数,这里 5 对应元素 1 重复次数,3 对应 2 重复次数

np.repeat([1,2],(5,3))
array([1, 1, 1, 1, 1, 2, 2, 2])

linspace

np.linspace(1,10,10)

linspace 是从 1 开始到 10 ,并且包括 10 在内 10 个数,并且保持两个数之间是等间距。

array([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])

这里使用 np.matrix 来创建矩阵,通常在实际工作中,很少用这种方式创建数组。

np.matrix('1, 2; 3, 4')
matrix([[1, 2],

[3, 4]])

np.array 不仅可以创建由数字所组成的数组,可以创建元素类似键值形式的数组,有点类似 dict 数据格式。

x = np.array([('a',0.8),('b',0.2)],dtype=[('label','a8'),('prob','f8')])
x['label'] #array([b'a', b'b'], dtype='|S8')
x['prob'] #array([0.8, 0.2])

slice

slice 可以对数据进行切分提取想要矩阵的部分

a.T
array([[ 0, 10, 20],
       [ 1, 11, 21],
       [ 2, 12, 22],
       [ 3, 13, 23],
       [ 4, 14, 24],
       [ 5, 15, 25],
       [ 6, 16, 26],
       [ 7, 17, 27],
       [ 8, 18, 28],
       [ 9, 19, 29]])
a.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False
a.strides
(80, 8)
a.transpose().strides
(8, 80)

下面方式表示获取矩阵中所有元素,每一个维度对应 : 冒号两次可以指定从该维度截取范围,例如 2:6 表示从 2 从开始取包括起始位置对应索引 2,但是并不包括结束索引 6 ,2、3、4、5 。然后没有指定起始索引,默认值为 0 如果没有指定结束索引,则会包括最后

 a[::]
array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]])

表示包括所有行和所有的列

a[:,:]
array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]])
a[1:]
array([[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]])
a[:1,:0]
array([], shape=(1, 0), dtype=int64)
a[1:2]
array([[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]])

-1 表示从最后索引,

a[2][-1]
29
a[::2].flags
  C_CONTIGUOUS : False
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False
b = a[::2]
b
array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
       [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]])
b[1,1] = 100
b
array([[  0,   1,   2,   3,   4,   5,   6,   7,   8,   9],
       [ 20, 100,  22,  23,  24,  25,  26,  27,  28,  29]])
a
array([[  0,   1,   2,   3,   4,   5,   6,   7,   8,   9],
       [ 10,  11,  12,  13,  14,  15,  16,  17,  18,  19],
       [ 20, 100,  22,  23,  24,  25,  26,  27,  28,  29]])
b.strides
(160, 8)
a[::2,::2]
array([[ 0,  2,  4,  6,  8],
       [20, 22, 24, 26, 28]])
a[::2,::2].strides
(160, 16)
a[::2,::2].flags
  C_CONTIGUOUS : False
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False
a = np.arange(9)
np.sum(a)
36
a = a.reshape(3,3)
a
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
np.sum(a)#36
np.sum(a,axis=0)
array([ 9, 12, 15])
np.sum(a,axis=1)
array([ 3, 12, 21])

numpy_002.png

np.max(a) #8

如果 axis=0 表示沿着列进行对每列进行求和,这里

np.max(a,axis=0)
array([6, 7, 8])
np.max(a,axis=1)
array([2, 5, 8])

Random numbers / Reshape

randSamData = np.random.random(6000)
randSamData.size
6000
randSamData.shape
(6000,)
matrixFromRandom = randSamData.reshape(200,30)
matrixFromRandom.shape
(200, 30)
matrixFromRandom.sum()
3031.7003898820385
np.sum(matrixFromRandom,axis=0).shape
(30,)
np.sum(matrixFromRandom,axis=1).shape
(200,)

examples

a = np.random.random(25)
a = a.reshape(5,5)
a = abs(a - .5)
array([[0.41991025, 0.49594387, 0.44649835, 0.05122809, 0.30759666],
       [0.31135579, 0.16002952, 0.09425651, 0.2736727 , 0.22365327],
       [0.14630922, 0.28061775, 0.47835247, 0.26424074, 0.30095314],
       [0.2464213 , 0.34153651, 0.34218199, 0.20117573, 0.28738542],
       [0.2546834 , 0.11571962, 0.49709044, 0.26470299, 0.29870598]])
np.argmin(a)
22
np.argmin(a,axis=0)
array([0, 0, 4, 1, 3])
np.argmin(a,axis=1)
array([1, 3, 2, 2, 2])
a
array([[0.08008975, 0.00405613, 0.05350165, 0.44877191, 0.80759666],
       [0.81135579, 0.66002952, 0.40574349, 0.2263273 , 0.72365327],
       [0.64630922, 0.78061775, 0.02164753, 0.76424074, 0.80095314],
       [0.2535787 , 0.84153651, 0.15781801, 0.70117573, 0.21261458],
       [0.2453166 , 0.38428038, 0.00290956, 0.76470299, 0.79870598]])
a = np.random.random(6000)
a = a.reshape(100,60)
var_info= np.var(a,axis=0)
var_info
array([0.07970669, 0.08105232, 0.07752225, 0.0791212 , 0.071325  ,
       0.09056107, 0.08525211, 0.07520314, 0.07803583, 0.0846846 ,
       0.08041744, 0.0787772 , 0.07325676, 0.08486322, 0.08971832,
       0.08468472, 0.08176249, 0.0841182 , 0.08866966, 0.08280574,
       0.07565074, 0.08349566, 0.07583339, 0.08607531, 0.08438269,
       0.07758068, 0.09285445, 0.09556774, 0.07544476, 0.07688961,
       0.09564055, 0.07588679, 0.08690152, 0.0795474 , 0.09216045,
       0.08488148, 0.07155058, 0.09163908, 0.07914615, 0.08409165,
       0.07493577, 0.07769284, 0.08409168, 0.09321944, 0.09140067,
       0.07522396, 0.08863015, 0.08656539, 0.08412094, 0.08824875,
       0.07299776, 0.08246982, 0.08561273, 0.08701495, 0.08772088,
       0.0884059 , 0.08140604, 0.08755139, 0.08905218, 0.10169545])
inc_order = np.argsort(var_info)
inc_order[-5:]
array([26, 43, 27, 30, 59])

broadcasting

broadcasting 这种想法如何是 Numpy 提出,可以算是 Numpy 的一个亮点了。方便对于维度不同,并不是所有维度不同的数组之间进行元素间的加、减和乘的运算。我们还是通过

a = np.array([1,2,3])
b = np.array([5,7,8])
a + b #array([ 6, 9, 11])
a + 1 #array([2, 3, 4])
a - 1 #array([0, 1, 2])

有了广播机制,如果将要将两个数组按元素进行操作,例如对两个对应位置元素进行


a = np.arange(100).reshape(20,5)
b = np.arange(20).reshape(20,1)

a + b
array([[ 0, 1, 2, 3, 4],
[ 6, 7, 8, 9, 10],
[ 12, 13, 14, 15, 16],
[ 18, 19, 20, 21, 22],
[ 24, 25, 26, 27, 28],
[ 30, 31, 32, 33, 34],
[ 36, 37, 38, 39, 40],
[ 42, 43, 44, 45, 46],
[ 48, 49, 50, 51, 52],
[ 54, 55, 56, 57, 58],
[ 60, 61, 62, 63, 64],
[ 66, 67, 68, 69, 70],
[ 72, 73, 74, 75, 76],
[ 78, 79, 80, 81, 82],
[ 84, 85, 86, 87, 88],
[ 90, 91, 92, 93, 94],
[ 96, 97, 98, 99, 100],
[102, 103, 104, 105, 106],
[108, 109, 110, 111, 112],
[114, 115, 116, 117, 118]])
a = np.ones((3,5,1,5,7))
b = np.ones((1,5,2,5,1))
# a + b
def funcArray(x,y):
    return x+y
func_created_array = np.fromfunction(funcArray,(15,4),dtype=int)
func_created_array
array([[ 0,  1,  2,  3],
       [ 1,  2,  3,  4],
       [ 2,  3,  4,  5],
       [ 3,  4,  5,  6],
       [ 4,  5,  6,  7],
       [ 5,  6,  7,  8],
       [ 6,  7,  8,  9],
       [ 7,  8,  9, 10],
       [ 8,  9, 10, 11],
       [ 9, 10, 11, 12],
       [10, 11, 12, 13],
       [11, 12, 13, 14],
       [12, 13, 14, 15],
       [13, 14, 15, 16],
       [14, 15, 16, 17]])

indexing

我们通常工作是在数组也好、矩阵也好中进行搜索或者按照一定规则提取数据。

a = np.arange(9)
a #array([0, 1, 2, 3, 4, 5, 6, 7, 8])
a[:,np.newaxis]

通过 np.newaxis 可以在某一个维度上增加一个维度,也就是将上面 (9,) 一维数组变换为 (9,1) 维度有点类似 pytorch 的 unsqueeze

array([[0],[1],[2],[3],[4],[5],[6],[7],[8]])
a = np.arange(9).reshape(3,3,1)
a
array(
[[[0],[1],[2]],
[[3],[4],[5]],
[[6],[7],[8]]])
a[1,...]

省略号表示对于除了在 1 维度我们选取了 2 行,其他维度为全选所有可能元素,对于 3 x 3 x 1 等价于 a[1,:,:]

array([[3],[4],[5]])
a = a.squeeze()
a.shape #(3, 3)

利用 arange 创建一个数组,然后再调用 reshape 将其形状转换为 3 x 3 x 1 的形状后。

a = np.arange(9).reshape(3,3,1)
a[1,:,:]

表示取元素第一个维度索引为 1 也就是 3 维矩阵第 2 行的元素。

array([[3],[4],[5]])
a = a.squeeze()
a.shape #(3, 3)
a[:,:,np.newaxis]

通过 np.newaxis 最最后一个维度上添加一个维度,也就是对每个元素进行了一次包裹,得到为 3 x 3 x 1 维的矩阵

array([
[[0],[1],[2]],
[[3],[4],[5]],
[[6],[7],[8]]])
a[[0,1,2],[0,1,2]]

这样得到就是对象线上元素的值,也就是(0,0)、(1,1) 和 (2,2) 位置上的值。

array([0, 4, 8])

通过 slice 来从中矩阵 a 获取 0 和 1 行,因为 0:2 是一个左闭右开的方式,得到引用,所以当修改 x 时,a 中对应位置元素的值也会发生变化。

x = a[0:2]
x[0,0] = 100
a
array([[100, 1, 2],

[ 3, 4, 5],

[ 6, 7, 8]])

a[::2,[1,2]]

array([[1, 2],

[7, 8]])
np.ix_([0,2],[1,2])
(array([[0],[2]]),array([[1, 2]]))
a[np.ix_([0,2],[1,2])]

这样就会从数组分别先取其 0 和 2 行,然后在从选好的行选取 1 和 2 列元素,也就是(0,1) 和(2,2) 位置的元素。

array([[1, 2],[7, 8]])

现在我们来创建 2 个矩阵分别是 3 x 3 和 3 x 3 x 3 形状的矩阵,然后需要对矩阵进行相加操作,如何直接

a = np.arange(9).reshape((3,3))
b = np.arange(27).reshape((3,3,3))
a = a[:,:,np.newaxis]
a + b
array([[[ 0, 1, 2],

[ 4, 5, 6],

[ 8, 9, 10]],

[[12, 13, 14],

[16, 17, 18],

[20, 21, 22]],

[[24, 25, 26],

[28, 29, 30],

[32, 33, 34]]])

a.shape #(3, 3, 1)

创建一个数组 a 然后通过 b=a 这样赋值操作 b 获取 a 的引用,b 和 a 是共享同一块内存地址,我们可以通过np.may_share_memory(a,b)来检查 a 和 b 是否指向同一个内存地址

import numpy as np
a = np.array([1,2,3,4,5])
b = a
b is a#True
np.may_share_memory(a,b)#True
print(a) #[1 2 3 4 5]
print(b) #[1 2 3 4 5]




b = np.array([1,2,3,4,5])


a = b.copy()


a is b #False
b #array([1, 2, 3, 4, 5])
a = b[:]
a is b #False
a[0] = 100
a #array([100, 2, 3, 4, 5])
b #array([100, 2, 3, 4, 5])
np.may_share_memory(a,b) #True
a = np.array([1,2,3,4,5])
b = a[:]
b[0] = 100
a #array([100, 2, 3, 4, 5])

我们来定义一个函数 func1 函数接受一个参数,然后对传入数组进行arr *=2 然后返回。这时传入的

def func1(arr):
    arr *= 2
    return arr
x = np.array([10])
y = func1(x)
y #array([20])
x #array([20])


def func2(arr):

arr = arr * 2
return arr
x = np.array([10])
y = func2(x)
y #array([20])
x #array([10])