过滤豆瓣租房小组中介贴之 python 实现 k 近邻算法(四)

豆瓣有各种小组,每个小组会有个主题,比如租房小组基本里面全是房源相关的,当然偶尔也会有广告出现。那么如何能够让机器知道你发的帖子到底是哪个主题的呢,简单来说就是如何给帖子分类。这个时候需要介绍机器学习一个最简单的算法,叫 k 近邻算法。 原理介绍见这篇 K NEAREST NEIGHBOR 算法

屏幕快照 2017-03-06 下午1.35.46

图中的有两个类型的样本数据,一类是蓝色的正方形,另一类是红色的三角形。而那个绿色的圆形是我们待分类的数据。

  • 如果K=3,那么离绿色点最近的有2个红色三角形和1个蓝色的正方形,这3个点投票,于是绿色的这个待分类点属于红色的三角形。
  • 如果K=5,那么离绿色点最近的有2个红色三角形和3个蓝色的正方形,这5个点投票,于是绿色的这个待分类点属于蓝色的正方形。

所以如果假设我们已经有了一些帖子,并知道他们是哪些类型的,比如旅游相关的(蓝色),租房相关的(红色)。这个时候有个新的帖子(绿色)需要判断它属于哪一类,只要计算它与其它帖子的距离,然后选出 k 个最近的点,就可以知道这个帖子应该分到哪个类中。

下面我们用 python 来实现它
首先我们需要将文本内容向量化,同样用到了 jieba 分词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def cut_word(content):
'''
(u'\u65b9\u5e84', 2.4582479479)
(u'\u82b3\u57ce\u56ed', 1.19547675029)
(u'\u53ef\u6708\u4ed8', 1.19547675029)
(u'\u4e00\u533a', 1.04666904475)
(u'\u5355\u95f4', 1.02371160058)
(u'\u51fa\u79df', 0.832472854883)
(u'\u5730\u94c1', 0.8200234078590001)
(u'\u4e2d\u4ecb', 0.7891864466530001)
(u'\u9644\u8fd1', 0.516934129144)
'''
tags = jieba.analyse.extract_tags(content, withWeight=True, topK=20)
return tags
def merge_tags(*tags):
tag_dict_list = []
v_list = []
for tag in tags:
tag_dict_list.append({i[0]: i[1] for i in tag})
merged_tag = []
for i in tag_dict_list:
merged_tag.extend(i.keys())
merged_tag = set(merged_tag)
for tag_dict in tag_dict_list:
v = []
for i in merged_tag:
if i in tag_dict:
v.append(tag_dict[i])
else:
v.append(0)
v_list.append(v)
return v_list

接下来就是 knn 算法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def kNNClassify(newInput, dataSet, labels, k):
numSamples = dataSet.shape[0] # shape[0] 代表行的数目
# 步骤1:计算欧几里得距离
diff = tile(newInput, (numSamples, 1)) - dataSet
squaredDiff = diff ** 2
squaredDist = sum(squaredDiff, axis=1)
distance = squaredDist ** 0.5
# 步骤2:对距离排序
sortedDistIndices = argsort(distance)
classCount = {}
for i in xrange(k):
# 步骤3:选择最近的 k 个距离
voteLabel = labels[sortedDistIndices[i]]
# 步骤4:对 label 次数计数
classCount[voteLabel] = classCount.get(voteLabel, 0) + 1
# 步骤5:返回投票最大的结果
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]

这里面我们计算使用的是欧几里得距离,其实我们也可以用前面的余弦相似度来做比较。

