转载请注明作者和出处:https://blog.csdn.net/weixin_41858342/article/details/86700041
代码及数据地址:https://github.com/yuankaihua668/knn-code-and-data.git
操作系统:WINDOWS 10
软件版本:python-3.6.2-amd64
编 者:一个从大山到原野的孩子
各位奋斗在学习路上的小伙伴们,大家好。相信各位或多或少都有一个自己的梦想【分类 聚类 回归 关联 推荐】,小野也不例外。因为我从小生活在重庆大巴山一个让绝大多数人都不曾听闻的小城镇。因此 ,从小知事起,就想越过那些不曾翻越的高山【升维】,去看看一望无际的平原【降维】、城里的汽车、形如长龙的火车、蜿蜒曲折拥有宽阔水面的大江大河【分割超平面】。可能很多同学,都无法想象一个生在深山老林的孩子,对于繁华都市、平坦世界的强烈渴望。回想起儿时那种情感是很微妙模糊的,而现在回忆曾经的向往又无比清晰。
在我们的童年,是没有幼儿园的,我们连幼儿园这个名字也没听过。如果要上学,那就是从一年级开始,而离我家最近的两所小学至少是有10公里远,每所小学一年级一般只有一个班,如果学生太少,那就和高年级的同学一起读书,老师教了高年级的同学,再来教我们【有监督 无监督 半监督学习】,在这个半山中的小学,一个老师通常是要教几门课几个年级的【样本分布不均】。即便教师资源如此匮乏,所有的同学和老师都很少有迟到的。那时候的教室和普通的民房有很大区别,最大的区别就是学校的墙外面是白色的,居民房是土色的。我们整个学校的教室就是三间,每间大概40平左右的白色土墙黑色瓦屋的房子。教室的长的两边分布着距离不均的长方形窗户,窗户中间用木条分开来,木条上贴了春季农民伯伯播种蔬菜的软的胶纸。贴上这些胶纸,冬天就吹不进来风,会暖和一些。那个时候,要是谁调皮捣蛋,把胶纸戳了小洞,是要受罚的。可是调皮的小伙伴总会有,当老师发现窗户破了洞,就会很生气的在班里清查,谁最后离开学校?谁不听话,谁的身高跟这个破洞最接近?谁玩过泥巴之类的问题?老师生气是因为在那个年代胶纸并不便宜,只有富裕的家庭才能买得起用,普通的农户就只有等暖和些时候再播种或者让幼苗和种子受些冻了。
上学的路途也并非那么平坦和一帆风顺,要到达学校,无论走哪条路【算法】,都要经过一些坡度极陡【梯度上升】,杂草丛生【离群点和噪音数据】,隔河无桥【相关性】,滑落滚石频率较高的路段。我从6岁开始读一年级,和现在大多数孩子差不多,在花坪村小学(不是你们插花的花瓶-哈哈),在路途艰难、教舍简陋(下雨天,瓦上漏水,教室浸水是常有的事)、师资匮乏的年代,同学们仍然是欢声笑语,生气勃发,朗朗读书声。读了半年后,就在放长假的时候,我们的教室塌了。因此,我从花坪小学换到生计梁小学(名字很难写对,那个时候都是这么叫的),相比之前的学校,这里更加平坦,但是其中一间教室后墙有很大的裂缝【算法改进 算法优点 算法缺点】,要是现在,那绝对是危墙。
在这里读了一个多学期,让我印象最深刻的是,欧老师对我们很好,经常中午叫我们几个离家最远的孩子去她家吃午饭,那时候就很好奇,一般家庭都是一天吃两顿饭,他们还要吃午饭。另外就是,到学校要过很多河流,河水在连续的雨天后变的异常的大异常的凶猛。我们还小,有时在河边等大人背我们过去,有时就会再走很多山路,去找一条很小的河流很窄处【最大间隔】跳过去。曾经,那些背过我过河的叔叔阿姨,有的已辞别人世,但我曾经对他们的感激之情,仍然记忆犹新。后面,父母听说有一个教学很好的吕老师开始教一年级了,又把我转送到长连(在有地主的年代,帮人长工的称呼)小学。在这里,开始了我正式的学习生涯。
在这里,我将陆续更新有趣的学习生涯与感悟。
在这里,当然,重要的是所有的分类、聚类、回归、关联、推荐等算法思想和算法代码。
其实,算法如同人生,不同的人生感悟,不同的人生道路,不同的实现途径。
其实,我并不是大牛,一来记录在此,方便学习回顾。二来,也希望能对大家有所帮助。
因此,写的不对的地方,还请你们多多指正!谢谢你们!
算法思想:
任何样本点的特性和该邻近点的特性类似,因此 某个点的多个邻近点中的大多数点的特性来表示当前点的特性。
一个对象的特征是由他邻近对象的特性来决定的。
可2分类、多分类,也可回归预测。
KNN分类算法:邻居点中类别最多的那个类别作为当前点的类别。
KNN回归算法: 使用所有邻近点的均值作为当前点的回归预测值。
应用场景:
①:电影分类:根据打斗镜头数和接吻镜头数,分爱情片何和动作片。
②:手写数据识别:0-9的识别。
③:约会数据分类:根据航班里程数 视频游戏时间比例 吃冰激凌升数分类倾向于约会的类型。
④:一个人有10个亲密的朋友,这些朋友都爱运动。那可以在一定程度上推断这个人也爱运动。
⑤:一个人在医疗网站上输入身高、体重、血型、是否抽烟喝酒。判定这个是健康、亚健康、有病。
算法流程:
①:计算已知类别数据集中的点与当前点之间的距离。
②:按照距离升序排序。
③:选取与当前点距离最小的k个点。
④:确定前k个点所在类别的出现频率。
⑤:返回前k个点出现频率最高的类别作为当前点的预测分类。
算法优点:
①:精度高、对异常值不敏感、无数据输入假定。
②:无需估计参数,无需训练; 直接计算距离 排序 投票统计 再排序。
③:特别适合于多分类问题(multi-modal,对象具有多个类别标签)时, KNN比SVM的表现要好。
算法缺点:
①:存在样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少)。
②:计算复杂度高、空间复杂度高、需要大量内存空间,数据量很大的时候不适合用,计算量太大。
③:最大的缺点是无法给出数据的内在含义。
算法改进:
方向1:距离要考虑到加权.改进缺点的第2点。
①:KNN算法因其提出时间较早,随着其他技术的不断更新和完善,KNN算法的诸多不足之处也逐渐显露,因此许多KNN算法的改进算法也应运而生。
针对以上算法的不足,算法的改进方向主要分成了分类效率和分类效果两方面。
②:分类效率:事先对样本属性进行约简,删除对分类结果影响较小的属性,快速的得出待分类样本的类别。该算法比较适用于样本容量比较大的类域的自动
分类,而那些样本容量较小的类域采用这种算法比较容易产生误分。
③:分类效果:采用权值的方法(和该样本距离小的邻居权值大)来改进,Han等人于2002年尝试利用贪心算法,针对文件分类时候做可调整权重的k最近邻算法
WA-KNN (weighted adjusted k nearest neighbor),以促进分类效果;而Li等人于2004年提出由于不同分类的文件本身有数量上有差异,
因此应该依照训练集合中各种分类的文件数量,选取不同数目的最近邻居,来参与分类。
方向2:kd_tree KNN的参数之一
kd_tree 是密度聚类函数(DBSCAN)算法中计算样本和核心点对象之间的距离来获取
最近邻,及KNN中用于计算最邻近的快速、便捷的构建方式。
样本量小的时候 可用brute 计算所有点的距离,然后找最近的距离。
样本量大的时候 用kd_tree 计算
kd_tree构建方式:
KD树采取从M个样本的N纬特征中,分别计算N个特征取值的方差,用方差最大的NK做为根节点,对于这个特征选取
中位数NKV作为样本的划分点,小于该值的划分到左子树,大于等于该值的划分到右子树。采用同样的办法,往下递归。直到右子节点与父节点值相等。
相关术语:
①:闵可夫斯基距离(Minkowski Distance)
A: 欧氏距离(Euclidean Distance)
B: 曼哈顿距离
C:切比雪夫距离
②:马氏距离(Mahalanobis Distance)
③:巴氏距离(Bhattacharyya Distance)
④:汉明距离(Hamming distance)
⑤:标准化欧氏距离 (Standardized Euclidean distance )
⑥:夹角余弦(Cosine)
⑦:杰卡德相似系数(Jaccard similarity coefficient)
⑧:皮尔逊系数(Pearson Correlation Coefficient)
⑨:k是一个超参,所有超参的确定必须通过交叉验证来给定。
重点笔记:
①:K取多大比较合理?通常k是不大于20的整数 通常取奇数。
②:投票?选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
③:数据集的分配问题。只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。
参考链接:
①:https://blog.csdn.net/mousever/article/details/45967643
https://blog.csdn.net/zhaohaibo_/article/details/80152670
https://blog.csdn.net/csshuke/article/details/75127924
https://blog.csdn.net/dark_scope/article/details/15809445 python 实现
算法代码:根据约会对象的飞行里程数 打游戏的时间比例 喝可乐的升数分类属于哪一类约会对象
# -*- coding: utf-8 -*-
# @Date : 2019-01-18 16:30
# @Author : 一个从大山到原野的孩子
# @完成时间:
#项目根据个人每年飞行里程数、玩视频游戏所耗时间百分比、每周消费的冰淇淋公升数
#来判断约会的可能性
from numpy import *
import operator
from os import listdir
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
"""
kNN: k Nearest Neighbors
Input: inX: vector to compare to existing dataset (1xN)
dataSet: size m data set of known vectors (NxM)
labels: data set labels (1xM vector)
k: number of neighbors to use for comparison (should be an odd number)
Output: the most popular class label
"""
inputfile='C:\\Users\\yuanye\\Desktop\\code\\dataone_knn_test.csv'
def file2matrix(filename):
"""
Function: 从文本文件中解析数据
Args: filename:文件名称字符串
Returns: returnMat:训练样本矩阵
classLabelVector:类标签向量
"""
#打开文件
fr = open(filename)
#得到文件行数
numberOFLines = len(fr.readlines())
#创建返回的NumPy矩阵
returnMat = zeros((numberOFLines, 3))
#创建返回的向量列表
classLabelVector = []
fr = open(filename)
index = 0
for line in fr.readlines():
#使用line.strip()截取掉多有的回车符
line = line.strip()
#使用tab字符将上一步得到的整行数据分割成一个元素列表
listFromLine = line.split(',')
#选取前三个元素,存储到特征矩阵中
returnMat[index, :] = listFromLine[0:3]
#将列表最后一列存储到向量classLabelVector中
classLabelVector.append(int(listFromLine[-1]))
index += 1
#返回训练样本矩阵和类标签向量
return returnMat, classLabelVector
def classify_knn(inX, dataSet, labels, k):
"""
Function: 创建数据集和标签
Args: inX:用于分类的输入向量 (1xN)
dataSet:输入的训练样本集 (NxM)
labels:标签向量 (1xM vector)
k:用于比较的近邻数量 (should be an odd number)
Returns: sortedClassCount[0][0]:分类结果
"""
#dataSet.shape[0]:求dataSet矩阵的行数
#dataSet.shape[1]:求dataSet矩阵的列数
#dataSet.shape:元组形式输出矩阵行数、列数
#数组何矩阵才有shape属性
dataSetSize = dataSet.shape[0]
#tile(A, B):将A重复B次,其中B可以是int类型也可以是元组类型 B=(3,2) 3行、原数据出现2次
#tile 是numpy数组 功能是重复某个数组
#向量inX与矩阵dataSet里面的每组数据做差
diffMat = tile(inX, (dataSetSize, 1)) - dataSet
#对求差后的矩阵求平方
sqDiffMat = diffMat**2
#sqDiffMat.sum(axis=0):对矩阵的每一列求和
#sqDiffMat.sum(axis=1):对矩阵的每一行求和
#sqDiffMat.sum():对整个矩阵求和
sqDistances = sqDiffMat.sum(axis=1)
#求平方根
distances = sqDistances**0.5
#print("distances",distances)
#对上式结果进行排序 返回的是按升序排列后的索引值
#知识延伸-Sort函数是list列表中的函数,而sorted可以对list或者iterator进行排序
#sort 操作的是内存;sorted 操作的是视图
#sorted有多个排序参数 cmp key reverse itemgetter
#cmp key 结合lambda排序,itemgetter 可以多级排序 列值顺序以元祖形式给出
sortedDistIndicies = distances.argsort()
#print("sortedDistIndicies",sortedDistIndicies)
#创建字典
classCount = {}
#给字典赋值
for i in range(k):
#字典的key
voteIlabel = labels[sortedDistIndicies[i]]
#classCount.get(voteIlabel,0):如果字典键的值中有voteIlabel,
#则返回0(第二个参数的值)
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#对classCount进行排序,sroted、items以及itermgetter 字典排序
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
#返回分类结果
return sortedClassCount[0][0]
def knn_scatter(inputfile):
'''
#简单作图方法
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(x,y,x1,y1)
plt.show()
'''
n = 1000 #number of points to create
xcord1 = []; ycord1 = []
xcord2 = []; ycord2 = []
xcord3 = []; ycord3 = []
markers =[]
colors =[]
fw = open(inputfile)
for i in range(n):
[r0,r1] = random.standard_normal(2)
myClass = random.uniform(0,1)
if (myClass <= 0.16):
fFlyer = random.uniform(22000, 60000)
tats = 3 + 1.6*r1
markers.append(20)
colors.append(2.1)
classLabel = 1 #'didntLike'
xcord1.append(fFlyer); ycord1.append(tats)
elif ((myClass > 0.16) and (myClass <= 0.33)):
fFlyer = 6000*r0 + 70000
tats = 10 + 3*r1 + 2*r0
markers.append(20)
colors.append(1.1)
classLabel = 1 #'didntLike'
if (tats < 0): tats =0
if (fFlyer < 0): fFlyer =0
xcord1.append(fFlyer); ycord1.append(tats)
elif ((myClass > 0.33) and (myClass <= 0.66)):
fFlyer = 5000*r0 + 10000
tats = 3 + 2.8*r1
markers.append(30)
colors.append(1.1)
classLabel = 2 #'smallDoses'
if (tats < 0): tats =0
if (fFlyer < 0): fFlyer =0
xcord2.append(fFlyer); ycord2.append(tats)
else:
fFlyer = 10000*r0 + 35000
tats = 10 + 2.0*r1
markers.append(50)
colors.append(0.1)
classLabel = 3 #'largeDoses'
if (tats < 0): tats =0
if (fFlyer < 0): fFlyer =0
xcord3.append(fFlyer); ycord3.append(tats)
fw.close()
fig = plt.figure()
ax = fig.add_subplot(111)
#ax.scatter(xcord,ycord, c=colors, s=markers)
type1 = ax.scatter(xcord1, ycord1, s=20, c='red')
type2 = ax.scatter(xcord2, ycord2, s=30, c='green')
type3 = ax.scatter(xcord3, ycord3, s=50, c='blue')
ax.legend([type1, type2, type3], ["Did Not Like", "Liked in Small Doses", "Liked in Large Doses"], loc=2)
ax.axis([-5000,100000,-2,25])
plt.xlabel('Frequent Flyier Miles Earned Per Year')
plt.ylabel('Percentage of Time Spent Playing Video Games')
plt.show()
def autoNorm(dataSet):
"""
Function: 归一化特征值
Args: dataSet:训练验本矩阵
Returns: normDataSet:归一化矩阵
ranges:每一列的差值
minVals:每一列的最小值
"""
#求取列的最小值
minVals = dataSet.min(0)
#求取列的最大值
maxVals = dataSet.max(0)
#最大值与最小值做差
ranges = maxVals - minVals
#创建输出矩阵normDataSet
normDataSet = zeros(shape(dataSet))
#m设定为矩阵dataSet的行数
m = dataSet.shape[0]
#对矩阵dataSet每个元素求差
normDataSet = dataSet - tile(minVals, (m, 1))
#对矩阵dataSet每个元素归一化
normDataSet = normDataSet/tile(ranges, (m, 1))
#返回归一化矩阵、差值向量和最小值向量
return normDataSet, ranges, minVals
def datingClassTest():
"""
Function: 分类器测试代码
Args: 无
Returns: classifierResult:分类器分类结果
datingLabels[i]:真实结果
errorCount:分类误差
"""
#测试集比例设定:10%
hoRatio = 0.1
#从文本文件中解析数据
datingDataMat, datingLabels = file2matrix(inputfile)
#归一化特征值
normMat, ranges, minVals = autoNorm(datingDataMat)
#计算normMat矩阵行数并赋值给m
m = normMat.shape[0]
#初始化测试向量个数
numTestVecs = int(m*hoRatio)
#初始化错误计数
errorCount = 0.0
#对测试集分类,返回分类结果并打印
for i in range(numTestVecs):
#传参给分类器进行分类,每个for循环改变的参数只有第一项的测试数据而已
classifierResult = classify_knn(normMat[i,:], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
#打印当前测试数据的分类结果个真实结果
print("the classfier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))
#如果分类结果不等于真是结果,错误计数加一
if (classifierResult != datingLabels[i]): errorCount += 1.0
#输出测试错误率
print("the total error rate is: %f" % (errorCount/float(numTestVecs)))
#输出测试错误数
print(errorCount)
def classifyPerson():
"""
Function: 约会网站测试函数
Args: percentTats:玩视频游戏消耗时间百分比
ffMiles:每年获得的飞行常客里程数
iceCream:每周消费的冰淇淋公升数
Returns: resultList:可交往程度
"""
#建立输出列表
resultList = ['not at all', 'in small doses', 'in large doses']
#读取键盘输入的数值
percentTats = float(input("percentage of time spent playing video games?"))
ffMiles = float(input("frequent flier miles earned per year?"))
iceCream = float(input("liters of ice cream consumed per week?"))
#从文本文件中解析数据
datingDataMat, datingLabels = file2matrix(inputfile)
#归一化特征值
normMat, ranges, minVals = autoNorm(datingDataMat)
#将先前读取的键盘输入填入数组
inArr = array([ffMiles, percentTats, iceCream])
#分类:这里也对输入数据进行了归一化
classifierResult = classify_knn((inArr - minVals) / ranges, normMat, datingLabels, 3)
#打印分类信息
print("You wil probably like this person: ", resultList[classifierResult - 1])
if __name__ == '__main__':
#print("datingDataMat",datingDataMat)
#knn_scatter(inputfile)
#datingClassTest()
classifyPerson()