K近邻是机器学习中最基础最简单的算法了,没有之一!
最近在复习机器学习、深度学习的算法,从头好好过一遍吧!
1.K近邻分类算法描述
输入:(1)训练样本集D,包括dataX, dataY (2)观测样本x
输出:观测样本x所属的类别y.
step0. 训练集D的输入部分预处理,并记录预处理的使用参数
step1. 指定距离度量,并选择K值
step2. 训练集D内找到预处理的样本x的前K个近邻
step3. 结合指定的决策规则,对x的类别y进行预测
2.三个基本要素
k近邻算法:计算已知类别数据集中的点与当前样本点之间的距离,根据前k个距离最小的样本投票决定目标样本的类别。
由此可知,K近邻算法的三个基本要素为:距离度量、超参数K的确定、决策规则(注:超参数即需要人为给定的参数,这里K一般不超过20)。
典型的距离度量方式与规范化预处理
距离度量方式
计算样本点之间距离的方式:
-
L
p
L_{p}
Lp距离 (闵可夫斯基距离)
L p ( x a , x b ) = ( ∑ i = 1 d ∣ x a ( i ) − x b ( i ) ∣ p ) 1 p L_{p}(x_{a}, x_{b}) = \left ( \sum_{i=1}^{d}\left |x_{a}^{\left ( i \right )} - x_{b}^{\left ( i \right )} \right |^{p} \right )^{\frac{1}{p}} Lp(xa,xb)=(∑i=1d∣∣∣xa(i)−xb(i)∣∣∣p)p1,其中,p>=1 - 欧式距离 ,也叫欧几里得距离,当p=2时
d ( x a , x b ) = ∑ i = 1 d ( x a ( i ) − x b ( i ) ) 2 d(x_{a}, x_{b}) = \sqrt{\sum_{i=1}^{d} (x_{a}^{\left ( i \right )}-x_{b}^{\left ( i \right )})^{2}} d(xa,xb)=∑i=1d(xa(i)−xb(i))2 - 曼哈顿距离 ,也叫绝对值距离,当p=1时
L 1 ( x a , x b ) = ∑ i = 1 d ∣ x a ( i ) − x b ( i ) ∣ L_{1}(x_{a}, x_{b}) = \sum_{i=1}^{d}\left |x_{a}^{\left ( i \right )} - x_{b}^{\left ( i \right )} \right | L1(xa,xb)=∑i=1d∣∣∣xa(i)−xb(i)∣∣∣ - 切比雪夫距离 ,也叫切氏距离、拉格朗日距离,当p=
∞
\infty
∞时
L ∝ ( x a , x b ) = m a x i ∣ x a ( i ) − x b ( i ) ∣ L_{\propto }(x_{a}, x_{b}) = \underset{i}{max}\left | {x_{a}}^{\left ( i \right )}-x_{b}^{\left ( i \right )} \right | L∝(xa,xb)=imax∣∣∣xa(i)−xb(i)∣∣∣
规范化预处理
规范化预处理的必要性:
样本的各特征间量纲的不同,固定量纲下采用不同量纲单位,会导致不同数量级的特征取值,所以在距离度量时,可能产生很大的问题:
大数量级、大动态范围的特征与小数量级、小动态范围的特征同时参与距离度量:起主导作用的前者可能淹没后者特征变化,起主导作用的特征所含的类鉴别信息不一定明显。
- z-score归一化(推荐)
x k ′ = x k − μ k σ k x_{k}{}' = \frac{x_{k} - \mu _{k}}{\sigma _{k}} xk′=σkxk−μk - min-max归一化 (线性映射)
x k ′ = x k − x k m i n x k m a x − x k m i n x_{k}{}' = \frac{x_{k} - x_{kmin}}{x_{kmax} - x_{kmin}} xk′=xkmax−xkminxk−xkmin
超参数K值的选取
m折交叉验证+分类问题评价指标
step1:训练集随机打乱,均分成m等份,每一份的训练样本数目
N
m
\frac{N}{m}
mN
step2:对于每个备选K值,
2-1. for i = 1, … , m do
拿出第i份作为验证集,其余m-1份作为估计集,利用估计集,对验证集的每个样本进行类别预测,得验证集预测错误率
E
r
r
i
(
K
)
Err_{i}\left ( K \right )
Erri(K)
2-2. 估计对应于该备选K值的平均错误率
μ
E
r
r
(
K
)
\mu _{Err\left ( K \right )}
μErr(K)、标准差
σ
E
r
r
(
K
)
\sigma _{Err\left ( K \right )}
σErr(K)
μ
E
r
r
(
K
)
=
1
m
⋅
∑
i
=
1
m
E
r
r
i
(
K
)
\mu _{Err\left ( K \right )} = \frac{1}{m} \cdot \sum_{i=1}^{m} Err_{i}\left ( K \right )
μErr(K)=m1⋅∑i=1mErri(K)
σ
E
r
r
(
K
)
=
1
m
⋅
∑
i
=
1
m
(
E
r
r
i
(
K
)
−
μ
E
r
r
(
K
)
)
2
\sigma _{Err\left ( K \right )} = \sqrt{\frac{1}{m}\cdot \sum_{i=1}^{m}\left ( Err_{i}\left ( K \right )-\mu _{Err\left ( K \right )} \right )^{2}}
σErr(K)=m1⋅∑i=1m(Erri(K)−μErr(K))2
表示为
μ
E
r
r
(
K
)
±
σ
E
r
r
(
K
)
\mu _{Err\left ( K \right )}\pm \sigma _{Err\left ( K \right )}
μErr(K)±σErr(K)
step3:选择最低的
μ
E
r
r
(
K
)
\mu _{Err\left ( K \right )}
μErr(K)对应的K值作为最终选择结果,若同时多个K值对应最小的
μ
E
r
r
(
K
)
\mu _{Err\left ( K \right )}
μErr(K),则选择其中最小的
σ
E
r
r
(
K
)
\sigma _{Err\left ( K \right )}
σErr(K)对应的K值作为最终选择结果。
决策规则
- 胜者为王 (多数表决)——传统的K近邻决策方式
等权投票,相当于每个样本投票的权重均为1。
观测x的K个近邻 N K ( x ) = N K , 1 ( x ) ∪ N K , 2 ( x ) ∪ . . . ∪ N K , C ( x ) N_{K}\left ( x \right ) = N_{K,1}(x)\cup N_{K,2}(x)\cup...\cup N_{K,C}(x) NK(x)=NK,1(x)∪NK,2(x)∪...∪NK,C(x)
来自第j类的近邻数: k j = ∑ x i ∈ N K ( x ) I ( y i = j ) = ∣ N K , j ( x ) ∣ k_{j} = \sum_{x_{i\in N_{K}(x)}}^{}I(y_{i} = j) = \left | N_{K, j}(x) \right | kj=∑xi∈NK(x)I(yi=j)=∣NK,j(x)∣
K = ∑ j = 1 C k j \sum_{j = 1}^{C}k_{j} ∑j=1Ckj
决策规则:若 k l k_{l} kl = m a x j = 1 , 2 , . . . , C k j \underset{j=1,2,...,C}{max}k_{j} j=1,2,...,Cmaxkj,则将x决策为第l类。
特殊情况:有多个类别同时达到票数最多。
处理方法: 选择最近邻样本对应的类别;随机选择其中一个类别;选择距离均值向量最近的样本对应的类别 - 加权投票
N K ( x ) N_{K}(x) NK(x)中K个训练样本类别标号 a 1 a_{1} a1, …, a K a_{K} aK,K个训练样本关于x的距离为 δ a 1 ≤ δ a 2 ≤ . . . ≤ δ a K \delta _{a_{1}} \leq \delta _{a_{2}}\leq ...\leq \delta _{a_{K}} δa1≤δa2≤...≤δaK
统计第j类的决策权重:
W j = ∑ i = 1 K w ( δ a i ) I ( a i = j ) , j = 1 , . . . , C W_{j} = \sum_{i=1}^{K}w(\delta {a_{i}})I(a_{i}=j),j = 1, ..., C Wj=∑i=1Kw(δai)I(ai=j),j=1,...,C
类别决策:若 W l = m a x j = 1 , 2 , . . . , C W j W_{l} = \underset{j=1,2,...,C}{max}W_{j} Wl=j=1,2,...,CmaxWj,则将x决策为第l类。
投票权重计算方式:
A. w ( δ a i ) = 1 ( δ a i ) 2 w(\delta _{a_{i}}) = \frac{1}{(\delta _{a_{i}})^{2}} w(δai)=(δai)21
B. w ( δ a i ) = e − ( δ a i ) 2 w(\delta _{a_{i}}) = e^{-(\delta _{a_{i}})^{2}} w(δai)=e−(δai)2
C. w ( δ a i ) = 1 δ a i w(\delta _{a_{i}}) = \frac{1}{\delta _{a_{i}}} w(δai)=δai1
各近邻的归一化权重: w ( δ a i ) = w ( δ a i ) ∑ j = 1 K w ( δ a j ) , i = 1 , . . . , K w(\delta _{a_{i}}) = \frac{w(\delta _{a_{i}})}{\sum_{j=1}^{K}w(\delta _{a_{j}})},i=1, ..., K w(δai)=∑j=1Kw(δaj)w(δai),i=1,...,K
3.Python语言算法实现
import numpy as np
import math
import operator
from sklearn import datasets
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import train_test_split
def normalization(dataX):
'''z-score归一化
Parameters:
dataX: 输入特征向量
Returns:
dataX1: 归一化之后的dataX
mu: 均值向量
sigma: 标准差向量
'''
mu = dataX.mean(0)
sigma = dataX.std(0)
dataX1 = (dataX - mu) / sigma
return dataX1, mu, sigma
def cross_validation(trainX, trainY, ks, m = 10):
'''m折交叉验证
Parameters:
trainX, trainY: 都是np.array类型,训练集
ks: 备选K值
m: 超参数
Returns:
k: 最终选择的K
'''
skf = StratifiedKFold(n_splits = m, shuffle=True, random_state=0) # 创建一个StratifiedKFold对象,分层 m折 随机打乱
accs = []
for train_ids, test_ids in skf.split(newTrainX, trainY):
# 固定样本划分下
estimationX = newTrainX[train_ids] # 估计集特征
validationX = newTrainX[test_ids] # 验证集特征
estimationY = trainY[train_ids] # 估计集标签
validationY = trainY[test_ids] # 验证集标签
n = estimationX.shape[0]
l = [] # 存放各个K值对应的错误率
for k in ks:
num = 0
for index, x in enumerate(validationX):
y_hat = knn(x, estimationX, estimationY, k)
if y_hat != validationY[index]:
num += 1
acc = num / n
l.append(acc)
accs.append(l)
accs = np.array(accs)
mu = accs.mean(0) # 不同K值对应的平均错误率
sigma = accs.std(0) # 不同K值对应的标准差
v = mu.min() # 最小的错误率
i = mu.argmin() # 最小错误率对应的索引
k = ks[i] # 最小平均错误率对应的K值作为最终选择的K
for index, value in enumerate(mu):
if(value == v):
if(sigma[index] < sigma[i]):
k = ks[index] # 如果多个K取得最小mu,则选择其中标准差小的对应的K
return k
def knn(x, trainX, trainY, k):
'''K近邻
parameters:
x: 输入样本
dataSet: 数据集特征矩阵
labels: 数据集类别标签
ks: 备选K值
returns:
y_hat: x预测类别
'''
# step1: 计算已知类别样本点与当前样本点之间的距离
sqDiffMat = (trainX - x) ** 2
sqDistance = sqDiffMat.sum(axis=1)
distance = sqDistance ** 0.5
# step2: 按距离从小到大排序
sortedDistIndices = distance.argsort()
# step3: 前k个样本投票决策
classCount = dict()
# step3-1: 等权投票
for i in range(k):
label = trainY[sortedDistIndices[i]]
classCount[label] = classCount.get(label, 0) + 1
# step3-2: 加权投票
# for i in range(k):
# position = sortedDistIndices[i]
# label = labels[position]
# d = distance[position]
# classCount[label] = classCount.get(label, 0) + 1 / (d ** 2)
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
y_hat = sortedClassCount[0][0]
return y_hat
def load_data():
iris = datasets.load_iris()
print(iris.keys())
n_samples, n_features = iris.data.shape
print('-----------------------数据集描述-----------------------')
print("样本数,特征数:", n_samples, n_features)
print("第一条样本:", iris.data[0])
print("标签形状:", iris.target.shape)
print("标签:", iris.target)
print("标签名称:", iris.target_names)
print("特征名称:", iris.feature_names)
dataSet = iris.data
labels = iris.target
return dataSet, labels
if __name__ == '__main__':
# 1.获取数据集
dataSet, labels = load_data() # 注意不能在dataSet上做标准化预处理
# 2.划分数据集
trainX, testX, trainY, testY = train_test_split(dataSet, labels, test_size=0.3, random_state=0)
# 3.标准化预处理
newTrainX, mu, sigma = normalization(trainX)
newTestX = (testX - mu) / sigma
# 4.选择合适的K值
ks = [1, 3, 5, 7, 9, 11, 13]
k = cross_validation(newTrainX, trainY, ks)
print('-----------------------模型结果-----------------------')
print('最合适的K值为:', k)
# 5.计算模型正确率
n = testX.shape[0]
cnt = 0
for i, x in enumerate(newTestX):
y_hat = knn(x, newTrainX, trainY, k)
if (y_hat == testY[i]):
cnt += 1
print("模型在测试集上的预测正确率:", cnt / n)
代码执行结果: