美文网首页
2020-08-10--KNN01

2020-08-10--KNN01

作者: program_white | 来源:发表于2020-08-12 23:38 被阅读0次
  • KNN算法的原理介绍
  • KNN算法的一个简单实现(肿瘤分类)
  • 将KNN算法封装成函数
  • 机器学习套路
  • 使用scikit-learn中的kNN
  • 封装自己的KNN(类)--模拟sklearn库中的KNN算法
  • 判断机器学习算法的性能
  • 分割训练集
  • 封装的分割函数
  • 使用自己编写的分割函数和KNN算法进行一次完整训练
  • 使用sklearn的分割函数和KNN算法进行一次完整训练
  • 分类准确度函数--score

1. KNN算法的原理介绍

1.优点:
  • 思想简单
  • 应用数学知识少
  • 效果好
  • 可以解释机器学习算法使用过程中很多细节问题
  • 更完整的刻画机器学习应用的流程
2.案例:

假设现在设计一个程序判断一个新的肿瘤病人是良性肿瘤还是恶性肿瘤。

1.先基于原有的肿瘤病人的发现时间和肿瘤大小(特征)对应的良性/恶性(值)建立了一张散点图,横坐标是肿瘤大小,纵坐标是发现时间,红色代表良性,蓝色代表恶性,现在要预测的病人的颜色为绿色。

  • 首先需要取一个k值(这个k值的取法后面会介绍),然后找到距离要预测的病人的点(绿点)距离最近的k个点。
  • 然后用第一步中取到的三个点进行投票,比如本例中投票结果就是蓝:红 = 3:0 ,3>0,所以判断这个新病人幻的事恶性肿瘤。

分析:
取k = 3,这出举例绿点最近的三个点进行判断,可知三个点都为恶行肿瘤,所以旅店大概率也为恶行肿瘤。

k的值一般取奇数,奇数个结果中进行判断的结果总会有胜出的一方,偶数的话会打平。

3.本质

如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。

2.KNN算法的一个简单实现(肿瘤分类)

1.时间和肿瘤大小坐标图

import numpy as np
import matplotlib.pyplot as plt

# 特征
raw_data_x= [[3.393533211,2.331273381],
             [2.110073483,1.781539638],
             [1.343808831,3.368360954],
             [3.582294042,4.679179110],
             [2.280362439,2.866990263],
             [7.423436942,4.696522875],
             [5.745051997,3.533989803],
             [9.172168622,2.511101045],
             [7.792783481,3.424088941],
             [7.939820817,0.791637231]
            ]
# 所属类别
raw_data_y = [0,0,0,0,0,1,1,1,1,1]

# 转为ndarray类型
X_train = np.array(raw_data_x)
y_train = np.array(raw_data_y)
print(y_train)

# 要预测的点
x = np.array([8.093607318,3.365731514])

'''画图(区分良性,恶性,以及测试数据)'''
good_ind = y_train == 0         # 获取良性肿瘤数据的bool索引
# print(ind)      [ True  True  True  True  True False False False False False]
plt.scatter(X_train[good_ind,0],X_train[good_ind,1],color='g')

# 恶性肿瘤
bad_ind = y_train == 1         # 获取良性肿瘤数据的bool索引
plt.scatter(X_train[bad_ind,0],X_train[bad_ind,1],color='r')

# 目标点
plt.scatter(x[0],x[1],color='b')

plt.show()

运行:

2.预测肿瘤分类:

 from math import sqrt

import numpy as np
import matplotlib.pyplot as plt
from collections import Counter

# 特征
raw_data_x= [[3.393533211,2.331273381],
             [2.110073483,1.781539638],
             [1.343808831,3.368360954],
             [3.582294042,4.679179110],
             [2.280362439,2.866990263],
             [7.423436942,4.696522875],
             [5.745051997,3.533989803],
             [9.172168622,2.511101045],
             [7.792783481,3.424088941],
             [7.939820817,0.791637231]
            ]
