BILSTM-CRF项目源码
结构
data
boson数据集分析
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
BILSTM-CRF
具体解析
数据在BISTM-CRF层中的整个流程:
实现:
- 初始化模型
- 前馈
- 损失函数
- 反馈
(具体解析详见链接)
train
加载处理过的数据
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
迁移MSRA数据集
数据处理
- 分割数据集中的data和lable
- 按行将单词提取到列表中
- 按照bme格式,给单字打label
- 按行将单词提取到列表中
- 按逻辑(’[,。!?、‘’“”()]/[O]’)分行
- 自定义tag2id&&id2tag
- 制作数据集(分单词,分单字,数据拉成一维)
- 通过pd.Series()给word打id
- 将word转成id,tag转成id,并进行数据规范(通过填充或删减将其长度固定)
- 划分训练集,测试集,验证集
注意:生成 tag2id 时,应该将空设置成 id = 0,否则在进行padding时用 0填充会打上其他标签!!!!!影响整个模型的训练
数据集一定要仔细仔细再仔细!!!否则可能有test数据集无法验证,训练出的模型根本没有识别能力等等等问题!!!!,tag如果不多建议自定义,在msra数据集中,自动生成tag2id和id2tag存在bug
BILSTM-CRF&&resuleCal
直接用现成的
训练
- 定model,loss,optimizer
- 定
START_TAG
STOP_TAG
EMBEDDING_DIM
HIDDEN_DIM
EPOCHS - 通过训练集进行训练
- 通过测试集验证准确率
- 保存模型
模型能力
train len: 1440
test len: 200
valid len 360
epoch = 1
train len: 1440
test len: 200
valid len 360
epoch = 1
处理brat标注后的数据
天池瑞金医院MMC人工智能辅助构建知识图谱大赛
代码数据集采用txt+ann类型,与brat相同,参考该代码进行分析
- def clean_ann(): ann数据将其lable和对应txt中的位置提取出来存在csv中
- **def word_level_tag2(filename)