最出名的推荐系统估计要非“协同过滤”莫属,经典的示例就是网站上的各种推荐,根据客户对美食或书籍或电影的评分,来推荐给爱好相似的客户类似商品。

但是,如果你没有任何客户对商品的评分,怎么办呢?评分也是需要积累的呀。有人会说打标签,好听点就是用户画像。打标签也不错,不过是个纯粹的体力活、苦活、累活。我们又不想太累,用上点简单的文本挖掘吧。当然,也许这些方法都是为了打标签,哈哈

1. 最简单的推荐系统(搜索——匹配某字段中的关键词)

直接在字段“关键字段”中使用正则表达式搜索关键字,返回大量包含该关键字的数据。

R代码:

#  读入数据
data <- read.csv("your_file.csv", stringsAsFactors = F)

#  找出合适的变量
data2 <- data[, c(13, 14, 16, 22, 26,
                29, 30, 41, 42, 43, 45, 47)]
                
# 正则表达式找出匹配的行号
id <- grepl("某关键词", data2$key)

# 返回所有匹配的数据
out <- data2[id, ]

# 返回前15行及个别列
out[1:15, c(5, 6, 12)]

python代码:

# 导入pandas 
import pandas as pd
# 读入数据
data = pd.read_csv("your_file.csv", encoding = "utf-8")
# 匹配关键词,返回true或false
rain_type = data.key.str.contains(u'某关键词')
# 返回所有包含关键词的数据
rain_d = data[rain_type == 1]

2. 优先级筛选的推荐方法:

首先,把字段“关键字段”中信息整理成一个字段“类型”,查找与目标数据的“类型”相同的数据。

然后,在同类型的数据中继续筛选出发生在同一个地点,甚至同一个人员。

R代码:

data_s <- read.csv("your_file.csv", stringsAsFactors = F)
temp1 <- data_s[grepl("关键词1", data_s$key), ]
temp1$type <- "关键词1"
temp2 <- data_s[grepl(“关键词2", data_s$key), ]
temp2$type <- "关键词2"
data_new <- rbind(temp1, temp2)
obj_id <- 2
obj <- data_new[obj_id, ]
train <- data_new[-obj_id, ]

#  找出同种类别的
train2 <- train[train$type == obj$type, ]

#  找出同类别发生在同一个站点的
if (sum(train2$PLACE_NAME == obj$PLACE_NAME) == 0) {
  train3 <- train2
} else {
  train3 <- train2[train2$PLACE_NAME == obj$PLACE_NAME, ]
}

#  找出同类别发生在同一个站点的同一个人的
if (sum(train3$FINDER == obj$FINDER) == 0) {
  train4 <- train3
} else {
  train4 <- train3[train3$FINDER == obj$FINDER, ]
}
train4[11:14, ]
obj[, c(1:5)]

python代码:

import pandas as pd
data = pd.read_csv("your_file.csv", encoding = "utf-8")
#  找出包含某种关键词的数据
temp1 = data[data.key.str.contains(u'关键词1') == 1]
temp1['type'] = "关键词1"
temp2 = data[data.key.str.contains(u'关键词2') == 1]
temp2['type'] = "关键词2"
# 合并两种关键词的数据
data_new = pd.concat([temp1, temp2])
# 给出目标观察值的位置,第几个数值
obj_id = 4
# 返回目标观察值
obj = data_new.iloc[obj_id, ]

# 从数据集中排除目标观察值的标签,不是位置,留下待推荐的数据
# obj.name返回目标观察值的index,即标签,是46
# obj_id是目标观察值的位置,是4
train = data_new.drop(obj.name)
# 找出同类别
train2 = train[train.type == obj.type]
# 对同类别数据,继续筛选,找发生在同一个地点的数据
#  这里不知道是什么原因,选列变量要根据位置,根据名字则失败
if (sum(train2.iloc[:, 0] == obj[0]) == 0):
    train3 = train2
else:
    train3 = train2[train2.iloc[:, 0] == obj[0]] 
# 继续根据优先级筛选,在上面的基础上,筛选是否有同一个人的数据
if (sum(train3.iloc[:, 1] == obj[1]) == 0):
    train4 = train3
else:
    train4 = train3[train3.iloc[:, 1] == obj[1]]

obj
train4.head()

3. 基于Simhash和海明距离算法的推荐:

首先,把字段“关键字段”的文字进行分词,提取关键字,计算simhash值。

然后,根据每行数据的simhash值,计算目标数据与其他所有数据之间的海明距离,推荐距离目标数据最近的前2个数据。

R代码:

library(jiebaR)

data_s <- read.csv("your_file.csv", stringsAsFactors = F)

sim <- function(data, obj) {
  #  初始化分词引擎
  simhasher = worker("simhash")

  #  计算需要匹配的语句与其他所有语句之间的距离
  out <- lapply(data$key, FUN = function(x) {
   as.numeric(distance(data$key[obj], x, simhasher)$distance)
  })

  # 距离以行号为名字
  names(out) <- 1 : length(data$key)

  # 删除需要匹配的语句与自身的距离
  out <- out[-obj]

  # 找到与需要匹配的语句距离从小到大的行号
  id <- as.numeric(names(sort(unlist(out))))

  # 返回与需要匹配的语句距离从小到大的推荐
  list("obj" = data[obj, c(1, 2, 3, 4, 5)],
      "degree" = paste(100 - unname(sort(unlist(out))[1:2]), "%", sep = ""),
      "similar" = data[id[1:2], c(1:9)])
}

sim(data = data_s, obj = 1)

python代码:

import pandas as pd
import jieba
import jieba.analyse
#  jiebaR中直接有计算simhash值和海明距离的函数
# jieba中貌似没有
# 同一个项目不同语言的接口,功能上居然有差异,有点吃惊
# 幸好找到有人实现了simhash在python中
http://blog.sina.com.cn/s/blog_62b8329101017vv3.html  
###  simhash算法的python实现
class simhash:
    # 构造函数
    def __init__(self, tokens = '', hashbits = 128):
       self.hashbits = hashbits
       self.hash = self.simhash(tokens);
       
    # toString函数
    def __str__(self):
       return str(self.hash)
   
    # 生成simhash值
    def simhash(self, tokens):
       v = [0] * self.hashbits
       for t in [self._string_hash(x) for x in tokens]:
           # t为token的普通hash值
           for i in range(self.hashbits):
               bitmask = 1 << i
               if t & bitmask :
                   v[i] += 1 # 查看当前bit位是否为1, 是的话将该位+1
               else:
                   v[i] -= 1 # 否则的话,该位-1
       fingerprint = 0
       for i in range(self.hashbits):
           if v[i] >= 0:
               fingerprint += 1 << i
       return fingerprint # 整个文档的fingerprint为最终各个位 >=0的和
   
    # 求海明距离
    def hamming_distance(self, other):
       x = (self.hash ^ other.hash) & ((1 << self.hashbits) - 1)
       tot = 0;
       while x:
           tot += 1
           x &= x - 1
       return tot
   
    # 求相似度
    def similarity (self, other):
       a = float(self.hash)
       b = float(other.hash)
       if a > b : return b / a
       else: return a / b
       
    # 针对source生成hash值 (一个可变长度版本的python的内置散列)
    def _string_hash(self, source):
       if source == "":
           return 0
       else:
           x = ord(source[0]) << 7
           m = 1000003
           mask = 2 ** self.hashbits - 1
           for c in source:
               x = ((x * m) ^ ord(c)) & mask
           x ^= len(source)
           if x == -1:
               x = -2
           return x 
########################################################
# 通过计算目标数据和其他数据的simhash值,得到相似度
def sim (data, obj):
    obj_data = data.iloc[obj, ]
    # 计算目标数据的simhash值
    word_obj = jieba.analyse.extract_tags(data.DEFECT_PHENOMENON.iloc[obj])
    hash_obj = simhash(word_obj)
    # 计算其他数据的simhash值并计算相似度
    sim_value = []
    for i in range(data.DEFECT_PHENOMENON.size):
       word_other = jieba.analyse.extract_tags(data.DEFECT_PHENOMENON.iloc[i])
       hash_other = simhash(word_other)
       sim_value.append(hash_obj.similarity(hash_other))
    # 把列表转换为pandas中的序列,因为有index记录每一个相似度对应的data中的行号,即位置
    # 删除目标变量与自身的相似度数值
    sim_series = pd.Series(sim_value).drop(obj_data.name)
    return sim_series 
###############################
# 读入数据
data = pd.read_csv("your_file.csv", encoding = "utf-8")
# 数据清洗
# 关键字段的数据类型,应该都为unicode,因为部分数据缺失为nan,类型为float,提取文本关键词报错
# 提取出关键字段非空的数据
data_new = data[data.key.isnull() == 0]
# 得到目标数据与所有其他数据之间的相似度
out = sim(data = data_new, obj = 9)
# 按照从大到小的顺序把相似度排序
sorted_out = out.sort(ascending = False, inplace= False)
# 目标关键词
print data_new.key.iloc[9]
# 推荐的最相似的前五个关键词
data_new.key.iloc[sorted_out.index[0:5]]

经人工测试了几个数据,python版的效果没有R版的好,不知道是因为python版的simhash算法和jiebaR内置的simhash算法的区别,还是python版的提取关键词和jiebaR的提取关键词有区别。 后来经检查,还是python版的simhash算法的jiebaR中内置的算法不同,前者只考虑了关键词,后者还考虑了关键词的权重,显然,后者的权重不是白加的,效果不是一般的好。 改进了python版的simhash算法,添加了权重部分,效果好多了,但还是没有jiebaR的效果好,应该是关键词对应的权重的差别,jieba和jiebaR输出的关键词的权重不同,对同样的数据。

更好点,其实可以先用simhash和海明距离找到类似的文本,然后使用优先级过滤再对类似的文本进行排序,更精细的确定相似性。

ps: python代码:

###  添加了权重的simhash算法#######################
###  simhash算法的python实现
class simhash:
    # 构造函数
    def __init__(self, tokens = '', hashbits = 128):
       self.hashbits = hashbits
       self.hash = self.simhash(tokens);
       
    # toString函数
    def __str__(self):
       return str(self.hash)
   
    # 生成simhash值
    def simhash(self, tokens):
       v = [0] * self.hashbits
       
       # tokens 代表关键词和对应的权重
       # only_token 代表关键词
       only_token = [x[0] for x in tokens]
       # 得到关键词对应的hash值
       only_hash = [self._string_hash(x) for x in only_token]
       # only_weight 代表关键词对应的权重
       only_weight = [x[1] for x in tokens]
       # 把hash值和对应的权重组合在一起
       hash_weight = zip(only_hash, only_weight)
       
       for t in hash_weight:
           # t为token的普通hash值权重对
           for i in range(self.hashbits):
               bitmask = 1 << i
               if t[0] & bitmask :
                   v[i] += t[1] # 查看当前bit位是否为1, 是的话将该位+weight
               else:
                   v[i] -= t[1] # 否则的话,该位-weight
       fingerprint = 0
       for i in range(self.hashbits):
           if v[i] >= 0:
               fingerprint += 1 << i
       return fingerprint # 整个文档的fingerprint为最终各个位 >=0的和
   
    # 求海明距离
    def hamming_distance(self, other):
       x = (self.hash ^ other.hash) & ((1 << self.hashbits) - 1)
       tot = 0;
       while x:
           tot += 1
           x &= x - 1
       return tot
   
    # 求相似度
    def similarity (self, other):
       a = float(self.hash)
       b = float(other.hash)
       if a > b : return b / a
       else: return a / b
       
    # 针对source生成hash值 (一个可变长度版本的python的内置散列)
    def _string_hash(self, source):
       if source == "":
           return 0
       else:
           x = ord(source[0]) << 7
           m = 1000003
           mask = 2 ** self.hashbits - 1
           for c in source:
               x = ((x * m) ^ ord(c)) & mask
           x ^= len(source)
           if x == -1:
               x = -2
           return x 
 
#  更新后的函数,限制关键词数量为5个,因为这里的测试文本就是几个句子,过多的关键词反而掩盖了重点

# 因为前面的数据结构发生了变化,下面的函数也有少许变动。
# 通过计算目标数据和其他数据的simhash值,得到相似度
def sim (data, obj):
    obj_data = data.iloc[obj, ]
    # 计算目标数据的simhash值
    word_obj = jieba.analyse.extract_tags(data.key.iloc[obj], topK = 5, withWeight= True)
    hash_obj = simhash(word_obj)
    # 计算其他数据的simhash值并计算相似度
    sim_value = []
    for i in range(data.key.size):
       word_other = jieba.analyse.extract_tags(data.key.iloc[i], topK = 5, withWeight= True)
       hash_other = simhash(word_other)
       sim_value.append(hash_obj.similarity(hash_other))
    # 把列表转换为pandas中的序列,因为有index记录每一个相似度对应的data中的行号,即位置
    # 删除目标变量与自身的相似度数值
    sim_series = pd.Series(sim_value).drop(obj_data.name)
    return sim_series

# 得到目标数据与所有其他数据之间的相似度
out = sim(data = data_new, obj = 9)
# 按照从大到小的顺序把相似度排序
sorted_out = out.sort(ascending = False, inplace= False)

# 目标关键词
print data_new.DEFECT_PHENOMENON.iloc[9]

# 推荐的最相似的前五个关键词
data_new.DEFECT_PHENOMENON.iloc[sorted_out.index[0:5]]

最后附上一个图,这个图清楚明了的解释了simhash算法的原理,这张图的原始出处不知道来自哪里,但几乎所有讲解simhash的文章都使用了这个图。

参考网址:

备注:转移自新浪博客,截至2021年11月,原阅读数144,评论0个。