《机器学习实战》笔记
1.K-近邻算法的优缺点
- 采用测量特征值间的距离的方法进行分类
- 优点在于,精度高,无数据输入假定,对异常值不敏感
- 缺点在于,计算复杂度和空间复杂度高
- 适用于数值型,标称型
2.KNN工作原理
- 存在训练样本集,样本集中每个数据都有标签及所属分类
- 新数据输入时,选择样本数据集中前k个(通常k不大于20)距离最近(最相似)的数据
- 统计k个数据中的分类数量,选择次数最多的分类分配给新数据
3.构建一个测试用的分类器
- 仅用于测试分类器在本机环境下是否可用,python3.6.5环境下pycharm2018.1.2
import operator
from numpy import tile, sqrt, array
def createDataSet():
arr = [[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]]
group = array(arr)
labels = ['A', 'A', 'B', 'B']
return group, labels
group, labels = createDataSet()
# print(group)
# print(labels)
# 参数依次为待分类的新数据,训练样本集,标签向量,k个取值
def classify0(inData, dataSet, labels, k):
# 1 利用欧式距离公式计算向量间距离
dataSetSize = dataSet.shape[0]
diffMat = tile(inData, (dataSetSize, 1)) - dataSet
sqDiffMat = pow(diffMat, 2)
sqDistances = sqDiffMat.sum(axis=1)
distances = sqrt(sqDistances)
# 按距离排序
sortedDistIndicies = distances.argsort()
classCount = {}
# 2 选择距离最小的k个点
for i in range(k):
votelabel = labels[sortedDistIndicies[i]]
classCount[votelabel] = classCount.get(votelabel, 0) + 1
# 3 按分类出现次数排序,将字典分解成元组列表
# itemgetter(1)按照第二个元素的次序对元组排序
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
# 返回次数最多的分类
return sortedClassCount[0][0]
# 输入一个待分类的新数据[0,0]测试返回的分类结果
r = classify0([0.0], group, labels, 3)
print(r)
4.为避免各维度单位不同导致的权重不同,要进行归一化处理
from numpy import tile
# 新值=(旧值-最小值)/(最大值-最小值)
# 得到的值都在0-1之间
def autoNorm(dataSet):
# 从列中选择最小值dataSet.min(0)
minVals=dataSet.min(0)
# 选择最大值
maxVals = dataSet.max(0)
ranges=maxVals-minVals
# shape()函数用于读取参数矩阵各维度的长度
# 用变量m表示参数矩阵第一列的长度
m=dataSet.shape[0]
# tile() 将变量内容复制成输入矩阵同样大小的矩阵,即生成一个列数与原矩阵相同,行数为1,各元素都为最小值的矩阵,进行运算
normDataSet=dataSet-tile(minVals,(m,1))
# 上式进行旧值-最小值,下式进行除法,得到均一化的矩阵
normDataSet=normDataSet/tile(ranges,(m,1))
# 函数返回值依次为均一化后的矩阵,最大与最小数据差值,最小值
return normDataSet,ranges,minVals
5.从本地文件中读取训练样本集的数据,格式为每行四组数字,以\t隔开
from numpy.ma import zeros, array
from demo.归一化特征值 import autoNorm
def file2matrix(filename):
# 1 按行读取本地文件,并返回行数
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines)
# 2 zeros()创建以0填充的矩阵,以元组作参数,规定行数为文件行数,列数为3
returnMat = zeros((numberOfLines, 3))
classLabelVector = []
index = 0
for line in arrayOLines:
# 3 拆分文件内容到列表
# strip()去除回车
line = line.strip()
# split()分割成元素列表
listFromLine = line.split('\t')
# 选取列表前3个元素存入特征值矩阵,行数与文件相同,内容为分割后的列表元素
returnMat[index, :] = listFromLine[0:3]
# 将列表最后一列以整数型存储进目的列表中
classLabelVector.append(int(listFromLine[-1]))
index += 1
# 返回值依次为特征值矩阵,用作标记结果的元素列表
return returnMat, classLabelVector
6.测试KNN算法的错误率,以90%作训练样本,10%作测试数据
from demo.归一化特征值 import autoNorm
from demo.读取文件 import file2matrix
from demo.KNN import classify0
def datingClassTest():
# 规定10%作测试数据
hoRatio=0.1
# 读取数据返回特征值矩阵,标签列表
datamat,labels=file2matrix('demo.txt')
# 归一化,返回归一化后矩阵
normMat, ranges, minVals = autoNorm(datamat)
# 计算向量数量,决定哪些测试,哪些做训练样本
m=normMat.shape[0]
# m为行数,以0.1*m后取整作为测试数据
numTestVecs=int(m*hoRatio)
errorCount=0.0
for i in range(numTestVecs):
# 利用自定义函数classify0(),依次传入第0至测试行数行数据,测试的行数至总行数用作训练样本,同前参的标签列,取最相似的3个向量作投票
# 返回的参数为猜测的分类结果
classResult=classify0(normMat[i,:],normMat[numTestVecs:m,:],labels[numTestVecs:m],3)
# classResult测试结果,labels真正的结果
print('测试:{},实际:{}'.format(classResult,labels[i]))
if classResult != labels[i]:
# 二者不同,错误数+1
errorCount += 1
# 错误率=错误数/测试总数
print('错误率:{}'.format(errorCount/float(numTestVecs)))
7.实际使用KNN算法,输入任意3组数字,返回预测的分类结果
from numpy.ma import array
from demo.读取文件 import file2matrix
from demo.归一化特征值 import autoNorm
from demo.KNN import classify0
def classifyPerson():
resultList = ['讨厌','一般','喜欢']
data1 = float(input('输入第一列数据'))
data2 = float(input('输入第二列数据'))
data3 = float(input('输入第三列数据'))
datamat,labels=file2matrix('demo.txt')
normmat,ranges,minvals = autoNorm(datamat)
inarr = array([data1,data2,data3])
classresult=classify0((inarr-minvals)/ranges,normmat,labels,3)
print('可能的结果是{}'.format(resultList[classresult-1]))
8.示例2:图像识别手写数字0~9,添加一个方法读取本地生成好的txt,进行图像识别错误率的测试
# 将图像转换为1*1024的向量
def img2vecotr(filename):
# 以0占位,构建1*1024的空数组
returnVect = zeros((1,1024))
fr = open(filename)
for i in range(32):
# 读取32行数据
lineStr = fr.readline()
for j in range(32):
# 每行前32个字存入数组
returnVect[0,32*i+j] = int(lineStr[j])
return returnVect
9.利用作者提供的测试集和训练集测试KNN算法的错误率
def handTest():
hwLabels = []
# listdir('文件夹'),获取文件夹下所有文件
trainList = listdir('E:\machinelearninginaction\Ch02\digits\\trainingDigits')
m = len(trainList)
trainMat = zeros((m,1024))
for i in range(m):
# 将文件名拆分后存入列表
filenameStr = trainList[i]
fileStr = filenameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
# 文件名开头的数字是给定的实际结果,存入一个列表备用
hwLabels.append(classNumStr)
trainMat[i,:] = img2vecotr(f'E:\machinelearninginaction\Ch02\digits\\trainingDigits\{filenameStr}')
testList = listdir('E:\machinelearninginaction\Ch02\digits\\trainingDigits')
errorcount=0.0
mTest = len(testList)
for i in range(mTest):
filenameStr = trainList[i]
fileStr = filenameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
vecotrTest = img2vecotr(f'E:\machinelearninginaction\Ch02\digits\\trainingDigits\{filenameStr}')
# 利用classify0()方法以k=3生成测试结果
ifierResult = classify0(vecotrTest,trainMat,hwLabels,3)
print(f'测试:{ifierResult},实际:{classNumStr}')
if (ifierResult != classNumStr):
errorcount += 1
print('错误率为:{}'.format(errorcount/float(mTest)))
10.KNN算法的局限性
- 执行效率低,在图像识别时,要对每个测试向量做2000次的欧氏距离计算,每次包含1024个维度的浮点运行,总计900次才能得到测试结果
- KNN为基于实例的学习,必须存在接近实际数据的训练样本数据
- 必须保存全部数据集,存储空间消耗大
- 无法给出数据的基础结构信息,无法获得样本的特征