KNN分类器实战
KNN分类器原理
令 为一个包含 个点 的数据集,其中 为类标签为 的点的子集,。
现给定一个测试点 以及需要考虑的邻居节点数为 ,令 代表从 到它的第 个最近邻居的距离。
根据这个距离我们可以画出一个以测试点 为中心,半径为 的 维超球体,表示为
本式中 表示测试点 到 集合 中的点 的距离。我们这里选取的是欧式距离,即 。
令 表示 的 个最邻近数据中被标注为类 的点的数目
其中, 是数据点真实所属类别。
处的类条件概率密度可估计为
是超球体体积,表示超球体所包含的属于类别 的个数与整体样本中一共类别 的个数的比值。
于是我们有后验概率
由于,所以
因此后验概率为
所以 的预测类为
由于 本身是固定的,所以上式成立,求 所属类就是找到其 个邻居中的多数类。
分类器实现
def str_column_to_int(dataset, column):
"""
将类别转化为int型
@dataset: 数据
@column: 需要转化的列
"""
class_values = [row[column] for row in dataset]
unique = set(class_values)
lookup = dict()
for i, value in enumerate(unique):
lookup[value] = i
for row in dataset:
row[column] = lookup[row[column]]
print(lookup)
return lookup
def cross_validation_split(dataset, n_folds):
"""
使用交叉检验方法验证算法
@dataset: 数据
@n_folds: 想要划分的折数
"""
dataset_split = list()
dataset_copy = list(dataset)
fold_size = int(len(dataset) / n_folds) # 一个fold的大小
for _ in range(n_folds):
fold = list()
while len(fold) < fold_size:
index = randrange(len(dataset_copy))
fold.append(dataset_copy.pop(index))
dataset_split.append(fold)
return dataset_split
def accuracy_metric(actual, predicted):
"""
计算准确率
@actual: 真实值
@predicted: 预测值
"""
correct = 0
for i in range(len(actual)):
if actual[i] == predicted[i]:
correct += 1
return correct / float(len(actual)) * 100.0
def evaluate_algorithm(dataset, algorithm, n_folds, *args):
"""
评估使用的分类算法(基于交叉检验)
@dataset: 数据
@algorithm: 使用的算法
@n_folds: 选择要划分的折数
@*args: 根据使用的分类算法而定,在朴素贝叶斯里面不需要其他的参数
"""
folds = cross_validation_split(dataset, n_folds)
scores = list()
for i in range(len(folds)):
train_set = np.delete(folds, i, axis=0)
# print(train_set)
test_set = list()
for row in folds[i]:
row_copy = list(row)
test_set.append(row_copy)
row_copy[-1] = None
predicted = algorithm(train_set, test_set, *args)
actual = [row[-1] for row in folds[i]]
accuracy = accuracy_metric(actual, predicted)
scores.append(accuracy)
return scores
def calculate_distance(point1, point2, length):
"""
计算两点之间的欧式空间距离
@point1: 数据点1
@point2: 数据点2
@length: 纬度数
"""
distance = 0
for i in range(length):
distance += (point1[i] - point2[i])**2
return sqrt(distance)
def get_neighbors(dataset, testpoint, k):
"""
获取最邻近的K个邻居节点
@dataset: 数据集
@testpoint: 目标测试点
@k: 需要获取的邻居数
"""
dataset = dataset.reshape((-1,5))
distances = []
for i in range(len(dataset)):
dist = calculate_distance(testpoint, dataset[i], len(testpoint)-1)
distances.append((dataset[i], dist))
distances.sort(key=operator.itemgetter(1)) # 根据距离来排序
neighbors = []
for i in range(k):
neighbors.append(distances[i][0])
return neighbors
def determine_class(neighbors):
"""
根据邻居节点类别,判断该簇应当属于哪个类别
@neighbors: 邻居节点列表
"""
classvotes = {}
for i in range(len(neighbors)):
res = neighbors[i][-1]
if (res in classvotes):
classvotes[res] += 1
else:
classvotes[res] = 1
sortedvotes = sorted(classvotes.items(), key=operator.itemgetter(1), reverse=True)
return sortedvotes[0][0] # 票数最多的那一个
def KNN(train, test, args):
"""
KNN分类器
@train: 训练集
@test: 测试集
@args: 其他参数,这里是k
"""
k = int(args['k'])
predictions = list()
for point in test:
neighbors = get_neighbors(train, point, k)
output = determine_class(neighbors)
predictions.append(output)
return(predictions)
使用鸢尾花数据集检验
seed(1)
filename = 'iris.csv'
dataset = pd.read_csv(filename).values
str_column_to_int(dataset, len(dataset[0])-1)
n_folds = 3
k = 5
scores = evaluate_algorithm(dataset, KNN, n_folds, {'k': k})
print('某个折上的准确率: %s' % scores)
print('算法的平均准确率: %.3f%%' % (sum(scores)/float(len(scores))))
结果为
{'Iris-versicolor': 0, 'Iris-setosa': 1, 'Iris-virginica': 2}
某个折上的准确率: [98.0, 98.0, 94.0]
算法的平均准确率: 96.667%
可视化分类结果:
def plot_clustering():
"""
绘制相关联矩阵和结果
"""
# 随机抽样2/3来训练,1/3来预测
train_index = np.random.choice(range(len(dataset)), int(len(dataset)*2/3), replace=False)
test_index = np.array(list(set(np.array([i for i in range(len(dataset))])).difference(set(train_index))))
train = dataset[train_index]
test = dataset[test_index]
prediction = KNN(train, test, {'k': 3})
result = pd.DataFrame(columns=['trained', 'sepal length', 'sepal width', 'petal length', 'petal width', 'predicted', 'class'], index=range(len(dataset)))
result.loc[train_index, 'trained'] = 1
result.loc[test_index, 'trained'] = 0
result.loc[test_index, 'predicted'] = prediction
for i in range(len(dataset)):
result.loc[i, ['sepal length', 'sepal width', 'petal length', 'petal width', 'class']] = dataset[i]
fig = px.scatter_matrix(result, dimensions=["sepal length", "sepal width", "petal length", "petal width", "predicted", "class"],
color="class", symbol="trained")
fig.update_layout(template='none', width=1200, height=1000,
margin=dict(l=50, r=50, t=50, b=50))
fig.show()
plot_clustering()
随交叉检验折数和给定的簇的数量()算法准确率的变化。
fig = make_subplots(rows=1, cols=2, subplot_titles=("Change folds", "Change cluster number"))
scores, index, acc = [], [], []
for i in range(2, 22):
score = evaluate_algorithm(dataset, KNN, i, {'k': 3})
scores.append(list(score))
acc.append(sum(score)/float(len(score)))
index.append([i for j in range(i)])
fig.append_trace(go.Scatter(x=[i + 2 for i in range(20)], y=acc,
mode='lines+markers',
name='mean'), row=1, col=1)
fig.append_trace(go.Scatter(x=sum(index, []), y=sum(scores, []),
mode='markers',
name='each'), row=1, col=1)
scores, index, acc = [], [], []
for j in range(1, 11):
score = evaluate_algorithm(dataset, KNN, 5, {'k': j})
scores.append(list(score))
acc.append(sum(score)/float(len(score)))
index.append(j)
fig.append_trace(go.Scatter(x=[i + 1 for i in range(10)], y=acc,
mode='lines+markers',
name='mean-acc'), row=1, col=2)
fig.update_layout(height=600, width=1200, template='none')
fig.update_yaxes(title_text="Accuracy", row=1, col=1)
fig.update_yaxes(title_text="Accuracy", row=1, col=2)
fig.update_xaxes(title_text="Folds Num", row=1, col=1)
fig.update_xaxes(title_text="Cluster Num", row=1, col=2)
fig.show()