# 所属类别
raw_data_y = [0,0,0,0,0,1,1,1,1,1]

# 转为ndarray类型
X_train = np.array(raw_data_x)
y_train = np.array(raw_data_y)

# 要预测的点
x = np.array([8.093607318,3.365731514])

# 按照预测点x与待预测点之间的欧拉距离(列表)
oli = [sqrt(np.sum((p - x)**2)) for p in raw_data_x]
print(oli)
# [4.812566907609877, 6.189696362066091, 6.749798999160064,
# 4.6986266144110695, 5.83460014556857, 1.4900114024329525,
# 2.354574897431513, 1.3761132675144652, 0.3064319992975,
# 2.5786840957478887]

# 通过欧拉距离列表,获得排序后索引
sort_ind = np.argsort(oli)
print(sort_ind)
# [8 7 5 6 9 3 0 4 1 2]

# 选出k的值  ----- 超参数,可以适当改变
k = 6

# 求出距离测试点最近的6个点的类别
topK_y = [y_train[i] for i in sort_ind[:k]]
print(topK_y)
# [1, 1, 1, 1, 1, 0]

cou = Counter(topK_y)
print(cou)
# Counter({1: 5, 0: 1})

# 获取统计数对象中数量最多的类别,most_common(个数)
more = cou.most_common(1)
print(more)        # [(1, 5)]

result = more[0][0]
print(f'预测分类为:{result}')

代码中难点:

  • 欧拉距离计算
  • 获取最近距离的点所在的类别-----获取欧拉距离后,对索引进行排序,直接使用类别的序列对索引进行获取,因为对索引进行排序后,数据序列的数据项和类别序列的数据项是对应的,所以可以直接在类别序列中获取指定索引的数据。

3.将KNN算法封装成函数

1.断言
x = -33
assert x>0, '这是正数'
print(x)

用于类似于debug的功能,assert后的表达式成立,则向下执行,否则,抛出断言错误。

2.封装函数
from math import sqrt
import numpy as np
from collections import Counter


def Knn_classify(k,X_train,ytrain,x):

    assert 1<k<X_train.shape[0],'k值必须有效'
    assert X_train.shape[0] == ytrain.shape[0],'数据集不符合规则'
    assert X_train.shape[1] == x.shape[0],'测试数据要符合训练数据的格式'

    oli = [sqrt(np.sum((p - x) ** 2)) for p in X_train]

    sort_ind = np.argsort(oli)

    topK_y = [ytrain[i] for i in sort_ind[:k]]

    cou = Counter(topK_y)

    more = cou.most_common(1)
    result = more[0][0]

    return result

# 特征
raw_data_x= [[3.393533211,2.331273381],
             [2.110073483,1.781539638],
             [1.343808831,3.368360954],
             [3.582294042,4.679179110],
             [2.280362439,2.866990263],
             [7.423436942,4.696522875],
             [5.745051997,3.533989803],
             [9.172168622,2.511101045],
             [7.792783481,3.424088941],
             [7.939820817,0.791637231]
            ]
# 所属类别
raw_data_y = [0,0,0,0,0,1,1,1,1,1]

# 转为ndarray类型
X_train = np.array(raw_data_x)
y_train = np.array(raw_data_y)

# 要预测的点
x = np.array([8.093607318,3.365731514])

result = Knn_classify(6,X_train,y_train,x)
print(result)            # 1

封装为函数的流程与之前的普通写法流程一致,知识将未知参数以参数的形式传进来而已,并且还要判断参数的合法性--使用assert。

4. 机器学习套路

可以说kNN是一个不需要训练过程的算法 k近邻算法是非常特殊的,可以被认为是没有模型的算法 为了和其他算法统一,可以认为训练数据集就是模型

1.使用scikit-learn中的kNN

上述的操作是自己搭建一个KNN算法的流程,但是KNN算法在机器学习库sklearn中已经存在,直接使用即可。

