《scikit-learn机器学习常用算法原理及编程实战》—2.3.2 Numpy运算

举报
华章计算机 发表于 2019/05/31 16:16:47 2019/05/31
【摘要】 本书摘自《scikit-learn机器学习常用算法原理及编程实战》一书中的第2章,第2.3.2节,编著是黄永昌 .

2.3.2  Numpy运算

  最简单的数值计算是数组和标量进行计算,计算过程是直接把数组里的元素和标量逐个进行计算:

  In [2]: a = np.arange(6)

  

  In [3]: a

  Out[3]: array([0, 1, 2, 3, 4, 5])

  

  In [4]: a + 5      # 数组和标量加法

  Out[4]: array([ 5,  6,  7,  8,  9, 10])

  

  In [7]: b = np.random.randint(1, 5, 20).reshape(4, 5)

  

  In [8]: b

  Out[8]:

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

         [3, 2, 2, 4, 3],

         [2, 4, 2, 1, 2],

         [2, 4, 4, 4, 2]])

  

  In [9]: b * 3        # 数组和标量乘法

  Out[9]:

  array([[ 9,  9,  6,  9, 12],

         [ 9,  6,  6, 12,  9],

         [ 6, 12,  6,  3,  6],

         [ 6, 12, 12, 12,  6]])

  

  使用Numpy的优点是运行速度会比较快,我们可以对比一下使用Python的循环与使用Numpy运算在效率上的差别,从Log里看到运行效率相差近100倍。

  

  In [10]: c = np.arange(10000)

  

  In [11]: %timeit c + 1

  10000 loops, best of 3: 23.7 us per loop

  

  In [12]: %timeit [i + 1 for i in c]

  100 loops, best of 3: 2.61 ms per loop

  

  另外一种是数组和数组的运算,如果数组的维度相同,那么在组里对应位置进行逐个元素的数学运算。

  

  In [16]: a = np.random.random_integers(1, 5, (5, 4))

  

  In [17]: a

  Out[17]:

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

        [1, 1, 4, 2],

        [1, 3, 5, 4],

        [3, 3, 3, 4],

        [5, 1, 5, 1]])

  

  In [23]: b = np.ones((5, 4), dtype=int)

  

  In [24]: b

  Out[24]:

  array([[1, 1, 1, 1],

        [1, 1, 1, 1],

        [1, 1, 1, 1],

        [1, 1, 1, 1],

        [1, 1, 1, 1]])

  

  In [25]: a + b            # 数组加法

  Out[25]:

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

        [2, 2, 5, 3],

        [2, 4, 6, 5],

        [4, 4, 4, 5],

        [6, 2, 6, 2]])

  

  In [26]: c = np.random.random_integers(1, 5, (3, 4))

  

  In [27]: c

  Out[27]:

  array([[5, 5, 4, 5],

        [2, 2, 1, 3],

        [5, 1, 1, 5]])

  

  In [28]: d = np.random.random_integers(1, 5, (3, 4))

  

  In [29]: d

  Out[29]:

  array([[3, 4, 5, 4],

        [4, 3, 4, 2],

        [1, 4, 5, 4]])

  

  In [30]: c * d              # 数组相乘,逐元素相乘,不是矩阵内积运算

  Out[30]:

  array([[15, 20, 20, 20],

        [ 8,  6,  4,  6],

        [ 5,  4,  5, 20]])

  

  需要注意的是,乘法是对应元素相乘,不是矩阵内积,矩阵内积使用的是np.dot()函数。

  

  In [2]: a = np.random.random_integers(1, 5, (3, 2))

  

  In [4]: b = np.random.random_integers(1, 5, (2, 3))

  

  In [5]: a

  Out[5]:

  array([[3, 1],

        [2, 3],

        [5, 1]])

  

  In [6]: b

  Out[6]:

  array([[5, 1, 2],

        [3, 1, 4]])

  

  In [7]: np.dot(a, b)                    # 矩阵内积

  Out[7]:

  array([[18,  4, 10],

        [19,  5, 16],

        [28,  6, 14]])

  如果数组的维度不同,则Numpy会试图使用广播机制来匹配,如果能匹配得上,就进行运算;如果不满足广播条件,则报错。

  

  In [30]: a = np.random.random_integers(1, 5, (5, 4))

  

  In [31]: a

  Out[31]:

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

        [1, 1, 4, 2],

        [1, 3, 5, 4],

        [3, 3, 3, 4],

        [5, 1, 5, 1]])

  

  In [32]: b = np.arange(4)

  

  In [33]: b

  Out[33]: array([0, 1, 2, 3])

  

  In [34]: a + b              # 5 ? 4 数组与 1 ? 4 数组的加法,满足广播条件

  Out[34]:

  array([[3, 2, 5, 5],

        [1, 2, 6, 5],

        [1, 4, 7, 7],

        [3, 4, 5, 7],

        [5, 2, 7, 4]])

  

  In [35]: c = np.arange(5)

  

  In [36]: c

  Out[36]: array([0, 1, 2, 3, 4])

  

  In [37]: a + c              # 5 ? 4 数组与 1 ? 5 数组加法,不满足广播条件,报错

  -----------------------------------------------------------------------

  ValueError                                Traceback (most recent call last)

  <ipython-input-37-ca57d551b7f3> in <module>()

  ----> 1 a + c

  

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

  

  In [38]: c = np.arange(5).reshape(5, 1)      # 转换为 5 ? 1 列向量

  

  In [39]: c

  Out[39]:

  array([[0],

        [1],

        [2],

        [3],

        [4]])

  

  In [40]: a + c            # 5 ? 4 数组与 5 ? 1 数组的加法,满足广播条件

  Out[40]:

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

        [2, 2, 5, 3],

        [3, 5, 7, 6],

        [6, 6, 6, 7],

        [9, 5, 9, 5]])

  

  从上面的例子可以看到,符合广播的条件是两个数组必须有一个维度可以扩展,然后在这个维度上进行复制,最终复制出两个相同维度的数组,再进行运算。具体可参阅上面代码中的注释。作为广播的一个特例,当一个二维数组和一个标量进行运算时,实际上执行的也是广播机制,它有两个维度可扩展,先在行上进行复制,再在列上进行复制,最终复制出和待运算的二维数组维度相同的数组后,再进行运算。

  数组还可以直接进行比较,返回一个同维度的布尔数组。针对布尔数组,可以使用all()/any()函数来返回布尔数组的标量值。

  

  In [42]: a = np.array([1, 2, 3, 4])

  

  In [43]: b = np.array([4, 2, 2, 4])

  

  In [44]: a == b

  Out[44]: array([False,  True, False,  True], dtype=bool)

  

  In [45]: a > b

  Out[45]: array([False, False,  True, False], dtype=bool)

  

  In [46]: (a == b).all()

  Out[46]: False

  

  In [47]: (a == b).any()

  Out[47]: True

  

  Numpy还提供了一些数组运算的内置函数:

  

  In [48]: a = np.arange(6)

  

  In [49]: a

  Out[49]: array([0, 1, 2, 3, 4, 5])

  

  In [50]: np.cos(a)

  Out[50]:

  array([ 1.        ,  0.54030231, -0.41614684, -0.9899925 , -0.65364362,

          0.28366219])

  

  In [52]: np.exp(a)

  Out[52]:

  array([   1.        ,    2.71828183,    7.3890561 ,   20.08553692,

           54.59815003,  148.4131591 ])

  

  In [53]: np.sqrt(a)

  Out[53]:

  array([ 0.        ,  1.        ,  1.41421356,  1.73205081,  2.        ,

          2.23606798])

  

  Numpy提供了一些基本的统计功能,包括求和、求平均值、求方差等:

  

  In [68]: a = np.random.random_integers(1, 5, 6)

  

  In [69]: a

  Out[69]: array([2, 1, 4, 5, 4, 1])

  

  In [70]: a.sum()

  Out[70]: 17

  

  In [71]: a.mean()

  Out[71]: 2.8333333333333335

  

  In [72]: a.std()

  Out[72]: 1.5723301886761005

  

  In [73]: a.min()

  Out[73]: 1

  

  In [74]: a.max()

  Out[74]: 5

  

  In [75]: a.argmin()            # 最小值元素所在的索引

  Out[75]: 1

  

  In [76]: a.argmax()            # 最大值元素所在的索引

  Out[76]: 3

  

  针对二维数组或者更高维度的数组,可以根据行或列来计算。

  

  In [77]: b = np.random.random_integers(1, 5, (6, 4))

  

  In [78]: b

  Out[78]:

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

        [4, 5, 1, 1],

        [4, 4, 1, 4],

        [3, 3, 4, 4],

        [3, 2, 4, 5],

        [3, 5, 2, 5]])

  

  In [85]: b.sum()

  Out[85]: 78

  

  In [86]: b.sum(axis=0)

  Out[86]: array([20, 21, 16, 21])

  

  In [87]: b.sum(axis=1)

  Out[87]: array([11, 11, 13, 14, 14, 15])

  

  In [88]: b.sum(axis=1).sum()

  Out[88]: 78

  

  In [94]: b.min(axis=1)

  Out[94]: array([2, 1, 1, 3, 2, 2])

  

  In [95]: b.argmin(axis=1)

  Out[95]: array([1, 2, 2, 0, 1, 2])

  

  In [96]: b.std(axis=1)

  Out[96]:

  array([ 0.8291562 ,  1.78535711,  1.29903811,  0.5       ,  1.11803399,

          1.29903811])

  

  其中,axis参数表示坐标轴,0表示按行计算,1表示按列计算。需要特别注意的是,按列计算后,计算结果Numpy会默认转换为行向量。这个行为和MATLAB/Octave等数值计算软件有较大的差异。

  下面通过例子来看一下Numpy数值计算的应用,考察的是简单的一维随机漫步算法。比如,两个人用一个均匀的硬币来***,硬币抛出正面和反面的概率各占一半。硬币抛出正面时甲方输给乙方一块钱,反面时乙方输给甲方一块钱。我们来考察在这种***规则下,随着抛硬币次数的增加,输赢的总金额呈现怎样的分布。

  若要解决这个问题,我们可以让足够多的人两两组成一组参与这个***游戏,然后抛足够多的硬币次数,就可以用统计的方法算出输赢的平均金额。当使用Numpy来实现时,生成多个由-1和1构成的足够长的随机数组,用来代表每次硬币抛出正面和反面的事件。这个二维数组中,每行表示一组参与***的人抛出正面和反面的事件序列,然后按行计算这个数组的累加和就是这每组输赢的金额,如图2-2所示。

