1 大纲概述

  文本分类这个系列将会有十篇左右,包括基于word2vec预训练的文本分类,与及基于最新的预训练模型(ELMo,BERT等)的文本分类。总共有以下系列:

  word2vec预训练词向量

  textCNN 模型

  charCNN 模型

  Bi-LSTM 模型

  Bi-LSTM + Attention 模型

  RCNN 模型

  Adversarial LSTM 模型

  Transformer 模型

  ELMo 预训练模型

  BERT 预训练模型

  jupyter notebook代码均在textClassifier仓库中,python代码在NLP-Project中的text_classfier中。

2 数据集

  数据集为IMDB 电影影评,总共有三个数据文件,在/data/rawData目录下,包括unlabeledTrainData.tsv,labeledTrainData.tsv,testData.tsv。在进行文本分类时需要有标签的数据(labeledTrainData),数据预处理如文本分类实战(一)—— word2vec预训练词向量中一样,预处理后的文件为/data/preprocess/labeledTrain.csv。

3 Transformer 模型结构

  Transformer模型来自于论文Attention Is All You Need,关于Transformer具体的介绍见这篇。Transformer模型具体结构如下图:

  

  Transformer结构有两种:Encoder和Decoder,在文本分类中只使用到了Encoder,Decoder是生成式模型,主要用于自然语言生成的。

4 参数配置

  1. import os
  2. import csv
  3. import time
  4. import datetime
  5. import random
  6. import json
  7.  
  8. import warnings
  9. from collections import Counter
  10. from math import sqrt
  11.  
  12. import gensim
  13. import pandas as pd
  14. import numpy as np
  15. import tensorflow as tf
  16. from sklearn.metrics import roc_auc_score, accuracy_score, precision_score, recall_score
  17. warnings.filterwarnings("ignore")
  1. # 配置参数
  2.  
  3. class TrainingConfig(object):
  4. epoches = 10
  5. evaluateEvery = 100
  6. checkpointEvery = 100
  7. learningRate = 0.001
  8.  
  9. class ModelConfig(object):
  10. embeddingSize = 200
  11.  
  12. filters = 128 # 内层一维卷积核的数量,外层卷积核的数量应该等于embeddingSize,因为要确保每个layer后的输出维度和输入维度是一致的。
  13. numHeads = 8 # Attention 的头数
  14. numBlocks = 1 # 设置transformer block的数量
  15. epsilon = 1e-8 # LayerNorm 层中的最小除数
  16. keepProp = 0.9 # multi head attention 中的dropout
  17.  
  18. dropoutKeepProb = 0.5 # 全连接层的dropout
  19. l2RegLambda = 0.0
  20.  
  21. class Config(object):
  22. sequenceLength = 200 # 取了所有序列长度的均值
  23. batchSize = 128
  24.  
  25. dataSource = "../data/preProcess/labeledTrain.csv"
  26.  
  27. stopWordSource = "../data/english"
  28.  
  29. numClasses = 1 # 二分类设置为1,多分类设置为类别的数目
  30.  
  31. rate = 0.8 # 训练集的比例
  32.  
  33. training = TrainingConfig()
  34.  
  35. model = ModelConfig()
  36.  
  37. # 实例化配置参数对象
  38. config = Config()

