Python·总结常用Python机器学习中的向量化操作(持续更新)

本文最后更新于:2024年6月6日星期四晚上6点30分

Numpy

注:本文中提到的第1维度对应于dim=0,以此类推

初始化与属性

常用属性

  • 维度ndim
  • 数据类型dtype
  • 数据总数量size
  • 每一项数据所占字节大小itemsize
  • 数据总字节大小nbytes

直接初始化

In

n0 = np.array(0)
n1 = np.array([0,1])						# 自动识别数据类型
n2 = np.array([0,1,2],dtype="int32")
n3 = np.array([0,1,2.0])					# 自动识别数据类型
n4 = np.array([[0,1,2]],dtype="float32")

print_format = "{0},{0}.dtype,{0}.ndim,{0}.size,{0}.itemsize,{0}.nbytes"
rprint(print_format.format("n0"))
rprint(print_format.format("n1"))
rprint(print_format.format("n2"))
rprint(print_format.format("n3"))
rprint(print_format.format("n4"))

Out

n0:0  ▍ n0.dtype:int64  ▍ n0.ndim:0  ▍ n0.size:1  ▍ n0.itemsize:8  ▍ n0.nbytes:8  ▍ 
n1:[0 1]  ▍ n1.dtype:int64  ▍ n1.ndim:1  ▍ n1.size:2  ▍ n1.itemsize:8  ▍ n1.nbytes:16  ▍ 
n2:[0 1 2]  ▍ n2.dtype:int32  ▍ n2.ndim:1  ▍ n2.size:3  ▍ n2.itemsize:4  ▍ n2.nbytes:12  ▍ 
n3:[0. 1. 2.]  ▍ n3.dtype:float64  ▍ n3.ndim:1  ▍ n3.size:3  ▍ n3.itemsize:8  ▍ n3.nbytes:24  ▍ 
n4:[[0. 1. 2.]]  ▍ n4.dtype:float32  ▍ n4.ndim:2  ▍ n4.size:3  ▍ n4.itemsize:4  ▍ n4.nbytes:12  ▍

填充初始化

  • 注意这部分的第一个参数shape需要在外面加对括号
  • 默认的datatype似乎会根据版本而异——但zeros和ones应该都是默认float

In

zeros = np.zeros((3,4))
ones = np.ones((3,4,2))
full = np.full((3,4),6)								# 形状3*4 填充值为6
full_like = np.full_like(full,7,dtype="float32")	# 形状和"full"相同 填充值为7

rprint("zeros",":\n")
rprint("zeros.dtype")
rprint("ones",":\n")
rprint("ones.dtype")
rprint("full",":\n")
rprint("full.dtype")
rprint("full_like",":\n")
rprint("full_like.dtype")

Out

zeros:
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]  ▍ 
zeros.dtype:float64  ▍ 
ones:
[[[1. 1.]
  [1. 1.]
  [1. 1.]
  [1. 1.]]

 [[1. 1.]
  [1. 1.]
  [1. 1.]
  [1. 1.]]

 [[1. 1.]
  [1. 1.]
  [1. 1.]
  [1. 1.]]]  ▍ 
ones.dtype:float64  ▍ 
full:
[[6 6 6 6]
 [6 6 6 6]
 [6 6 6 6]]  ▍ 
full.dtype:int64  ▍ 
full_like:
[[7. 7. 7. 7.]
 [7. 7. 7. 7.]
 [7. 7. 7. 7.]]  ▍ 
full_like.dtype:float32  ▍

随机初始化

  • 注意,随机初始化时需要注意指明shape的方式

In

np.random.seed(42)							# ====Reset the seed to 42====
rand_mat_1 = np.random.rand(2,2)			# np.random.rand不需要加tuple
np.random.seed(42)							# ====Reset the seed to 42====
rand_mat_2 = np.random.random_sample((2,2))	# np.random.random_sample则需要加tuple
rand_1 = np.random.randint(3)				# np.random.randint给出一个[0,x)的整数
rand_2 = np.random.randint(3)
np.random.seed(42)							# ====Reset the seed to 42====
rand_3 = np.random.randint(3)
rand_4 = np.random.randint(3)
np.random.seed(42)							# ====Reset the seed to 42====
rand_5 = np.random.randint(0,3)				# np.random.randint也可以指明区间范围
rand_6 = np.random.randint(0,3)
np.random.seed(42)							# ====Reset the seed to 42====
rand_mat_3 = np.random.randint(3,7,(3,3))	# np.random.randint可以形成矩阵!
np.random.seed(42)							# ====Reset the seed to 42====
rand_mat_4 = np.array([np.random.randint(3,7) for _ in range(9)]).reshape(3,3)

