此代码是参考我大boss的框架写成的,当然大boss的这个框架的数据处理部分是很经典的,可以用来做分类器,例如用来处理二分类问题,也可以在此基础上做其他任务,就比如罪名预测等。
2018年5月份我大boss参加了中国法研杯---司法人工智能挑战赛(CAIL2018)
,这个比赛是为了促进法律智能相关技术的发展,在最高人民法院信息中心、共青团中央青年发展部的指导下,中国司法大数据研究院、中国中文信息学会、中电科系统团委联合清华大学、北京大学、中国科学院软件研究所共同举办。
在比赛中总共有三个任务需要做,罪名预测是其中的第一个任务。罪名预测
:根据刑事法律文书中的案情描述和事实部分,预测被告人被判的罪名。如果想对这个比赛了解的更具体请参考boss的比赛总结:这里查看
_Load_Each_Data()函数:分别一行行取读取处理训练、开发、测试文件(处理完一个文件再处理下一个文件依次进行)。将文件每一行的句子(事实描述 )和标签(罪名)分别处理成列表然后放在这句话的实例对象inst中,同时实例对象还包括事实描述的词的个数以及这句话中金标罪名的个数。即:
inst(每一句话)包括:
- fact(事实描述)列表
- accusation(金标罪名)列表
- fact_size(事实描述—词的个数)
- accusation_labels_size(金标罪名的个数)
- fact_index(事实描述中每个词的唯一id)
- accusation_labels_index(每个金标罪名的唯一id)
训练集文件中每遍历、处理完一个句子就将其句子放进insts大列表中,然后再将包含着训练集所有句子信息的insts放进列表data_list列表中;然后依次处理开发集、测试集文件(方法同训练集)dataload()函数:然后分别将开发、测试集的insts添加进入data_list列表中。即:
data_list列表包括:
- train的insts
- dev的insts
- test的insts
data_list = [[inst1,inst2,......],[inst1,inst2......],[inst1,inst2,......]]
注:红色代表训练集的insts列表,绿色代表开发集的insts的列表,蓝色代表测试集的insts的列表。
data_load()函数的作用就是为了得到train_data(data_list[0])、dev_data(data_list[1])、test_data(data_list[2])(有的时候可能不包含测试集,开发集数据就作为测试集数据)
下一步就是建立词典了。在CreateAlphabet()类中只传入了训练集来建立词典(我也是突然想到,如果只用训练集来建立词典的话,可能词典中会没有开发集、测试集中的某些词,那开发和测试集建立batch的时候在词典中没有出现的单词要用unk表示,这样会不会影响准确率呢?问了一下大boss,他这样解释的:不知道测试集的情况很多,所以大多数情况都是采用训练集建立词典)。那好,到此就心中有谱了,只将训练集数据传进了建立词典的类中。_build_data()函数就是把训练集数据放进datasets列表中datasets.extend(train_data),[扩展:可自行查看extend()函数的作用,往它里边添加的必须是列表,添加的列表只是把列表中的内容放到了新的列表中,而不是把整个列表放进新的列表中]
遍历datasets列表,取出下标和句子[拓展:用到enumerate(列表)函数:就是同时取出列表中的句子和对应的下标] 再遍历每一句话的每一个词,只要没有在word_state词典中出现的词其value值赋值为1,出现过的词不断的累加value值。
word_state的形式如下:[("被告",3),("某",1),...] ;遍历每一句话的金标罪名,同理统计词频,将键值对写进
label_state词典中,形式如下:[("盗窃",100),("故意伤害",33),...]
- 然后将word_state当作参数传进initialWord2idAndId2Word()函数中:取word_state字典中的key值(词)的value值(统计的个数)和设置的最小词频进行比较,将大于等于最小词频的key值传进函数from_string()函数中。
from_string(word_state)函数作用:给大于等于最小词频的词唯一的id标识,比如OrderedDict[('<unk>', 0),'(<pad>',1),('被告',2),...]
- 将label_state当作参数传进initialWord2idAndId2Word()函数中:函数作用同理,只不过金标罪名可以不和最小词频进行比较,因为毕竟是金标,最小词频就是1,不可能过滤掉金标罪名的,直接将label_state字典中的key值(罪名)传进函数from_string()函数中。
from_string(label_state)函数作用給所有罪名唯一标识,比如:OrderedDict([('故意伤害',0), ('盗窃', 1)]...)
到这里建词典工作已经完成,接下来就是batch部分(这一部分是我下的功夫最大的一部分,因为它和之前的二分类问题的不同在于它涉及到多标签二元分类问题,即一个事实描述对应多个金标罪名,所以在建立batch这一块区别很大,应留意)。
createIterator()函数就是为了创建训练、开发、测试数据的迭代器。其中_convert_word2id()函数:它的作用是分别来处理训练、开发、测试集三个文件,将每个文件中的事实描述中的词和该事实描述所对应的罪名的唯一id都找出来。还是先说处理训练集数据的步骤,其他两个同理。就是遍历训练数据中的每个句子的每个单词,凡是在word_alphabet中能唯一标识的就返回该单词的唯一标识,在单词的词典中找不到的单词就将唯一标识赋值为unkid,每找到一个单词的唯一id就将id添加到inst.fact_index列表中。即每一句话的fact_index列表中存着这句话中所有词的唯一id。同时也遍历该句话对应的金标罪名,在label_alphabet金标罪名词典中意义找对应的唯一id添加进inst.accusation_labels_index列表中。
_Create_Each_Iterator()函数是定义一个batch空列表,每从训练数据中读取一句话就将其放进batch列表中,直到达到所定义的batch_size或者最后一个batch可能不满batch_size但是只要所有的batch中的句子加起来是训练数据的总数,最后的几个句子也是一个batch,这就是程序中判断if len(batch) == batch_size or count_inst == len(insts)的解释。比如这样[16,16,......2]batch_size是16,最后一批不足16个句子,只有两个,那么这两个句子也是一个batch。然后将每一个batch当作参数传进_Create_Each_Batch()函数中:首先找出这一批数据中每句话的词的数量,一直遍历比较找出这一批数据中句子长度最长的句子,将它的词数赋值给max_word_size。
初始化一个词特征的0矩阵(一个batch为例):(长是batch_length,宽是max_word_size)
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
初始化一个标签特征的0矩阵(一个batch为例):(矩阵大小是batch_length句话*每句话中需要划分label_alphabet.vocab_size个区域)这一部分比较难理解,为什么要这样建立矩阵。因为前面提到罪名预测涉及到多标签二元分类问题,而且首先需要在每个罪名上做二分类。就是这个句子涉及到的罪名该对应位置就是1,涉及不到的罪名对应位置就是0,所以,每个句子都应该划分(所有罪名数量)个区域,只是这句话涉及不到的罪名位置是0而已。矩阵形式如下:标记颜色是为了说明不同颜色是不同的句子,由于时间有限,在这里假设这是最后一个batch,只有两句话,红色的是第一个句子,绿色是第二个句子,假设总罪名有五个,就在第一句话再划分五个区域,在下边会讲到每个位置代表特定的罪名。两句话,假设罪名是五个,那这个矩阵的大小就是2*5
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
词的矩阵和标签的矩阵都已经初始化,现在开始分别往格各自矩阵中填入数据:
- 词特征的矩阵:遍历一个batch中的每一句话中的每一个单词,然后再fact_index列表中找到该单词的唯一id,id 写进该句话该单词对应的位置,那些为了补够max_word_size长度,但是没有单词的地方用paddingid填充。
- 标签特征的矩阵:遍历该句话后边对应的罪名列表,涉及到该罪名,就在矩阵中对应该句话的区域中定位,然后看该罪名对应的id是多少,就在该句话对应区域中再找到该罪名id对应的位置上写上1,假如第二句话对应的罪名有两个,id分别是2和4,则表示如下:
0 |
0 |
1 |
0 |
1 |
然后将训练数据建立的所有迭代器放进self.features列表中,再将self.features列表添加进入self.data_iter列表中,然后清空self.features列表,继续处理开发集数据的迭代器添加添加进self.features列表,再将开发集的self.features列表添加到self.data_iter列表中,接着处理测试集数据,同上。即:
self.data_iter = [[iter1,iter2,...],[iter1,iter2,...],[iter1,iter2,...]]
注:红色代表训练集创建的迭代器列表,绿色代表开发集创建的迭代器列表,蓝色代表测试集创建的迭代器列表。
迭代器工作已经完成,一切准备就绪,接下来的工作是定义模型和过模型。有时间会继续更新模型这部分的......