本文思路来源于fsauor2018,仅作为个人笔记。
数据预处理
作者将原始数据处理成了4个文件,但是这部分代码并没有提供。
- train.json / validation.json / testa.json
文件的每一行格式如下:
{"id": "0", "content": "吼吼吼 , 萌 死 人 的 棒棒糖 , 中 了 大众 点评 的 霸王餐 , 太 可爱 了 。 一直 就 好奇 这个 棒棒 糖 是 怎么 个 东西 , 大众 点评 给 了 我 这个 土老 冒 一个 见识 的 机会 。 看 介绍 棒棒 糖 是 用 <place> 糖 做 的 , 不 会 很 甜 , 中间 的 照片 是 糯米 的 , 能 食用 , 真是 太 高端 大气 上档次 了 , 还 可以 买 蝴蝶 结扎口 , 送 人 可以 买 礼盒 。 我 是 先 打 的 卖家 电话 , 加 了 微信 , 给 卖家传 的 照片 。 等 了 几 天 , 卖家 就 告诉 我 可以 取 货 了 , 去 <place> 那 取 的 。 虽然 连 卖家 的 面 都 没 见到 , 但是 还是 谢谢 卖家 送 我 这么 可爱 的 东西 , 太 喜欢 了 , 这 哪 舍得 吃 啊 。", "location_traffic_convenience": "-2", "location_distance_from_business_district": "-2", "location_easy_to_find": "-2", "service_wait_time": "-2", "service_waiters_attitude": "1", "service_parking_convenience": "-2", "service_serving_speed": "-2", "price_level": "-2", "price_cost_effective": "-2", "price_discount": "1", "environment_decoration": "-2", "environment_noise": "-2", "environment_space": "-2", "environment_cleaness": "-2", "dish_portion": "-2", "dish_taste": "-2", "dish_look": "1", "dish_recommendation": "-2", "others_overall_experience": "1", "others_willing_to_consume_again": "-2"}
在这一部分,作者将使用了ltp分词工具进行了分词,并且使用了ltp做了命名实体识别,之后将其中的地名和机构名分别用<place>
,<org>
代替。其他部分都和原始数据保持了一致,对于测试文件的label是未知的,因此用空字符串代替。
- 词汇表
作者选取了训练集中最常见的5万个词。
前三个分别是: <unk>
: 出现次数比较少的词<sos>
: 文本的开头<eos>
: 文本的结尾,也用作padding- Embedding file
这是一个glove格式的embedding file,作者使用的使用搜狗新闻训练的。 - Label file
包含了20个种类的英文名。
训练细节
Tokenize
如果分词的结果不在word2id中,则先进行分割,然后再在word2id中找,再找不到才设定为<unk>
。句子开头和结尾设为<sos>
,<eos>
。
Embedding dropout
首先是对embedding matrix
做dropout
:
mask = tf.nn.dropout(tf.ones([vocab_size]), keep_prob=1-embedding_dropout)*(1-embedding_dropout)
mask = tf.expand_dims(mask, 1)
embedding = mask * embedding
其次对embedding的结果做dropout:
inputs_embedded = tf.nn.dropout(inputs_embedded, keep_prob=dropout_keep_prob)
Focal loss
作者给出的focal loss
关键代码
L = - labels * tf.pow(1-y_pred, gamma) * tf.log(y_pred)
Focal loss原论文的思路
- 针对类别不均衡问题:
类别不均衡时,显然样本数量多(特别特别多)的那一类会对总的loss起决定作用,因此loss的下降方向也就是为了优化该类服务,这就会导致样本数量少的那一类学习的不够充分。
该问题可以通过加上一个控制权重来解决问题,对于属于少数类别的样本,增大 α \alpha α即可。
C E ( p t ) = − α t l o g ( p t ) CE(p_t) = -\alpha_tlog(p_t) CE(pt)=−αtlog(pt)
然而,这仅仅解决了正负样本之间的平衡问题,并没有区分易分/难分样本。 - 针对易分/难分样本
F L ( p t ) = − ( 1 − p t ) γ l o g ( p t ) FL(p_t) = -(1-p_t)^\gamma log(p_t) FL(pt)=−(1−pt)γlog(pt)
显然样本越易分, p t p_t pt越大,则它贡献的loss就越小,相对来说,难分样本所占的比重就会变大。
在实际使用中,通常结合上述两种思路,即采用如下形式:
F L ( p t ) = − α t ( 1 − p t ) γ l o g ( p t ) FL(p_t)=-\alpha_t(1-p_t)^\gamma log(p_t) FL(pt)=−αt(1−pt)γlog(pt)
本文作者采用 α = 0.25 , γ = 2 \alpha=0.25,\gamma=2 α=0.25,γ=2效果最好。
模型架构
模型可以看成是一个seq2seq模型,下面是一些细节:
- 一个Embedding layer上面叠加了三个双向LSTM层作为encoder
- 第二层和第三层Bi-LSTM直接添加了残差连接
- 解码器最终的输出是每一层输出的加权和。Scalars和weight都是从ELMO中直接copy过来的可学习的变量。
- 解码器由一个简单的LSTM cell和注意力机制构成,步长为20步,每一步为一个label得到一个输出。解码器的输入为学习到的embedding
- 解码器的输出再喂到两个全连接层得到最终的logits。