基于BERT的中文情感分析实践

1. BERT简介

BERT的全称为Bidirectional Encoder Representation from Transformers,是一个预训练的语言表征模型。它强调了不再像以往一样采用传统的单向语言模型或者把两个单向语言模型进行浅层拼接的方法进行预训练,而是采用新的masked language model(MLM),以致能生成深度的双向语言表征。BERT论文发表时提及在11个NLP(Natural Language Processing,自然语言处理)任务中获得了新的state-of-the-art的结果。

2. 环境配置

  • Ubuntu16.04
  • Anaconda3
  • python >= 3.6
  • tensorflow >= 1.12.0
  • pandas

先安装conda

# 查看conda环境
conda info -e

通过conda创建一个新的环境bert,切换到bert环境

# 切换到bert环境
conda activate bert

3. ChnSentiCorp数据集

我们选取ChnSentiCorp数据集,里面包含7000 多条酒店评论数据,5000 多条正向评论,2000 多条负向评论,这些评论数据有两个字段:label, review。

数据字段:
label:1表示正向评论,0表示负向评论
review:评论内容

数据地址是:https://raw.githubusercontent.com/SophonPlus/ChineseNlpCorpus/master/datasets/ChnSentiCorp_htl_all/ChnSentiCorp_htl_all.csv
新建一个脚本split_data.py,拆分成训练集train.csv,开发集dev.csv,测试集test.csv,比例8:1:1。

import pandas as pd 

df = pd.read_csv('ChnSentiCorp_htl_all.csv', dtype=str)

df = df.dropna()
df = df.applymap(lambda x: str(x).strip())

df = df.sample(frac=1).reset_index(drop=True)

# split train:dev:test as 8:1:1
train_df = df.iloc[:6212]
dev_df = df.iloc[6212:6989]
test_df = df.iloc[6989:]

train_df.to_csv('train.csv', sep=',', index=False)
dev_df.to_csv('dev.csv', sep=',', index=False)
test_df.to_csv('test.csv', sep=',', index=False)

脚本执行完成后:
├── ChnSentiCorp_htl_all.csv
├── dev.csv
├── split_data.py
├── test.csv
└── train.csv

4. 下载BERT源码和预训练模型

  1. 下载BERT源码
    https://github.com/google-research/bert/
git clone https://github.com/google-research/bert.git
  1. 下载BERT中文预训练模型
    下载地址: https://storage.googleapis.com/bert_models/2018_11_03/chinese_L-12_H-768_A-12.zip
    解压到自定义目录下
    ├── bert_config.json
    ├── bert_model.ckpt.data-00000-of-00001
    ├── bert_model.ckpt.index
    ├── bert_model.ckpt.meta
    └── vocab.txt

5. 修改代码

在run_classifier.py文件中有一个基类DataProcessor类,其代码如下:

class DataProcessor(object):
  """Base class for data converters for sequence classification data sets."""

  def get_train_examples(self, data_dir):
    """Gets a collection of `InputExample`s for the train set."""
    raise NotImplementedError()

  def get_dev_examples(self, data_dir):
    """Gets a collection of `InputExample`s for the dev set."""
    raise NotImplementedError()

  def get_test_examples(self, data_dir):
    """Gets a collection of `InputExample`s for prediction."""
    raise NotImplementedError()

  def get_labels(self):
    """Gets the list of labels for this data set."""
    raise NotImplementedError()

在这个基类中定义了一个读取文件的静态方法_read_tsv,四个分别获取训练集,验证集,测试集和标签的方法。在run_classsifier.py文件中我们可以看到,google对于一些公开数据集已经写了一些processor,如XnliProcessor,MnliProcessor,MrpcProcessor和ColaProcessor。这给我们提供了一个很好的示例,指导我们如何针对自己的数据集来写processor。接下来我们要定义自己的数据处理的类,我们将新增的类命名为SentimentProcessor。

