基于word2vec的疾病和手术相关词语的相似度计算

项目需要预测是否患有骨质疏松,患者做过的手术类型是其中的一维特征,因此需要得到骨质疏松或骨量减少和手术之间的关系,此处选择用word2vec得到词语之间的相似度。

一、文本预处理

原始数据为csv格式,需要处理的为whatoperation这一列特征,即患者做过的手术类型。
该列特征为文本类型,每行有若干个手术名称,名称之间用中文逗号分隔符分开,由于是人手动填的,有一些表述上的问题,因此规范性不高。

(1)去除表示方位的词

个人主观判断这些词对骨质疏松的预测影响较小,保留的话容易对相似度造成影响,因此去除。
这类词包括:

左、右、单、双、左侧、右侧、单侧、双侧、右叶等。

(2)同义词替换

在医学上含义相似或相同时,统一为常见表述。或者统一为便于分词的形式。
此处个人主观判断较多。
这类词包括:

次全/大部——部分
腰椎钉子钉入/腰突单摘术——腰椎间盘突出
甲旁亢——甲状旁腺功能亢进
脊椎膜瘤——脊膜瘤 甲状腺腺瘤——甲状腺瘤
肝多发囊肿——多发性肝囊肿
子宫切除术——子宫切除

(3)错别字替换

破腹产——剖腹产

(4)统一分隔符

“和”、“;”、“,”等统一为全角逗号

(5)统计分析

首先将csv的whatoperation列筛选非空值并复制粘贴到txt中,对txt进行处理。

def getwordsset(infile, outfile):
    infp = open(infile, "r",encoding='utf8')
    lines = infp.readlines()
    words = []
    for li in lines:
        if li.split():
            xs = li.split()[0].split(',')
            for x in xs:
                # print(x)
                if x != '\ufeff':
                    words.append(x)

    wordsset = set(words)
    print("词典的大小为:",len(wordsset))
    infp.close()

    outfp = open(outfile, "w", encoding='utf8')
    for word in wordsset:
        outfp.writelines(word)
        outfp.writelines('\n')
    outfp.close()


    return wordsset
wordsset = getwordsset("../data/operation.txt", "../data/operation2.txt")

从1112个非空值中,得到包含全部手术名词的去重后的词典,大小为257个词。
由于这些词中包含一些大部分语料库中都没有的长词,如“腹部平滑肌瘤”、“腰椎压缩性骨折”等。因此对其进行分词:

def fenci(wordsset):
    smallwords = []
    for word in wordsset:
        word_seged = jieba.cut(word.strip())
        for wordfenci in word_seged:
            if wordfenci != '\ufeff':
                smallwords.append(wordfenci)

    smallwords = set(smallwords)
    print("分词后词典大小为:",len(smallwords))

    return smallwords
smallwords = fenci(wordsset)

对这200多个长词进行分词。
分词后词典大小为: 220
统计分词后的词是否在维基百科中文语料库中全部出现:

def findInWiki(wordsset):
    model = word2vec.Word2Vec.load('F:\IBM\data\维基百科\extracted\AA\word2vecModel\WikiCHModel')
    count = 0
    dic = {}
    print(wordsset)
    for word in wordsset:
        try:
            embedding = model[word]
            # print(word)
        except Exception:
            embedding = np.random.uniform(0, 1, 5)
            print(word)
            count += 1
        dic[word] = embedding
    print("不在维基语料库中的分词后的词的个数:",count)
    return dic

不在维基语料库中的分词后的词的个数:8
8个词为:造瘘、臂骨、锥切、融合术、全切、截趾、胆囊结石、脊膜瘤。

二、构建Word2vec模型

1. 用gensim学习word2vec

gensim是一个很好用的Python NLP的包,不光可以用于使用word2vec,还有很多其他的API可以用。它封装了google的C语言版的word2vec。当然我们可以可以直接使用C语言版的word2vec来学习,但是个人认为没有gensim的python版来的方便。

安装gensim是很容易的,使用"pip install gensim"即可。但是需要注意的是gensim对numpy的版本有要求,所以安装过程中可能会偷偷的升级你的numpy版本。而windows版的numpy直接装或者升级是有问题的。此时我们需要卸载numpy,并重新下载带mkl的符合gensim版本要求的numpy,下载地址在此:http://www.lfd.uci.edu/~gohlke/pythonlibs/#scipy 。安装方法和scikit-learn 和pandas 基于windows单机机器学习环境的搭建这一篇第4步的方法一样。

安装成功的标志是你可以在代码里做下面的import而不出错:

from gensim.models import word2vec

2.获取word2vec训练语料

首先做了一些无用的尝试,此处仅记录过程。(这部分可以忽略掉)

骨质疏松相关医药语料库

