以 如何判断葡萄酒种类 为例,展示 K近邻算法的实现。
一. 构造数据集:
代码:
# 构造数据集
rowdata = {'颜色深度': [14.13,13.2,13.16,14.27,13.24,12.07,12.43,11.79,12.37,12.04,
14.13,13.2,13.16,14.27,13.24,12.07,12.43,11.79,12.37,12.04],
'酒精浓度': [5.64,4.28,5.68,4.80,4.22,2.76,3.94,3.1,2.12,2.6,
5.64,4.28,5.68,4.80,4.22,2.76,3.94,3.1,2.12,2.6],
'品种': [0,0,0,0,0,1,1,1,1,1,
0,0,0,0,0,1,1,1,1,1]} # 0 代表 “黑皮诺”,1 代表 “赤霞珠”
wine_data = pd.DataFrame(rowdata)
X = np.array(wine_data[['颜色深度', '酒精浓度']]) # 把特征(酒的属性)放在X
y = np.array(wine_data['品种']) # 把标签(酒的类别)放在Y
Xtrain,Xtest,Ytrain,Ytest=train_test_split(X,y,test_size=0.2,random_state=420)部分数据集展示如下:

二. 数据预处理:
如果不同特征变量的量纲级别相差较大且在建模时相互影响,则可以对数据进行预处理,数据标准化常见方法有min-max标准化 和 均值归一化。
代码:
python实现:
data = Xtrain
X_train = (data-np.min(data,axis=0))/(np.max(data,axis=0)-np.min(data,axis=0))sklearn实现:
from sklearn.preprocessing import MinMaxScaler as mms
from sklearn.model_selection import train_test_split
#归一化
MMS_01=mms().fit(Xtrain) #求训练集最大/小值
MMS_02=mms().fit(Xtest) #求测试集最大/小值
#转换
X_train=MMS_01.transform(Xtrain)
X_test =MMS_02.transform(Xtest)注意事项:
应先分数据集,再做标准化
1.如果我们在全数据集上进行归一化,那最小值和极差的选取是会参考测试集中的数据的状况的。因此,当我们归一化后,无论我们如何分割数据,都会由一部分测试集的信息被“泄露”给训练集,这会使得我们的模型效果被高估。
2.在现实业务中,我们只知道训练集的数据,不了解测试集究竟会长什么样,所以我们要利用训练集上的最小值和极差来归一化测试集。
三.调参:
该算法需要调的超参数只有k:
1.通过遍历k值选出模型平均得分最高的k参数;
2.通过K折交叉验证提高模型的泛化能力。
sklearn实现:
score = []
var = []
krange=range(1,7) #设置不同的k值,从1到6都看看
for i in krange:
clf = KNeighborsClassifier(n_neighbors=i)
cvresult = CVS(clf,X_train,Ytrain,cv=4)
score.append(cvresult.mean()) # 每次交叉验证返回的得分数组,再求数组值
var.append(cvresult.var())
print(krange)
print(score)
print(var)注意事项:
交叉验证的数据集应该为训练集,不应包括测试集数据。
运行结果为:

根据得分和方差可以得出结论:k = 1 时模型效果最好。
四.训练模型和预测:
根据调参结果,选择k = 1。
python实现:
def predict(Xtest, Xtrain, Ytrain, k):
return [KNN(x, Xtrain, Ytrain, k) for x in Xtest]
def KNN(new_data, Xtrain, Ytrain, k):
distance = [sqrt(np.sum((x-new_data)**2)) for x in Xtrain]
sort_dist = np.argsort(distance)
topK = [Ytrain[i] for i in sort_dist[:k]]
return pd.Series(topK).value_counts().index[0]
result = predict(X_test, X_train, Ytrain, 1)
print(result, Ytest)sklearn实现:
from sklearn.neighbors import KNeighborsClassifier
# 0 代表 “黑皮诺”,1 代表 “赤霞珠”
clf = KNeighborsClassifier(n_neighbors = 1)
clf = clf.fit(X_train, Ytrain)
result = clf.predict(X_test) # 返回预测的标签
print(result, Ytest)
print("准确率:", clf.score(X_test, Ytest))运行结果为:

五.结论:
从运行结果可以看出该模型的预测效果很差。
一般预测效果差有两个原因:
1.该算法模型并不适合该数据场景的预测;
2.输入的数据本身有问题(如数据量太小,数据值错误)。
本项目是因为数据量太小的原因导致的。
六.改进:
将输入数据改为sklearn自带数据。
1.导入数据
从之前数据量20,维度2 增加到了 数据量178,维度13
代码:
from sklearn.datasets import load_wine
#读取数据集
data = load_wine()
X = data.data
y = data.target # 0琴酒 1雪莉 2贝尔摩德
#DateFrame格式显示
name = ['酒精','苹果酸','灰','灰的碱性','镁','总酚','类黄酮',
'非黄烷类酚类','花青素','颜色强度','色调','od280/od315稀释葡萄酒','脯氨酸', '种类']
data=np.concatenate((X,y.reshape(-1,1)),axis=1)
table=pd.DataFrame(data=data, columns=name)
print(table)运行结果:

2.调参
由于数据量增大了,可以增加超参搜索范围和交叉验证折数。
代码:
score = []
var = []
krange=range(1,21) #设置不同的k值,从1到20都看看
for i in krange:
clf = KNeighborsClassifier(n_neighbors=i)
cvresult = CVS(clf,X_train,Ytrain,cv=5)
score.append(cvresult.mean()) # 每次交叉验证返回的得分数组,再求数组值
var.append(cvresult.var())
print(krange)
print(score)
print(var)运行结果:

可以看到 k = 13 时 效果最佳。
3.训练模型和预测
根据调参结果,选择k = 13。
代码:
# 0琴酒 1雪莉 2贝尔摩德
clf = KNeighborsClassifier(n_neighbors = 13)
clf = clf.fit(X_train, Ytrain)
result = clf.predict(X_test) # 返回预测的标签
print(result)
print(Ytest)
print("准确率:", clf.score(X_test, Ytest))运行结果:

4.结论
可以看到增加数据量后准确率明显提高。该模型可以应用于红酒种类预测。