测试下看看效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# 租房相关
content1 = u"""
可月付 无中介 方庄地铁附近 芳城园一区单间出租
我的房子在方庄地铁附近的芳城园一区,正规小区楼房,
三家合住,现出租一间主卧和一间带小阳台次卧,室内家电齐全,
冰箱,洗衣机等都有,可洗澡上网,做饭都可以,小区交通便利,四通八达,
希望入住的是附近正常上班的朋友
"""
content2 = u"""
可月付 无中介 方庄地铁附近 芳城园一区主卧次卧出租
我的房子在方庄地铁附近的芳城园一区,正规小区楼房,
三家合住,现出租一间主卧和一间带小阳台次卧,室内家电齐全,
冰箱,洗衣机等都有,可洗澡上网,做饭都可以,小区交通便利,四通八达,
希望入住的是附近正常上班的朋友
"""
content3 = u"""方庄地铁附近 芳城园一区次卧出租
我的房子在方庄地铁附近的芳城园一区,正规小区楼房,
三家合住,现出租一间主卧和一间带小阳台次卧,室内家电齐全,
冰箱,洗衣机等都有,可洗澡上网,做饭都可以,小区交通便利,四通八达,
希望入住的是附近正常上班的朋友
"""
test_content4 = u"""二环玉蜓桥旁下月27号后可入住二居
方庄方古园一区5号楼下月27日到期出租,
我是房主无中介费 ,新一年租6000元每月押一付三,主次卧可分开住。
距地铁5号线蒲黄榆站5分钟路程。房屋60平正向,另有看守固定车位。
"""
# 旅游相关
content5 = u"""世界尽头和冷酷仙境 去南极大部分都是从乌斯怀亚出发,
这是南美大陆的最南端,所以也被称为世界尽头。在这里,有很多东西都已经到了终点:
世界最尽头的邮局;世界最尽头的徒步线路;还有传说中“泛美公路”的终点,
这条路从阿拉斯加一路向南开过来,17848公里,开到这里居然也到头了,没路了…然而,
世界尽头也可以是起点,这里距离南极大陆只有不到1000公里的距离,世界尽头之后就是冷酷仙境。
"""
content6 = u"""走 走停停,看看世界上其中一条最美丽最浪漫的海岸线:那不勒斯湾到amalfi 海岸。
欧洲 最美是小镇,意大利南部小镇与北部山区小镇完全不一样的风格但又一样是浪漫和美丽的世 外桃源。
这个海岸线之所以美,原因跟托斯卡纳的山区一样, 建筑与自然的完美融合。海、 山与小屋群形成了一副美丽的风景画。
沿着铁路和公路,陆路从那不勒斯走到Amalfi。
"""
content7 = u"""仙本娜是一个几乎由潜水业支撑的小镇,走几步路就是潜店,
来自世界各地的游客每个人都在说着潜水。不大,但是比起马布就丰富多了,
有旅馆,酒吧,咖啡店,饭店,小卖部,还有一个大超市。到了仙本娜,
去一间叫龙门客栈(DRAGON ING)的旅馆CHECK IN。这是一件建在水上的树皮旅馆,
不论室内室外的墙面全部是树皮,很有原始的风味,房间内有冷风机,价格也不贵,66马币,
一个大床,有电视机,带卫生间。从马布搬过来,觉得这个环境真是天堂。旅馆由水上的很多长廊组成,
连成一条网,我几乎迷路。
"""
test_content8 = """ 环游世界当然不能真地把旅行都抛掉啦,伟大的古文明和异国文化是环游世界的其中一个重要目的。
美洲的玛雅文明,非洲的埃及文明,亚洲的印度文明,一个个从人类起源就可以追溯的古文明。
一个个让人目定口呆的世界文化遗产。感受人类的力量同时感受时间的伟大和残酷。 还有令人陶醉的异国文化,去捷克感受的波西米亚文化,
到伦敦西区和纽约百老汇看歌舞剧等等,品味澳大利亚的葡萄酒苏格兰的威士忌墨西哥的龙舌兰。有时候,小资也是一种情调啊!
"""
tag1 = cut_word(content1)
tag2 = cut_word(content2)
tag3 = cut_word(content3)
tag4 = cut_word(test_content4)
tag5 = cut_word(content5)
tag6 = cut_word(content6)
tag7 = cut_word(content7)
tag8 = cut_word(test_content8)
v = [tag1, tag2, tag3, tag4, tag5, tag6, tag7, tag8]
data = merge_tags(*v)
v4 = data.pop(3)
v8 = data.pop()
dataSet = array(data)
labels = array(['租房', '租房', '租房', '旅游', '旅游', '旅游'])
k = 3
v4 = array(v4)
outputLabel = kNNClassify(v4, dataSet, labels, k)
print "Your input is: v4 and classified to class: ", outputLabel
v8 = array(v8)
outputLabel = kNNClassify(v8, dataSet, labels, k)
print "Your input is: v8 and classified to class: ", outputLabel

content1, content2, content3 是我们已知的属于租房类别的,content5, content6, content7 是我们已知的属于旅游类别的,test_content4, test_content8 是需要测试的例子。

1
2
Your input is: v4 and classified to class: 租房
Your input is: v8 and classified to class: 旅游

代码见: https://gist.github.com/facert/972ee8e5801a51003534b1a9ceeda322

总结:

作为最简单的机器学习算法,k 近邻算法也有一定的局限性,比如分类速度慢,每一个测试用例,都需要和所有的训练样本计算距离。