2019-02 Classification in NLP

每年在分类上的paper不断,我主要罗列一些我觉得还行的分类模型吧。

1. Self-Attention based Bidirection LSTM for Text Classification

2. Transformer for Text Classification

 以上两种模型的思想原理都在前面讲过:https://www.jianshu.com/p/f169eaec9b8b

3. Adversarial training methods for supervised Text Classification

以上paper地址:Adversarial training methods for supervised Text Classification
加了对抗的loss后使得分类的效果似乎提升的很大,所以这里就将这模型罗列在这里。下图就是该模型的框图:

The model with perturbed embeddings.png

上图模型能够有效的地方有两个关键点:

  1. 在输入的时候加上一个扰动 r ,使得模型更具备健壮性。
  2. 这里,词向量如果是trainable的,所以为了防止模型学习到较大膜的词向量而抵消扰动的作用,需要将词向量进行归一化。
Normalization Equation.png

该模型的loss有两个部分loss组成:
L_{total}=L_{cls}+L_{adv}=-p(y|x;\theta) -p(y|x+r_{adv};\hat{\theta}),对于这个公式和以下公式的理解为:当在 Word Embedding 上添加的 Perturbation 使得L_{adv}很大时,由于计算 L_{adv}的参数是不参与梯度计算的,也就是说 模型 (LSTM 和最后的 Dense Layer) 的 Weight 和 Bias 的改变并不会影响L_{adv},模型只能通过改变 Word Embedding 来努力降低L_{adv}

Cost Equation.png

当然,r 的计算并不是那么简单,但可以通过如下的公式等效的表示:

The computation of r .png
   def _add_perturbation(self, embedded, loss):
        """Adds gradient to embedding"""
        grad, = tf.gradients(loss,
                             embedded,
                             aggregation_method=tf.AggregationMethod.EXPERIMENTAL_ACCUMULATE_N)
        grad = tf.stop_gradient(grad)  ## 用来阻挡计算 grad里的参数更新,也就是说一旦加入了perturbation就只会更新embedded这个参数
        perturb = scale_l2(grad, self.epsilon)
        return embedded + perturb

总的来说,该模型作用是:

  1. 对抗训练可以使得句子的语义不会因为微小的变动而大相径庭,也就是说有着相似语法的但是有不同语义的句子向量空间会分离的较大。
  2. 不能把单纯的加微小扰动的操作看作是加上noise,因为加噪的正则化效果是远差于加对抗loss产生的正则化效果的。
  3. 词向量在训练后会变得更加精准。

4. RMDL: Random Multimodel Deep Learning for Classification

以上paper地址:https://arxiv.org/abs/1805.01890
这个模型效果是远超于以上模型的,我就拿来仔细看了半天,结果是篇含金量不高的文章,就是一个Emsemble的基本思想,在DNN,RNN,CNN中进行类别的多数投票。毕竟Emsemble大法好啊,确实可以秒杀很多单一模型的效果。其框图如下:

RMDL.png

不过怎么说,其中的Keras代码还是值得借鉴的!

5. Deep Pyramid Convolutional Neural Networks for Text Categorization (DPCNN)

Three Models.png

上图中可以看出,DPCNN既保村了一般TextCNN的conv层的处理类型方式,又采取了ResNet的残差连接方式。这是深层的word-level的创新之作。其每个部分的结构描述如下:

  1. region embedding:
      首先,这里不是完全和TextCNN的conv层一样,是不保留词序 (即使用词袋模型) ,即首先对N-gram中的N个词的embedding取均值得到一个size=D的向量,然后设置一组size=D的一维卷积核对该N-gram进行卷积。
      其次,再是类似bert的预训练方式,通过预测下一句的embedding的方式进行无监督的训练。
  2. 3 conv;250:对上述的embedding每3个做conv,但是feature map不变就是250。但是得做等长的卷积。
  3. 1/2 pooling: 就是对上述的conv结果每2个进行max-pooling,得到以前embedding的一半长度。对第2,3步的过程描述图如下:
  4. Shortcut connections with pre-activation: 就是借鉴resnet的方式进行残差的连接。


    image.png

