书接上文:CMeKG代码解读(以项目为导向从零开始学习知识图谱)(三)_chen_nnn的博客-CSDN博客作者从零开始学习和知识图谱有关技术和内容,而本文的核心内容是对CMeKG的python代码进行学习和解读,供大家讨论参考共同进步。CMeKG(Chinese Medical Knowledge Graph)是利用自然语言处理与文本挖掘技术,基于大规模医学文本数据,以人机结合的方式研发的中文医学知识图谱。https://blog.csdn.net/chen_nnn/article/details/122842069?spm=1001.2014.3001.5501
开始新的一个文件的解读,加油加油!
目录
medical_cws.py
medical_seg类:
from_input():
from_txt():
recover_to_text():
predict_sentense():
predict_file():
结语
该项目文件将主要用于医学文本分词,而之前的medical_re.py文件则是用于医学关系抽取。在文件的开始还是常规的引入各种模块,但是后面的utils、cws_constant、model_cws都是自己书写的函数。os.environ[‘环境变量名称’]=‘环境变量值’ ,其中key和value均为string类型,而文中这个语句的作用是设置程序可见的显卡ID。如果"CUDA_VISIBLE_DEVICES" 书写错误,也不报错,自动选用第一个显卡。
import codecs
import torch
from torch.autograd import Variable
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
from utils import load_vocab
from cws_constant import *
from model_cws import BERT_LSTM_CRF
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "2"
class medical_seg(object):
def __init__(self):
self.NEWPATH = '/Users/yangyf/workplace/model/medical_cws/pytorch_model.pkl'
if torch.cuda.is_available():
self.device = torch.device("cuda", 0)
self.use_cuda = True
else:
self.device = torch.device("cpu")
self.use_cuda = False
self.vocab = load_vocab('/Users/yangyf/workplace/model/medical_cws/vocab.txt')
self.vocab_reverse = {v: k for k, v in self.vocab.items()}
self.model = BERT_LSTM_CRF('/Users/yangyf/workplace/model/medical_cws', tagset_size, 768, 200, 2,
dropout_ratio=0.5, dropout1=0.5, use_cuda=use_cuda)
if use_cuda:
self.model.cuda()
该init中主要是将所需要的设备和数据引入,首先定义好路径,然后询问设备cuda(GPU)是否就绪,如果可以的话就将设备设置为0号cuda,并将使用cuda(use_cuda)的参数设置为真,否则将在cpu上运行并将use_cuda设置为假。然后从文件夹中加载词汇文本并将其以字典的形式存储在vocab_reverse中,并且加载分词模型,并设定好参数,最后判断如果use_cuda为真,则在cuda上开始分词。
def from_input(self, input_str):
# 单行的输入
raw_text = []
textid = []
textmask = []
textlength = []
text = ['[CLS]'] + [x for x in input_str] + ['[SEP]']
raw_text.append(text)
cur_len = len(text)
# raw_textid = [self.vocab[x] for x in text] + [0] * (max_length - cur_len)
raw_textid = [self.vocab[x] for x in text if self.vocab.__contains__(x)] + [0] * (max_length - cur_len)
textid.append(raw_textid)
raw_textmask = [1] * cur_len + [0] * (max_length - cur_len)
textmask.append(raw_textmask)
textlength.append([cur_len])
textid = torch.LongTensor(textid)
textmask = torch.LongTensor(textmask)
textlength = torch.LongTensor(textlength)
return raw_text, textid, textmask, textlength
首先根据单行输入的字符串列表,在函数内部定好text内容,在前面加上文本开始符,最后加上文本结束符。然后将其附在原始文本之后。首先判断如果在text文本中的词出现在vocab字典中则将其保存下来,如果不足max_length则用0补齐,并将这些出现在字典中的id附在textid中记录下来,同时定义一个掩码,掩码的长度就是文本的长度,不足最大长度则用0补齐,然后附在textmask之后,同时也将长度存储在textlength中,最后将textid、textmask、textlength转换成longtensor型并返回。
def from_txt(self, input_path):
# 多行输入
raw_text = []
textid = []
textmask = []
textlength = []
with open(input_path, 'r', encoding='utf-8') as f:
for line in f.readlines():
if len(line) > 148:
line = line[:148]
temptext = ['[CLS]'] + [x for x in line[:-1]] + ['[SEP]']
cur_len = len(temptext)
raw_text.append(temptext)
tempid = [self.vocab[x] for x in temptext[:cur_len]] + [0] * (max_length - cur_len)
textid.append(tempid)
textmask.append([1] * cur_len + [0] * (max_length - cur_len))
textlength.append([cur_len])
textid = torch.LongTensor(textid)
textmask = torch.LongTensor(textmask)
textlength = torch.LongTensor(textlength)
return raw_text, textid, textmask, textlength
上一个函数用于单行输入,该函数用于处理多行输入时的文本切分,前面是同样的预定义,然后以'utf-8'的编码形式编译文件中的内容,如果文件中的某一行超过了148字长,就截取前148个词,之后的处理就和处理单行输入时的文本内容一致了,不再赘述。
def recover_to_text(self, pred, raw_text):
# 输入[标签list]和[原文list],batch为1
pred = [i2l_dic[t.item()] for t in pred[0]]
pred = pred[:len(raw_text)]
pred = pred[1:-1]
raw_text = raw_text[1:-1]
raw = ""
res = ""
for tag, char in zip(pred, raw_text):
res += char
if tag in ["S", 'E']:
res += ' '
raw += char
return raw, res
我认为该函数功能比较简单,就是根据模型预测好之后的pred按照预测结果将词从原文中取出来,并返回原文和分词之后的结果。
def predict_sentence(self, sentence):
if sentence == '':
print("输入为空!请重新输入")
return
if len(sentence) > 148:
print("输入句子过长,请输入小于148的长度字符!")
sentence = sentence[:148]
raw_text, test_ids, test_masks, test_lengths = self.from_input(sentence)
test_dataset = TensorDataset(test_ids, test_masks, test_lengths)
test_loader = DataLoader(test_dataset, shuffle=False, batch_size=1)
# self.model.load_state_dict(torch.load(self.NEWPATH, map_location={'cuda:0': str(self.device)}))
self.model.load_state_dict(torch.load(self.NEWPATH,map_location=self.device))
self.model.eval()
for i, dev_batch in enumerate(test_loader):
sentence, masks, lengths = dev_batch
batch_raw_text = raw_text[i]
sentence, masks, lengths = Variable(sentence), Variable(masks), Variable(lengths)
if use_cuda:
sentence = sentence.cuda()
masks = masks.cuda()
predict_tags = self.model(sentence, masks)
predict_tags.tolist()
raw, res = self.recover_to_text(predict_tags, batch_raw_text)
#print("输入:", raw)
#print("结果:", res)
return res
首先对输入的句子做最基本的判断是否为空,是否超过最大字长。然后将输入得到的sentence输入from_input函数中处理,返回原文、id、mask和length。然后是TensorDataset可以用来对 tensor 进行打包,就好像 python 中的 zip 功能。该类通过每一个 tensor 的第一个维度进行索引。因此,该类中的 tensor 第一维度必须相等。而DataLoader就是用来包装所使用的数据,每次抛出一批数据,从数据库中每次抽出batch_size个样本。至于load_state_dict()则是对模型进行完成度匹配,然后对模型的参数进行评价。 然后根据enumerate生成的编号进行循环,取出原文,掩码和长度等信息,然后询问是否能在cuda上运行,如果可以的话则将对句子和掩码放到GPU上运行。然后进行模型的训练得到预测的标签,并将该标签和原文作为输入参数放到recover_to_text中进行匹配输出。
def predict_file(self, input_file, output_file):
# raw_text, test_ids, test_masks, test_lengths = self.from_txt("./data/raw_text.txt")
raw_text, test_ids, test_masks, test_lengths = self.from_txt(input_file)
test_dataset = TensorDataset(test_ids, test_masks, test_lengths)
test_loader = DataLoader(test_dataset, shuffle=False, batch_size=1)
self.model.load_state_dict(torch.load(self.NEWPATH, map_location={'cuda:0': str(self.device)}))
self.model.eval()
op_file = codecs.open(output_file, 'w', 'utf-8')
for i, dev_batch in enumerate(test_loader):
sentence, masks, lengths = dev_batch
batch_raw_text = raw_text[i]
sentence, masks, lengths = Variable(sentence), Variable(masks), Variable(lengths)
if use_cuda:
sentence = sentence.cuda()
masks = masks.cuda()
predict_tags = self.model(sentence, masks)
predict_tags.tolist()
raw, res = self.recover_to_text(predict_tags, batch_raw_text)
op_file.write(res + '\n')
op_file.close()
print('处理完成!')
print("results have been stored in {}".format(output_file))
此时是处理多行输入的情况,根据输入的文本路径,打开文件,同样经过TensorDataset和DataLoader,还有模型的匹配和评价步骤。对于文本中内容的处理与处理单行输入是一致,只不过是添加了部分文档操作的内容。
医疗文本分词的源码文件也已经分析完了,相比起来前面的关系抽取难度降低了不少,也比较好懂,但是目前会遇到一些原理上的问题,还需学习部分机器学习的内容也许可以帮的上忙。如果您看到这里的话,希望您能留下一个免费的赞赞,这对我的帮助很大,对我本人同样也有着激励作用。