from sklearn.neighbors import KNeighborsClassifier
import numpy as np


# n_neighbors就是k值
kNN_classifier = KNeighborsClassifier(n_neighbors=6)


# 特征
raw_data_x= [[3.393533211,2.331273381],
             [2.110073483,1.781539638],
             [1.343808831,3.368360954],
             [3.582294042,4.679179110],
             [2.280362439,2.866990263],
             [7.423436942,4.696522875],
             [5.745051997,3.533989803],
             [9.172168622,2.511101045],
             [7.792783481,3.424088941],
             [7.939820817,0.791637231]
            ]
# 所属类别
raw_data_y = [0,0,0,0,0,1,1,1,1,1]

# 转为ndarray类型
X_train = np.array(raw_data_x)
y_train = np.array(raw_data_y)

# 训练模型
kNN_classifier.fit(X_train, y_train)

# 要预测的点
x = np.array([8.093607318,3.365731514])
y = np.array([1,3])

# predict传入多维数组(使用reshape将测试数据转为二维数组),直接传入predict
# x = x.reshape(1,-1)
# print(x)          # [[8.09360732 3.36573151]]
# result = kNN_classifier.predict(x)
# print(result[0])      # 1

也可里理解为传入一个数组,这个数组里每一项都是一个要预测的点---[x1,x2,x3]
result = kNN_classifier.predict([x,y,x,y])
print(result)         # [1 0 1 0]

2.封装自己的KNN--模拟sklearn库中的KNN算法

在之前我们将自己手写的KNN封装为函数,现在要封装为类:

from math import sqrt
import numpy as np
from collections import Counter

class KnnClass:

    def __init__(self,k):
        self.k = k
        self.x_train = None
        self.y_train = None

    def fit(self,x_train,y_train):

        self.x_train = x_train
        self.y_train = y_train

        # 返回自身(当前类),用于下边调用
        return self

    def perdict(self,X_perdict):
        y_predict = [self._perdict(x) for x in X_perdict]

        return np.array(y_predict)


    def _perdict(self,x):
        oli = [sqrt(np.sum((p - x) ** 2)) for p in self.x_train]

        sort_ind = np.argsort(oli)

        topK_y = [self.y_train[i] for i in sort_ind[:self.k]]

        cou = Counter(topK_y)

        more = cou.most_common(1)
        result = more[0][0]

        return result



k = KnnClass(6)

# 所述类别
raw_data_x= [[3.393533211,2.331273381],
             [2.110073483,1.781539638],
             [1.343808831,3.368360954],
             [3.582294042,4.679179110],
             [2.280362439,2.866990263],
             [7.423436942,4.696522875],
             [5.745051997,3.533989803],
             [9.172168622,2.511101045],
             [7.792783481,3.424088941],
             [7.939820817,0.791637231]
            ]
# 所属类别
raw_data_y = [0,0,0,0,0,1,1,1,1,1]

X_train = np.array(raw_data_x)
y_train = np.array(raw_data_y)
# 要预测的点
x1 = np.array([8.093607318,3.365731514])
x2 = np.array([1,3])
k.fit(X_train, y_train)
# X_predict = x.reshape(1, -1)
y_predict = k.perdict([x1,x2])

print(y_predict)       # [1 0]

5. 判断机器学习算法的性能

将训练集分割为训练集和测试集:

iris数据集

首先认识一下iris数据集,通过sklearn库导入即可

from sklearn import datasets
import matplotlib.pyplot as plt

# 加载鸢尾花数据集
iris = datasets.load_iris()

print(iris.keys())
# dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename'])

# 数据集
print(iris.data.shape)        # (150, 4)

# 列名
print(iris.feature_names)    # ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

