NumPy 快速入门
NumPy 是用于 Python 中多维数组计算的软件库。
本文部分内容翻译自斯坦福大学文档 CS231n - Python NumPy Tutorial。
什么是 NumPy
NumPy 是 Python 中科学计算的基础包。它是一个 Python 库,提供多维数组对象、各种派生对象 (例如数组和矩阵) 以及各种用于快速数组操作的例程,包括数学、逻辑、形状操作、排序、选择、I/O、离散傅里叶变换、基本线性代数、基本统计运算、随机模拟等等。
NumPy 包的核心是 ndarray 对象。它封装了同构数据类型的 n 维数组,许多操作在编译代码中执行以提高性能。NumPy 数组和标准 Python 序列之间有几个重要的区别:
- 与 Python 列表 (可以动态增长) 不同,NumPy 数组在创建时大小是固定的。更改 ndarray 的大小将创建一个新数组并删除原始数组。
- NumPy 数组中的元素都必须具有相同的数据类型,因此在内存中的大小也相同。例外:可以拥有 (Python,包括 NumPy) 对象的数组,从而允许不同大小元素的数组。
- NumPy 数组有助于对大量数据进行高级数学和其他类型的运算。通常,与使用 Python 的内置序列相比,此类操作的执行效率更高,并且代码更少。
越来越多的基于 Python 的科学和数学包正在使用 NumPy 数组;尽管这些通常支持 Python 序列输入,但它们在处理之前将此类输入转换为 NumPy 数组,并且通常输出 NumPy 数组。换句话说,为了有效地使用当今大部分 (甚至是大多数) 基于 Python 的科学/数学软件,仅仅知道如何使用 Python 的内置序列类型是不够的 - 还需要知道如何使用 NumPy 数组。
关于序列大小和速度的问题在科学计算中尤为重要。作为一个简单的示例,考虑将一维序列中的每个元素与相同长度的另一个序列中的相应元素相乘的情况。如果数据存储在两个 Python 列表 a
和 b
中,我们可以迭代每个元素:
c = []
for i in range(len(a)):
c.append(a[i] * b[i])
这会产生正确的答案,但如果 a
和 b
各自包含数百万个数字,我们将为 Python 中的循环效率低下付出代价。我们可以通过编写 C 语言更快地完成相同的任务 (为了清楚起见,我们忽略了变量声明和初始化、内存分配等)
for (i = 0; i < rows; i++) {
c[i] = a[i]*b[i];
}
这节省了解释 Python 代码和操作 Python 对象所涉及的所有开销,但代价是牺牲了从 Python 编码中获得的好处。此外,所需的编码工作随着数据维度的增加而增加。例如,在二维数组的情况下,C 代码 (如前所述) 扩展为
for (i = 0; i < rows; i++) {
for (j = 0; j < columns; j++) {
c[i][j] = a[i][j]*b[i][j];
}
}
NumPy 为我们提供了两全其美的功能:当涉及 ndarray 时,逐元素操作是“默认模式”,但逐元素操作可以通过预编译的 C 代码快速执行。在 NumPy 中可以用
c = a * b
执行前面示例的操作,速度接近 C 语言,同时代码简单,符合我们基于 Python 的期望。
为什么 NumPy 很快?
矢量化的代码没有任何显式循环、索引等 - 当然,这些事情实际上是在优化的预编译 C 代码中被处理了。矢量化代码具有许多优点,其中包括:
- 矢量化代码更简洁,更容易阅读
- 更少的代码行通常意味着更少的错误
- 代码更类似于标准数学符号 (通常更容易正确编码数学结构)
- 矢量化会产生更多符合 Python 风格的代码;如果没有矢量化,我们的代码将充斥着低效且难以阅读的
for
循环
使用 Numpy
Numpy 是 Python 中科学计算的核心库。它提供了一个高性能的多维数组对象,以及使用这些数组的工具。
数组
Numpy 的核心功能是 ndarray
(即 n-dimensional array
,多维数组) 数据结构。这是一个表示多维度、同质并且固定大小的数组对象。维数是数组的秩 rank
;数组的形状 shape
是一个整数元组,表示沿着每个维度的数组大小。
我们可以从嵌套的 Python 列表初始化 Numpy 数组,并使用方括号来访问元素:
import numpy as np
a = np.array([1, 2, 3]) # Create a rank 1 array
print type(a) # Prints "<type 'numpy.ndarray'>"
print a.shape # Prints "(3,)"
print a[0], a[1], a[2] # Prints "1 2 3"
a[0] = 5 # Change an element of the array
print a # Prints "[5, 2, 3]"
b = np.array([[1,2,3],[4,5,6]]) # Create a rank 2 array
print b.shape # Prints "(2, 3)"
print b[0, 0], b[0, 1], b[1, 0] # Prints "1 2 4"
Numpy 还提供许多创建数组的函数:
import numpy as np
a = np.zeros((2,2)) # Create an array of all zeros
print a # Prints "[[ 0. 0.]
# [ 0. 0.]]"
b = np.ones((1,2)) # Create an array of all ones
print b # Prints "[[ 1. 1.]]"
c = np.full((2,2), 7) # Create a constant array
print c # Prints "[[ 7. 7.]
# [ 7. 7.]]"
d = np.eye(2) # Create a 2x2 identity matrix
print d # Prints "[[ 1. 0.]
# [ 0. 1.]]"
e = np.random.random((2,2)) # Create an array filled with random values
print e # Might print "[[ 0.91940167 0.08143941]
# [ 0.68744134 0.87236687]]"
你可以在这份文档中阅读关于数组创建的其他方法。
数组索引
Numpy 提供了多种索引到数组的方法。
切片:与 Python 列表类似,可以对 Numpy 数组进行切片。由于数组可能是多维的,因此必须为数组的每个维度指定一个切片:
import numpy as np
# Create the following rank 2 array with shape (3, 4)
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]]
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
# Use slicing to pull out the subarray consisting of the first 2 rows
# and columns 1 and 2; b is the following array of shape (2, 2):
# [[2 3]
# [6 7]]
b = a[:2, 1:3]
# A slice of an array is a view into the same data, so modifying it
# will modify the original array.
print a[0, 1] # Prints "2"
b[0, 0] = 77 # b[0, 0] is the same piece of data as a[0, 1]
print a[0, 1] # Prints "77"
你还可以将整数索引与片段索引进行混合。但是,这样做会产生比原始数组更低的数组:
import numpy as np
# Create the following rank 2 array with shape (3, 4)
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]]
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
# Two ways of accessing the data in the middle row of the array.
# Mixing integer indexing with slices yields an array of lower rank,
# while using only slices yields an array of the same rank as the
# original array:
row_r1 = a[1, :] # Rank 1 view of the second row of a
row_r2 = a[1:2, :] # Rank 2 view of the second row of a
print row_r1, row_r1.shape # Prints "[5 6 7 8] (4,)"
print row_r2, row_r2.shape # Prints "[[5 6 7 8]] (1, 4)"
# We can make the same distinction when accessing columns of an array:
col_r1 = a[:, 1]
col_r2 = a[:, 1:2]
print col_r1, col_r1.shape # Prints "[ 2 6 10] (3,)"
print col_r2, col_r2.shape # Prints "[[ 2]
# [ 6]
# [10]] (3, 1)"
整数数组索引:当你使用切片索引到 Numpy 数组时,生成的数组视图将始终是原始数组的子数组。相反,整数数组索引允许你使用另一个数组的数据来构造任意数组。例如:
import numpy as np
a = np.array([[1,2], [3, 4], [5, 6]])
# An example of integer array indexing.
# The returned array will have shape (3,) and
print a[[0, 1, 2], [0, 1, 0]] # Prints "[1 4 5]"
# The above example of integer array indexing is equivalent to this:
print np.array([a[0, 0], a[1, 1], a[2, 0]]) # Prints "[1 4 5]"
# When using integer array indexing, you can reuse the same
# element from the source array:
print a[[0, 0], [1, 1]] # Prints "[2 2]"
# Equivalent to the previous integer array indexing example
print np.array([a[0, 1], a[0, 1]]) # Prints "[2 2]"
一个有用的整数数组索引技巧,是从矩阵的每一行中选择一个元素:
import numpy as np
# Create a new array from which we will select elements
a = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
print a # prints "array([[ 1, 2, 3],
# [ 4, 5, 6],
# [ 7, 8, 9],
# [10, 11, 12]])"
# Create an array of indices
b = np.array([0, 2, 0, 1])
# Select one element from each row of a using the indices in b
print a[np.arange(4), b] # Prints "[ 1 6 7 11]"
# Mutate one element from each row of a using the indices in b
a[np.arange(4), b] += 10
print a # prints "array([[11, 2, 3],
# [ 4, 5, 16],
# [17, 8, 9],
# [10, 21, 12]])
布尔数组索引:布尔数组索引可以让你选择数组的任意元素。通常,这种类型的索引用于选择满足某些条件的数组的元素。例如:
import numpy as np
a = np.array([[1,2], [3, 4], [5, 6]])
bool_idx = (a > 2) # Find the elements of a that are bigger than 2;
# this returns a numpy array of Booleans of the same
# shape as a, where each slot of bool_idx tells
# whether that element of a is > 2.
print bool_idx # Prints "[[False False]
# [ True True]
# [ True True]]"
# We use boolean array indexing to construct a rank 1 array
# consisting of the elements of a corresponding to the True values
# of bool_idx
print a[bool_idx] # Prints "[3 4 5 6]"
# We can do all of the above in a single concise statement:
print a[a > 2] # Prints "[3 4 5 6]"
为了简洁起见,我们省略了很多有关 Numpy 数组索引的细节;如果你想了解更多,请阅读这份文档。
数据类型
每个 Numpy 数组都是相同类型元素的网格。Numpy 提供了大量可用于构造数组的数值数据类型。创建数组时,Numpy 会尝试猜测数据类型,但构造数组的函数通常还包含一个可选参数来显式指定数据类型。下面是一个例子:
import numpy as np
x = np.array([1, 2]) # Let numpy choose the datatype
print(x.dtype) # Prints "int64"
x = np.array([1.0, 2.0]) # Let numpy choose the datatype
print(x.dtype) # Prints "float64"
x = np.array([1, 2], dtype=np.int64) # Force a particular datatype
print(x.dtype) # Prints "int64"