print("====Reset the seed to 42====")
rprint("rand_mat_1",":\n")
print("====Reset the seed to 42====")
rprint("rand_mat_2",":\n")
rprint("rand_1,rand_2")
print("====Reset the seed to 42====")
rprint("rand_3,rand_4")
print("====Reset the seed to 42====")
rprint("rand_5,rand_6")
print("====Reset the seed to 42====")
rprint("rand_mat_3",":\n")
print("====Reset the seed to 42====")
rprint("rand_mat_4",":\n")

Out

====Reset the seed to 42====
rand_mat_1:
[[0.37454012 0.95071431]
 [0.73199394 0.59865848]]  ▍ 
====Reset the seed to 42====
rand_mat_2:
[[0.37454012 0.95071431]
 [0.73199394 0.59865848]]  ▍ 
rand_1:2  ▍ rand_2:1  ▍ 
====Reset the seed to 42====
rand_3:2  ▍ rand_4:0  ▍ 
====Reset the seed to 42====
rand_5:2  ▍ rand_6:0  ▍ 
====Reset the seed to 42====
rand_mat_3:
[[5 6 3]
 [5 5 6]
 [3 3 5]]  ▍ 
====Reset the seed to 42====
rand_mat_4:
[[5 6 3]
 [5 5 6]
 [3 3 5]]  ▍
关于seed

