第4章 基本的数学知识

[toc]

4.2.1 向量和矩阵

向量(vector)指既有大小又有方向的对象。对我们而言,向量是用来表示一系列数字的一维数组。换句话说,向量是一个由数字构成的列表。

向量通常用箭头或者粗体字表示,如下所示:

$\vec{x}$ 或 x

向量可以被拆分为更小粒度,即向量中包含的数字。我们用索引标示法表示向量中的元素,如下所示:

当$\vec{x}$ =$$
\left(
\begin{matrix}
3 \
6 \
8
\end{matrix}
\right) \tag{1}
$$时,$x_1$=3

在数学中,我们通常用索引1表示第1个元素。计算机程序则通常用索引0表示第1个元素

在Python中,我们有多种方式表示数组。例如可以用Python的列表表示前面的数组:

1
x=[3,6,8]

然而,我建议最好用numpy中的数组类型表示数组,因为它能提供更多的向量运算功能。

1
2
import numpy as np
x=np.array([3,6,8])

不管在Python中以何种方式表示向量,向量都为我们提供了一种存储多维单一数据点或观察值的简单方法。

假设我们用0~100表示员工对每个部门的平均满意度,其中人力资源部得分57,工程部得分89,管理部得分94。

我们可以用以下向量表示这一组数据:

x=$$
\left(
\begin{matrix}
x_1 \
x_2 \
x_3
\end{matrix}
\right) \tag{1}$$=$$
\left(
\begin{matrix}
57 \
89 \
94
\end{matrix}
\right) \tag{1}$$

这个向量存储了3个不同的信息,这正是向量在数据科学中的重要用途。

理论上,你也可以将向量想象成Pandas中的Series对象。所以,我们很自然地联想到DataFrame对象。事实上,以上向量只需简单扩展,就可以由一维扩展为多维。

矩阵(matrix)是二维数组的表示形式。矩阵有两个特征需要特别关注。矩阵的维度用n×m表示,即矩阵有n行m列。矩阵通常用大写符合、加粗斜体字表示,比如大写X。例如下面这个矩阵:
$$
\left(
\begin{matrix}
3 & 4 \
8 & 55 \
5 & 9
\end{matrix}
\right)
$$
它有3行2列,所以是3×2矩阵。

如果矩阵的行数和列数相等,则称之为正方形矩阵(square matrix),简称方阵。

矩阵通常用来存储结构化数据,它是Pandas中DataFrame类型的泛化。因此,矩阵是数据科学工具箱中最重要的数学工具。

继续之前的案例,假设有3个位于不同地点的办公室,每个办公室都有人力资源部、工程部和管理部。我们可以用3个向量分别表示以上3个办公室3个部门的满意度得分,如下所示:

x=$$
\left(
\begin{matrix}
57 \
89 \
94
\end{matrix}
\right)$$,y=$$
\left(
\begin{matrix}
67 \
87 \
84
\end{matrix}
\right)$$,z=$$
\left(
\begin{matrix}
65 \
98 \
60
\end{matrix}
\right)$$

然而,这样的表示方式不但难以处理,而且不具有扩展性。如果我们有100个办公室,就需要100个不同的数组存储信息。矩阵恰好能解决这一问题。我们可以创建一个3×3矩阵,每一行代表不同的部门,每一列代表不同的办公室。

办公室1 办公室2 办公室3
人力资源部 57 67 65
工程部 89 87 98
管理部 94 84 60

这样看起来更加自然。我们去掉行标签和列标签之后,剩下的数字就组成了一个真正的矩阵。

X=$$
\begin{pmatrix}
57 & 67 & 65 \
89 & 87 & 98 \
94 & 84 & 60
\end{pmatrix}$$

快速练习

(1)如果增加第4个办公室,矩阵需要增加1行,还是增加1列?

(2)增加第4个办公室后,矩阵的维度是多少?

(3)如果在最初的矩阵X中去除“管理部”的数据,新矩阵的维度是什么?

(4)计算矩阵所含元素数量的通用公式是什么?

答案

(1)增加1列。

(2)3×4

(3)2×3

(4)n×m,其中n为矩阵行数,m为矩阵列数。

4.2.2 算术符号

点积

点积(dot product)是类似于加法和乘法的运算符,通常用来合并两个向量,如下所示:
$$
\begin{pmatrix}
3\
7
\end{pmatrix}
·
\begin{pmatrix}
9\
5
\end{pmatrix}=3×9+7×5=62
$$

点积的结果是一个单一的数值,称为标量(scalar)