image.png

图2-2  硬币***示意图

  在实际计算时,先求出每组输赢金额的平方,再求平均值。最后把平方根的值用绿色的点画在二维坐标上,同时画出的红色曲线,来对比两组曲线的重合情况。

  

  %matplotlib inline

  import matplotlib.pyplot as plt

  import numpy as np

  

  n_person = 2000          # 总共创建 2000 组参加***

  n_times = 500            # 抛硬币 500 次

  

  t = np.arange(n_times)

  # 创建只包含 -1 和 1 两种类型元素的数组来表示输赢序列

  steps = 2 * np.random.random_integers(0, 1, (n_person, n_times)) - 1

  

  amount = np.cumsum(steps, axis=1)            # 计算每组的输赢总额

  sd_amount = amount ** 2                      # 计算平方

  mean_sd_amount = sd_amount.mean(axis=0)      # 所有参加***的组求平均值

  

  # 画出数据(绿色),同时画出平方根的曲线(红色)

  plt.xlabel(r"$t$")

  plt.ylabel(r"$\sqrt{\langle (\delta x)^2 \rangle}$")

  plt.plot(t, np.sqrt(mean_sd_amount), 'g.', t, np.sqrt(t), 'r-');

  

  上面代码可以直接在IPython notebook环境下运行,可以看到两根线基本重叠。即一维随机漫步算法的***法则,输赢的金额和***的次数之间基本呈现平方根曲线的分布,如图2-3所示。