从上面的例子也可以看到,在设置完随机种子后,同样的生成随机数据命令序列的结果是一样的(rand_3,rand_4和rand_5,rand_6,以及rand_mat_3和rand_mat_4的生成),但不同的命令序列结果就并非如此(如rand_mat_2,rand_1和rand_3,rand_4)。

  • 我的理解是:设置完随机种子后,就有一个固定的随机数序列,每次调用生成随机数据的命令就是从这个序列中按顺序取,但是不同的命令(比如生成整数和生成浮点数)的进一步处理可能不同(#TODO探究)

其他初始化

单位矩阵

In

np.identity(5)

Out

array([[1., 0., 0., 0., 0.],
       [0., 1., 0., 0., 0.],
       [0., 0., 1., 0., 0.],
       [0., 0., 0., 1., 0.],
       [0., 0., 0., 0., 1.]])

复制/赋值

直接赋值 =

copy_directly = origin

In

origin = np.arange(4)
copy_directly = origin						# 直接赋值

print("======= Before Modify ======")
rprint("origin")
print("copy_directly = origin")
rprint("copy_directly")

print("=========== Infos ==========")
rprint("origin.flags.owndata")				# 检查origin是否是底层数据的拥有者
rprint("copy_directly.flags.owndata")		# 检查copy_directly是否是底层数据的拥有者
rprint("id(origin) == id(copy_directly)")	# 检查两者id是否相同
											# =====================
origin[-1] = 10								# 修改origin
print("====== origin[-1] = 10 =====")
rprint("origin")
rprint("copy_directly")						# copy_directly也被修改了
											# =====================
copy_directly[0] = 10						# 修改copy_directly
print("==== copy_directly[0] = 10 ====")
rprint("copy_directly")
rprint("origin")							# origin也被修改了

Out

======= Before Modify ======
origin:[0 1 2 3]  ▍ 
copy_directly = origin
copy_directly:[0 1 2 3]  ▍ 
=========== Infos ==========
origin.flags.owndata:True  ▍ 
copy_directly.flags.owndata:True  ▍ 
id(origin) == id(copy_directly):True  ▍ 
====== origin[-1] = 10 =====
origin:[ 0  1  2 10]  ▍ 
copy_directly:[ 0  1  2 10]  ▍ 
==== copy_directly[0] = 10 ====
copy_directly:[10  1  2 10]  ▍ 
origin:[10  1  2 10]  ▍
  • 可以看到,直接赋值获得的array和原始的array之间有以下关系:
    • 都是底层数据的拥有者
    • id相同
    • 修改其中一个都会影响另外一个

切片 = []

copy_slice = origin[:]

  • 注意,numpy中的切片的性质和python对于列表的切片的性质不同

In

origin = np.arange(4)
copy_slice = origin[:]						# 切片

print("======= Before Modify ======")
rprint("origin")
print("copy_slice = origin[:]")
rprint("copy_slice")

print("=========== Infos ==========")
rprint("origin.flags.owndata")				# 检查origin是否是底层数据的拥有者
rprint("copy_slice.flags.owndata")			# 检查copy_directly是否是底层数据的拥有者
rprint("id(origin) == id(copy_slice)")		# 检查两者id是否相同
											# =====================
origin[-1] = 10								# 修改origin
print("====== origin[-1] = 10 =====")
rprint("origin")
rprint("copy_slice")						# copy_slice也被修改了
											# =====================
copy_slice[0] = 10							# 修改copy_slice
print("==== copy_slice[0] = 10 ====")
rprint("copy_slice")
rprint("origin")							# origin也被修改了

Out

======= Before Modify ======
origin:[0 1 2 3]  ▍ 
copy_slice = origin[:]
copy_slice:[0 1 2 3]  ▍ 
=========== Infos ==========
origin.flags.owndata:True  ▍ 
copy_slice.flags.owndata:False  ▍ 
id(origin) == id(copy_slice):False  ▍ 
====== origin[-1] = 10 =====
origin:[ 0  1  2 10]  ▍ 
copy_slice:[ 0  1  2 10]  ▍ 
==== copy_slice[0] = 10 ====
copy_slice:[10  1  2 10]  ▍ 
origin:[10  1  2 10]  ▍
  • 可以看到,通过切片获得的array和原始的array之间有以下关系:
    • 只有原始array是底层数据的拥有者
    • 二者id不再相同
    • 修改其中一个同样会影响另一个

深拷贝 = .copy()

  • 为了真正的生成修改互不影响的array,我们需要使用.copy()生成一个新的array
  • 注意这里直接使用copy就是深拷贝了,不用加上True之类的参数(注意和torch里的tensor区分)

In

origin = np.arange(4)
copy_deep = origin.copy()                   # 深拷贝

print("======= Before Modify ======")
rprint("origin")
print("copy_deep = origin.copy()")
rprint("copy_deep")

print("=========== Infos ==========")
rprint("origin.flags.owndata")              # 检查origin是否是底层数据的拥有者
rprint("copy_deep.flags.owndata")           # 检查copy_deep是否是底层数据的拥有者
rprint("id(origin) == id(copy_deep)")       # 检查两者id是否相同
                                            # =====================
origin[-1] = 10                             # 修改origin
print("====== origin[-1] = 10 =====")
rprint("origin")
rprint("copy_deep")                         # copy_deep没有被修改
                                            # =====================
copy_deep[0] = 10                           # 修改copy_deep
print("==== copy_deep[0] = 10 =====")
rprint("copy_deep")
rprint("origin")                            # origin没有被修改

Out

======= Before Modify ======
origin:[0 1 2 3]  ▍ 
copy_deep = origin.copy()
copy_deep:[0 1 2 3]  ▍ 
=========== Infos ==========
origin.flags.owndata:True  ▍ 
copy_deep.flags.owndata:True  ▍ 
id(origin) == id(copy_deep):False  ▍ 
====== origin[-1] = 10 =====
origin:[ 0  1  2 10]  ▍ 
copy_deep:[0 1 2 3]  ▍ 
==== copy_deep[0] = 10 =====
copy_deep:[10  1  2  3]  ▍ 
origin:[ 0  1  2 10]  ▍
  • 可以看到:
    • 两个array都是对底层数据的拥有者
    • 两者id不同
    • 对其中一个修改,不会影响另一个

变换

reshape

就不用说了

repeat

In

origin = np.array([[1,2,3]])
repeat_0 = np.repeat(origin,3, axis=0)	# np.repeat可以指定axis重复n次
repeat_1 = np.repeat(origin,3, axis=1)	# 在目标维度然后按目标方向repeat对应的数据

rprint("origin",":\n")
rprint("repeat_0",":\n")
rprint("repeat_1",":\n")

Out

origin:
[[1 2 3]]  ▍ 
repeat_0:
[[1 2 3]
 [1 2 3]
 [1 2 3]]  ▍ 
repeat_1:
[[1 1 1 2 2 2 3 3 3]]  ▍
关于axis

关于Numpy中的axis(其实Torch和Pandas中也是类似的),可以这么理解

  • 根据axis的维度,沿着该维度走下去的方向就是目标方向

    • origin:
      [[1 2 3]]

      np.repeat(origin,3, axis=0)

      ​ 沿着axis=0只有一个[1 2 3],继续走进行repeat其实就是把[1 2 3]repeat了n次,最后也就是

      [[1 2 3] [1 2 3] [1 2 3]] 
      # or
      [[1 2 3]
       [1 2 3]
       [1 2 3]]

stack

  • numpy中的stack主要有两种,一种是np.vstack&np.hstack,另一种就叫np.stack。下面分这两种展开:
vstack&hstack

In

one_array = np.ones((3,4))
zero_array = np.zeros((3,4))

sprint("one_array")
sprint("zero_array")
print("----------------")
sprint("np.vstack([one_array,zero_array])")
sprint("np.hstack([one_array,zero_array])")

Out

one_array
- - - - -
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]]  ▍ 
===============
zero_array
- - - - -
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]  ▍ 
===============
----------------
np.vstack([one_array,zero_array])
- - - - -
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]  ▍ 
===============
np.hstack([one_array,zero_array])
- - - - -
[[1. 1. 1. 1. 0. 0. 0. 0.]
 [1. 1. 1. 1. 0. 0. 0. 0.]
 [1. 1. 1. 1. 0. 0. 0. 0.]]  ▍ 