这有何用途呢?假设我们用向量表示用户对喜剧、言情剧和动作片3种不同类型电影的喜欢程度,取值范围1~5。

假设某用户喜欢喜剧,不喜欢言情剧,对动作片不喜欢也不讨厌,那么用向量表示如下:
$$
\begin{pmatrix}
5\
1\
3
\end{pmatrix}
$$
其中:

  • 5表示喜欢喜剧;
  • 1表示不喜欢言情剧;
  • 3表示不喜欢也不讨厌动作片。

现在,假设有两部电影,一部是言情喜剧片,一部是喜剧动作片。每部电影同样有自己的特征向量,如下所示:

$$m_1$$=$$
\left(
\begin{matrix}
4 \
5 \
1
\end{matrix}
\right)$$,$$m_2$$=$$
\left(
\begin{matrix}
5 \
1 \
5
\end{matrix}
\right)$$

其中,$m_1$是言情喜剧片,$m_2$是喜剧动作片。

为了决定向用户推荐哪部电影,我们使用点积将用户的偏好向量和每部电影的特征向量相乘,乘积最高的电影将被推荐给用户。

image-20230315173117096

image-20230315173258184

image-20230315173324662

4.2.3 图表

斜率

image-20230315174020847

斜率=m=$ {y_2-y_1 \over x_2-x_1} $

斜率指两点之间的变化比率(rate of change)。变化比率在数据科学种非常重要,特别是在涉及微分方程和微积分运算时。

变化比率是表示变量如何同时变化,以及变化程度的指标。假设我们为咖啡温度和时间建模,得到的变化比率是:

$- {2℉ \over 1min} $

这个变化比率指咖啡温度每分钟下降2℉。

4.2.4 指数/对数

指数(exponent)指数字和自身相乘的次数。

$$2^4$$=2 · 2 · 2 · 2=16

2是底数,4是指数

对数(logarithm)则用于回答“以A数为底(base)得到B的指数是多少?”

$\log_2(16)$=4

2是底数,4是对数

如果你发现这两个概念非常接近,那就对了!事实上,对数和指数高度相关,对数即指数。以上两个公式是同一个事物的两种表示方式,其核心思想是2×2×2×2=16。

image-20230315180750262

另外,对数和指数在计算增长率时也特别有用。大部分时候,指数和对数可以帮助我们对数量的增长趋势进行建模。

常数e约等于2.718,它在实践中具有很强的实用性。最常见的例子是计算资金的增长情况。假设银行账户有5000美元,以年化利率3%进行增长。我们使用以下公式对存款余额进行建模:
$$
A=Pe^{rt}
$$
其中:

  • A为最终的存款金额;
  • P为初始投资金额(5000美元);
  • e为常数(2.718);
  • r为年化增长率(0.03);
  • t为时间(单位:年)。

我们非常好奇账户余额需要多长时间才能翻倍?用公式表示如下:
$$
10000=5000e^{0.03t}
$$
等价于求解:
$$
2=e^{0.03t}(两边都除以5000)
$$
image-20230315181542765

4.2.5 集合论

集合论(set theory)指面向集合对象的数学运算,它被认为是其他数学原理和定理的根基之一。对我们而言,集合论主要用来处理包含大量元素的群体。

集合是由一组不重复对象构成的群体,仅此而已!集合可以被看作Python中的列表,但不含重复对象。事实上,在Python中甚至有专门的set对象。

1
2
3
4
5
6
s=set()

s=set([1,2,2,3,2,1,2,2,3,2])
#will remove duplicates from a list

s=={1,2,3}

在Python中,花括号{}既可以指集合(set),也可以指字典(dictionary)。字典由键和键值成对组成,比如:

1
2
3
4
5
6
7
8
9
10
dict={"dog":"human's best friend","cat":"destroyer of world"}
dict["dog"] #=="human's best friend"
len(dict["cat"]) #==18
#but if we try to create a pair with the same key as an existing key
dict["dog"]="Arf"

dict
{"dog":"Arf","cat":"destroyer of world"}
#It will override the previous value
#dictionaries cannot have two values for one key.

字典和集合共用同样的符号,是因为它们具有同样的特性:集合不可以用重复项,正如字典不可以有重复键一样。

集合的大小(magnitude)是一个描述集合所含元素数量的值,表示方式为:|A|=A的大小

1
2
s #=={1,2,3}
len(s) #==3 magnitude of s

集合可以为空,用符号Ø表示。空集的大小为零。

