算法原理
给定一个已知标签类别的训练数据集,输入没有标签的新数据后,在训练数据集中找到与新数据最邻近的k个实例,如果这k个实例的多数属于这个类别,那么新数据就属于这个类别。
k-近邻算法步骤如下:
计算已知类别数据集中的点与当前点之间的距离;
按照距离递增次序排序;
选取与当前点距离最小的k个点;
确定前k个点所在类别的出现频率;
返回前k个点所出现频率最高的类别作为当前点的预测分类。
k近邻法中,当训练集,距离度量(如欧氏距离),k值的选择及分类决策规则(如多数表决)确定后,队友任何一个新的输入实例,它所属的类唯一的确定。这相当于根据上述要素将特征空间划分为一些子空间,确定子控件中的每个点所述的类。
距离度量
- 欧式距离(P=2)
- 曼哈顿距离(P=1)
- 由不同的距离度量所确定的最近邻点是不同的
K值的选择
- 选择较小的k值,相当于用较小的邻域中的训练实例进行预测,学习的近似误差会减小,只有与输入实例较近的训练实例才对预测结果起作用,缺点是估计误差会增大,对邻近的点非常敏感,k越小,模型越复杂,容易过拟合。
- 如果k=N,那么无论输入实例是什么,都将简单的预测它属于在训练集中最多的类,这时的模型过于简单,完全忽视了训练实例中大量有用的信息
- 在应用中,k值一般去较小的值,通常采用交叉验证法来确定
K近邻的实现方法
- 线性扫描:计算输入实例与每一个训练实例的距离,当训练集很大时,计算非常耗时
- kd树方法:使用特征的结构存储训练数据,以减少计算距离的次数,提高k近邻搜索的效率。kd树是二叉树,表示对k维空间的一个划分,其每个结点对应于k为空间划分中的一个超矩形区域。
示例:根据接吻镜头数和打斗镜头数预测电影类型
import pandas as pd
"""
函数功能:KNN分类器-预测电影类型
参数说明:
inx:需要预测分类的数据集
dataSet:训练数据集
k:选择距离最小的k个点
返回:result:预测结果
日期:2019-7-30
"""
#构建数据集
rowdata={'接吻镜头':[101,89,97,5,9,8],
'打斗镜头':[1,5,12,108,112,115],
'电影名称':['无问西东','后来的我们','前任3','红海行动','唐人街探案','战狼2'],
'电影类型':['爱情片','爱情片','爱情片','动作片','动作片','动作片']}
movie_data = pd.DataFrame(rowdata)
new_data = [24, 67]
dataSet = movie_data
k=4
def classify0(new_data, dataSet, k):
result = []
#dist = list((((dataSet.iloc[:6, 0:2] - new_data) ** 2).sum(1)) ** 0.5)
#loc按列或行名字索引;iloc按行或列序号索引
dist = list((((dataSet.loc[:6, ['打斗镜头','接吻镜头']] - new_data) ** 2).sum(1)) ** 0.5)
#sum()默认为0按行求和,sum(1)为按列求和 列表list中元素的类型可以不同
dist_l = pd.DataFrame({'dist': dist, 'labels': (dataSet.iloc[:, 3])})
dr = dist_l.sort_values(by='dist')[:k]
#by:axis轴上的某个索引或索引列表
re = dr.loc[:, 'labels'].value_counts()
result.append(re.index[0])
return result
print(classify0(new_data,dataSet,k))
示例:约会网站匹配效果预测
#准备数据
datingTest = pd.read_table('datingTestSet.txt',header=None)
#把不同标签用颜色区分
Colors = []
for i in range(datingTest.shape[0]):
m = datingTest.iloc[i,-1]
if m=='didntLike':
Colors.append('black')
if m=='smallDoses':
Colors.append('orange')
if m=='largeDoses':
Colors.append('red')
#绘制两两特征之间的散点图分析数据
plt.rcParams['font.sans-serif']=['Simhei'] #图中字体设置为黑体
pl=plt.figure(figsize=(12,8))
fig1=pl.add_subplot(221)
plt.scatter(datingTest.iloc[:,1],datingTest.iloc[:,2],marker='.',c=Colors)
plt.xlabel('玩游戏视频所占时间比')
plt.ylabel('每周消费冰淇淋公升数')
fig2=pl.add_subplot(222)
plt.scatter(datingTest.iloc[:,0],datingTest.iloc[:,1],marker='.',c=Colors)
plt.xlabel('每年飞行常客里程')
plt.ylabel('玩游戏视频所占时间比')
fig3=pl.add_subplot(223)
plt.scatter(datingTest.iloc[:,0],datingTest.iloc[:,2],marker='.',c=Colors)
plt.xlabel('每年飞行常客里程')
plt.ylabel('每周消费冰淇淋公升数')
plt.show()
可视化效果
"""
函数功能:归一化
参数说明:
dataSet:原始数据集
返回:0-1标准化之后的数据集
"""
def minmax(dataSet):
minDf = dataSet.min()
maxDf = dataSet.max()
normSet = (dataSet - minDf )/(maxDf - minDf)
return normSet
"""
函数功能:切分训练集和测试集
参数说明:
dataSet:原始数据集
rate:训练集所占比例
返回:切分好的训练集和测试集
"""
def randSplit(dataSet,rate=0.9):
n = dataSet.shape[0]
m = int(n*rate)
train = dataSet.iloc[:m,:]
test = dataSet.iloc[m:,:]
test.index = range(test.shape[0]) #重置index
return train,test
#分类器针对于约会网站的测试代码
def datingClass(train,test,k):
n = train.shape[1] - 1
m = test.shape[0]
result = []
for i in range(m):
dist = list((((train.iloc[:, :n] - test.iloc[i, :n]) ** 2).sum(1))**5)
dist_l = pd.DataFrame({'dist': dist, 'labels': (train.iloc[:, n])})
dr = dist_l.sort_values(by = 'dist')[: k]
re = dr.loc[:, 'labels'].value_counts()
result.append(re.index[0])
result = pd.Series(result)
test.insert(loc=0, column='predict',value=result) #经过调试发现把预测结果插入到了0列
acc = (test.iloc[:,-1]==test.iloc[:,0]).mean()
print('模型预测准确率为',acc)
return test
#将归一化后的数据与label按列拼接
datingT = pd.concat([minmax(datingTest.iloc[:, :3]), datingTest.iloc[:,3]], axis=1)
#将数据集分为训练集和测试集
train,test = randSplit(datingT,0.9)
#使用分类器测试
datingClass(train,test,5)
示例:手写数字识别
import os
import pandas as pd
from Levenshtein import hamming
"""
函数功能:KNN分类器-手写数字识别
"""
#准备数据
"""
函数功能:得到标记好的训练集
"""
def get_train():
path='digits/trainingDigits'
trainingFileList = os.listdir(path) #得到path下的所有文件名组成个list
train = pd.DataFrame()
img = []
labels =[]
for i in range(len(trainingFileList)):
filename = trainingFileList[i]
txt = pd.read_csv('digits/trainingDigits/%s'%filename,header=None) #读取filename文件
num = ''
for i in range(txt.shape[0]): #将32*32D的数据转换为1*1024D的数据
num += txt.iloc[i,:]
img.append(num[0])
filelabel = filename.split('_')[0] #用_对文件名进行切分,获取到第0个字符串即为标签
labels.append(filelabel)
train['img'] = img
train['labels'] = labels
return train
"""
函数功能:得到标记好的测试集
"""
def get_test():
path='digits/testDigits'
testFileList = os.listdir(path)
test=pd.DataFrame()
img = []
labels =[]
for i in range(len(testFileList)):
filename = testFileList[i]
txt = pd.read_csv('digits/testDigits/%s'%filename,header=None) #获取目录时有修改:%
num = ''
for i in range(txt.shape[0]):
num += txt.iloc[i,:]
img.append(num[0])
filelabel = filename.split('_')[0]
labels.append(filelabel)
test['img'] = img
test['labels'] = labels
return test
"""
函数功能:k-近邻算法实现手写数字分类
参数说明:
train:训练集
test:测试集
k:k-近邻参数,即选择距离最小的k个点
返回:预测好分类的测试集
"""
def handwritingClass(train,test,k):
n = train.shape[0]
m = test.shape[0]
result = []
for i in range(m):
dist = []
for j in range(n):
d =str(hamming(train.iloc[j,0], test.iloc[i,0])) #hamming 海明距离,计算两个等长字符串之间相异字符的个数
dist.append(d)
dist_l = pd.DataFrame({'dist': dist, 'labels': (train.iloc[:, 1])})
dr = dist_l.sort_values(by = 'dist')[: k]
re = dr.loc[:, 'labels'].value_counts()
result.append(re.index[0])
result = pd.Series(result)
test['predict'] = result
acc = (test.iloc[:,-1]==test.iloc[:,-2]).mean()
print(f'模型预测准确率为{acc}')
return test
train = get_train()
test = get_test()
handwritingClass(train,test,3)