class SentimentProcessor(DataProcessor):
  """Base class for data converters for sequence classification data sets."""

  def get_train_examples(self, data_dir):
    """See base class."""
    lines = self._read_csv(
        os.path.join(data_dir, "train.csv"))
    examples = []
    for (i, line) in enumerate(lines):
      if i == 0:
        continue
      guid = "train-%d" % (i)
      text_a = tokenization.convert_to_unicode(line[1])
      label = tokenization.convert_to_unicode(line[0])
      if label == tokenization.convert_to_unicode("contradictory"):
        label = tokenization.convert_to_unicode("contradiction")
      examples.append(
          InputExample(guid=guid, text_a=text_a, text_b=None, label=label))
    return examples

  def get_dev_examples(self, data_dir):
    """See base class."""
    lines = self._read_csv(
        os.path.join(data_dir, "dev.csv"))
    examples = []
    for (i, line) in enumerate(lines):
      if i == 0:
        continue
      guid = "dev-%d" % (i)
      text_a = tokenization.convert_to_unicode(line[1])
      label = tokenization.convert_to_unicode(line[0])
      if label == tokenization.convert_to_unicode("contradictory"):
        label = tokenization.convert_to_unicode("contradiction")
      examples.append(
          InputExample(guid=guid, text_a=text_a, text_b=None, label=label))
    return examples

  def get_test_examples(self, data_dir):
    """See base class."""
    lines = self._read_csv(
        os.path.join(data_dir, "test.csv"))
    examples = []
    for (i, line) in enumerate(lines):
      if i == 0:
        continue
      guid = "test-%d" % (i)
      text_a = tokenization.convert_to_unicode(line[1])
      label = tokenization.convert_to_unicode(line[0])
      if label == tokenization.convert_to_unicode("contradictory"):
        label = tokenization.convert_to_unicode("contradiction")
      examples.append(
          InputExample(guid=guid, text_a=text_a, text_b=None, label=label))
    return examples

  def get_labels(self):
    """See base class."""
    return ["0", "1"]

  @classmethod
  def _read_csv(cls, input_file, quotechar=None):
    """Reads a tab separated value file."""
    with tf.gfile.Open(input_file, "r") as f:
      reader = csv.reader(f, delimiter=",", quotechar=None)
      lines = []
      for line in reader:
        lines.append(line)
      return lines

在processors中增加SentimentProcessor

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

  processors = {
      "cola": ColaProcessor,
      "mnli": MnliProcessor,
      "mrpc": MrpcProcessor,
      "xnli": XnliProcessor,
      "senti": SentimentProcessor,
  }

6. 训练BERT模型

新建一个脚本文件train.sh,内容如下,日志文件输出到train.log,通过 tail -f train.log 查看,通过nvidia-smi命令查看GPU状态。

参数说明:
data_dir: 训练数据的地址
task_name: processor的名字
vocab_file: 字典地址,用默认提供的就可以了,当然也可以自定义
bert_config_file: 配置文件
output_dir: 模型的输出地址
do_train: 是否做fine-tuning,默认为false,如果为true必须重写获取训练集的方法
do_eval: 是否运行验证集,默认为false,如果为true必须重写获取验证集的方法
do_predict: 是否做预测,默认为false,如果为true必须重写获取测试集的方法

#!/bin/bash

export BERT_BASE_DIR=bert-models/chinese_L-12_H-768_A-12
export MY_DATASET=data
export OUTPUT_PATH=output
export TASK_NAME=senti

nohup /home/peng/anaconda3/envs/bert/bin/python run_classifier.py \
  --data_dir=$MY_DATASET \
  --task_name=$TASK_NAME \
  --output_dir=$OUTPUT_PATH \
  --vocab_file=$BERT_BASE_DIR/vocab.txt \
  --init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \
  --bert_config_file=$BERT_BASE_DIR/bert_config.json \
  --do_train=True \
  --do_eval=True \
  --do_predict=True \
  --max_seq_length=128 \
  --train_batch_size=16 \
  --learning_rate=5e-5 \
  --num_train_epochs=2.0 \
  >train.log 2>&1 &

7. 训练结果

训练结果在自定义OUTPUT_PATH/eval_results.txt中,

eval_accuracy = 0.84942085
eval_loss = 0.3728643
global_step = 776
loss = 0.3766538

测试集的预测结果在OUTPUT_PATH/test_results.tsv中,
前5条数据格式如下,两列数据分别表示[0, 1]概率:

0.012343313     0.9876567
0.9637287       0.03627124
0.3622907       0.6377093
0.0120654255    0.9879346
0.41722867      0.5827713

test.csv数据集中前5条如下:


image.png

8. 参考资料

  1. 快速用BERT实现情感分析
  2. 基于Bert的中文情感分析代码及分析
  3. BERT_中文情感分类操作及代码
  4. 基于Bert的中文情感分析代码及分析
  5. BERT文本分类实战
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351