我们使用符号∈表示某个元素包含在集合中,如下所示:
$$
2∈{1,2,3}
$$
2包含在由1、2和3组成的集合中。如果一个集合包含在另一个集合中,我们称第1个集合是第2集合的子集。
$$
A={1,5,6},B={1,5,6,7,8}
\
A\subseteq B
$$
A是B的子集,B是A的超集。如果A是B的子集且A不等于B(即B中至少有一个元素不包含在A中),那么A是B的真子集。以下是集合的例子:

  • 偶数集合是整数集合的子集;
  • 每个集合都是自身的子集,但不是真子集;
  • 由所有推文构成的集合是由所有英文推文构成集合的超集。

在数据科学中,我们用集合(或者列表)表示一系列对象,很多时候用来归纳用户的行为。事实上,这种方式非常常见。

假设一家营销公司想预测客户将购买哪个品牌的衣服。我们有客户历史上购买衣服的品牌信息,我们的目标是预测该客户会喜欢的新品牌。假设1个客户曾购买过Target、Banana Republic和Old Navy,在Python中表示如下:

1
2
3
user1={"Target","Banana Republic","Old Navy"}
#note that we use {} notation to create a set
#compare that to using [] to make a list

请注意,我们在以上代码中使用{}符号创建一个集合,而不是用[]符号创建一个列表。

第2个客户购买过的品牌有:

1
user2={"Banana Republic","Gap","Kohl's"}

我们希望知道这两个客户的相似程度。根据目前有限的信息,我们可以将相似性定义为两个客户都购买过的品牌有哪些,这被称为交集(intersection)

两个集合的交集指由同时出现在两个集合中的元素组成的集合,用符号$$\cap$$表示,如下所示:
$$
user1 \cap user2={Banana Republic}
\
|user1 \cap user2|=1
$$
以上两个用户的交集只有1个,看起来好像并不是特别棒。但是,考虑到每个集合仅有3个元素,1/3的相似度似乎也并不坏。

如果我们想知道两个客户总共购买过的品牌有哪些,这称之为并集(union)

两个集合的并集指由两个集合中所有元素组成的集合,用符号$$\cup$$表示,如下所示:
$$
user1 \cup user2={Banana\ Republic,Target,Old\ Navy,Gap,Kohl’s}
\
|user1 \cup user2|=5
$$
当我们计算user1和user2的相似度时,应同时使用交集和并集。user1和user2的交集有1个元素,并集有5个元素。所以,我们可以定义两者的相似度为:
$$
{|user1 \cap user2|\over|user1 \cup user2|}={1\over5}=0.2
$$
事实上,在集合理论中它有一个正式的名字,叫杰卡德相似度(jaccard similarity)

对于集合A和B,两者的杰卡德相似度为:
$$
JS(A,B)={|A \cap B|\over|A \cup B|}
$$
从以上公式不难发现,杰卡德相似度也可以被定义为交集的大小除以并集的大小。

杰卡德相似度为我们提供了一种量化集合相似性的方法。

根据直觉,我们推测杰卡德相似度的值介于0~1,当相似度接近0时表示几乎没有共性,当相似度接近1时表示客户相似性最高。如果我们仔细考虑一下它的定义,这个推测就显得更加合理:
$$
JS(A,B)={两个客户都买过的品牌数量\over两个客户总共买过的品牌数量}
$$
以上内容在Python中表示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
user1={"Target","Banana Republic","Old Navy"}
user2={"Banana Republic","Gap","Kohl's"}

def jaccard(user1,user2):
#用&计算交集,用|计算并集
stores_in_common=len(user1 & user2)
stores_all_together=len(user1 | user2)
return stores_in_common/float(stores_all_together)

#I cast stores_all)together as a float to return a decimal answer
#instead of python's default integer division

jaccard(user1,user2) #==0.2 or 1/5

当我们开始学习概率论处理多维数据时,集合论的应用将越来越广泛。我们使用集合表示现实世界发生的各种事件。

4.3 线性代数

还记得之前的电影推荐引擎吗?如果我们要从10000部电影中选出10部推荐给用户,需要怎么办呢?我们需要将每个用户的简历和10000部电影分别计算点积。线性代数(linear algebra)为我们提供了更高效的计算工具。

线性代数是计算矩阵和向量的一个数学分支。它通过对计算对象进行分解和重构,增强了实用性。下面我们介绍几个线性代数的处理规则。

矩阵乘积