5 生成训练数据

  1)将数据加载进来,将句子分割成词表示,并去除低频词和停用词。

  2)将词映射成索引表示,构建词汇-索引映射表,并保存成json的数据格式,之后做inference时可以用到。(注意,有的词可能不在word2vec的预训练词向量中,这种词直接用UNK表示)

  3)从预训练的词向量模型中读取出词向量,作为初始化值输入到模型中。

  4)将数据集分割成训练集和测试集

  1. # 数据预处理的类,生成训练集和测试集
  2.  
  3. class Dataset(object):
  4. def __init__(self, config):
  5. self.config = config
  6. self._dataSource = config.dataSource
  7. self._stopWordSource = config.stopWordSource
  8.  
  9. self._sequenceLength = config.sequenceLength # 每条输入的序列处理为定长
  10. self._embeddingSize = config.model.embeddingSize
  11. self._batchSize = config.batchSize
  12. self._rate = config.rate
  13.  
  14. self._stopWordDict = {}
  15.  
  16. self.trainReviews = []
  17. self.trainLabels = []
  18.  
  19. self.evalReviews = []
  20. self.evalLabels = []
  21.  
  22. self.wordEmbedding =None
  23.  
  24. self.labelList = []
  25.  
  26. def _readData(self, filePath):
  27. """
  28. 从csv文件中读取数据集
  29. """
  30.  
  31. df = pd.read_csv(filePath)
  32.  
  33. if self.config.numClasses == 1:
  34. labels = df["sentiment"].tolist()
  35. elif self.config.numClasses > 1:
  36. labels = df["rate"].tolist()
  37.  
  38. review = df["review"].tolist()
  39. reviews = [line.strip().split() for line in review]
  40.  
  41. return reviews, labels
  42.  
  43. def _labelToIndex(self, labels, label2idx):
  44. """
  45. 将标签转换成索引表示
  46. """
  47. labelIds = [label2idx[label] for label in labels]
  48. return labelIds
  49.  
  50. def _wordToIndex(self, reviews, word2idx):
  51. """
  52. 将词转换成索引
  53. """
  54. reviewIds = [[word2idx.get(item, word2idx["UNK"]) for item in review] for review in reviews]
  55. return reviewIds
  56.  
  57. def _genTrainEvalData(self, x, y, word2idx, rate):
  58. """
  59. 生成训练集和验证集
  60. """
  61. reviews = []
  62. for review in x:
  63. if len(review) >= self._sequenceLength:
  64. reviews.append(review[:self._sequenceLength])
  65. else:
  66. reviews.append(review + [word2idx["PAD"]] * (self._sequenceLength - len(review)))
  67.  
  68. trainIndex = int(len(x) * rate)
  69.  
  70. trainReviews = np.asarray(reviews[:trainIndex], dtype="int64")
  71. trainLabels = np.array(y[:trainIndex], dtype="float32")
  72.  
  73. evalReviews = np.asarray(reviews[trainIndex:], dtype="int64")
  74. evalLabels = np.array(y[trainIndex:], dtype="float32")
  75.  
  76. return trainReviews, trainLabels, evalReviews, evalLabels
  77.  
  78. def _genVocabulary(self, reviews, labels):
  79. """
  80. 生成词向量和词汇-索引映射字典,可以用全数据集
  81. """
  82.  
  83. allWords = [word for review in reviews for word in review]
  84.  
  85. # 去掉停用词
  86. subWords = [word for word in allWords if word not in self.stopWordDict]
  87.  
  88. wordCount = Counter(subWords) # 统计词频
  89. sortWordCount = sorted(wordCount.items(), key=lambda x: x[1], reverse=True)
  90.  
  91. # 去除低频词
  92. words = [item[0] for item in sortWordCount if item[1] >= 5]
  93.  
  94. vocab, wordEmbedding = self._getWordEmbedding(words)
  95. self.wordEmbedding = wordEmbedding
  96.  
  97. word2idx = dict(zip(vocab, list(range(len(vocab)))))
  98.  
  99. uniqueLabel = list(set(labels))
  100. label2idx = dict(zip(uniqueLabel, list(range(len(uniqueLabel)))))
  101. self.labelList = list(range(len(uniqueLabel)))
  102.  
  103. # 将词汇-索引映射表保存为json数据,之后做inference时直接加载来处理数据
  104. with open("../data/wordJson/word2idx.json", "w", encoding="utf-8") as f:
  105. json.dump(word2idx, f)
  106.  
  107. with open("../data/wordJson/label2idx.json", "w", encoding="utf-8") as f:
  108. json.dump(label2idx, f)
  109.  
  110. return word2idx, label2idx
  111.  
  112. def _getWordEmbedding(self, words):
  113. """
  114. 按照我们的数据集中的单词取出预训练好的word2vec中的词向量
  115. """
  116.  
  117. wordVec = gensim.models.KeyedVectors.load_word2vec_format("../word2vec/word2Vec.bin", binary=True)
  118. vocab = []
  119. wordEmbedding = []
  120.  
  121. # 添加 "pad" 和 "UNK",
  122. vocab.append("PAD")
  123. vocab.append("UNK")
  124. wordEmbedding.append(np.zeros(self._embeddingSize))
  125. wordEmbedding.append(np.random.randn(self._embeddingSize))
  126.  
  127. for word in words:
  128. try:
  129. vector = wordVec.wv[word]
  130. vocab.append(word)
  131. wordEmbedding.append(vector)
  132. except:
  133. print(word + "不存在于词向量中")
  134.  
  135. return vocab, np.array(wordEmbedding)
  136.  
  137. def _readStopWord(self, stopWordPath):
  138. """
  139. 读取停用词
  140. """
  141.  
  142. with open(stopWordPath, "r") as f:
  143. stopWords = f.read()
  144. stopWordList = stopWords.splitlines()
  145. # 将停用词用列表的形式生成,之后查找停用词时会比较快
  146. self.stopWordDict = dict(zip(stopWordList, list(range(len(stopWordList)))))
  147.  
  148. def dataGen(self):
  149. """
  150. 初始化训练集和验证集
  151. """
  152.  
  153. # 初始化停用词
  154. self._readStopWord(self._stopWordSource)
  155.  
  156. # 初始化数据集
  157. reviews, labels = self._readData(self._dataSource)
  158.  
  159. # 初始化词汇-索引映射表和词向量矩阵
  160. word2idx, label2idx = self._genVocabulary(reviews, labels)
  161.  
  162. # 将标签和句子数值化
  163. labelIds = self._labelToIndex(labels, label2idx)
  164. reviewIds = self._wordToIndex(reviews, word2idx)
  165.  
  166. # 初始化训练集和测试集
  167. trainReviews, trainLabels, evalReviews, evalLabels = self._genTrainEvalData(reviewIds, labelIds, word2idx, self._rate)
  168. self.trainReviews = trainReviews
  169. self.trainLabels = trainLabels
  170.  
  171. self.evalReviews = evalReviews
  172. self.evalLabels = evalLabels
  173.  
  174. data = Dataset(config)
  175. data.dataGen()

6 生成batch数据集

  采用生成器的形式向模型输入batch数据集,(生成器可以避免将所有的数据加入到内存中)

  1. # 输出batch数据集
  2.  
  3. def nextBatch(x, y, batchSize):
  4. """
  5. 生成batch数据集,用生成器的方式输出
  6. """
  7.  
  8. perm = np.arange(len(x))
  9. np.random.shuffle(perm)
  10. x = x[perm]
  11. y = y[perm]
  12.  
  13. numBatches = len(x) // batchSize
  14.  
  15. for i in range(numBatches):
  16. start = i * batchSize
  17. end = start + batchSize
  18. batchX = np.array(x[start: end], dtype="int64")
  19. batchY = np.array(y[start: end], dtype="float32")
  20.  
  21. yield batchX, batchY

