机器学习实战_KNN(一)
阅读原文时间:2023年07月11日阅读:1

KNN 即 k_近邻算法(k- nearest neighbor) ,就是寻找K个邻居作为该样本的特征,近朱者赤,近墨者黑,你的邻居是什么特征,那么就认为你也具备该特征;核心公式为:

数据来源:https://github.com/apachecn/AiLearning/blob/master/data/2.KNN/datingTestSet2.txt

读取数据转换成矩阵

# 提取文件中的数据 转换成矩阵
def file2matric(filename):
"""
disc:
param: filename: 导入数据文本
return: 数据矩阵
"""
f = open(filename,'r',encoding= 'utf-8')
# 获取文件的行数
lines_list = f.readlines()
num_of_lines = len(lines_list)
# 创建存放标签的列表
class_label_list = []
# 生成对应的空矩阵 zeros(2,3) 就是生成2行3列的0矩阵
returnMat = np.zeros((num_of_lines,3))
# 将文本中的数据放到矩阵中
for i in range(num_of_lines):
lines = lines_list[i].strip().split('\t')
# 将文本中的前3个数据放到矩阵中
returnMat[i,:] = lines[0:3]
# 将标签存到列表中
class_label_list.append(int(lines[-1]))

# print(returnMat)  
return returnMat, class\_label\_list

利用 matplotlib 绘制散点图

def DrawScatter(dataMat,label_list):
# 导入中文字体,及字体大小
zhfont = FontProperties(fname='C:/Windows/Fonts/simsun.ttc', size=14)
# 绘制绘图窗口 2行2列
fig,ax = plt.subplots(2,2,figsize=(13,8))
# 不同标签赋予不同颜色
label_color = []
for i in label_list:
if i == 1:
label_color.append('black')
elif i == 2:
label_color.append('orange')
elif i == 3:
label_color.append('red')
# 开始绘制散点图 设定散点尺寸与透明度
scatter_size = 12
scatter_alpha = 0.5
# ===================散点图========================
ax[0][0].scatter(dataMat[:,0], dataMat[:,1],color = label_color,s = scatter_size ,alpha = scatter_alpha)
ax[0][1].scatter(dataMat[:, 1], dataMat[:, 2], color=label_color, s=scatter_size , alpha=scatter_alpha)
ax[1][0].scatter(dataMat[:, 0], dataMat[:, 2], color=label_color, s=scatter_size , alpha=scatter_alpha)

# 坐标轴标题  
title\_list = \['每年获得的飞行常客里程数和玩视频游戏所消耗时间占比',  
              '每年获得的飞行常客里程数和每周消费的冰激淋公升数',  
              '玩视频游戏所消耗时间占比和每周消费的冰激淋公升数'\]  
x\_name\_list = \['每年获得的飞行常客里程数','玩视频游戏所消耗时间占比','每周消费的冰激淋公升数'\]  
y\_name\_list = \['玩视频游戏所消耗时间占比','每周消费的冰激淋公升数','每年获得的飞行常客里程数'\]  
#设置图例  
didntLike = mlines.Line2D(\[\], \[\], color='black', marker='.',  
                  markersize=6, label='didntLike')  
smallDoses = mlines.Line2D(\[\], \[\], color='orange', marker='.',  
                  markersize=6, label='smallDoses')  
largeDoses = mlines.Line2D(\[\], \[\], color='red', marker='.',  
                  markersize=6, label='largeDoses')

p = 0  
for i in range(2):  
    for j in range(2):  
        if p > 2:  
            break  
        # 设置坐标轴名称和标题  
        plt.setp(ax\[i\]\[j\].set\_title(u'%s'%(title\_list\[p\]),FontProperties = zhfont),size=9, weight='bold', color='red')  
        plt.setp(ax\[i\]\[j\].set\_xlabel(u'%s'%(x\_name\_list\[p\]),FontProperties = zhfont), size=7, weight='bold', color='black')  
        plt.setp(ax\[i\]\[j\].set\_ylabel(u'%s'%(y\_name\_list\[p\]),FontProperties = zhfont), size=7, weight='bold', color='black')  
        p+=1  
