介绍
一种基于分类和回归方法,属于监督学习方法
流程
给定一个训练集,然后基于某种距离度量找出训练集中与其最靠近的k个训练样本, 然后基于这k个"邻居"的信息来进行预测
分类
分类问题很容易理解,就是选择k个样本出现最多的类别标记来作为预测结果,这种方法叫做 投票法
示意图:

从示意图中,可知,k的大小实际就是投票的数量
回归
要预测的点的值,根据离它最近的k个点求平均距离,根据这些平均距离,就可以拟合得到一条线
示意图:

注: 在以上示例说明中,我们都仅仅考虑的是距离的远近,其实很多时候我们还需要考虑权重问题, 比如说里的越近,权重就越大
示意图:

代码
简单实现分类问题
import numpy as np import matplotlib.pyplot as plt # 构建数据集 X = np.array([[3.393533211, 2.331273381], [3.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] ]) # 数据集对应的特征 # 样本对应的类别,0表示良性肿瘤,1表示恶性肿瘤 y = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) # 查看数据的分布情况 plt.scatter(X[y == 0,0],X[y == 0,1],color="r") plt.scatter(X[y == 1,0],X[y == 1,1],color="b") plt.show()

# 假设现在来一个新的病人 x = np.array([8.093607318, 3.365731514]) plt.scatter(X[y == 0,0],X[y == 0,1],color="r") plt.scatter(X[y == 1,0],X[y == 1,1],color="b") plt.scatter(x[0],x[1],color="g") plt.show()

# 算出每一个点与x的距离 distance = [] for i in X: # 求两点距离 d = np.sqrt(np.sum((i - x) ** 2)) distance.append(d) # 对距离进行排序 # 返回的是索引 nearest = np.argsort(distance) # 取k=6,表示取最近的6个点 k = 6 top6 = [y[i] for i in nearest[:k]] print(top6) # [1, 1, 1, 1, 1, 0] # 获取出现结果最多的类别,也就是获取投票的结果 from collections import Counter votes = Counter(top6) print(votes.most_common(1)[0][0]) # 得到结果为1,该病人为恶性肿瘤
算法性能问题
在设计好自己的算法之后,怎么去判断算法的好坏呢?
一般情况下,都是将数据集分为训练集和测试集,训练集主要是用来进行模型训练,测试集主要是用来验证模型的好坏
定义自己的knn算法 knn.py import numpy as np from collections import Counter from metrics import accuracy_score class KNNClassifer: """ 自定义分类器 """ def __init__(self,k=5): """ 默认k=5 :param k: """ assert k >= 1, "k 必须大于等于1" self.k = k self._X_train = None self._y_train = None def fit(self,X_train,y_train): """ fit模型 :param X_train: :param y_train: :return: """ assert X_train.shape[0] == y_train.shape[0], "真实值和预测值得size必须相等" assert self.k <= X_train.shape[0], "训练集的size必须大于等于k" self._X_train = X_train self._y_train = y_train return self def predict(self,X_predict): """ 预测 :param X_predict: :return: """ assert self._X_train is not None and self._y_train is not None, "必须在fit之后才能predict" assert X_predict.shape[1] == self._X_train.shape[1], "预测集的特征数量必须和训练集的特征数量相等" y_predict = [self._predict(x) for x in X_predict] return np.array(y_predict) def _predict(self,x): """ 对单个数据进行预测 :param x: :return: """ assert x.shape[0] == self._X_train.shape[1],"预测集的特征数量必须和训练集的特征数量相等" distance = [np.sqrt(np.sum((x_train - x)**2)) for x_train in self._X_train] nearest = np.argsort(distance) topK = [self._y_train[i] for i in nearest[:self.k]] votes = Counter(topK) return votes.most_common(1)[0][0] def score(self,X_test,y_test): """ 计算准确度 :param X_test: :param y_test: :return: """ y_predict = self.predict(X_test) return accuracy_score(y_test,y_predict) def __repr__(self): return "KNNClassifer(k=%d)" % self.k metrics.py def accuracy_score(y_true, y_predict): """ 准确率得分 :param y_true: 真实数据 :param y_predict: 预测数据 :return: """ # 声明两者的size必须一致 assert y_true.shape[0] == y_predict.shape[0], "真实值和预测值得size必须相等" return sum(y_true == y_predict) / float(len(y_true))
两个算法的比较
from sklearn import datasets from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import train_test_split from knn import KNNClassifer ## 通过鸢尾花来测试KNN算法 iris = datasets.load_iris() # 获取对应的数据 X = iris.data y = iris.target # 查看数据的维度 print("X shape: ",X.shape) print("y shape: ",y.shape) # 将数据分为训练集和测试集 X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2) # 使用自己定义的KNN来创建模型 myknn = KNNClassifer() myknn.fit(X_train,y_train) print("my knn score: ",myknn.score(X_test,y_test)) # my knn score: 0.9333333333333333 # # ====================完美分割线==================== # # # 使用sklearn来创建模型 knn = KNeighborsClassifier() knn.fit(X_train,y_train) score = knn.score(X_test,y_test) print("sklearn knn score: ",score) # sklearn knn score: 0.9333333333333333
发现,我们定义的算法和sklearn的算法得到一样的准确率
超参数
简单来说,就是机器学习在运行之前,需要指定的参数
与模型参数的区别: 算法过程中学习的参数
就KNN而言,是没有模型参数的,因为KNN算法实际上并没有进行模型的训练,我们平时说的调参,指的就是超参数
那么怎么寻找到好的超参数呢:
1. 领域知识
2. 经验数值
3. 实验搜索
超参数
- k
- 距离权重: 将距离的倒数作为权重
- 超参数P: 主要是用来控制使用的是哪个距离公式
- metric: 树的度量
关于距离:

超参数选择代码实现
from sklearn import datasets from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import train_test_split # 加载手写数据识别的数据集 digits = datasets.load_digits() X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, test_size=0.2, random_state=1) knn = KNeighborsClassifier(n_neighbors=3) knn.fit(X_train, y_train) print("score: ", knn.score(X_test, y_test)) # 0.9944444444444445 # 通过for循环来搜索最好的k best_score = 0.0 best_k = -1 for i in range(1, 11): knn = KNeighborsClassifier(n_neighbors=i) knn.fit(X_train, y_train) if knn.score(X_test, y_test) > best_score: best_score = knn.score(X_test, y_test) best_k = i print("search k: ", best_k, " search score: ", best_score) # search k: 7 search score: 0.9972222222222222 # 考虑距离权重超参数 best_score = 0.0 best_k = -1 best_method = "" # uniform: 不考虑距离 # distance: 考虑距离 for method in ["uniform", "distance"]: for k in range(1, 11): knn = KNeighborsClassifier(n_neighbors=k, weights=method) knn.fit(X_train, y_train) score = knn.score(X_test, y_test) if score > best_score: best_score = score best_k = k best_method = method print("search k for weight: ", best_k, " search score for weight: ", best_score, " search method for weight: ", best_method) # search k for weight: 7 search score for weight: 0.9972222222222222 search method for # weight: uniform # 考虑明科夫斯基距离 best_p = -1 best_score = 0.0 best_k = -1 for k in range(1,11): for p in range(1,6): knn = KNeighborsClassifier(n_neighbors=k,weights="distance",p=p) knn.fit(X_train,y_train) score = knn.score(X_test,y_test) if score > best_score: best_score = score best_p = p best_k = k print("search k for distance: ", best_k, " search score for distance: ", best_score, " search p for distance: ", best_p) # search k for distance: 3 search score for distance: 0.9972222222222222 search p for distance: # 4
网格搜索
在上面,我们通过自己的方法,去寻找最优参数,在sklearn中,已经给我们定义好了网格搜索的函数
代码实现:
from sklearn import datasets from sklearn.model_selection import train_test_split,GridSearchCV from sklearn.neighbors import KNeighborsClassifier digits = datasets.load_digits() X = digits.data y = digits.target X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,random_state=1) # 使用默认参数查看评分 knn = KNeighborsClassifier() knn.fit(X_train,y_train) print("base score: ",knn.score(X_test,y_test)) # 通过GridSearchCV来进行网格搜索 param_grid = [ { "weights":["uniform"], "n_neighbors":[i for i in range(1,11)] }, { "weights":["distance"], "n_neighbors":[i for i in range(1,11)], "p":[i for i in range(1,6)] } ] knn = KNeighborsClassifier() # n_jobs: 表示使用几个核 # verbose: 查看输出日志 grid_search = GridSearchCV(knn,param_grid=param_grid,verbose=1) grid_search.fit(X_train,y_train) # 获取最佳得分 print("best score for GSCV: ",grid_search.best_score_) # 获取最佳参数 print("best params for GSCD: ",grid_search.best_params_) # 获取评分 print("best score: ",grid_search.best_estimator_.score(X_test,y_test))
数据归一化
为什么需要数据进行归一化
肿瘤案例:
1) 假设有两个特征: 肿瘤的大小(cm)和肿瘤发现的时间(天),对于样本一来说,肿瘤大小为1cm,但是我们已经发现200天了
样本2的肿瘤大小为5cm,但是我们发现的时间为100天,当我们采用欧拉距离进行计算的时候,发现肿瘤的发现时间占主导
地位,虽然5和1相差5倍,100和200相差2倍,但是由于量纲不一样,导致发现时间占距离的主要因素
2) 现在讲发现时间的单位变成年,200天相当于0.55年,100天相当于0.27年,这个时候肿瘤大小占有主导地位
如果我们不对数据进行处理的话,我们计算出样本之间的距离是有偏差的
因此,我们需要对数据进行归一化处理
那么怎么对数据进行归一化处理呢?
简单的说,就是将数据映射到同一尺度上
常用到的方法

