# 29 数组和向量化计算

## 大纲

* 一维数组
* 二维数组
* 数组操作
* 向量化计算
* 练习

## 一维数组

前面几课我们学习到了很多数据类型，例如列表、字典、集合等，它们都可以保存一系列的数据信息。不过对于大规模的数据计算，它们并不都适合，而现代很多人工智能处理的数据类型都是数组(array)，让我们看一下数组的特点，如何操作，它和之前学到数据类型的联系和区别。

```python
import numpy as np
```

加载导入numpy模块，将这个模块重命名为np，主要是为了后面代码写起来简单。

```python
x_list = [1,2,3,4]
type(x_list)
```

```
list
```

先建立一个列表，再建立一个数组，打印二者的类型，可以看到类型不同。

```python
x_array = np.array([1,2,3,4])
type(x_array)
```

```
numpy.ndarray
```

数组的特点在于，它是有维度的，ndim可以告诉我们维度有几维，因为x\_array只是一个向量，它只有一维，同时shape可以告诉我们有多少个元素。

```python
x_array.ndim
```

```
1
```

```python
x_array.shape
```

```
(4,)
```

```python
x_array.size
```

```
4
```

dtype可以告诉我们数组内部存放的数据是什么格式的，这里的具体格式是32位整数型。

```python
x_array.dtype
```

```
dtype('int32')
```

数组的重要特点在于支持向量化计算，如果我们要计算这些数值的平方再求和，如果是用列表的数据类型，根据我们之前学到的知识，需要写一个循环，或是使用列表解析。

```python
x_sum = 0
for x in x_list:
    x_sum = x_sum+x**2
print(x_sum)
```

```
30
```

```python
sum([x**2 for x in x_list])
```

```
30
```

如果是数组类型，则更加简单，它可以直接对每个元素做平方操作。这个例子可以看到数组的优点，在于向量化运算非常方便快速。

```python
x_array**2
```

```
array([ 1,  4,  9, 16], dtype=int32)
```

```python
sum(x_array**2)
```

```
30
```

前面是手动输入一些数值创建数组，可以无需手动输入，创建一些有规律的数组，例如全为0或全为1的数组。

```python
np.ones(4)
```

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

```python
np.zeros(4)
```

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

也可以定义一些有规律的序列，例如利用arange来定义从0到1的序列数组，不同元素间的步长间隔是0.2，这样就产生了0、0.2、0.4、0.6、0.8这样的序列。

```python
np.arange(start=0,stop=1,step=0.2)
```

```
array([0. , 0.2, 0.4, 0.6, 0.8])
```

linspace是类似的函数，不过参数num是指最终产生多少个元素，程序会自动判断步长间隔。

```python
np.linspace(start=0, stop=1, num=5)
```

```
array([0.  , 0.25, 0.5 , 0.75, 1.  ])
```

小结：数组和列表很相似，它们都是一组元素构成，例如平面上的坐标就是有两个元素的向量，数组的优点在于向量化计算，快速而方便。

## 二维数组

前面建立的是向量，是一维的数组，数组可以是高维的，我们先来建立一个二维的数组，其实可以看作是一个矩阵。矩阵从直观上理解就是排成一个矩形的数组。下面来定义一个简单的二维数组。

```python
x = np.array([[1,2],[3,4]])
print(x)
```

```
[[1 2]
 [3 4]]
```

上例用一个嵌套列表放入array函数，定义了一个二维数组，二维数组中有四个元素，以2乘2的形式排列。

```python
x.ndim
```

```
2
```

```python
x.shape
```

```
(2, 2)
```

```python
x.size
```

```
4
```

此时用ndim函数可以告诉我们这个数组有两个维度，排列的形式是2行2列，总共有4个元素。和一维数组一样，也可以用zeros来定义全0的二维数组。

```python
x = np.zeros([3,3])
print(x)
```

```
[[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]
```

```python
x.shape
```