7 Transformer模型

  关于transformer模型的一些使用心得:

  1)我在这里选择固定的one-hot的position embedding比论文中提出的利用正弦余弦函数生成的position embedding的效果要好,可能的原因是论文中提出的position embedding是作为可训练的值传入的,

    这样就增加了模型的复杂度,在小数据集(IMDB训练集大小:20000)上导致性能有所下降。

  2)mask可能不需要,添加mask和去除mask对结果基本没啥影响,也许在其他的任务或者数据集上有作用,但论文也并没有提出一定要在encoder结构中加入mask,mask更多的是用在decoder。

  3)transformer的层数,transformer的层数可以根据自己的数据集大小调整,在小数据集上基本上一层就够了。

  4)在subLayers上加dropout正则化,主要是在multi-head attention层加,因为feed forward是用卷积实现的,不加dropout应该没关系,当然如果feed forward用全连接层实现,那也加上dropout。

  5)在小数据集上transformer的效果并不一定比Bi-LSTM + Attention好,在IMDB上效果就更差。

  1. # 生成位置嵌入
  2. def fixedPositionEmbedding(batchSize, sequenceLen):
  3. embeddedPosition = []
  4. for batch in range(batchSize):
  5. x = []
  6. for step in range(sequenceLen):
  7. a = np.zeros(sequenceLen)
  8. a[step] = 1
  9. x.append(a)
  10. embeddedPosition.append(x)
  11.  
  12. return np.array(embeddedPosition, dtype="float32")
  13.  
  14. # 模型构建
  15.  
  16. class Transformer(object):
  17. """
  18. Transformer Encoder 用于文本分类
  19. """
  20. def __init__(self, config, wordEmbedding):
  21.  
  22. # 定义模型的输入
  23. self.inputX = tf.placeholder(tf.int32, [None, config.sequenceLength], name="inputX")
  24. self.inputY = tf.placeholder(tf.int32, [None], name="inputY")
  25.  
  26. self.dropoutKeepProb = tf.placeholder(tf.float32, name="dropoutKeepProb")
  27. self.embeddedPosition = tf.placeholder(tf.float32, [None, config.sequenceLength, config.sequenceLength], name="embeddedPosition")
  28.  
  29. self.config = config
  30.  
  31. # 定义l2损失
  32. l2Loss = tf.constant(0.0)
  33.  
  34. # 词嵌入层, 位置向量的定义方式有两种:一是直接用固定的one-hot的形式传入,然后和词向量拼接,在当前的数据集上表现效果更好。另一种
  35. # 就是按照论文中的方法实现,这样的效果反而更差,可能是增大了模型的复杂度,在小数据集上表现不佳。
  36.  
  37. with tf.name_scope("embedding"):
  38.  
  39. # 利用预训练的词向量初始化词嵌入矩阵
  40. self.W = tf.Variable(tf.cast(wordEmbedding, dtype=tf.float32, name="word2vec") ,name="W")
  41. # 利用词嵌入矩阵将输入的数据中的词转换成词向量,维度[batch_size, sequence_length, embedding_size]
  42. self.embedded = tf.nn.embedding_lookup(self.W, self.inputX)
  43. self.embeddedWords = tf.concat([self.embedded, self.embeddedPosition], -1)
  44.  
  45. with tf.name_scope("transformer"):
  46. for i in range(config.model.numBlocks):
  47. with tf.name_scope("transformer-{}".format(i + 1)):
  48.  
  49. # 维度[batch_size, sequence_length, embedding_size]
  50. multiHeadAtt = self._multiheadAttention(rawKeys=self.inputX, queries=self.embeddedWords,
  51. keys=self.embeddedWords)
  52. # 维度[batch_size, sequence_length, embedding_size]
  53. self.embeddedWords = self._feedForward(multiHeadAtt,
  54. [config.model.filters, config.model.embeddingSize + config.sequenceLength])
  55.  
  56. outputs = tf.reshape(self.embeddedWords, [-1, config.sequenceLength * (config.model.embeddingSize + config.sequenceLength)])
  57.  
  58. outputSize = outputs.get_shape()[-1].value
  59.  
  60. # with tf.name_scope("wordEmbedding"):
  61. # self.W = tf.Variable(tf.cast(wordEmbedding, dtype=tf.float32, name="word2vec"), name="W")
  62. # self.wordEmbedded = tf.nn.embedding_lookup(self.W, self.inputX)
  63.  
  64. # with tf.name_scope("positionEmbedding"):
  65. # print(self.wordEmbedded)
  66. # self.positionEmbedded = self._positionEmbedding()
  67.  
  68. # self.embeddedWords = self.wordEmbedded + self.positionEmbedded
  69.  
  70. # with tf.name_scope("transformer"):
  71. # for i in range(config.model.numBlocks):
  72. # with tf.name_scope("transformer-{}".format(i + 1)):
  73.  
  74. # # 维度[batch_size, sequence_length, embedding_size]
  75. # multiHeadAtt = self._multiheadAttention(rawKeys=self.wordEmbedded, queries=self.embeddedWords,
  76. # keys=self.embeddedWords)
  77. # # 维度[batch_size, sequence_length, embedding_size]
  78. # self.embeddedWords = self._feedForward(multiHeadAtt, [config.model.filters, config.model.embeddingSize])
  79.  
  80. # outputs = tf.reshape(self.embeddedWords, [-1, config.sequenceLength * (config.model.embeddingSize)])
  81.  
  82. # outputSize = outputs.get_shape()[-1].value
  83.  
  84. with tf.name_scope("dropout"):
  85. outputs = tf.nn.dropout(outputs, keep_prob=self.dropoutKeepProb)
  86.  
  87. # 全连接层的输出
  88. with tf.name_scope("output"):
  89. outputW = tf.get_variable(
  90. "outputW",
  91. shape=[outputSize, config.numClasses],
  92. initializer=tf.contrib.layers.xavier_initializer())
  93.  
  94. outputB= tf.Variable(tf.constant(0.1, shape=[config.numClasses]), name="outputB")
  95. l2Loss += tf.nn.l2_loss(outputW)
  96. l2Loss += tf.nn.l2_loss(outputB)
  97. self.logits = tf.nn.xw_plus_b(outputs, outputW, outputB, name="logits")
  98.  
  99. if config.numClasses == 1:
  100. self.predictions = tf.cast(tf.greater_equal(self.logits, 0.0), tf.float32, name="predictions")
  101. elif config.numClasses > 1:
  102. self.predictions = tf.argmax(self.logits, axis=-1, name="predictions")
  103.  
  104. # 计算二元交叉熵损失
  105. with tf.name_scope("loss"):
  106.  
  107. if config.numClasses == 1:
  108. losses = tf.nn.sigmoid_cross_entropy_with_logits(logits=self.logits, labels=tf.cast(tf.reshape(self.inputY, [-1, 1]),
  109. dtype=tf.float32))
  110. elif config.numClasses > 1:
  111. losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.logits, labels=self.inputY)
  112.  
  113. self.loss = tf.reduce_mean(losses) + config.model.l2RegLambda * l2Loss
  114.  
  115. def _layerNormalization(self, inputs, scope="layerNorm"):
  116. # LayerNorm层和BN层有所不同
  117. epsilon = self.config.model.epsilon
  118.  
  119. inputsShape = inputs.get_shape() # [batch_size, sequence_length, embedding_size]
  120.  
  121. paramsShape = inputsShape[-1:]
  122.  
  123. # LayerNorm是在最后的维度上计算输入的数据的均值和方差,BN层是考虑所有维度的
  124. # mean, variance的维度都是[batch_size, sequence_len, 1]
  125. mean, variance = tf.nn.moments(inputs, [-1], keep_dims=True)
  126.  
  127. beta = tf.Variable(tf.zeros(paramsShape))
  128.  
  129. gamma = tf.Variable(tf.ones(paramsShape))
  130. normalized = (inputs - mean) / ((variance + epsilon) ** .5)
  131.  
  132. outputs = gamma * normalized + beta
  133.  
  134. return outputs
  135.  
  136. def _multiheadAttention(self, rawKeys, queries, keys, numUnits=None, causality=False, scope="multiheadAttention"):
  137. # rawKeys 的作用是为了计算mask时用的,因为keys是加上了position embedding的,其中不存在padding为0的值
  138.  
  139. numHeads = self.config.model.numHeads
  140. keepProp = self.config.model.keepProp
  141.  
  142. if numUnits is None: # 若是没传入值,直接去输入数据的最后一维,即embedding size.
  143. numUnits = queries.get_shape().as_list()[-1]
  144.  
  145. # tf.layers.dense可以做多维tensor数据的非线性映射,在计算self-Attention时,一定要对这三个值进行非线性映射,
  146. # 其实这一步就是论文中Multi-Head Attention中的对分割后的数据进行权重映射的步骤,我们在这里先映射后分割,原则上是一样的。
  147. # Q, K, V的维度都是[batch_size, sequence_length, embedding_size]
  148. Q = tf.layers.dense(queries, numUnits, activation=tf.nn.relu)
  149. K = tf.layers.dense(keys, numUnits, activation=tf.nn.relu)
  150. V = tf.layers.dense(keys, numUnits, activation=tf.nn.relu)
  151.  
  152. # 将数据按最后一维分割成num_heads个, 然后按照第一维拼接
  153. # Q, K, V 的维度都是[batch_size * numHeads, sequence_length, embedding_size/numHeads]
  154. Q_ = tf.concat(tf.split(Q, numHeads, axis=-1), axis=0)
  155. K_ = tf.concat(tf.split(K, numHeads, axis=-1), axis=0)
  156. V_ = tf.concat(tf.split(V, numHeads, axis=-1), axis=0)
  157.  
  158. # 计算keys和queries之间的点积,维度[batch_size * numHeads, queries_len, key_len], 后两维是queries和keys的序列长度
  159. similary = tf.matmul(Q_, tf.transpose(K_, [0, 2, 1]))
  160.  
  161. # 对计算的点积进行缩放处理,除以向量长度的根号值
  162. scaledSimilary = similary / (K_.get_shape().as_list()[-1] ** 0.5)
  163.  
  164. # 在我们输入的序列中会存在padding这个样的填充词,这种词应该对最终的结果是毫无帮助的,原则上说当padding都是输入0时,
  165. # 计算出来的权重应该也是0,但是在transformer中引入了位置向量,当和位置向量相加之后,其值就不为0了,因此在添加位置向量
  166. # 之前,我们需要将其mask为0。虽然在queries中也存在这样的填充词,但原则上模型的结果之和输入有关,而且在self-Attention中
  167. # queryies = keys,因此只要一方为0,计算出的权重就为0。
  168. # 具体关于key mask的介绍可以看看这里: https://github.com/Kyubyong/transformer/issues/3
  169.  
  170. # 利用tf,tile进行张量扩张, 维度[batch_size * numHeads, keys_len] keys_len = keys 的序列长度
  171. keyMasks = tf.tile(rawKeys, [numHeads, 1])
  172.  
  173. # 增加一个维度,并进行扩张,得到维度[batch_size * numHeads, queries_len, keys_len]
  174. keyMasks = tf.tile(tf.expand_dims(keyMasks, 1), [1, tf.shape(queries)[1], 1])
  175.  
  176. # tf.ones_like生成元素全为1,维度和scaledSimilary相同的tensor, 然后得到负无穷大的值
  177. paddings = tf.ones_like(scaledSimilary) * (-2 ** (32 + 1))
  178.  
  179. # tf.where(condition, x, y),condition中的元素为bool值,其中对应的True用x中的元素替换,对应的False用y中的元素替换
  180. # 因此condition,x,y的维度是一样的。下面就是keyMasks中的值为0就用paddings中的值替换
  181. maskedSimilary = tf.where(tf.equal(keyMasks, 0), paddings, scaledSimilary) # 维度[batch_size * numHeads, queries_len, key_len]
  182.  
  183. # 在计算当前的词时,只考虑上文,不考虑下文,出现在Transformer Decoder中。在文本分类时,可以只用Transformer Encoder。
  184. # Decoder是生成模型,主要用在语言生成中
  185. if causality:
  186. diagVals = tf.ones_like(maskedSimilary[0, :, :]) # [queries_len, keys_len]
  187. tril = tf.contrib.linalg.LinearOperatorTriL(diagVals).to_dense() # [queries_len, keys_len]
  188. masks = tf.tile(tf.expand_dims(tril, 0), [tf.shape(maskedSimilary)[0], 1, 1]) # [batch_size * numHeads, queries_len, keys_len]
  189.  
  190. paddings = tf.ones_like(masks) * (-2 ** (32 + 1))
  191. maskedSimilary = tf.where(tf.equal(masks, 0), paddings, maskedSimilary) # [batch_size * numHeads, queries_len, keys_len]
  192.  
  193. # 通过softmax计算权重系数,维度 [batch_size * numHeads, queries_len, keys_len]
  194. weights = tf.nn.softmax(maskedSimilary)
  195.  
  196. # 加权和得到输出值, 维度[batch_size * numHeads, sequence_length, embedding_size/numHeads]
  197. outputs = tf.matmul(weights, V_)
  198.  
  199. # 将多头Attention计算的得到的输出重组成最初的维度[batch_size, sequence_length, embedding_size]
  200. outputs = tf.concat(tf.split(outputs, numHeads, axis=0), axis=2)
  201.  
  202. outputs = tf.nn.dropout(outputs, keep_prob=keepProp)
  203.  
  204. # 对每个subLayers建立残差连接,即H(x) = F(x) + x
  205. outputs += queries
  206. # normalization 层
  207. outputs = self._layerNormalization(outputs)
  208. return outputs
  209.  
  210. def _feedForward(self, inputs, filters, scope="multiheadAttention"):
  211. # 在这里的前向传播采用卷积神经网络
  212.  
  213. # 内层
  214. params = {"inputs": inputs, "filters": filters[0], "kernel_size": 1,
  215. "activation": tf.nn.relu, "use_bias": True}
  216. outputs = tf.layers.conv1d(**params)
  217.  
  218. # 外层
  219. params = {"inputs": outputs, "filters": filters[1], "kernel_size": 1,
  220. "activation": None, "use_bias": True}
  221.  
  222. # 这里用到了一维卷积,实际上卷积核尺寸还是二维的,只是只需要指定高度,宽度和embedding size的尺寸一致
  223. # 维度[batch_size, sequence_length, embedding_size]
  224. outputs = tf.layers.conv1d(**params)
  225.  
  226. # 残差连接
  227. outputs += inputs
  228.  
  229. # 归一化处理
  230. outputs = self._layerNormalization(outputs)
  231.  
  232. return outputs
  233.  
  234. def _positionEmbedding(self, scope="positionEmbedding"):
  235. # 生成可训练的位置向量
  236. batchSize = self.config.batchSize
  237. sequenceLen = self.config.sequenceLength
  238. embeddingSize = self.config.model.embeddingSize
  239.  
  240. # 生成位置的索引,并扩张到batch中所有的样本上
  241. positionIndex = tf.tile(tf.expand_dims(tf.range(sequenceLen), 0), [batchSize, 1])
  242.  
  243. # 根据正弦和余弦函数来获得每个位置上的embedding的第一部分
  244. positionEmbedding = np.array([[pos / np.power(10000, (i-i%2) / embeddingSize) for i in range(embeddingSize)]
  245. for pos in range(sequenceLen)])
  246.  
  247. # 然后根据奇偶性分别用sin和cos函数来包装
  248. positionEmbedding[:, 0::2] = np.sin(positionEmbedding[:, 0::2])
  249. positionEmbedding[:, 1::2] = np.cos(positionEmbedding[:, 1::2])
  250.  
  251. # 将positionEmbedding转换成tensor的格式
  252. positionEmbedding_ = tf.cast(positionEmbedding, dtype=tf.float32)
  253.  
  254. # 得到三维的矩阵[batchSize, sequenceLen, embeddingSize]
  255. positionEmbedded = tf.nn.embedding_lookup(positionEmbedding_, positionIndex)
  256.  
  257. return positionEmbedded