Note: paper中阐述的值得注意的地方有很多,比如

  1. 对于conv来说,一般我们习惯先将x作weighting之后,再作activation。但是paper中确说先对x作activation,再作weighting效果会更好。
  2. 对于开始的region embedding的选取方法也是要进行实验甄选的,可以bow input或者bag-of-n-gram input或者就是单个词向量。
  3. 该模型设计的很巧妙,其实在region embedding之后就不是用conv层在抽feature了。而是通过使用conv层的并行化优势,充当了fc层进行分类层的设计,这样作用就是捕捉长距离特征之间的关系,通过加weight,可以使得分类器更加精准。

代码如下:

    def inference(self):
        # 词向量映射
        isTraining = True
        with tf.name_scope("embedding"):
            embedding = tf.get_variable("embedding", [self.vocab_size, self.embedding_dim])
            embedding_inputs = tf.nn.embedding_lookup(embedding, self.input_x)
            embedding_inputs = tf.expand_dims(embedding_inputs, axis=-1)  # [None,seq,embedding,1]
            # region_embedding  # [batch,seq-3+1,1,250]
            region_embedding = tf.layers.conv2d(embedding_inputs,
                                                self.num_filters,
                                                [self.kernel_size, self.embedding_dim])

            pre_activation = tf.nn.relu(region_embedding, name='preactivation')

        with tf.name_scope("conv3_0"):
            conv3 = tf.layers.conv2d(pre_activation,
                                     self.num_filters,
                                     self.kernel_size,
                                     padding="same",
                                     activation=tf.nn.relu)
            conv3 = tf.layers.batch_normalization(conv3, training=isTraining)  ##

        with tf.name_scope("conv3_1"):
            conv3 = tf.layers.conv2d(conv3,
                                     self.num_filters,
                                     self.kernel_size,
                                     padding="same",
                                     activation=tf.nn.relu)
            conv3 = tf.layers.batch_normalization(conv3, training=isTraining)

        # resdul
        conv3 = conv3 + region_embedding  # [batch,seq-3+1,1,250]
        with tf.name_scope("pool_1"):
            pool = tf.pad(conv3, paddings=[[0, 0], [0, 1], [0, 0], [0, 0]])  ## 在那一维添加
            pool = tf.nn.max_pool(pool, [1, 3, 1, 1], strides=[1, 2, 1, 1], padding='VALID')

        with tf.name_scope("conv3_2"):
            conv3 = tf.layers.conv2d(pool, self.num_filters, self.kernel_size,
                                     padding="same", activation=tf.nn.relu)
            conv3 = tf.layers.batch_normalization(conv3, training=isTraining)

        with tf.name_scope("conv3_3"):
            conv3 = tf.layers.conv2d(conv3, self.num_filters, self.kernel_size,
                                     padding="same", activation=tf.nn.relu)
            conv3 = tf.layers.batch_normalization(conv3, training=isTraining)

        # resdul
        conv3 = conv3 + pool
        pool_size = int((self.seq_length - 3 + 1) / 2)
        conv3 = tf.layers.max_pooling1d(tf.squeeze(conv3, [2]), pool_size, 1)
        conv3 = tf.squeeze(conv3, [1])  # [batch,250]
        conv3 = tf.nn.dropout(conv3, self.keep_prob)

        with tf.name_scope("score"):
            # classify
            self.logits = tf.layers.dense(conv3, self.num_classes, name='fc2')
            self.y_pred_cls = tf.argmax(tf.nn.softmax(self.logits), 1, name="pred")

        with tf.name_scope("loss"):
            # 损失函数,交叉熵
            cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
                logits=self.logits, labels=self.input_y)

            # l2_loss = tf.losses.get_regularization_loss()
            self.loss = tf.reduce_mean(cross_entropy, name="loss")
            # self.loss += l2_loss

            # optim
            self.optim = tf.train.AdamOptimizer(
                learning_rate=self.learning_rate).minimize(self.loss)
        with tf.name_scope("accuracy"):
            # 准确率
            correct_pred = tf.equal(tf.argmax(self.input_y, 1), self.y_pred_cls)
            self.acc = tf.reduce_mean(tf.cast(correct_pred, tf.float32), name="acc")

