机器学习 | 有监督学习 | K近邻算法 (实现)
1321
2023.07.21
2023.07.21
发布于 未知归属地

以 如何判断葡萄酒种类 为例,展示 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)

部分数据集展示如下:
image.png

二. 数据预处理:
如果不同特征变量的量纲级别相差较大且在建模时相互影响,则可以对数据进行预处理,数据标准化常见方法有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值,从16都看看
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)

注意事项:
交叉验证的数据集应该为训练集,不应包括测试集数据。
运行结果为:
image.png
根据得分和方差可以得出结论: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))

运行结果为:
image.png

五.结论:
从运行结果可以看出该模型的预测效果很差。
一般预测效果差有两个原因:
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)

运行结果:
image.png
2.调参
由于数据量增大了,可以增加超参搜索范围和交叉验证折数。
代码:

score = []
var = []
krange=range(1,21) #设置不同的k值,从120都看看
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)

运行结果:
image.png
可以看到 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))

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

评论 (4)