===============
  • 可以看到,vstack和hstack的效果十分直观,就是一个竖着拼,一个横着拼,所以拼的时候需要保证size对齐
stack
  • np.stack稍微复杂一点,可以传入更多参数

In

one_array = np.ones((3,4))
zero_array = np.zeros((3,4))

sprint("one_array")
sprint("zero_array")
print("----------------")
sprint("np.stack([one_array,zero_array],axis=0)")
sprint("np.stack([one_array,zero_array],axis=1)")
sprint("np.stack([one_array,zero_array],axis=2)")

Out

one_array
- - - - -
[[1. 1. 1. 1.]
 [1. 1. 1. 1.]
 [1. 1. 1. 1.]]  ▍ 
===============
zero_array
- - - - -
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]  ▍ 
===============
----------------
np.stack([one_array,zero_array],axis=0)
- - - - -
[[[1. 1. 1. 1.]
  [1. 1. 1. 1.]
  [1. 1. 1. 1.]]

 [[0. 0. 0. 0.]
  [0. 0. 0. 0.]
  [0. 0. 0. 0.]]]  ▍ 
===============
np.stack([one_array,zero_array],axis=1)
- - - - -
[[[1. 1. 1. 1.]
  [0. 0. 0. 0.]]

 [[1. 1. 1. 1.]
  [0. 0. 0. 0.]]

 [[1. 1. 1. 1.]
  [0. 0. 0. 0.]]]  ▍ 
===============
np.stack([one_array,zero_array],axis=2)
- - - - -
[[[1. 0.]
  [1. 0.]
  [1. 0.]
  [1. 0.]]

 [[1. 0.]
  [1. 0.]
  [1. 0.]
  [1. 0.]]

 [[1. 0.]
  [1. 0.]
  [1. 0.]
  [1. 0.]]]  ▍ 
