賽題分析
資料樣例
首先,對比賽提供的資料進行分析,資料的內容和形式如下:
文 號:(2016)豫1402刑初53號 段落內容:商丘市梁園區人民檢察院指控:1、2015年7月17日、18日,被告人劉磊、杜嚴二人分別在山東省單縣中心醫院和商丘市工行新苑盜竊現代瑞納轎車兩輛,共價值人民幣107594元。其中將一輛轎車低價賣給被告人苗某某,被告人苗某某明知是贓車而予以收購。公訴機關向法庭提供了被告,是被告人供述、被害人陳述、證人證言、鑑定意見、有關書證等證據,認為被告劉磊、杜嚴的行為觸犯了《中華人民共和國刑法》第二百六十四條之規定,構成盜竊罪。系共同犯罪。被害人苗某某的行為觸犯了《中華人民共和國刑法》第二百一十二條第一款之規定,構成掩飾、隱瞞犯罪所得罪。請求依法判處。 被告人集合:[“劉磊”,“杜嚴”,“苗某某”] 句 子:1、2015年7月17日、18日,被告人劉磊、杜嚴二人分別在山東省單縣中心醫院和商丘市工行新苑盜竊現代瑞納轎車兩輛,共價值人民幣107594元 要素原始值:盜竊現代瑞納轎車 要素名稱:盜竊、搶劫、詐騙、搶奪的機動車 被告人:[“劉磊”]
Baseline(official)
任務定義:序列分類
模型描述
class ErnieForElementClassification(ErnieModel): def __init__(self, cfg, name=None): super(ErnieForElementClassification, self).__init__(cfg, name=name) initializer = F.initializer.TruncatedNormal(scale=cfg['initializer_range']) self.classifier = _build_linear(cfg['hidden_size'], cfg['num_labels'], append_name(name, 'cls'), initializer) prob = cfg.get('classifier_dropout_prob', cfg['hidden_dropout_prob']) self.dropout = lambda i: L.dropout(i, dropout_prob=prob, dropout_implementation="upscale_in_train",) if self.training else i @add_docstring(ErnieModel.forward.__doc__) def forward(self, *args, **kwargs): labels = kwargs.pop('labels', None) pooled, encoded = super(ErnieForElementClassification, self).forward(*args, **kwargs) hidden = self.dropout(pooled) logits = self.classifier(hidden) logits = L.sigmoid(logits) sqz_logits = L.squeeze(logits, axes=[1]) if labels is not None: if len(labels.shape) == 1: labels = L.reshape(labels, [-1, 1]) part1 = L.elementwise_mul(labels, L.log(logits)) part2 = L.elementwise_mul(1-labels, L.log(1-logits)) loss = - L.elementwise_add(part1, part2) loss = L.reduce_mean(loss) return loss, sqz_logits else: return sqz_logits
資料去噪
Original:官方提供的原始資料集train.txt。 Preprocessed:將Original資料重新整理,將“被告人集合”拆分成單獨的“被告人”。 Denoised:去除Preprocessed中,同時滿足問題(1)和(2)的樣本。 Denoised_without_no_person:去除Denoised中,存在問題(1)的樣本。
def pad_data(file_name, tokenizer, max_len): """ This function is used as the Dataset Class in PyTorch """ # configuration: file_content = json.load(open(file_name, encoding='utf-8')) data = [] for line in file_content: paragraph = line['paragraph'] person = line['person'] element = line['element_name'] sentence = line['sentence'] ovalue = line["ovalue"] label = line['label'] sentence_a = add_dollar2person(person) + element sentence_b = add_star2sentence(sentence, ovalue) src_id, sent_id = tokenizer.encode(sentence_a, sentence_b, truncate_to=max_len-3) # 3 special tokens # pad src_id and sent_id (with 0 and 1 respectively) src_id = np.pad(src_id, [0, max_len-len(src_id)], 'constant', constant_values=0) sent_id = np.pad(sent_id, [0, max_len-len(sent_id)], 'constant', constant_values=1) data.append((src_id, sent_id, label)) return data def make_batches(data, batch_size, shuffle=True): """ This function is used as the DataLoader Class in PyTorch """ if shuffle: np.random.shuffle(data) loader = [] for j in range(len(data)//batch_size): one_batch_data = data[j * batch_size:(j + 1) * batch_size] src_id, sent_id, label = zip(*one_batch_data) src_id = np.stack(src_id) sent_id = np.stack(sent_id) label = np.stack(label).astype(np.float32) # change the data type to compute BCELoss conveniently loader.append((src_id, sent_id, label)) return loader
def train(model, dataset, lr=1e-5, batch_size=1, epochs=10): max_steps = epochs * (len(dataset) // batch_size) # max_train_steps = args.epoch * num_train_examples // args.batch_size // dev_count optimizer = AdamW( # learning_rate=LinearDecay(lr, int(0), max_steps), learning_rate=lr, parameter_list=model.parameters(), weight_decay=0) model.train() logging.info('start training process!') for epoch in range(epochs): # shuffle the dataset every epoch by reloading it data_loader = make_batches(dataset, batch_size=batch_size, shuffle=True) running_loss = 0.0 for i, data in enumerate(data_loader): # prepare inputs for the model src_ids, sent_ids, labels = data # convert numpy variables to paddle variables src_ids = D.to_variable(src_ids) sent_ids = D.to_variable(sent_ids) labels = D.to_variable(labels) # feed into the model outs = model(src_ids, sent_ids, labels=labels) loss = outs[0] loss.backward() optimizer.minimize(loss) model.clear_gradients() running_loss += loss.numpy()[0] if i % 10 == 9: print('epoch: ', epoch + 1, '\tstep: ', i + 1, '\trunning_loss: ', running_loss) running_loss = 0.0 state_dict = model.state_dict() F.save_dygraph(state_dict, './saved/plan3_all/model_'+str(epoch+1)+'epoch') print('model_'+str(epoch+1)+'epoch saved') logging.info('all model parameters saved!')
效果對比
最終與baseline相比,我們的方案在F1、Precision和Recall三項指標上都有明顯的提升。在所有25支參賽隊伍中排名第一,其中F1和Precision值均為所有參賽隊伍最好成績。
方案總結
本方案將比賽任務重新定義為序列分類任務,這一任務形式將判斷要素名稱與被告人之間關係所需的關鍵資訊直接作為模型的輸入,並且在關鍵資訊處新增了特殊符號,有效增強了關鍵資訊,降低了模型判斷的難度。在訓練資料方面,本方案剔除了部分噪聲資料。
實驗結果也表明這一操作能夠提升模型的預測表現。在測試階段,本方案對於句子中沒有被告人的情況採取了向前擴一句的方式。這一方式能夠解決部分問題,但對於前一句仍不包含被告人的情況效果較差。並且在擴句後,輸入序列的長度增加,而輸入序列的最大長度不能超過512。因此,本方案仍需解決以下兩種情況:
(1) 向前擴句後,句子中仍不包含被告人的情況;
(2) 輸入序列較長的情況(分詞之後達到1000個token以上)
方案改進