image.png

图2-3  一维随机漫步算法

  感兴趣的读者,可以参阅Wikipedia上二维随机漫步的情况en.wikipedia.org/wiki/ Random_walk,也可以思考一下如何使用Numpy来实现。

  在之前的代码里,我们经常使用np.reshape()进行数组维度变换,而np.ravel()则正好相反,它把多维数组“摊平”,变成一维向量。

  

  In [11]: a = np.arange(12)

  

  In [12]: a

  Out[12]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

  

  In [14]: b = a.reshape(4, 3)          # 转换为 4?3 的二维数组

  

  In [15]: b

  Out[15]:

  array([[ 0,  1,  2],

        [ 3,  4,  5],

        [ 6,  7,  8],

        [ 9, 10, 11]])

  

  In [16]: b.ravel()                        # 变为一维向量

  Out[16]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

  

  另外一个常用的方法是使用np.newaxis给数组添加一个维度。

  

  In [20]: a = np.arange(4)

  

  In [21]: a

  Out[21]: array([0, 1, 2, 3])

  

  In [22]: a.shape

  Out[22]: (4,)

  

  In [23]: b = a[:, np.newaxis]            # 在列上添加一个维度,变成 4?1 数组

  

  In [24]: b

  Out[24]:

  array([[0],

        [1],

        [2],

        [3]])

  

  In [25]: b.shape

  Out[25]: (4, 1)

  

  In [26]: c = a[np.newaxis, :]          # 在行上添加一个维度,变成 1?4 数组

  

  In [27]: c

  Out[27]: array([[0, 1, 2, 3]])

  

  In [28]: c.shape

  Out[28]: (1, 4)

  

  Numpy提供了数组排序的功能,可以按行单独排序,也可以按列单独排序。排序时,可以返回一个备份,也可以直接把排序后的结果保存在当前数组里。

  

  In [36]: a = np.random.random_integers(1, 10, (6, 4))

  

  In [37]: a

  Out[37]:

  array([[ 1,  4,  8, 10],

        [10,  9,  6,  2],

        [ 1,  4, 10,  5],

        [ 5,  7,  1,  1],

        [ 5,  2,  2,  8],

        [ 6, 10, 10,  7]])

  

  In [40]: b = np.sort(a, axis=1)        # 按行独立排序,返回一个备份

  

  In [41]: b

  Out[41]:

  array([[ 1,  4,  8, 10],

        [ 2,  6,  9, 10],

        [ 1,  4,  5, 10],

        [ 1,  1,  5,  7],

        [ 2,  2,  5,  8],

        [ 6,  7, 10, 10]])

  

  In [42]: a.sort(axis=0)                # 按列排序,直接把结果保存到当前数组

  

  In [43]: a

  Out[43]:

  array([[ 1,  2,  1,  1],

        [ 1,  4,  2,  2],

        [ 5,  4,  6,  5],

        [ 5,  7,  8,  7],

        [ 6,  9, 10,  8],

        [10, 10, 10, 10]])

  

  还可以直接计算出排序后的索引,利用排序后的索引可以直接获取到排序后的数组。

  

  In [52]: a = np.random.random_integers(1, 10, 6)

  

  In [53]: a

  Out[53]: array([10,  5,  3,  8,  4,  8])

  

  In [55]: idx = a.argsort()

  

  In [56]: idx

  Out[56]: array([2, 4, 1, 3, 5, 0])

  

  In [57]: a[idx]

  Out[57]: array([ 3,  4,  5,  8,  8, 10])

  

  Numpy的高级功能包括多项式求解以及多项式拟合的功能。下面的代码构建了一个二阶多项式x2 -4x+3。

  

  In [80]: p = np.poly1d([1, -4, 3])      # 二阶多项式的系数

  

  In [81]: p(0)                            # 自变量为 0 时的多项式的值

  Out[81]: 3

  

  In [82]: p.roots                            # 多项式的根

  Out[82]: array([ 3.,  1.])

  

  In [83]: p(p.roots)                        # 多项式根处的值肯定是 0

  Out[83]: array([ 0.,  0.])

  

  In [84]: p.order                            # 多项式的阶数

  Out[84]: 2

  

  In [85]: p.coeffs                        # 多项式的系数

  Out[85]: array([ 1, -4,  3])

  

  Numpy提供的np.polyfit()函数可以用多项式对数据进行拟合。在下面的例子里,我们生成20个在平方根曲线周围引入随机噪声的点,用一个3阶多项式来拟合这些点。

  

  %matplotlib inline

  import matplotlib.pyplot as plt

  import numpy as np

  

  n_dots = 20

  n_order = 3

  

  x = np.linspace(0, 1, n_dots)                    # [0, 1] 之间创建 20 个点

  y = np.sqrt(x) + 0.2*np.random.rand(n_dots)

  p = np.poly1d(np.polyfit(x, y, n_order))      # 用 3 阶多项式拟合

  print p.coeffs

  

  # 画出拟合出来的多项式所表达的曲线以及原始的点

  t = np.linspace(0, 1, 200)

  plt.plot(x, y, 'ro', t, p(t), '-');

  运行效果如图2-4所示。

