oragindata
{{product_name:浙江在线杭州}}{{time:4月25日}}讯(记者{{person_name: 施宇翔}} 通讯员 {{person_name:方英}})毒贩很“时髦”,用{{product_name:微信}}交易毒品。没料想警方也很“潮”,将计就计,一举将其擒获。记者从{{org_name:杭州江干区公安分局}}了解到,经过一个多月的侦查工作,{{org_name:江干区禁毒专案组}}抓获吸贩毒人员5名,缴获“冰毒”400余克,毒资30000余元,扣押汽车一辆。{{location:黑龙江}}籍男子{{person_name:钱某}}长期落脚于宾馆、单身公寓,经常变换住址。他有一辆车,经常半夜驾车来往于{{location:杭州主城区}}的各大宾馆和单身公寓,并且常要活动到{{time:凌晨6、7点钟}},{{time:白天}}则在家里呼呼大睡。{{person_name:钱某}}不寻常的特征,引起了警方注意。禁毒大队通过侦查,发现{{person_name:钱某}}实际上是在向落脚于宾馆和单身公寓的吸毒人员贩送“冰毒”。
第一层处理:实现对单个字打标签,具体实现:按照括号配对," : "左边的是tag,右边的是word。再将标签分为开始,中间,结束,分别给word中的每个字打tag。给没有“{{ }}”的每个字打上/o。将处理后的文件输入wordtag中。
def origin2tag(): input_data = codecs.open('./origindata.txt','r','utf-8') output_data = codecs.open('./wordtag.txt','w','utf-8') # 一个line是一个待分析的实例 for line in input_data.readlines(): # 将line中的词组分出来 line=line.strip() i=0 #读取词的时候计数 while i <len(line): if line[i] == '{': i+=2 temp="" while line[i]!='}': temp+=line[i] i+=1 i+=2 # ":"左边是标签,右边是实体 word=temp.split(':') sen = word[1] # 给词组打开始,中间和结束 output_data.write(sen[0]+"/B_"+word[0]+" ") for j in sen[1:len(sen)-1]: output_data.write(j+"/M_"+word[0]+" ") output_data.write(sen[-1]+"/E_"+word[0]+" ") else: output_data.write(line[i]+"/O ") i+=1 output_data.write('\n') input_data.close() output_data.close()
wordtag
浙/B_product_name 江/M_product_name 在/M_product_name 线/M_product_name 杭/M_product_name 州/E_product_name 4/B_time 月/M_time 2/M_time 5/M_time 日/E_time 讯/O (/O 记/O 者/O /B_person_name 施/M_person_name 宇/M_person_name 翔/E_person_name /O 通/O 讯/O 员/O /O 方/B_person_name 英/E_person_name )/O 毒/O 贩/O 很/O “/O 时/O 髦/O ”/O ,/O 用/O 微/B_product_name 信/E_product_name 交/O 易/O 毒/O 品/O 。/O 没/O 料/O 想/O 警/O 方/O 也/O 很/O “/O 潮/O ”/O ,/O 将/O 计/O 就/O 计/O ,/O 一/O 举/O 将/O 其/O 擒/O 获/O 。/O 记/O 者/O 从/O 杭/B_org_name 州/M_org_name 江/M_org_name 干/M_org_name 区/M_org_name 公/M_org_name 安/M_org_name 分/M_org_name 局/E_org_name 了/O 解/O 到/O ,/O 经/O 过/O 一/O 个/O 多/O 月/O 的/O 侦/O 查/O 工/O 作/O ,/O 江/B_org_name 干/M_org_name 区/M_org_name 禁/M_org_name 毒/M_org_name 专/M_org_name 案/M_org_name 组/E_org_name 抓/O 获/O 吸/O 贩/O 毒/O 人/O 员/O 5/O 名/O ,/O 缴/O 获/O “/O 冰/O 毒/O ”/O 4/O 0/O 0/O 余/O 克/O ,/O 毒/O 资/O 3/O 0/O 0/O 0/O 0/O 余/O 元/O ,/O 扣/O 押/O 汽/O 车/O 一/O 辆/O 。/O 黑/B_location 龙/M_location 江/E_location 籍/O 男/O 子/O 钱/B_person_name 某/E_person_name 长/O 期/O 落/O 脚/O 于/O 宾/O 馆/O 、/O 单/O 身/O 公/O 寓/O ,/O 经/O 常/O 变/O 换/O 住/O 址/O 。/O 他/O 有/O 一/O 辆/O 车/O ,/O 经/O 常/O 半/O 夜/O 驾/O 车/O 来/O 往/O 于/O 杭/B_location 州/M_location 主/M_location 城/M_location 区/E_location 的/O 各/O 大/O 宾/O 馆/O 和/O 单/O 身/O 公/O 寓/O ,/O 并/O 且/O 常/O 要/O 活/O 动/O 到/O 凌/B_time 晨/M_time 6/M_time 、/M_time 7/M_time 点/M_time 钟/E_time ,/O 白/B_time 天/E_time 则/O 在/O 家/O 里/O 呼/O 呼/O 大/O 睡/O 。/O 钱/B_person_name 某/E_person_name 不/O 寻/O 常/O 的/O 特/O 征/O ,/O 引/O 起/O 了/O 警/O 方/O 注/O 意/O 。/O 禁/O 毒/O 大/O 队/O 通/O 过/O 侦/O 查/O ,/O 发/O 现/O 钱/B_person_name 某/E_person_name 实/O 际/O 上/O 是/O 在/O 向/O 落/O 脚/O 于/O 宾/O 馆/O 和/O 单/O 身/O 公/O 寓/O 的/O 吸/O 毒/O 人/O 员/O 贩/O 送/O “/O 冰/O 毒/O ”/O 。/O
第二层处理:# 按照标点符号进行分段
# 文本处理阶段二 # 逻辑分段(标点符号) def tagsplit(): with open('./wordtag.txt','rb') as inp: texts = inp.read().decode('utf-8') sentences = re.split('[,。!?、‘’“”()]/[O]', texts) output_data = codecs.open('./wordtagsplit.txt','w','utf-8') for sentence in sentences: if sentence != " ": output_data.write(sentence.strip()+'\n') output_data.close()
浙/B_product_name 江/M_product_name 在/M_product_name 线/M_product_name 杭/M_product_name 州/E_product_name 4/B_time 月/M_time 2/M_time 5/M_time 日/E_time 讯/O
第三层处理:制作对应数据,单个字对应的id,tag对应的id,tag对应的字,训练用的数据集
def data2pkl(): datas = list() labels = list() linedata=list() linelabel=list() tags = set() # 将标注的文本进行数据处理(获取实体文本以及标签,分别存入两个list datas labels,且一一对应,用集合tag统计存在多少种类型的标签) input_data = codecs.open('./wordtagsplit.txt','r','utf-8') #处理已经打好标签的文本 for line in input_data.readlines(): line = line.split() linedata=[] linelabel=[] numNotO=0 for word in line: word = word.split('/') #数据中每个词对用空格隔开,每个词和对应的标签使用“/”分隔符隔开的 linedata.append(word[0]) linelabel.append(word[1]) tags.add(word[1]) if word[1]!='O': #o不是我们所需要识别的内容的标签 numNotO+=1 if numNotO!=0: datas.append(linedata) labels.append(linelabel) input_data.close() print(len(datas),tags) print(len(labels)) # 实体打id # from compiler.ast import flatten all_words = flatten(datas) sr_allwords = pd.Series(all_words) #给words打对应的id sr_allwords = sr_allwords.value_counts() #统计各word出现的次数,且按照由高到低排列 set_words = sr_allwords.index set_ids = list(range(1, len(set_words)+1)) # 标签打id tags = [i for i in tags] tag_ids = list(range(len(tags))) # 四种二元关系 word2id = pd.Series(set_ids, index=set_words) id2word = pd.Series(set_words, index=set_ids) tag2id = pd.Series(tag_ids, index=tags) id2tag = pd.Series(tags, index=tag_ids) # 给找不到的实体单独打一个id word2id["unknow"] = len(word2id)+1 print(word2id) # 每60个词打包在一起,不足的就截断到60个词 max_len = 60 def X_padding(words): ids = list(word2id[words]) if len(ids) >= max_len: return ids[:max_len] ids.extend([0]*(max_len-len(ids))) return ids def y_padding(tags): ids = list(tag2id[tags]) if len(ids) >= max_len: return ids[:max_len] ids.extend([0]*(max_len-len(ids))) return ids #建立word和tag的一一对应的二维表,行名是顺序数字 df_data = pd.DataFrame({'words': datas, 'tags': labels}, index=list(range(len(datas)))) # 填充数据 df_data['x'] = df_data['words'].apply(X_padding) df_data['y'] = df_data['tags'].apply(y_padding) # 数据结构化 x = np.asarray(list(df_data['x'].values)) y = np.asarray(list(df_data['y'].values)) # 划分数据集(训练:验证:测试 =0.66 : 0.16 : 0.2) from sklearn.model_selection import train_test_split x_train,x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=43) x_train, x_valid, y_train, y_valid = train_test_split(x_train, y_train, test_size=0.2, random_state=43) # 将结构化后的数据送入pkl中存储 import pickle import os with open('../Bosondata.pkl', 'wb') as outp: pickle.dump(word2id, outp) pickle.dump(id2word, outp) pickle.dump(tag2id, outp) pickle.dump(id2tag, outp) pickle.dump(x_train, outp) pickle.dump(y_train, outp) pickle.dump(x_test, outp) pickle.dump(y_test, outp) pickle.dump(x_valid, outp) pickle.dump(y_valid, outp) print('** Finished saving the data.')
import collections # 数据拉成一维的 def flatten(x): result = [] for el in x: if isinstance(x, collections.Iterable) and not isinstance(el, str): result.extend(flatten(el)) else: result.append(el) return result
具体解析
数据在BISTM-CRF层中的整个流程:
实现:
加载处理过的数据
with open('../data/Bosondata.pkl', 'rb') as inp: word2id = pickle.load(inp) id2word = pickle.load(inp) tag2id = pickle.load(inp) id2tag = pickle.load(inp) x_train = pickle.load(inp) y_train = pickle.load(inp) x_test = pickle.load(inp) y_test = pickle.load(inp) x_valid = pickle.load(inp) y_valid = pickle.load(inp)
设置初始值
START_TAG = "<START>" STOP_TAG = "<STOP>" EMBEDDING_DIM = 100 HIDDEN_DIM = 200 EPOCHS = 5 tag2id[START_TAG]=len(tag2id) tag2id[STOP_TAG]=len(tag2id)
模型
model = BiLSTM_CRF(len(word2id)+1, tag2id, EMBEDDING_DIM, HIDDEN_DIM)
优化器
optimizer = optim.SGD(model.parameters(), lr=0.005, weight_decay=1e-4)
训练
for sentence, tags in zip(x_train,y_train): index+=1 model.zero_grad() # 数据处理的时候已将word转换成id进行存储 # sentence = torch.tensor([word2id[w] for w in sentence], dtype=torch.long) # 数据处理的时候已将tag转换成id进行存储 # tags = torch.tensor([tag2id[t] for t in tags], dtype=torch.long) sentence = torch.tensor(sentence) tags = torch.tensor(tags) loss = model.neg_log_likelihood(sentence, tags) loss.backward() optimizer.step() if index%30==0: print("epoch", epoch, "index", index)
测试
entityres=[] entityall=[] for sentence, tags in zip(x_test,y_test): sentence=torch.tensor(sentence, dtype=torch.long) score,predict = model(sentence) sentence = sentence.numpy().tolist() tags = tags.tolist() entityres = calculate(sentence,predict,id2word,id2tag,entityres) entityall = calculate(sentence,tags,id2word,id2tag,entityall) jiaoji = [i for i in entityres if i in entityall] if len(jiaoji)!=0: zhun = float(len(jiaoji))/len(entityres) zhao = float(len(jiaoji))/len(entityall) print("test:") print("zhun:", zhun) print("zhao:", zhao) print("f:", (2 * zhun * zhao) / (zhun + zhao)) else: print("zhun:",0)
保存模型
path_name = "./model"+str(epoch)+".pkl" print(path_name) torch.save(model, path_name) print("model has been saved")
epoch=1
train len: 1216
test len: 380
epoch=1
train len: 1600
test len: 500
注意:生成 tag2id 时,应该将空设置成 id = 0,否则在进行padding时用 0填充会打上其他标签!!!!!影响整个模型的训练
数据集一定要仔细仔细再仔细!!!否则可能有test数据集无法验证,训练出的模型根本没有识别能力等等等问题!!!!,tag如果不多建议自定义,在msra数据集中,自动生成tag2id和id2tag存在bug
直接用现成的
train len: 1440
test len: 200
valid len 360
epoch = 1
train len: 1440
test len: 200
valid len 360
epoch = 1
天池瑞金医院MMC人工智能辅助构建知识图谱大赛
代码数据集采用txt+ann类型,与brat相同,参考该代码进行分析