使用BERT進行情感分類預測及代碼實例

0. BERT介紹

google 在2018年放出的大殺器, 作爲當前NLP的最新技術,此模型在NLP的多個上游下游問題上都取得很好的成績,刷新記錄, 具體原理可以自行google, 這樣一個新的技術, 值得我們學習一下, 下面我將給出一個使用BERT進行情感分類預測及代碼實例, 供大家分享學習。

1. BERT配置

要求配置如下, tensorflow版本不能太低。

tensorflow >= 1.11.0   # CPU Version of TensorFlow.
tensorflow-gpu  >= 1.11.0  # GPU version of TensorFlow.

1.1. clone BERT 代碼

首先clone 官方代碼,地址如下

https://github.com/google-research/bert.git

之後我們主要專注於run_classifier.py, 因爲這個py文件是BERT的分類任務接口, 接下來我們主要就是對這個文件進行修改, 然後運行即可。 同理 其他任務則運行其他文件,例如問答訓練是run_squad.py等

1.2. 數據處理

我們一共需要兩種數據

  • 數據是BERT開放的預訓練模型
  • 我們的數據集

1.2.1預訓練模型

這是google花費大量資源訓練出來的預訓練模型, 我的數據集是英文句子, 所以我們使用 BERT-base,uncased
也就是基礎版本+有大小寫版本, 當然如果你用中文,google也提供了相應的數據集

下載地址如下: BERT-Base, Uncased下載地址
其他的可以在倉庫的readme裏找到相應的下載地址
在這裏插入圖片描述
然後隨便放到一個地方,這裏放到了uncased文件夾裏, 路徑不一樣,模型運行參數會有一點點變化

1.2.2數據集

數據集介紹如下:

這是一個面向句子的情感分類問題。訓練集和測試集已給出,使用訓練集進行模型訓練並對測試集中各句子進行情感預測。訓練集包含10026行數據,測試集包含4850行數據。

訓練集

訓練集中,每一行代表一條訓練語句,包括四部分內容,使用’\t’分隔符:

ID1 ID2 polarity text

其中ID1和ID2是爬取句子內容的定位信息,同學們無需使用。text是句子內容,polarity是句子對應情感類型,有positive、negative、neutral三種。

測試集

測試集中,每一行代表一條待預測語句,包括四部分內容,使用’\t’分隔符:

NA line_num unknwn text

以上是我得到的數據集, 但是我們需要從訓練集裏面挑10%作爲開發集

開發集

開發集是由訓練集得到的,我們使用pandas得到開發集, 代碼如下, 開發集和訓練集比例爲9:1, 兩個數據集數據不重合, 我們運行以下代碼,得到訓練集和開發集, 測試集不用重新劃分, 用現成的即可

data_cut_off.py

import os
import pandas as pd
from sklearn.utils import shuffle


if __name__ == '__main__':
    path = "glue/"
    pd_all = pd.read_csv(os.path.join(path, "data.tsv"), sep='\t' )
    pd_all = shuffle(pd_all)


    dev_set = pd_all.iloc[0:int(pd_all.shape[0]/10)]
    train_set = pd_all.iloc[int(pd_all.shape[0]/10): int(pd_all.shape[0])]
    dev_set.to_csv("glue/dev.tsv", index=False, sep='\t')
    train_set.to_csv("glue/train.tsv", index=False, sep='\t')

這樣我們就得到我們的數據集合。

在這裏插入圖片描述

2. 修改代碼

因爲這次是分類問題, 所以我們需要修改run_classify.py

2.1 加入新的處理類

因爲我們是做一個分類的任務, 裏面自帶4個任務的處理類, 其中ColaProcessor是單句分類,和我們的任務較爲相近, 所以我們模仿這個類,寫一個自己的處理類。