```
(3, 3)
```

二维数组可以直接定义，也可以通过对一维数组的修改来定义，例如下面是先定义一个一维数组，然后使用reshape，将其修改成一个2行5列的二维数组。

```python
x = np.arange(0,1,0.1)
```

```python
x.shape
```

```
(10,)
```

```python
x = x.reshape(2,5)
```

```python
print(x)
```

```
[[0.  0.1 0.2 0.3 0.4]
 [0.5 0.6 0.7 0.8 0.9]]
```

```python
x.shape
```

```
(2, 5)
```

小结：一维数组可以理解为在一条线上排列的向量，而二维数组在一个矩形平面上排列的向量，也可以定义更高维的数组。

## 数组操作

和列表等数据一样，可以做创建、选择、修改、删除、补充等操作，先定义一个简单的一维数组。

```python
x = np.arange(0,1,0.1)
print(x)
```

```
[0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]
```

数组可以做选取操作，就是选择其中某个或某些元素。这种操作是用方括号来进行的。

```python
x[0]
```

```
0.0
```

```python
x[-1]
```

```
0.9
```

```python
x[:3]
```

```
array([0. , 0.1, 0.2])
```

和列表类似，可以在方括号内使用数字编号或结合冒号选择一个或多个元素。对于二维数组，选取操作略有不同。

```python
x = x.reshape(2,5)
print(x)
```

```
[[0.  0.1 0.2 0.3 0.4]
 [0.5 0.6 0.7 0.8 0.9]]
```

```python
x[0,1]
```

```
0.1
```

此时选取操作也是在一个方括号中进行，不过需要有一个逗号，逗号前表示取哪一行，后面表示取哪一列，上例就是取第0行，第1列的数字

```python
x[1,:]
```

```
array([0.5, 0.6, 0.7, 0.8, 0.9])
```

```python
x[:,-1]
```

```
array([0.4, 0.9])
```

也可以结合冒号，表示选取第1行所有元素，或是最后一列的所有元素。数组中的元素不仅可以通过方括号来选取，还可以用来修改。

```python
x[0,0] = 1
```

```python
x
```

```
array([[1. , 0.1, 0.2, 0.3, 0.4],
       [0.5, 0.6, 0.7, 0.8, 0.9]])
```

numpy也提供了一系列的函数来对数组进行添加和删除操作。

```python
np.append(x,10)
```

```
array([ 1. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9, 10. ])
```

```python
np.insert(x,0,10)
```

```
array([10. ,  1. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9])
```

```python
np.delete(x,-1)
```

```
array([1. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8])
```

数组里不仅可以存放数字，还可以存放计算逻辑值，例如来计算x这个数组中大于0.5的有哪些。返回了一个逻辑数组。

```python
y = x>=0.5
print(y)
```

```
[[ True False False False False]
 [ True  True  True  True  True]]
```

逻辑数组可以放入方括号，作为选取的标准。

```python
x[y]
```

```
array([1. , 0.5, 0.6, 0.7, 0.8, 0.9])
```

```python
x[x>=0.5]
```

```
array([1. , 0.5, 0.6, 0.7, 0.8, 0.9])
```

```python
x[x>=0.5] = 1
```

```python
x
```

```
array([[1. , 0.1, 0.2, 0.3, 0.4],
       [1. , 1. , 1. , 1. , 1. ]])
```

```python
x
```

```
array([[1. , 0.1, 0.2, 0.3, 0.4],
       [1. , 1. , 1. , 1. , 1. ]])
```

前面我们介绍了数组查询和修改，增加和删除,我们可以将两个数组进行拼接

```python
y = np.arange(10,11,0.1).reshape(2,5)
```

```python
y
```

```
array([[10. , 10.1, 10.2, 10.3, 10.4],
       [10.5, 10.6, 10.7, 10.8, 10.9]])
```

```python
x = np.concatenate([x,y])
```

```python
x
```