# 标签
print(iris.target)
# [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
#  0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
#  1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
#  2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
#  2 2]
print(iris.target.shape)     # (150,)
  • keys:该数据集中的内容,是通过key值获取的,例如通过iris.data获取源数据。
  • feature_names:获取数据集中数据的列名。
  • data:鸢尾花数据,150行4列,列名对应feature_names,其分类结果在target中。
  • target:获取数据集对应的标签(类似于答案)。

画图

由于这个iris数据集数据的维度是4列的,所以不能直接话在坐标上,所以截取其前两列/后两列进行描点画图。

并且根据分类不同描出不同的颜色。

'''画图'''
data_iris = iris.data[:,:2]
print(data_iris.shape)      # (150, 2)

target_true = iris.target == 0
target_false = iris.target == 1
target_false_two = iris.target == 2

plt.scatter(data_iris[target_true,0],data_iris[target_true,1],color='r')
plt.scatter(data_iris[target_false,0],data_iris[target_false,1],color='b')
plt.scatter(data_iris[target_false_two,0],data_iris[target_false_two,1],color='g')

plt.show()

# 后俩列数据
data_iris = iris.data[:,2:]
print(data_iris.shape)      # (150, 2)

target_true = iris.target == 0
target_false = iris.target == 1
target_false_two = iris.target == 2

plt.scatter(data_iris[target_true,0],data_iris[target_true,1],color='r')
plt.scatter(data_iris[target_false,0],data_iris[target_false,1],color='b')
plt.scatter(data_iris[target_false_two,0],data_iris[target_false_two,1],color='g')

plt.show()

前两列:

后两列:

6.train_test_spilt--分割训练集

一般训练集和测试集的比例为5:1,但是并不是单纯的前80%为训练集,后20%为测试集,因为原数据是按照分类规则有序排序的,如果这样分割,其分类的数据训练不平均。

例如:鸢尾花的后20%的数据基本上都是2类的,如果将其设为测试集,那么训练集中的2分类数量不足,0和1分类的数量充足,最终测试的时候,结果会有很大偏差。

所以要随机对其进行分割:

from sklearn import datasets
import numpy as np


# 加载鸢尾花数据集
iris = datasets.load_iris()

#后两列数据
data_iris = iris.data[:,2:]
# 获取标签
target = iris.target

# permutation(n) 给出从0到n-1的一个随机排
shuffle_indexes = np.random.permutation(len(data_iris))

print(shuffle_indexes)
# [  7 106  56  37  63  52  64   2 149 140 147  46 114  26  45  44 128 113
#  139 123  28  55  76 118 112  48   0 102  10  86 108  36  98  70 115 100
#   11 137  77  74  81 109 107 144  49  50   8 125  24  33  42 141  34 124
#  129  31  73  90  39  83  79 136  14   9 134 121 120  51  13  53  66   5
#   80  92   4  82 105 122  21 116  12  29 130  18 103 131  84  47  93  65
#   30  58 111 143 138  99 132  17  20  61  62  54  22 126 101  43  97  91
#    1  41  25 117  16  32 133  75  87  59  72  88 145  27 135  89 148  23
#   78  40  38  85 127  19   3  15  95  69 142   6 119 146  67  71 104  57
#   68  94  35  60 110  96]

# 测试数据集的比例
test_ratio = 0.2
# 获取测试数据集的长度
tets_size = int(len(data_iris) * test_ratio)
print(tets_size)      # 30

# 获取测试数据集和训练数据集的索引
test_indexes = shuffle_indexes[:tets_size]
train_indexes = shuffle_indexes[tets_size:]
print(test_indexes)
# [111 141 140  16  79  18 136 112  39  27 115  66  63  23  53   6 132  57
#   89  59  68  42  75  34 101  28  51  73   2 139]
print(train_indexes)
# [ 78 107  44  19 133 126 121 148  70 144  67 146  93  92 129   0 124 131
#   62 130  17  26  91  82  10 102  61  29  52   4  83  21  41  96  24 104
#  117  22 142 110  95 138 143   1  15  87  84 100 137  40 105  31  77  97
#    7  60 127  25  36 114   9 119  38 149  65 120 134  43  71  12   8  32
#   50 123  20 125  74  35  85  81  69  58  14  33  76  94 116  98  13  45
#  113 128  54  55  30  49 135  37 109  72 108 103  11 147  46  80  64  56
#  106  47  48 122  86   3  99 118   5  90  88 145]

