美文网首页
2020-08-13--KNN02

2020-08-13--KNN02

作者: program_white | 来源:发表于2020-08-13 21:50 被阅读0次
    • 超参数和模型参数
    • 超参数:距离的权重
    • 距离公式的选择
    • 网格搜索最佳模型
    • 数据归一化
    • 最值归一化 normalization
    • 均值方差归一化 Standardization
    • 对测试数据集如何归一化
    • 使用sklearn库中的StandardScaler(均值方差归一化)
    • 实现自己的StandardScaler
    • KNN的缺点

    1.超参数和模型参数

    • 超参数:再算法运行前需要决定的参数。例如KNN中的k参数就是典型的超参数
    • 模型参数:算法过程中学习的参数,例如y = ax+b,a和b就是再学习过程中训练出来的参数。

    KNN算法没有模型参数
    KNN算法中的k是典型的超参数

    寻找好的超参数

    • 领域知识
    • 经验数值
    • 实验搜索

    寻找最好的k

    from sklearn import datasets
    from sklearn.model_selection import train_test_split
    from sklearn.neighbors import KNeighborsClassifier
    
    # 加载手写识别数据
    df = datasets.load_digits()
    # 数据
    mdata = df.data
    # 标签
    mlabel = df.target
    
    # 分割数据集
    X_train, X_test, y_train, y_test = train_test_split(mdata,mlabel,test_size=0.2)
    
    # 寻找最好k值
    best_k = -1
    best_score = 0
    for i in range(1,11):
        knn_clf = KNeighborsClassifier(n_neighbors=i)
        
        knn_clf.fit(X_train,y_train)
        
        score = knn_clf.score(X_test,y_test)
        
        if score>best_score:
            best_score = score
            best_k = i
    
    print(best_k)      # 3
    
    print(best_score)       # 0.9805555555555555
    

    把指定一个范围中的k都实验一遍,看谁的正确率高,就是谁。

    kNN的另外一个超参数:距离的权重

    一般情况下使用距离的倒数作为权重

    那么是考虑距离还是不考虑距离呢,肯定是谁的正确率高就用谁了。

    数据集还是使用上边的,权重的值有:

    • 'uniform':不考虑距离,只考虑个数
    • 'distance':考虑距离和权重
    best_method = ""
    best_score = 0.0
    best_k = -1
    for method in ["uniform","distance"]:
        for k in range(1,11):
            knn_clf = KNeighborsClassifier(n_neighbors=k,weights=method)
            knn_clf.fit(X_train,y_train)
            score = knn_clf.score(X_test,y_test)
            if score > best_score:
                best_k = k
                best_score = score
                best_method = method
    print("best_k=",best_k)
    print("best_score=",best_score)
    print("best_method=",best_method)
    # best_k= 3
    # best_score= 0.9777777777777777
    # best_method= uniform       不考虑距离
    

    但是由于数据分割是随机的,所以这个情况是多变的。

    距离公式

    • 欧拉距离
    • 曼哈顿距离
    • 两种距离的整理对比
    • 明克夫斯基距离

    到这里,我们获得了一个新的超参数 p。

    所以而我们现在要讨论的是,再计算距离的时候,是使用欧拉距离还是曼哈顿距离还是明可夫斯基距离呢?
    也就是说上边公式的p的值为多少时最好? 正确率更高,失误率更少呢?

    那么我们呢需要试验一下:

    best_p = -1
    best_score = 0.0
    best_k = -1
    for k in range(1,11):
        for p in range(1,6):
            knn_clf = KNeighborsClassifier(n_neighbors=k,weights='distance',p=p)
            knn_clf.fit(X_train,y_train)
            score = knn_clf.score(X_test,y_test)
            if score > best_score:
                best_k = k
                best_score = score
                best_p = p
    print("best_p=",best_p)
    print("best_k=",best_k)
    print("best_score=",best_score)
    # best_p= 2
    # best_k= 1
    # best_score= 0.9916666666666667
    

    分析:
    在sklearn库中的KNN算法类中,逐个传入p的值,来控制计算公式,
    这里采用控制变量法,控制k的值,逐个实验p计算,算出正确率最好的p,接着下一个k值,最终找出最好的p和k。

    这个结果根据训练集和测试及数据的不同,会发生相应的变化。

    2. 网格搜索

    网格搜索是用于寻找最佳的算法模型(最佳的超参数)。

    # 调用GridSearchCV创建网格搜索对象,传入参数为Classifier对象以及参数列表
    from sklearn.model_selection import GridSearchCV, train_test_split
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn import datasets
    
    # 1.创建网格搜索超参数数据--[{},{}]
    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)]
        }
    ]
    
    # 2.先new一个默认的Classifier对象,确定算法
    knn_clf = KNeighborsClassifier()
    
    # 3.GridSearchCV(算法对象,超参数数据)
    grid_search = GridSearchCV(knn_clf,param_grid)
    
    
    # 加载数据
    df = datasets.load_iris()
    mdata = df.data
    mlabel = df.target
    X_train, X_test, y_train, y_test = train_test_split(mdata,mlabel,test_size=0.2)
    
    # 4.添加数据
    grid_search.fit(X_train,y_train)
    
    # 5.返回网格对象的最佳参数
    ps = grid_search.best_params_
    print(ps)        # {'n_neighbors': 10, 'weights': 'uniform'}
    
    # 获取最佳模型,也就是算法模型+最佳参数的一个最佳模型
    mod = grid_search.best_estimator_
    
    # 验证该模型是不是之前创建的默认算法KNN模型
    print(mod is knn_clf)      # False
    
    # 测试 ,测试数据
    result = mod.score(X_test,y_test)
    print(result)     # 1.0
    

    分析:

    1. 使用sklearn中的一个模块GridSearchCV--网格交叉验证,用于寻找算法和参数之间的最佳值。
    2. 组织超参数的数据结构---类似于[{key:val},{key:val},...],{}里的val一般为多个值,使用list的方式赋值。
    3. 确定算法模型,创建KNN算法对象knn_clf。
    4. 创建网格对象grid_search,将算法对象和超参数数据传进去,也有其他的参数:

    n_jobs :多线程并行处理,占用几个核,-1为使用所有的核
    verbose :是否打印搜索信息,传入值越大,输出信息越详细

    1. 添加分割号的训练集数据到网格搜索对象的fit()中。
    2. 这是的网格对象有很多的参数,例如:
    • best_estimator_:最好的算法模型
    • best_score_:最好的正确率
    • best_params_:最好的超参数
    1. 根据best_estimator_参数找到最好的算法模型(最佳参数),调用score(x,y)进行测试测试集,输出最好的正确率。

    3. 数据归一化

    样本间的距离被一个字段所主导

    解决方案 :将所有的数据映射到同一尺度

    最值归一化 normalization:把所有数据映射到0-1之间

    • X表示这列数据的每个值,需要映射。
    • Xmin表示这列数据中的最小值
    • Xmax表示这列数据中的最大值
    • Xscale表示最终映射的结果

    适用于分布有明显边界的情况;受outlier影响较大。

    原因:当一列数据有一个很大的值时,那么最值归一化公式的分母就会变得无限大,分子相对来说很小,例如0.00001,那么这样的话,该列中数据的最小值无限小,最大值为1,这样的映射没有意义。

    一维数组最值归一化:

    import numpy as np
    
    x = np.random.randint(0,100,size=100)
    print(x)
    # [51 80 57 96 35 98 75 51 52 56 86 27 16 39 69 59 27 63 28 66 75 74 48 51
    #  64  6 69 25 41 33  7 69 99 32 55 37 59 73 47 58 38 60 64  9 41 64 37 57
    #  65  7 57  4 12  2 30 96 55 36 43 18 54 81 30 93 95 64 16 62 41 14 65 99
    #  57 36 81 10 16 55 66  0 89 21 66 91 50 14 20 29 57 14 36 34 44 42 47 46
    #  37  1  4 46]
    
    x_one = (x-np.min(x))/(np.max(x)-np.min(x))
    print(x_one)
    # [0.51515152 0.80808081 0.57575758 0.96969697 0.35353535 0.98989899
    #  0.75757576 0.51515152 0.52525253 0.56565657 0.86868687 0.27272727
    #  0.16161616 0.39393939 0.6969697  0.5959596  0.27272727 0.63636364
    #  0.28282828 0.66666667 0.75757576 0.74747475 0.48484848 0.51515152
    #  0.64646465 0.06060606 0.6969697  0.25252525 0.41414141 0.33333333
    #  0.07070707 0.6969697  1.         0.32323232 0.55555556 0.37373737
    #  0.5959596  0.73737374 0.47474747 0.58585859 0.38383838 0.60606061
    #  0.64646465 0.09090909 0.41414141 0.64646465 0.37373737 0.57575758
    #  0.65656566 0.07070707 0.57575758 0.04040404 0.12121212 0.02020202
    #  0.3030303  0.96969697 0.55555556 0.36363636 0.43434343 0.18181818
    #  0.54545455 0.81818182 0.3030303  0.93939394 0.95959596 0.64646465
    #  0.16161616 0.62626263 0.41414141 0.14141414 0.65656566 1.
    #  0.57575758 0.36363636 0.81818182 0.1010101  0.16161616 0.55555556
    #  0.66666667 0.         0.8989899  0.21212121 0.66666667 0.91919192
    #  0.50505051 0.14141414 0.2020202  0.29292929 0.57575758 0.14141414
    #  0.36363636 0.34343434 0.44444444 0.42424242 0.47474747 0.46464646
    #  0.37373737 0.01010101 0.04040404 0.46464646]
    
    x = np.random.randint(0,100,size=100)
    x[0] = 100000
    print(x)
    # [100000     85     92     96     40     50     80     94     13     63
    #      49      9     17     34     21     37     11     56     25     91
    #      58     46      0     91     99     16     21     49     58     44
    #      64     30      8     84     63     98     32     43     56     13
    #       3     26     65     99     32     67     74     97     76     81
    #      79     59     17     13     96     12     67     64     22      9
    #      59     35     24     71     48     82      6     79     80     88
    #      44     18     11      3     59     95      8     17     59      3
    #      93     54     72      8     45     15     64     80     81     37
    #      86     12     10     21     54     77     40      5     59     80]
    x_one = (x-np.min(x))/(np.max(x)-np.min(x))
    print(x_one)
    # [1.0e+00 8.5e-04 9.2e-04 9.6e-04 4.0e-04 5.0e-04 8.0e-04 9.4e-04 1.3e-04
    #  6.3e-04 4.9e-04 9.0e-05 1.7e-04 3.4e-04 2.1e-04 3.7e-04 1.1e-04 5.6e-04
    #  2.5e-04 9.1e-04 5.8e-04 4.6e-04 0.0e+00 9.1e-04 9.9e-04 1.6e-04 2.1e-04
    #  4.9e-04 5.8e-04 4.4e-04 6.4e-04 3.0e-04 8.0e-05 8.4e-04 6.3e-04 9.8e-04
    #  3.2e-04 4.3e-04 5.6e-04 1.3e-04 3.0e-05 2.6e-04 6.5e-04 9.9e-04 3.2e-04
    #  6.7e-04 7.4e-04 9.7e-04 7.6e-04 8.1e-04 7.9e-04 5.9e-04 1.7e-04 1.3e-04
    #  9.6e-04 1.2e-04 6.7e-04 6.4e-04 2.2e-04 9.0e-05 5.9e-04 3.5e-04 2.4e-04
    #  7.1e-04 4.8e-04 8.2e-04 6.0e-05 7.9e-04 8.0e-04 8.8e-04 4.4e-04 1.8e-04
    #  1.1e-04 3.0e-05 5.9e-04 9.5e-04 8.0e-05 1.7e-04 5.9e-04 3.0e-05 9.3e-04
    #  5.4e-04 7.2e-04 8.0e-05 4.5e-04 1.5e-04 6.4e-04 8.0e-04 8.1e-04 3.7e-04
    #  8.6e-04 1.2e-04 1.0e-04 2.1e-04 5.4e-04 7.7e-04 4.0e-04 5.0e-05 5.9e-04
    #  8.0e-04]
    

    二维数组均值归一化:

    x = np.random.randint(0,50,(25,2))
    x = np.array(x,dtype=float)
    print(x)
    # [[ 7. 31.]
    #  [ 3. 71.]
    #  [34. 74.]
    #  [98. 87.]
    #  [92. 68.]
    #  [23. 16.]
    #  [68. 23.]
    #  [18. 70.]
    #  [39. 78.]
    #  [45. 64.]
    #  [22.  7.]
    #  [75. 32.]
    #  [73. 74.]
    #  [78. 35.]
    #  [90. 70.]
    #  [78.  6.]
    #  [15. 76.]
    #  [ 4. 41.]
    #  [30. 44.]
    #  [46. 41.]
    #  [ 9. 58.]
    #  [84. 79.]
    #  [62. 48.]
    #  [73. 48.]
    #  [48. 39.]]
    
    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]))
    print(x)
    # [[0.         0.79166667]
    #  [0.70731707 0.79166667]
    #  [0.56097561 0.4375    ]
    #  [0.6097561  0.3125    ]
    #  [0.29268293 0.39583333]
    #  [0.70731707 0.95833333]
    #  [0.92682927 0.66666667]
    #  [0.97560976 1.        ]
    #  [0.6097561  0.16666667]
    #  [0.80487805 0.625     ]
    #  [0.02439024 0.04166667]
    #  [0.3902439  0.91666667]
    #  [0.24390244 0.89583333]
    #  [0.34146341 0.72916667]
    #  [0.         0.91666667]
    #  [0.73170732 0.02083333]
    #  [0.19512195 0.72916667]
    #  [0.34146341 0.8125    ]
    #  [0.02439024 0.27083333]
    #  [0.73170732 0.        ]
    #  [0.48780488 0.77083333]
    #  [0.56097561 0.41666667]
    #  [1.         0.0625    ]
    #  [0.46341463 0.14583333]
    #  [0.73170732 0.20833333]]
    

    均值方差归一化 Standardization

    把所有数据归一到均值为0方差为1的分布中

    适用于数据分布没有明显边界,有可能存在极端情况值。

    • Xmean:该列数据的均值
    • S:该列数据的标准差

    一维数组的均值方差归一化:

    import numpy as np
    
    x = np.random.randint(0,100,size=100)
    print(x)
    
    ava = np.mean(x)
    print(ava)
    # 53.64
    std = np.std(x)
    print(std)
    # 30.182286195714205
    
    x_one = (x-ava)/std
    print(x_one)
    
    

    二维数组的均值方差归一化:

    import matplotlib.pyplot as plt
    
    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])
    print(X2)
    
    plt.scatter(X2[:,0],X2[:,1])
    plt.show()
    

    图:


    根据均值方差归一化公式得:每列数据标准值都是0,方差都为1:

    std = np.mean(X2[:,0])
    print(std)
    # 7.105427357601002e-17
    std = np.mean(X2[:,1])
    print(std)
    # -9.325873406851315e-17
    
    std = np.std(X2[:,0])
    print(std)    # 1.0
    std = np.std(X2[:,1])
    print(std)    # 1.0
    

    4.对测试数据集如何归一化

    未知测试数据归一化结果 = (测试数据-训练数据的均值)/训练数据的标准差

    5.在scikit-learn中使用Scaler

    使用sklearn库中的StandardScaler(均值方差归一化):
    import numpy as np
    from sklearn import datasets
    from sklearn.preprocessing import StandardScaler
    
    iris = datasets.load_iris()
    
    X = iris.data
    y = iris.target
    
    from sklearn.model_selection import train_test_split
    X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,random_state=666)
    
    # 创建StandardScaler对象
    ss = StandardScaler()
    
    # 将训练数据传进fit方法中
    ss.fit(X_train)
    
    # 均值
    ava = ss.mean_
    print(ava)
    # [5.83416667 3.08666667 3.70833333 1.17      ]
    
    # 标准差
    sc = ss.scale_
    print(sc)
    # [0.81019502 0.44327067 1.76401924 0.75317107]
    
    '''均值方差归一化映射'''
    X_train = ss.transform(X_train)
    print(X_train)
    
    X_test = ss.transform(X_test)
    print(X_test)
    
    '''knn算法测试'''
    from sklearn.neighbors import KNeighborsClassifier
    knn_clf = KNeighborsClassifier(n_neighbors=3)
    
    knn_clf.fit(X_train,y_train)
    
    result = knn_clf.score(X_test,y_test)
    print(result)
    # 1.0
    
    实现自己的StandardScaler
    class StandardScaler:
        def __init__(self):
            self.mean = None
            self.scale = None
    
        def fit(self,X):
            # 每类的均值数组
            self.mean = [np.mean(X[:,i]) for i in range(X.shape[1])]
            # 每列的标准差
            self.scale = [np.std(X[:,i]) for i in range(X.shape[1])]
    
        def transform(self,X):
            # 创建一个与传进来的X形状相同的array,类型为float
            resX = np.empty(shape=X.shape, dtype=float)
    
            # 遍历X的列数
            for col in range(X.shape[1]):
                # 将resX中的对应列替换为传进来的数据集X通过计算的来的归一化的数据
                resX[:,col] = (X[:,col]-self.mean[col]) / self.scale[col]
    
            return resX
    

    在数据集归一化中调用:

    se = StandardScaler()        # 实例化类对象
    se.fit(X_train)            # 计算平均值和标准差
    X_train2 = se.transform(X_train)           # 归一化训练集
    X_test2 = se.transform(X_test)             # 归一化测试集
    print(X_test2)
    
    # 测试算法
    knn_clf2 = KNeighborsClassifier(n_neighbors=3) 
    knn_clf2.fit(X_train2,y_train)
    result = knn_clf2.score(X_test2,y_test)
    print(result)     # 1.0
    

    6.最值归一化的算法

    最直归一化算法相对简单,不在解释

    KNN的缺点

    分析:

    1. 在预测一个新的测试集中的每一个点时,都需要计算该点与训练集中的每一个点之间来计算距离,因为有m个点,所以要进行for循环m次。
    2. n个特征表示的就是n维的数据,那么在计算的公式中计算时,就要计算n次减法平方,这样在计算公式的底层就循环了n次。
    3. 那么上述总计下来时间复杂度就是(m*n)

    相关文章

      网友评论

          本文标题:2020-08-13--KNN02

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