class EmloProcessor(DataProcessor):
    """Processor for the Emotion data set ."""

    def get_train_examples(self, data_dir):
        """定義開發集的數據是什麼,data_dir會作爲參數傳進去, 這裏就是加上你的文件名即可 """
        return self._create_examples(
            self._read_tsv(os.path.join(data_dir, "train.tsv"), ), "train")

    def get_dev_examples(self, data_dir):
        """定義開發集的數據是什麼,data_dir會作爲參數傳進去,模型訓練的時候會用到,這裏就是加上你的文件名即可 """
        return self._create_examples(
            self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")

    def get_test_examples(self, data_dir):
        """定義測試集的數據是什麼, 用於預測數據 ,在訓練時沒有用到這個函數, 這裏寫預測的數據集"""
        return self._create_examples(
            self._read_tsv(os.path.join(data_dir, "test.tsv")), "test")

    def get_labels(self):
        """ 這裏是顯示你一共有幾個分類標籤, 在此任務中我有3個標籤,如實寫上  標籤值和 csv裏面存的值相同 """
        return ["neutral", "positive", "negative"]

    def _create_examples(self, lines, set_type):
        """這個函數是用來把數據處理, 把每一個例子分成3個部分,填入到InputExample的3個參數
        text_a 是 第一個句子的文本
        text_b 是 第二個句子的文本 但是由於此任務是單句分類, 所以 這裏傳入爲None
        guid 是一個二元組  第一個表示此數據是什麼數據集類型(train dev test) 第二個表示數據標號
        label 表示句子類別
        """
        examples = []
        for (i, line) in enumerate(lines):

            guid = "%s-%s" % (set_type, i)
            #print(line, i)
            # 獲取text  第三列和第四列分別是 類別  文本 所以分情況添加
            text_a = tokenization.convert_to_unicode(line[3])
            if set_type == "test":
                #測試集的label 是要預測的 所以我們暫時先隨便填一個類別即可 這裏我選擇都是neutral類
                label = "neutral"
            else:
                label = tokenization.convert_to_unicode(line[2])

            # 加入樣本
            examples.append(
                InputExample(guid=guid, text_a=text_a, text_b=None, label=label))
        return examples

有興趣的盆友可以看看其他的3個類。

2.2 處理類註冊

同樣我們需要在主函數裏把我們的類當做參數選項,給他加個選項, 也就是當參數填emlo時,使用的數據處理類是我們自己寫的處理類

def main(_):
  tf.logging.set_verbosity(tf.logging.INFO)

  processors = {
      "cola": ColaProcessor,
      "mnli": MnliProcessor,
      "mrpc": MrpcProcessor,
      "xnli": XnliProcessor,
      "emlo": EmloProcessor
  }

3. 運行代碼

運行代碼需要提供參數, 這裏我建議直接在pycharm編譯器里加參數,或者直接命令行運行參數, 而不用按照官方教材 run xxx.sh

這裏我給出我的編譯參數, 如果你運行不了, 建議 改小max_seq_length, train_batch_size,

python
run_classifier.py
--task_name=emlo
--do_train=true
--do_eval=true
--data_dir=./glue
--vocab_file=./uncased/uncased_L-12_H-768_A-12/vocab.txt
--bert_config_file=./uncased/uncased_L-12_H-768_A-12/bert_config.json
--init_checkpoint=./uncased/uncased_L-12_H-768_A-12/bert_model.ckpt
--max_seq_length=128
--train_batch_size=32
--learning_rate=2e-5
--num_train_epochs=3.0
--output_dir=./tmp/emotion/
  • task_name 表示我調用的是什麼處理類 ,這裏我們是用我們新的類所以選 emlo
  • 文件dir 可以自己定義, 如果無定義到會出錯, 我這裏是有3個文件夾 uncased裏面放預訓練模型, glue放數據,tmp/emotion裏面放結果

訓練結果如下:
在這裏插入圖片描述

4. 分類預測

4.1 修改參數, 進行預測

預測的話, 將運行參數改爲以下即可

python run_classifier.py 
  --task_name=emlo 
  --do_predict=true 
  --data_dir=./glue 
  --vocab_file=./uncased/uncased_L-12_H-768_A-12/vocab.txt 
  --bert_config_file=./uncased/uncased_L-12_H-768_A-12/bert_config.json 
  --init_checkpoint=./tmp/emotion/
  --max_seq_length=128 
  --output_dir=./tmp/emotion_out/

將會返回一個tsv, 每一列表示這一行的樣本是這一類的概率
在這裏插入圖片描述
每一類代表的類別和 在run_classify.py裏面定義的lab順序相同
在這裏插入圖片描述

4.2 得到類別

結果不能是概率,而是類別, 所以我們寫一個腳本進行轉化
get_results.py

import os
import pandas as pd


if __name__ == '__main__':
    path = "tmp/emotion_out/"
    pd_all = pd.read_csv(os.path.join(path, "test_results.tsv") ,sep='\t',header=None)

    data = pd.DataFrame(columns=['polarity'])
    print(pd_all.shape)

    for index in pd_all.index:
        neutral_score = pd_all.loc[index].values[0]
        positive_score = pd_all.loc[index].values[1]
        negative_score = pd_all.loc[index].values[2]

        if max(neutral_score, positive_score, negative_score) == neutral_score:
            # data.append(pd.DataFrame([index, "neutral"],columns=['id','polarity']),ignore_index=True)
            data.loc[index+1] = ["neutral"]
        elif max(neutral_score, positive_score, negative_score) == positive_score:
            #data.append(pd.DataFrame([index, "positive"],columns=['id','polarity']),ignore_index=True)
            data.loc[index+1] = [ "positive"]
        else:
            #data.append(pd.DataFrame([index, "negative"],columns=['id','polarity']),ignore_index=True)
            data.loc[index+1] = [ "negative"]
        #print(negative_score, positive_score, negative_score)

    data.to_csv(os.path.join(path, "pre_sample.tsv"),sep = '\t')
    #print(data)

得到最終預測結果
在這裏插入圖片描述

5. 運行問題

5.1 出現內存不夠

官方解釋影響內存大小的參數
The factors that affect memory usage are:

  • max_seq_length: The released models were trained with sequence lengths
    up to 512, but you can fine-tune with a shorter max sequence length to save
    substantial memory. This is controlled by the max_seq_length flag in our
    example code.
  • train_batch_size: The memory usage is also directly proportional to
    the batch size.
  • Model type, BERT-Base vs. BERT-Large: The BERT-Large model
    requires significantly more memory than BERT-Base.
  • Optimizer: The default optimizer for BERT is Adam, which requires a lot
    of extra memory to store the m and v vectors. Switching to a more memory
    efficient optimizer can reduce memory usage, but can also affect the
    results. We have not experimented with other optimizers for fine-tuning.

Using the default training scripts (run_classifier.py and run_squad.py), we
benchmarked the maximum batch size on single Titan X GPU (12GB RAM) with
TensorFlow 1.11.0:

System Seq Length Max Batch Size
BERT-Base 64 64
128 32
256 16
320 14
384 12
512 6
BERT-Large 64 12
128 6
256 2
320 1
384 0
512 0
  • 對於參數max_seq_length, train_batch_size 越小, 內存使用越小
  • 對於使用的預訓練集 BERT-Base 使用內存比 BERT-Large小
  • 使用不同的優化器也會造成一定的影響

作者給出了一些他使用的12g顯存的搭配, 可以參考使用

6. 源碼 GITHUB 地址

特意直接開放源碼, 供大家參考

https://github.com/wangjiwu/BERT-emotion-classification

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