8 定义计算metrics的函数

  1. """
  2. 定义各类性能指标
  3. """
  4.  
  5. def mean(item: list) -> float:
  6. """
  7. 计算列表中元素的平均值
  8. :param item: 列表对象
  9. :return:
  10. """
  11. res = sum(item) / len(item) if len(item) > 0 else 0
  12. return res
  13.  
  14. def accuracy(pred_y, true_y):
  15. """
  16. 计算二类和多类的准确率
  17. :param pred_y: 预测结果
  18. :param true_y: 真实结果
  19. :return:
  20. """
  21. if isinstance(pred_y[0], list):
  22. pred_y = [item[0] for item in pred_y]
  23. corr = 0
  24. for i in range(len(pred_y)):
  25. if pred_y[i] == true_y[i]:
  26. corr += 1
  27. acc = corr / len(pred_y) if len(pred_y) > 0 else 0
  28. return acc
  29.  
  30. def binary_precision(pred_y, true_y, positive=1):
  31. """
  32. 二类的精确率计算
  33. :param pred_y: 预测结果
  34. :param true_y: 真实结果
  35. :param positive: 正例的索引表示
  36. :return:
  37. """
  38. corr = 0
  39. pred_corr = 0
  40. for i in range(len(pred_y)):
  41. if pred_y[i] == positive:
  42. pred_corr += 1
  43. if pred_y[i] == true_y[i]:
  44. corr += 1
  45.  
  46. prec = corr / pred_corr if pred_corr > 0 else 0
  47. return prec
  48.  
  49. def binary_recall(pred_y, true_y, positive=1):
  50. """
  51. 二类的召回率
  52. :param pred_y: 预测结果
  53. :param true_y: 真实结果
  54. :param positive: 正例的索引表示
  55. :return:
  56. """
  57. corr = 0
  58. true_corr = 0
  59. for i in range(len(pred_y)):
  60. if true_y[i] == positive:
  61. true_corr += 1
  62. if pred_y[i] == true_y[i]:
  63. corr += 1
  64.  
  65. rec = corr / true_corr if true_corr > 0 else 0
  66. return rec
  67.  
  68. def binary_f_beta(pred_y, true_y, beta=1.0, positive=1):
  69. """
  70. 二类的f beta值
  71. :param pred_y: 预测结果
  72. :param true_y: 真实结果
  73. :param beta: beta值
  74. :param positive: 正例的索引表示
  75. :return:
  76. """
  77. precision = binary_precision(pred_y, true_y, positive)
  78. recall = binary_recall(pred_y, true_y, positive)
  79. try:
  80. f_b = (1 + beta * beta) * precision * recall / (beta * beta * precision + recall)
  81. except:
  82. f_b = 0
  83. return f_b
  84.  
  85. def multi_precision(pred_y, true_y, labels):
  86. """
  87. 多类的精确率
  88. :param pred_y: 预测结果
  89. :param true_y: 真实结果
  90. :param labels: 标签列表
  91. :return:
  92. """
  93. if isinstance(pred_y[0], list):
  94. pred_y = [item[0] for item in pred_y]
  95.  
  96. precisions = [binary_precision(pred_y, true_y, label) for label in labels]
  97. prec = mean(precisions)
  98. return prec
  99.  
  100. def multi_recall(pred_y, true_y, labels):
  101. """
  102. 多类的召回率
  103. :param pred_y: 预测结果
  104. :param true_y: 真实结果
  105. :param labels: 标签列表
  106. :return:
  107. """
  108. if isinstance(pred_y[0], list):
  109. pred_y = [item[0] for item in pred_y]
  110.  
  111. recalls = [binary_recall(pred_y, true_y, label) for label in labels]
  112. rec = mean(recalls)
  113. return rec
  114.  
  115. def multi_f_beta(pred_y, true_y, labels, beta=1.0):
  116. """
  117. 多类的f beta值
  118. :param pred_y: 预测结果
  119. :param true_y: 真实结果
  120. :param labels: 标签列表
  121. :param beta: beta值
  122. :return:
  123. """
  124. if isinstance(pred_y[0], list):
  125. pred_y = [item[0] for item in pred_y]
  126.  
  127. f_betas = [binary_f_beta(pred_y, true_y, beta, label) for label in labels]
  128. f_beta = mean(f_betas)
  129. return f_beta
  130.  
  131. def get_binary_metrics(pred_y, true_y, f_beta=1.0):
  132. """
  133. 得到二分类的性能指标
  134. :param pred_y:
  135. :param true_y:
  136. :param f_beta:
  137. :return:
  138. """
  139. acc = accuracy(pred_y, true_y)
  140. recall = binary_recall(pred_y, true_y)
  141. precision = binary_precision(pred_y, true_y)
  142. f_beta = binary_f_beta(pred_y, true_y, f_beta)
  143. return acc, recall, precision, f_beta
  144.  
  145. def get_multi_metrics(pred_y, true_y, labels, f_beta=1.0):
  146. """
  147. 得到多分类的性能指标
  148. :param pred_y:
  149. :param true_y:
  150. :param labels:
  151. :param f_beta:
  152. :return:
  153. """
  154. acc = accuracy(pred_y, true_y)
  155. recall = multi_recall(pred_y, true_y, labels)
  156. precision = multi_precision(pred_y, true_y, labels)
  157. f_beta = multi_f_beta(pred_y, true_y, labels, f_beta)
  158. return acc, recall, precision, f_beta

