zoukankan      html  css  js  c++  java
  • 人工智能--第二天--KNN算法实现

    一、案例说明

      有一份数据,公司让你写一个程序,他告诉我们这个测试数据的的已知条件,我们告诉他这个数据应该在哪一个类别

              

    二、主要构思

      1.如何分析该项目,思路产生的过程  

        1. 不同类别数据之间有没有区别? 假设有

        2. 如果有区别,区别在哪? 和年龄,长相等有没有关系?没有关系 区别在工资,淘宝,电视时间

        3. 区别在于特征的值 很难直接找到一个或者多个确定的条件来判断数据的类别

        4. 能不能找到待预测的数据比较接近的人

           如何判断两个样本之间的距离 使用欧式距离

        5. 找到距离最近的前k个人    5

        6. 统计前k个样本中,出现次数最多的那个类别作为待遇测数据的类别

      2.详细设计 

        1. 读取数据,分离出特征矩阵和目标向量

        2. 归一化特征矩阵:最大最小值归一化

        3. 计算新样本到所有样本之间的距离 计算方式使用欧式距离或者曼哈顿距离

          

        4. 找到距离新样本最近的前k个样本 k值的选择凭经验 3-15个 最好是奇数

        5. 统计出现次数最多的那个类别

        6. 将该类别作为新样本的类别输出

      3.每个功能的代码实现--Python

       1.load方法:读取数据,并分离出特征矩阵和目标向量

     1     def load(filename, sep):
     2         """
     3         读取元数据,并分离特征矩阵和目标向量
     4         :param filename: 源文件路径
     5         :return: Feature_matrix,aims_vector 特征矩阵 目标向量
     6         """
     7         with open(filename, "r")as f:
     8             res = f.readlines()
     9         # 先用strip去除
    再用	分离
    10         res = [i.strip().split(sep) for i in res]
    11         ##切割特征矩阵和目标向量 一般来说目标向量都在最后一列
    12         # 目标向量
    13         aims_vector = [i[-1] for i in res]
    14         # 特征矩阵
    15         Feature_matrix = [i[:-1] for i in res]
    16         return Feature_matrix, aims_vector

          2.return_only方法:归一化

     1     def return_only(X):
     2         ###方式一:不使用转置 ,直接改变原矩阵
     3         for i in range(len(X[0])):
     4             column = [float(x[i]) for x in X]
     5             max_c, min_c = max(column), min(column)
     6             # 改变原矩阵
     7             for j in X:
     8                 j[i] = (float(j[i]) - min_c) / (max_c - min_c)
     9         return X
    10         ###方式二:使用矩阵的转置
    11         # new_X = []
    12         # for i in range(len(X[0])):
    13         #     col = [float(x[i]) for x in X]
    14         #     max_col, min_col = max(col), min(col)
    15         #     new_col = [(c - min_col) / (max_col - min_col) for c in col]
    16         #     new_X.append(new_col)
    17         # # 外层是转置后的行数,内层是转置后列数
    18         # # 双层循环 内层在第一个 外层在第二个
    19         # return [[new_X[i][j] for i in range(len(new_X))] for j in range(len(new_X[0]))]

          3.class_ify方法 :KNN分类器

     1     def class_ify( X, Y, x):
     2         """
     3         分类器
     4         :param X:特征矩阵
     5         :param Y:目标向量
     6         :param x:测试数据
     7         :return:测试数据的类别
     8         """
     9         # 对测试数据x的容错处理
    10         x_ = list(x)
    11         x_1 = [float(i) for i in x_]
    12         # 收集结果的列表
    13         res_li = []
    14         # X是二位列表双层循环,第一层(外层)循环每一行,第二层(内层)循环列数,计算欧式距离
    15         for i in X:
    16             dis_sum = 0
    17             for j in range(len(i)):
    18                 # 计算每个i与x的距离
    19                 dis_sum += (i[j] - x_1[j]) ** 2
    20             dis_sum = dis_sum ** 0.5
    21             # 得到目标的结果列表,该列表和Y列表一一对应,因为X,Y,是对应的,res_li是x按位计算得来的,所以和Y也是对应的
    22             res_li.append(dis_sum)
    23         # 排序,取k个值
    24         res_li1 = sorted(res_li)[:self.k]
    25         # 获得前k个值对应的结果列表
    26         # 排序后原来的对应关系被打乱,根据原来的res_li获得的index即为现在需要的index
    27         res_y = [Y[res_li.index(i)] for i in res_li1]
    28         # 内置方法:统计序列中相同个元素出现的个数,返回以元素,个数组合成的元组列表
    29         return Counter(res_y).most_common(1)[0][0]

         4.score方法:准确率计算

     1     def score(train_X, train_Y, test_X, test_Y):
     2         """
     3         做准确率的计算
     4         :param train_X: 训练的特征矩阵
     5         :param train_Y: 训练的目标向量
     6         :param test_X:  测试的特征举证
     7         :param test_Y:  测试的目标向量
     8         :return: 测试后准确率
     9         """
    10         count = 0
    11         for index, value in enumerate(test_X):
    12             test_i = class_ify(train_X, train_Y, value)
    13             if test_i == test_Y[index]:
    14                 count += 1
    15         return format(count / len(test_X), ".2%")

      5.作为第四个方法的工具:随机分配训练集和测试集

     1     def X_split(X, Y, split_size):
     2         """
     3         根据split_size 随机分割X,Y
     4         :param X: 特征矩阵
     5         :param Y: 目标向量
     6         :param split_size: 分割系数
     7         :return: 训练集和测试集 (每个集合包括特征矩阵和目标向量)
     8         """
     9         test_X, test_Y = [], []
    10         while len(test_X) <= len(Y) * split_size:
    11             index = random.choice(range(len(X)))
    12             test_X.append(X.pop(index))
    13             test_Y.append(Y.pop(index))
    14         return X, Y, test_X, test_Y

    三、封装KNN框架

      以上的代码可重用性还是很差,还是有冗余代码,所以我们要自己封装一个可重用性高,代码简洁的小框架。

      参考request框架:实现多个功能,但每个功能的底层都是一样的

      1.封装思路

        KNN类: 

        属性:k 

        方法:

        分类器 参数:特征矩阵,目标向量,测试数据
        准确率计算 参数:训练集,测试集

        Util类
        方法
        读取源数据,参数:filename,sep

        特征归一化 参数:特征矩阵

        训练集,测试集分割 参数:特征矩阵,目标向量,随机比例

       以上只是我们参考前面的代码就能想出来的俩个类及其属性、方法

       但是我们还要想一下,那就是我们的分类器是基于欧氏距离计算的,那要是别人想要用曼哈顿距离,余弦距离或者自定义的分类器呢,怎么办?

       避免硬编码,我们需要向用户提供可重写的class_ify方法,但是根据原来的构思KNN类中还有准确率计算的方法,所以这个解决方法就是我们再写一个基类,来让KNN继承这个类

       把准确率的算法放到基类中即可,下面是代码:

    Base类和KNN类

     1 class Base():
     2     # 该方法必须让子类重写
     3     def class_ify(self, X, x, Y):
     4         raise ValueError('class_ify方法必须重写!!!')
     5 
     6     # 问题一、如何让这个方法不能重写
     7     def score(self, train_X, train_Y, test_X, test_Y):
     8         """
     9         做准确率的计算
    10         :param train_X: 训练的特征矩阵
    11         :param train_Y: 训练的目标向量
    12         :param test_X:  测试的特征举证
    13         :param test_Y:  测试的目标向量
    14         :return: 测试后准确率
    15         """
    16         count = 0
    17         for index, value in enumerate(test_X):
    18             test_i = self.class_ify(train_X, train_Y, value)
    19             if test_i == test_Y[index]:
    20                 count += 1
    21         return format(count / len(test_X), ".2%")
    22 
    23 
    24 class KNN(Base):
    25     def __init__(self, k):
    26         self.k = k
    27 
    28     def class_ify(self, X, Y, x):
    29         """
    30         分类器
    31         :param X:特征矩阵
    32         :param Y:目标向量
    33         :param x:测试数据
    34         :return:测试数据的类别
    35         """
    36         # 对测试数据x的容错处理
    37         x_ = list(x)
    38         x_1 = [float(i) for i in x_]
    39         # 收集结果的列表
    40         res_li = []
    41         # X是二位列表双层循环,第一层(外层)循环每一行,第二层(内层)循环列数,计算欧式距离
    42         for i in X:
    43             dis_sum = 0
    44             for j in range(len(i)):
    45                 # 计算每个i与x的距离
    46                 dis_sum += (i[j] - x_1[j]) ** 2
    47             dis_sum = dis_sum ** 0.5
    48             # 得到目标的结果列表,该列表和Y列表一一对应,因为X,Y,是对应的,res_li是x按位计算得来的,所以和Y也是对应的
    49             res_li.append(dis_sum)
    50         # 排序,取k个值
    51         res_li1 = sorted(res_li)[:self.k]
    52         # 获得前k个值对应的结果列表
    53         # 排序后原来的对应关系被打乱,根据原来的res_li获得的index即为现在需要的index
    54         res_y = [Y[res_li.index(i)] for i in res_li1]
    55         # 内置方法:统计序列中相同个元素出现的个数,返回以元素,个数组合成的元组列表
    56         return Counter(res_y).most_common(1)[0][0]

    Util 工具类

     1 class Util():
     2     def load(self, filename, sep):
     3         """
     4         读取元数据,并分离特征矩阵和目标向量
     5         :param filename: 源文件路径
     6         :return: Feature_matrix,aims_vector 特征矩阵 目标向量
     7         """
     8         with open(filename, "r")as f:
     9             res = f.readlines()
    10         # 先用strip去除
    再用	分离
    11         res = [i.strip().split(sep) for i in res]
    12         ##切割特征矩阵和目标向量 一般来说目标向量都在最后一列
    13         # 目标向量
    14         aims_vector = [i[-1] for i in res]
    15         # 特征矩阵
    16         Feature_matrix = [i[:-1] for i in res]
    17         return Feature_matrix, aims_vector
    18 
    19     def return_only(self, X):
    20         ###方式一:不使用转置 ,直接改变原矩阵
    21         for i in range(len(X[0])):
    22             column = [float(x[i]) for x in X]
    23             max_c, min_c = max(column), min(column)
    24             # 改变原矩阵
    25             for j in X:
    26                 j[i] = (float(j[i]) - min_c) / (max_c - min_c)
    27         return X
    28         ###方式二:使用矩阵的转置
    29         # new_X = []
    30         # for i in range(len(X[0])):
    31         #     col = [float(x[i]) for x in X]
    32         #     max_col, min_col = max(col), min(col)
    33         #     new_col = [(c - min_col) / (max_col - min_col) for c in col]
    34         #     new_X.append(new_col)
    35         # # 外层是转置后的行数,内层是转置后列数
    36         # # 双层循环 内层在第一个 外层在第二个
    37         # return [[new_X[i][j] for i in range(len(new_X))] for j in range(len(new_X[0]))]
    38 
    39     def X_split(self, X, Y, split_size):
    40         """
    41         根据split_size 随机分割X,Y
    42         :param X: 特征矩阵
    43         :param Y: 目标向量
    44         :param split_size: 分割系数
    45         :return: 训练集和测试集 (每个集合包括特征矩阵和目标向量)
    46         """
    47         test_X, test_Y = [], []
    48         while len(test_X) <= len(Y) * split_size:
    49             index = random.choice(range(len(X)))
    50             test_X.append(X.pop(index))
    51             test_Y.append(Y.pop(index))
    52         return X, Y, test_X, test_Y

      为了封装的更加彻底我们加上一执行类,保证类的完整性

     1 class Cmx():
     2     # 面向客户的执行类
     3     def __init__(self, result_filename, k=3, knn_cls=None):
     4         """
     5         :param result_filename: 源数据文件
     6         :param knn_cls: knn分类器cls,不指定使用默认值
     7         :param k: knn的选择前几个 3,5,7 奇数
     8         :return:
     9         """
    10         self.result_filename = result_filename
    11         self.k = k
    12         self.knn_cls = knn_cls if knn_cls else KNN
    13 
    14     def first_handler(self, sep):
    15         # 实例化工具类 对源文件加工处理
    16         util = Util()
    17         X, Y = util.load(self.result_filename, sep)
    18         # 最大最小归一化
    19         X = util.return_only(X)
    20         return X, Y
    21 
    22     # 计算测试数据的所属类
    23     def check_class(self, x, sep='	'):
    24         """
    25         :param x:待测试的数据
    26         :param sep: 特征矩阵和目标向量的 分隔符
    27         :param split_size: 训练集和测试的随机分类比例 【0-1】
    28         :return: 目标的所属类
    29         """
    30         X, Y = self.first_handler(sep)
    31         # train_X, train_Y, test_X, test_Y = util.X_split(X, Y,split_size)
    32         # 开始分类
    33         return self.knn_cls(self.k).class_ify(X, Y, x)
    34 
    35     # 计算准确率
    36     def Accuracy(self, split_size=0.2, sep='	'):
    37         X, Y = self.first_handler(sep)
    38         # 随机分配训练集和测试集
    39         train_X, train_Y, test_X, test_Y = Util().X_split(X, Y, split_size)
    40         res_sc = self.knn_cls(self.k).score(train_X, train_Y, test_X, test_Y)
    41         return res_sc

      测试代码:

    1     result_filename = "【你的文件路径】dating.txt"
    2     cmx = Cmx(result_filename)
    3     ##获得所属类
    4     print(cmx.check_class((74676,14.445740)))
    5     ##获得准确率
    6     print(cmx.Accuracy(split_size=0.6))
    7     print(cmx.Accuracy(split_size=0.6))
    8     print(cmx.Accuracy(split_size=0.6))

      

  • 相关阅读:
    常用搜索指令
    chrome浏览器常用快捷键
    倒排文档
    hdu4570Multi-bit Trie (间隙DP)
    HTTP工作原理
    腾讯和58都市“聘请”秘诀是什么?
    Atitit。团队建设--管理最佳实践--如何留住关键人才,防止人才外流 ??
    于Eclipse传导C/C++配置方法开发(20140721新)
    通过京东淘宝的技术发展和技术演进,探索未来的技术和体系结构
    C++ Primer 学习笔记_41_STL实践与分析(15)--先来看看算法【下一个】
  • 原文地址:https://www.cnblogs.com/cmxbky1314/p/12349915.html
Copyright © 2011-2022 走看看