# 添加图例  
ax\[0\]\[0\].legend(handles=\[didntLike, smallDoses, largeDoses\])  
ax\[0\]\[1\].legend(handles=\[didntLike, smallDoses, largeDoses\])  
ax\[1\]\[0\].legend(handles=\[didntLike, smallDoses, largeDoses\])

plt.show()

对数据进行归一化处理

由于不同数据的范围波动不同,在权重一样的情况下,需要进行归一化,即将数据转换成0-1之间

# 对矩阵进行归一化处理
def dataNorm(dataMat):
"""
:param dataMat:
:return: 归一化后的数据集
归一化公式: Y = (X - Xmin)/(Xmax - Xmin)
"""
# max(0) min(0) 求出每列的最大值和最小值
d_min = dataMat.min(0)
d_max = dataMat.max(0)
# 计算极差
d_ranges = d_max - d_min
# 创建输出矩阵
normDataSet = np.zeros(np.shape(dataMat))
print(normDataSet)
# 获得矩阵行数 .shape 获取矩阵的大小 3x3
m = dataMat.shape[0]
# 计算 (X - Xmin) 这部分 首先要创建Xmin矩阵 将d_min扩展到m行
# 需要使用np.tile 函数进行扩展 将d_min扩展成m行1列 变成m x 3 矩阵
normDataSet = dataMat - np.tile(d_min,(m,1))
print(normDataSet)
# 计算Y
normDataSet = normDataSet / np.tile(d_ranges,(m,1))

print(normDataSet)  
return normDataSet

创建分类函数与分类器(kNN算法的实现)

(ps:每次都需要将测试数据与所有训练数据进行对比,感觉比较繁琐)

def classfy_fun(test_data, train_data, labels, k):
"""

:param test\_data: 测试集  
:param train\_data: 训练集  
:param labels: 训练集标签  
:param k:  KNN 算法参数 选择距离最小的个数  
:return:  分类结果  
"""  
# 计算训练集的矩阵行数  
train\_size = train\_data.shape\[0\]  
# 接下来按照欧氏距离进行元素距离计算 公式  
#     将测试集扩充成与训练集相同行数 求差  
diffMat = np.tile(test\_data,(train\_size,1)) - train\_data  
# 将差值矩阵的每个元素平方  
sq\_diffMat = diffMat\*\*2  
# 差值平方矩阵每行元素相加 axis = 1 是按行相加  
sum\_diffMat = sq\_diffMat.sum(axis = 1)  
# 对新的求和矩阵进行开方 得到距离值  
distances = sum\_diffMat \*\* 0.5  
# 获得距离值中从小到大值的索引  
sorted\_distant = distances.argsort()  
# 定义一个字典 存放标签 与 出现的数量  
class\_count = {}  
for i in range(k):  
    # 找出前k个距离值最小的对应标签  
    temp\_label = labels\[sorted\_distant\[i\]\]  
    # 将标签作为 key 存放到字典中 出现次数作为 value  
    class\_count\[temp\_label\] = class\_count.get(temp\_label,0) + 1  
# 将字典按照value 大小进行排序  
sort\_class\_count = sorted(class\_count.items(),key = operator.itemgetter(1))  
return sort\_class\_count\[0\]\[0\]  
pass  

创建分类器函数

def dating_class_test():

# 首先获取文件,将文件分成测试集和训练集  
dating\_Mat, dating\_label = file2matric('datingdata.txt')  
# 设置测试集的比例  
test\_ratio = 0.1  
# 数据归一化  
normMat = dataNorm(dating\_Mat)  
#获得矩阵的行数  
m = normMat.shape\[0\]  
# 计算测试集的数量  
numTestData = int(m \* test\_ratio)  
# 错误分类的数量  
error\_count = 0.0

for i in range(numTestData):  
    class\_result = classfy\_fun(dating\_Mat\[i,:\], dating\_Mat\[numTestData:m,:\],  
                               dating\_label\[numTestData:m\],4 )  
    print("分类结果:%s,实际分类:%s"%(class\_result,dating\_label\[i\]))  
    if class\_result != dating\_label\[i\]:  
        error\_count += 1  
# print("错误识别的数量:%f" %error\_count)  
print("正确率:%f%% \\n" %((1 - error\_count / numTestData)\*100))