9 训练模型

  在训练时,我们定义了tensorBoard的输出,并定义了两种模型保存的方法。 

  1. # 训练模型
  2.  
  3. # 生成训练集和验证集
  4. trainReviews = data.trainReviews
  5. trainLabels = data.trainLabels
  6. evalReviews = data.evalReviews
  7. evalLabels = data.evalLabels
  8.  
  9. wordEmbedding = data.wordEmbedding
  10. labelList = data.labelList
  11.  
  12. embeddedPosition = fixedPositionEmbedding(config.batchSize, config.sequenceLength)
  13.  
  14. # 定义计算图
  15. with tf.Graph().as_default():
  16.  
  17. session_conf = tf.ConfigProto(allow_soft_placement=True, log_device_placement=False)
  18. session_conf.gpu_options.allow_growth=True
  19. session_conf.gpu_options.per_process_gpu_memory_fraction = 0.9 # 配置gpu占用率
  20.  
  21. sess = tf.Session(config=session_conf)
  22.  
  23. # 定义会话
  24. with sess.as_default():
  25. transformer = Transformer(config, wordEmbedding)
  26.  
  27. globalStep = tf.Variable(0, name="globalStep", trainable=False)
  28. # 定义优化函数,传入学习速率参数
  29. optimizer = tf.train.AdamOptimizer(config.training.learningRate)
  30. # 计算梯度,得到梯度和变量
  31. gradsAndVars = optimizer.compute_gradients(transformer.loss)
  32. # 将梯度应用到变量下,生成训练器
  33. trainOp = optimizer.apply_gradients(gradsAndVars, global_step=globalStep)
  34.  
  35. # 用summary绘制tensorBoard
  36. gradSummaries = []
  37. for g, v in gradsAndVars:
  38. if g is not None:
  39. tf.summary.histogram("{}/grad/hist".format(v.name), g)
  40. tf.summary.scalar("{}/grad/sparsity".format(v.name), tf.nn.zero_fraction(g))
  41.  
  42. outDir = os.path.abspath(os.path.join(os.path.curdir, "summarys"))
  43. print("Writing to {}\n".format(outDir))
  44.  
  45. lossSummary = tf.summary.scalar("loss", transformer.loss)
  46. summaryOp = tf.summary.merge_all()
  47.  
  48. trainSummaryDir = os.path.join(outDir, "train")
  49. trainSummaryWriter = tf.summary.FileWriter(trainSummaryDir, sess.graph)
  50.  
  51. evalSummaryDir = os.path.join(outDir, "eval")
  52. evalSummaryWriter = tf.summary.FileWriter(evalSummaryDir, sess.graph)
  53.  
  54. # 初始化所有变量
  55. saver = tf.train.Saver(tf.global_variables(), max_to_keep=5)
  56.  
  57. # 保存模型的一种方式,保存为pb文件
  58. savedModelPath = "../model/transformer/savedModel"
  59. if os.path.exists(savedModelPath):
  60. os.rmdir(savedModelPath)
  61. builder = tf.saved_model.builder.SavedModelBuilder(savedModelPath)
  62.  
  63. sess.run(tf.global_variables_initializer())
  64.  
  65. def trainStep(batchX, batchY):
  66. """
  67. 训练函数
  68. """
  69. feed_dict = {
  70. transformer.inputX: batchX,
  71. transformer.inputY: batchY,
  72. transformer.dropoutKeepProb: config.model.dropoutKeepProb,
  73. transformer.embeddedPosition: embeddedPosition
  74. }
  75. _, summary, step, loss, predictions = sess.run(
  76. [trainOp, summaryOp, globalStep, transformer.loss, transformer.predictions],
  77. feed_dict)
  78.  
  79. if config.numClasses == 1:
  80. acc, recall, prec, f_beta = get_binary_metrics(pred_y=predictions, true_y=batchY)
  81.  
  82. elif config.numClasses > 1:
  83. acc, recall, prec, f_beta = get_multi_metrics(pred_y=predictions, true_y=batchY,
  84. labels=labelList)
  85.  
  86. trainSummaryWriter.add_summary(summary, step)
  87.  
  88. return loss, acc, prec, recall, f_beta
  89.  
  90. def devStep(batchX, batchY):
  91. """
  92. 验证函数
  93. """
  94. feed_dict = {
  95. transformer.inputX: batchX,
  96. transformer.inputY: batchY,
  97. transformer.dropoutKeepProb: 1.0,
  98. transformer.embeddedPosition: embeddedPosition
  99. }
  100. summary, step, loss, predictions = sess.run(
  101. [summaryOp, globalStep, transformer.loss, transformer.predictions],
  102. feed_dict)
  103.  
  104. if config.numClasses == 1:
  105. acc, recall, prec, f_beta = get_binary_metrics(pred_y=predictions, true_y=batchY)
  106.  
  107. elif config.numClasses > 1:
  108. acc, recall, prec, f_beta = get_multi_metrics(pred_y=predictions, true_y=batchY,
  109. labels=labelList)
  110.  
  111. trainSummaryWriter.add_summary(summary, step)
  112.  
  113. return loss, acc, prec, recall, f_beta
  114.  
  115. for i in range(config.training.epoches):
  116. # 训练模型
  117. print("start training model")
  118. for batchTrain in nextBatch(trainReviews, trainLabels, config.batchSize):
  119. loss, acc, prec, recall, f_beta = trainStep(batchTrain[0], batchTrain[1])
  120.  
  121. currentStep = tf.train.global_step(sess, globalStep)
  122. print("train: step: {}, loss: {}, acc: {}, recall: {}, precision: {}, f_beta: {}".format(
  123. currentStep, loss, acc, recall, prec, f_beta))
  124. if currentStep % config.training.evaluateEvery == 0:
  125. print("\nEvaluation:")
  126.  
  127. losses = []
  128. accs = []
  129. f_betas = []
  130. precisions = []
  131. recalls = []
  132.  
  133. for batchEval in nextBatch(evalReviews, evalLabels, config.batchSize):
  134. loss, acc, precision, recall, f_beta = devStep(batchEval[0], batchEval[1])
  135. losses.append(loss)
  136. accs.append(acc)
  137. f_betas.append(f_beta)
  138. precisions.append(precision)
  139. recalls.append(recall)
  140.  
  141. time_str = datetime.datetime.now().isoformat()
  142. print("{}, step: {}, loss: {}, acc: {},precision: {}, recall: {}, f_beta: {}".format(time_str, currentStep, mean(losses),
  143. mean(accs), mean(precisions),
  144. mean(recalls), mean(f_betas)))
  145.  
  146. if currentStep % config.training.checkpointEvery == 0:
  147. # 保存模型的另一种方法,保存checkpoint文件
  148. path = saver.save(sess, "../model/Transformer/model/my-model", global_step=currentStep)
  149. print("Saved model checkpoint to {}\n".format(path))
  150.  
  151. inputs = {"inputX": tf.saved_model.utils.build_tensor_info(transformer.inputX),
  152. "keepProb": tf.saved_model.utils.build_tensor_info(transformer.dropoutKeepProb)}
  153.  
  154. outputs = {"predictions": tf.saved_model.utils.build_tensor_info(transformer.predictions)}
  155.  
  156. prediction_signature = tf.saved_model.signature_def_utils.build_signature_def(inputs=inputs, outputs=outputs,
  157. method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME)
  158. legacy_init_op = tf.group(tf.tables_initializer(), name="legacy_init_op")
  159. builder.add_meta_graph_and_variables(sess, [tf.saved_model.tag_constants.SERVING],
  160. signature_def_map={"predict": prediction_signature}, legacy_init_op=legacy_init_op)
  161.  
  162. builder.save()

