通常使用numpy的时候经常会遇到的一些这样的错误,

这是由于numpy数组在进行运算的时候形状不满足广播机制的要求,一般通过转置或reshape等方法可以解决问题,但是没有从根本上了解广播机制。
广播(broadcasting)指的是不同形状的数组之间的算数运算的执行方式。

数组与标量值的乘法

import numpy as np
arr = np.arange(5)
arr #-> array([0, 1, 2, 3, 4])
arr * 4 #-> array([ 0, 4, 8, 12, 16])

在这个乘法运算中,标量值4被广播到了其他所有元素上

通过减去列平均值的方式对数组每一列进行距平化处理

arr = np.random.randn(4,3)
arr #-> array([[ 1.83518156, 0.86096695, 0.18681254],
    # [ 1.32276051, 0.97987486, 0.27828887],
    # [ 0.65269467, 0.91924574, -0.71780692],
    # [-0.05431312, 0.58711748, -1.21710134]])
arr.mean(axis=0) #-> array([ 0.93908091, 0.83680126, -0.36745171])

关于mean中的axis参数,个人是这么理解的:
numpy中,axis = 0为行轴(竖直方向),axis = 1为列轴(水平方向),指定axis表示该操作沿axis进行,得到结果将是一个shape为除去该axisarray
在上例中,arr.mean(axis=0)表示对arr沿着轴0(竖直方向)求均值,即求列均值。而arr含有3列,所以结果含有3个元素,这与上面的结论相符。

demeaned = arr - arr.mean(axis=0)
demeaned
> array([[ 0.89610065,  0.02416569,  0.55426426],
           [ 0.3836796 ,  0.1430736 ,  0.64574058],
           [-0.28638623,  0.08244448, -0.35035521],
           [-0.99339402, -0.24968378, -0.84964963]])
demeaned.mean(axis=0)
> array([ -5.55111512e-17,  -5.55111512e-17,   0.00000000e+00])

广播的原则

如果两个数组的后缘维度(从末尾开始算起的维度)轴长度相符其中一方的长度为1,则认为它们是广播兼容的。广播会在缺失维度和(或)轴长度为1的维度上进行。

在上面的对arr每一列减去列平均值的例子中,arr的后缘维度为3arr.mean(0)后缘维度也是3,满足轴长度相符的条件,广播会在缺失维度进行。
这里有点奇怪的是缺失维度不是axis=1,而是axis=0,个人理解是缺失维度指的是两个arr除了轴长度匹配的维度,在上面的例子中,正好是axis=0这块欢迎指正
arr.mean(0)沿着axis=0广播,可以看作是把arr.mean(0)沿着竖直方向复制4份,即广播的时候arr.mean(0)相当于一个shape=(4,3)的数组,数组的每一行均相同,均为arr.mean(0)

各行减去行均值

row_means = arr.mean(axis=1)
row_means.shape
> (4,)
arr - row_means
> ---------------------------------------------------------------------------

    ValueError                                Traceback (most recent call last)

    <ipython-input-10-3d1314c7e700> in <module>()
    ----> 1 arr - row_means


    ValueError: operands could not be broadcast together with shapes (4,3) (4,) 

直接相减,报错,无法进行广播。回顾上面的原则,要么满足后缘维度轴长度相等,要么满足其中一方长度为1。在这个例子中,两者均不满足,所以报错。根据广播原则,较小数组的广播维必须为1
解决方案是为较小的数组添加一个长度为1的新轴。
numpy提供了一种通过索引机制插入轴的特殊语法。通过特殊的np.newaxis属性以及“全”切片来插入新轴。

上面的例子中,我们希望实现二维数组各行减去行均值,我们需要你将行均值沿着水平方向进行广播,广播轴为axis=1,对arr.mean(1)添加一个新轴axis=1

row_means[:,np.newaxis].shape
> (4, 1)
arr - row_means[:,np.newaxis]
> array([[ 0.87419454, -0.10002007, -0.77417447],
           [ 0.46245243,  0.11956678, -0.58201921],
           [ 0.36798351,  0.63453458, -1.00251808],
           [ 0.17378588,  0.81521647, -0.98900235]])

另一个例子

a = np.array([1,2,3])
a.shape # -> (3,)
b = np.array([[1,],[2,],[3]]) # -> (3,1)
b - a # -> array([[ 0, -1, -2],
                # [ 1, 0, -1],
                # [ 2, 1, 0]])

输出为什么是一个3*3的数组?我们来分析以下,根据广播原则,b满足其中一方轴长度为1,那么广播会沿着长度为1的轴,及axis=1进行,对数组b沿着axis=1即水平方向进行复制,相当于b变成一个shape(3,3)且各列均为[1,2,3]的数组,一个维度为(3,3)的数组减去一个维度为(3,)的数组,满足后缘维度轴长度相等,数组a沿着axis=0即竖直方向进行广播,相当远a变成一个shape(3,3)且个行均为[1,2,3]的数组。
相减的时候,b被广播成为

<nobr> 123123123 </nobr>

a被广播成为
<nobr> 111222333 </nobr>

结果应该是
<nobr> 012101210 </nobr>

三维情况

下面的例子中,构造一个3*4*5的随机数组arr_3d,我们希望实现对arr_3d的每个元素减去其深度(axis=2)方向的均值

#构造三维数组
arr_3d = np.random.randn(3,4,5)
#求深度方向的均值,想想结果的shape是什么?原始shape是(3,4,5)
#除去axis=2后还剩(3,4)
depth_means = arr_3d.mean(axis=2)
depth_means.shape
> (3, 4)
#arr(3,4,5)和depth_means(3,4)不能直接广播,后缘维度不相符且不存在轴长度为1的轴
#添加广播轴
arr_3d_new = arr_3d - depth_means[:,:,np.newaxis]
arr_3d_new.mean(axis=2)#结果为0
>  array([[ -5.55111512e-17,   4.44089210e-17,   4.44089210e-17,
              4.44089210e-17],
           [ -8.88178420e-17,  -1.11022302e-16,  -6.66133815e-17,
              0.00000000e+00],
           [  0.00000000e+00,  -7.77156117e-17,  -2.22044605e-17,
             -2.22044605e-17]])

以上就是关于numpy广播机制的一点理解,如果有不对的地方欢迎指正!