===============
  • 注意,无论是vstack/hstack还是stack,传入的被stack的都是用[]括起来的,所以参数axis指明的应该理解为加上了[]之后的维度

  • axis = 0的时候,我们的[one_array,zero_array]的第一维度其实就是这两个array,所以就是:

    # 将
    [[1. 1. 1. 1.]
     [1. 1. 1. 1.]
     [1. 1. 1. 1.]]
    # 与
    [[0. 0. 0. 0.]
     [0. 0. 0. 0.]
     [0. 0. 0. 0.]]
    # 拼接,得到
    [[[1. 1. 1. 1.]
      [1. 1. 1. 1.]
      [1. 1. 1. 1.]]
    
     [[0. 0. 0. 0.]
      [0. 0. 0. 0.]
      [0. 0. 0. 0.]]]
  • axis = 1的时候,我们的[one_array,zero_array]的第二维度其实就是这两个array中的第一维度,也就是:

    # one_array
    [
        [1. 1. 1. 1.] # <-
        [1. 1. 1. 1.] # <-
        [1. 1. 1. 1.] # <-
    ]

    所以结果:

    [
        [
        [1. 1. 1. 1.]		# 从[one_array,zero_array]中选第一个的
        [0. 0. 0. 0.]		# 从[one_array,zero_array]中选第二个的
     				]		# 选完了,封闭一层
        [
        [1. 1. 1. 1.]
        [0. 0. 0. 0.]
        			]
        [
        [1. 1. 1. 1.]
        [0. 0. 0. 0.]
        			]
    					]
  • axis=2也是一样的道理,直接看结果吧:

    [[[
        1. 		# 来源于[one_array,zero_array]中第一个的第2维的数据
        0. 		# 来源于[one_array,zero_array]中第二个的第2维的数据
    		]	# 选完了,封闭一层
      [1. 0.]
      [1. 0.]
      [1. 0.]]
    
     [[1. 0.]
      [1. 0.]
      [1. 0.]
      [1. 0.]]
    
     [[1. 0.]
      [1. 0.]
      [1. 0.]
      [1. 0.]]]

数据索引

这部分的更深入介绍请参考:https://numpy.org/doc/stable/user/basics.indexing.html

假设我们有一个3维数组n3d,我们考虑各种读取/修改数组的方式

In

n3d = np.array([[[1,2],[3,4]],[[5,6],[7,8]]])
n3d

Out

array([[[1, 2],
        [3, 4]],

       [[5, 6],
        [7, 8]]])
  • 我们可以用[,,]的方式分别指定三维数组的三个维度

原则

  • 每对一个维度使用单个数字细化选取,则会降低一维
  • :(包括含:的表达式)细化维度,不会降低维度
  • 每对一个维度使用一维[]进行细化选取,则会降低一维;但最后总的会加一维
  • []的更高维更为复杂,咱不讨论

用单个数字选取

In

n3d = np.arange(1,28).reshape(3,3,3)

sprint("n3d")
sprint("n3d[0]")						# 对第1维用单个数字0进行选取
sprint("n3d[:,:,0]")					# 对第3维用单个数字0进行选取
sprint("n3d[...,0]")					# 也可以用这种...省略号的方式替代

Out

n3d
- - - - -
[[[ 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]]]  ▍ 
===============
n3d[0]
- - - - -
[[1 2 3]
 [4 5 6]
 [7 8 9]]  ▍ 
===============
n3d[:,:,0]
- - - - -
[[ 1  4  7]
 [10 13 16]
 [19 22 25]]  ▍ 
===============
n3d[...,0]
- - - - -
[[ 1  4  7]
 [10 13 16]
 [19 22 25]]  ▍ 
===============
关于选取逻辑
  • 我们以n3d[:,:,0]为例

    • 首先看下n3d的模样
    n3d
    - - - - -
    [[[ 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]]]
    • 第一维是:,即保留所有的第一维数据

      由于n3d的形状为3*3*3,第一维有三个数据,我们可以做以下拆分

    # 第一个数据
     [[ 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]]
    • 第二维也是:,故也是保留第二维的所有数据

      上一点我们说到第一维已经全部保留了,所以我们对于第一维的每个数据都要进行第二维的细化选取(只不过我们这里用:了,可以看作没有细化选取)。以第一维的第一个数据为例:

      # 第一个数据
       [
        [ 1  2  3]	# 保留
        [ 4  5  6]	# 保留
        [ 7  8  9]	# 保留
        			]
      # ... 第一维的其余数据也是同样的操作
    • 现在到了最后一维,这里用的是单个数字0进行细化选取,也就是对于最后一维只选中第一个数据

      同样由于前面两维都是保留的全部数据,我们以上一维度的数据为例说明单个数字的细化选取:

      # 第一个数据
       [
        # 
        [ 
        	1  		# 选中
        	2  		
        	3		
        	   ]
        
        [ 
        	4  		# 选中
        	5  
        	6
        	   ]	
        [ 
        	7  		# 选中
        	8  
        	9
        	   ]
        			]
      # ... 第一维的其余数据也是同样的操作

      其实就和一个一维的用单个数字索引一样,会降维,如 np.array([1,2,3])[0] = 1 一般

      于是就变成了:

      # 第一个数据
       [ 1  4  7 ]
      # ... 第一维的其余数据也是同样的操作

      拼上第一维的其他两个数据即为2维array

