背景
我的朋友海伦,一直使用在线约会网站,寻找适合自己的约会对象。约会网站会推荐给她不同的人选,但是她没有从中找到喜欢的人。她总结了一下以前交往过的人有3种类型(不喜欢、魅力一般、极具魅力),但是她不知道网站推荐的人选究竟属于哪一类。
需求:希望分类器能将这些推荐的人选,划分到确切的类别中。
解决思路
/knn.png)
代码
(1)收集数据
(2)准备数据:filematrix1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import numpy as np
#用Python处理文本文件非常容易
def filematrix(filename):
fr = open(filename) #打开文件
fr_lines = fr.readlines()
m = len(fr_lines) #文件文件包含多少行
X_raw = np.zeros((m,3)) #初始化特征矩阵
y_raw = [] #初始化标签列表
i = 0 #初始化指向特征矩阵的索引
mapdict = {'didntLike':1,'smallDoses':2,'largeDoses':3} #将标签的字符串转为数值
for line in fr_lines: #遍历每一行数据
line = line.strip() #删除最后的回车符
line_list = line.split('\t') #用\t分割该行数据
X_raw[i,:] = line_list[0:3] #前三个元素是特征
y_raw.append(mapdict[line_list[-1]]) #最后一个元素是标签
i += 1
return X_raw,y_raw #返回原始特征矩阵、标签
1 | >> X_raw, y_raw = filematrix('datingTestSet.txt') |
(3)归一化:autoNorm1
2
3
4
5
6
7
8
9def autoNorm(X_raw): #输入是一个矩阵,而不是向量
minvals = X_raw.min(0) #每列的最小值
maxvals = X_raw.max(0) #每列的最大值
rangevals = maxvals - minvals #每列的极差
X_norm = np.zeros(X_raw.shape) #初始化特征矩阵
m = X_raw.shape[0] #特征矩阵行数
X_norm = X_raw - np.tile(minvals,(m,1)) #(分子)每个元素减去对应列的最小值
X_norm = X_norm/np.tile(rangevals,(m,1)) #每个元素除以对应列的极差
return X_norm, rangevals, minvals #返回每一列的极差、最小值,是为了之后输入一个特征向量时做预测
1 | >>> X_norm, rangelist, minvals = autoNorm(X_raw) |
(4)knn算法:knnFunc1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def knnFunc(x_test, X_norm, y_raw, k):
m = X_norm.shape[0] #有几个样本(行数)
X_diff = np.tile(x_test,(m,1)) - X_norm #每个训练数据点与新数据的差值
X_sqdiff = X_diff**2 #每个元素取平方
x_sqdiffsum = X_sqdiff.sum(axis=1) #对每一行的元素求和
x_distance = x_sqdiffsum**0.5 #每个元素取根号,得到距离向量
sorted_indice = x_distance.argsort() #升序排序,返回索引
classCount={} #空字典,用来放{'类别':出现次数}
for i in range(k): #提取前k个索引
votellabel = y_raw[sorted_indice[i]] #索引对应的类别
classCount[votellabel] = classCount.get(votellabel,0) + 1 #存在→加1,不存在→为1
sortedClassCount = sorted(classCount.items(), key=lambda x:x[1], reverse=True) #返回以tuple为元素的列表,按照tuple的第二个元素排序
return sortedClassCount[0][0]
(5)测试模型误分类率:datingClassTest1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def datingClassTest():
train_test_ratio = 0.05
X_raw, y_raw = filematrix('datingTestSet.txt') #读取原始数据
X_norm ,_,_ = autoNorm(X_raw) #归一化
m = X_norm.shape[0] #特征矩阵行数
test_num = int(m*train_test_ratio) #测试数据的个数
X_train = X_norm[test_num:m,:]
y_train = y_raw[test_num:m]
errorCount = 0.0 #初始化误分类次数
for i in range(test_num): #遍历测试数据的索引
knn_result = knnFunc(X_norm[i,:], X_train, y_train, 3)
if (knn_result != y_raw[i]):
errorCount += 1.0
error_rate = errorCount/float(test_num) #计算误分类率
return error_rate
1 | >> datingClassTest() |
(6)使用模型1
2
3
4
5
6
7
8
9
10
11
12def classfyPerson():
x1 = float(input('每年获得的飞行常客里程数:')) #手动输入x1、x2、x3的值
x2 = float(input('玩视频游戏所消耗时间百分比:'))
x3 = float(input('每周消费的冰淇淋公升数:'))
X_raw, y_raw = filematrix('datingTestSet.txt')
X_norm, rangevals, minvals = autoNorm(X_raw)
x_pred = np.array([x1,x2,x3])
x_pred = (x_pred - minvals)/rangevals
y_pred = knnFunc(x_pred, X_norm, y_raw, 3)
mlist = ['didntLike','smallDoses','largeDoses']
print('您可能会认为这个人:%s' % (mlist[y_pred-1]))
1 | >> classfyPerson() |
【补充】代码中使用到的函数
(1)文件对象的三个“读”方法:read()、readline()、readlines()
read()每次读取整个文件,通常用于将文件内容放到一个字符串变量中。这对于连续的面向行的处理是比不要的,而且如果文件大于可用内存,则不可能实现这种处理;
readlines()也是一次读取整个文件,自动将文件内容分析成一个列表,列表的元素是每一行字符串(可以用for循环提取)1
2
3
4
5
6
7>> fr = open('datingTestSet.txt')
>> for line in fr.readlines():
line = line.strip() #去掉最后的回车
print(type(line),line)
<class 'str'> 40920 8.326976 0.953952 largeDoses
<class 'str'> 14488 7.153469 1.673904 smallDoses
readline()每次只读取一行,只有遇到回车(\r)或者换行符(\n)才会返回结构,通常比readlines()慢得多,仅当没有足够内存可以一次读取整个文件时,才应该使用它1
2
3
4
5
6
7
8>> fr = open('datingTestSet.txt')
>> while True:
line = fr.readline()
line = line.strip()
print(type(line), line)
<class 'str'> 40920 8.326976 0.953952 largeDoses
<class 'str'> 14488 7.153469 1.673904 smallDoses
(2)np.tile(数组, 数组重复的次数)1
2
3
4
5
6
7
8
9
10
11
12>> np.tile([0,0],2)
array([0,0,0,0]) #在列方向上重复2次,还是一维
>> np.tile([0,0],[1,1]) #在行方向上重复1次,列方向上重复1次
array([[0,0]])
>> np.tile([0,0],[2,1]) #行2次,列1次
array([[0,0]
[0,0]])
>> np.tile([0,0],[1,2]) #行1次,列2次,变成二维
array([[0,0,0,0]])
>> np.tile([0,0],[2,3]) #行2次,列3次
array([[0,0,0,0,0,0]
[0,0,0,0,0,0]])
(3)numpy数组的sum方法1
2
3
4
5
6
7>> a = np.array([[1,2,3],[4,5,6]])
>> a.sum()
21
>> a.sum(axis=0) #每一列的元素相加
array([5, 7, 9])
>> a.sum(axis=1) #每一行的元素相加
array([ 6, 15])
(4)字典的get方法1
2
3
4
5
6
7>> a = {'A':1, 'B':6}
>> a.get('A',0)
1
>> a.get('B',0)
6
>> a.get('C',0)
0
(5)对字典排序的三种方法
①sorted(dict.items(), key=lambda x:x[1])
②import operator; sorted(dict.items(), keys=operator.itemgetter(1))
③sorted(zip(d.values(), d.keys())
注意:sorted是内建函数,它的可选参数①reverse,默认是False,升序排序;②key,为每个元素提取比较值的函数
dict.items返回[(key,value),..]的迭代器,是无序的
lambda x:x[1]取每个tuple的第二个元素1
2
3>> a = {'A':1, 'B':6}
>> b = sorted(a.items(),key=lambda x:x[1], reverse=True)
[('B', 6), ('A', 1)]
(6)matplotlib.pyplot.scatter
参数①x,y 是输入数据,是(n,)的数组
②s 设置数据点的大小,是变量或(n,)的数组,默认为20
③c 颜色序列
④marker 数据点的形状,默认为’o’
⑤cmap Colormap实例,默认None
⑥norm 数据点的亮度0-1,float数据,默认None
⑦alpha 透明度0-1,默认None1
2
3
4
5
6
7
8
9
10
11>> import matplotlib.pyplot as plt
>> fig = plt.figure(figsize=(8,6)) #画板
>> ax = fig.add_subplot(111) #画纸,或者前两个合并fig,ax = plt.subplots(1,1,figsize=(8,6))
>> ax.scatter(一系列参数) #画散点图
>> plt.show()
#plt无法正常显示中文的解决方法
>> from matplotlib.font_manager import FontProperties #导入字体
>> font_set = FontProperties(fname=r'c:\windows\fonts\simsun.ttc',size=15) #字体实例
>> ax.set_ylabel(u'x轴标签',fontproperties = font_set) #散点图的字体设置
>> ax.legend(prop = font_set) #图例的字体设置
【补充】用sklearn实现knn算法,用学习曲线评估
sklearn.neighbors.KNeighborsClassifier(n_neighbors=3, weights=’uniform’, algorithm=’auto’, leaf_size=30, p=2, metric=’minkowski’, metric_params=None)
①n_neighbors k值
②weights 临近实例的权重,可以取三个值 ‘uniform’是默认→权重相同的情况,’distance’权重与距离成反比、自定义函数对权值进行设定
③algorithm 可以取4个值,’auto’、’ball_tree’、’kd_tree’、’brute’
④leaf_size ball_tree和kd_tree树的叶子数量
⑤p 距离度量的p值
⑥metric 距离度量的方法1
2
3
4
5
6
7
8from sklearn.neighbors import KNeighborsClassifier
#提取数据并归一化
X,y_train = file2matrix('datingTestSet2.txt')
X_train ,_ ,_= autoNorm(X)
#创建knn算法实例
knn = KNeighborsClassifier(n_neighbors=3)
sklearn.learning_curve.learning_curve(estimator, X, y, groups=None, train_sizes=array([0.1, 0.33, 0.55, 0.78, 1. ]), cv=’warn’, scoring=None, exploit_incremental_learning=False, n_jobs=None, pre_dispatch=’all’, verbose=0, shuffle=False, random_state=None, error_score=’raise-deprecating’)
①estimator 训练器的名称
②X 训练集特征
③y 训练集标签
④train_sizes 划分出的训练集,占总数据集的比例0-1,也可以输入绝对数量
⑤cv 是整型,交叉验证folds的个数,默认是3
⑥scoring 打分函数
⑦n_jobs 并行运行的作业数,默认是1
⑧random_state 整型→随机数生成器使用的种子
有3个返回值:x轴刻度数组、训练集分数(n_ticks, n_cv_folds)、测试集分数(n_ticks, n_cv_folds)。刻度数可能是:不同的样本数、某一参数的不同值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23from sklearn.learning_curve import learning_curve
import matplotlib.pyplot as plt
train_sizes,train_scores,test_scores=learning_curve(estimator=knn,X = X_train, y = y_train, train_sizes = np.linspace(0.1,1.0,10), cv=10, n_jobs = 1)
train_mean = np.mean(train_scores, axis=1) #每行的平均值(同一样本量不同folds)
train_std = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(train_scores, axis=1)
param_range = np.linspace(0.1,1.0,10) #训练集的比重
plt.plot(train_sizes, train_mean, color='blue', marker='o', markersize=5,label='training accuracy')
plt.fill_between(train_sizes,train_mean+train_std, train_mean-train_std, alpha=0.15, color='blue') #填充两个数据集之间的区域
plt.plot(train_sizes, test_mean, color='green', marker='s',linestyle='--', markersize=5,label='test accuracy')
plt.fill_between(train_sizes,test_mean+test_std, test_mean-test_std, alpha=0.15, color='green')
plt.grid()
plt.ylim([0.8, 1.0])
plt.legend(loc='lower right')
plt.xlabel('Number os training samples')
plt.ylabel('Accuracy')
/学习曲线.png)
训练集和测试集的准确率收敛,并且都很高。