4.1 向量
抽象地说,向量是指可以加总(以生成新的向量),可以乘以标量(即数字),也可以生成 新的向量的对象。
具体来说(对我们而言),向量是有限维空间的点。即使你本无意视你的数据为向量,将数值数据表示为向量也是非常好的处理方式。
比如,如果你有很多人的身高、体重、年龄数据,就可以把数据记为三维向量 (height, weight, age)。如果你教的一个班有四门考试,就可以把学生成绩记为四维向量 (exam1, exam2, exam3, exam4)。
最简单的入门方法是将向量表示为数字的列表。一个包含三个数字的列表对应一个三维空 间的向量,反之亦然:
<code>height_weight_age
=
[70,
170
,
40
]
grade
=
[95,
80
,#考试2
75
,#考试3
62
]
/<code>
首先,我们常常需要对两个向量做加法。向量以分量方式做运算。这意味着,如果两个向量v和w长度相同,那它们的和就是一个新的向量,
其中向量的第一个元素等于 v[0] +w[0], 第二个元素等于v[1] +w[1], 以此类推。(如果两个向量的长度不同,则不能相加。)
例如, 向量[1, 2]加上向量[2, 1] 等于[1+2 , 2+1] 或[3, 3] , 如图 4,1
我们可以很容易的实现这个功能: 对向量调用zip函数, 同时用列表解析使向量的相应元素相加:
<code>def
vector_add
(v , w)
""
"adds corresponding elements"
""
return
[v_i + w_ifor
v_i , w_iin
zip(v , w)] 同样,对两个向量做减法,只需要使向量的相应元素相减:def
vector_subtract
( v, w)
:""
"subtracts corresponding elements"
""
return
[v_i - w_ifor
v_i, w_iin
zip(v, w)] /<code>
有时,我们需要对一系列向量做加法。即生成一个新向量, 其第一个元素是这一系列向量第一个元素的和,第二个元素是这一系列向量第二个元素的和,以此类推。最简单的方法是每次递加一个向量:
<code>def
vector_sum
(vectors)
:"""sums all corresponding elements"""
result = vectors[0
]for
vectorin
vectors[1
: ]: result = vector_add(result, vector)return
result/<code>
当你思考这个解决方法时,我们正通过 vector_add 函数,即使用 reduce 的方式来加总这 一系列的向量。换句话说,我们用高级的函数更加简洁地实现了这个功能:
<code>def
vector_sum
(vectors)
:return
reduce(vector_add, vectors)/<code>
4.2 矩阵
矩阵是一个二维的数据集合。我们将矩阵表示为列表的列表,每个内部列表的大小都一 样,表示矩阵的一行。如果 A 是一个矩阵,那么 A[i][j] 就表示第 i 行第 j 列的元素。按照 数学表达的惯例,我们通常用大写字母表示矩阵。例如:
<code>A =[[1, 2, 3], # A有2行3列 [4, 5, 6]]
B =[[1, 2], # B有3行2列 [3, 4], [5, 6]]
/<code>
在数学中,矩阵的第一行通常称为“第 1 行”,第一列通常称为“第 1 列”。 而我们需要将矩阵的形式和 Python 的列表统一起来:Python 中的列表从 0 开始索引,所以,我们将矩阵的第一行称为“第 0 行”,将第一列称为“第 0 列”。
基于列表的列表这种表达形式,矩阵 A 具有 len(A) 行和 len(A[0]) 列,我们把这称作它的 形状(shape):
<code>def
shape
(A)
: num_rows = len(A) num_cols = len(A[0
])if
Aelse
0
return
num_rows, num_cols/<code>
如果一个矩阵有 n 行 k 列,则可以记为 n×k 矩阵。我们可以把这个 n×k 矩阵的每一行都 当作一个长度为 k 的向量,把每一列都当作一个长度为 n 的向量
<code>def
get_row
(A, i)
:return
A[i]def
get_column
(A, j)
:return
[A_i[j]for
A_iin
A] /<code>
同样,我们也可以根据形状和用来生成元素的函数来创建矩阵。可以通过一个嵌套的列表 解析来实现:
<code>/<code>
有了这个函数,就可以生成一个 5×5 的单位矩阵(对角线元素是 1,其他元素是 0):
<code>def
is_diagonal
(i, j)
:"""1's on the 'diagonal', 0's everywhere else"""
return
1
if
i == jelse
0
identity_matrix = make_matrix(5
,5
, is_diagonal) /<code>
矩阵的重要性不言而喻,原因有以下几个。
首先,可以通过将每个向量看成是矩阵的一行,来用矩阵表示一个包含多维向量的数据 集。例如,如果有 1000 个人的身高、体重和年龄,就可以创建一个 1000×3 的矩阵
<code>data
=
[[70,
170
,
40
],
[65,
120
,
26
],
[77,
250
,
19
],
]
/<code>
第二,随后我们会看到,可以用一个 n×k 矩阵表示一个线性函数,这个函数将一个 k 维的
向量映射到一个 n 维的向量上。我们使用的很多技术与概念都隐含着这样的函数。
第三,可以用矩阵表示二维关系。在第 1 章中,我们曾经将网络边际表示为数据对 (i, j) 的集合。我们还可以通过建立矩阵 A 来实现这个描述:如果节点 i 和节点 j 有关系,则用 矩阵的元素 A[i][j] 为 1 来表示;若节点 i 和节点 j 没有关系,则用 A[i][j] 为 0 来表示。
回想前面讲过的关系:
<code>friendships
=
[(0,
1
),
(0,
2
),
(1,
2
),
(1,
3
),
(2,
3
),
(3,
4
),
(4,
5
),
(5,
6
),
(5,
7
),
(6,
8
),
(7,
8
),
(8,
9
)]
/<code>
可以通过矩阵形式再现:
<code>friendships
=
[[0,
1
,
1
,
0
,
0
,
0
,
0
,
0
,
0
,
0
],
[1,
0
,
1
,
1
,
0
,
0
,
0
,
0
,
0
,
0
],
[1,
1
,
0
,
1
,
0
,
0
,
0
,
0
,
0
,
0
],
[0,
1
,
1
,
0
,
1
,
0
,
0
,
0
,
0
,
0
],
[0,
0
,
0
,
1
,
0
,
1
,
0
,
0
,
0
,
0
],
[0,
0
,
0
,
0
,
1
,
0
,
1
,
1
,
0
,
0
],
[0,
0
,
0
,
0
,
0
,
1
,
0
,
0
,
1
,
0
],
[0,
0
,
0
,
0
,
0
,
1
,
0
,
0
,
1
,
0
],
[0,
0
,
0
,
0
,
0
,
0
,
1
,
1
,
0
,
1
],
[0,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
1
,
0
]]
/<code>
如果关系很少,这种表示形式的效率就会很低,因为必须存储很多零。但是,通过矩阵表 示可以快速地查找确认某两个节点是否是连接的——通过一个矩阵查找函数即可,不必 (有可能会)搜索每条边:
<code>friendships[0
][2
] == 1 # true, 0和2是朋友 friendships[0
][8
] == 1 # false, 0和8不是朋友/<code>
同样,若想找到一个节点的所有连接,只需检查这个节点所在的列(或行):
<code>friends_of_five = [i for i, is_friend in enumerate(friendships[5]) if is_friend] /<code>
为了加速这个搜寻过程,我们之前给每个节点对象都添加了一个连接列表。但对于太大 的、扩展性强的图形来说,成本太高,维护也很难。
本书随后还会探讨这些矩阵。