:选取

正如上面提到的那样,如果在某一维度单用一个:,则会保留该维度层面的所有数据,也不会降低维度;除此之外,还有进行切片的方式进行选取,同样不会降低维度

  • 进阶就是 start:stop:step

In

n3d = np.arange(1,28).reshape(3,3,3)

sprint("n3d")
sprint("n3d[:,:,0]")
sprint("n3d[:,:,0:1]")					# 最后一维使用切片的方式进行选取

Out

n3d
- - - - -
[[[ 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]]]  ▍ 
===============
n3d[:,:,0]
- - - - -
[[ 1  4  7]
 [10 13 16]
 [19 22 25]]  ▍ 
===============
n3d[:,:,0:1]
- - - - -
[[[ 1]
  [ 4]
  [ 7]]

 [[10]
  [13]
  [16]]

 [[19]
  [22]
  [25]]]  ▍ 
===============
  • 其实这个也很好理解,可以结合上面的”关于选取逻辑”的介绍来讲
    • 在用单个数字下标进行选取的时候,可以看作是对于一个array(或者看成python内置的list)选取其中的某个元素,所以维度会下降1;而用切片进行选择也同样可以这样去考虑——使用切片形成的还是array(或list)!故其实维度是保持不变的。

[]选取

这一块相对比较复杂

纯Integer array indexing

我们先讨论每个维度用到的都是同样shape的array/[]的情况

  • 总的来说,对于每一维都是array/[]索引的方式来说,有以下的等式:
result = x[ind_1, ind_2, ..., ind_N]
# result[i_1, ..., i_M] == x[ind_1[i_1, ..., i_M], 
#							 ind_2[i_1, ..., i_M],
#							 ..., 
#							 ind_N[i_1, ..., i_M]]
  • 直接看公式可能有点抽象,我们可以通过下面的例子来直观的感受一下:

    x = np.array([[ 0,  1,  2],
                  [ 3,  4,  5],
                  [ 6,  7,  8],
                  [ 9, 10, 11]])
    rows = np.array([[0, 0],
                     [3, 3]], dtype=np.intp)
    columns = np.array([[0, 2],
                        [0, 2]], dtype=np.intp)
    x[rows, columns]
    # 结果:
    # array([[ 0,  2],
    #        [ 9, 11]])

    其实就是得到一个array,其中rows指明了行,columns指明了列,比如rows[0,0]和columns[0,0]就指明了得到的result的(0,0)位置的元素为x数组的(rows[0,0],columns[0,0])位置的数——这里刚好(0,0)对应的也刚好是0;可以看result的(1,0)位置的元素(9)其实就是x数组的(3,0)位置的元素,而(3,0)就是(rows[1,0],columns[1,0])

Broadcasting
  • 对于不同维度有不同的shape的array进行index的情况,首先会尝试进行broadcast(广播)
  • If the index arrays do not have the same shape, there is an attempt to broadcast them to the same shape.

我们还是用上面那个例子,尝试用不同shape的array进行选取

rows = np.array([0, 3], dtype=np.intp)
columns = np.array([0, 2], dtype=np.intp) # 1*2
# rows[:, np.newaxis]: [[0],[3]] —— 2*1
x[rows[:, np.newaxis], columns]	# 进行broadcast成2*2,也就是和是上面的两个一样
# array([[ 0,  2],
#        [ 9, 11]])
简单例子

In

y = np.arange(35).reshape(5, 7)
sprint("y")
sprint("y[np.array([0, 2, 4]), 1]")			# 其实这种就是一种会发生broadcast的情形了
sprint("y[np.array([0, 2, 4]), [1,1,1]]")	# 这里就是显式地展示怎么进行indexing

