本专栏计划借助Pandas与sklearn重新实现书中的实战案例。
k-近邻算法
1. KNN算法流程
对未知类别属性的数据集中的每个样本依次执行以下操作:
1、计算已知类别数据集中的点与当前点之间的距离;
2、按照距离递增次序排序;
3、选取与当前点距离最小的k个点;
4、确定前k个点所在类别的出现频率;
5、返回前k个点所出现频率最高的类别作为当前点的预测分类。
2. KNN改进约会网站的配对效果
2.1 数据准备:从文本中解析数据
海伦女士一直使用在线约会网站寻找适合自己的约会对象,她发现自己交往过的人可以进行如下分类:
不喜欢的人、魅力一般的人、极具魅力的人
海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet2.txt
中,每个样本数据占据一行,总共有1000行。样本主要包含以下三个特征:
1.每年获得的飞行常客里程数
2.玩视频游戏所消耗时间百分比
3.每周消费的冰淇淋公升数
#从文本中解析数据
import pandas as pd
def file2matrix(filename):
"""
parameters:
filename
return:
dataset, labels
"""
# provided sample is tab separated and no columns name
dataset = pd.read_csv(filename, sep='\t',header=None, names=["frequentFlyerMiles", "VideoGamePlayed", "IceCreamEaten", "labels"])
#print(dataset)
features_matrix = dataset.iloc[:, :3]
labels = dataset.iloc[:,3]
return features_matrix, labels
dataset, labels = file2matrix('datingTestSet2.txt')
dataset.head()
2.2 数据可视化:散点图
import matplotlib.pyplot as plt
def show_data(dataset, labels):
color_label = []
for i in labels:
if i == 1:
color_label.append('green')#dislike
elif i == 2:
color_label.append('blue')#smallDoses
elif i == 3:
color_label.append('yellow')#largeDoses
fig = plt.figure(figsize=(15,5))
ax = fig.add_subplot(131)
ax.set_xlabel('玩视频游戏所占时间比')
ax.set_ylabel('每周消费的冰淇淋公斤数')
ax.scatter(dataset.iloc[:,1], dataset.iloc[:,2], c=color_label)
ax = fig.add_subplot(132)
ax.set_xlabel('每年的飞行里程数')
ax.set_ylabel('玩视频游戏所占时间比')
ax.scatter(dataset.iloc[:,0], dataset.iloc[:,1], c=color_label)
ax = fig.add_subplot(133)
ax.set_xlabel('每周消费的冰淇淋公斤数')
ax.set_ylabel('每年的飞行里程数')
ax.scatter(dataset.iloc[:,2], dataset.iloc[:,0], c=color_label)
plt.show()
show_data(dataset, labels)
通过可视化数据,我们似乎也能发现一些规律,例如从子图二中可以发现海伦似乎更喜欢那些每年有一定飞行里程数并且又有一定的玩游戏时间占比的约会对象。
2.3 数据处理:归一化数值
在使用欧氏距离计算样本之间的距离时,差值大的属性对计算结果的影响也较大,而在上述三个特征中,每年获取的飞行常客里程数对于计算结果的影响将远远大于其他两个特征-玩视频游戏所耗时间占比和每周消费冰淇淋公斤数的影响。而产生这种现象的唯一原因,仅仅是因为飞行常客里程数远大于其他的特征值。
海伦认为这三种特征是同等重要的,因此作为三个等权重的特征之一,飞行常客里程数并不应该如此严重地影响到计算结果。
在处理这种不同取值范围的特征值时,我们通常采用的办法是将数值归一化,例如将取值范围变成0到1之间。
def normfeature(x):
x_min = x.min()
x_max = x.max()
x = (x-x_min) / (x_max-x_min)
return x, x_min, x_max-x_min
2.4 构建KNN模型
from sklearn import neighbors
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(dataset, labels, test_size=0.1)
clf = neighbors.KNeighborsClassifier(n_neighbors=3)
clf.fit(x_train, y_train)
#print(clf.get_params()) # 模型参数
clf.score(x_test, y_test) # 测试集得分
#模型存储与加载
#import joblib
#joblib.dump(clf, 'model.pkl')
#clf_knn = joblib.load('model.pkl')
下面是对KNeighborsClassifier
模型的一些其余实验:
对测试集前10个样本进行预测
print(y_test[:10])
print(x_test[:10])
clf.predict(x_test[:10]) #预测
可以发现编号为200、624的样本被预测错了, 笑哭…,接下来的实验以数据集中编号为624的样本进行。
print(dataset.iloc[624,:])
print(labels.iloc[624])
print(x_test.iloc[7].values.reshape(1,-1))
print(clf.predict(x_test.iloc[7].values.reshape(1,-1))) #预测
寻找测试样本的K-近邻
my_neighbors = clf.kneighbors(x_test.iloc[7].values.reshape(1,-1))
print(my_neighbors)
for i in my_neighbors[1][0]:
#print(labels[i]) 错误示范 注意是测试集中样本索引
print(y_train.iloc[i])
可以看到,三个近邻确实投票将测试样本分成了类别1
2.5 模型使用:构建可用系统
接下来我们使用模型来对海伦的约会对象进行分类。
import numpy as py
def classifyperson():
resultlist = ['not at all', 'in small doses', 'in large doses']
percentTats = float(input("玩视频游戏所消耗时间百分比:"))
ffMiles = float(input("每年获得的飞行常客里程数:"))
iceCream = float(input("每周消费的冰淇淋公升数:"))
# 生成NumPy数组,测试集
inArr = np.array([ffMiles, percentTats, iceCream])
# 测试集归一化
norminArr = (inArr - x_min) / ranges
print(norminArr)
result = clf.predict(norminArr.values.reshape(1, -1))
#print(result, type(result))
print("你可能%s这个人" % (resultlist[result[0] - 1]))
classifyperson()
3. KNN实现手写识别系统
3.1 数据准备:将图像处理为向量
import pandas as pd
#显示所有列
pd.set_option('display.max_columns', None)
#显示所有行
pd.set_option('display.max_rows', None)
import numpy as np
def img2vector(filename):
"""
convert img text file to vector
return: 1*1024 vector
"""
dataframe = pd.read_fwf(filename, widths=[1]*32, header=None)
return dataframe.values.reshape(-1)
#return np.ravel(dataframe) #亦可行
testvector = img2vector('testDigits/0_0.txt')
print(testvector)
3.2 数据集处理
import os
def handwriting():
# preparing training dataset
train_dir = os.listdir('trainingDigits')
m = len(train_dir)
#print(m)
train_labels = []
train_data = []
for i in range(m):
file_name = train_dir[i]
#print(file_name)
str_name = file_name.split('.')[0] # 0_0
str_class = str_name.split('_')[0] # 0
train_labels.append(int(str_class))
train_data.append(img2vector('trainingDigits/%s' % file_name))
#print(len(train_labels))
#print(len(train_data))
train_labels = pd.Series(train_labels)
#print(train_labels)
train_data = pd.DataFrame(train_data)
#print(train_data)
#print(type(train_data))
print('training dataset has been prepared')
test_dir = os.listdir('testDigits')
m_test = len(test_dir)
test_labels = []
test_data = []
for i in range(m_test):
file_name = test_dir[i]
#print(file_name)
str_name = file_name.split('.')[0] # 0_0
str_class = str_name.split('_')[0] # 0
test_labels.append(int(str_class))
test_data.append(img2vector('testDigits/%s' % file_name))
#print(len(train_labels))
#print(len(train_data))
test_labels = pd.Series(test_labels)
#print(train_labels)
test_data = pd.DataFrame(test_data)
#print(train_data)
#print(type(train_data))
print('test dataset has been prepared')
return train_data, train_labels, test_data, test_labels
train_data, train_labels, test_data, test_labels = handwriting()
3.3 模型构建
clf = neighbors.KNeighborsClassifier(n_neighbors=3)
clf.fit(train_data, train_labels)
clf.score(test_data, test_labels)
test_predict = clf.predict(test_data) #预测
print(test_predict)
print(sum(test_predict!=test_labels)) #12
4. KNN总结
KNN优点:
- 简单易用,原理较为简单,本文中只给出了分类的实例,也可以用于回归问题。
- 可用于数值型和标称型数据。
- 只需要记住所有的训练样本,无需额外的训练过程等。
KNN缺点:
- 计算复杂度高,实际使用时可能非常耗时。
- 难以处理样本不平衡问题。
- 无法给出数据的内在含义。
5. 参考链接
1、Pandas input/output 官方文档
2、本书源代码 Github地址 基于Python2.6
3、sklearn.neighbors 官方文档