```
array([[ 1. ,  0.1,  0.2,  0.3,  0.4],
       [ 1. ,  1. ,  1. ,  1. ,  1. ],
       [10. , 10.1, 10.2, 10.3, 10.4],
       [10.5, 10.6, 10.7, 10.8, 10.9]])
```

小结：二维数组是一维数组的扩展，它的选取、修改和一维数组是类似的，只不过需要两个数字编号进行操作，二维数组是最常见的人工智能计算载体，需要多加练习。

## 向量化计算

所谓向量化计算，是指numpy格式的数组，不需要做循环就可以进行很丰富的数学计算。例如加减乘除等基本运算。

```python
x = np.linspace(0,1,5)
print(x)
```

```
[0.   0.25 0.5  0.75 1.  ]
```

```python
3 * x
```

```
array([0.  , 0.75, 1.5 , 2.25, 3.  ])
```

```python
x/2
```

```
array([0.   , 0.125, 0.25 , 0.375, 0.5  ])
```

```python
x**2 + 2*x - 3
```

```
array([-3.    , -2.4375, -1.75  , -0.9375,  0.    ])
```

```python
np.sin(x)
```

```
array([0.        , 0.24740396, 0.47942554, 0.68163876, 0.84147098])
```

如果是一个自定义的函数，它并不天然支持这种向量化的表达。

```python
x = np.arange(1,10,dtype=int)
print(x)
```

```
[1 2 3 4 5 6 7 8 9]
```

```python
def odd_even(x):
    if x%2 == 0:
        return "偶数"
    else:
        return "奇数"
```

```python
odd_even(x)
```

```
ValueError                                Traceback (most recent call last)

<ipython-input-20-451016d16111> in <module>()
----> 1 odd_even(x)


<ipython-input-19-36bfaa3761ac> in odd_even(x)
      1 def odd_even(x):
----> 2     if x%2 == 0:
      3         return "偶数"
      4     else:
      5         return "奇数"


ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
```

此时需要用vectorize来将这个普通函数转成一个向量化函数。

```python
odd_even_v = np.vectorize(odd_even)
```

```python
print(odd_even_v(x))
```

```
['奇数' '偶数' '奇数' '偶数' '奇数' '偶数' '奇数' '偶数' '奇数']
```

其实这个函数的功能可以直接用下面的代码来完成，即找到那些可以被2整除的数字，然后选取出来

```python
x[x%2==0]
```

```
array([2, 4, 6, 8])
```

还有一种非常重要的计算就是矩阵计算。

```python
x = np.array([[1,0],[1,0]])
print(x)
```

```
[[1 0]
 [1 0]]
```

```python
y = np.array([[0.1,0.2],[0.3,0.4]])
print(y)
```

```
[[0.1 0.2]
 [0.3 0.4]]
```

```python
x.dot(y)
```

```
array([[0.1, 0.2],
       [0.1, 0.2]])
```

小结：向量化计算是数组的特有功能，不需要写循环，计算速度快。在对数值进行编程时应该尽量转化为numpy数组，以利用向量化计算。如果是自定义函数，则可以使用vectorize进行转换。

## 练习

用列表和数组两种方法，找到一百万以下所有能被3和7整除的数字之和，并比较两种方法的计算消耗时间。

```python
n = 1000000
```

```python
from time import time
time_start = time()
sum_list = sum([x for x in range(n) if x%3==0 and x%7==0])
time_end = time()
print("运行时间{x:.4f}秒,和等于{y}".format(x=time_end-time_start,y=sum_list))
```

```
运行时间0.0719秒,和等于23809976190
```

```python
from time import time
time_start = time()
x = np.arange(n,dtype='int64')
sum_array = np.sum(x[(x%3==0)&(x%7==0)])
time_end = time()
print("运行时间{x:.4f}秒,和等于{y}".format(x=time_end-time_start,y=sum_array))
```

```
运行时间0.0409秒,和等于23809976190
```

##