和数值乘积类似,我们也可以对矩阵进行乘积运算。本质上,矩阵乘积是同时进行大量点积运算的过程。以下面这两个矩阵乘积为例:
$$
\begin{pmatrix}
1 & 5\
5 & 8\
7 & 8
\end{pmatrix}·
\begin{pmatrix}
3 & 4\
2 & 5 \
\end{pmatrix}
$$
需要注意的是:

  • 矩阵乘积不支持交换律,矩阵相乘的顺序影响着最终结果,这一点和数值乘积有所区别。
  • 为了对矩阵进行乘积,矩阵的维度必须匹配。第1个矩阵的列数必须和第2个矩阵的行数一致。

image-20230317090905544

矩阵如何相乘

本质上,矩阵乘积是进行一系列点积的过程。有一个简单的方法计算矩阵乘积。回到之前的例子:
$$
\begin{pmatrix}
1 & 5\
5 & 8\
7 & 8
\end{pmatrix}·
\begin{pmatrix}
3 & 4\
2 & 5 \
\end{pmatrix}
$$
我们已经知道矩阵乘积的结果是一个3×2的新矩阵,新矩阵的样子如下:
$$
\begin{pmatrix}
m_{11} & m_{12} \
m_{21} & m_{22} \
m_{31} & m_{32}
\end{pmatrix}
$$

请注意,新矩阵的每个元素都有下标,第1个数字表示行号,第2个数字表示列号。$$m_{32}$$指第3行第2列的元素。

新矩阵的每个元素都是原矩阵对应行和列的点积。比如$m_{xy}$是第1个矩阵x行和第2个矩阵y列的点积,如下所示:
$$
m_{11}=(1,5)·\begin{pmatrix}
3\
2\
\end{pmatrix}=13
\
m_{12}=(1,5)·\begin{pmatrix}
4\
5\
\end{pmatrix}=29
$$
以此类推,我们将得到乘积后的新矩阵:
$$
\begin{pmatrix}
13 & 29\
31 & 60\
37 & 68\
\end{pmatrix}
$$
回到电影推荐的案例。我们已经知道用户对喜剧、言情剧和动作片的偏好是:
$$
U=用户偏好=\begin{pmatrix}
5\
1\
3
\end{pmatrix}
$$
假设我们有10000部电影,每部电影都有喜剧、言情剧和动作片3个特征值。为了给用户推荐电影,我们需要将用户的偏好向量和10000部电影的特征向量进行乘积。我们可以用矩阵乘积表示。

我们用矩阵符号表示以上矩阵,而不是将10000个矩阵都列出来。我们已经有了表示用户偏好的向量U(可以被看作3×1矩阵),以及所有电影组成的3×10000维度的矩阵:
$$
M=3×10000维度矩阵
$$
现在,我们有两个矩阵:一个是3×1,另一个是3×10000。由于这两个矩阵维度不满足相乘条件,所以无法直接相乘。我们需要对矩阵U进行转置(transpose),即行转为列,列转为行。转置后的新矩阵如下:
$$
U^T=U的转置=(5,1,3)
$$
image-20230317092946997

下面在Python中完成以上内容。

1
2
3
4
5
6
7
8
9
10
import numpy as np

#create user preferences
user_pref=np.array([5,1,3])

#create a random movie matrix of 10000 movies
movies=np.random.randint(5,size=(3,10000))+1

#Note that the randint will make random integers from 0-4
#so I added a 1 at the end to increase the scale from 1-5

我们使用numpy模块的array函数创建矩阵,user_pref和movies是我们得到的数据。我们使用numpy模块的shape方法检查矩阵的维度,如下所示:

1
2
print(user_pref.shape)  #(1,3)
print(movies.shape) #(3,10000)

矩阵维度正确。下面我们继续使用numpy模块计算两个矩阵的乘积:

1
2
#np.dot does both dot products and matrix multiplication
np.dot(user_pref,movies)

计算结果是一个由整数组成的数组,每一个值表示对应电影的推荐指数。

image-20230317094347169

下面我们对本例进行扩展,计算推荐超过10000部电影所需的时间。

1
2
3
4
5
6
7
import time

for num_movies in (10000,100000,1000000,10000000,100000000):
movies=np.random.randint(5,size=(3,num_movies))+1
now=time.time()
np.dot(user_pref,movies)
print(time.time()-now,"seconds to run",num_movies,"movies")

image-20230317094507489

我在jupyter notebook上的运行结果如下

1
2
3
4
5
0.0 seconds to run 10000 movies
0.0 seconds to run 100000 movies
0.004986286163330078 seconds to run 1000000 movies
0.05382490158081055 seconds to run 10000000 movies
0.5453550815582275 seconds to run 100000000 movies