# 根据索引获取测试和训练数据集
x_train = data_iris[train_indexes]
y_train = target[train_indexes]
print(x_train.shape)      # (120, 2)
print(y_train.shape)      # (120,)

x_test = data_iris[test_indexes]
y_test = target[test_indexes]
print(x_test.shape)       # (30, 2)
print(y_test.shape)       # (30,)

1.封装的测试分割函数用于分割训练集

from sklearn import datasets
import numpy as np

def train_test_split(x,y,rate=0.2,seed=None):
    '''

    :param x: 数据
    :param y: target标签
    :param rate: 比率
    :param seed: 随机种子
    :return: 训练集,训练集对应标签,测试集,测试集对应标签
    '''

    np.random.seed(666)  # 生成随机数种子
    shuffle_indexes = np.random.permutation(len(x))

    tets_size = int(len(x) * rate)

    test_indexes = shuffle_indexes[:tets_size]
    train_indexes = shuffle_indexes[tets_size:]

    x_train = x[train_indexes]
    y_train = y[train_indexes]

    x_test = x[test_indexes]
    y_test = y[test_indexes]

    return x_train,y_train,x_test,y_test



# 加载鸢尾花数据集
iris = datasets.load_iris()

#后两列数据
data_iris = iris.data[:,2:]
# 获取标签
target = iris.target

x_train,y_train,x_test,y_test = train_test_split(data_iris,target,rate=0.2,seed=666)
print(x_train.shape)     # (120, 2)
print(y_train.shape)     # (120,)
print(x_test.shape)       # (30, 2)
print(y_test.shape)      # (30,)

2.使用自己编写的分割函数和KNN算法进行一次完整训练

train_test_split:自己封装的分割函数

def train_test_split(x,y,rate=0.2,seed=None):
    '''

    :param x: 数据
    :param y: target标签
    :param rate: 比率
    :param seed: 随机种子
    :return: 训练集,训练集对应标签,测试集,测试集对应标签
    '''

    np.random.seed(1)  # 生成随机数种子
    shuffle_indexes = np.random.permutation(len(x))

    tets_size = int(len(x) * rate)

    test_indexes = shuffle_indexes[:tets_size]
    train_indexes = shuffle_indexes[tets_size:]

    x_train = x[train_indexes]
    y_train = y[train_indexes]

    x_test = x[test_indexes]
    y_test = y[test_indexes]

    return x_train,y_train,x_test,y_test

KnnClass:自己封装的KNN算法

class KnnClass:

    def __init__(self,k):
        self.k = k
        self.x_train = None
        self.y_train = None

    def fit(self,x_train,y_train):

        self.x_train = x_train
        self.y_train = y_train

        # 返回自身(当前类),用于下边调用
        return self

    def perdict(self,X_perdict):
        y_predict = [self._perdict(x) for x in X_perdict]

        return np.array(y_predict)


    def _perdict(self,x):
        oli = [sqrt(np.sum((p - x) ** 2)) for p in self.x_train]

        sort_ind = np.argsort(oli)

        topK_y = [self.y_train[i] for i in sort_ind[:self.k]]

        cou = Counter(topK_y)

        more = cou.most_common(1)
        result = more[0][0]

        return result

main.py:

from sklearn import datasets
import numpy as np
from KNN.Knn_iris02.train_split_def import train_test_split       # 分割函数
from KNN.Knn_classify01.knn_class import KnnClass            # KNN算法类


# 加载鸢尾花数据集
iris = datasets.load_iris()

