CoType源码测试
论文:《CoType: Joint Extraction of Typed Entities and Relations with Knowledge Bases》
原本是想跑一遍cotype,但是准备好数据集以后发现项目里面的cotype是用c写的,目前的能力无法debug和后续优化模型,网上也没有找到python版的项目代码。。。无可奈何放弃了。
发在这里是希望有大神看到,能提供一下cotype的python版本实现。
1. 资源下载
1.1. 项目代码下载
git clone https://github.com/INK-USC/shifted-label-distribution.git
1.2. 数据下载
1.2.1. KBP(科学获取)
googledrive下载地址
1.2.2. NYT(科学获取)
googledrive下载地址
1.2.3. TACRED(收费)
需要注册帐号下载,找同学要的数据
2. 环境准备
Python = 3.6.12
Pytorch = 1.7.0
jdk = 1.8.0_92
2.1. Pytorch安装
pip install http://download.pytorch.org/whl/cpu/torch-0.3.1-cp36-cp36m-linux_x86_64.whl
pip install torchvision --no-deps
# 按装torchvision时忽略依赖,不然会安装最新的torch
Pytorch其他版本参考地址
注意:
from torch.nn.utils.rnn import pad_packed_sequence, pack_padded_sequence, pack_sequence, PackedSequence
中,使用0.3.1版本的torch会报错ImportError: cannot import name 'pack_sequence
猜测本项目的实际pytorch版本为1.7.0
3. 项目模型
项目里面包含了很多模型代码,本文只针对CoType模型进行数据准备。
3.1. 基于特征
- ReHession
- CoType
- Logistic Regression
3.2. 基于神经网络
- Bi-GRU
- Bi-LSTM
- PCNN
- CNN
- PositionAware LSTM
- Bi-GRU+ATT
- PCNN+ATT
4. 数据预处理
4.1. TACRED
4.1.1. 生成json数据文件
python DataProcessor/gen_tacred.py
得到train_split.json
,test.json
,dev.json
三个文件。
4.1.2. 运行数据聚类脚本
sh ./brown_clustering.sh TACRED
本数据集处理时,./brown_clustering.sh
文件中的python2 DataProcessor/dev_set_partition.py --dataset $Data --ratio 0.1
可以注释掉不运行,这一步是将训练数据按照9:1分为训练集和验证集,在上一步骤中已经产生了的dev.json文件。后面是将json数据生成brown文件,将原始txt文件聚类到300个类。
虚拟机耗时:15m52.688s
4.1.3. 安装配置corenlp_server
VERSION=4.2.0
- 安装stanford coreNLP
下载stanford-corenlp-4.2.0-models-english.jar
解压stanford-corenlp-latest.zip文件,并将jar文件放入解压后的文件夹(如:/home/appendDisk/Downloads/stanford-corenlp-4.2.0)中。
- 配置环境变量
sudo vim /etc/profile
# 添加下面内容,并保存退出
export CORENLP_HOME=/home/appendDisk/Downloads/stanford-corenlp-4.2.0
#应用新环境变量
source /etc/profile
4.1.4. 统一python脚本到python3
项目代码中混合了py2的代码,花了点儿时间转到py3。
DataProcessor/feature_generation.py
将文件中python2的print XXX
语法都改成print(XXX)
。
DataProcessor/ner_feature.py
将文件中python2的print XXX
语法都改成print(XXX)
;
将文件中的下面两句注释掉。
reload(sys)
sys.setdefaultencoding('utf8')
将文件中python2的print(e.message, e.arg)
修改为:print(e.arg)
;
将文件中sorted(,cpm=)
换成如下形式:(共3个)
# sorted_map = sorted(mentionCountByNumOfLabels.items(),cmp=lambda x,y:x[0]-y[0])
from functools import cmp_to_key # 在开头引用
sorted_map = sorted(mentionCountByNumOfLabels.items(), key=cmp_to_key(lambda x, y: x[0] - y[0]))
DataProcessor/pruning_heuristics.py
将文件中python2的print XXX
语法都改成print(XXX)
;
将文件中的下面两句注释掉。
reload(sys)
sys.setdefaultencoding('utf8')
DataProcessor/statistic.py
将文件中的下面两句注释掉。
reload(sys)
sys.setdefaultencoding('utf8')
-
项目代码中的
xrange
全部替换为range
-
DataProcessor/mention_reader.py
替换_decode
函数中的下面语句:
@staticmethod
def _decode(mention_json):
...
# if mention_json == '':
if mention_json == '' or mention_json == b'':
return None
...
4.1.5. DataProcessor/Feature/other_features.py
修改文件代码注释
class EMTypeFeature(AbstractFeature):
def apply(self, sentence, mention, features):
for em in sentence.entityMentions:
if em.start == mention.em1Start and em.end == mention.em1End:
# features.append('EM1_TYPE_%s' % sentence.ner[em.start]) # comment this line
features.append('EM1_TYPE_%s' % em.labels) # uncomment this line
if em.start == mention.em2Start and em.end == mention.em2End:
# features.append('EM2_TYPE_%s' % sentence.ner[em.start]) # comment this line
features.append('EM2_TYPE_%s' % em.labels) # uncomment this line
4.1.6. DataProcessor/nlp_parse.py
from stanza.nlp.corenlp import CoreNLPClient
修改为:from stanza.server import CoreNLPClient
;
将文件中python2的print('parse error: ', e.message, e.arg)
修改为:print('parse error: ', e.args)
;
将NLPParser的初始化部分改为如下:
class NLPParser(object):
"""
NLP parse, including Part-Of-Speech tagging.
Attributes
==========
parser: StanfordCoreNLP
the Staford Core NLP parser
"""
def __init__(self, portnum):
self.parser = CoreNLPClient(default_annotators=['ssplit', 'tokenize', 'pos', 'ner'], memory='4G', endpoint="http://localhost:{}".format(portnum))
- 添加
CoreNLPClient
参数memory
;将参数server
改为endpoint
- 添加
NLPParser
初始化参数portnum
(可以支持多线程开启多个server进行语料标注)
同步修改本文件中parse
函数代码,添加参数portnum
:
def parse(sentences, g, lock, procNum, isTrain, nOfNones, portNum, parsePOSBeforehand=False):
rmCount = 0
discardRmCount = 0
parser = NLPParser(portNum)
4.1.7. DataProcessor/feature_generation.py
修改multi_process_parse
函数部分,添加参数portnum
:
# def multi_process_parse(fin, fout, isTrain, nOfNones):
def multi_process_parse(fin, fout, isTrain, nOfNones, portNum):
file = open(fin, 'r')
sentences = file.readlines()
sentsPerProc = int(math.floor(len(sentences)*1.0/numOfProcesses))
lock = Lock()
processes = []
# out_file = open(fout, 'w', 0)
out_file = open(fout, 'w')
for i in range(numOfProcesses):
if i == numOfProcesses - 1:
# p = Process(target=parse, args=(sentences[i*sentsPerProc:], out_file, lock, i, isTrain, nOfNones))
p = Process(target=parse, args=(sentences[i*sentsPerProc:], out_file, lock, i, isTrain, nOfNones, portNum+i))
else:
# p = Process(target=parse, args=(sentences[i*sentsPerProc:(i+1)*sentsPerProc], out_file, lock, i, isTrain, nOfNones))
p = Process(target=parse, args=(sentences[i*sentsPerProc:(i+1)*sentsPerProc], out_file, lock, i, isTrain, nOfNones, portNum+i))
p.start()
processes.append(p)
for proc in processes:
proc.join()
out_file.close()
修改if __name__ == "__main__"
中的multi_process_parse
传参部分,添加参数portnum
:
print('Start nlp parsing')
# multi_process_parse(raw_train_json, train_json, True, numOfNones)
multi_process_parse(raw_train_json, train_json, True, numOfNones, 9001)
print('Train set parsing done')
# multi_process_parse(raw_dev_json, dev_json, False, 1)
multi_process_parse(raw_dev_json, dev_json, False, 1, 9010)
print('Dev set parsing done')
# multi_process_parse(raw_test_json, test_json, False, 1)
multi_process_parse(raw_test_json, test_json, False, 1, 9020)
print('Test set parsing done')
这里的端口号可以自选,确认不会被占用即可
4.1.8. feature_extraction.sh
#python2 -m pip install tqdm stanza ujson unidecode requests nltk --user
python -m pip install tqdm stanza ujson unidecode requests nltk --no-deps -i https://pypi.doubanio.com/simple
#python2 DataProcessor/feature_generation.py $Data 5 0 1.0 1
python DataProcessor/feature_generation.py $Data 3 0 1.0 1
--no-deps
表示不安装依赖- 修改
DataProcessor/feature_generation.py
的第二个入参numOfProcesses
为3,目前自用的虚拟机状态最多可以支持正常启动3个server,多了java会报OOM,无法分配内存等性能错误,故改为3。
4.1.9. Feature
module的引用
运行时可能会发生引用不到Feature
里面的东西,只需要在DataProcessor/Feature/__init__.py
里面添加:
import sys
import os
sys.path.append(os.path.join(os.getcwd(), "DataProcessor/Feature"))
4.1.10. 运行抽取文件
sh ./feature_extraction.sh TACRED
团队服务器开5个server耗时约1.5h
运行时server也可能会突然死掉,应该和机器性能有关。如果不是所有server都死了,那就请耐心等待。
4.1.11. 生成数据文件
python DataProcessor/gen_bag_level_data.py --in_dir ./data/neural/TACRED --out_dir ./data/neural_att/TACRED
5. 训练cotype模型
5.1. 准备CPP运行环境
5.1.1. 安装GSL-2.5
cd /home/dannie/Downloads
tar -xzvf gsl-2.5.tar.gz
su - root
cd /home/dannie/Downloads/gsl-2.5
./configure && make && make install
编译需要几分钟,成功后,默认安装在了/usr/local下。
添加到环境变量:
cd /usr/local/bin
ln /usr/local/lib/libgsl.so /usr/local/lib/libgsl.so.0
vim /etc/profile
# 最下面加入:
export LD_LIBRARY_PATH=/usr/local/lib: $LD_LIBRARY_PATH
# 退出
source /etc/profile
sudo ldconfig
5.1.2. 安装eigen库
sudo apt-get install libeigen3-dev
sudo cp -r /usr/local/include/eigen3/Eigen /usr/local/include
# 查看版本信息
head -20 /usr/include/eigen3/Eigen/src/Core/util/Macros.h
5.2. 训练TACRED
CoType/retype-rm -data TACRED -mode m -size 50 -negative 3 -threads 3 -alpha 0.0001 -samples 1 -iters 1000 -lr 0.01
到这里运行c脚本文件出错,暂时无法排查原因。
只能等以后学C或者靠大神来带带了。
太悲伤了。。。