2020-08-13--KNN02

2020-08-13  本文已影响0人  program_white
  • 超参数和模型参数
  • 超参数:距离的权重
  • 距离公式的选择
  • 网格搜索最佳模型
  • 数据归一化
  • 最值归一化 normalization
  • 均值方差归一化 Standardization
  • 对测试数据集如何归一化
  • 使用sklearn库中的StandardScaler(均值方差归一化)
  • 实现自己的StandardScaler
  • KNN的缺点

1.超参数和模型参数

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的另外一个超参数:距离的权重

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

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

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

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. 这是的网格对象有很多的参数,例如:
  1. 根据best_estimator_参数找到最好的算法模型(最佳参数),调用score(x,y)进行测试测试集,输出最好的正确率。

3. 数据归一化

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

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

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

适用于分布有明显边界的情况;受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的分布中

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

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

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)
上一篇 下一篇

猜你喜欢

热点阅读