从结果看 识别率还是很低的,目前k值为4 ,可以改变k值看看正确率的变化

完整代码

#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
【KNN 实战】

"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
from matplotlib.font_manager import FontProperties
import operator

# 提取文件中的数据 转换成矩阵
def file2matric(filename):
"""
disc:
param: filename: 导入数据文本
return: 数据矩阵
"""
f = open(filename,'r',encoding= 'utf-8')
# 获取文件的行数
lines_list = f.readlines()
num_of_lines = len(lines_list)
# 创建存放标签的列表
class_label_list = []
# 生成对应的空矩阵 zeros(2,3) 就是生成2行3列的0矩阵
returnMat = np.zeros((num_of_lines,3))
# 将文本中的数据放到矩阵中
for i in range(num_of_lines):
lines = lines_list[i].strip().split('\t')
# 将文本中的前3个数据放到矩阵中
returnMat[i,:] = lines[0:3]
# 将标签存到列表中
class_label_list.append(int(lines[-1]))

 # print(returnMat)  
 return returnMat, class\_label\_list

def DrawScatter(dataMat,label_list):
# 导入中文字体,及字体大小
zhfont = FontProperties(fname='C:/Windows/Fonts/simsun.ttc', size=14)
# 绘制绘图窗口 2行2列
fig,ax = plt.subplots(2,2,figsize=(13,8))
# 不同标签赋予不同颜色
label_color = []
for i in label_list:
if i == 1:
label_color.append('black')
elif i == 2:
label_color.append('orange')
elif i == 3:
label_color.append('red')
# 开始绘制散点图 设定散点尺寸与透明度
scatter_size = 12
scatter_alpha = 0.5
# ===================散点图========================
ax[0][0].scatter(dataMat[:,0], dataMat[:,1],color = label_color,s = scatter_size ,alpha = scatter_alpha)
ax[0][1].scatter(dataMat[:, 1], dataMat[:, 2], color=label_color, s=scatter_size , alpha=scatter_alpha)
ax[1][0].scatter(dataMat[:, 0], dataMat[:, 2], color=label_color, s=scatter_size , alpha=scatter_alpha)

 # 坐标轴标题  
 title\_list = \['每年获得的飞行常客里程数和玩视频游戏所消耗时间占比',  
               '每年获得的飞行常客里程数和每周消费的冰激淋公升数',  
               '玩视频游戏所消耗时间占比和每周消费的冰激淋公升数'\]  
 x\_name\_list = \['每年获得的飞行常客里程数','玩视频游戏所消耗时间占比','每周消费的冰激淋公升数'\]  
 y\_name\_list = \['玩视频游戏所消耗时间占比','每周消费的冰激淋公升数','每年获得的飞行常客里程数'\]  
 #设置图例  
 didntLike = mlines.Line2D(\[\], \[\], color='black', marker='.',  
                   markersize=6, label='didntLike')  
 smallDoses = mlines.Line2D(\[\], \[\], color='orange', marker='.',  
                   markersize=6, label='smallDoses')  
 largeDoses = mlines.Line2D(\[\], \[\], color='red', marker='.',  
                   markersize=6, label='largeDoses')

 p = 0  
 for i in range(2):  
     for j in range(2):  
         if p > 2:  
             break  
         # 设置坐标轴名称和标题  
         plt.setp(ax\[i\]\[j\].set\_title(u'%s'%(title\_list\[p\]),FontProperties = zhfont),size=9, weight='bold', color='red')  
         plt.setp(ax\[i\]\[j\].set\_xlabel(u'%s'%(x\_name\_list\[p\]),FontProperties = zhfont), size=7, weight='bold', color='black')  
         plt.setp(ax\[i\]\[j\].set\_ylabel(u'%s'%(y\_name\_list\[p\]),FontProperties = zhfont), size=7, weight='bold', color='black')  
         p+=1  
 # 添加图例  
 ax\[0\]\[0\].legend(handles=\[didntLike, smallDoses, largeDoses\])  
 ax\[0\]\[1\].legend(handles=\[didntLike, smallDoses, largeDoses\])  
 ax\[1\]\[0\].legend(handles=\[didntLike, smallDoses, largeDoses\])  
 plt.savefig('.\\\\123.png', bbox\_inches='tight')  
 plt.show()