文本分类实战(八)—— Transformer模型的更多相关文章

  1. 文本分类实战(七)—— Adversarial LSTM模型

    1 大纲概述 文本分类这个系列将会有十篇左右,包括基于word2vec预训练的文本分类,与及基于最新的预训练模型(ELMo,BERT等)的文本分类.总共有以下系列: word2vec预训练词向量 te ...

  2. 文本分类实战(六)—— RCNN模型

    1 大纲概述 文本分类这个系列将会有十篇左右,包括基于word2vec预训练的文本分类,与及基于最新的预训练模型(ELMo,BERT等)的文本分类.总共有以下系列: word2vec预训练词向量 te ...

  3. 文本分类实战(五)—— Bi-LSTM + Attention模型

    1 大纲概述 文本分类这个系列将会有十篇左右,包括基于word2vec预训练的文本分类,与及基于最新的预训练模型(ELMo,BERT等)的文本分类.总共有以下系列: word2vec预训练词向量 te ...

  4. 文本分类实战(四)—— Bi-LSTM模型

    1 大纲概述 文本分类这个系列将会有十篇左右,包括基于word2vec预训练的文本分类,与及基于最新的预训练模型(ELMo,BERT等)的文本分类.总共有以下系列: word2vec预训练词向量 te ...

  5. 文本分类实战(三)—— charCNN模型

    1 大纲概述 文本分类这个系列将会有十篇左右,包括基于word2vec预训练的文本分类,与及基于最新的预训练模型(ELMo,BERT等)的文本分类.总共有以下系列: word2vec预训练词向量 te ...

  6. 文本分类实战(二)—— textCNN 模型

    1 大纲概述 文本分类这个系列将会有十篇左右,包括基于word2vec预训练的文本分类,与及基于最新的预训练模型(ELMo,BERT等)的文本分类.总共有以下系列: word2vec预训练词向量 te ...

  7. 文本分类实战(十)—— BERT 预训练模型

    1 大纲概述 文本分类这个系列将会有十篇左右,包括基于word2vec预训练的文本分类,与及基于最新的预训练模型(ELMo,BERT等)的文本分类.总共有以下系列: word2vec预训练词向量 te ...

  8. 文本分类实战(九)—— ELMO 预训练模型

    1 大纲概述 文本分类这个系列将会有十篇左右,包括基于word2vec预训练的文本分类,与及基于最新的预训练模型(ELMo,BERT等)的文本分类.总共有以下系列: word2vec预训练词向量 te ...

  9. Python 1行代码实现文本分类(实战笔记),含代码详细说明及运行结果

    Python 1行代码实现文本分类(实战笔记),含代码详细说明及运行结果 一.详细说明及代码 tc.py =============================================== ...

