机器学习是人工智能领域的一个核心分支,机器学习自身又可根据训练数据有无标签分为监督学习(supervised Learning)和无监督学习(unsupervised Learning),当然还有强化学习(Reinforcement Learning),可能还有很多。
机器学习,也就是让机器去学习,核心在学习。那么如何让机器向人一样的学习呢?例如人可以很轻易的分辨一个动物是猫还是狗。这来源于我们从小开始的经验积累。这也叫经验学习。机器也如此,要想使计算机分辨出一个动物是猫还是狗,就必须喂给计算机数据,对应不同的应用,数据集少至数百,多则数万甚至更多。
就拿猫狗分辨来解释一下监督和无监督学习,它们的核心区别在于数据集的不同
监督学习:我们要喂给计算机的训练数据集包括不同的猫狗照片各500张,并给每张照片标注分类。机器通过这1000张已知答案的数据来学习。学习方式可以通过多种算法,例如KNN。
无监督学习:我们喂给计算机的训练数据集包括不同的猫狗照片各500张,每张照片的分类我们也不知道。机器通过这1000张未知答案的数据来学习。机器可以通过照片数据的内在关系将其区分开。
强化学习(Reinforcement Learning):强化学习是智能体(Agent)以“试错”的方式进行学习,通过与环境进行交互获得的奖赏指导行为,目标是使智能体获得最大的奖赏。如果Agent的某个行为策略导致环境正的奖赏(强化信号),那么Agent以后产生这个行为策略的趋势便会加强。Agent的目标是在每个离散状态发现最优策略以使期望的折扣奖赏和最大。
一、分类算法—KNN/K-近邻
KNN模型(K-Nearest Neighbor,K近邻算法),一个典型的非参数模型,也就是说计算机通过KNN学到的知识并不是以数值权重的方式存储下来的。(非参数可以简单理解为每个特征占预测结果的权重相等)
概述:因为属于监督学习的范畴,所以我们需要有数据集,称之为训练样本集,即我们知道训练样本集中的数据的分类。这一组数据也有特征,就是不同属性。当要预测一组新数据的分类时,用新数据的每个特征与样本集中每组数据的特征对应比较,计算距离。最后提取距离最近的(最相似)的K个数据,通常K是不大于20的整数,最后选取K个最相似数据中出现次数最多的分类,作为新数据的分类。
物以类聚,人以群分
—《易经》
二、简单分类
先给出算法的伪代码
(1)计算已知类别数据集中的点与当前点之间的距离
(2)按照距离递增次序排序
(3)选取与当前距离最近的K个点
(4)确定前K个点所在类别的出现频率
(5)返回前K个点出现频率最高的类别作为当前点的预测分类
下面通过一个读者推荐的案例来解释KNN算法
读者ID | 收藏CPU书籍 | 收藏AI书籍 | 收藏OS书籍 | 收藏Java书籍 | 收藏Python书籍 | 收藏Js书籍 | 收藏阅前端书籍 | 收藏C书籍 | 读者分类 |
---|---|---|---|---|---|---|---|---|---|
1 | 1 | 5 | 1 | 0 | 2 | 0 | 1 | 1 | AI |
2 | 1 | 6 | 1 | 0 | 1 | 0 | 1 | 0 | AI |
3 | 1 | 7 | 1 | 0 | 1 | 0 | 1 | 1 | AI |
4 | 6 | 2 | 1 | 0 | 2 | 1 | 1 | 1 | 硬件 |
5 | 1 | 9 | 1 | 0 | 1 | 1 | 2 | 0 | AI |
6 | 1 | 2 | 6 | 3 | 1 | 1 | 1 | 1 | 软件 |
当一位爱好书籍方向未知的读者加入后,根据他一段时间的借书情况,预测他的分类,数据如下
读者ID | 收藏CPU书籍 | 收藏AI书籍 | 收藏OS书籍 | 收藏Java书籍 | 收藏Python书籍 | 收藏Js书籍 | 收藏阅前端书籍 | 收藏C书籍 | 读者分类 |
---|---|---|---|---|---|---|---|---|---|
23 | 2 | 6 | 0 | 1 | 3 | 1 | 0 | 0 |
下面计算ID为23的读者的各特征与已知分类读者对应特征的距离
读者ID | 与未知读者的距离 |
---|---|
1 | 2.82842712 |
2 | 3 |
3 | 3.31662479 |
4 | 6.08276253 |
5 | 4.47213595 |
6 | 7.93725393 |
计算距离采用欧式距离公式
∑
i
=
1
n
(
x
i
−
x
0
)
2
2
\sqrt[2]{\sum_{i=1}^n{(x_i-x_0)^2}}
2i=1∑n(xi−x0)2
下面导入Numpy函数库,因为机器学习涉及到很多线性代数的知识,所以这个库我门以后会常用。
第一步:解析数据
这里我直接写好了上述的数据,实际过程中可以解析文本文档、excel、csv、网络格式的数据。
def createDataSet():
group = array(
[[1, 5, 1, 0, 2, 0, 1, 1], [1, 6, 1, 0, 1, 0, 1, 0], [1, 7, 1, 0, 1, 0, 1, 1], [6, 2, 1, 0, 2, 1, 1, 1],
[1, 9, 1, 0, 1, 1, 2, 0], [1, 2, 6, 3, 1, 1, 1, 1]])
labels = ['AI', 'AI', 'AI', '硬件', 'AI', '软件']
return group, labels
接着是KNN的重点,编写分类器classify
# inx是用于分类的测试向量,dataSet是训练样本集,labels是标签
# 且labels的元素数目与数组dataSet的行数相同,K是最相邻的K个点
def classify(inX, dataSet, labels, K):
# shape[0]表示最外围的数组的维数,这里也就是行数;
# shape[1]表示次外围的数组的维数,这里也就是列数,数字不断增大,维数由外到内。
dataSetSize = dataSet.shape[0]
# tile(A,reps) tile将inX沿行复制一倍,相当于没有变化;再沿列复制dataSetSize倍
# 如果有三个参数,第一个参数是将后面的结果复制几次
# n维矩阵减一位矩阵,相当于每一维都减一维矩阵
diffMat = tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat ** 2
# axis=0返回矩阵的所有元素相加的结果,axis=1将行向量相加返回新矩阵
sqDistances = sqDiffMat.sum(axis=1)
# 将距离开平方
distances = sqDistances ** 0.5
# 对distance升序排序,返回一个索引序列,每个数字表示数据在distances中的索引
sortedDistIndicies = distances.argsort()
# 定义一个空字典
classCount = {}
# print(sortedDistIndicies)
for i in range(K):
voteIlabel = labels[sortedDistIndicies[i]]
# voteIlabel这里表示的A或B,是标签
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# get(voteilabel,0):原型为get(key,默认值),作用是获取key对应的值,如果不存在key,则新增key,值为默认值
# 这里很巧妙的运用字典,将标签当做key,前K个最近的点,每一个点对应的标签出现一次,字典中标签相应的value值就加1.
# print(classCount)
sortedClassCount = sorted(classCount.items(),
key=operator.itemgetter(1), reverse=True)
# operator.itemgetter()获取对象的哪些维的数据
# print(sortedClassCount)
return sortedClassCount[0][0]
未排序的classCount为{‘AI’: 3},表明前K个距离最近的数据类型全为AI,按value分类自然也为AI
if __name__ == '__main__':
group, labels = KNN.createDataSet()
print(KNN.classify([2, 6, 0, 1, 3, 1, 0, 0], group, labels, 3))
三、改进分类
1.归一化处理
实际应用中还会有很多其他的问题,例如数据归一化处理
例如我们增加一个特征,每年的平均阅读时间,修改后的数据表如下:
读者ID | 收藏CPU书籍 | 收藏AI书籍 | 收藏OS书籍 | 收藏Java书籍 | 收藏Python书籍 | 收藏Js书籍 | 收藏阅前端书籍 | 收藏C书籍 | 每年的平均阅读小时 | 读者分类 |
---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 5 | 1 | 0 | 2 | 0 | 1 | 1 | 120 | AI |
2 | 1 | 6 | 1 | 0 | 1 | 0 | 1 | 0 | 300 | AI |
3 | 1 | 7 | 1 | 0 | 1 | 0 | 1 | 1 | 40 | AI |
4 | 6 | 2 | 1 | 0 | 2 | 1 | 1 | 1 | 60 | 硬件 |
5 | 1 | 9 | 1 | 0 | 1 | 1 | 2 | 0 | 70 | AI |
6 | 1 | 2 | 6 | 3 | 1 | 1 | 1 | 1 | 55 | 软件 |
未知读者的数据如下:
读者ID | 收藏CPU书籍 | 收藏AI书籍 | 收藏OS书籍 | 收藏Java书籍 | 收藏Python书籍 | 收藏Js书籍 | 收藏阅前端书籍 | 收藏C书籍 | 每年的平均阅读小时 | 读者分类 |
---|---|---|---|---|---|---|---|---|---|---|
23 | 2 | 6 | 0 | 1 | 3 | 1 | 0 | 0 | 60 |
新的距离如下:
读者ID | 与未知读者的距离 |
---|---|
1 | 60.06662967 |
2 | 240.01874927 |
3 | 20.27313493 |
4 | 6.08276253 |
5 | 10.95445115 |
6 | 9.38083152 |
得出结果则是
[ 60.06662967 240.01874927 20.27313493 6.08276253 10.95445115
9.38083152]
{'硬件': 1, '软件': 1, 'AI': 1}
硬件
这就是由于特征的数值过大,占的权重过大,会严重影响计算结果。所以我们要对特征数值较大进行归一化处理,将取指范围处理为0-1或-1到1之间。公式如下
x
n
e
w
=
(
x
o
l
d
−
m
i
n
x
)
/
(
m
a
x
x
−
m
i
n
x
)
x_{new} = (x_{old}-min_x)/(max_x-min_x)
xnew=(xold−minx)/(maxx−minx)
这只是一种最简单的归一化处理公式,欧式距离、标准化欧式距离、马氏距离、余弦距离
# 归一化数值,防止因为某些数值本身值过大的原因影响
def autoNorm(dataSet):
# min():无参-数组中所有值的最小值
# min(0)—> axis=0 每列的最小值 1—> axis=1 每行的最小值
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
# zeros()返回给定形状和类型的新数组,用0填充。
# shape返回元组形式的行数和列数—>(行数,列数)
normDataSet = zeros(shape(dataSet))
m = dataSet.shape[0]
# tile是复制矩阵,将minVals在行上复制m次,在列上复制1次;因为矩阵减法必须矩阵大小一样,除法也相同
normDataSet = dataSet - tile(minVals, (m, 1))
# 在某些数值处理软件包,/可能意味着矩阵除法,但在NumPy库中,矩阵除法需要使用函数linalg.solve(matA,matB)
normDataSet = normDataSet / tile(ranges, (m, 1))
return normDataSet, ranges, minVals
if __name__ == '__main__':
group, labels = KNN.createDataSet()
group, ranges, minVals = KNN.autoNorm(group)
print(group)
print(KNN.classify0([2, 6, 0, 1, 3, 1, 0, 0, 60], group, labels, 3))
再次执行发现结果又恢复正常
2.评估算法的正确率
通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%去测试分类器,训练即是找出使得正确率最大的K值。
def test():
hoRatio = 0.10
DataMat, labels = KNN.createDataSet()
normMat, ranges, minVals = KNN.autoNorm(DataMat)
dataNumber = normMat.shape[0]
numTestVecs = int(dataNumber * hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = KNN.classify0(normMat[i, :], normMat[numTestVecs:dataNumber, :],
labels[numTestVecs:dataNumber], K=3)
print("No.%d, the classifier came back with:%s, the real answer is: %s" % (
i + 1, classifierResult, labels[i]))
if classifierResult != labels[i]:
errorCount += 1.0
print("the total number of errors is: %d" % errorCount)
print("the total error rate is:%.2f%%" % (errorCount / float(numTestVecs) * 100))
当然,这是我随意选择的一个案例,可能在应用和数据的合理上有所不足,但窥一斑而知全豹,我们可以通过上述的几个关键点来应用KNN去解决一些实际问题。第二节将展示完整的手写识别系统的案例
四、总结
缺点
- 保存模型需要保存全部的样本集
- 训练过程很快,预测速度很慢
- 无法给出任何数据的基础结构信息,无法知晓平均实例样本和典型实例样本具有什么特征
优点
- 精度高,对异常值不敏感、无数据输入假定
- 原理简单
很多现实中的问题都可以转化成分类/回归问题,例如: