一、简介
参考:https://blog.csdn.net/c406495762/article/details/75172850
环境:win 10, notebook,python 3.6
原blog较长,主要实现其算法,尽可能的简洁些,并对一些细节给出实例,
如果熟练,细节处的函数尽可能的可以忽略,写出来主要是未来加强印象。
三、四、五、三个实例,前两个同一种方法,分类函数是同一个,只是数据不一样,最后一个使用SKLerrn实现KNN,十分方便。
二、算法简介
1、原理:
输入没有标签的新数据后,将新的数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
2、距离度量
欧式距离
3、步骤:
计算已知类别数据集中的点与当前点之间的距离;
按照距离递增次序排序;
选取与当前点距离最小的k个点;
确定前k个点所在类别的出现频率;
返回前k个点所出现频率最高的类别作为当前点的预测分类。
k-近邻算法的一般流程:
收集数据:可以使用爬虫进行数据的收集,也可以使用第三方提供的免费或收费的数据。一般来讲,数据放在txt文本文件中,按照一定的格式进行存储,便于解析及处理。
准备数据:使用Python解析、预处理数据。
分析数据:可以使用很多方法对数据进行分析,例如使用Matplotlib将数据可视化。
测试算法:计算错误率。
使用算法:错误率在可接受范围内,就可以运行k-近邻算法进行分类。
优缺点:
优点
简单好用,容易理解,精度高,理论成熟,既可以用来做分类也可以用来做回归;
可用于数值型数据和离散型数据;
训练时间复杂度为O(n);无数据输入假定;
对异常值不敏感。
缺点:
计算复杂性高;空间复杂性高;
样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少);
一般数值很大的时候不用这个,计算量太大。但是单个样本又不能太少,否则容易发生误分。
最大的缺点是无法给出数据的内在含义。
三、KNN简单实例
训练集是一个矩阵,测试集也是一个矩阵,要预测测试集中每一个数据的类别
算法步骤:
1、将测试集中的每一个数据扩展成和训练集一样大小的矩阵,用于该数据与训练集的每一个数据进行距离计算
2、扩展矩阵与训练集对应元素相减,
3、相减后的每一个元素进行平方,
4、每一行的元素相加
5、开根号,
6、找出k个最小的的值,这k个中,类别最多的那个即是预测类型
如图:
代码如下:
def classify(inX,dataSet,labels,k):
# 返回行数
dataSetSize = dataSet.shape[0]
# 扩展维度,相减
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
# 平方
sqDiffMat = diffMat**2
# 求和
sqDistances = sqDiffMat.sum(axis=1)
# 开根号
distances = sqDistances**0.5
#返回distances中元素从小到大排序后的索引值
sortedDistIndices = distances.argsort()
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndices[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
# 对字典按值排序,返回的是一个列表,列表元素是元组
sortedClassCount = sorted(classCount.items(),key=lambda item:item[1],reverse=True)
return sortedClassCount[0][0]
完整代码:
import numpy as np
group = np.array([[1,101],[5,89],[108,5],[115,8]])
labels = ['爱情片','爱情片','动作片','动作片']
def classify(inX,dataSet,labels,k):
# 返回行数
dataSetSize = dataSet.shape[0]
# 扩展维度,相减
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances**0.5
#返回distances中元素从小到大排序后的索引值
sortedDistIndices = distances.argsort()
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndices[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
# 对字典按值排序,返回的是一个列表,列表元素是元组
sortedClassCount = sorted(classCount.items(),key=lambda item:item[1],reverse=True)
return sortedClassCount[0][0]
test = [101,20]
test_class = classify(test, group, labels, 3)
print(test_class)
预测结果:
动作片
注:源代码使用了operator模块对字典进行排序
python补充:
四个函数,tile(), np.sum(), np.argsort(), sorted(),
(1)np.tile()
import numpy as np
a = [2,3]
b = np.tile(a,(4,3))
print(b)
[[2 3 2 3 2 3]
[2 3 2 3 2 3]
[2 3 2 3 2 3]
[2 3 2 3 2 3]]
(2)np.argsort():
返回从小到大排序后的索引值
import numpy as np
a = np.array([1,2,3,4])
b = np.array([4,3,2,1])
print(a.argsort())
print(b.argsort())
[0 1 2 3]
[3 2 1 0]
(3)np.sum():
0:返回列相加
1:返回行相加
import numpy as np
a = np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]])
print(a)
print('---------axis=0-----------')
b = a.sum(axis=0)
print(b)
print('---------axis=1-----------')
c = a.sum(axis=1)
print(c)
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
---------axis=0-----------
[22 26 30]
---------axis=1-----------
[ 6 15 24 33]
(4)sorted():
第一个参数:可迭代对象
第二个参数,排序规则
第三个参数,是否降序,True为降序,False为升序,
返回值是一个列表,
d = {'mom':23,'linme':34,'ioio':89,'falali':232}
a = sorted(d.items(),key=lambda item:item[1],reverse=True)
print(a)
c = sorted(d.items(),key=lambda item:item[1],reverse=False)
print(c)
[('falali', 232), ('ioio', 89), ('linme', 34), ('mom', 23)]
[('mom', 23), ('linme', 34), ('ioio', 89), ('falali', 232)]
(5)数据处理过程:
import numpy as np
group = np.array([[1,2],[6,4],[80,81],[78,83]])
print('----------group------------')
print('group: ')
print(group)
test = [3,4]
mat = np.tile(test,(4,1))
print('----------mat------------')
print('mat: ')
print(mat)
mat_g = mat - group
print('----------mat_g------------')
print('mat_g: ')
print(mat_g)
mat_2 = mat_g**2
print('----------mat_2------------')
print('mat_2: ')
print(mat_2)
mat_sum = mat_2.sum(axis=1)
print('----------mat_sum------------')
print('mat_sum: ')
print(mat_sum)
----------group------------
group:
[[ 1 2]
[ 6 4]
[80 81]
[78 83]]
----------mat------------
mat:
[[3 4]
[3 4]
[3 4]
[3 4]]
----------mat_g------------
mat_g:
[[ 2 2]
[ -3 0]
[-77 -77]
[-75 -79]]
----------mat_2------------
mat_2:
[[ 4 4]
[ 9 0]
[5929 5929]
[5625 6241]]
----------mat_sum------------
mat_sum:
[ 8 9 11858 11866]
四、海伦数据
文件总共四列,最后一列是标签,海伦收集的样本数据主要包含以下3种特征:
每年获得的飞行常客里程数
玩视频游戏所消耗时间百分比
每周消费的冰淇淋公升数
分析数据:数据可视化,数据归一化
数据读取,获取特征矩阵和标签,
在windows上使用open打开utf-8编码的txt文件时开头会有一个多余的字符\ufeff,它叫BOM,是用来声明编码等信息的,但python会把它当作文本解析。对UTF-16, Python将BOM解码为空字串。然而对UTF-8, BOM被解码为一个字符\ufeff。
步骤:
打开文件, 去除BOM, 获取行数, 对于每一行:
去除每行前后空白符, 按tab切分 前三列保存到特征矩阵中, 最后一保存到标签中,
def file2matrix(filename):
fr = open(filename,'r',encoding = 'utf-8')
arrayOLines = fr.readlines()
#针对有BOM的UTF-8文本,应该去掉BOM,否则后面会引发错误。
arrayOLines[0]=arrayOLines[0].lstrip('\ufeff')
numberOfLines = len(arrayOLines)
returnMat = np.zeros((numberOfLines,3))
classLabelVector = []
index = 0
for line in arrayOLines:
line = line.strip()
listFromLine = line.split('\t')
#将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
returnMat[index,:] = listFromLine[0:3]
if listFromLine[-1] == 'didntLike':
classLabelVector.append(1)
elif listFromLine[-1] == 'smallDoses':
classLabelVector.append(2)
elif listFromLine[-1] == 'largeDoses':
classLabelVector.append(3)
index += 1
return returnMat, classLabelVector
归一化步骤:
1、找出每一列最大、小值,返回值是一维列表,每个元素代表该列最大、小值
2、最大值列表减去最小值列表得出差列表
3、将最小值列表扩展为数据集大小,
4、将差值列表扩展为数据集大小,
5、原始数据减去最小值扩展,然后除以差值扩展
def autoNorm(dataSet):
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = np.zeros(np.shape(dataSet))
m = dataSet.shape[0]
# 原始值减去最小值
normDataSet = dataSet - np.tile(minVals, (m, 1))
normDataSet = normDataSet / np.tile(ranges, (m, 1))
return normDataSet, ranges, minVals
则全部代码:
import numpy as np
def classify(inX,dataSet,labels,k):
# 返回行数
dataSetSize = dataSet.shape[0]
# 扩展维度,相减
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances**0.5
#返回distances中元素从小到大排序后的索引值
sortedDistIndices = distances.argsort()
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndices[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
# 对字典按值排序,返回的是一个列表,列表元素是元组
sortedClassCount = sorted(classCount.items(),key=lambda item:item[1],reverse=True)
return sortedClassCount[0][0]
def file2matrix(filename):
fr = open(filename,'r',encoding = 'utf-8')
arrayOLines = fr.readlines()
#针对有BOM的UTF-8文本,应该去掉BOM,否则后面会引发错误。
arrayOLines[0]=arrayOLines[0].lstrip('\ufeff')
numberOfLines = len(arrayOLines)
returnMat = np.zeros((numberOfLines,3))
classLabelVector = []
index = 0
for line in arrayOLines:
line = line.strip()
listFromLine = line.split('\t')
#将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
returnMat[index,:] = listFromLine[0:3]
if listFromLine[-1] == 'didntLike':
classLabelVector.append(1)
elif listFromLine[-1] == 'smallDoses':
classLabelVector.append(2)
elif listFromLine[-1] == 'largeDoses':
classLabelVector.append(3)
index += 1
return returnMat, classLabelVector
def autoNorm(dataSet):
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = np.zeros(np.shape(dataSet))
m = dataSet.shape[0]
# 原始值减去最小值
normDataSet = dataSet - np.tile(minVals, (m, 1))
normDataSet = normDataSet / np.tile(ranges, (m, 1))
return normDataSet, ranges, minVals
def main():
filename = "datingTestSet.txt"
datingDataMat, datingLabels = file2matrix(filename)
normMat, ranges, minVals = autoNorm(datingDataMat)
# 行数
m = normMat.shape[0]
# 测试比率
hoRatio = 0.10
numTestVecs = int(m * hoRatio)
#分类错误计数
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = classify(normMat[i,:], normMat[numTestVecs:m,:],
datingLabels[numTestVecs:m], 4)
print("分类结果:%s\t真实类别:%d" % (classifierResult, datingLabels[i]))
if classifierResult != datingLabels[i]:
print('分类: ',classifierResult)
print('真实: ',datingLabels[i])
errorCount += 1.0
print("错误率:%f%%" %(errorCount/float(numTestVecs)*100))
main()
结果(只输出最后几行):
分类结果:2 真实类别:3
分类: 2
真实: 3
分类结果:1 真实类别:1
分类结果:2 真实类别:2
分类结果:1 真实类别:1
分类结果:3 真实类别:3
分类结果:3 真实类别:3
分类结果:2 真实类别:2
分类结果:2 真实类别:1
分类: 2
真实: 1
分类结果:1 真实类别:1
错误率:4.000000%
python函数:min(),max(), 列表切分
(1)min(),max()
0:列最值
1:行最值
空:全局最值
import numpy as np
a = np.array([[1,2,3,10,4,5],[2,3,4,3,5,6],[3,4,5,4,6,7]])
print(a)
print('-------全局最大小------')
print(a.min())
print(a.max())
print('------列最大小-------')
print(a.min(0))
print(a.max(0))
print('------行最大小-------')
print(a.min(1))
print(a.max(1))
[[ 1 2 3 10 4 5]
[ 2 3 4 3 5 6]
[ 3 4 5 4 6 7]]
-------全局最大小------
1
10
------列最大小-------
[1 2 3 3 4 5]
[ 3 4 5 10 6 7]
------行最大小-------
[1 2 3]
[10 6 7]
(2)列表切分:
import numpy as np
a = np.array([[1,2,3,4,5],[2,3,4,5,6],[3,4,5,6,7]])
print(a)
print('----------------')
print('a[2,:]: ')
print(a[2,:])
print('----------------')
print('a[0:2,:]: ')
print(a[0:2,:])
print('----------------')
print('a[0:2]: ')
print(a[0:2])
[[1 2 3 4 5]
[2 3 4 5 6]
[3 4 5 6 7]]
----------------
a[2,:]:
[3 4 5 6 7]
----------------
a[0:2,:]:
[[1 2 3 4 5]
[2 3 4 5 6]]
----------------
a[0:2]:
[[1 2 3 4 5]
[2 3 4 5 6]]
五、使用SKLearn中的KNN对手写字体进行处理
步骤:
1、获取训练集特征矩阵,标签
2、使用训练集特征矩阵、标签初始化KNN实例
3、获取测试集特征矩阵,标签 4、预测测试集,记录正确率
其实即代码:
实例KNN: neigh = kNN(n_neighbors = 3, algorithm = 'auto') neigh.fit(trainingMat, hwLabels)
预测: classifierResult = neigh.predict(vectorUnderTest)
首先将32*32的数据展开成1*1024,然后实例KNN,预测,代码如下:
import numpy as np
from os import listdir
from sklearn.neighbors import KNeighborsClassifier as kNN
# 将32*32展开成1*1024
def img2vector(filename):
returnVect = np.zeros((1, 1024))
fr = open(filename)
for i in range(32):
lineStr = fr.readline()
#每一行的前32个元素依次添加到returnVect中
for j in range(32):
returnVect[0, 32*i+j] = int(lineStr[j])
return returnVect
def handwritingClassTest():
hwLabels = []
# 训练集列表
trainingFileList = listdir('trainingDigits')
m = len(trainingFileList)
trainingMat = np.zeros((m, 1024))
# 获取训练集特征矩阵及其标签
for i in range(m):
# 训练集文件名
fileNameStr = trainingFileList[i]
# 数字
classNumber = int(fileNameStr.split('_')[0])
# 训练集标签列表
hwLabels.append(classNumber)
# 训练集特征矩阵,即 M*1024
trainingMat[i,:] = img2vector('trainingDigits/%s' % (fileNameStr))
# 构建分类器
neigh = kNN(n_neighbors = 3, algorithm = 'auto')
neigh.fit(trainingMat, hwLabels)
# 测试集文件名列表
testFileList = listdir('testDigits')
errorCount = 0.0
# 测试集数量
mTest = len(testFileList)
for i in range(mTest):
# 文件名
fileNameStr = testFileList[i]
# 类别
classNumber = int(fileNameStr.split('_')[0])
# 特征矩阵
vectorUnderTest = img2vector('testDigits/%s' % (fileNameStr))
# 预测结果
classifierResult = neigh.predict(vectorUnderTest)
print("分类返回结果为%d\t真实结果为%d" % (classifierResult, classNumber))
if(classifierResult != classNumber):
errorCount += 1.0
print("总共错了%d个数据\n错误率为%f%%" % (errorCount, errorCount/mTest * 100))
handwritingClassTest()
结果(最后几行输出):
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
分类返回结果为9 真实结果为9
总共错了12个数据
错误率为1.268499%
我们发现,使用SKLearn确实比自己手写要方便的多。