A--最近邻分类器-KNN
#导入必要的包 import numpy as np import pandas as pd import matplotlib as mpl import matplotlib.pyplot as plt %matplotlib inline
构建一个KNN分类器
In [6]:
def classify0_1(train,test,k):#train数据集 test 测试集 k=k值 n = train.shape[1] - 1 m = test.shape[0] result = [] for i in range(m): #利用广播计算测试集每一行分别对训练集求距离 得到Series 并转换成list赋值给dist dist = list(((train.iloc[:, :n] - test.iloc[i, :n]) **2).sum(1)) #得到距离数值与标签列生成的DataFram dist_l = pd.DataFrame({'dist': dist, 'labels': (train.iloc[:,n])}) #按照距离排序(默认升序)截取前K行 dr = dist_l.sort_values(by = 'dist')[: k] #对截取的前K个标签进行计数 re = dr.loc[:, 'labels'].value_counts() #截取排名第一的标签赋值给result result.append(re.index[0]) #创建一个Series result = pd.Series(result) #在测试集创建一列,并且赋值是result test['predict'] = result return test
In [2]:
#测试分类器是否能正常工作 def createDataSet():#创建一组数据 group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]]) labels = ['A','A','B','B'] return group, labels
In [7]:
group, labels = createDataSet()#维度不一致用vstack进行纵拼接,横拼接用hstack train = np.vstack([group, [0, 0]]) labels.append('B')
In [8]:
train
Out[8]:
In [10]:
labels
Out[10]:
In [11]:
#然后生成DataFrame train = pd.DataFrame({'x1': train[:, 0], 'x2': train[:, 1], 'labels': labels}) train = train.reindex(['x1'] + ['x2'] + ['labels'], axis=1)#生成DataFrame是无序的需要用reindex 排序
In [12]:
#创建测试集 p1 = [1, 2] p2 = [0, 1] test = pd.DataFrame({'x1':p1, 'x2':p2})
In [16]:
test #注意 创建DataFrame时,值是看成列向量处理的
Out[16]:
In [18]:
result=classify0_1(train,test,3)#分类器运行正常 result
Out[18]:
让我们进一步完善我们的模型
可视化展示
In [19]:
#修改列标 result.columns = ['x1', 'x2', 'labels'] result
Out[19]:
In [20]:
#合并数据集 train input = pd.concat([train, result], ignore_index=True)#concat 拼接函数 ignore_index=True 忽略我们的索引 从新排列 input
Out[20]:
In [21]:
#添加2列用于作图,第一列区分标签,第二例区分测试集与训练集 input['Ind1'] = 1 for i in range(input.shape[0]): if(input.iloc[i, 2] == 'B'): input.iloc[i, 3] = 0 input['Ind2'] = [1, 1, 1, 1, 1, 0.5, 0.5] input
Out[21]:
In [22]:
#注:scatter 画散点图可以针对每个点做定制化处理 s大小 c 颜色 plt.scatter(input.iloc[:, 0], input.iloc[:, 1],s=200*input.iloc[:, 4], c=input.iloc[:, 3])
Out[22]:
用鸢尾花数据执行算法
In [40]:
iris = pd.read_csv("iris (1).txt",header = None)# header = 表明第一行不是标题 iris.columns = ['sepal_length_cm', 'sepal_width_cm', 'petal_length_cm', 'petal_width_cm', 'class'] iris.head()
Out[40]:
In [41]:
iris.shape
Out[41]:
手动区分训练集与测试集
In [42]:
#s手动区分训练集与测试集 import random def randSplit(dataSet, rate):# dataset 数据集 rate 训练集抽取比列 #截取索引并打乱排序 l = list(dataSet.index) random.shuffle(l) #将索引返回给数据 dataSet.index = l n = dataSet.shape[0] m = int(n * rate) #分别抽取训练集与测试集 train = dataSet.loc[range(m), :] test = dataSet.loc[range(m, n), :] #恢复索引 dataSet.index = range(dataSet.shape[0]) test.index = range(test.shape[0]) return train, test
In [43]:
train, test=randSplit(iris,0.5)
In [45]:
train.shape
Out[45]:
In [46]:
test.shape
Out[46]:
In [48]:
classify0_1(train,test,3)#因为鸢尾花数据的良好特性,所以分类结果非常完美
用一个不那么完美的数据测试
In [90]:
date=pd.read_table("datingTestSet.txt",header=None) date.head()
Out[90]:
发现数据需要进行去量纲,先写好归一化函数
In [52]:
#0-1标准化 def MaxMinNormalization(dataSet): maxDf = dataSet.max() minDf = dataSet.min() normSet = (dataSet - minDf) / (maxDf - minDf) return normSet
In [54]:
#Z-score归一化 def Z_ScoreNormalization(dataSet): stdDf = dataSet.std() meanDf = dataSet.mean() normSet = (dataSet - meanDf) / stdDf return normSet #两者区别在于 标准化容易受极端值影响,需要去除极端值,而归一化不受极端值影响但是运算量较大
In [67]:
#sigmod压缩法 def sigmodNormalization(dataSet): normSet = 1 / (1 + np.exp(-dataSet)) return normSet
In [150]:
#标准化之后与原来数据集的最后一列拼接 date= pd.concat([MaxMinNormalization(date.iloc[:, :3]), date.iloc[:, 3]], axis=1) date.head()
Out[150]:
In [65]:
#2-8比列切分测试集与训练集 date_train,date_test=randSplit(date,0.8)
In [88]:
#K值取2进行分类 result=classify0_1(date_train,date_test,2) result.head()
Out[88]:
下面进行模型效力判定
In [83]:
#首先封装一个准确率判断模型 def accuracyCalculation(dataSet): m = dataSet.shape[0] #如果后一列数据=前一列数据,对返回为True的行计数 res = (dataSet.iloc[:, -1] == dataSet.iloc[:, -2]).value_counts() acc = res.loc[True] / m print("Model accuracy is :{}".format(acc) ) return acc
In [84]:
accuracyCalculation(result)#准确率96.5%
Out[84]:
二分类问题的混淆矩阵
In [101]:
#dataSet 为数据集 pos为数据集中为1 的变量 neg 为数据集中为 0 的变量 在执行中需要人为指定 def confusionMatrix(dataSet,pos,neg): TP = dataSet.loc[(dataSet.iloc[:, -1] == pos) & (dataSet.iloc[:,-2] == pos),].shape[0] FN = dataSet.loc[(dataSet.iloc[:, -1] == neg) & (dataSet.iloc[:,-2] == pos),].shape[0] TN = dataSet.loc[(dataSet.iloc[:, -1] == neg) & (dataSet.iloc[:,-2] == neg),].shape[0] FP = dataSet.loc[(dataSet.iloc[:, -1] == pos) & (dataSet.iloc[:,-2] == neg),].shape[0] dataSet_ac = (TP + TN) / (TP + TN + FP + FN) dataSet_pr = TP / (TP + FP) dataSet_re = TP / (TP + FN) dataSet_sp = TN / (TN + FP) dataSet_F = 2 * dataSet_pr * dataSet_re / (dataSet_pr + dataSet_re) print("模型准确率为:{}".format(dataSet_ac)) print("模型精确度为:{}".format(dataSet_pr)) print("模型召回率为:{}".format(dataSet_re)) print("模型特异度为:{}".format(dataSet_sp)) print("模型F指标为:{}".format(dataSet_F)) return [dataSet_ac, dataSet_pr, dataSet_re, dataSet_sp, dataSet_F]
In [97]:
#为了测试混淆矩阵我们抽取出只有2分类问题的数据 cla = (date.iloc[:, 3] == 'smallDoses') | (date.iloc[:, 3] == 'didntLike') dating_part = date.loc[cla,] dating_part.iloc[:, -1].value_counts() dating_part.index = range(dating_part.shape[0]) dating_part.head()
Out[97]:
In [98]:
date_train,date_test=randSplit(dating_part,0.8)
In [99]:
result=classify0_1(date_train,date_test,2)
2
result.head()
Out[99]:
In [102]:
confusionMatrix(result,"smallDoses","didntLike")
Out[102]:
KNN模型计算距离之后实行一人一票制,下面我们加入权重,对各点的票数加以区分 惩罚因子公式为w=1/d(x',x)**2
In [105]:
def classify0_2(train,test,k): n = train.shape[1] - 1 m = test.shape[0] result = [] for i in range(m): dist = list(((train.iloc[:, :n] - test.iloc[i, :n]) ** 2).sum(1)) dist_l = pd.DataFrame({'dist': dist, 'labels': (train.iloc[:, n])}) dr = dist_l.sort_values(by = 'dist')[: k] dr['re'] = 1 / dr.iloc[:, 0] re = dr.groupby('labels').sum() re.sort_values(by = 're', ascending=False) result.append(re.index[0]) result = pd.Series(result) test['predict'] = result return test
In [115]:
date_train,date_test=randSplit(date,0.8)
In [119]:
result1=classify0_1(date_train,date_test,3)
result2=classify0_2(date_train,date_test,3)
In [117]:
accuracyCalculation(result1)
Out[117]:
In [118]:
accuracyCalculation(result2)
Out[118]:
创建一个K值学习曲线
In [122]:
def kLearningCurve(classify, train, test, k): """ 说明: classify:此处输入我们的分类器classify0_1或classify0_1 teain:输入训练集 test:输入测试集 K :输入我们的K值取值范围 1-k 左右均包含 accuracyCalculation:这之前自定义的准确率判别函数 """ yAc = [] for i in range(k): yAc.append(accuracyCalculation(classify(train, test, i+1))) plt.plot(range(1, k+1), yAc, '-o',color='black') return yAc
In [124]:
date_train,date_test=randSplit(date,0.8)
In [125]:
kLearningCurve(classify0_1, date_train,date_test,10)
Out[125]:
In [126]:
kLearningCurve(classify0_2, date_train,date_test,10)
Out[126]:
可见加入惩罚因子之后模型表现较为稳定,下面试一试加入交叉验证看看效果
In [129]:
#首先利用index乱序方法对数据进行随机等分切分 def randSplit_1(dataSet, n): """说明: dataset:我的数据集 n :我需要切分成多少份 """ l = list(dataSet.index) random.shuffle(l) dataSet.index = l m = dataSet.shape[0] splitSet = [] k = m / n for i in range(n): if i < (n-1): splitSet.append(dataSet.loc[range(i*int(k),(i+1)*int(k)), :]) else: splitSet.append(dataSet.loc[range(i*int(k), m), :]) dataSet.index = range(dataSet.shape[0]) return splitSet
In [130]:
#试一试效果 sp = randSplit_1(date, 10)
In [132]:
sp[2].shape
Out[132]:
In [135]:
sp[0].head()
Out[135]:
在此基础上创建完整的交叉验证自定义函数
In [137]:
def crossVali(dataSet, randSplit, classify, n, k): """ 交叉验证函数说明: dataSet:进行分类测试的数据集 randSplit:自定义随机切分函数 classify:自定义KNN 分类器 n:数据等分个数 k:KNN中选取最近邻个数 accuracyCalculation:自定义准确率计算函数 """ sp = randSplit(dataSet, n)#将数据切人完毕 result = np.array([]) for i in range(n): test = sp[0]#取第一份数据作为测试集 del sp[0]#在原数据中删除位置 train = pd.concat(sp)#合并剩下的数据集作为训练集 train.index = range(train.shape[0])#更新索引 test.index = range(test.shape[0]) test = classify(train, test, k)#开始分类 result = np.append(result, accuracyCalculation(test))#将准确率返回result test = pd.DataFrame(test.drop(['predict'], axis = 1))#删除分类之后的标签列 sp.append(test)#第一份数据使用完成之后,添加会原数据中 return result, result.mean(), result.var()
In [139]:
crossVali(date,randSplit_1,classify0_1, 10, 3)
Out[139]:
由此可见数据集中训练集与测试集的切分问题对模型是有影响的
下面将交叉验证函数与K值学习曲线嵌套在一起,利用交叉验证的均值来修正K值学习曲线的准确率结果,来选取K值
In [141]:
def kLearningCurve_1(dataSet, classify, n, k): """ K值学习曲线参数说明: dataSet:我们的数据集 classify:用于指定分类函数 n :指定交叉验证中数据集切分数量 K : 指定K值选取范围,1-K,左右均包含 """ yAc_mean = [] yAc_up = [] yAc_down = [] for i in range(k): result_cv, result_mean, result_var = crossVali(dataSet,randSplit_1, classify, n, i+1) yAc_mean.append(result_mean) yAc_up.append(result_mean+result_var) yAc_down.append(result_mean-result_var) plt.plot(range(1, k+1), yAc_mean, '-o',color='black') plt.plot(range(1, k+1), yAc_up, '--o',color='red') plt.plot(range(1, k+1), yAc_down, '--o',color='red') return yAc_mean, yAc_up, yAc_down #用均值反应集中趋势,用方差表示离中程度,当方差较大时均值的效力将被削弱
In [142]:
kLearningCurve_1(date,classify0_1,10,10)
Out[142]:
In [ ]:
K值的选取,一般选择拐点,且方差较小的K
In [151]:
import time %time kLearningCurve_1(date,classify0_2,10,10)
Out[151]:
In [149]:
date.iloc[:,3].value_counts()
Out[149]:
In [ ]:
KNN的sklean 方法
In [ ]:
from sklearn.neighbors import KNeighborsClassifier k = 3 clf = KNeighborsClassifier(n_neighbors=k) clf.fit(group, labels)