image.png

图2-4  多项式拟合

  另外一个有意思的例子是,使用Numpy求圆周率π的值。使用的算法是蒙特卡罗方法(Monte Carlo method)。其主要思想是,在一个正方形内,用正方形的边长画出一个1/4圆的扇形,假设圆的半径为r,则正方形的面积为r2,圆的面积为1/4πr2,它们的面积之比是π/4。

  我们在正方形内随机产生足够多的点,计算落在扇形区域内的点的个数与总的点个数的比值。当产生的随机点足够多时,这个比值和面积比值应该是一致的。这样我们就可以算出π的值。判断一个点是否落在扇形区域的方法是计算这个点到圆心的距离,当距离小于半径时,说明这个点落在了扇形内,如图2-5所示。

image.png

图2-5  蒙特卡罗方法计算圆周率

  import numpy as np

  

  # 假设圆的半径为1,圆心在原点

  n_dots = 1000000

  x = np.random.random(n_dots)

  y = np.random.random(n_dots)                    # 随机产生一百万个点

  distance = np.sqrt(x ** 2 + y ** 2)              # 计算每个点到圆心的距离

  in_circle = distance[distance < 1]              # 所有落在扇形内的点

  

  pi = 4 * float(len(in_circle)) / n_dots        # 计算出 PI 的值

  print pi

  

  如果我们取一百万个点,计算出来的圆周率大概是3.142376,读者可以修改n_dots的值来看不同的点的个数对计算结果的精度影响。蒙特卡罗方法算法还有非常广泛的应用,感兴趣的读者可以在网络上搜索一下这个算法,详细了解其应用场景。

  最后介绍一下Numpy文件相关的操作。Numpy数组作为文本文件,可以直接保存到文件系统里,也可以从文件系统里读取出数据:

  

  In [92]: a = np.arange(15).reshape(3,5)

  

  In [93]: a

  Out[93]:

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

        [ 5,  6,  7,  8,  9],

        [10, 11, 12, 13, 14]])

  

  In [94]: np.savetxt('a.txt', a)

  

  In [95]: ls

  a.txt

  

  In [96]: cat a.txt                  # 写入时把整型转换为浮点型了

  0.000000000000000000e+00 1.000000000000000000e+00 2.000000000000000000e+00 

  3.000000000000000000e+00 4.000000000000000000e+00

  5.000000000000000000e+00 6.000000000000000000e+00 7.000000000000000000e+00 

  8.000000000000000000e+00 9.000000000000000000e+00

  1.000000000000000000e+01 1.100000000000000000e+01 1.200000000000000000e+01 

    1.300000000000000000e+01 1.400000000000000000e+01

  

  In [97]: b = np.loadtxt('a.txt')

  

  In [98]: b                          # 读出时也是浮点型

  Out[98]:

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

        [  5.,   6.,   7.,   8.,   9.],

        [ 10.,  11.,  12.,  13.,  14.]])

  

  保存为文本格式的可读性好,但性能较低。也可以直接保存为Numpy特有的二进制格式:

  

  In [116]: a

  Out[116]:

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

        [ 5,  6,  7,  8,  9],

        [10, 11, 12, 13, 14]])

  

  In [117]: np.save('a.npy', a)

  

  In [118]: ls -l          # 新生成的 a.npy 与 a.txt 的大小不同

  total 8

  -rw-r--r-- 1 kamidox 200 Mar 27 21:27 a.npy

  -rw-r--r-- 1 kamidox 375 Mar 27 21:00 a.txt

  

  In [119]: c = np.load('a.npy')

  

  In [120]: c                  # 读入的是整型,不是浮点型

  Out[120]:

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

        [ 5,  6,  7,  8,  9],

        [10, 11, 12, 13, 14]])

  

  本节只是Numpy的快速入门介绍,详细的信息可以参阅Numpy的官方网站www.numpy.  org。本节介绍的几个例子可参阅随书代码ch02.02.ipynb。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。