本节将讲述一种能快速对文本进行模糊搜索,找出相同与相似文本的方法。由于自己建立分词算法与模糊词词典会比较花时间,本人直接引用相关库。
本文将要用到的库:
Numpy:python的科学计算库
URL: https://www.numpy.org/
Pickle:python的用于存储Object的库
URL: https://docs.python.org/3/library/pickle.html
Pkuseg:北京大学的分词库
URL: https://github.com/lancopku/pkuseg-python
Synonyms:一个用于计算词语相似度的库
URL: https://www.tutorialspoint.com/python/python_synonyms_and_antonyms.htm
为什么要做这个算法?
1,本人做某个AI时需要用到。
2,以上的库都只专精于某项功能,synonyms虽然能计算两个词语之间的相似度,但是对于批量处理文本来说,效率可能会不太高。因为总不能逐个逐个词语地去对比相似度,这样计算量会很大。
3,本人前段时间做了一阵子的RNN算法,而RNN的输入与输出用到了一个叫Onehot的编码方式。学到了新知识,就像用它来做点什么。
所以综上,本人会使用本人近段时间所学到的知识来编写一个能够从文本中快速搜索相似文本的算法。
预备知识
one_hot算法的基本知识:
https://hackernoon.com/what-is-one-hot-encoding-why-and-when-do-you-have-to-use-it-e3c6186d008f
矩阵空间的理解:大家可以看一下我之前写的矩阵运算与空间观。
https://blog.csdn.net/WilliamLeong/article/details/90730990
本章
算法大概想法:
1,读入需要进行文本搜索的词库或者文本。
2,如果是文本的话,对文本进行预处理(断句)。
3,对文本和词库进行分词。
4,建立词库。
5,建立onehot索引。
6,输入进行搜索的词或文本并进行预处理。
7,计算长度相似度,词语相似度,顺序相似度。
8,根据相似度找到相对应的词语或者文本。
9,输出。
具体实现:
1,把文本读取到程序中,并进行断句:
def load_split_txt(filepath = "./test.txt"):
f = open(filepath, "r",encoding='UTF-8')
text = f.read()
text = text.split('。')
return text
如果是不需要进行断句,可以用以下这个方法来读取,把读取到的数据添加到List中:
def load_original_txt(filepath = "./test.txt"):
charList = []
charListAscii = []
for line in open(filepath,'r', encoding='UTF-8'): #读入文本
for i in range(len(line)):
if ord(line[i]) not in charListAscii:
charList.append(line[i])
charListAscii.append(ord(line[i]))
return charList,charListAscii
2,建立自己的词库,由于词库里面的词必须是唯一的,所以建立词库我分成了两个函数:
def get_unique(input): #取唯一词
strUnique = []
widgets = ['Progress get unique: ', Percentage(), ' ', Bar('#'), ' ', Timer(),
' ', ETA(), ' ', FileTransferSpeed()]
pbar = ProgressBar(widgets=widgets, maxval=10 * len(input)).start()
for i in range(len(input)):
pbar.update(10 * i + 1)
time.sleep(0.01)
if input[i] not in str_unique:
str_unique.append(input[i])
pbar.finish()
return str_unique
def build_word_dict(wordList):
wordDict = {}
wordList_onehot = numpy_eye(wordList)
widgets = ['Progress bulid word dict: ', Percentage(), ' ', Bar('#'), ' ', Timer(),
' ', ETA(), ' ', FileTransferSpeed()]
pbar = ProgressBar(widgets=widgets, maxval=10 * len(wordList)).start()
for i in range(len(wordList)):
pbar.update(10*i + 1)
time.sleep(0.01)
wordDict.update({wordList[i]: np.argmax(wordList_onehot, axis=0)[i]})
pbar.finish()
return wordDict
3,对文本进行分词,直接引用Pkuseg:
strOne = seg.cut(strOne)
4,建立词库,只需要执行一次,第二次开始请直接读取预先存储好的词典:
def build_word_dict(wordList):
wordDict = {}
wordList_onehot = numpy_eye(wordList)
widgets = ['Progress bulid word dict: ', Percentage(), ' ', Bar('#'), ' ', Timer(),
' ', ETA(), ' ', FileTransferSpeed()]
pbar = ProgressBar(widgets=widgets, maxval=10 * len(wordList)).start()
for i in range(len(wordList)):
pbar.update(10*i + 1)
time.sleep(0.01)
wordDict.update({wordList[i]: np.argmax(wordList_onehot, axis=0)[i]})
pbar.finish()
return wordDict
wordDict = build_word_dict(get_unique(wordList)) #建立词典
wordDict = load_obj("chiWordDict") #读取词典
5,建立onehot索引:
def build_word_dict(strList):
wordDict = {}
widgets = ['Progress bulid word dict: ', Percentage(), ' ', Bar('#'), ' ', Timer(),
' ', ETA(), ' ', FileTransferSpeed()]
pbar = ProgressBar(widgets=widgets, maxval=10 * len(strList)).start()
for i in range(len(strList)):
pbar.update(10*i + 1)
time.sleep(0.0001)
for j in range(len(seg.cut(strList[i]))):
if seg.cut(strList[i])[j] not in wordDict:
wordDict.update({seg.cut(strList[i])[j]: len(wordDict)+1})
pbar.finish()
return wordDict
def build_word_index(wordList,wordDict):
widgets = ['Progress build str index: ', Percentage(), ' ', Bar('#'), ' ', Timer(),
' ', ETA(), ' ', FileTransferSpeed()]
max1 = len(wordDict)
array = np.zeros((max1, len(wordList)))
pbar = ProgressBar(widgets=widgets, maxval=10 * len(wordList)).start()
for i in range(len(wordList)):
pbar.update(10 * i + 1)
time.sleep(0.0001)
tempseg = seg.cut(wordList[i])
str_onehot = word_dict_one_hot(tempseg,wordDict)
for j in range(len(str_onehot)):
array[str_onehot[j]-1,i] = 1
pbar.finish()
return array
6,对输入进行预处理:
def word_dict_one_hot(inputs,wordDict):#取出输入文本在词典中所对应的onehot编码
word_onehot = []
for i in range(len(inputs)):
if inputs[i] not in wordDict:
wordDict.update({inputs[i]: len(wordDict) + 1})
word_onehot.append(wordDict[inputs[i]])
return word_onehot
def numpy_one_hot(input1): #生成输入文本的onehot编码
maxnum = max(input1)
array = np.zeros((maxnum,len(input1)))
for i in range(len(input1)):
array[input1[i]-1,i] = 1
return array
def allign_onehot(input1,input2): #把所需要求相似度的输入文本的shape变成相同
length = max(input1.shape[1], input2.shape[1])
height = max(input1.shape[0], input2.shape[0])
if input1.shape[1] < length:
input1 = np.concatenate((input1,np.zeros((input1.shape[0],length - input1.shape[1]))),axis=1)
else:
input2 = np.concatenate((input2,np.zeros((input2.shape[0],length - input2.shape[1]))),axis=1)
if input1.shape[0] < height:
input1 = np.concatenate((input1,np.zeros((height - input1.shape[0],input1.shape[1]))),axis=0)
else:
input2 = np.concatenate((input2,np.zeros((height - input2.shape[0],input2.shape[1]))),axis=0)
return input1,input2
7,计算长度相似度,词语相似度,顺序相似度:
def length_similar(input1,input2): #长度相似度
if len(input1) > len(input2):
return float(len(input2)/len(input1))
else:
return float(len(input1)) / float(len(input2))
def word_similar_onehot(input1,input2): #词语相似度
length = np.sum(input1)+np.sum(input2) #计算两个文本的长度
input1 = np.sum(input1,axis = 1) #对文本1进行维度压缩,得到该文本的词向量
input2 = np.sum(input2,axis = 1) #对文本2进行维度压缩,得到该文本的词向量
similar = 1-np.sum(np.abs((input1-input2))/length) #计算词语相似度
return similar
def order_similar_onehot(input1,input2): #词语顺序相似度
similar = np.sum(np.abs(input1-input2))
return similar
注意:词语顺序相似度的输出有点特别,顺序完全相同时为0,不相同时则大于0。这里我没有做归一处理,主要是根据不同业务要求也许有不同的设置方法,这里有兴趣的人可以自己对输出做个转换!
8,根据相似度找到相对应的词语或者文本:
def find_syn_word(input,dict,word_index):
index = []
syn = []
words = []
for i in range(len(input)):
for j in range(len(input[i][0])):
if str(input[i][0][j]) in dict:
for k in range(int(np.sum(word_index,axis=1)[dict[str(input[i][0][j])]-1])):
index.append(np.argmax(word_index[dict[str(input[i][0][j])]-1],axis=0))
word_index[dict[str(input[i][0][j])]-1,np.argmax(word_index[dict[str(input[i][0][j])]-1])] = 0
syn.append(input[i][1][j]/len(input))
words.append(str(input[i][0][j]))
return index,syn,words
说明:
1,在词典中找到输入文本中的词语所对应的onehot编码。
2,对word_index进行维度压缩,判断对应编码位置的数字为多少,0则不存在,1则代表该词库中存在1个相似结果,如此类推。
3,对word_index进行argmax(onehot编码特点)操作,找到该相似词所对应的位置。
注意:该函数只会找出含有单个词语的类似文本,而对于含有多个词语的相同文本则会出现输出重复,类似度也会不太准确。这个问题留给大家解决吧。不懂可以问我,我就不公开完整的正确代码了。
完整代码地址请到我的Github:
https://github.com/WilliamHMLeong/SimilarSearch