#1. K最近邻(k-Nearest Neighbor)分类算法

采用测量不同特征值之间的距离方法进行分类。
K近邻分类算法的主要思想:如果一个样本在特征空间中的k个最相似)的样本中的大多数属于某一个类别,则该样本也属于这个类别(这里对于最相似的判定主要是通过特征值向量的距离)

##1.1. 算法特点及伪代码

  • KNN算法中,所选择的邻居都是已经正确分类的对象(训练集)
  • KNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,KNN方法较其他方法更为适合。
  • 当样本不平衡时,如一个类的样本容量很大,而其他类样本容量很小时,有可能导致当输入一个新样本时,该样本的K个邻居中大容量类的样本占多数

伪代码描述

1
2
3
4
5
1. 计算已知类别数据集中的点与当前点之间的距离
2. 按照距离递增次序排序
3. 选取与当前点距离最小的k个点
4. 确定前k个点所在类别的出现频率
5.返回前k个点出现频率最高的类别作为当前点的预测分类

##1.2. Python实现

这篇博文写的有些匆忙, 如果以后有时间的话, 我会进行重新整理

  • 特征抽取, 对于每个类别的文本进行特征抽取, 获取特征词集合, 用于匹配测试文本, 生成特征向量
1
2
3
4
5
6
7
8
9
10
11
12
13
def extract_feature() :
text = ""
post_set = []; class_set = []; feature = []
jieba.analyse.set_stop_words("stop_word.txt")
for index in range(len(dict_list)) :
with open("./lily/" + dict_list[index] + ".txt", "r") as my_file :
#读入每个板块所有的帖子
for post in my_file :
post_set.append(list(jieba.cut(post, cut_all = False))) #将post字符串存入list
class_set.append(index)
text += post
feature.extend(jieba.analyse.extract_tags(text, 100))
return feature, post_set, class_set
  • 将每一个训练文本生成对应的特征向量, 首先对训练文本分词, 每一个文本表示成一个词list, 然后遍历整个词list, 与特征词集合做匹配, 匹配成功, 则当前为计算词频, 匹配不成功置0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def createQuery(post_set, feature) :
query_set = []
vec_size = len(feature)
for post in post_set :
vector = [0] * vec_size
for word in post :
if word in feature :
vector[feature.index(word)] += 1
query_set.append(vector)
return query_set
def makeVector(post, feature, size) :
vector = [0] * size
for word in post :
if word in feature :
vector[feature.index(word)] += 1
return vector
  • 计算欧式距离, 获得不同向量之间的距离
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def knnClassify(vecX, query_set, class_set, k) :
query_set = array(query_set)
query_set_size = query_set.shape[0]
mid_mat = tile(vecX, (query_set_size, 1)) - query_set
sqrt_two = mid_mat ** 2
sqrt_distances = sqrt_two.sum(axis = 1)
distances = sqrt_distances ** 0.5
sorted_dist = distances.argsort()
class_count = {}
for i in range(k) :
label = class_set[sorted_dist[i]]
class_count[label] = class_count.get(label, 0) + 1
sort_class_count = sorted(class_count.iteritems(), key = operator.itemgetter(1), reverse = True)
return sort_class_count[0][0]
  • 对测试文本进行暴力搜索, 找到距离文本最近的k个文本, 然后找到k文本中出现次数最后的类别, 整个类别就是测试文本的类别

距离搜索的缺陷: 当训练文本过大时, 需要用测试文本与每个训练文本计算欧式距离, 导致计算时间过长,

#2.局部敏感哈希

LSH的基本思想是:将原始数据空间中的两个相邻数据点通过相同的映射或投影变换(projection)后,这两个数据点在新的数据空间中仍然相邻的概率很大,而不相邻的数据点被映射到同一个桶的概率很小

#2.1. LSH的具体描述
如果我们对原始数据进行一些hash映射后,我们希望原先相邻的两个数据能够被hash到相同的桶内,具有相同的桶号。对原始数据集合中所有的数据都进行hash映射后,我们就得到了一个hash table,这些原始数据集被分散到了hash table的桶内,每个桶会落入一些原始数据,属于同一个桶内的数据就有很大可能是相邻的,当然也存在不相邻的数据被hash到了同一个桶内。因此,如果我们能够找到这样一些hash functions,使得经过它们的哈希映射变换后,原始空间中相邻的数据落入相同的桶内的话,那么我们在该数据集合中进行近邻查找就变得容易了,我们只需要将查询数据进行哈希映射得到其桶号,然后取出该桶号对应桶内的所有数据,再进行线性匹配即可查找到与查询数据相邻的数据。LSH将一个在超大集合内查找相邻元素的问题转化为了在一个很小的集合内查找相邻元素的问题,显然计算量下降了很多

1
2
3
4
5
6
hash function需要满足以下两个条件:
1)如果d(x,y) ≤ d1, 则h(x) = h(y)的概率至少为p1;
2)如果d(x,y) ≥ d2, 则h(x) = h(y)的概率至多为p2;
其中d(x,y)表示x和y之间的距离,d1 < d2, h(x)和h(y)分别表示对x和y进行hash变换。
满足以上两个条件的hash functions称为(d1,d2,p1,p2)-sensitive。而通过一个或多个(d1,d2,p1,p2)-sensitive的hash function对原始数据集合进行hashing生成一个或多个hash table的过程称为Locality-sensitive Hashing。

通俗来讲,就是距离较近的点映射到同一个位置的概率大,距离较远的点映射到同一个位置的概率小。这对下面将点集映射得到候选结果集的操作很重要,能保证得到的候选结果集是可用的。

LSH算法可以看做两步:

  1. 将与测试文本完全不相关的向量剔除掉(哈希函数映射过程),只保留与测试文本相似的概率较大的向量作为待比较向量(哈希表中希望产生冲突, 使类似的文本能够映射到同一个桶里)
  2. 讲测试文本向量与剩余向量逐一对比, 找到前k个临近的向量

#2.Python实现

使用 Cosine distance

Cosine distance:cos(theta) = A·B / |A||B|

常用来判断两个向量之间的夹角,夹角越小,表示它们越相似

  • 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
    //Cosine distance的计算
    #计算向量的模
    def magnitudeProd(vec1, vec2) :
    total1 = 0
    total2 = 0
    for i in xrange(dimension) :
    val1 = vec1[i]
    val2 = vec2[i]
    total1 += val1**2
    total2 += val2**2
    return (total1 * total2)**0.5
    #计算两个向量之间的夹角, 看是否在一个角度范围内
    def cosineDistance(vec1, vec2) :
    mag_prod = magnitudeProd(vec1, vec2)
    if mag_prod == 0 :
    return 0
    return dotProduct(vec1, vec2) / magnitudeProd(vec1, vec2)
    #计算两个向量的内积
    def dotProduct(vec1, vec2) :
    total = 0
    for i in xrange(len(vec1)) :
    total += vec1[i] * vec2[i]
    return total
  • 计算测试文本和所有训练文本的哈希值生成哈希表

1
2
3
4
5
6
7
8
def localitySensitiveHash(vec, planes) :
dot_prod_list = [dotProduct(vec, plane) for plane in planes]
return (sum([2 ** i if dot_prod_list[i] > 0 else 0 for i in xrange(0, len(dot_prod_list))]) % 8)
hash_table = [(localitySensitiveHash(row, planes), class_set[class_index]) for class_index, row in enumerate(train_set)]
hash_dict = defaultdict(list)
for (hash_value, class_index) in hash_table :
hash_dict[hash_value].append(class_index)
  • 最后对一个桶内的所有的向量进行KNN算法计算距离, 找出前K个出现频率最高的