input不可编辑属性_谁不喜欢图文并茂呢:基于多模态信息的属性抽取
0. 前言
最近做屬性抽取,并且基于多模態(tài)信息(文本+圖片)那種,然后發(fā)現(xiàn)了一個比較經(jīng)典的論文“Multimodal Attribute Extraction”。正好就順著這個論文的思路,捋一下這個任務(wù),復(fù)現(xiàn)一下,再記錄一下實(shí)踐過程和想法
論文在 2017 年,提出了這么一個做多模態(tài)任務(wù)的框架,在現(xiàn)在來看感覺還挺經(jīng)典的,雖然具體方法放到今天來看平平無奇,沒什么高端操作,比不起現(xiàn)在的論文里各種眼花繚亂一通亂秀。但是出發(fā)總要從原點(diǎn)開始,中間有些想法和總結(jié),有點(diǎn)什么就記點(diǎn)什么
(NIPS 2017) Multimodal Attribute Extraction?arxiv.org1. 問題描述
什么是屬性抽取:現(xiàn)實(shí)世界的任何事物,都要靠若干屬性來修飾和描述,比如你買了一個肉夾饃,它的“肉料”是“牛肉”還是“豬肉”,它的“口味”是“麻辣”還是“不辣”,都需要被描述清楚,你才能做出決定想不想吃,要不要買
那到哪去獲取這些“屬性描述”呢?你可以看看菜單上寫沒寫,也可以直接問老板,簡單直接,但是這些“原始數(shù)據(jù)”不總是完美的。萬一老板今天心情不好,不樂意跟你說話,怎么辦。你就需要靠自己。你往冷柜一看,上面寫著是“牛肉”的,再往調(diào)料盤一看,沒有辣椒粉的罐子,就推理出應(yīng)該是“不辣”的。挺好,開心地點(diǎn)了一個吃,做了一次成功的“屬性抽取”,被自己的機(jī)智所折服
但是也有可能,你吃著發(fā)現(xiàn),是辣的,吐了。原來老板把辣椒粉裝在別的罐子里了。這個故事告訴我們,“屬性抽取”也是有風(fēng)險的,因?yàn)樵紨?shù)據(jù)也是有噪聲的
怎么做屬性抽取:兩種方式,“抽取式”和“生成式”。直接上圖
抽取式:就是抽取輸入文本中的字詞,組成預(yù)測的屬性值。也就是說,預(yù)測出的屬性值一定要在輸入側(cè)出現(xiàn)過
實(shí)現(xiàn)上,可以用上圖所示的序列標(biāo)注的方式,也可以做成類似 SQuAD 那種的 QA 問題,把屬性作為 query,把屬性值作為 answer,最終輸出兩個 index,取原文中兩個 index 之間的字詞作為屬性值
另外,用序列標(biāo)注的方式做的話,也有不同的實(shí)現(xiàn)方式,比如不把待預(yù)測的屬性名(Query Attribute)作為輸入,而是作為輸出。也就是說,不告訴模型想預(yù)測哪個屬性,模型你覺得你能預(yù)測到哪個屬性就輸出哪個屬性。這種實(shí)現(xiàn)方式也包括幾種,比如可以做成一個多任務(wù)學(xué)習(xí),一個子任務(wù)做分類預(yù)測屬性名,一個子任務(wù)做序列標(biāo)注標(biāo)記屬性值;也可以只做一個序列標(biāo)注任務(wù),屬性名靠標(biāo)簽本身標(biāo)記,例如把原本的“B”標(biāo)簽擴(kuò)展成“B-口味”“B-肉料”等多個標(biāo)簽,每個標(biāo)簽對應(yīng)一種屬性。五花八門,百川入海,殊途同歸。各種方式都有人做,具體文章不一一列舉了
列一個最近看到抽取式方法的論文吧,方法非常簡單明了,一眼就懂
Scaling Up Open Tagging from Tens to Thousands: Comprehension Empowered Attribute Value Extraction from Product Title. ACL2019生成式:就是直接生成屬性值,而這個屬性值不一定在輸入文本中出現(xiàn),只要模型在訓(xùn)練數(shù)據(jù)中見過就 ok
實(shí)現(xiàn)上,可以直接在一個“大而完整的屬性詞表”上做分類任務(wù),也可以用 seq2seq 的方式把屬性值分割成字詞序列輸出。但是無論如何,都是需要一個“大而完整的屬性詞表”,也就是屬性值一定是可枚舉可窮盡的。代表論文就比如最開始提到的那篇
兩種方式優(yōu)缺點(diǎn)比較:
抽取式
- 只能抽取在輸入文本中出現(xiàn)過的屬性值,導(dǎo)致很多屬性不可做
- 預(yù)測屬性值一定在輸入中出現(xiàn)過,具有一定可解釋性,準(zhǔn)確性也更高,不會亂預(yù)測
生成式
- 只能預(yù)測可枚舉的高頻屬性,導(dǎo)致很多屬性值不可做
- 預(yù)測出來的屬性值沒有可解釋性,在實(shí)際業(yè)務(wù)中,預(yù)測出來結(jié)果也不一定敢用(比如模型預(yù)測出來這個肉夾饃用的是牛肉,但是你也不一定敢信,又看不出來,萬一是假的)
當(dāng)然也可以融各自所長,做一個既能抽取又能生成的模型:比如基于 CopyNet 或者 Pointer-Generator,針對不同的輸入情況,可以自由在“抽取模式”和“生成模式”之間進(jìn)行切換。感覺聽上去還比較高級吧,相關(guān)文章沒看到過,感興趣可以試一下,一條光明的小道指給你們了,還沒畢業(yè)的可以拿去發(fā)論文哈,不用客氣
背景故事就講這些吧。下面就是具體的模型和實(shí)踐過程
代碼地址
- 數(shù)據(jù)集:MAE-dataset https://rloganiv.github.io/mae/
- 環(huán)境:Python3.6, Tensorflow 1.12
- 代碼:
原始數(shù)據(jù)文件太大了,包括文本信息與圖片文件,需要的話可以自己下載,把下好的數(shù)據(jù)解壓到 data/origin/train 以及 data/origin/test 目錄下,然后通過 data/process_data.py 把數(shù)據(jù)處理后生成到 data/train 和 data/test 下即可
但是出于客戶為先的服務(wù)態(tài)度,我隨機(jī) sample 了一些數(shù)據(jù)留在了 data/train 以及 data/test 目錄下,不用費(fèi)力去下載原始數(shù)據(jù)就能跑 demo,就是為了給你們那種 git clone 完就能原地起飛的暢爽體驗(yàn)
不用客氣
2. 模型及實(shí)現(xiàn)
先講模型,最后說數(shù)據(jù)處理
模型很簡單小巧,看圖,一眼就懂
用 CNN 得到文本的編碼向量;用預(yù)訓(xùn)練的 VGG 提取圖片的特征向量,作為輸入,然后接一層全連接層將得到圖片的編碼向量;直接用 Embedding 矩陣編碼待預(yù)測屬性;最后用一個融合層(Fusion Layer)將以上三個向量融合在一起,再接一層全連接,就得到預(yù)測的屬性值了
然后上代碼,先說輸入
self.inputs_seq = tf.placeholder(tf.int32, [None, None], name="inputs_seq") self.inputs_seq_len = tf.placeholder(tf.int32, [None], name="inputs_seq_len") self.inputs_image = tf.placeholder(tf.float32, [None, 3, img_embedding_size], name="inputs_image") self.inputs_attr = tf.placeholder(tf.int32, [None], name="inputs_attr") self.outputs_value = tf.placeholder(tf.int32, [None], name='outputs_value')這里就提一下 inputs_image,這個是輸入的圖片信息,每個商品最多 3 張圖片,每張圖片被VGG 預(yù)先提取為一個 4096 維(img_embedding_size=4096)的特征向量,具體提取方法后面數(shù)據(jù)處理部分再說
接下來就是按照圖上的結(jié)構(gòu)搭模型
with tf.variable_scope('word_embedding'):embedding_matrix = tf.get_variable("embedding_matrix", [vocab_size_word, txt_embedding_size], dtype=tf.float32)txt_embedded = tf.nn.embedding_lookup(embedding_matrix, self.inputs_seq) # B * S * Dwith tf.variable_scope('txt_encoder'):conv = tf.layers.conv1d(txt_embedded,filters=txt_hidden_size,kernel_size=5,strides=1,padding="valid") # B * (S-5+1) * Dpool = tf.reduce_max(conv, axis=1)pool_drop = tf.layers.dropout(pool, 0.5)txt_contents = tf.layers.dense(pool_drop, txt_hidden_size, activation="relu") # B * Dwith tf.variable_scope('img_encoder'):img_input = tf.layers.dropout(self.inputs_image, 0.5)img_embedded = tf.layers.dense(img_input, img_hidden_size, activation="relu") # B * 3 * Dimg_contents = tf.reduce_max(img_embedded, axis=1) # B * Dwith tf.variable_scope('attr_encoder'):embedding_matrix = tf.get_variable("embedding_matrix", [vocab_size_attr, attr_embedding_size], dtype=tf.float32)attr_contents = tf.nn.embedding_lookup(embedding_matrix, self.inputs_attr) # B * Dwith tf.variable_scope('fusion_layer'):if fusion_mode == "concat":#fusion_all = tf.concat([txt_contents, img_contents, attr_contents], axis=1)fusion_all = tf.concat([txt_contents, attr_contents], axis=1)fusion_contents = tf.layers.dense(fusion_all, fusion_hidden_size) # B * Delif fusion_mode == "gmu":fusion_ta = tf.concat([txt_contents, attr_contents], axis=1)fusion_contents_ta = tf.layers.dense(fusion_ta, fusion_hidden_size) # B * Dfusion_ia = tf.concat([img_contents, attr_contents], axis=1)fusion_contents_ia = tf.layers.dense(fusion_ia, fusion_hidden_size) # B * Dfusion_ti = tf.concat([txt_contents, img_contents], axis=1)gate = tf.nn.sigmoid(tf.layers.dense(fusion_ti, 1)) # B * 1fusion_contents = gate * fusion_contents_ta + (1 - gate) * fusion_contents_ia # B * D這里說下這個多模態(tài)融合層(Fusion Layer)。相比單獨(dú)的 NLP 任務(wù)或者 CV 任務(wù),多模態(tài)任務(wù)也沒什么特別的地方,無非是多了一項(xiàng)輸入。所以,它的亮點(diǎn)就在于,多模態(tài)信息之間如何進(jìn)行“交互”。把它倆“交”好了,讓人眼前一亮,就能讓人覺得你這個新穎、高級、給力
現(xiàn)在常用的交互方式有很多,比如(1)直接 concat 然后接一層全連接(2)二者之間做一層 Attention(3)類似 VL-BERT 那種,把文本輸入和圖片輸入前后拼接在一起,通過 Transformer 去學(xué)他們之間的關(guān)系,本質(zhì)上還是學(xué) Attention,等等的
這篇論文里還提供了一種方式:gated multimodal unit(gmu),如代碼中所示。這個 gmu 還挺有意思的。商品的有些屬性可以通過圖片看出來(比如顏色),而有些屬性不行,必須要從文字中讀出來(比如內(nèi)存大小),這個 gmu 就是基于 gate 機(jī)制,讓模型自己去學(xué),什么時候看圖,什么時候讀字。實(shí)現(xiàn)上也比較簡單,看圖就懂
這種 gate 機(jī)制聽上去就很高級。但是就這個數(shù)據(jù)集上而言,實(shí)際效果遠(yuǎn)不如直接 concat。任憑你花里胡哨,不如我簡單粗暴
還有一些其他的交互方式,比如我們最近在做的一種,展示效果還是挺炫的。本質(zhì)上還是計(jì)算 Attention,但不是把圖片整個壓縮成一整個向量,而是分成不同區(qū)域,保留每個區(qū)域的表征向量,這樣在文本中提到某個屬性時,可以觀察模型是否可以在圖片上找到這個屬性對應(yīng)的區(qū)域。比如下圖里,文本里提到“小翻領(lǐng)”,模型就更加關(guān)注圖片上衣領(lǐng)那塊區(qū)域。分享一下圖一樂,具體細(xì)節(jié)不細(xì)講跑題了
Multimodal Joint Attribute Prediction and Value Extraction for E-commerce Product. Zhu T, Wang Y, Li H, et al. EMNLP 2020.接下來是 loss。loss 本身沒啥可講的,但是這里還有點(diǎn)意思。這篇論文用的是基于“余弦相似度”的 loss,一般常用的是“交叉熵”loss。兩者公式不一樣,但是出發(fā)點(diǎn)都是相同的,感覺應(yīng)該沒什么區(qū)別。但是從實(shí)驗(yàn)結(jié)果來看,有時候 loss 的影響可太大了
先上兩種方式的實(shí)現(xiàn)代碼
# D 是 predicted value 的向量維度 # V 是全部 value 的詞表大小 distribute_matrix = tf.get_variable("distribute_matrix", [fusion_hidden_size, vocab_size_value], dtype=tf.float32) # D * V# 下面是用 cos_similarity 來得到 predicted values 以及計(jì)算 loss dot_part1 = tf.expand_dims(fusion_contents, axis=-1) # B * D * 1 dot_part2 = tf.expand_dims(distribute_matrix, axis=0) # 1 * D * V dot = tf.reduce_sum(dot_part1 * dot_part2, axis=1) # B * V norm_part1 = tf.expand_dims(tf.sqrt(tf.reduce_sum(fusion_contents * fusion_contents, axis=1)), axis=-1) # B * 1 norm_part2 = tf.expand_dims(tf.sqrt(tf.reduce_sum(distribute_matrix * distribute_matrix, axis=0)), axis=0) # 1 * V norm = norm_part1 * norm_part2 # B * V norm = tf.clip_by_value(norm, clip_value_min=1e-8, clip_value_max=1e5) # 截?cái)嘁幌路乐狗帜笧? sims = dot / norm # B * V # 即 predicted value 和全部詞表中 value 的余弦相似度結(jié)果 outputs = tf.argmax(sims, 1, name="outputs") # outputs, 即最終的 predicted values pos_value_onehot = tf.one_hot(self.outputs_value, vocab_size_value) # B * V loss_pos = sims * pos_value_onehot # B * V loss_pos = tf.square(1 - tf.reduce_sum(loss_pos, axis=1)) # 正例的 loss loss_neg = sims * (1 - pos_value_onehot) # B * V loss_neg = tf.clip_by_value(loss_neg, clip_value_min=0, clip_value_max=1) # B * V loss_neg = tf.reduce_sum(tf.square(loss_neg), axis=1) # 負(fù)例的 loss loss = loss_pos + loss_neg # B * V loss = tf.reduce_mean(loss) # B# 下面是用 cross_entropy 來得到 predicted values 以及計(jì)算 loss logits = tf.matmul(fusion_contents, distribute_matrix) # B * V probs = tf.nn.softmax(logits, name="probs") # B * V outputs = tf.argmax(probs, 1, name="outputs") # outputs, 即最終的 predicted values # loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=self.outputs_value) pos_value_onehot = tf.one_hot(self.outputs_value, vocab_size_value) # B * V probs = tf.clip_by_value(p, clip_value_min=1e-8, clip_value_max=1) # 截?cái)嘁幌路乐筶og里為0 loss = -1 * tf.reduce_sum(pos_value_onehot * tf.log(probs), axis=-1) # B * V loss = tf.reduce_mean(loss) # Bdistribute_matrix 是一個 D * V 的矩陣,放在余弦相似度那里,可以理解成把詞表中每個 value 做成一個 D 維的向量,儲存在整個矩陣中,然后逐個去跟預(yù)測出的 value vector 計(jì)算相似度,看哪個相似度最高就選哪個;放在交叉熵那里,可以理解成一個 dense 層,把預(yù)測出的 value vector 映射到 V 維的概率分布上,每一維對應(yīng)詞表中一個 value 的概率
交叉熵這里是手寫的,便于理解,實(shí)際應(yīng)用中可以直接換成 tf 現(xiàn)成的函數(shù) sparse_softmax_cross_entropy_with_logits,效果要好一些,里面做了很多優(yōu)化的
這里為啥要提一下這個 loss 呢,因?yàn)閮煞N loss 感覺上出發(fā)點(diǎn)都是一樣的,都是提高正例上的相似度或概率,打壓負(fù)例,應(yīng)該區(qū)別不大。論文里是每條數(shù)據(jù)只采樣一個負(fù)例,聽上去不太合理,我試了一下效果也不好,然后就把所有的負(fù)例 loss 都算進(jìn)來(reduce_sum那里)。但是實(shí)驗(yàn)結(jié)果顯示,交叉熵要好太多了,學(xué)習(xí)速度也不是一個量級的
至于啥原因,很難講,可能是機(jī)器玄學(xué)吧。有可能是交叉熵這種 y*log(p) 的形式本身非常適合分類任務(wù),也可能是計(jì)算交叉熵前的 softmax 非常適合反向傳播。我亂猜的。有機(jī)會請交叉熵喝酒讓他親自給我嘮一嘮
單方面宣布交叉熵是 loss 屆的王,萬物皆可交叉熵
最后上一下實(shí)驗(yàn)結(jié)果對比,直接引用論文里的表了。單用圖片的話(Image Baseline),效果還比不上直接寫規(guī)則(Most-Common Value)。文字+圖片的話(Multimodal Baseline)效果會比單用文字(Text Baseline)好一點(diǎn)點(diǎn)點(diǎn)點(diǎn)。說明核心還是文字,圖片只是配角
由此出發(fā)再多說一句,不了解啊,瞎猜,是不是現(xiàn)在有很多的多模態(tài)任務(wù),把圖片或者視頻或者語音的信息引入之后,效果上并沒有什么明顯提升,其實(shí)都是表面光鮮的配角
但是高級就完事了。秀就完事了
3. 數(shù)據(jù)預(yù)處理
關(guān)于數(shù)據(jù)怎么預(yù)處理,都可以從代碼里找到,感興趣的話看一眼代碼就可以
這里主要提一下怎么對圖片進(jìn)行預(yù)處理?,F(xiàn)在一般多模態(tài)任務(wù)都會先用一個預(yù)訓(xùn)練模型把圖片提取為特征向量吧,如果把原始圖片作為輸入的話,模型還要另外搭好多層 CNN 去學(xué)習(xí)圖片特征提取,對模型來說壓力太大了。預(yù)訓(xùn)練模型可以用 VGG 或者 Resnet 或者其他的,可以直接把一張圖片直接壓縮成一個 D 維向量,也可以把圖片分割為 Nx * Ny 塊,輸出形狀為(Nx * Ny * D)的結(jié)果
具體實(shí)現(xiàn)方式我沒有找到 tensorflow 版本的,就用的 pytorch 官方提供的接口torchvision.models,網(wǎng)上可以直接搜到教程就不細(xì)說了。運(yùn)行代碼時會自動下載預(yù)訓(xùn)練好的模型,如果下載較慢可以本地下好然后傳到它的默認(rèn)下載目錄下,一般是 ~/.torch/models 這種目錄,具體路徑可以看輸出提示
然后假設(shè)一共有 N 張圖片,分別用預(yù)訓(xùn)練模型跑一邊得到一個 N*D 的矩陣,儲存下來。然后訓(xùn)練的時候,按照每條輸入數(shù)據(jù)對應(yīng)的 image index 去矩陣中取,比如第一個商品有 3 張圖片,分別是第 1,3,128 號圖片,就去從這個矩陣中抽
大家如果平時在預(yù)處理或者訓(xùn)練過程中,有更科學(xué)的數(shù)據(jù)處理方式,非常歡迎指導(dǎo)交流,普渡眾生,善莫大焉,獨(dú)樂樂不如眾樂樂
就這樣,打完收工
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的input不可编辑属性_谁不喜欢图文并茂呢:基于多模态信息的属性抽取的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 两根网线两个路由器一台电脑2根网线如何用
- 下一篇: 全局变量求平均分最高分最低分_想去江苏读