一、机器学习实战之K-近邻算法
以下代码来自《机器学习实战》,对约会数据,手写数字数据,用KNN进行分类,我在每行代码后添加了详细的注释,以便理解
算法概述:给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的k个实例,这k个实例的多数属于某个类,就把该输入实例分为这个类
优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度高
适用数据范围:数值型和标称型
from numpy import *
import operator
import matplotlib
import matplotlib.pyplot as plt
from os import listdir
分类器主函数,通过输入测试特征向量,训练特征矩阵,训练标签向量,k值,输出分类结果
# inX:用于分类的输入向量
# dataSet:输入的训练样本集
# lables:标签向量
# k:选择最近邻的数目
def classify0(inX, dataSet, labels, k):
dataSetSize = dataSet.shape[0] #训练数据集的行数
diffMat = tile(inX, (dataSetSize, 1)) - dataSet # 把输入向量转换成和训练集的行数一样的一列向量,然后减去训练集,就相当于把输入向量减去每一个训练集样本
sqDiffMat = diffMat ** 2 # 计算每个差的平方
sqDistances = sqDiffMat.sum(axis=1) #按行算平方和
distances = sqDistances ** 0.5 # 平方和再开根号,以上为计算欧式距离, 结果为输入向量与训练集每个数据的欧氏距离
sortedDistIndicies = distances.argsort() # 对距离进行排序 升序,返回的是对应的数据的原位置 而不是原数据值
classCount = {}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]] # 取第i个距离对应的标签值
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 # get方法表示访问字典的Key为voteIlabel的值,若没有这个key,值设为0,若有这个Key,则值+1
sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse=True) # .items()返回一个列表,operator.itemgetter(1)针对列表中每一个数据的1位置的数字进行降序
return sortedClassCount[0][0] # 返回排名数第一的第一个数据也就是标签
把数据从文件里取出来,转换成训练矩阵,和训练标签向量
# 此数据有3 个特征值 , 1个label
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) # 得到文件的行数
returnMat = zeros((numberOfLines, 3)) # 构造一个文件行数,3列的全0矩阵
classLabelVector = []
index = 0
for line in arrayOLines:
line = line.strip()
listFromLine = line.split('\t') # 对每行数据制表符分隔成一个列表
returnMat[index, :] = listFromLine[0:3] # 取列表的0:3 按index 覆盖全来的全0 矩阵
classLabelVector.append(int(listFromLine[-1])) # 取最后一个数据,代表的是分类
index += 1
return returnMat, classLabelVector
有的特征数值太大,归一化特征值到0-1之间
# 归一化特征值 newValue = (oldValue - min) / (max - min)
def autoNorm(dataSet):
minVals = dataSet.min(0) # 取出每列的最小值 , 结果是一个行向量
maxVals = dataSet.max(0) # 取出每列的最大值, 结果是一个行向量
ranges = maxVals - minVals # 得到每列最大值减去最小值的一个范围矩阵,结果是一个行向量
normDataSet = zeros(shape(dataSet)) # 构造和训练集数据一样的全0 矩阵
m = dataSet.shape[0] # m 是dataSet的行数
normDataSet = dataSet - tile(minVals, (m, 1)) # 把最小值行向量,扩展成和训练集数据行数一样的矩阵,再拿训练集数据减去这个最小值扩展矩阵
normDataSet = normDataSet/tile(ranges, (m, 1)) # 拿上面的结果矩阵,除以把范围矩阵扩展成和训练集数据行数一样的扩展矩阵
return normDataSet, ranges, minVals
分类过程主函数,打印出结果
def datingClassTest():
hoRatio = 0.10 # 表示把百分之多少的数据作为测试数据
datingDataMat, datingLabels = file2matrix("./datingTestSet2.txt") # 把原始数据变成矩阵形式
normMat, ranges, minVals = autoNorm(datingDataMat) # 归一化数据
m = normMat.shape[0]
numTestVecs = int(m * hoRatio) # 代表多少条数据是测试集
errorCount = 0.0
for i in range(numTestVecs): # i表示第几个测试数据,numTestVecs:m 表示训练集数据,k=3
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 4)
print("分类器返回结果是:%d, 真实结果是:%d"%(classifierResult, datingLabels[i]))
if classifierResult != datingLabels[i]:
errorCount += 1.0
print("总的错误率为:%f"%(errorCount / float(numTestVecs)))
结果还不错,4%的错误率
定义一个接收输入,预测输入数据分类的函数
def classifyPerson():
resultList = ['根本不','有一点','非常']
percentTats = float(input("玩游戏所耗时间百分比是多少?"))
ffMiles = float(input("每年获取的飞机常客里程数是多少?"))
iceCream = float(input("每周消费的冰淇淋公升数是多少?"))
datingDataMat, datingLabels = file2matrix("./datingTestSet2.txt")
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream])
classifierResult = classify0((inArr - minVals) / ranges, normMat, datingLabels, 3)
print("你可能"+resultList[classifierResult - 1]+'喜欢这个人')
手写识别系统
定义一个把图像转换为向量的函数
#将图像转化为向量, 32*32像素的黑白图像
def img2vector(filename):
returnVect = zeros((1, 1024))
fr = open(filename)
for i in range(32):
lineStr = fr.readline()
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 = zeros((m, 1024)) # 构建一个m*1024的全0向量
for i in range(m): # 对每一行训练数据
fileNameStr = trainingFileList[i] # 得到数据的文件名
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
hwLabels.append(classNumStr) # 从文件名中取出对应的是哪个数字,添加到标签列表里 (文件名如:0_13.txt 代表手写数字0的第13个样本)
trainingMat[i, :] = img2vector("./trainingDigits/%s"%fileNameStr) # 从目录中把手写数字图片转化为向量 放到训练矩阵
testFileList = listdir("./testDigits") # 取出测试集目录下所有的文件名
errorCount = 0.0
mTest = len(testFileList) # 测试集文件的个数
for i in range(mTest): #对每一行测试数据
fileNameStr = testFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0]) # 从文件名中得到该数据的真实分类
vectorUnderTest = img2vector("./testDigits/%s"%fileNameStr) # 把文件图片转化为向量
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) # 往分类器中输入参数:该测试向量,训练矩阵,训练矩阵对应的分类,k值,得到分类器返回分类
print("分类器返回结果是:%d, 真实结果是:%d"%(classifierResult, classNumStr))
if classifierResult != classNumStr:
errorCount += 1.0
print("\n错误分类的个数:%d"%errorCount)
print("\n总的错误率:%f"%(errorCount / float(mTest)))
结果超棒,只有1.2%的错误率!
那么接下来,我们自己找个数据集应用一下吧!
在UCI机器学习库里随便找了个关于鲍鱼的数据 http://archive.ics.uci.edu/ml/datasets/Abalone
把刚才的file2matrix 和 datingClassTest函数简单改一改
def file2matrix_new(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines)
returnMat = zeros((numberOfLines, 8))
classLabelVector = []
index = 0
for line in arrayOLines:
line = line.strip()
listFromLine = line.split(',')
returnMat[index, :] = listFromLine[1:]
classLabelVector.append(listFromLine[0])
index += 1
return returnMat, classLabelVector
def abaloneClassTest():
hoRatio = 0.10 # 表示把百分之多少的数据作为测试数据
abaloneDataMat, abaloneLabels = file2matrix_new("./abalone.data") # 把原始数据变成矩阵形式
normMat, ranges, minVals = autoNorm(datingDataMat) # 归一化数据
m = normMat.shape[0]
numTestVecs = int(m * hoRatio) # 代表多少条数据是测试集
errorCount = 0.0
for i in range(numTestVecs): # i表示第几个测试数据,numTestVecs:m 表示训练集数据,k=3
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], abaloneLabels[numTestVecs:m], 3)
print("分类器返回结果是:%s, 真实结果是:%s"%(classifierResult, abaloneLabels[i]))
if classifierResult != abaloneLabels[i]:
errorCount += 1.0
print("总的错误率为:%f"%(errorCount / float(numTestVecs)))
错误率超过50%。。。笋干爆炸,如果错误率70%那就说明你反过来选就是30%了,而50%左右基本等于随机选择。。。
看来还得继续往后学习啊