一.概述
LEAM(Joint Embedding of Words and Labels for Text Classification),是Guoyin Wang等提出的一種文本分類新方法,看論文標題就可以發現,該方法主要是構建 "類別標籤 (label) 與詞向量 (word-embedding)的聯合嵌入",使用注意力機制 (Attention) 作爲 laebl 與 word-embedding 溝通的橋樑。通常其他算法如TextCNN,FastText,TextRCNN,HAN等,只把類別標籤 (label)作用於網絡的最後一層,即計算loss等的時候使用,LEAM等大約可以看成是引入 類別標籤(label) 信息吧。
類別標籤 (label) 嵌入信息是有效的,在圖像領域,圖像與文本的多任務領域,圖像中識別文本等。特別是少樣本 (zero-shot) 問題,嵌入空間中捕獲的標籤相關性, 可以在某些類不可見時增強預測效果。在NLP領域,類別標籤 (label) 與 詞 (word)的關係,之前一般認爲沒什麼大用,如 2015年論文 Pte: Predictive text embedding through large-scale heterogeneous text networks 構建的詞-詞、詞-文檔、詞-類別標籤異構網絡,試圖構建各種關係獲取更多的信息;
又如2017年等的論文MTLE: Multi-Task Label Embedding for Text Classification,使用文本分類中的多任務學習利用隱式相關任務之間的相關性以提取共同特徵併產生績效收益,其提出的一種多任務標籤嵌入的方法,將文本分類中的標籤轉化爲語義標籤向量,從而將原始任務轉換爲向量匹配任務。其實現了多任務標籤嵌入的無監督、有監督和半監督模型,所有這些模型都利用了任務之間的語義相關性,以便於在涉及更多任務時進行縮放和轉移。
LEAM網絡與其上的方法不同,在BERT興起後,Tranformer成爲迄今爲止最好的NLP特徵抽取工具,LEAM也果斷使用注意力機制Attention構建label與word-embedding的關係。注意力機制(Attention)的表示方法有三種:點乘相似度、權重和餘弦相似度,如Transformer使用的是KVQ的權重法,又如使用的是,這種一步到位、端到端的構建方法,使得LEAM達到了較好的效果。此外,LEAM的副產品還可以突出醫學文本的關鍵詞信息。
github地址: https://github.com/yongzhuo/Keras-TextClassification
二.LEAM原理圖
2.1 LEAM網絡核心架構
l
2.2 基本計算
2.2.1 C * V / G
C是一個需要訓練的weight (tendorflow用tf.add_weight),C的轉置維度爲詞向量維度(embed_size) * 類別數(label) ,
V是普通的word-embedding,維度爲 批尺寸(batch-size) * 文本長度(len_max) * 詞向量維度 (embed_size),
G是需要訓練的矩陣,維度爲 批尺寸(batch-size) * 文本長度(len_max) * 類別數(label)
2.2.2 其他操作
三.LEAM代碼實現
3.1 github地址: https://github.com/yongzhuo/Keras-TextClassification
LEAM代碼比較麻煩,用到了tensorflow,構建了一個專門的Layer
from keras.regularizers import L1L2, Regularizer
# from keras.engine.topology import Layer
from keras.layers import Layer
from keras import backend as K
import tensorflow as tf
class CVG_Layer(Layer):
def __init__(self, embed_size, filter, label, **kwargs):
self.embed_size = embed_size
self.filter = filter
self.label = label
super().__init__(** kwargs)
def build(self, input_shape):
self._filter = self.add_weight(name=f'filter_{self.filter}',
shape=(self.filter, self.label, 1, 1),
regularizer=L1L2(0.00032),
initializer='uniform',
trainable=True)
self.class_w = self.add_weight(name='class_w',
shape=(self.label, self.embed_size),
regularizer=L1L2(0.0000032),
initializer='uniform',
trainable=True)
self.b = self.add_weight(name='bias',
shape=(1,),
regularizer=L1L2(0.00032),
initializer='uniform',
trainable=True)
super().build(input_shape)
def call(self, input):
# C * V / G
# l2_normalize of x, y
input_norm = tf.nn.l2_normalize(input) # b * s * e
class_w_relu = tf.nn.relu(self.class_w) # c * e
label_embedding_reshape = tf.transpose(class_w_relu, [1, 0]) # e * c
label_embedding_reshape_norm = tf.nn.l2_normalize(label_embedding_reshape) # e * c
# C * V
G = tf.contrib.keras.backend.dot(input_norm, label_embedding_reshape_norm) # b * s * c
G_transpose = tf.transpose(G, [0, 2, 1]) # b * c * s
G_expand = tf.expand_dims(G_transpose, axis=-1) # b * c * s * 1
# text_cnn
conv = tf.nn.conv2d(name='conv', input=G_expand, filter=self._filter,
strides=[1, 1, 1, 1], padding='SAME')
pool = tf.nn.relu(name='relu', features=tf.nn.bias_add(conv, self.b)) # b * c * s * 1
# pool = tf.nn.max_pool(name='pool', value=h, ksize=[1, int((self.filters[0]-1)/2), 1, 1],
# strides=[1, 1, 1, 1], padding='SAME')
# max_pool
pool_squeeze = tf.squeeze(pool, axis=-1) # b * c * s
pool_squeeze_transpose = tf.transpose(pool_squeeze, [0, 2, 1]) # b * s * c
G_max_squeeze = tf.reduce_max(input_tensor=pool_squeeze_transpose, axis=-1, keepdims=True) # b * s * 1
# divide of softmax
exp_logits = tf.exp(G_max_squeeze)
exp_logits_sum = tf.reduce_sum(exp_logits, axis=1, keepdims=True)
att_v_max = tf.div(exp_logits, exp_logits_sum)
# β * V
x_att = tf.multiply(input, att_v_max)
x_att_sum = tf.reduce_sum(x_att, axis=1)
return x_att_sum
def compute_output_shape(self, input_shape):
return None, K.int_shape(self.class_w)[1]
希望對你有所幫助!