对比两文档相似度
前言
由于我的一门课结课的大作业是对比两文档相似度,所以,我用几天的时间开始自学python并搞完作业,由于过程比较曲折,特在此记录一下。
思路
- 对于这两个文档进行分词提取词向量,生成两个向量序列。
- 比较向量序列的相似度,即为两文档的相似度。
过程
一、分词,提取词向量
1、处理文档
由于python无法直接处理doc或docx文档,所以我们需要将doc或docx文档先转成txt,然后再进行处理,然而我太菜,所以我就先将doc转成docx,再将docx转成txt,同时,还需要doc文档名字一定,否则会对下面的操作有影响。我这里强制命名doc文档为"data1.doc"和"data2.doc"
(1)首先,我们先将doc文本处理成docx。我们得安装pypiwin32,即输入
pip install pypiwin32
然后python程序如下:
from win32com import client as wc #导入模块
def doc_to_docx(file):#file为文件的路径
word = wc.Dispatch("Word.Application") # 打开word应用程序
doc = word.Documents.Open(file) #打开word文件
doc.SaveAs("{}x".format(file), 12)#另存为后缀为".docx"的文件,其中参数12指docx文件
doc.Close() #关闭原来word文件
word.Quit()
#print("完成!")
return "{}x".format(file)
file = r"G:\miniconda\procedure\data1.doc"
doc_to_docx(file)
file = r"G:\miniconda\procedure\data2.doc"
doc_to_docx(file)
这样就出现"data1.docx""data2.docx"了
(2)然后,我们将docx文本处理成txt,我们得导入docx库,从而可以在python里面处理docx。处理后得到"file1.txt"和"file2.txt"
输入
pip install docx
python代码如下:
import docx
from docx import Document
file1=docx.Document('data1.docx')
#file1=file1.encode('utf-8').decode('utf-8')
with open('file1.txt','w',encoding='utf-8') as fw:
for context in file1.paragraphs:
text=context.text.split('\n')
fw.write(f"{text[0]}\n")
file2=docx.Document('data2.docx')
#file1=file1.encode('utf-8').decode('utf-8')
with open('file2.txt','w',encoding='utf-8') as fw:
for context in file2.paragraphs:
text=context.text.split('\n')
fw.write(f"{text[0]}\n")
这里需要格外注意编码问题,python好像只认utf-8
所以这里写入file1.txt时用utf-8编码,即“encoding=‘utf-8’”。
那行注释没有用,这里写出来只是为了提醒你们,如果这么写会报错。
2、分词
(1)此时,我们得到的txt中只是有了完整的文本内容,但要想生成词向量,必须变成一个个词,在此之前,我们需要将txt文本中的标点符号、英语和停用词去除,标点符号和英文比较好弄,网上查停用词有4、5千个,会把一些词如“一切”“不择手段”等词也给去掉,我认为这些词应该在文本中也有一定的意义,所以,我没有按照网上的来,我只删掉了“的”“了”“吗”等词,这些停用词我记录在"punctuation.txt"里面:
,
。
《
》
/
?
●
;
‘
’
:
“
”
、
】
【
|
}
{
=
+
-
——
)
(
*
&
……
%
¥
#
@
!
~
·
,
.
;
'
'
"
:
;
\
|
]
{
[
}
)
(
*
&
^
%
$
#
~
…
“
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
Z
X
C
V
B
N
M
A
S
D
F
G
G
H
J
K
L
Q
W
E
R
T
Y
U
O
P
1
2
3
4
5
6
7
8
9
0
I
的
了
呀
啊
—
然后我们用punctuation来处理生成的"file1.txt"和"file2.txt"
我们还需要导入codecs库来专门处理编码问题
python代码如下:
import re
import codecs
import chardet
file1=codecs.open('file1.txt', 'r',encoding='utf-8').read()
file1 = file1.encode('utf-8').decode('utf-8')
punct=codecs.open('punctuation.txt','r',encoding='utf-8')
punctuation=list()
for line in punct:
word=line.strip('\r\n')
word=word.encode('utf-8').decode('utf-8')
punctuation.append(word)
deal1=codecs.open('file1_1.txt','w',encoding='utf-8')
str=''.encode('utf-8').decode('utf-8')
for item in file1:
if item in punctuation:
continue
else:
str=str+item
with open('file1_1.txt','a',encoding='utf-8') as deal1:
deal1.write(str)
这里只处理了file1,file2处理方式相同,改改名字就行了。(以下的内容都只处理文本1,文本2操作相同。)得到的文本储存在"file1_1.txt"和"file2_1.txt"中
(2)终于到了分词的环节,这里我们需要用到jieba库,它有中文分词的功能。先下载jieba库
pip install jieba
然后python代码如下:
import jieba
import jieba.analyse
import jieba.posseg as pseg
import codecs,sys
import warnings
import logging
import os.path
import sys
file1=codecs.open('file1_1.txt','r',encoding='utf-8')
target=codecs.open('file1_2.txt','w',encoding='utf-8')
print('open files')
line_num=1
line=file1.readline()
while line:
line_seg=" ".join(jieba.cut(line))
target.writelines(line_seg)
line_num=line_num+1
line=file1.readline()
file1.close()
target.close()
得到的文本储存在了"file1_2.txt"和"file2_2.txt"中,我们打开后就可以发现已经分好词了。
3、生成词向量
我用gensim库里面的word2vec来生成词向量,注意我们首先得输入大量的数据,如果只用要比较的两篇文本来输入,生成的词向量实际没啥用。
其实,如果要生成词向量的话,使用word2vec不太好,因为它无法给出一个不在model里面的词的词向量,这个功能fasttext可以支持。(但我觉得它即使能够得出,估计词向量也没啥实际意义,而且我已经在word2vec上浪费太多时间了,所以我最后就选了word2vec)
如果我们选word2vec,我想的一个补救方法就是我们训练完model比较两文本相似度时,先将那两个文本投进去,再生成一波新词,但word2vec如果不调默认参数,会自动忽略一些出现频率低的词,所以可能还是有一些词没有被添加,这时候我就直接把它们忽视了,这种词应该占比较少,所以对结果影响应该不大(我猜的,感兴趣的可自行探究)
当然,一个避免这个问题的最好方法就是你提前用大量的数据把model训练的很好,里面确保完全包括了你要比较的文档的所有词,这样就没这个烦恼了。
首先下载gensim库
pip install gensim
(1)训练词向量库
其次训练一个词向量库,当然,上述的分词操作是必备的
python代码如下:
import warnings
import logging
import os.path
import sys
import multiprocessing
import gensim
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence
# 忽略警告
warnings.filterwarnings(action='ignore', category=UserWarning, module='gensim')
if __name__ == '__main__':
program = os.path.basename(sys.argv[0]) # 读取当前文件的文件名
logger = logging.getLogger(program)
logging.basicConfig(format='%(asctime)s: %(levelname)s: %(message)s',level=logging.INFO)
logger.info("running %s" % ' '.join(sys.argv))
# inp为输入语料, outp1为输出模型, outp2为vector格式的模型
inp = '结果1.txt'
out_model = 'result_1.model'
out_vector = 'result_1.vector'
# 训练skip-gram模型
model = Word2Vec(LineSentence(inp), vector_size=50, window=5, min_count=5,
workers=multiprocessing.cpu_count())
# 保存模型
model.save(out_model)
# 保存词向量
model.wv.save_word2vec_format(out_vector, binary=False)
此处保存模型有两个方式,第一句是保存一个model,你可以在这个model的基础上再进行训练添加词向量,当然,添加方法下文会介绍。但这个文件你打不开,无法看到你的词向量。第二句是保存所有的词向量,它可以用txt打开,打开后你就看到了你所有的词向量,但它是无法再进行训练的,所以这两个都必不可少。
(2)训练词向量库时用的数据
我首先用了《龙族》《盗墓笔记》《三体》这三本小说来训练,我才知道原来小说占的内存这么少!在此手动狗头 它们每个小说只占几MB,太小了,所以我迫不得已,在网上寻找较大的数据。这里常用的是维基百科里的数据,但它是繁体,我不想再进行繁简体转化了,所以我没有用它。
我找到了人民日报上2014的数据,然后我就用了它,它其实也只有200MB左右,并不咋优秀(这里注意一下,我跑的时候200MB跑得很快,我在b站上看老师说如果是1GB左右的量级的话,可能要跑上个几小时)
但是当我下载好后,我发现它给的数据都是分散的,它里面有若干个txt,每个txt大概有1KB左右,如果像原先一样分词,那耗时太长,所以我就上网查了个合并多个txt为一个txt文档的程序。
#coding=utf-8
import os
import os.path #文件夹遍历函数
#获取目标文件夹的路径
filedir = 'G:/miniconda/procedure/people2014/2014/0123'
#获取当前文件夹中的文件名称列表
filenames=os.listdir(filedir)
#打开当前目录下的result.txt文件,如果没有则创建
f=open('G:/miniconda/procedure/people2014/2014/result2.txt','w')
#先遍历文件名
for filename in filenames:
filepath = filedir+'/'+filename
#遍历单个文件,读取行数
for line in open(filepath,"r",encoding="utf-8"):
f.writelines(line)
f.write('\n')
#关闭文件
f.close()
(3) 正式处理文档
python代码如下:
import gensim
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence
model=gensim.models.Word2Vec.load("result_2.model")
more_sentence='file1_2.txt'
model.build_vocab(LineSentence(more_sentence),update=True)
model.train(LineSentence(more_sentence),total_examples=2000000,epochs=model.epochs)
model.save('result_2.model')
model.wv.save_word2vec_format('result_2.vector')
这里第一行代码是指读取我们已经训练好的model;第二行代码是读取我们上文处理出的’file1_2.txt’,这里面记录的是已经分好的词。第三、四行就是激动人心的添加新词环节,完了就再保存一下。
4、得到向量序列
这里我们需要通过遍历已经分好词的’file1_2.txt’文档,得到每个词,然后从model中找出它的词向量,在加进一个向量中。
python代码如下:
file1=codecs.open('file1_2.txt','r',encoding='utf-8')
model=gensim.models.Word2Vec.load("result_2.model")
content=file1.read()
a=content.split()
x=np.empty(50,dtype='float32')
c=[]
i=0
j=0
while (a[i] in model.wv.key_to_index)!=1:
i=i+1
x=model.wv[a[i]]
c.append(a[i])
j+=1
for i in range(i+1,len(a)-1):
if a[i] in model.wv.key_to_index:
x=np.vstack((x,model.wv[a[i]]))
c.append(a[i])
j+=1
#print(model.wv[a[i]])
else:
continue
x.astype('float32')
file1.close()
file1是打开’file1_2.txt’,
model进行载入,content读入,
a进行对content分割,这个分割是按照空格、换行等字符进行的。
x即为储存词向量的向量,首先给它生成一个空向量
由于word2vec中无法给出一个不在model里的词一个词向量,所以如果你贸然赋给x,就会报错,会报一个奇怪的错误:KeyError: “Key ‘用词’ not present”,但是你上网查询很难查的到,实际上这就是因为某个词在词向量中没有,你又直接调用了它。所以,我的处理方法就是,如果又一个词在model中没有,就直接忽略它(实际上是没有办法的办法,你们可以对其进行改进)
这里需要说明的一点是网上查到的查询一个词是否在model中的方法是不能用的,至少在我这个word2vec模型中不能用,可能是因为版本问题,我这个版本查询是:
if a[i] in model.wv.key_to_index
字面翻译一下就是a[i]这个词是不是在model里面
其次,当我们知道一个词a[i]时,我们需要用到model.wv[a[i]]来调用它的词向量。
还有x.astype(‘float32’) 是后面降低内存消耗时要用到的(虽然最后我也没搞懂到底咋降低内存)
最后我们就得到了x向量存储着这篇文章的向量序列,以及c数组储存着这篇文章的词(两者都删掉了model中不存在的词)
二、比较向量序列相似度
1、初始想法
我上网搜索发现WRD可以用来判断两文档相似度(实际上也是两向量序列),于是开始上网嫖代码,这里给出网址《从EMD、WMD到WRD:文本向量序列的相似度计算 》
苏剑林. (May. 13, 2020). 《从EMD、WMD到WRD:文本向量序列的相似度计算 》[Blog post]. Retrieved from https://kexue.fm/archives/7388
我看不懂,但我大为震撼
反正有代码,程序员讲究的就是一个代码复用
但遗憾的是,这个代码跑不动。因为只是两个10KB以内的文档就有差不多1000个词,仔细研究苏神给出的公式我们可以发现,我们要用linprog处理一个自由变量在
1
0
6
10^6
106这个数量级,如果直接运行,那么会报错,大致意思就是内存不够,好像需要16G左右,我于是上网查询,发现了两种解决办法:
(1)因为numpy开数组时默认是float64的,所以我想把float64改为float32甚至float16,这样能节省比较多的内存,但是鼓捣了半天,用astype(‘float32’)完全不管用,所以这条路我不会。
(2)把虚拟内存开大,这个上网查询即可得到,比较简单,但即使我开的大于16G,依旧报错,所以这条路也不通。
(3)一条未曾想过的道路:我不用liprog了,我上网查了一下,决定改为pulp,这也可以处理线性规划问题,于是又是一通鼓捣,在此附上我的代码(提前说明一下,能跑是能跑了,就是太慢,建议不要使用)
'''
def solve_ilp(objective,constraints):
prob=pulp.LpProblem("LP1",pulp.LpMinimize)
prob+=objective
for cons in constraints:
prob+=cons
status=prob.solve()
if status !=1:
return None
else:
return pulp.value(prob.objective)
def wasserstein_distance(p, q, D):
"""通过线性规划求Wasserstein距离
p.shape=[m], q.shape=[n], D.shape=[m, n]
p.sum()=1, q.sum()=1, p∈[0,1], q∈[0,1]
"""
variables=[[pulp.LpVariable(f'x{i}{j}',lowBound=0) for j in range(0,len(q))]for i in range(0,len(p))]
print(len(variables))
constraints=[]
#A_eq = []
#A_eq.astype('float32')
for i in range(len(p)):
A = np.zeros_like(D,dtype='float32')
#A.astype('float32')
#constraints.append(sum([variables[i*len(q)+k] for k in range(0,len(q))])==1)
A[i, :] = 1
constraints.append(sum([A[k]*variables[k] for k in range(0,len(variables))])==1)
# A_eq.append(A.reshape(-1))
#A_eq.astype('float32')
for i in range(len(q)):
A = np.zeros_like(D,dtype='float32')
#A.astype('float32')
#constraints.append(sum([variables[k*len(q)+i] for k in range(0,len(p))])==1)
A[:, i] = 1
constraints.append(sum([A[k]*variables[k] for k in range(0,len(variables))])==1)
# A_eq.append(A.reshape(-1))
#A_eq.astype('float32')
# A_eq = np.array(A_eq,dtype='float32')
#A_eq.astype('float32')
#b_eq = np.concatenate([p, q],dtype='float32')
# b_eq.astype('float32')
D = D.reshape(-1)
D.astype('float32')
objective=sum([D[i]*variables[i] for i in range(0,len(p)*len(q))])
#result =op.linprog(D, A_eq=A_eq[:-1], b_eq=b_eq[:-1])
result=solve_ilp(objective,constraints)
return result
#return result.fun
def word_rotator_distance(x, y):
"""WRD(Word Rotator's Distance)的参考实现
x.shape=[m,d], y.shape=[n,d]
"""
x_norm = (x**2).sum(axis=1, keepdims=True)**0.5
y_norm = (y**2).sum(axis=1, keepdims=True)**0.5
p = x_norm[:, 0] / x_norm.sum()
q = y_norm[:, 0] / y_norm.sum()
D = 1 - np.dot(x / x_norm, (y / y_norm).T)
#print(x/x_norm)
return wasserstein_distance(p, q, D)
#return x_norm
'''
很遗憾,虽然能跑了,但我跑了两小时还没跑出来,所以我决定不用它。
2、我的想法
(1)对于x中的每个向量,我们希望找到y中对应附近的最相似的向量,然后计算它们的平均相似度
s
i
m
a
v
e
r
a
g
e
sim_{average}
simaverage,这可以代表匹配向量的相似程度,设成功匹配了n个向量,y中有m个向量,这样
s
i
m
a
v
e
r
a
g
e
∗
n
/
m
sim_{average}*n/m
simaverage∗n/m代表了两向量序列的相似程度,即为
s
i
m
a
l
l
/
m
sim_{all}/m
simall/m
(2)当我们需要计算两向量相似度时,最容易想到的就是欧式距离,还有一个余弦距离,但它们会有“高维灾难”,我亲测发现如果向量为50维,用余弦距离的话几乎返回的都是0.99到1之间的值,这显然没有起到作用,其实仔细一想就能发现,任取一个向量,会有49维的空间与其垂直,所以余弦距离几乎没用;欧式距离我也用过,但是不太好比较,因为我训练的词向量模长最大值为400多,而平均值大概在8左右,这样返回值不好度量。
(3)所以为了克服高维灾难,我选择了pearson相关系数来度量,首先它返回的是-1到1,返回值比较直观,而且在数学上对于pearson系数也有一个判断方法来确定它们的相关程度;其次pearson主要看的是两列数的变化趋势是否相同,它是不比较模长的,这里我确实没有用到模长信息,但是有关前提是模长代表着该词的使用频率,那么我们可以假设,该词的基本特征是与它的模长无关的。
下面是处理两向量相似度的代码:
def similarity(vector1,vector2):
#return (1+np.dot(vector1,vector2)/(np.linalg.norm(vector1)*(np.linalg.norm(vector2))))/2
#return np.dot(vector1-vector2,vector1-vector2)/3
sum1=0
sum2=0
for i in range(50):
sum1=sum1+vector1[i]
sum2=sum2+vector2[i]
sum1=sum1/50
sum2=sum2/50
a1=0
a2=0
b=0
for i in range(50):
a1=a1+(vector1[i]-sum1)*(vector1[i]-sum1)
a2=a2+(vector2[i]-sum2)*(vector2[i]-sum2)
b=b+(vector1[i]-sum1)*(vector2[i]-sum2)
a1=cmath.sqrt(a1)
a2=cmath.sqrt(a2)
if b>0:
return b/(a1*a2)
else:
return -1*b/(a1*a2)
我们发现,第一点中的“y中对应附近的最相似的向量”不太好辨别:
- 首先不能比较完全相同的向量,因为考虑有两文档,两者初始完全相同,这时候,我们把其中一个文档的第一个词和最后一个词互换,如果足够“幸运”,那么我们就发现,这两个文档仅匹配出了一个词。所以如果两向量足够相似,大于某一阈值,就可以了。
- 其次,我们不能通篇查找比较,因为考虑还是初始相同的两文档,在其中一个文档首插入一个没什么关系的词,然后我们就发现,它连一个词也匹配不了。
- 所以,综上考虑,我决定,选取10个向量进行比较,如果还是没有匹配上,就直接舍弃。
(补充说明,这个阈值可以通过pearson系数的“自带”的阈值进行比较,即大于0.8即强相关。同时,这个阈值,还有这个10个单词,是我自己想的,所以,这就提供给你们做灵敏度分析了)
最后就是计算的代码:
def solve(x,y):
memory=np.zeros(len(x))
j=0
for i in range(len(x)):
memory[i]=-1
for i in range(len(x)):
temp_j=j
record=j
if j>=len(y):
break
#if i==0:
# print(j)
# print(similarity(x[i],y[j]))
while similarity(x[i],y[j])<0.8:
#print("run")
if similarity(x[i],y[record])<similarity(x[i],y[j]):
record=j
if j-temp_j>10:
j=record
break
j=j+1
if j>=len(y):
break
if j==len(y):
break
if similarity(x[i],y[j])<0.8:
j=record
memory[i]=record
else:
memory[i]=j
j=j+1
record=0
for i in range(len(x)):
if memory[i]!=-1:
record=i
else:
break
record=record+1
a=0
#print(record)
for i in range(record):
if similarity(x[i],y[int(memory[i])])!=0:
print(similarity(x[i],y[int(memory[i])]))
#print(memory[i])
a=a+similarity(x[i],y[int(memory[i])])
#print(record)
return a/len(y)
三、总代码
import re
import codecs
import chardet
import jieba
import jieba.analyse
import jieba.posseg as pseg
import codecs,sys
import warnings
import logging
import os.path
import sys
import multiprocessing
import numpy as np
from win32com import client as wc #导入模块
from scipy import optimize as op
import gensim
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence
import docx
from docx import Document
#import pulp as pulp
import cmath
def doc_to_docx(file):
word = wc.Dispatch("Word.Application") # 打开word应用程序
doc = word.Documents.Open(file) #打开word文件
doc.SaveAs("{}x".format(file), 12)#另存为后缀为".docx"的文件,其中参数12指docx文件
doc.Close() #关闭原来word文件
word.Quit()
#print("完成!")
return "{}x".format(file)
def similarity(vector1,vector2):
#return (1+np.dot(vector1,vector2)/(np.linalg.norm(vector1)*(np.linalg.norm(vector2))))/2
#return np.dot(vector1-vector2,vector1-vector2)/3
sum1=0
sum2=0
for i in range(50):
sum1=sum1+vector1[i]
sum2=sum2+vector2[i]
sum1=sum1/50
sum2=sum2/50
a1=0
a2=0
b=0
for i in range(50):
a1=a1+(vector1[i]-sum1)*(vector1[i]-sum1)
a2=a2+(vector2[i]-sum2)*(vector2[i]-sum2)
b=b+(vector1[i]-sum1)*(vector2[i]-sum2)
a1=cmath.sqrt(a1)
a2=cmath.sqrt(a2)
if b>0:
return b/(a1*a2)
else:
return -1*b/(a1*a2)
def solve(x,y):
memory=np.zeros(len(x))
j=0
for i in range(len(x)):
memory[i]=-1
for i in range(len(x)):
temp_j=j
record=j
if j>=len(y):
break
#if i==0:
# print(j)
# print(similarity(x[i],y[j]))
while similarity(x[i],y[j])<0.8:
#print("run")
if similarity(x[i],y[record])<similarity(x[i],y[j]):
record=j
if j-temp_j>10:
j=record
break
j=j+1
if j>=len(y):
break
if j==len(y):
break
if similarity(x[i],y[j])<0.8:
j=record
memory[i]=record
else:
memory[i]=j
j=j+1
record=0
for i in range(len(x)):
if memory[i]!=-1:
record=i
else:
break
record=record+1
a=0
#print(record)
for i in range(record):
if similarity(x[i],y[int(memory[i])])!=0:
print(similarity(x[i],y[int(memory[i])]))
#print(memory[i])
a=a+similarity(x[i],y[int(memory[i])])
return a/len(y)
'''
def solve_ilp(objective,constraints):
prob=pulp.LpProblem("LP1",pulp.LpMinimize)
prob+=objective
for cons in constraints:
prob+=cons
status=prob.solve()
if status !=1:
return None
else:
return pulp.value(prob.objective)
def wasserstein_distance(p, q, D):
"""通过线性规划求Wasserstein距离
p.shape=[m], q.shape=[n], D.shape=[m, n]
p.sum()=1, q.sum()=1, p∈[0,1], q∈[0,1]
"""
variables=[[pulp.LpVariable(f'x{i}{j}',lowBound=0) for j in range(0,len(q))]for i in range(0,len(p))]
print(len(variables))
constraints=[]
#A_eq = []
#A_eq.astype('float32')
for i in range(len(p)):
A = np.zeros_like(D,dtype='float32')
#A.astype('float32')
#constraints.append(sum([variables[i*len(q)+k] for k in range(0,len(q))])==1)
A[i, :] = 1
constraints.append(sum([A[k]*variables[k] for k in range(0,len(variables))])==1)
# A_eq.append(A.reshape(-1))
#A_eq.astype('float32')
for i in range(len(q)):
A = np.zeros_like(D,dtype='float32')
#A.astype('float32')
#constraints.append(sum([variables[k*len(q)+i] for k in range(0,len(p))])==1)
A[:, i] = 1
constraints.append(sum([A[k]*variables[k] for k in range(0,len(variables))])==1)
# A_eq.append(A.reshape(-1))
#A_eq.astype('float32')
# A_eq = np.array(A_eq,dtype='float32')
#A_eq.astype('float32')
#b_eq = np.concatenate([p, q],dtype='float32')
# b_eq.astype('float32')
D = D.reshape(-1)
D.astype('float32')
objective=sum([D[i]*variables[i] for i in range(0,len(p)*len(q))])
#result =op.linprog(D, A_eq=A_eq[:-1], b_eq=b_eq[:-1])
result=solve_ilp(objective,constraints)
return result
#return result.fun
def word_rotator_distance(x, y):
"""WRD(Word Rotator's Distance)的参考实现
x.shape=[m,d], y.shape=[n,d]
"""
x_norm = (x**2).sum(axis=1, keepdims=True)**0.5
y_norm = (y**2).sum(axis=1, keepdims=True)**0.5
p = x_norm[:, 0] / x_norm.sum()
q = y_norm[:, 0] / y_norm.sum()
D = 1 - np.dot(x / x_norm, (y / y_norm).T)
#print(x/x_norm)
return wasserstein_distance(p, q, D)
#return x_norm
'''
file = r"G:\miniconda\procedure\data1.doc"
doc_to_docx(file)
file = r"G:\miniconda\procedure\data2.doc"
doc_to_docx(file)
file1=docx.Document('data1.docx')
#file1=file1.encode('utf-8').decode('utf-8')
with open('file1.txt','w',encoding='utf-8') as fw:
for context in file1.paragraphs:
text=context.text.split('\n')
fw.write(f"{text[0]}\n")
file1=codecs.open('file1.txt', 'r',encoding='utf-8').read()
file1 = file1.encode('utf-8').decode('utf-8')
punct=codecs.open('punctuation.txt','r',encoding='utf-8')
punctuation=list()
for line in punct:
word=line.strip('\r\n')
word=word.encode('utf-8').decode('utf-8')
punctuation.append(word)
deal1=codecs.open('file1_1.txt','w',encoding='utf-8')
str=''.encode('utf-8').decode('utf-8')
for item in file1:
if item in punctuation:
continue
else:
str=str+item
with open('file1_1.txt','a',encoding='utf-8') as deal1:
deal1.write(str)
file1=codecs.open('file1_1.txt','r',encoding='utf-8')
target=codecs.open('file1_2.txt','w',encoding='utf-8')
print('open files')
line_num=1
line=file1.readline()
while line:
line_seg=" ".join(jieba.cut(line))
target.writelines(line_seg)
line_num=line_num+1
line=file1.readline()
file1.close()
target.close()
file2=docx.Document('data2.docx')
#file1=file1.encode('utf-8').decode('utf-8')
with open('file2.txt','w',encoding='utf-8') as fw:
for context in file2.paragraphs:
text=context.text.split('\n')
fw.write(f"{text[0]}\n")
file2=codecs.open('file2.txt', 'r',encoding='utf-8').read()
file2 = file2.encode('utf-8').decode('utf-8')
punct=codecs.open('punctuation.txt','r',encoding='utf-8')
punctuation=list()
for line in punct:
word=line.strip('\r\n')
word=word.encode('utf-8').decode('utf-8')
punctuation.append(word)
deal2=codecs.open('file2_1.txt','w',encoding='utf-8')
str=''.encode('utf-8').decode('utf-8')
for item in file2:
if item in punctuation:
continue
else:
str=str+item
with open('file2_1.txt','a',encoding='utf-8') as deal1:
deal1.write(str)
file2=codecs.open('file2_1.txt','r',encoding='utf-8')
target=codecs.open('file2_2.txt','w',encoding='utf-8')
print('open files')
line_num=1
line=file2.readline()
while line:
line_seg=" ".join(jieba.cut(line))
target.writelines(line_seg)
line_num=line_num+1
line=file2.readline()
file2.close()
target.close()
model=gensim.models.Word2Vec.load("result_2.model")
more_sentence='file1_2.txt'
model.build_vocab(LineSentence(more_sentence),update=True)
model.train(LineSentence(more_sentence),total_examples=2000000,epochs=model.epochs,word_count=1)
model.save('result_2.model')
model.wv.save_word2vec_format('result_2.vector')
model=gensim.models.Word2Vec.load("result_2.model")
more_sentence='file2_2.txt'
model.build_vocab(LineSentence(more_sentence),update=True)
model.train(LineSentence(more_sentence),total_examples=2000000,epochs=model.epochs)
model.save('result_2.model')
model.wv.save_word2vec_format('result_2.vector')
file1=codecs.open('file1_2.txt','r',encoding='utf-8')
model=gensim.models.Word2Vec.load("result_2.model")
content=file1.read()
a=content.split()
x=np.empty(50,dtype='float32')
c=[]
i=0
j=0
while (a[i] in model.wv.key_to_index)!=1:
i=i+1
x=model.wv[a[i]]
c.append(a[i])
j+=1
for i in range(i+1,len(a)-1):
if a[i] in model.wv.key_to_index:
x=np.vstack((x,model.wv[a[i]]))
c.append(a[i])
j+=1
#print(model.wv[a[i]])
else:
continue
x.astype('float32')
file1.close()
file2=codecs.open('file2_2.txt','r',encoding='utf-8')
model=gensim.models.Word2Vec.load("result_2.model")
content=file2.read()
b=content.split()
y=np.empty(50,dtype='float32')
i=0
while (b[i] in model.wv.key_to_index)!=1:
i=i+1
y=model.wv[b[i]]
for i in range(0,len(b)-1):
if b[i] in model.wv.key_to_index:
y=np.vstack((y,model.wv[b[i]]))
else:
continue
y.astype('float32')
file2.close()
with open('向量表示.txt','w',encoding='utf-8') as fw:
fw.write('第一个文本:\n')
for i in range(len(x)):
#text=context.text.split('\n')
temp=x[i]
fw.write(f"{temp[0]}\n")
fw.write('\n\n第二个文本:\n')
for i in range(len(y)):
#text=context.text.split('\n')
temp=y[i]
fw.write(f"{temp[0]}\n")
#print(word_rotator_distance(x,y)/2*100,'%')
#print(word_rotator_distance(x,y))
#print(x)
#c=word_rotator_distance(x,y)
#print(model.wv[a[0]])
#print(x[0])
if len(x)<len(y):
print(solve(x,y).real)
else:
print(solve(y,x).real)
四、改进
目前我能够想到的改进方向有:
- 我们在匹配时可以引入动态规划,计算出最大匹配度。
- 在匹配后,我们就可以得到俩个维数相同的向量集,然后说不定此处也可以通过pearson系数来搞,但我不会。
欢迎进行改进。