- 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,所以判断这个新病人幻的事恶性肿瘤。
![](https://img.haomeiwen.com/i22697958/01f11f19ad1ca510.png)
分析:
取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()
运行:
![](https://img.haomeiwen.com/i22697958/b2384f3603dc0198.png)
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)
![](https://img.haomeiwen.com/i22697958/f664f9bd09b8a6a4.png)
用于类似于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. 机器学习套路
![](https://img.haomeiwen.com/i22697958/b2da91e6afde4b96.png)
可以说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. 判断机器学习算法的性能
![](https://img.haomeiwen.com/i22697958/b1439c47fd6f5d7b.png)
将训练集分割为训练集和测试集:
![](https://img.haomeiwen.com/i22697958/164d0cb69e1152ed.png)
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()
前两列:
![](https://img.haomeiwen.com/i22697958/98c66157750e1559.png)
后两列:
![](https://img.haomeiwen.com/i22697958/192d5160a68218bb.png)
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}')
结果:
![](https://img.haomeiwen.com/i22697958/c67fc23ca16e0f09.png)
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)
结果:
![](https://img.haomeiwen.com/i22697958/9c964594f97d211a.png)
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)
结果:
![](https://img.haomeiwen.com/i22697958/c2c583235ebf1741.png)
封装计算正确率函数
在计算函数的基础上再次封装,将参数调整为传入:测试集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)
网友评论