# 对矩阵进行归一化处理
def dataNorm(dataMat):
"""
:param dataMat:
:return: 归一化后的数据集
归一化公式: Y = (X - Xmin)/(Xmax - Xmin)
"""
# max(0) min(0) 求出每列的最大值和最小值
d_min = dataMat.min(0)
d_max = dataMat.max(0)
# 计算极差
d_ranges = d_max - d_min
# 创建输出矩阵
normDataSet = np.zeros(np.shape(dataMat))
print(normDataSet)
# 获得矩阵行数 .shape 获取矩阵的大小 3x3
m = dataMat.shape[0]
# 计算 (X - Xmin) 这部分 首先要创建Xmin矩阵 将d_min扩展到m行
# 需要使用np.tile 函数进行扩展 将d_min扩展成m行1列 变成m x 3 矩阵
normDataSet = dataMat - np.tile(d_min,(m,1))
print(normDataSet)
# 计算Y
normDataSet = normDataSet / np.tile(d_ranges,(m,1))

 print(normDataSet)  
 return normDataSet

def classfy_fun(test_data, train_data, labels, k):
"""

 :param test\_data: 测试集  
 :param train\_data: 训练集  
 :param labels: 训练集标签  
 :param k:  KNN 算法参数 选择距离最小的个数  
 :return:  分类结果  
 """  
 # 计算训练集的矩阵行数  
 train\_size = train\_data.shape\[0\]  
 # 接下来按照欧氏距离进行元素距离计算 公式  
 #     将测试集扩充成与训练集相同行数 求差  
 diffMat = np.tile(test\_data,(train\_size,1)) - train\_data  
 # 将差值矩阵的每个元素平方  
 sq\_diffMat = diffMat\*\*2  
 # 差值平方矩阵每行元素相加 axis = 1 是按行相加  
 sum\_diffMat = sq\_diffMat.sum(axis = 1)  
 # 对新的求和矩阵进行开方 得到距离值  
 distances = sum\_diffMat \*\* 0.5  
 # 获得距离值中从小到大值的索引  
 sorted\_distant = distances.argsort()  
 # 定义一个字典 存放标签 与 出现的数量  
 class\_count = {}  
 for i in range(k):  
     # 找出前k个距离值最小的对应标签  
     temp\_label = labels\[sorted\_distant\[i\]\]  
     # 将标签作为 key 存放到字典中 出现次数作为 value  
     class\_count\[temp\_label\] = class\_count.get(temp\_label,0) + 1  
 # 将字典按照value 大小进行排序  
 sort\_class\_count = sorted(class\_count.items(),key = operator.itemgetter(1))  
 return sort\_class\_count\[0\]\[0\]  
 pass  

# 创建分类器函数
def dating_class_test():

 # 首先获取文件,将文件分成测试集和训练集  
 dating\_Mat, dating\_label = file2matric('datingdata.txt')  
 # 设置测试集的比例  
 test\_ratio = 0.1  
 # 数据归一化  
 normMat = dataNorm(dating\_Mat)  
 #获得矩阵的行数  
 m = normMat.shape\[0\]  
 # 计算测试集的数量  
 numTestData = int(m \* test\_ratio)  
 # 错误分类的数量  
 error\_count = 0.0

 for i in range(numTestData):  
     class\_result = classfy\_fun(dating\_Mat\[i,:\], dating\_Mat\[numTestData:m,:\],  
                                dating\_label\[numTestData:m\],4 )  
     print("分类结果:%s,实际分类:%s"%(class\_result,dating\_label\[i\]))  
     if class\_result != dating\_label\[i\]:  
         error\_count += 1  
 # print("错误识别的数量:%f" %error\_count)  
 print("正确率:%f%% \\n" %((1 - error\_count / numTestData)\*100))

def main():
# reMat, label = file2matric('datingdata.txt')
# DrawScatter(reMat,label )
# dataNorm(reMat)
# 测试分类情况
dating_class_test()
pass

if __name__ =='__main__':
main()