KNN算法中关于数据分析和机器学习的应用
K-近邻法
KNN做为機器学习中最为简单的算法,其实用性还是很强的.
KNN就是所谓的物以类聚的方法(近朱者赤,近墨者黑) KNN就是在一个数据模型中,当新进来一个样本,通过比较与数据集中其他样本的距离来对该新样本做一个分类
看一个简单的示例:
以下是一组模拟数据 raw_data_X 为肿瘤病人的模拟数据 并且对于肿瘤有两个特征值
raw_data_y是肿瘤病人的病况 0为良性肿瘤 1为恶性肿瘤
raw_data_X = [[3.393533211,2.331273381],
[3.110073483,1.781539638],
[1.343808831,3.369360594],
[3.582294042,4.679179110],
[2.280362439,2.866990263],
[7.423436942,4.696522875],
[5.745023312,3.533312331],
[9.172163123,2.511103121],
[7.792731231,3.424088123],
[7.939820817,0.791637231]
]
raw_data_y = [0,0,0,0,0,1,1,1,1,1]
使用matplotlib绘制散点图
import numpy as np
from matplotlib import pyplot as plt
X_train = np.array(raw_data_X)
y_train = np.array(raw_data_y)
plt.scatter(X_train[y_train==0,0],X_train[y_train==0,1],color="g")
plt.scatter(X_train[y_train==1,0],X_train[y_train==1,1],color="r")
plt.show()
图1
现在进来一个新的肿瘤样本
x = np.array([8.093607318,3.365731514])
plt.scatter(X_train[y_train==0,0],X_train[y_train==0,1],color="g")
plt.scatter(X_train[y_train==1,0],X_train[y_train==1,1],color="r")
plt.scatter(x[0],x[1],color="b")
plt.show()
图2
上图中的蓝点为新的肿瘤样本
我们回到KNN算法 很简单 就是取最近的k个样本 并让那k个样本来投票来决定该新的样本的结果
通俗一点说呢就是 比如k取3 那就取离这个样本最近的三个样本 在上图中 离该新样本的三个样本都是恶性肿瘤
那么很不幸 经过投票 该样本是恶性肿瘤
再来看另外一个样本
x2 = np.array([5.093607323,3.365731514])
plt.scatter(X_train[y_train==0,0],X_train[y_train==0,1],color="g")
plt.scatter(X_train[y_train==1,0],X_train[y_train==1,1],color="r")
plt.scatter(x2[0],x2[1],color="b")
plt.show()
图3
这个时候就不好判断样本与样本之间的距离了
一般可以使用欧拉距离来计算出样本之间的距离
向量各个元素的差的平方求和然后求平方根
from math import sqrt
distances = [sqrt(np.sum((x2-x_train)**2)) for x_train in X_train]
distances
[1.9900642238939008,
2.538517361455215,
3.749800248123507,
2.0023017806427585,
2.8571121106462054,
2.6831160429772547,
0.6726262862475981,
4.167134159567629,
2.6997546859948547,
3.837563371620197]
使用argsort排序distances
nearest = np.argsort(distances)
nearest
array([6, 0, 3, 1, 5, 8, 4, 2, 9, 7], dtype=int64)
我们假设k=3
top_k为最近的三个结果值
k=3
top_y = [y_train[i] for i in nearest[:k]]
top_y
[1, 0, 0]
from collections import Counter
votes = Counter(top_y)
votes.most_common(1)[0][0]
0
投票结果是0 所有大概估计这个肿瘤样本是良性肿瘤
但是只是大概 为什么呢? 从上图不难看出 该样本离恶性肿瘤的那个样本很近 而离另外两个良性肿瘤样本很远 甚至恶性肿瘤的权重比那两个良性肿瘤样本相加还要多
这个时候就牵扯出另外一个概念
超参数
我们可以考虑最近k个样本与新样本的距离,也可以不考虑
在scikit-learn中封装了关于weights的设置
我们首先来看一下scikit-learn中对KNN的封装
from sklearn.neighbors import KNeighborsClassifier
knn_cls = KNeighborsClassifier(n_neighbors=3,weights="distance")
knn_cls.fit(X_train,y_train)
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=1, n_neighbors=3, p=2,
weights='distance')
x2.shape
(2,)
x2 = x2.reshape(1,-1)
knn_cls.predict(x2)
array([1])
结果是1,是恶性肿瘤,所有在一个算法中使用不同的超参数,往往预测出来的结果大为不同
这是在机器学习领域很重要的一个环节
调参!!!
再想想,在考虑距离的方式上,我们有很多种方式
在这里说三种,其实前两种都属于第三种的一部分
曼哈顿距离 欧拉距离 明可夫斯基距离
在上图红蓝黄均为曼哈顿距离 而绿色为欧拉距离
曼哈顿距离
明可夫斯基距离
不难看出 当p=1时为曼哈顿距离,当p=2时为欧拉距离
来看一下scikit-learn中对于p这个超参数的封装
knn_cls = KNeighborsClassifier(n_neighbors=3,weights="distance",p=1) # 使用曼哈顿距离
knn_cls.fit(X_train,y_train)
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=1, n_neighbors=3, p=1,
weights='distance')
knn_cls.predict(x2)
array([1])
当使用曼哈顿距离时,结果为1
但由于有的超参数之间是有关联的,所以我们一般可使用sklearn中的model_selection中的网格搜索,而网格搜索使用的验证数据准确性的方法是交叉验证,我们不深入探讨交叉验证,我们后面再一起来看如何验证数据的分类准确性
from sklearn.model_selection import GridSearchCV
param_grid = [
{
"weights":["uniform"],
"n_neighbors":[i for i in range(1,6)] # 看从一到10之间的k
},
{
"weights":["distance"],
"n_neighbors":[i for i in range(1,6)],
"p":[i for i in range(1,3)]
}
]
knn_cls = KNeighborsClassifier()
gridSearch = GridSearchCV(knn_cls,param_grid,n_jobs=2,verbose=3) # n_jobs 为调用的核数 verbose为执行过程中信息的详细度,数字越高越详细
gridSearch.fit(X_train,y_train)
Fitting 3 folds for each of 15 candidates, totalling 45 fits
[Parallel(n_jobs=2)]: Done 45 out of 45 | elapsed: 0.7s finished
GridSearchCV(cv=None, error_score='raise',
estimator=KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=1, n_neighbors=5, p=2,
weights='uniform'),
fit_params=None, iid=True, n_jobs=2,
param_grid=[{'weights': ['uniform'], 'n_neighbors': [1, 2, 3, 4, 5]}, {'weights': ['distance'], 'n_neighbors': [1, 2, 3, 4, 5], 'p': [1, 2]}],
pre_dispatch='2*n_jobs', refit=True, return_train_score='warn',
scoring=None, verbose=3)
gridSearch.best_score_
1.0
gridSearch.best_params_
{'n_neighbors': 1, 'weights': 'uniform'}
knn_cls = gridSearch.best_estimator_
knn_cls.predict(x2)
array([1])
在上面我们可以看到有一个best_score是1.0还有其他best_params的参数 说的是当n_neighbors=1,weights=uniform的时候分类准确度最高
而什么是分类准确度呢???
回过头看 当我们拿到一个X_train和y_train的时候,我们并不知道这个训练集是否准确,这个训练集时候符合真实环境,换句话说就是这个训练集能不能用来预测数据 所以我们可以把拿到的训练集分为一个训练集和一个测试数据集
先自己来实现一下 我们使用另外一个更大的sklearn内置的数据集
鸢尾花数据集
先加载一个鸢尾花数据集
from sklearn import datasets
iris = datasets.load_iris()
iris.data.shape
iris.target.shape
(150,)
看一下鸢尾花数据集的四个特征分别是
萼片长度 萼片宽度 花瓣长度 花瓣宽度
iris.feature_names
['sepal length (cm)',
'sepal width (cm)',
'petal length (cm)',
'petal width (cm)']
datas = iris.data
targets = iris.target
我们先把iris的特征赋值为X_train iris的结果赋值为y_train
X_train = iris.data
X_train.shape
(150, 4)
y_train = iris.target
y_train.shape
(150,)
我们把X_train和y_train合并并且打乱,以确保后面的测试训练集有不同的特征和结果
merge_datas = np.hstack([X_train,y_train.reshape(-1,1)])
new_datas = np.random.permutation(merge_datas)
new_X_train,new_y_train = np.hsplit(new_datas,[-1])
我们把训练集和测试集的比例分为8:2
test_size = int(len(new_X_train)*0.2)
new_y_train = new_y_train.reshape(len(new_X_train))
X_train = new_X_train[test_size:]
X_test = new_X_train[:test_size]
y_train = new_y_train[test_size:]
y_test = new_y_train[:test_size]
X_test.shape
(30, 4)
X_train.shape
(120, 4)
y_train.shape
(120,)
y_test.shape
(30,)
我们使用sklearn中的Knn,把X_train和y_train fit到knn中,再predict出X_test的结果
knn_cls = KNeighborsClassifier(6) #k=6
knn_cls.fit(X_train,y_train)
y_predict = knn_cls.predict(X_test)
y_predict
array([0., 0., 0., 1., 2., 0., 0., 2., 0., 0., 1., 1., 0., 0., 1., 0., 1.,
1., 2., 0., 2., 0., 2., 2., 1., 0., 2., 2., 0., 1.])
score = sum(y_predict==y_test)/len(y_test)
score
0.9666666666666667
出现了!!! 该knn算法的分类准确度是0.9666666 约等于 96%
我们使用一下sklearn中的train_test_split 顾名思义就是sklearn中封装的分割训练集测试集的方法
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(iris.data,iris.target,test_size=0.2,random_state=888) #random_state为随机种子
print("shape of X_train:{},X_test:{},y_train{},y_test:{}".format(X_train.shape,X_test.shape,y_train.shape,y_test.shape))
shape of X_train:(120, 4),X_test:(30, 4),y_train(120,),y_test:(30,)
knn_cls = KNeighborsClassifier(6)
knn_cls.fit(X_train,y_train)
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=1, n_neighbors=6, p=2,
weights='uniform')
knn_cls.predict(X_test)
array([1, 2, 2, 2, 2, 1, 1, 2, 2, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 2, 2,
0, 1, 2, 2, 2, 0, 0, 0])
y_test
array([1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 2, 2,
0, 1, 2, 2, 1, 0, 0, 0])
knn_cls.score(X_test,y_test)
0.9333333333333333
使用内置封装的score可以预测出score 结果是0.9333333333333333
再对鸢尾花数据集使用一下grid_search
knn_cls2 = KNeighborsClassifier()
param_grid = [
{
"weights":["uniform"],
"n_neighbors":[i for i in range(1,11)] # 看从一到10之间的k
},
{
"weights":["distance"],
"n_neighbors":[i for i in range(1,11)],
"p":[i for i in range(1,6)]
}
]
gridSearch2 = GridSearchCV(knn_cls2,param_grid,n_jobs=2,verbose=2)
gridSearch2.fit(X_train,y_train)
Fitting 3 folds for each of 60 candidates, totalling 180 fits
[Parallel(n_jobs=2)]: Done 180 out of 180 | elapsed: 1.3s finished
GridSearchCV(cv=None, error_score='raise',
estimator=KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=1, n_neighbors=5, p=2,
weights='uniform'),
fit_params=None, iid=True, n_jobs=2,
param_grid=[{'weights': ['uniform'], 'n_neighbors': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}, {'weights': ['distance'], 'n_neighbors': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'p': [1, 2, 3, 4, 5]}],
pre_dispatch='2*n_jobs', refit=True, return_train_score='warn',
scoring=None, verbose=2)
gridSearch2.best_params_
{'n_neighbors': 4, 'weights': 'uniform'}
knn_cls2 = gridSearch2.best_estimator_
knn_cls2.score(X_test,y_test)
0.9333333333333333
数据归一化
解决方案:将所有的数据映射到同一尺度
样本 | 肿瘤大小(厘米) | 发现时间(天) |
---|---|---|
样本1 | 1 | 200 |
样本2 | 5 | 100 |
可以看出 上面的样本计算出来的欧拉距离其实是被发现的时间决定的
所有我们可以把发现的时间的单位由天化为年
用专业的话来说
数据标准化(归一化)处理是数据挖掘的一项基础工作,不同评价指标往往具有不同的量纲和量纲单位,这样的情况会影响到数据分析的结果,为了消除指标之间的量纲影响,需要进行数据标准化处理,以解决数据指标之间的可比性。原始数据经过数据标准化处理后,各指标处于同一数量级,适合进行综合对比评价。以下是两种常用的归一化方法:
Standardization 均值标准差归一化
数据没有明显边界可使用,比较常用
mean和std分别为对应特征的均值和标准差。量化后的特征将分布在[-1, 1]区间。
normalizaion 最值归一化
大多数机器学习算法中,会选择Standardization来进行特征缩放,但是,normalizaion也并非会被弃置一地。在数字图像处理中,像素强度通常就会被量化到[0,1]区间,在一般的神经网络算法中,也会要求特征被量化[0,1]区间。
使用sklearn中的scale
from sklearn.preprocessing import StandardScaler
standarScaler = StandardScaler()
standarScaler.fit(X_train)
StandardScaler(copy=True, with_mean=True, with_std=True)
X_train = standarScaler.transform(X_train)
X_test = standarScaler.transform(X_test)
knn_cls = KNeighborsClassifier(6)
knn_cls.fit(X_train,y_train)
knn_cls.score(X_test,y_test)
0.9333333333333333
网友评论