BERT预训练模型在诸多NLP任务中都取得最优的结果。在处理文本分类问题时,即可以直接用BERT模型作为文本分类的模型,也可以将BERT模型的最后层输出的结果作为word embedding导入到我们定制的文本分类模型中(如text-CNN等)。总之现在只要你的计算资源能满足,一般问题都可以用BERT来处理,此次针对公司的一个实际项目——一个多类别(61类)的文本分类问题,其就取得了很好的结果。

  我们此次的任务是一个数据分布极度不平衡的多类别文本分类(有的类别下只有几个或者十几个样本,有的类别下又有几千个样本),在不做不平衡数据处理且不采用BERT模型时,其取得的F1值只有50%,而在不做不平衡数据处理但采用BERT模型时,其F1值能达到65%,但是在用bert模型时获得F1值时却存在一些问题。

  在tensorflow中只提供了二分类的precision,recall,f1值的计算接口,而bert源代码中的run_classifier.py文件中训练模型,验证模型等都是用的estimator API,这些高层API极大的限制了修改代码的灵活性。好在tensorflow源码中有一个方法可以计算混淆矩阵的方法,并且会返回一个operation。注意:这个和tf.confusion_matrix()不同,具体看源代码中下面这段代码:

        elif mode == tf.estimator.ModeKeys.EVAL:

            def metric_fn(per_example_loss, label_ids, logits, num_labels):
predictions = tf.argmax(logits, axis=-1, output_type=tf.int32)
accuracy = tf.metrics.accuracy(
labels=label_ids, predictions=predictions)
          
          # 这里的metrics时我们定义的一个python文件,在下面会介绍
conf_mat = metrics.get_metrics_ops(label_ids, predictions, num_labels) loss = tf.metrics.mean(values=per_example_loss)
return {
"eval_accuracy": accuracy,
"eval_cm": conf_mat,
"eval_loss": loss,
}

  验证时的性能指标计算都在这个方法里面,而且在return的这个字典中每个值必须是一个tuple。以accuracy为例,tf.metrics.accuracy返回的是一个(accuracy, update_op)这样一个tuple,而我们上一段说的tf.confusion_matrix只返回一个混淆矩阵。因此在这里我们使用一个内部的方法,方法导入如下:

from tensorflow.python.ops.metrics_impl import _streaming_confusion_matrix

这个方法会返回一个(confusion_matrix, update_op)的tuple。我们新建一个metrics.py文件,里面的代码如下:

import numpy as np
import tensorflow as tf
from tensorflow.python.ops.metrics_impl import _streaming_confusion_matrix def get_metrics_ops(labels, predictions, num_labels):
  # 得到混淆矩阵和update_op,在这里我们需要将生成的混淆矩阵转换成tensor
cm, op = _streaming_confusion_matrix(labels, predictions, num_labels)
tf.logging.info(type(cm))
tf.logging.info(type(op)) return (tf.convert_to_tensor(cm), op) def get_metrics(conf_mat, num_labels):
  # 得到numpy类型的混淆矩阵,然后计算precision,recall,f1值。
precisions = []
recalls = []
for i in range(num_labels):
tp = conf_mat[i][i].sum()
col_sum = conf_mat[:, i].sum()
row_sum = conf_mat[i].sum() precision = tp / col_sum if col_sum > 0 else 0
recall = tp / row_sum if row_sum > 0 else 0 precisions.append(precision)
recalls.append(recall) pre = sum(precisions) / len(precisions)
rec = sum(recalls) / len(recalls)
f1 = 2 * pre * rec / (pre + rec) return pre, rec, f1

最上面一段代码中return的字典中的值可以在run_classifier.py中main函数中的下面一段代码中得到:

    if FLAGS.do_eval:
eval_examples = processor.get_dev_examples(FLAGS.data_dir)
num_actual_eval_examples = len(eval_examples)
if FLAGS.use_tpu:
# TPU requires a fixed batch size for all batches, therefore the number
# of examples must be a multiple of the batch size, or else examples
# will get dropped. So we pad with fake examples which are ignored
# later on. These do NOT count towards the metric (all tf.metrics
# support a per-instance weight, and these get a weight of 0.0).
while len(eval_examples) % FLAGS.eval_batch_size != 0:
eval_examples.append(PaddingInputExample()) eval_file = os.path.join(FLAGS.output_dir, "eval.tf_record")
file_based_convert_examples_to_features(
eval_examples, label_list, FLAGS.max_seq_length, tokenizer, eval_file) tf.logging.info("***** Running evaluation *****")
tf.logging.info(" Num examples = %d (%d actual, %d padding)",
len(eval_examples), num_actual_eval_examples,
len(eval_examples) - num_actual_eval_examples)
tf.logging.info(" Batch size = %d", FLAGS.eval_batch_size) # This tells the estimator to run through the entire set.
eval_steps = None
# However, if running eval on the TPU, you will need to specify the
# number of steps.
if FLAGS.use_tpu:
assert len(eval_examples) % FLAGS.eval_batch_size == 0
eval_steps = int(len(eval_examples) // FLAGS.eval_batch_size) eval_drop_remainder = True if FLAGS.use_tpu else False
eval_input_fn = file_based_input_fn_builder(
input_file=eval_file,
seq_length=FLAGS.max_seq_length,
is_training=False,
drop_remainder=eval_drop_remainder)

     # result中就是return返回的字典
result = estimator.evaluate(input_fn=eval_input_fn, steps=eval_steps) output_eval_file = os.path.join(FLAGS.output_dir, "eval_results.txt")
with tf.gfile.GFile(output_eval_file, "w") as writer:
tf.logging.info("***** Eval results *****")
       
       # 我们可以拿到混淆矩阵(现在时numpy的形式),调用metrics.py文件中的方法来得到precision,recall,f1值
pre, rec, f1 = metrics.get_metrics(result["eval_cm"], len(label_list))
tf.logging.info("eval_precision: {}".format(pre))
tf.logging.info("eval_recall: {}".format(rec))
tf.logging.info("eval_f1: {}".format(f1))
tf.logging.info("eval_accuracy: {}".format(result["eval_accuracy"]))
tf.logging.info("eval_loss: {}".format(result["eval_loss"])) np.save("conf_mat.npy", result["eval_cm"])

通过上面的代码拿到混淆矩阵后,调用metrics.py文件中的get_metrics方法就可以得到precision,recall,f1值。

最新文章

  1. [Top-Down Approach]Take Notes
  2. niginx代理配置
  3. 学习安装并配置前端自动化工具Gulp
  4. 关于SIGSEGV错误及处理方法(转)
  5. Git commit 常见用法
  6. 黑马程序员——【Java基础】——正则表达式
  7. BZOJ3790 : 神奇项链
  8. lintcode : 跳跃游戏
  9. 按照行拆分textarea
  10. (转载)delphi实例TDBGrid用右键菜单复制行粘贴行
  11. Button with Hover Effect (Learned from 百度脑图)
  12. Teach Yourself Scheme in Fixnum Days 6 recursion递归
  13. 什么是xss盲打
  14. USACO 2.3 Cow Pedigrees
  15. 【HAL库每天一例】freemodbus移植
  16. jmeter接口系列:时间戳、加密
  17. 补充资料——自己实现极大似然估计(最大似然估计)MLE
  18. ensureCapacity增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。
  19. c++之sizeof的用法
  20. java Map按Key排序

热门文章

  1. Python爬虫 【requests】request for humans
  2. Java EE中的容器和注入分析,历史与未来
  3. MySQL学习(四)Join 等开发常用的操作 --- 2019年2月
  4. 03 JVM 从入门到实战 | 简述垃圾回收算法
  5. 为什么分库分表使用2的N次方 一个字节用两位16进制
  6. 从壹开始前后端分离 [ vue + .netcore 补充教程 ] 三十║ Nuxt实战:动态路由+同构
  7. JS中some()和every()和join()和concat()和pop(),push(),shift(),unshfit()和map()和filter()
  8. 【Python3爬虫】常见反爬虫措施及解决办法(一)
  9. ASP.NET Core 2.2 十八.各种Filter的内部处理机制及执行顺序
  10. Java数组协变与范型不变性