6. 对分类效果影响大的trick

  1. 关于分词器:确保分词器与词向量表中的token是match的,不然会有很多的OOV问题,即使分的再好又有什么用呢?下面就说下怎么去处理吧。
      1.在已知预训练词向量的分词器时,就直接使用该分词器。
      2.在未知预训练词向量的分词器时,要么用多个分词器去下游任务尝试,看看哪个表现更好;要么就用自己的分词器去预训练一份好的词向量。
      Note: 需要注意的是大小写问题、OOV的定义问题等都会影响下游任务的效果。

  2. 关于中文字向量
     如果只想用char-level的向量,就得预训练字向量,并且要把窗口开大一些,不要直接使用word-level的窗口大小。

  3. 关于数据集噪声
     数据集噪声分为两种:x内部的噪声 (文本是比较随意的言语、不严谨等) ;y的噪声 (有些样本可能是被标错或者很难定义是哪一类,具有二义性时)。
     第1种噪声的处理是:先用char-level的向量和word-level的向量进行效果的对比,如果char-level更好就直接使用;否则就需要提高word-level的向量效果,那就是使用FastText训练一份词向量,其中需要将char ngram的窗口设置为1-2左右,这样为了让模型捕获错别字 (例如“似乎”写成了“是乎”,可以让这些错别字一下子语义回到了一起,一定程度上对抗了分词器产生的噪声)。
     第2种噪声的处理是:做标签平滑的效果很差。所以首先忽略噪声训练好一个模型,再将该模型去预测其中的训练集和验证集,将那些高置信的错误样本进行badcase的总结,最后要么发现一定的规律写一个脚本批量纠错标注,要么就删除错误的标注样本。(其实这里就是Active Learning的思想)

  4. 关于baseline的选择
     个人觉得其实可以TextCNN起手,这个作为baseline其实一点也不会差;其次就是用Transformer再去尝试,总之RNN最好别用,因为在实际项目中毕竟时间性能也是考量的一个大标准。

  5. Dropout层加在哪里
     一句话:word embedding层后、pooling层后、FC层后。可以先保持概率统一,有时间再去微调不同层的Dropout。

  6. 关于二分类
     二分类问题一定要用sigmoid作为输出层的激活函数吗?其实未必,实测中用包含两类的softmax函数会带来零点几个点的提升,虽然有点玄学。

  7. 关于多分类
     可以先用N个二分类问题进行拆解原多分类问题,在tf中有现成的API哦:tf.nn.sigmoid_cross_entropy_with_logits

  8. 关于类别不均衡问题
     如果是类别比例是1:9这种比例以下的话,就不用管它,因为模型这点的健壮性还是有的。但是如果类别比例相差太大,也就是一个batch中都是同一个样本,那么就需要均衡了!

9. 加深特征提取层抑或加深分类层?
 这个问题目前来看,分两种情况:一种是特征是否隐藏很深,如果很深则特征提取层需要加深;否则就加深分类层(一般是全连接层)看看。而对于上述的DPCNN则是在已有抽取到的feature上面精准的设计Conv层来替换分类层,可以解决全连接层因为训练过慢的问题。

7. 未完待续~

参考文献:

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

推荐阅读更多精彩内容

  • 格列佛孤注一掷地起航了,本打算找一个无人居住的小岛,依靠自己的劳动来维持基本的生活需要。经过几个小时的航程,到达了...
    严亭玉阅读 535评论 0 0
  • 驱车前往 五台山 阅众山风景 观各路神仙 古刹昭光千野碧 史林满目九天收 古道幽深入台怀 白塔底下几多情 吾问僧人...
    立正站好叫哥阅读 279评论 0 4
  • 从前,我以为演戏并没有什么了不起的,不就是动动脸耍耍嘴皮子,并没有好赞赏的。 后来呢看了一些报道,听了一些导演的见...
    Anna聪阅读 248评论 0 0
  • 有一天啊,宝宝 001 有这个念头,出于一本书蔡庚永的《有一天啊,宝宝》 这本书淘自苏州平江路的一家旧书店 我的宝...
    瑾熙吉娜阅读 671评论 0 0
  • 我期待我的食指早点好,马上要训练长嘴壶¯\_(ツ)_/¯
    雨木目_b4b0阅读 160评论 1 0