首先从下文中提到的大的全科疾病问答语料库的全部json数据中找出和骨质疏松相关的疾病数据,此处通过疾病名称中是否含有’骨’或’钙’来判断。
处理过程如下:
(1)首先从全部json数据中找出和骨质疏松相关的疾病数据,此处通过疾病名称中是否含有’骨’或’钙’来判断,得到473个相关疾病。代码如下:

# 查找全部json数组中和骨质疏松相关的疾病
fileread = open('data/medical.json','r',encoding='utf-8')
filewritedisname = open("data/disease-osteoporosis.txt",'w',encoding='utf-8')

count = 0
diseasename = []
for line in fileread.readlines():
    dic = json.loads(line)
    if '骨' in dic['name'] or '钙' in dic['name']:
        count += 1
        diseasename.append(json.dumps(dic['name'], ensure_ascii=False).strip('"'))   # 注意此处应先把双引号去掉,否则list里有两个引号
        filewritedisname.write(json.dumps(dic['name'], ensure_ascii=False) + '\n')
print("相关疾病名称加载入文件完成...")
print(count)
print(diseasename)
fileread.close()


# 根据疾病名称从全部json中筛选出骨质疏松相关的数据
fileread = open('data/medical.json','r',encoding='utf-8')
filewrite = open("data/osteoporosis.json", "w", encoding='utf-8')

count = 0
for line in fileread.readlines():
    dic = json.loads(line)
    if dic['name'] in diseasename:
        count += 1
        filewrite.write(json.dumps(dic, ensure_ascii=False)+'\n')

print("相关疾病数据加载入文件完成...")
print(count)
fileread.close()

(2)得到json中字典全部的值,即疾病的全部属性

# 提取json字典中部分需要的值
fileread = open("../data/osteoporosis.json", "r", encoding='utf-8')
keys = set()
for line in fileread.readlines():
    dic = json.loads(line)
    if isinstance(dic, dict):  # 判断是否是字典类型isinstance 返回True false
        for key in dic:
            # print("key:%s" % key)
            keys.add('"'+key+'"')
    break

print(keys)
fileread.close()

(3)从全部的keys里手工挑选出有用的属性,并得到对应的文本值,保存到txt

# 从全部的keys里手工挑选出文本有用的keys,并得到对应的value,保存到txt
keys = ['recommand_drug', 'desc', 'name', 'prevent', 'not_eat', 'do_eat', 'recommand_eat', 'recommand_drug', 'easy_get', 'drug_detail', 'cause', 'symptom', 'acompany', 'check', 'common_drug']

fileread = open("../data/osteoporosis.json", "r", encoding='utf-8')
filewrite = open("../data/KBQAdata.txt", "w", encoding='utf-8')
for line in fileread.readlines():
    dic = json.loads(line)
    for key in dic:
        if key in keys and dic[key]!=[]:   # 注意字典的值可能为空
            filewrite.write(str(dic[key]).replace("\n","").replace("[","").replace("]","")+' ')    # 注意行内有换行符和[]
    filewrite.write('\n')

fileread.close()
filewrite.close()

这个语料库的问题在于只有骨质疏松相关的内容,很多手术词不在语料库里。

维基百科中文语料库

具体训练过程可以参考这篇:使用中文维基百科训练word2vec模型
这个语料库的问题是在训练的时候没有指定自定义词典,导致语料库里没有“骨质疏松”这个词。。。然后分别计算“骨质”和“疏松”与手术词之间的相似度并求平均,发现不够准确。

骨质疏松相关文献语料库

这个语料库构建的思路是手动获取骨质疏松预防诊疗指南等文章和骨质疏松与各个手术词相关的文献,进行OCR,得到txt格式的语料。
这个思路的问题在于,只用骨质疏松相关的文章,缺失词太多;加上手术词相关文献的话,手术词太多了,检索起来没有头绪,手动下载文献和OCR太麻烦。

--------------------------------------------------有用的正文分隔符---------------------------------------------------------
实际采用的语料为:

疾病全科语料库+骨质疏松相关文献语料库

来自于github上的开源项目,基于知识图谱的医药领域问答项目QABasedOnMedicaKnowledgeGraph。该语料为json格式,包含了8000多种疾病相关的各种属性,(1)首先从json中提取和骨质疏松预测相关的文本数据

import json

# 从全部的keys里手工挑选出文本有用的keys,并得到对应的value,保存到txt
keys = ['recommand_drug', 'desc', 'name', 'prevent', 'not_eat', 'do_eat', 'recommand_eat', 'recommand_drug', 'easy_get', 'drug_detail', 'cause', 'symptom', 'acompany', 'check', 'common_drug']