Out

y
- - - - -
[[ 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 30 31 32 33 34]]  ▍ 
===============
y[np.array([0, 2, 4]), 1]
- - - - -
[ 1 15 29]  ▍ 
===============
y[np.array([0, 2, 4]), [1,1,1]]
- - - - -
[ 1 15 29]  ▍ 
===============
复杂例子

In

n3d = np.arange(1,28).reshape(3,3,3)

sprint("n3d")
sprint("n3d[[[0],[2]],:]")					# 该结果的维度参考rows矩阵[[0],[2]]的维度
sprint("n3d[[[0],[2]],:,[1,2]]")			# 这里发生了broadcast,第一维和第三维的都是
sprint("n3d[[[0,0],[2,2]],:,[[1,2],[1,2]]]")# 这里就是显式地将上面的broadcast等价写出

Out

n3d
- - - - -
[[[ 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]]]  ▍ 
===============
n3d[[[0],[2]],:]			# 相当于[[n3d的第一维的第0个],[n3d的第一维的第2个]]
- - - - -
[[[[ 1  2  3]
   [ 4  5  6]
   [ 7  8  9]]]


 [[[19 20 21]
   [22 23 24]
   [25 26 27]]]]  ▍ 
===============
n3d[[[0],[2]],:,[1,2]]
- - - - -
[[[ 2  5  8]
  [ 3  6  9]]

 [[20 23 26]
  [21 24 27]]]  ▍ 
===============
n3d[[[0,0],[2,2]],:,[[1,2],[1,2]]]
- - - - -
[[[ 2  5  8]
  [ 3  6  9]]

 [[20 23 26]
  [21 24 27]]]  ▍ 
===============
Non-Broadcast
  • 当只有A single advanced index时,如只使用了一个array index,则可以从效果上替换slice/:

    A single advanced index can, for example, replace a slice and the result array will be the same. However, it is a copy and may have a different memory layout. A slice is preferable when it is possible.

    (但好像不是deep copy)

  • 注意这里的条件是single,如果有多个,则会尝试进行上面所说的broadcast

In

sprint("n3d[:,:,[1,2]]")
sprint("n3d[:,:,1:3]")
sprint("(n3d[:,:,0:3] == n3d[:,:,[0,1,2]]).all()")

Out

n3d[:,:,[1,2]]
- - - - -
[[[ 2  3]
  [ 5  6]
  [ 8  9]]

 [[11 12]
  [14 15]
  [17 18]]

 [[20 21]
  [23 24]
  [26 27]]]  ▍ 
===============
n3d[:,:,1:3]
- - - - -
[[[ 2  3]
  [ 5  6]
  [ 8  9]]

 [[11 12]
  [14 15]
  [17 18]]

 [[20 21]
  [23 24]
  [26 27]]]  ▍ 
===============
(n3d[:,:,0:3] == n3d[:,:,[0,1,2]]).all()
- - - - -
True  ▍ 
===============
  • 我们可以试下假设有多个array index会发生什么:

    sprint("n3d[[[0],[2]],:,[1,2]]")
    sprint("n3d[[[0],[2]],:,1:3]")
    sprint("(n3d[[[0],[2]],:,0:3] == n3d[[[0],[2]],:,[0,1,2]]).all()")
    n3d[[[0],[2]],:,[1,2]]
    - - - - -
    [[[ 2  5  8]
      [ 3  6  9]]
    
     [[20 23 26]
      [21 24 27]]]  ▍ 
    ===============
    n3d[[[0],[2]],:,1:3]
    - - - - -
    [[[[ 2  3]
       [ 5  6]
       [ 8  9]]]
    
    
     [[[20 21]
       [23 24]
       [26 27]]]]  ▍ 
    ===============
    (n3d[[[0],[2]],:,0:3] == n3d[[[0],[2]],:,[0,1,2]]).all()
    - - - - -
    False  ▍ 
    ===============

参考

Torch

Pandas


Python·总结常用Python机器学习中的向量化操作(持续更新)
https://asyu.in/2024/06/04/python-vector-operation/
作者
Yu
发布于
2024年6月4日
更新于
2024年6月6日 18时
许可协议