#后两列数据
data_iris = iris.data[:,2:]
# 获取标签
target = iris.target

x_train,y_train,x_test,y_test = train_test_split(data_iris,target,rate=0.2,seed=666)

print(x_train.shape)     # (120, 2)
print(y_train.shape)     # (120,)
print(x_test.shape)       # (30, 2)
print(y_test.shape)      # (30,)


knn = KnnClass(6)
# 训练模型
knn.fit(x_train,y_train)

# 添加测试数据
res = knn.perdict(x_test)

print(res)
# [2 2 1 0 1 0 2 1 2 0 2 1 1 1 0 2 0 0 1 2 0 1 1 1 0 0 0 2 2 0]
print(y_test)
# [2 2 1 0 1 0 2 1 1 0 2 1 1 1 0 2 0 0 1 2 0 1 1 1 0 0 0 2 2 0]

# 对比res和y_test中的数据,算判断正确数据的占比
lv = np.sum(res == y_test)/y_test.shape[0]

print(f'得分:{lv}')

结果:

3.使用sklearn的分割函数和KNN算法进行一次完整训练

from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier


# 加载鸢尾花数据集
iris = datasets.load_iris()

#后两列数据
data_iris = iris.data[:,2:]
# 获取标签
target = iris.target

X_train,X_test,y_train,y_test = train_test_split(data_iris,target)

sklearn_knn_clf = KNeighborsClassifier(n_neighbors=6)

sklearn_knn_clf.fit(X_train,y_train)

y_predict = sklearn_knn_clf.predict(X_test)
print(y_predict)
print(y_test)

result = np.sum(y_predict==y_test)/len(y_test)
print(result)

结果:

7.分类准确度函数

对于计算分类正确率的公式:
np.sum(new_test == right_test) / len(right_test)
我们也可以将其封装为一个函数放在自定义的KNN的类中。

class KnnClass:
    ....
    ....

    def score(self,new_test,right_test):

        return np.sum(new_test == right_test) / len(right_test)

这个方法中传入参数:perdict方法返回的测试集结果正确的测试机结果

在调用时:

lv = knn.score(res,y_test)

print(f'得分:{lv}')

scikit-learn中的accuracy_score

在sklearn库中也有相关方法 -----accuracy_score

# 自己封装的计算正确率的方法
lv = knn.score(res,y_test)

print(f'得分:{lv}')

# sklearn中的正确率方法
from sklearn.metrics import accuracy_score

lv = accuracy_score(res,y_test)
print(lv)

结果:

封装计算正确率函数

在计算函数的基础上再次封装,将参数调整为传入:测试集x,测试集标签y,这样直接在定义的函数中调用perdict方法:

class KnnClass:
...
...

    def score2(self,x,y):
        y_perdict = self.perdict(x)

        return np.sum(y_perdict==y)/len(y)

再main函数中调用:

# 自己封装的计算正确率函数
lv = knn.score(res,y_test)

print(f'得分:{lv}')        # 得分:0.9666666666666667

from sklearn.metrics import accuracy_score

# sklearn中的计算正确率函数
lv = accuracy_score(res,y_test)
print(lv)       # 0.9666666666666667

# 二次封装
lv = knn.score2(x_test,y_test)
print(lv)     # 0.9666666666666667

这样就省去了再外边调用perdict的麻烦。

sklearn中的score函数

sklearn中也有相关函数
传入参数:测试数据集,测试数据对应的正确标签集

result = accuracy_score(y_predict,y_test)
print(result)

result = sklearn_knn_clf.score(X_test,y_test)
print(result)

相关文章

  • 2020-08-10--KNN01

    KNN算法的原理介绍KNN算法的一个简单实现(肿瘤分类)将KNN算法封装成函数机器学习套路使用scikit-lea...

网友评论

      本文标题:2020-08-10--KNN01

      本文链接:https://www.haomeiwen.com/subject/podsdktx.html