fileread = open("../data/medical.json", "r", encoding='utf-8')
filewrite = open("../data/KBQAdata-all.txt", "w", encoding='utf-8')
for line in fileread.readlines():
    dic = json.loads(line)
    for key in dic:
        if key in keys and dic[key]!=[]:   # 注意字典的值可能为空
            filewrite.write(str(dic[key]).replace("\n","").replace("[","").replace("]","")+' ')    # 注意行内有换行符和[]
    filewrite.write('\n')

fileread.close()
filewrite.close()

(2)构建自定义词典
原项目中有一个自定义词典worddict.txt,包含了全部的医药饮食相关的实体。针对骨质疏松这个特定任务,从患者所做手术此列特征中提出全部文本,加入worddict.txt中并去重。
(3)分词去停

import jieba

jieba.load_userdict("../data/worddict.txt")        #加载自定义词典

# 创建停用词list
def stopwordslist(filepath):
    stopwords = [line.strip() for line in open(filepath, 'r',encoding='utf-8').readlines()]
    return stopwords

# 对句子进行分词
def seg_sentence(sentence):
    sentence_seged = jieba.cut(sentence.strip())
    stopwords = stopwordslist('../data/stopwords.txt')  # 这里加载停用词的路径
    outstr = ''
    for word in sentence_seged:
        if word not in stopwords:
            if word != '\t':
                outstr += word
                outstr += " "
    return outstr


inputs1 = open('../data/text-KBQA.txt', 'r',encoding='utf-8')  # 疾病百科
inputs2 = open('../data/text-literature.txt','r',encoding='utf-8')  # 相关文献集合
outputs = open('../data/text-all-fenci.txt', 'w',encoding='utf-8')  # 两部分txt分词后的集合
for line in inputs1:
    line_seg = seg_sentence(line)  # 这里的返回值是字符串
    outputs.write(line_seg)
inputs1.close()
for line in inputs2:
    line_seg = seg_sentence(line)  # 这里的返回值是字符串
    outputs.write(line_seg)
inputs2.close()
outputs.close()

3.训练

def trainw2v():
    # 训练word2vec
    sentences = word2vec.Text8Corpus(u"../data/text-all-fenci.txt")  # 加载语料
    model = word2vec.Word2Vec(sentences, size=5)  # 训练skip-gram模型; 默认window=5

    # 保存模型
    model.save('../model/word2vec-large2')

三、计算相似度

def calSimilarity1(wordsset):
    ''' 计算骨质疏松和手术词之间的相似度——用爬虫数据/相关文献txt训练'''
    # model = word2vec.Word2Vec.load('F:\IBM\data\维基百科\extracted\AA\word2vecModel\WikiCHModel')
    model = word2vec.Word2Vec.load('../model/word2vec-large2')
    sim_words = {}
    for word in wordsset:
        if word != '\ufeff':
            try:
                sim = model.similarity(u"骨质疏松", word)
                # sim_seged = (model.similarity(u"骨质", wordfenci) + model.similarity(u"疏松", wordfenci)) / 2  #  取手术短词的相似度平均值
            except Exception:
                sim = np.random.uniform(-1, 1)
        print(u"'骨质疏松'和%s的相似度为:%f" % (word , sim))
        sim_words[word] = sim
    print(sim_words)
    return sim_words

def full_csv(sim_words):
    ''' 将相似度值按顺序写入Excel中 '''
    wb_read = xlrd.open_workbook('F:\毕设开题\数据整理\\newforCode.xlsx')  # 打开原始文件

    sheet_pri = wb_read.sheets()[1]
    operations = sheet_pri.col_values(26)[1:]
    print(operations)

    wb_result = xlwt.Workbook()  # 新建一个文件,用来保存结果
    sheet_result = wb_result.add_sheet('operation', cell_overwrite_ok=True)

    for i in range(len(operations)):
        if operations[i] == '':
            sheet_result.write(i, 0, '')
        else:
            sim_sum = []
            for word in operations[i].split(','):
                sim_sum.append(sim_words[word])
            sim = max(sim_sum)               # 当一个人做了多种手术时,取最大的相似度
            print(operations[i],sim)
            sheet_result.write(i, 0, str(sim))
    wb_result.save('C:\\Users\ASUS\Desktop\sim3.xls')

总结:
主观判断相似度情况是爬虫数据+文献的好于爬虫的好于维基百科的,因为维基百科中缺失词太多,只能通过jieba将手术词分开,会降低语义效果。
word2vec语料库越大训练效果越好,然而专业领域的语料实在太难获得了。语料选择有些武断,如果有机会可以尝试使用自定义词典进行分词后的维基百科语料库,对比下效果。

参考网址:
用gensim学习word2vec

发布了143 篇原创文章 · 获赞 161 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/vivian_ll/article/details/89534093