基于N-gram算法解决实际应用场景中的重复问题
作者:互联网
基本原理可以参考我的另一篇博客:https://www.cnblogs.com/limingqi/p/12891738.html
本文主要基于N-gram算法解决实际应用场景中的重复问题,适用于标题重复,内容重复等文本的处理。
连续性重复问题:
例如:隆鼻隆鼻隆鼻隆鼻,隆鼻隆鼻隆鼻隆鼻。 去重之后:隆鼻,隆鼻。
我不喜欢双眼皮双眼皮双眼皮双眼皮双眼皮双眼皮双眼皮。 去重之后:我不喜欢双眼皮。
基本思路:
首先给出一个简单的例子,比如:
“不匹配门店门店门店门店很方便,因为我们上了年纪了,你看我们60岁的人那你叫我们在小孩又不在身边。”
一种比较朴素的做法是先对上面的文本进行分词,然后再对连续重复词(比如"门店")做去重。但分词器有可能会造成分词错误,从而影响最终去重,分词的技巧和分词方面的抑制问题都是会影响最终结果的。
比如,采用pyltp对原始文本分词,得到的结果如下:
“不 匹 配门店 门店 门店 门店 很 方便 , 因为 我们 上 了 年纪 了 , 你 看 我们 60 岁 的 人 那 你 叫 我们 在 小孩 又 不 在 身边 。”
显然,“配门店”作为一个词导致最终去重失败。
现在我们换个思路,是否可以用最简单的n-gram来实现我们的目的呢?答案是肯定的。我们依然以上面作为例子,对其做bigram,得到以下结果:
['不匹', '匹配', '配门', '门店', '店门', '门店', '店门', '门店', '店门', '门店', '店很', '很方', '方便', '便,', ',因', '因为', '为我', '我们', '们上', '上了', '了年', '年纪', '纪了', '了,', ',你', '你看', '看我', '我们', '们6', '60', '0岁', '岁的', '的人', '人那', '那你', '你叫', '叫我', '我们', '们在', '在小', '小孩', '孩又', '又不', '不在', '在身', '身边', '边,', ',我', '我的', '的小', '小孩', '孩又', '又就', '就又', '又出', '出去', '去了', '了,', ',要', '要是', '是等', '等他', '他回', '回来', '来再', '再搞', '搞签', '签证', '证就', '就好', '好麻', '麻烦', '烦。']
可以看到,对于连续重复的词,如果该词是由两个character组成的,则bigram处理后每3个segment便重复,如连续重复词“门店门店门店门店”对应的bigram序列为['门店', '店门', '门店', '店门', '门店', '店门', '门店']。那假如连续重复词是由3个character组成?同样道理,做trigram后每隔4个segment出现重复,比如“不匹配不匹配”的trigram序列为['不匹配', '匹配不', '配不匹', '不匹配']。
代码实现:
需要注意的是由于我们不能提前知道原始文本中连续重复词是由多少个character组成的,所以在我的代码中提供了一个参数max_ngram_length,表明对原始文本采用不大于该值的所有ngram来进行分割并去重。完整的python代码实现如下。
def merge(sentence, max_ngram_length = 4):
'''合并文本中连续重复的词'''
final_merge_sent = sentence
max_ngram_length = min(max_ngram_length, len(sentence))
for i in range(max_ngram_length, 0, -1):
start = 0
end = len(final_merge_sent) - i + 1
ngrams = []
while start < end:
ngrams.append(final_merge_sent[start: start + i])
start += 1
result = []
for cur_word in ngrams:
result.append(cur_word)
if len(result) > i:
pre_word = result[len(result) - i - 1]
if pre_word == cur_word:
for k in range(i):
result.pop()
cur_merge_sent = ""
for word in result:
if not cur_merge_sent:
cur_merge_sent += word
else:
cur_merge_sent += word[-1]
final_merge_sent = cur_merge_sent
return final_merge_sent
if __name__ == "__main__":
text = "不匹配门店门店门店门店很方便,因为我们上了年纪了,你看我们60岁的人那你叫我们在小孩又不在身边。"
text = text.replace(';','').replace(':','').replace('?','').replace('~','').replace('.','').replace('。','').replace(' ','')
#添加了处理标点符号的功能,可以结合数据添加需要过滤的标点符号。
print(merge(text,max_ngram_length = 4))
text = "我爱爱北京天安门天安门,天安门上上太阳太阳升。"
print(merge(text, max_ngram_length = 4))
测试案例的输出结果:
不匹配门店很方便,因为我们上了年纪了,你看我们60岁的人那你叫我们在小孩又不在身边。
我爱北京天安门,天安门上太阳升。
实验结论:发现这个算法用在句子去重是可行的,但是需要额外考虑标点符号重复的问题,标题标点符号的代码已经准备好了,可以在处理完标点符号的基础之上,在用这个方法,具体是否可行合理需要结合数据具体分析。这种方法无法过滤到叠词,妈妈,爸爸,谢谢这种词无法考虑和过滤掉
其标点符号处理的代码如下:
from __future__ import print_function
from __future__ import unicode_literals
import sys
import time
import os
import requests
import json
import math
import jieba as jieba
fuhao_word_set = set()
def init_fuhao_word_dict(fuhao_word_path):
"""
初始化符号列表
param data:符号的路径
return:
"""
global fuhao_word_dict
input_file = open(fuhao_word_path, "r")
lines = input_file.readlines()
input_file.close()
for line in lines:
fuhao_word_set.add(line.strip().decode("utf8"))
white_fuhao_set_1 = set("""'"()[]{}‘’“”《》()【】{}"""[:])
white_fuhao_set_2 = set("""'"()[]{}‘’“”《》()【】{}?,?,"""[:])
white_fuhao_set_3 = set("""'"()[]{}‘’“”《》()【】{}??…"""[:])
white_fuhao_set_3.add("...")
white_fuhao_set_3.add("……")
stop_word_set = set(["你好", "谢谢", "多谢"])
def get_word_list_jieba(content):
"""
对内容进行分词
param data:内容
return:
"""
seg = jieba.cut(content, cut_all=False)
return list(seg)
def get_dis_fuhao_word_list(word_list):
"""
对内容进行分词
param data:内容
return:
"""
str_len = len(word_list)
dis_word_list = []
dis_word_num = 0
fuhao_num = 0
if str_len > 1:
pro_idx = -1
# pro_word = word_list[0]
for i in range(0, str_len):
if word_list[i] in fuhao_word_set:
fuhao_num += 1
if pro_idx == -1:
pro_idx = i
dis_word_list.append(word_list[i])
else:
if word_list[pro_idx] in fuhao_word_set and word_list[i] in fuhao_word_set:
dis_word_num += 1
else:
dis_word_list.append(word_list[i])
pro_idx = i
else:
pro_idx = -1
dis_word_list.append(word_list[i])
return dis_word_list, dis_word_num, fuhao_num
kuohao_map = {")": "(", ")": "("}
def format_fuhao_word_list(word_list):
"""
标准化的符号列表
param data:去掉符号的列表
return:
"""
str_len = len(word_list)
str_idx = 0
res_word_list = []
word_kuohao_dict = dict()
if str_len > 1:
for i in range(0, str_len):
if word_list[i] in fuhao_word_set:
if str_idx == 0:
if word_list[i] in white_fuhao_set_1:
res_word_list.append(word_list[i])
str_idx += 1
else:
pass
else:
if i == str_len - 1:
if word_list[i] in white_fuhao_set_3:
res_word_list.append(word_list[i])
str_idx += 1
else:
if word_list[i] in white_fuhao_set_2:
res_word_list.append(word_list[i])
str_idx += 1
else:
res_word_list.append(" ")
str_idx += 1
else:
res_word_list.append(word_list[i])
str_idx += 1
if len(res_word_list) > 0:
for i in range(0, len(res_word_list)):
if res_word_list[i] in ["(", "("]:
word_kuohao_dict[res_word_list[i]] = i
if res_word_list[i] in [")", ")"]:
kuohao_tmp = kuohao_map.get(res_word_list[i])
if kuohao_tmp in word_kuohao_dict:
word_kuohao_dict.pop(kuohao_tmp)
else:
res_word_list[i] = " "
if len(word_kuohao_dict) > 0:
for j in word_kuohao_dict.values():
res_word_list[j] = " "
return res_word_list
return word_list
if __name__ == '__main__':
init_fuhao_word_dict("/home/work/limingqi01/title_duplicate/fuhao_1.txt")
title = "样算o型腿吗?严重吗怎么矫正?!!!"
print(title)
word_list = get_word_list_jieba(title)
dis_word_list, dis_word_num, fuhao_num = get_dis_fuhao_word_list(word_list)
print(dis_word_list, dis_word_num)
res_word_list = format_fuhao_word_list(dis_word_list)
s = ''
for i in res_word_list:
s = s + i
print(s)
去重的优化方式:通过jieba分词,更好的切词之后,去掉相邻重复的词就好了,可以更好的考虑叠词,两种方法可以一起结合使用,线上效果才会不错。
import jieba
# 精确模式 (默认)
seg_list = jieba.cut('??????????????/', cut_all=False)
seg = list(seg_list)
print(seg)
def del_adjacent(alist):
for i in range(len(alist) - 1, 0, -1):
if alist[i] == alist[i-1]:
del alist[i]
return alist
seg = del_adjacent(seg)
print(seg)
间接性重复问题:
前提都要进行符号化等预处理操作
句子重复的问题:'中国中国','中国和中国和', '中国和 中国和'(去掉空格才行)
解决思路:你可以观察到一个字符串被认为是重复的,它的长度必须能够被重复序列的长度整除;这是一个生成从1
到n / 2
的长度除数的解决方案,将原始字符串除以具有除数长度的子串,并测试结果集的相等性;
正则表达式:
import re
REPEATER = re.compile(r"(.+?)\1+$")
def repeated(s):
match = REPEATER.match(s)
return match.group(1) if match else None
正则表达式(.+?)\\1+$
分为三个部分:(.+?)
是一个匹配组,包含至少一个(但尽可能少)任何字符(因为+?
是非贪婪的 );\\1+
检查第一部分中匹配组的至少一次重复;$
检查字符串的结尾,
以确保在重复的子字符串之后没有额外的,非重复的内容(并且使用re.match()
确保在重复的子字符串之前没有非重复的文本)。
非正则表达式解决方案一:
def repeat(string):
for i in range(1, len(string) // 2 + 1):
if not len(string) % len(string[0:i]) and string[0:i] * (len(string) // len(string[0:i])) == string:
return string[0:i]
非正在表达式二:
def repeat(string):
l = len(string)
for i in range(1, len(string)//2+1):
if l%i: continue
s = string[0:i]
if s*(l//i) == string:
return s
上面的解决方案很少比原始解决方案慢几个百分点,但它通常要快一点 - 有时速度要快很多;对于较长的字符串,它仍然不比davidism快,而对于短字符串,零的正则表达式解决方案更胜一筹;
标签:场景,word,fuhao,list,门店,len,算法,set,gram 来源: https://www.cnblogs.com/limingqi/p/14159466.html