随机推荐

  1. salesforce lightning零基础学习(十三) 自定义Lookup组件(Single & Multiple)

    上一篇简单的介绍了自定义的Lookup单选的组件,功能为通过引用组件Attribute传递相关的sObject Name,捕捉用户输入的信息,从而实现搜索的功能. 我们做项目的时候,可能要从多个表中获 ...

  2. 行为驱动:Cucumber + Selenium + Java(三) - 使用标签实现测试分组

    在上一篇中,我们写出了Selenium + Cucumber + Java环境下的第一个BDD自动化测试用例,这一篇我们说说怎么用标签对用例进行分组. 3.1 Cucumber标签 实际工作中,我们的 ...

  3. 如何学习SpringCloud?(SpringCloud模板)

    前言 对于SpringCloud来说(下面简称SC),现在网上已经有很多教程写的非常详细,因为SC的组件特别多,虽然不是所有组件都需要用到,但是学习的时候我们都需要去学习和了解.所以我想如果再写把每一 ...

  4. Ubuntu安装谷歌浏览器

    首选方法: sudo wget http://www.linuxidc.com/files/repo/google-chrome.list -P /etc/apt/sources.list.d/ wg ...

  5. ELK-log4j2异步输出+logstash

    1.pom.xml配置文件 <dependency> <groupId>log4j</groupId> <artifactId>log4j</ar ...

  6. Redis【入门】就这一篇!

    Redis 概述 在我们日常的Java Web开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一 ...

  7. Python机器学习笔记 使用scikit-learn工具进行PCA降维

    之前总结过关于PCA的知识:深入学习主成分分析(PCA)算法原理.这里打算再写一篇笔记,总结一下如何使用scikit-learn工具来进行PCA降维. 在数据处理中,经常会遇到特征维度比样本数量多得多 ...

  8. Java开发笔记(五十七)因抽象方法而产生的抽象类

    前面介绍了类的常见用法,令人感叹面向对象的强大,几乎日常生活中的所有事物,都可以抽象成Java的基类及其子类.然而抽象操作也有副作用,就是某个抽象而来的行为可能是不确定的,比如半夜鸡叫,如果是公鸡则必 ...

  9. Win10系统给文件夹添加备注

    在Win10系统中,相信大多用户都没有看到过文件或者是文件夹上有备注信息.下面给大家分享下在Win10系统中给文件夹或文件添加备注的方法.在添加备注之前,首先我们要在需要显示备注的文件夹中显示&quo ...

  10. 单元测试与Mockito

    1.什么是单元测试? 顾名思义单元测试就是对软件系统中最小的单元(函数.类)做测试,类似焊接电路板前对每个电容器(电子元器件)的测试.从软件测试分级来看,单元测试是最底层也是离程序员最近的一层,一般由 ...