郑捷《机器学习算法原理与编程实践》学习笔记(第一章 机器学习基础)
机器学习对象是指含有一组特征的行向量,也称为特征向量。
一般而言,一个对象应视为完整的个体,代表现实中有意义的事物,不能轻易拆分。
对于文本类数据集,需要首先生成词袋列表,再将每个词出现的词频数值化。
例如:
- My dog ate my homework.
- My cat ate the sandwich.
- A dolphin ate the homework.
生成词袋列表: s = [a, ate, dolphin, dog, homework, my, sandwich, the]
,该列表记录了上述文本中出现的每个不重复的词。
各段文字根据生成的词袋列表转化为对象,其维度就是词袋列表的长度。向量化后的文本称为词向量。其中(0)表示词袋中对应的词未出现在文本中;
(1)表示该词出现在文本中1次;如若多次出现,则累加。
import copy
text1 = 'My dog ate my homework'
text2 = 'My cat ate the sandwich'
text3 = 'A dolphin ate the homework'
t1 = text1.split(' ')
t2 = text2.split(' ')
t3 = text3.split(' ')
d = list(set(t1 + t2 + t3))
def f(t):
c = copy.deepcopy(d)
for x in range(len(c)):
if c[x] in t:
c[x] = 1
else:
c[x] = 0
return c
c1 = f(t1)
c2 = f(t2)
c3 = f(t3)
上面的c1、c2、c3
便是我们需要的词向量。
c1
[0, 1, 0, 1, 0, 0, 1, 1, 0, 1]
c2
[0, 1, 1, 1, 1, 0, 0, 0, 1, 0]
c3
[1, 1, 0, 0, 0, 1, 0, 0, 1, 1]
矩阵
import numpy as np
a = np.array([1, 2, 34, 5])
b = copy.deepcopy(a) # 深复制
b[0] = 9
b
array([ 9, 2, 34, 5])
a
array([ 1, 2, 34, 5])
c = a # 浅复制
c[0] = 9
c
array([ 9, 2, 34, 5])
a
array([ 9, 2, 34, 5])
机器学习中的对象是被特征化的客观事物,而表是容纳这些对象的容器。换言之,对象是表中的元素,表是对象的集合。但这个集合有点特殊,即表中的每个对象都有相同的特征和维度,对象对于每个特征都有一定的取值。
- 矩阵是具有相同特征和维度的对象的集合,表现为一张二维数据表;
- 一个对象表示为矩阵中的一行,一个特征表示为矩阵中的一列,每个特征都有数值型取值。
- 特征相同、取值相异的对象集合所构成的矩阵,使得对象之间既相互独立,又相互联系。
- 由特征列的取值范围所有构成的矩阵空间应具有完整性,即能够反映出事物的空间形式或变化。
关于第三点:比如动物的种属和植物的种属是相互独立的,但是如果放到一张表中,我们使用一种特征向量来衡量它们,比如种属、重量、颜色等,这样便给二者建立了联系。
- 分类或聚类可以看作根据对象特征的相似性与差异性,对矩阵空间的一种划分。
- 预测或回归可以看作根据对象在某种序列(时间)上的相关性,表现为特征取值变化的一种趋势。
分类、聚类和回归是机器学习最基本的主题。通过矩阵,可以构建客观事物的多维度数学模型,并通过条件概率分布、梯度、神经网络、协方差等运算方式,多角度地认识和分析事物。矩阵的用途主要有(3)点:
- 解线性方程组,比如二维矩阵可以理解为一个平面直角坐标系内的点集,通过计算点与点之间的距离,完成聚类、分类和预测,类似的运算可以扩展到多维;
- 方程降次,也就是利用矩阵的二次型,通过升维将线性不可分的数据集映射到高维中,转化为线性可分的情形,这是支持向量机的基本原理之一。
- 变换,通过特征值和特征向量完成维度约简,简化类似图片这种高维数据集的运算,主成分分析使用的就是这个原理。
矢量化编程和GPU计算
基于矩阵的算法都是针对向量的,这里也称为矢量。处理基于矩阵的基本运算,就是矢量化编程。
矢量化编程的一个重要特点是可以直接将数学公式转化为相应的程序代码,极大地方便了程序的阅读和调试,使得复杂数学公式的实现变得简单和方便。
mylist = [1, 2, 3, 4, 5]
a = 10
mymatrix = np.mat(mylist)
a * mymatrix
matrix([[10, 20, 30, 40, 50]])
矩阵的初始化
myZero = np.zeros([3, 5]) # 全0矩阵
myZero
array([[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]])
myOnes = np.ones([3, 5]) # 全1矩阵
myOnes
array([[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.],
[ 1., 1., 1., 1., 1.]])
myRand = np.random.rand(3, 4) # 0~1 随机数矩阵
myRand
array([[ 0.59393901, 0.49917793, 0.85212989, 0.58955052],
[ 0.01495929, 0.77227977, 0.36688307, 0.02204415],
[ 0.92013148, 0.38079991, 0.36128693, 0.22103762]])
myEye = np.eye(3) # 单位阵
myEye
array([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.]])
矩阵的元素运算
在元素级别的运算。
- ((A + B)_{i, j} = (A)_{i,j} + (B)_{i,j})
myOnes = np.ones([3, 3])
myEye = np.eye(3)
myEye + myOnes
array([[ 2., 1., 1.],
[ 1., 2., 1.],
[ 1., 1., 2.]])
myEye - myOnes
array([[ 0., -1., -1.],
[-1., 0., -1.],
[-1., -1., 0.]])
- 矩阵数乘
((cA)_{i,j} = c cdot A_{i, j})
mymatrix = np.mat([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
a = 10
a * mymatrix
matrix([[10, 20, 30],
[40, 50, 60],
[70, 80, 90]])
- 矩阵所有元素求和
(sum(A) = sum_{i=1}^{m} sum_{j=1}^{n} {A_{i,j}})
mymatrix = np.mat([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
np.sum(mymatrix)
45
- 矩阵各元素的积
((A imes B)_{i,j} = A_{i,j} imes B_{i,j})
A =np.mat([[1, 2, 3], [4, 5, 6], [6, 7, 9]])
B = 1.5 * np.ones([3, 3])
np.multiply(A, B)
matrix([[ 1.5, 3. , 4.5],
[ 6. , 7.5, 9. ],
[ 9. , 10.5, 13.5]])
- 矩阵各元素的(n)次幂
(A_{i,j}^2 = A_{i,j} imes A_{i,j})
A = np.mat([[1, 2, 3], [4, 5, 6], [6, 7, 9]])
np.power(A, 2)
matrix([[ 1, 4, 9],
[16, 25, 36],
[36, 49, 81]], dtype=int32)
矩阵的乘法
([A, B]_{i,j} = sum_{r=1}^{n}{A_{i,r} B_{r, j}})
A = np.mat([[1, 2, 3], [4, 5, 6], [6, 7, 9]])
B = np.mat([[1], [2], [3]])
A * B
matrix([[14],
[32],
[47]])
矩阵的转置
((A)^{T}_{i,j} = A_{j,i})
A = np.mat([[1, 2, 3], [4, 5, 6], [6, 7, 9]])
A.T
matrix([[1, 4, 6],
[2, 5, 7],
[3, 6, 9]])
A.transpose()
matrix([[1, 4, 6],
[2, 5, 7],
[3, 6, 9]])
行列数、切片、复制、比较
A = np.mat([[1, 2, 3], [4, 5, 6], [6, 7, 9]])
[m, n] = np.shape(A) # 矩阵的行列数
m, n
(3, 3)
a = A[0] # 按行切片
a
matrix([[1, 2, 3]])
b = A.T[0] # 按列切片
b
matrix([[1, 4, 6]])
b1 = A[:, 0] # 按列切片
b1
matrix([[1],
[4],
[6]])
C = A.copy() # 复制
C
matrix([[1, 2, 3],
[4, 5, 6],
[6, 7, 9]])
A < A.T # 比较
matrix([[False, True, True],
[False, False, True],
[False, False, False]], dtype=bool)
Linalg
线性代数库
- 行列式
A = np.mat([[1, 2, 4, 5, 7],
[9, 12, 11, 8, 2],
[6, 4, 3, 2, 1],
[9, 1, 3, 4, 5],
[0, 2, 3, 4, 1]])
np.linalg.det(A)
-811.99999999999932
- 求逆
np.linalg.inv(A)
matrix([[-0.07142857, -0.01231527, 0.05295567, 0.09605911, -0.00862069],
[ 0.21428571, -0.37684729, 1.22044335, -0.46059113, 0.3362069 ],
[-0.21428571, 0.82512315, -2.04802956, 0.56403941, -0.92241379],
[ 0. , -0.4137931 , 0.87931034, -0.17241379, 0.81034483],
[ 0.21428571, -0.06650246, 0.18596059, -0.08128079, -0.14655172]])
- 矩阵的对称
AT = A.T # 矩阵的对称
A * AT
matrix([[ 95, 131, 43, 78, 43],
[131, 414, 153, 168, 91],
[ 43, 153, 66, 80, 26],
[ 78, 168, 80, 132, 32],
[ 43, 91, 26, 32, 30]])
- 矩阵的秩
np.linalg.matrix_rank(A)
5
- 线性方程组
b = [1, 0, 1, 0, 1]
s = np.linalg.solve(A, b)
s
array([-0.0270936 , 1.77093596, -3.18472906, 1.68965517, 0.25369458])
机器学习的数学基础
数学研究的是客观世界的空间形式与数量性质,即事物在时空中普遍存在与运动的规律。
现代数学三大基石:
1. 概率论:说明事物可能会是什么样;
2. 数值分析:揭示为什么这样,以及如何变成这样;
3. 线性代数:事物从来都不只有一个样子,需要多角度的观察。
import scipy.spatial.distance as dist # 导入 SciPy 距离公式
1. 相似性度量
两个向量之间的距离(作为(n)坐标系的点)称为样本之间的相似性度量(Similarity Measurement)。它反映为某类事物在距离上接近或远离的程度。
A = np.mat([8, 1, 6])
# 手工计算
modA = np.sqrt(np.sum(np.power(A, 2)))
modA
10.04987562112089
# 库函数
normA = np.linalg.norm(A)
normA
10.04987562112089
闵科夫斯基距离(Minkowski Distance)
(p)范数: (||x - y||_p)
- (p=1):曼哈顿距离(Manhattan Distance)
- (p=2):欧式距离(Euclidean Distance)
- (p= infty):切比雪夫距离(Chebyshev Distance)
v1 = np.mat([1, 2, 3])
v2 = np.mat([4, 5, 6])
np.sqrt((v1 - v2) * (v1 - v2).T) # 欧式距离
matrix([[ 5.19615242]])
np.sum(np.abs(v1 - v2)) # 曼哈顿距离
9
np.abs(v1 - v2).max() # # 切比雪夫距离
3
夹角余弦(Cosine)
夹角余弦可用来衡量两个向量方向的差异。
cosV12 = np.dot(v1, v2.T)/(np.linalg.norm(v1) * np.linalg.norm(v2))
cosV12
matrix([[ 0.97463185]])
1 - dist.cosine(v1, v2.T)
0.9746318461970761
汉明距离(Hamming Distance)
汉明距离:两个等长字符串(s1)与(s2)之间的汉明距离定义为将其中一个变为另一个所需要的最小替换次数。
等价于(||x||_{0})
matV = np.mat([[1, 1, 0, 1, 0, 1, 0, 0, 1], [0, 1, 1, 0, 0, 0, 1, 1, 1]])
smstr = np.nonzero(matV[0] - matV[1])
smstr
(array([0, 0, 0, 0, 0, 0], dtype=int64),
array([0, 2, 3, 5, 6, 7], dtype=int64))
np.shape(smstr[0])[0] # 汉明距离
6
x = np.array([[1,0,0], [0,2,0], [1,1,0]])
x
array([[1, 0, 0],
[0, 2, 0],
[1, 1, 0]])
np.nonzero(x)
(array([0, 1, 2, 2], dtype=int64), array([0, 1, 0, 1], dtype=int64))
x[np.nonzero(x)]
array([1, 2, 1, 1])
np.transpose(np.nonzero(x))
array([[0, 0],
[1, 1],
[2, 0],
[2, 1]], dtype=int64)
杰卡德相似系数(Jaccard Similarty Coefficient)
衡量两个集合的相似度:$$J(A,B) = frac{mid A igcap Bmid}{mid A igcup B mid}$$
杰卡德距离
衡量两个集合的区分度:(J_{delta}(A,B) = 1 - J(A,B))
设样本 (A) 和 (B) 是两个 (n) 维向量,取值为 (0) 或 (1) 。我们将样本看成一个集合,(1) 表示集合包含该元素,(0) 表示集合不包含该元素。
- p:样本 (A) 和 (B) 都是 (1) 的维度的个数。
- q:样本 (A) 是 (1)、样本(B)是 (0) 的维度的个数。
- r:样本 (A) 是 (0)、样本(B)是 (1) 的维度的个数。
- s:样本 (A) 和(B)都是 (0) 维度的个数。
这样样本 (A) 和 (B) 的杰卡德相似系数可表示为:
matV = np.mat([[1, 1, 0, 1, 0, 1, 0, 0, 1], [0, 1, 1, 0, 0, 0, 1, 1, 1]])
dist.pdist(matV, 'jaccard') # 杰卡德
array([ 0.75])
理解随机性
我们接下来从整体上观察矩阵(集合)中对象分布与矩阵整体的关系。
想要真正理解概率,需要弄清楚两个问题:
- 确定性与随机性
- 统计规律
洛伦兹动力学方程描绘出的运动轨迹像一个展开了双翅的蝴蝶,所以又称为蝴蝶效应。确定性与随机性被统一了:一方面,运动的轨迹必然落在“蝴蝶”上,决不会远离它们而去,这是确定性的表现,表明系统未来的所有运动都被限制在一个明确的范围之内;另一方面,运动轨迹变化缠绕的规则却是随机性的,任何时候你都无法准确判定下一次运动的轨迹将落在“蝴蝶”的哪一侧翅膀上哪一点上。也就是说,这个系统运动大的范围是确定的、可测的,但是运动的细节是随机的、不可预测的。
从统计学角度去看,蝴蝶效应说明了:
- 样本总体(特征向量)的取值范围一般是确定的,所有样本对象(包括已经存在的和未出现的)的取值都位于此空间范围中;
- 无论收集再多的样本对象,也不能使这种随机性降低或消失。
因此,随机性是事物的一种根本的、内在的、无法根除的性质,也是一切事物(概率)的本质属性。
世界上绝大多数的规律都来源于统计学。
概率论
对事物运动这种不确定(随机性)的度量就是概率论。
衡量事物运动的随机性,必须从整体而不是局部来认知事物,因为从每个局部,事物可能看起来都是不同的(或相同的)。
- 样本(样本点):原指随机试验的一个结果,可以理解为矩阵中的一个对象(特征向量)
- 样本空间:原指随机试验所有结果的集合,可以理解为矩阵的所有对象,引申为对象特征的取值范围
- 随机事件:是指样本空间的一个子集,可以理解为某个分类,它实际指向一种概率分布
- 随机变量:可以理解为指向某个事件的一个变量
- 随机变量的概率分布:给定随机变量的取值范围,导致某种随机事件出现的可能性。从机器学习角度来看,就是符合随机变量取值范围的某个对象属于某个类别或服从某种趋势的可能性。
概率论的妙处在于,由于随机性的存在,有时候无法直接讨论随机变量如何从样本空间映射到事件空间,因为随机变量的取值范围允许它映射到事件空间的任一事件。此时只有通过研究随机变量映射为每个事件的可能性,也就是说某个对象属于某个类别的可能性,才能做出合理的判断。
多元统计基础
矩阵是具有相同特征和维度的对象的集合,其中每个对象,也称为行向量,都具有一个以上的特征。如果每个特征都用一个随机变量来表示,那么从概率论的角度,一个对象就可以表示为 (n) 个随机变量的整体,其中 (X = (X_{1}, X_{2}, cdots, X_{n})) 为 (n) 维随机变量或者随机向量。每个对象就是随机向量的一组取值,矩阵中的所有对象构成了随机向量的联合和边缘概率分布。
示例:水果联合概率分布
( ext{红色}) | ( ext{黄色}) | ( ext{绿色}) | $ ext{颜色} $ | |
---|---|---|---|---|
苹果(1) | 0.4 | 0.1 | 0 | 0.5 |
李子(2) | 0 | 0.45 | 0.05 | 0.5 |
水果 | 0.4 | 0.55 | 0.05 | 1.0 |
随机向量的联合和边缘概率分布描述了对象特征之间的概率关系。在机器学习中,对象及对象构成的矩阵都是多元数据,因此,所有与概率相关的算法都以对象的联合和边缘概率分布为基础。
特征间的相关性
- 对矩阵行向量的划分,即分类过程;
- 而对矩阵特征列的研究主要应用于预测活动,抽象点说,就是在一个时间序列上观察两列数据之间的相关性。
- 期望:衡量样本某个特征列取值范围的平均值。
- 方差:衡量样本某个特征列取值范围的离散程度。
- 协方差矩阵和相关系数:衡量样本特征列之间线性相关性。
相关系数(Correlation Coefficient)与相关距离(Correlation Distance)
(mathbf{X},mathbf{Y}) 是列向量.
协方差矩阵:
相关系数矩阵:
相关系数 (相关系数矩阵中的元素) 是衡量两个特征列之间相关程度的,其取值范围是([-1, 1])。相关系数的绝对值越大,表明两个特征列之间相关程度越高。当它们线性相关时,相关系数取值为 (1)(正线性相关)或 (-1)(负线性相关)。
相关距离:
相关系数矩阵:(以下面的例子来说)若把第一个特征列作为参照数据(自身的相关程度为1),则与第二个与第一个的相关程度为 (97\%)。
featuremat = np.mat([[88.5, 96.8, 104.1, 111.3, 117.7, 124.0,
130.0, 135.4, 140.2, 145.3, 151.9, 159.5,
165.9, 169.8, 171.6, 172.3, 172.7],
[12.54, 14.65, 16.64, 18.98, 21.26, 24.06,
27.33, 30.46, 33.09, 37.9, 42.3, 48.4,
54.1, 57.2, 59.06, 60.4, 67.3]])
# 计算均值
mv1 = np.mean(featuremat[0]) # 第一列的均值
mv2 = np.mean(featuremat[1]) # 第二列的均值
# 计算标准差
dv1 = np.std(featuremat[0])
dv2 = np.std(featuremat[1])
corref = np.mean(np.multiply(featuremat[0] - mv1, featuremat[1] - mv2)) / (dv1 * dv2)
corref
0.97484719582346291
# 相关系数矩阵
np.corrcoef(featuremat)
array([[ 1. , 0.9748472],
[ 0.9748472, 1. ]])
马氏距离(Mahalanobis Distance)
定义:有(m)个样本向量 (X_1 dots X_m),协方差矩阵记为 (S),均值记为向量 (mu),则其中样本向量 (X) 到 (mu) 的马氏距离为:
其中 (X_i) 与 (X_j) 之间的马氏距离为:
优点:与量纲无关,排除变量之间的相关性干扰。
featuremat = np.mat([[88.5, 96.8, 104.1, 111.3, 117.7, 124.0,
130.0, 135.4, 140.2, 145.3, 151.9, 159.5,
165.9, 169.8, 171.6, 172.3, 172.7],
[12.54, 14.65, 16.64, 18.98, 21.26, 24.06,
27.33, 30.46, 33.09, 37.9, 42.3, 48.4,
54.1, 57.2, 59.06, 60.4, 67.3]])
convinv = np.linalg.inv(np.cov(featuremat))
tp = featuremat.T[0] - featuremat.T[1]
distma = np.sqrt(np.dot(np.dot(tp, convinv), tp.T))
distma
matrix([[ 0.81929564]])
矩阵——空间变换
由特征列的取值范围所构成的矩阵空间应该具有完整性,即能够反映事物的空间形式或变化规律。
在机器学习中,训练集构成的矩阵为了能够反映事物的规律常常需要包含大量的样本,或因为建模的需要。矩阵中的向量具有很大的维度,就是我们所说的完整性。虽然理论上,矩阵空间可以包含无限多个向量,但是我们无法做到也没有必要那样做。因此,我们希望做到两点:
- 向量之间,以及向量与矩阵之间的运算到底有什么意义;为什么向量和矩阵运算能够揭示出事物的空间形式。
- 矩阵能否通过运算抽取出事物最本质的特征,通过这些特征就能反映出对象集合表达的形式和规律。
关于向量 & 矩阵
向量 (overrightarrow{x}) 是有方向和大小的量。其大小为 (overrightarrow{x}) 到原点的距离,方向为 (overrightarrow{x}) 与各个维度的坐标轴的夹角。即向量表示一个经过原点的有向线段。
基底向量对应于坐标系的坐标轴。
- 向量的加法满足平行四边形法则,数乘是向量的伸缩。
- 向量与矩阵的乘法 (xA):就是一个向量从一个线性空间(坐标系),通过选取一个新的基底,变换到这个新的基底构成的另一个线性空间的过程。
- 矩阵乘法 (AB):(A) 表示向量组对应的坐标,(B) 表示坐标系对应的矩阵。即向量组中的向量发生了旋转和伸缩变换。
特征值与特征向量
若向量在线性变换下只发生伸缩变换,而没有旋转的效果,则称此向量为特征向量(也叫线性不变量)。
A = np.mat([[8, 1, 6], [3, 5, 7], [4, 9, 2]])
evals, evecs = np.linalg.eig(A)
print('特征值:',evals, '
特征向量:', evecs)
特征值: [ 15. 4.89897949 -4.89897949]
特征向量: [[-0.57735027 -0.81305253 -0.34164801]
[-0.57735027 0.47140452 -0.47140452]
[-0.57735027 0.34164801 0.81305253]]
还原出原矩阵
(A) 是原矩阵,(Q) 是特征向量,(Sigma) 特征值构成的对角阵。
sigma = evals * np.eye(m)
sigma
array([[ 15. , 0. , -0. ],
[ 0. , 4.89897949, -0. ],
[ 0. , 0. , -4.89897949]])
sigma = np.diag(evals)
sigma
array([[ 15. , 0. , 0. ],
[ 0. , 4.89897949, 0. ],
[ 0. , 0. , -4.89897949]])
evecs * sigma * np.linalg.inv(evecs) # 原矩阵
matrix([[ 8., 1., 6.],
[ 3., 5., 7.],
[ 4., 9., 2.]])
数据归一化
归一化是一种简化计算的方式,将有量纲的表达式,经过变换,转换为无量纲的表达式,称为标量。
归一化是机器学习的一项基础工作。有两种形式:
- 把数变为 ((0, 1)) 之间的小数;
- 把有量纲表达式变为无量纲表达式。
在统计学中,归一化的具体作用是归纳统一样本的统计分布性。归一化在 ((0, 1)) 之间是统计的概率分布,在 ((-1, +1)) 之间是统计的坐标分布。
从集合的角度来看,可以做维度的归一,即抽象化归一,把不重要的、不具可比性的集合中的元素的属性去掉,保留人们关心的那些属性,这样,本来不具可比性的事物或对象就归为一类,然后就可以比较了。
数据标准化(Data Normalization Method)
数据标准化是归一化的一种形式,本质上也是消除量纲的差异,比较认识。
主要方法:按比例缩放,使之落入一个较小的特定区间。
vectormat = np.mat([[1, 2, 3], [4, 5, 6]])
v12 = vectormat[0] - vectormat[1]
np.sqrt(v12 * v12.T)
matrix([[ 5.19615242]])
# norm
varmat = np.std(vectormat.T, axis = 0)
normvmat = (vectormat - np.mean(vectormat)) / varmat.T
normv12 = normvmat[0] - normvmat[1]
np.sqrt(normv12 * normv12.T)
matrix([[ 6.36396103]])