为了能够熟悉不能机器学习算法的原理,所以在此将学习《机器学习实战》这本书的笔记给记录下来,因为此书使用python2写的,而我用的是python3,所以和原书的代码或有不同
—————————————————————————————————
k-近邻算法采用测量不同特征值之间的距离来进行分类
优点:精度高,对异常值不敏感,无数据输入假定
缺点:计算复杂度高、空间复杂度高
适用数据范围:数值型和分类型
原理:首先,我们必须得有一份含有分类标签的数据集,为训练数据集。比如我们要预测用户是否会流失,那么分类标签就是流失和未流失。然后有一份新的数据集,这份数据集并没有分类标签,k-近邻算法就会将新的数据集和训练数据集进行比较,从训练数据集中选出与新数据集每个数据最相近的K个数据,查看这K个数据所属标签哪类最多,比如流失,就把新数据集中的那个数据分类为流失。怎么判断是否相近呢?K-近邻是计算不同数据的距离
k-近邻算法的原理伪代码
对未知类别属性的数据集中的每个数据点依次执行以下操作:
(1) 计算已知类别数据集中的点与当前点之间的距离
(2) 按照距离递增次序排序
(3) 选出与当前距离最近的K个点
(4) 统计出K个点所属类别的频率
(5) 返回K个点出现频率最高的的类别作为当前点的预测类别
真正的代码实现
#建立一个KNN.py文件,将下面的代码写入到该文件中
def knnClassify(inX,dataSet,labels,k):
'''
函数功能:K-近邻算法
#-------------------------------------#
参数解释:
inX:未知类别的数据集,和dataSet有相同列数
dataSet:已经类别的数据集
labels:已知类别数据集所对应类别
k:k近邻
'''
dataSetSize = dataSet.shape[0]
#计算已知类别数据集中的点与当前点之间的距离,计算的是欧氏距离
#将inX按行重复dataSetSize行,按列重复1列,就可以得到和dataSet相同行列
diffMat = np.tile(inX,(dataSetSize,1)) - dataSet
sqDiffMat = diffMat**2
#sum(axis=0)代表按列加总,sum(axis=1)代表按行
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances**0.5
sortedDistIndicies = distances.argsort() #返回数组值从小到大的索引值
#统计出K个点所属类别的频率
classCount = {}
for i in range(k):
votIlabel = labels[sortedDistIndicies[i]] #选出与当前距离最近的K个点
classCount[votIlabel] = classCount.get(votIlabel,0)+1
#将classCount字典变成一个可迭代的元组列表,
#然后运用operator运算符模块的itemgetter方法对第二个元素的次序对元组进行降序排序
#按照距离递增次序排序
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
#返回K个点出现频率最高的的类别作为当前点的预测类别
return sortedClassCount[0][0]
以上代码只是为了实现k-近邻算法原理,没有什么实际作用,接下来我们利用约会网站的数据来进行实操一下
数据为:https://pan.baidu.com/s/1zIGz6GtEU20UeT6hemP4MQ
数据的前三列数据的含义分别是:
飞行常客里程数,玩视频游戏所耗时间百分比和每周消费冰淇淋公升数
- 因为这份数据是TXT格式的,而《机器学习实战上的》上读取数据的代码有点复杂了,所以这里用的是我自己写的代码
#将下面的代码写入到KNN.py文件中
import pandas as pd
def panduan(x):
if x=='didntLike':
return 1
elif x=='smallDoses':
return 2
elif x=='largeDoses':
return 3
def file2matrix(filename,n,m,l,p):
'''
函数功能:将TXT格式中的数据转化为数组和列表数据
#--------------------------------------------#
参数说明:
filename:数据文件所在路径
n:数据集文件中不包含类别那一列的数据的开头列数,通常是0
m:数据集文件中不包含类别那一列的数据的最后一列列数
l:数据集中类别所在的那一列的列数
P:l+1
'''
df = pd.read_table(filename,engine='python',header=None)
returnMat = np.array(df.loc[:,n:m])
classLabel = df.loc[:,l:p]
classLabel.columns=['类别']
classLabelVec=classLabel['类别'].map(panduan)
classLabelVector = list(classLabelVec)
return returnMat,classLabelVector
- 分析数据:使用 Matplotlib 创建散点图
import KNN(该模块即我们之前建立的文件)
import matplotlib.pyplot as plt
import matplotlib
#指定默认字体
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['font.family'] = 'sans-serif'
#解决负号'-'显示为方块的问题
matplotlib.rcParams['axes.unicode_minus'] = False
#读取数据并画图
if __name__ == '__main__':
filename = 'datingTestSet.txt'
datingDataMat,datingLabels = KNN.file2matrix(filename,0,2,3,4)
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2], \
15.0*KNN.np.array(datingLabels),15.0*KNN.np.array(datingLabels))
plt.xlabel("玩视频游戏所耗时间百分比")
plt.ylabel("每周所消费的冰淇淋公升数")
plt.show()
image.png
- 将数值归一化
从以上原理我们知道K近邻算法比较适合的数据类型是数据值,而数据型有大有小,如果数据量纲相差较大的话会在计算欧式距离的时候给予特征值比较大的特征较大的权重,所以在用K-近邻算法前,我们可以将原始数据处理一下
#在KNN.py文件中写入以下代码
def autoNorm(dataSet):
minVals = dataSet.min(axis=0)
maxVals = dataSet.max(axis=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
如果KNN里面的内容有所更改的话,这时候在用import KNN是没用的,我们必选要用以下的两句代码重新载入KNN.py
import importlib
importlib.reload(KNN)
if __name__=='__main__':
filename='datingTestSet.txt'
datingDataMat,datingLabels = KNN.file2matrix(filename,0,2,3,4)
normMat,ranges,minVals=KNN.autoNorm(datingDataMat)
- 计算错误率来评估算法分类好坏
在KNN.py文件中写入以下代码
def datingClassTest(filename):
#只留下百分之一比例用作测试集
hoRatio = 0.1
datingDataMat,datingLabels =file2matrix(filename,0,2,3,4)
normMat,ranges,minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m*hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = knnClassify(normMat[i,:],normMat[numTestVecs:m,:], \
datingLabels[numTestVecs:m],3)
if (classifierResult != datingLabels[i]):
errorCount += 1.0
print('The total error rate is: %f'%(errorCount/float(numTestVecs)))
记得再重新载入
importlib.reload(KNN)
因为这里是默认K为3
if __name__=='__main__':
filename='datingTestSet.txt'
KNN.datingClassTest(filename,0,2,3,4)
也可以循环K,将错误率最低的K找出来
将KNN.py中的datingClassTest函数修改一下,变成以下的样子
def datingClassTest2(filename,k):
hoRatio = 0.1
datingDataMat,datingLabels =file2matrix(filename,0,2,3,4)
normMat,ranges,minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m*hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = knnClassify(normMat[i,:],normMat[numTestVecs:m,:], \
datingLabels[numTestVecs:m],k)
if (classifierResult != datingLabels[i]):
errorCount += 1.0
print('The total error rate is: %f'%(errorCount/float(numTestVecs)))
return errorCount
#-------------------------------------------------------------------------------#
记得再重新载入
importlib.reload(KNN)
if __name__=='__main__':
filename='datingTestSet.txt'
error = {}
for j in KNN.np.arange(2,6):
error[j] = KNN.datingClassTest2(filename,j)
print(error.items())