<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          百度NLP賽事實(shí)踐解讀!

          共 19615字,需瀏覽 40分鐘

           ·

          2022-11-22 11:10

          開放領(lǐng)域的搜索場(chǎng)景下得到的網(wǎng)頁(yè)數(shù)據(jù)會(huì)非常復(fù)雜,其中往往存在著網(wǎng)頁(yè)文檔質(zhì)量參差不齊、長(zhǎng)短不一,問(wèn)題答案分布零散、長(zhǎng)度較長(zhǎng)等問(wèn)題,給答案抽取答案置信度計(jì)算帶來(lái)了較大挑戰(zhàn)。

          本文基于百度搜索技術(shù)創(chuàng)新挑戰(zhàn)賽中的搜索問(wèn)答賽題,對(duì)搜索問(wèn)答類任務(wù)做詳細(xì)解讀。本文思路如圖:

          (本文實(shí)踐講解框架,其中改進(jìn)思路見文末)

          賽題背景

          本賽題希望調(diào)研真實(shí)網(wǎng)絡(luò)環(huán)境下的文檔級(jí)機(jī)器閱讀理解技術(shù),共分為兩個(gè)子任務(wù),涉及基于復(fù)雜網(wǎng)頁(yè)文檔內(nèi)容的答案抽取答案檢驗(yàn)技術(shù)(詳細(xì)任務(wù)定義可參考賽事官網(wǎng))。

          賽事官網(wǎng):

          https://aistudio.baidu.com/aistudio/competition/detail/660/0/introduction

          難點(diǎn)分析

          如何在文檔長(zhǎng)度不定,答案長(zhǎng)度較長(zhǎng)的數(shù)據(jù)環(huán)境中取得良好且魯棒的答案抽取效果是子任務(wù)1關(guān)注的重點(diǎn)。

          方案介紹

          賽題可以視為基礎(chǔ)的信息抽取任務(wù),也可以直接視為問(wèn)答類型的信息抽取問(wèn)題。我們需要構(gòu)建一個(gè)模型,根據(jù)query從document中找到想要的答案。

          思路一:BERT或ERNIE

          如果我們使用BERT 或者 ERNIE 可以直接參考如下思路,模型的輸出可以為對(duì)應(yīng)的兩個(gè)位置,分別是回答的開始位置和結(jié)束位置。

          這里需要深入一下模型的實(shí)現(xiàn)細(xì)節(jié):

          • query和documnet是一起輸入給模型,一般情況下query在前面。

          • 回答對(duì)應(yīng)的輸出可以通過(guò)模型輸出后的全連接層完成分類,當(dāng)然回歸也可以。


          思路二:QA

          如果采用QA的思路,則需要將比賽數(shù)據(jù)集轉(zhuǎn)換為QA的格式,特別是文本的處理:長(zhǎng)文本需要進(jìn)行截?cái)唷?/span>

          方案代碼

          方案借助QA的思路,使用ERNIE快速完成模型訓(xùn)練與預(yù)測(cè)。同時(shí),文末給出了提分改進(jìn)方案的思路。

          詳情可參考源Notebook:

          https://aistudio.baidu.com/aistudio/projectdetail/5013840(一鍵運(yùn)行提交

          步驟1:解壓數(shù)據(jù)集

          !pip install paddle-ernie > log.log
          # !cp data/data174963/data_task1.tar /home/aistudio/
          !tar -xf /home/aistudio/data_task1.tar

          步驟2:讀取數(shù)據(jù)集

          # 導(dǎo)入常見的庫(kù)
          import numpy as np
          import pandas as pd
          import os, sys, json
          # 讀取訓(xùn)練集、測(cè)試集和驗(yàn)證集
          train_json = pd.read_json('data_task1/train_data/train.json', lines=True)
          test_json = pd.read_json('data_task1/test_data/test.json', lines=True)
          dev_json = pd.read_json('data_task1/dev_data/dev.json', lines=True)
          # 查看數(shù)據(jù)集樣例
          train_json.head(1)
          train_json.iloc[0]
          test_json.iloc[0]

          步驟3:加載ERNIE模型

          這里我們使用paddlenlp==2.0.7,當(dāng)然你也可以選擇更高的版本。更高的版本會(huì)將損失計(jì)算也封裝進(jìn)去,其他的部分區(qū)別不大。

          import paddle
          import paddlenlp
          print('paddle version', paddle.__version__)
          print('paddlenlp version', paddlenlp.__version__)

          from paddlenlp.transformers import ErnieForQuestionAnswering, ErnieTokenizer
          tokenizer = ErnieTokenizer.from_pretrained('ernie-1.0')
          model = ErnieForQuestionAnswering.from_pretrained('ernie-1.0')
          # 對(duì)文檔的文檔進(jìn)行劃分、計(jì)算文檔的長(zhǎng)度
          train_json['doc_sentence'] = train_json['doc_text'].str.split('。')
          train_json['doc_sentence_length'] = train_json['doc_sentence'].apply(lambda doc: [len(sentence) for sentence in doc])
          train_json['doc_sentence_length_max'] = train_json['doc_sentence_length'].apply(max)
          train_json = train_json[train_json['doc_sentence_length_max'] < 10000# 刪除了部分超長(zhǎng)文檔

          # 對(duì)文檔的文檔進(jìn)行劃分、計(jì)算文檔的長(zhǎng)度
          dev_json['doc_sentence'] = dev_json['doc_text'].str.split('。')
          dev_json['doc_sentence_length'] = dev_json['doc_sentence'].apply(lambda doc: [len(sentence) for sentence in doc])
          dev_json['doc_sentence_length_max'] = dev_json['doc_sentence_length'].apply(max)
          dev_json = dev_json[dev_json['doc_sentence_length_max'] < 10000# 刪除了部分超長(zhǎng)文檔

          # 對(duì)文檔的文檔進(jìn)行劃分、計(jì)算文檔的長(zhǎng)度
          test_json['doc_sentence'] = test_json['doc_text'].str.split('。')
          test_json['doc_sentence_length'] = test_json['doc_sentence'].apply(lambda doc: [len(sentence) for sentence in doc])
          test_json['doc_sentence_length_max'] = test_json['doc_sentence_length'].apply(max)
          train_json.iloc[10]
          test_json.iloc[10]

          步驟4:構(gòu)建數(shù)據(jù)集

          接下來(lái)需要構(gòu)建QA任務(wù)的數(shù)據(jù)集,這里的數(shù)據(jù)集需要處理為如下的格式:

          query [SEP] sentence of document
          • 訓(xùn)練集數(shù)據(jù)集處理
          train_encoding = []

          # for idx in range(len(train_json)):
          for idx in range(10000):

              # 讀取原始數(shù)據(jù)的一條樣本
              title = train_json.iloc[idx]['title']
              answer_start_list = train_json.iloc[idx]['answer_start_list']
              answer_list = train_json.iloc[idx]['answer_list']
              doc_text = train_json.iloc[idx]['doc_text']
              query = train_json.iloc[idx]['query']
              doc_sentence = train_json.iloc[idx]['doc_sentence']
              
              #  對(duì)于文章中的每個(gè)句子
              for sentence in set(doc_sentence):

                  # 如果存在答案
                  for answer in answer_list:
                      answer = answer.strip("。")
                      
                      # 如果問(wèn)題 + 答案 太長(zhǎng),跳過(guò)
                      if len(query + sentence) > 512:
                          continue
                      
                      # 對(duì)問(wèn)題 + 答案進(jìn)行編碼
                      encoding = tokenizer.encode(query, sentence, max_seq_len=512, return_length=True
                          return_position_ids=True, pad_to_max_seq_len=True, return_attention_mask=True)
                      
                      # 如果答案在這個(gè)句子中,找到start 和 end的 位置
                      if answer in sentence:            
                          encoding['start_positions'] = len(query) + 2 + sentence.index(answer)
                          encoding['end_positions'] = len(query) + 2 + sentence.index(answer) + len(answer)

                      # 如果不存在,則位置設(shè)置為0
                      else:
                          encoding['start_positions'] = 0
                          encoding['end_positions'] = 0
                      
                      # 存儲(chǔ)正樣本
                      if encoding['start_positions'] != 0:
                          train_encoding.append(encoding)
                      
                      # 對(duì)負(fù)樣本進(jìn)行采樣,因?yàn)樨?fù)樣本太多
                      # 正樣本:query + sentence -> answer 的情況
                      # 負(fù)樣本:query + sentence -> No answer 的情況
                      if encoding['start_positions'] == 0 and np.random.randint(0100) > 99:
                          train_encoding.append(encoding)
              
              if len(train_encoding) % 500 == 0:
                  print(len(train_encoding))
          • 驗(yàn)證集數(shù)據(jù)集處理
          val_encoding = []

          for idx in range(len(dev_json)):
          # for idx in range(200):
              title = dev_json.iloc[idx]['title']
              answer_start_list = dev_json.iloc[idx]['answer_start_list']
              answer_list = dev_json.iloc[idx]['answer_list']
              doc_text = dev_json.iloc[idx]['doc_text']
              query = dev_json.iloc[idx]['query']
              doc_sentence = dev_json.iloc[idx]['doc_sentence']

              for sentence in set(doc_sentence):
                  for answer in answer_list:
                      answer = answer.strip("。")

                      if len(query + sentence) > 512:
                          continue
                      
                      encoding = tokenizer.encode(query, sentence, max_seq_len=512, return_length=True
                          return_position_ids=True, pad_to_max_seq_len=True, return_attention_mask=True)

                      if answer in sentence:            
                          encoding['start_positions'] = len(query) + 2 + sentence.index(answer)
                          encoding['end_positions'] = len(query) + 2 + sentence.index(answer) + len(answer)
                      else:
                          encoding['start_positions'] = 0
                          encoding['end_positions'] = 0
                      
                      if encoding['start_positions'] != 0:
                          val_encoding.append(encoding)

                      if encoding['start_positions'] == 0 and np.random.randint(0100) > 99:
                          val_encoding.append(encoding)
          • 測(cè)試集數(shù)據(jù)集處理
          test_encoding = []
          test_raw_txt = []
          for idx in range(len(test_json)):
              title = test_json.iloc[idx]['title']
              doc_text = test_json.iloc[idx]['doc_text']
              query = test_json.iloc[idx]['query']
              doc_sentence = test_json.iloc[idx]['doc_sentence']

              for sentence in set(doc_sentence):
                  if len(query + sentence) > 512:
                      continue
                  
                  encoding = tokenizer.encode(query, sentence, max_seq_len=512, return_length=True
                      return_position_ids=True, pad_to_max_seq_len=True, return_attention_mask=True)

                  test_encoding.append(encoding)
                  test_raw_txt.append(
                      [idx, query, sentence]
                  )

          步驟5:批量數(shù)據(jù)讀取

          # 手動(dòng)將數(shù)據(jù)集進(jìn)行批量打包
          def data_generator(data_encoding, batch_size = 6):
              for idx in range(len(data_encoding) // batch_size):
                  batch_data = data_encoding[idx * batch_size : (idx+1) * batch_size]
                  batch_encoding = {}
                  for key in batch_data[0].keys():
                      if key == 'seq_len':
                          continue
                      batch_encoding[key] = paddle.to_tensor(np.array([x[key] for x in batch_data]))
                  
                  yield batch_encoding

          步驟6:模型訓(xùn)練與驗(yàn)證

          # 優(yōu)化器
          optimizer = paddle.optimizer.SGD(0.0005, parameters=model.parameters())

          # 損失函數(shù)
          loss_fct = paddle.nn.CrossEntropyLoss()
          best_val_start_acc = 0

          for epoch in range(10):
              # 每次打亂訓(xùn)練集,防止過(guò)擬合
              np.random.shuffle(train_encoding)
              
              # 訓(xùn)練部分
              train_loss = []
              for batch_encoding in data_generator(train_encoding, 10):

                  # ERNIE正向傳播
                  start_logits, end_logits = model(batch_encoding['input_ids'], batch_encoding['token_type_ids'])

                  # 計(jì)算損失
                  start_loss = loss_fct(start_logits, batch_encoding['start_positions'])
                  end_loss = loss_fct(end_logits, batch_encoding['end_positions'])
                  total_loss = (start_loss + end_loss) / 2
                  
                  # 參數(shù)更新
                  total_loss.backward()
                  train_loss.append(total_loss)
                  optimizer.step()
                  optimizer.clear_gradients()
              
              # 驗(yàn)證部分
              val_start_acc = []
              val_end_acc = []
              with paddle.no_grad():
                  for batch_encoding in data_generator(val_encoding, 10):

                      # ERNIE正向傳播
                      start_logits, end_logits = model(batch_encoding['input_ids'], batch_encoding['token_type_ids'])

                      # 計(jì)算識(shí)別精度
                      start_acc = paddle.mean((start_logits.argmax(1) == batch_encoding['start_positions']).astype(float))
                      end_acc = paddle.mean((end_logits.argmax(1) == batch_encoding['end_positions']).astype(float))

                      val_start_acc.append(start_acc)
                      val_end_acc.append(end_acc)

              # 轉(zhuǎn)換數(shù)據(jù)格式為float
              train_loss = paddle.to_tensor(train_loss).mean().item()
              val_start_acc = paddle.to_tensor(val_start_acc).mean().item()
              val_end_acc = paddle.to_tensor(val_end_acc).mean().item()
              
              # 存儲(chǔ)最優(yōu)模型
              if val_start_acc > best_val_start_acc:
                  paddle.save(model.state_dict(), 'model.pkl')
                  best_val_start_acc = val_start_acc
              
              # 每個(gè)epoch打印輸出結(jié)果
              print(f'Epoch {epoch}{train_loss:3f}{val_start_acc:3f}/{val_end_acc:3f}')
          # 關(guān)閉dropout
          model = model.eval()

          步驟7:模型預(yù)測(cè)

          test_start_idx = []
          test_end_idx = []

          # 對(duì)測(cè)試集中query 和 sentence的情況進(jìn)行預(yù)測(cè)
          with paddle.no_grad():
              for batch_encoding in data_generator(test_encoding, 12):
                  start_logits, end_logits = model(batch_encoding['input_ids'], batch_encoding['token_type_ids'])
                  
                  test_start_idx += start_logits.argmax(1).tolist()
                  test_end_idx += end_logits.argmax(1).tolist()
                  
                  if len(test_start_idx) % 500 == 0:
                      print(len(test_start_idx), len(test_encoding))
          test_submit = [''] * len(test_json)

          # 對(duì)預(yù)測(cè)結(jié)果進(jìn)行后處理
          for (idx, query, sentence), st_idx, end_idx in zip(test_raw_txt, test_start_idx, test_end_idx):

              # 如果start 或 end位置識(shí)別失敗,或 start位置 晚于 end位置
              if st_idx == 0 or end_idx == 0 or st_idx >= end_idx:
                  continue
              
              # 如果start位置在query部分
              if st_idx - len(query) - 2 < 0:
                  continue
              
              test_submit[idx] += sentence[st_idx - len(query) - 2: end_idx - len(query) - 2]
          # 生成提交結(jié)果
          with open('subtask1_test_pred.txt''w'as up:
              for x in test_submit:
                  if x == '':
                      up.write('1\tNoAnswer\n')
                  else:
                      up.write('1\t'+x+'\n')

          改進(jìn)方向

          從精度改變大小,可以從以下幾個(gè)角度改進(jìn):訓(xùn)練數(shù)據(jù) > 數(shù)據(jù)處理 > 模型與預(yù)訓(xùn)練 > 模型集成

          • 訓(xùn)練數(shù)據(jù):使用全量的訓(xùn)練數(shù)據(jù)
          • 數(shù)據(jù)處理:對(duì)文檔進(jìn)行切分,現(xiàn)在使用。進(jìn)行切分,后續(xù)也可以嘗試其他。
          • 模型與預(yù)處理:嘗試ERNIE版本,或者進(jìn)行預(yù)訓(xùn)練。
          • 模型集成:
            • 嘗試不同的數(shù)據(jù)劃分得到不同的模型
            • 嘗試不同的文本處理方法得到不同的模型

          當(dāng)然也可以考慮其他數(shù)據(jù),如不同的網(wǎng)頁(yè)擁有答案的概率不同,以及從標(biāo)題可以判斷是否包含答案。

          完整代碼也可以點(diǎn)擊左下角“原文鏈接”進(jìn)行查看。

          整理不易,點(diǎn)三連

          瀏覽 50
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  性拍拍视频 | 91久久精品国自产合 | 久久视频网站 | A级黄色网 | 国产在线视频网 |