Kaggle文本可讀性識別大賽銀牌方案覆盤

1 前言


歷史三個月,文本可讀性識別大賽終於落下帷幕,我們隊伍的ID爲wordpeace,隊員分別爲:致Great,firfile,heshien,heng zheng,XiaobaiLan,私榜取得91名成績,排名top2%,整體比賽競爭比較大,鄰近手分數非常接近甚至片段並列,同時私榜出現大面積抖動。

2 比賽簡介


比賽名稱:CommonLit Readability Prize
比賽任務:在本次比賽中,選手構建算法來評估 3-12 年級課堂使用的閱讀文本段落的複雜性,用來評估文本的可讀性,是否通俗易懂。
比賽鏈接https://www.kaggle.com/c/commonlitreadabilityprize/overview
評估指標
\text{RMSE} = \sqrt{\frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2}
比賽數據

id:每條文本的唯一ID
url_legal:數據來源,測試集中爲空
license :數據許可協議,測試集中爲空
excerpt :需要預測的測試集文本
target :可讀性分數,目標值
standard_error :衡量每個摘錄的多個評分者之間的分數分佈。不包括測試數據。

3 方案總結


總體方案爲:

  • 基於比賽任務給定的訓練集和測試集語料進行繼續預訓練:MLM任務
  • 對於預訓練模型輸出拼接其他網絡層進行微調,主要用到的池化層有AttentionHead,MeanPooling以及預訓練模型最後四個隱層輸出的組合。
  • 融合非常簡單,根據公榜分數設置權重進行加權相加

【論文解讀】文本分類上分利器:Bert微調trick大全
B站回放:科大訊飛NLP文本分類賽事上分利器:Bert微調技巧大全 ChallengeHub分享

3.1 ITPT:繼續預訓練


Bert是在通用的語料上進行預訓練的,如果要在特定領域應用文本分類,數據分佈一定是有一些差距的。這時候可以考慮進行深度預訓練。

Within-task pre-training:Bert在訓練語料上進行預訓練

import warnings
import pandas as pd
from transformers import (AutoModelForMaskedLM,
                          AutoTokenizer, LineByLineTextDataset,
                          DataCollatorForLanguageModeling,
                          Trainer, TrainingArguments)

warnings.filterwarnings('ignore')

train_data = pd.read_csv('data/train/train.csv', sep='\t')
test_data = pd.read_csv('data/test/test.csv', sep='\t')
train_data['text'] = train_data['title'] + '.' + train_data['abstract']
test_data['text'] = test_data['title'] + '.' + test_data['abstract']
data = pd.concat([train_data, test_data])
data['text'] = data['text'].apply(lambda x: x.replace('\n', ''))

text = '\n'.join(data.text.tolist())

with open('text.txt', 'w') as f:
    f.write(text)

model_name = 'roberta-base'
model = AutoModelForMaskedLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.save_pretrained('./paper_roberta_base')

train_dataset = LineByLineTextDataset(
    tokenizer=tokenizer,
    file_path="text.txt",  # mention train text file here
    block_size=256)

valid_dataset = LineByLineTextDataset(
    tokenizer=tokenizer,
    file_path="text.txt",  # mention valid text file here
    block_size=256)

data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer, mlm=True, mlm_probability=0.15)

training_args = TrainingArguments(
    output_dir="./paper_roberta_base_chk",  # select model path for checkpoint
    overwrite_output_dir=True,
    num_train_epochs=5,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=16,
    gradient_accumulation_steps=2,
    evaluation_strategy='steps',
    save_total_limit=2,
    eval_steps=200,
    metric_for_best_model='eval_loss',
    greater_is_better=False,
    load_best_model_at_end=True,
    prediction_loss_only=True,
    report_to="none")

trainer = Trainer(
    model=model,
    args=training_args,
    data_collator=data_collator,
    train_dataset=train_dataset,
    eval_dataset=valid_dataset)

trainer.train()
trainer.save_model(f'./paper_roberta_base')

3.2 不同層的特徵

BERT 的每一層都捕獲輸入文本的不同特徵。 文本研究了來自不同層的特徵的有效性, 然後我們微調模型並記錄測試錯誤率的性能。

class AttentionHead(nn.Module):
    def __init__(self, h_size, hidden_dim=512):
        super().__init__()
        self.W = nn.Linear(h_size, hidden_dim)
        self.V = nn.Linear(hidden_dim, 1)
        
    def forward(self, features):
        att = torch.tanh(self.W(features))
        score = self.V(att)
        attention_weights = torch.softmax(score, dim=1)
        context_vector = attention_weights * features
        context_vector = torch.sum(context_vector, dim=1)

        return context_vector
    
class MeanPoolingHead(nn.Module):
    def __init__(self, h_size, hidden_dim=512):
        super().__init__()
        
    def forward(self, last_hidden_state,attention_mask):
        input_mask_expanded = attention_mask.unsqueeze(-1).expand(last_hidden_state.size()).float()
        sum_embeddings = torch.sum(last_hidden_state * input_mask_expanded, 1)
        sum_mask = input_mask_expanded.sum(1)
        sum_mask = torch.clamp(sum_mask, min=1e-9)
        mean_embeddings = sum_embeddings / sum_mask
        return mean_embeddings

    
    
class MaxPoolingHead(nn.Module):
    def __init__(self, h_size, hidden_dim=512):
        super().__init__()
        
    def forward(self, last_hidden_state,attention_mask):
        input_mask_expanded = attention_mask.unsqueeze(-1).expand(last_hidden_state.size()).float()
        last_hidden_state[input_mask_expanded == 0] = -1e9   # large negative value
        max_embeddings, _ = torch.max(last_hidden_state, 1)
        return max_embeddings

3.3 模型層間差分學習率

我們發現爲下層分配較低的學習率對微調Roberta-Large 是有效的,比較合適的設置是 ξ=0.95 和 lr=2.0e-5,其中24代表Large模型encoder層數,如果使用base需要改成12.

def get_parameters(model, model_init_lr, multiplier, classifier_lr):
    parameters = []
    lr = model_init_lr
    for layer in range(24,-1,-1):
        layer_params = {
            'params': [p for n,p in model.named_parameters() if f'encoder.layer.{layer}.' in n],
            'lr': lr
        }
        parameters.append(layer_params)
        lr *= multiplier
    classifier_params = {
        'params': [p for n,p in model.named_parameters() if 'layer_norm' in n or 'linear' in n 
                   or 'pooling' in n],
        'lr': classifier_lr
    }
    parameters.append(classifier_params)
    return parameters
parameters=get_parameters(model,2e-5,0.95, 1e-4)
optimizer=AdamW(parameters)

4 比賽總結

在閱讀前排方案之後,我們比較好的地方是單模5折可以達到0.458的分數,融合一些基礎微調模型就可以達到0.455-0.456的分數,不足之處是微調模型比較單一,只採用了Deberta和Roberta的Large模型,另外在其他預訓練模型比如XLNET或者T5嘗試比較少;另外預訓練的時候使用的語料沒有做數據增強等

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章