博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

第一章 KNN算法知识点

Posted on 2018-08-28 17:48  Jasonhy  阅读(401)  评论(0)    收藏  举报

介绍

一种基于分类和回归方法,属于监督学习方法

流程

给定一个训练集,然后基于某种距离度量找出训练集中与其最靠近的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. 实验搜索

超参数

  1. k
  2. 距离权重: 将距离的倒数作为权重
  3. 超参数P: 主要是用来控制使用的是哪个距离公式
  4. 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) 维数灾难,指的是随着维度的增加,"看似相近"的两个点之间的距离越来越大
       解决方法: 降维