既然我们对训练集进行了归一化处理,那么怎么对测试集进行归一化呢
训练集归一化: mean_train 和 std_train
测试集归一化: 使用的是训练集的mean_train和std_train进行归一化
(x_test - mean_train) / std_train
这样做的原因:
测试数据是模拟真实环境的:
1) 真实环境很有可能无法得到所有测试数据的均值和方差
2) 对数据的归一化也算是算法的一部分
最值归一化代码实现:
import numpy as np import matplotlib.pyplot as plt # 使用最值归一化 x = np.random.randint(0,100,100) maximum_value = (x - np.min(x)) / (np.max(x) - np.min(x)) # 所有数据都在0-1之间 print(maximum_value) # 对二维数组进行最值归一化 X = np.random.randint(0,100,(50,2)) X = np.array(X,dtype=float) # 分别求对应最值归一化 X[:,0] = (X[:,0] - np.min(X[:,0])) / (np.max(X[:,0] - np.min(X[:,0]))) X[:,1] = (X[:,1] - np.min(X[:,1])) / (np.max(X[:,1] - np.min(X[:,1]))) # 查看数据分布 plt.scatter(X[:,0],X[:,1]) plt.show() # 分别查看均值和方差 print("mean: ",np.mean(X[:,0])) print("std: ",np.std(X[:,0]))
示意图:

均值归一化代码实现
# 均值归一化 X2 = np.random.randint(0,100,(50,2)) X2 = np.array(X2,dtype=float) # 实现 X2[:,0] = (X2[:,0] - np.mean(X2[:,0])) / np.std(X2[:,0]) X2[:,1] = (X2[:,1] - np.mean(X2[:,1])) / np.std(X2[:,1]) # 查看数据分布 plt.scatter(X2[:,0],X2[:,1]) plt.show() # 查看均值和方差 print("mean: ",np.mean(X2[:,0])) print("std: ",np.std(X2[:,0]))
分布图

使用sklearn来实现归一化
代码实现
from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.neighbors import KNeighborsClassifier iris = datasets.load_iris() X = iris.data y = iris.target # 划分训练集和测试集 X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,random_state=1) # 均值方差归一化 stantardScalar = StandardScaler() stantardScalar.fit(X_train) # 查看均值 print("mean: ",stantardScalar.mean_) # 查看方差 print("std: ",stantardScalar.scale_) # 归一化处理 X_train = stantardScalar.transform(X_train) print("train 归一化 : ",X_train[:10]) # 对test也进行归一化 X_test_standard = stantardScalar.transform(X_test) # test归一化 print("test 归一化: ",X_test_standard[:10]) # 使用KNN进行分类 knn = KNeighborsClassifier(n_neighbors=3) knn.fit(X_train,y_train) print("score: ",knn.score(X_test_standard,y_test))
关于knn算法的思考
优点:
1) 可以解决分类问题
2) 算法简单
3) 可以解决回归问题
缺点:
1) 训练低下,如果训练集有m个样本,n个特征,则预测一个新的数据,需要O(m*n),
针对这个问题,可以使用树结构来进行优化,KD-Tree,Ball-Tree,但是效率还是
比较低
2) 需要的数据高度相关
3) 预测的结果性不具有可解释性
4) 维数灾难,指的是随着维度的增加,"看似相近"的两个点之间的距离越来越大
解决方法: 降维

浙公网安备 33010602011771号