kNN示例: 改进约会网站的配对结果
从文本解析数据
- 已经有文本数据 datingTestSet2.txt.
- 有1000个样本.
- 每个样本有三种特征:每年获得的飞行常客里程数, 玩视频游戏所耗时间百分比, 每周消耗的冰激凌公升数.
使用到的函数
主要是文件处理的函数.
- 函数open()
用于打开一个文件,并返回文件对象.
如果该文件无法被打开,会抛出 OSError.
注意: 使用 open() 函数一定要保证关闭文件对象,即调用 close() 函数.
格式: open(file, mode=‘r’)
- 方法 f.readlines()
用于读取文件中的所有行,它和调用不指定 size 参数的 read() 函数类似,只不过该函数返回是一个字符串列表,其中每个元素为文件中的一行内容.
和 readline() 函数一样,readlines() 函数在读取每一行时,会连同行尾的换行符一块读取.
f= open("students.txt")
lines = f.readlines()
print(lines)
['James 12 m\n', 'Chris 13 m\n', 'Tom 11 m\n', 'Lucy 12 f']
- 函数 np.zeros((m,n))
返回mxn大小的全0数组.
- 字符串的 s.strip() 方法
Python strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
注意: 该方法只能删除开头或是结尾的字符,不能删除中间部分的字符。
★注意: python中字符串是不可变对象,所以使用strip方法时,返回的是一个新的字符串对象,而不是在原先的字符串上改变。
f= open("students.txt")
lines = f.readlines()
print(lines)
[print(line) for line in lines]
[print(line.strip()) for line in lines]
['James 12 m\n', 'Chris 13 m\n', 'Tom 11 m\n', 'Lucy 12 f']
James 12 m
Chris 13 m
Tom 11 m
Lucy 12 f
James 12 m
Chris 13 m
Tom 11 m
Lucy 12 f
['James 12 m\n', 'Chris 13 m\n', 'Tom 11 m\n', 'Lucy 12 f']
- 字符串 s.split()方法
split() 通过指定分隔符对字符串进行切片,如果第二个参数 num 有指定值,则分割为 num+1 个子字符串。
语法: str.split(str=“”, num=string.count(str))
返回字符串列表.
line = lines[0]
split_line = line.split()
print(split_line)
['James', '12', 'm']
程序
- 书上的数据集有三个特征,但是我下载下来的text文件里只有两个特征的数据,所以这里dataSet只提取原数据前两列。
(缺少 每周消耗的冰激凌公升数)
def file2matrix(file_name):
"""目的:将文本文件变成numpy数组"""
f = open(file_name) # 获取文件对象
lines = f.readlines() # 按行读取文件,返回每一行组成的字符串列表
len_data = len(lines)
data_set = np.zeros((len_data, 2)) # 预分配数据集矩阵
ind = 0 # 记录行标
labels = []
for line in lines:
line = line.strip('\n') # 先将行末的换行符去掉
list_from_line = line.split('\t') # 按tab隔开将每行字符串变成一个字符串列表
data_set[ind, :] = list_from_line[:2] # 将一行中的前两个元素提取为特征
labels.append(int(list_from_line[-1]))
ind += 1
return data_set, labels
dating_mat, dating_labels = file2matrix('datingTestSet2.txt')
print(dating_mat, dating_labels[:10], sep='\n')
[[4.0920000e+04 8.3269760e+00]
[1.4488000e+04 7.1534690e+00]
[2.6052000e+04 1.4418710e+00]
...
[2.6575000e+04 1.0650102e+01]
[4.8111000e+04 9.1345280e+00]
[4.3757000e+04 7.8826010e+00]]
[3, 2, 1, 1, 1, 1, 3, 3, 1, 3]
- 直接从text文件里读取的数字是字符串格式。
- 读取出的python list里的数是字符串形式,但是直接转化成numpy array时,会自动帮助转化成数字格式。比如函数中的这一行:
data_set[ind, :] = list_from_line[:2]
- 以上dataSet数组在读取文件数据前,使用zeros进行预分配了。然后借助index来把文件数据放置dataSet的合适位置。
- python list 是可变对象,也就是能在原对象上改变这个对象,所以可以append等等操作。
- numpy array需要添加一行或者一个元素,可以用np.append(),np.row_stack(),np.column_stack(),np.concanate()…
Matplotlib散点图
- 需要先导入matplotlib模块
- 导入matplotlib模块中的pyplot,重命名为plt
– Matplotlib是Python的绘图库,其中的pyplot包封装了很多画图的函数。
– Matplotlib.pyplot 包含一系列类似 MATLAB 中绘图函数的相关函数。
– 每个 Matplotlib.pyplot 中的函数会对当前的图像进行一些修改,例如:产生新的图像,在图像中产生新的绘图区域,在绘图区域中画线,给绘图加上标记,等等……
– Matplotlib.pyplot 会自动记住当前的图像和绘图区域,因此这些函数会直接作用在当前的图像上。
来自:https://www.sohu.com/a/343708772_120104204
- List item
# 画散点图进行数据分析
# 散点图的横纵坐标都是特征
# 使用matplotlib库
fig = plt.figure(1) # 创建画布对象,并且将编号设置为1
ax = fig.add_subplot(111) # 创建副画布对象
ax.scatter(dating_mat[:, 0], dating_mat[:, 1], s=5) # 在ax这个画布上画散点图
plt.show() # 展示所有画布
figure对象是没有scatter()方法的,ax对象有scatter()、legend()之类的方法。在这应该也是使用add_subplot(111)的原因。
- 使用色彩标记不同分类的点
将上面的scatter那一行换成下面加长的:
ax.scatter(dating_mat[:, 0], dating_mat[:, 1], 15 * np.array(dating_labels), 15 * np.array(dating_labels)) # 在ax这个画布上画散点图
详细解释一下:scatter第三个位置默认是参数s,调整点的大小,这里使用dating_labels序列来控制前面dating_mat对应的点的大小。dating_labels是1-3的整数,所以dating_mat里属于不同类别的点会被赋予不同的大小。
scatter第四个位置默认是参数c,调整颜色,同理,属于不同分类的点会被赋予不同颜色。也说明颜色参数c不仅可以接受字符串,也可以接受整数。
- 再完善一下,加上标题,坐标轴名称,标签。【默认不支持添加中文标题和轴标题,菜鸟有解决办法,之后补上】
- 将数据分为三个部分画图,但是在一个坐标上。
marker_label = [1, 2, 3]
for i in range(1, 4):
index_aux = (np.array(dating_labels) == i)
ax2.scatter(dating_mat[index_aux, 0], dating_mat[index_aux, 1], label=marker_label[i - 1], s=i * 15)
分三部分画的时候,会自动给不同的部分添加不同的颜色。
准备数据:归一化数值
- 根据距离公式,发现里程数对计算结果的影响要比游戏时间多得多,因为它的差值跨度很大,所以需要对数据进行归一化,使得每个特征对结果的影响权重是一样的。(认为每个特征的重要性相同)
- 归一化公式:(newValue = (oldValue - minValue) / (maxValue- minValue)
- 注意:归一化是对数据中 相同特征为一组 进行归一化。 表示 不同特征对结果的影响应该是相同的。
- 定义一个归一化函数来给数据进行归一化。
# 归一化函数
def autoNorm(dataSet):
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
r_num = dataSet.shape[0]
ranges = maxVals - minVals
normDataSet = (dataSet - np.tile(minVals, (r_num, 1))) / (np.tile(ranges, (r_num, 1)))
return normDataSet, ranges, minVals
测试数据
- 测试分类器的效果。
- 通常使用错误率进行评估。分类器给出错误结果的次数 / 测试数据的总数
- 一般只提供已有数据的90%作为训练样本,而10%作为测试样本。
- 测试数据应该是随机选择的。 但是这个约会数据并没有按照特定目的来排序,所以可以随意选择10%。这里选前10%。
- 定义一个计数器变量,每次分错就+1.
def datingClassTest():
# 准备数据
hoRatiao = 0.1
dating_mat, labels = file2matrix('datingTestSet2.txt') # 从文件提取数据
norm_dating_mat, ranges, min_vals = autoNorm(dating_mat) # 处理数据,归一化数据
# 提取测试数据,在总的数据上提取10%,由于数据本来就是随机排序,不需要随机抽取数据进行测试,直接选取前10%
num_data = dating_mat.shape[0] # 总的数据数量
num_test = int(hoRatiao * num_data) # 要测试的数据数量
error_count = 0 # 错误计数器
for i in range(num_test):
res = classify0(norm_dating_mat[i], norm_dating_mat[num_test:], labels[num_test:], 3)
'''norm_dating_mat[i]是要分类的向量, norm_dating_mat[num_test:]是训练特征集,labels[num_test:]训练标签集,3是k'''
print("the classifier came back with %d, the real answer is %d" % (res, labels[i]))
if not res == labels[i]: error_count += 1 # 如果分错,错误计数器加1
error_rate = error_count / num_test
print("the total error rate is : ", error_rate)
return error_count / num_test
可以修改k值,hoRatiao,观察对错误率有何影响。
使用算法:构建完整可用系统
- 写一个函数,用户交互型,使得海伦可以直接输入数据,然后返回给她结论。
# 约会网站预测函数,用户交互型
def classifyPerson():
res_list = [0, '不喜欢', '魅力一般', '极具魅力']
# 数据
dating_mat, labels = file2matrix('datingTestSet2.txt')
# 归一化
norm_dating_mat = autoNorm(dating_mat)
# 获取用户数据
ff_miles = float(input("每年获取的飞行常客里程数?"))
percenttTats = float(input("玩视频游戏所耗时间百分比?"))
inX = [ff_miles, percenttTats]
res = classify0(inX, dating_mat, labels, 3)
print("你觉得这个人" + res_list[res] + '.')
每年获取的飞行常客里程数?10000
玩视频游戏所耗时间百分比?10
你觉得这个人魅力一般.