芒果TV商品意圖識別top3思路分享

比賽簡介

主辦方提供了商品名稱和用戶query數據供選手進行模型訓練,希望選手能夠設計出一套高效、精準的商品意圖識別模型,以幫助提升電商搜索的效果,改善顧客的購買體驗。

其中提供了兩份數據,一個是goods_data.csv是商品名稱數據,一個是query_data.csv是用戶query數據,共39470條

前期我們做的嘗試比較多,後面差不多爛尾了,慶幸b榜還在第一頁,下面介紹下我們隊伍的比賽思路。

數據處理

由於本賽題數據分類一個質量比較高的goods數據,一個是用戶場景下的query數據(相對有噪音),前期我們嘗試單獨訓練goods或者query數據效果不是很好,goods數據容易過擬合,query數據比較難收斂,後續實驗我們選擇將兩份數據進行合併訓練,效果得到明顯提升。

文本長度統計如下:商品名稱數據中 文本字符長度最大爲39,最小爲6。我們在訓練中選擇了覆蓋絕大部分數據長度的大小26,其餘沒有做過多嘗試。


數據劃分

由於本賽題的樣本的標籤分佈不均衡,我們採用多折分層採樣的方式進行劃分訓練集,然後輸入到模型進行訓練,直接採用sklearn的StratifiedKFold

from sklearn.model_selection import StratifiedKFold

模型設計

由於Bert等預訓練模型變體效果要好於傳統NLP建模方法,我們一開始實驗是從預訓練模型開始建模的,對比了幾個模型變體之間的效果,其中本次比賽給出的baseline ernie模型效果比較好,嘗試了開放的ernie3.0效果不如1.0;其次nezha 效果和chinese-roberta-wwm也不錯。

  • ernie-1.0
  • nezha
  • chinese-roberta-wwm

訓練優化

我們嘗試了一些NLP訓練優化方法,

  • 對抗訓練:嘗試了FGM/PGD,其中PGD沒有效果,FGM在chinese-roberta-wwm模型有提升效果
class FGM():
    def __init__(self, model):
        self.model = model
        self.backup = {}
    def attack(self, epsilon=1., emb_name='word_embeddings'):
        # emb_name這個參數要換成你模型中embedding的參數名
        for name, param in self.model.named_parameters():
            if param.requires_grad and emb_name in name:
                self.backup[name] = param.data.clone()
                norm = torch.norm(param.grad)
                if norm != 0 and not torch.isnan(norm):
                    r_at = epsilon * param.grad / norm
                    param.data.add_(r_at)
    def restore(self, emb_name='emb.'):
        # emb_name這個參數要換成你模型中embedding的參數名
        for name, param in self.model.named_parameters():
            if param.requires_grad and emb_name in name: 
                assert name in self.backup
                param.data = self.backup[name]
        self.backup = {}

  • 模型泛化:加入了MultiDropout、Rdrop,其中Rdrop在nezha模型提升比較明顯
import torch.nn.functional as F

# define your task model, which outputs the classifier logits
model = TaskModel()

def compute_kl_loss(self, p, q, pad_mask=None):
    
    p_loss = F.kl_div(F.log_softmax(p, dim=-1), F.softmax(q, dim=-1), reduction='none')
    q_loss = F.kl_div(F.log_softmax(q, dim=-1), F.softmax(p, dim=-1), reduction='none')
    
    # pad_mask is for seq-level tasks
    if pad_mask is not None:
        p_loss.masked_fill_(pad_mask, 0.)
        q_loss.masked_fill_(pad_mask, 0.)


    p_loss = p_loss.sum()
    q_loss = q_loss.sum()

    loss = (p_loss + q_loss) / 2
    return loss

# keep dropout and forward twice
logits = model(x)

logits2 = model(x)

# cross entropy loss for classifier
ce_loss = 0.5 * (cross_entropy_loss(logits, label) + cross_entropy_loss(logits2, label))

kl_loss = compute_kl_loss(logits, logits2)

# carefully choose hyper-parameters
loss = ce_loss + α * kl_loss
  • ema在nezha模型有提升效果
class EMA():
    def __init__(self, model, decay):
        self.model = model
        self.decay = decay
        self.shadow = {}
        self.backup = {}
 
    def register(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                self.shadow[name] = param.data.clone()
 
    def update(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                assert name in self.shadow
                new_average = (1.0 - self.decay) * param.data + self.decay * self.shadow[name]
                self.shadow[name] = new_average.clone()
 
    def apply_shadow(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                assert name in self.shadow
                self.backup[name] = param.data
                param.data = self.shadow[name]
 
    def restore(self):
        for name, param in self.model.named_parameters():
            if param.requires_grad:
                assert name in self.backup
                param.data = self.backup[name]
        self.backup = {}
 
# 初始化
ema = EMA(model, 0.999)
ema.register()
 
# 訓練過程中,更新完參數後,同步update shadow weights
def train():
    optimizer.step()
    ema.update()
 
# eval前,apply shadow weights;eval之後,恢復原來模型的參數
def evaluate():
    ema.apply_shadow()
    # evaluate
    ema.restore()

模型融合

爲了避免模型抖動,我們主要依賴線下cv分數以及a榜分數,對模型設置權重進行加權融合,具體融合方式如下:


其中preden1可以是模型融合的結果,然後基於它的分數再去分配其他兩個單模的分數

比賽結論

本次比賽數據因爲長度比較短以及粒度爲實體名詞級別的,ernie效果比較好,確實是意外之喜。由於時間問題有些想法還是沒有去做嘗試,主要有:

  • 數據增強
  • AWP對抗訓練
  • 投票融合等

希望其他前排大佬可以多多交流

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