tensorflow包_在Keras和Tensorflow中使用深度卷积网络生成Meme(表情包)文本
作者?| dylan wenzlau
來源?|?Medium
編輯?|?代碼醫生團隊
本文介紹如何構建深度轉換網絡實現端到端的文本生成。在這一過程中,包括有關數據清理,訓練,模型設計和預測算法相關的內容。
第1步:構建訓練數據
數據集使用了Imgflip Meme Generator(一款根據文本生成表情包的工具)用戶的~100M公共memes標題。為了加速訓練并降低模型的復雜性,僅使用48個最受歡迎的Meme(表情包)和每個Meme(表情包)準確的20,000個字幕,總計960,000個字幕作為訓練數據。每個角色都會有一個訓練示例在標題中,總計約45,000,000個訓練樣例。這里選擇了角色級生成而不是單詞級別,因為Meme(表情包)傾向于使用拼寫和語法。此外字符級深度學習是單詞級深度學習的超集,因此如果有足夠的數據并且模型設計足以了解所有復雜性,則可以實現更高的準確性。如果嘗試下面的完成模型,還會看到char級別可以更有趣!
https://imgflip.com/memegenerator
以下是第一個Meme(表情包)標題是“制作所有memes”時的訓練數據。省略了從數據庫中讀取代碼并執行初始清理的代碼,因為它非常標準,可以通過多種方式完成。
training_data?=?[
????["000000061533??0??",?"m"],
????["000000061533??0??m",?"a"],
????["000000061533??0??ma",?"k"],
????["000000061533??0??mak",?"e"],
????["000000061533??0??make",?"|"],
????["000000061533??1??make|",?"a"],
????["000000061533??1??make|a",?"l"],
????["000000061533??1??make|al",?"l"],
????["000000061533??1??make|all",?"?"],
????["000000061533??1??make|all?",?"t"],
????["000000061533??1??make|all?t",?"h"],
????["000000061533??1??make|all?th",?"e"],
????["000000061533??1??make|all?the",?"?"],
????["000000061533??1??make|all?the?",?"m"],
????["000000061533??1??make|all?the?m",?"e"],
????["000000061533??1??make|all?the?me",?"m"],
????["000000061533??1??make|all?the?mem",?"e"],
????["000000061533??1??make|all?the?meme",?"s"],
????["000000061533??1??make|all?the?memes",?"|"],
????...?45?million?more?rows?here?...
]#?we'll?need?our?feature?text?and?labels?as?separate?arrays?later
texts?=?[row[0]?for?row?in?training_data]
labels?=?[row[1]?for?row?in?training_data]
像機器學習中的大多數事情一樣,這只是一個分類問題。將左側的文本字符串分類為~70個不同的buckets 中的一個,其中buckets 是字符。
解壓縮格式:
前12個字符是Meme(表情包)模板ID。這允許模型區分正在訓練它的48個不同的Meme(表情包)。字符串左邊用零填充,因此所有ID都是相同的長度。
0或1是被預測的當前文本框的索引,一般0是機頂盒和1是底盒,雖然許多記因是更復雜的。這兩個空格只是額外的間距,以確保模型可以將框索引與模板ID和Meme(表情包)文本區分開來。注意:至關重要的是卷積內核寬度(在本文后面看到)不比4個空格加上索引字符(也就是≤5)寬。
之后是meme的文本,用|作為文本框的結尾字符。
最后一個字符(第二個數組項)是序列中的下一個字符。
在訓練之前,數據使用了幾種清洗技術:
調整前導和尾隨空格,并用\s+單個空格字符替換重復的空格()。
應用最少10個字符的字符串長度,這樣就不會生成無聊的單字或單字母Memes(表情包文本)。
應用最大字符串長度為82個字符,因此不會生成超長表情包字符,因為模型將更快地訓練。82是任意的,它只是使整個訓練字符串大約100個字符。
將所有內容轉換為小寫以減少模型必須學習的字符數,并且因為許多Memes(表情包文本)只是全部大寫。
使用非ascii字符跳過meme標題可以降低模型必須學習的復雜性。這意味著特征文本和標簽都將來自一組僅約70個字符,具體取決于訓練數據恰好包含哪些ascii字符。
跳過包含豎線字符的meme標題,|因為它是特殊的文本框結尾字符。
通過語言檢測庫運行文本,并跳過不太可能是英語的meme標題。提高生成的文本的質量,因為模型只需要學習一種語言,相同的字符序列可以在多種語言中有意義。
跳過已添加到訓練集中的重復Memes(表情包文本)標題,以減少模型簡單記憶整個Memes(表情包文本)標題的機會。
數據現在已準備就緒,可以輸入神經網絡!
第2步:數據轉換
首先,在代碼中導入python庫:?
from?keras?import?Sequentialfrom?keras.preprocessing.sequence?import?pad_sequencesfrom?keras.callbacks?import?ModelCheckpointfrom?keras.layers?import?Dense,?Dropout,?GlobalMaxPooling1D,?Conv1D,?MaxPooling1D,?Embeddingfrom?keras.layers.normalization?import?BatchNormalizationimport?numpy?as?npimport?util??#?util?is?a?custom?file?I?wrote,?see?github?link?below
因為神經網絡只能對張量(向量/矩陣/多維數組)進行操作,所以需要對文本進行轉化。每個訓練文本將通過從數據中找到的約70個唯一字符的數組中用相應的索引替換每個字符,將其轉換為整數數組(等級1張量)。字符數組的順序是任意的,但選擇按字符頻率對其進行排序,以便在更改訓練數據量時保持大致一致。Keras有一個Tokenizer類,可以使用它(使用char_level = True),這里使用的是自己的util函數,因為它比Keras tokenizer更快。
#?output:?{'?':?1,?'0':?2,?'e':?3,?...?}
char_to_int?=?util.map_char_to_int(texts)#?output:?[[2,?2,?27,?11,?...],?...?]
sequences?=?util.texts_to_sequences(texts,?char_to_int)
labels?=?[char_to_int[char]?for?char?in?labels]
這些是數據按頻率順序包含的字符:
0etoains|rhl1udmy2cg4p53wf6b897kv."!?j:x,*"z-q/&$)(#%+_@=>;
接下來將填充帶有前導零的整數序列,因此它們的長度都相同,因為模型的張量數學要求每個訓練示例的形狀相同。(注意:可以在這里使用低至100的長度,因為文本只有100個字符,但希望以后所有的池操作都可以被2完全整除。)
SEQUENCE_LENGTH?=?128
data?=?pad_sequences(sequences,?maxlen=SEQUENCE_LENGTH)
最后將調整訓練數據并將其分為訓練和驗證集。改組(隨機化順序)確保數據的特定子集不總是用于驗證準確性的子集。將一些數據拆分成驗證集使能夠衡量模型在不允許它用于訓練的示例上的表現。
#?randomize?order?of?training?data
indices?=?np.arange(data.shape[0])
np.random.shuffle(indices)
data?=?data[indices]
labels?=?labels[indices]#?validation?set?can?be?much?smaller?if?we?use?a?lot?of?data
validation_ratio?=?0.2?if?data.shape[0]?1000000?else?0.02
num_validation_samples?=?int(validation_ratio?*?data.shape[0])
x_train?=?data[:-num_validation_samples]
y_train?=?labels[:-num_validation_samples]
x_val?=?data[-num_validation_samples:]
y_val?=?labels[-num_validation_samples:]
第3步:模型設計
這里選擇使用卷積網絡,在Keras上構建conv網絡模型的代碼如下:
EMBEDDING_DIM?=?16
model?=?Sequential()
model.add(Embedding(len(char_to_int)?+?1,?EMBEDDING_DIM,?input_length=SEQUENCE_LENGTH))
model.add(Conv1D(1024,?5,?activation='relu',?padding='same'))
model.add(BatchNormalization())
model.add(MaxPooling1D(2))
model.add(Dropout(0.25))
model.add(Conv1D(1024,?5,?activation='relu',?padding='same'))
model.add(BatchNormalization())
model.add(MaxPooling1D(2))
model.add(Dropout(0.25))
model.add(Conv1D(1024,?5,?activation='relu',?padding='same'))
model.add(BatchNormalization())
model.add(MaxPooling1D(2))
model.add(Dropout(0.25))
model.add(Conv1D(1024,?5,?activation='relu',?padding='same'))
model.add(BatchNormalization())
model.add(MaxPooling1D(2))
model.add(Dropout(0.25))
model.add(Conv1D(1024,?5,?activation='relu',?padding='same'))
model.add(BatchNormalization())
model.add(GlobalMaxPooling1D())
model.add(Dropout(0.25))
model.add(Dense(1024,?activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(0.25))
model.add(Dense(len(labels_index),?activation='softmax'))
model.compile(loss='sparse_categorical_crossentropy',?optimizer='rmsprop',?metrics=['acc'])
代碼步驟如下:
首先,模型使用Keras嵌入將每個輸入示例從128個整數的數組(每個表示一個文本字符)轉換為128x16矩陣。嵌入是一個層,它學習將每個字符轉換為表示為整數的最佳方式,而不是表示為16個浮點數的數組[0.02, ..., -0.91]。這允許模型通過在16維空間中將它們彼此靠近地嵌入來了解哪些字符的使用類似,并最終提高模型預測的準確性。
接下來,添加5個卷積層,每個層的內核大小為5,1024個過濾器,以及ReLU激活。從概念上講,第一個轉換層正在學習如何從字符構造單詞,后來的層正在學習構建更長的單詞和單詞鏈(n-gram),每個單詞都比前一個更抽象。
padding='same' 用于確保圖層的輸出尺寸與輸入尺寸相同,因為否則寬度5卷積會使內核的每一側的圖層尺寸減小2。
選擇1024作為濾波器的數量,因為它是訓練速度和模型精度之間的良好折衷,由試驗和錯誤確定。對于其他數據集,我建議從128個過濾器開始,然后將其增加/減少兩倍,以查看會發生什么。更多過濾器通常意味著更好的模型準確性,但訓練速度較慢,運行時預測較慢,模型尺寸較大。但是如果數據太少或過濾器太多,模型可能會過度擬合,精度會下降,在這種情況下,應該減少過濾器。
在測試尺寸為2,3,5和7之后選擇大小為5的卷積核。其中2和3的卷積確實更差, 7需要更多的參數,這會使訓練變慢。在研究中,其他人已經成功地使用了3到7種不同組合的卷積大小,大小為5的卷積核通常在文本數據上表現得相當不錯。
選擇ReLU激活是因為它快速,簡單,并且非常適用于各種各樣的用例。
在每個conv層之后添加批量標準化,以便基于給定批次的均值和方差對下一層的輸入參數進行標準化。深度學習工程師尚未完全理解這種機制,歸一化輸入參數可以提高訓練速度,并且由于消失/爆炸的梯度,對于更深的網絡變得更加重要。在每個轉換層之后添加一個Dropout層,以幫助防止該層簡單地記憶數據和過度擬合。Dropout(0.25)隨機丟棄25%的參數(將它們設置為零)。
在每個轉換層之間添加MaxPooling1D(2),以將128個字符的序列“擠壓”成下列層中的64,32,16和8個字符的序列。從概念上講,這允許卷積濾波器從更深層中的文本中學習更多抽象模式,因為在每個最大池操作將維度減少2倍之后,寬度5內核將跨越兩倍的字符。
在所有轉換圖層之后,使用全局最大合并圖層,它與普通的最大合并圖層相同,只是它會自動選擇縮小輸入尺寸以匹配下一圖層的大小。最后一層只是標準的密集(完全連接)層,有1024個神經元,最后是70個神經元,因為分類器需要為70個不同的標簽輸出概率。
model.compile步驟非常標準。RMSprop優化器是一個不錯的優化器,沒有嘗試為這個神經網絡改變它。loss=sparse_categorical_crossentropy告訴希望它優化的模型,以便在一組2個或更多類別(又名標簽)中選擇最佳類別。“稀疏”部分指的是標簽是0到70之間的整數,而不是長度為70的一個one-hot陣列。使用一個one-hot陣列作為標簽需要更多的內存,更多的處理時間,并且不會影響模型的準確性。不要使用一個one-hot標簽!
Keras有一個很好的model.summary()功能,可以查看模型:
_________________________________________________________________
Layer?(type)?????????????????Output?Shape??????????????Param?#
=================================================================
embedding_1?(Embedding)??????(None,?128,?16)???????????1136
_________________________________________________________________
conv1d_1?(Conv1D)????????????(None,?128,?1024)?????????82944
_________________________________________________________________
batch_normalization_1?(Batch?(None,?128,?1024)?????????4096
_________________________________________________________________
max_pooling1d_1?(MaxPooling1?(None,?64,?1024)??????????0
_________________________________________________________________
dropout_1?(Dropout)??????????(None,?64,?1024)??????????0
_________________________________________________________________
conv1d_2?(Conv1D)????????????(None,?64,?1024)??????????5243904
_________________________________________________________________
batch_normalization_2?(Batch?(None,?64,?1024)??????????4096
_________________________________________________________________
max_pooling1d_2?(MaxPooling1?(None,?32,?1024)??????????0
_________________________________________________________________
dropout_2?(Dropout)??????????(None,?32,?1024)??????????0
_________________________________________________________________
conv1d_3?(Conv1D)????????????(None,?32,?1024)??????????5243904
_________________________________________________________________
batch_normalization_3?(Batch?(None,?32,?1024)??????????4096
_________________________________________________________________
max_pooling1d_3?(MaxPooling1?(None,?16,?1024)??????????0
_________________________________________________________________
dropout_3?(Dropout)??????????(None,?16,?1024)??????????0
_________________________________________________________________
conv1d_4?(Conv1D)????????????(None,?16,?1024)??????????5243904
_________________________________________________________________
batch_normalization_4?(Batch?(None,?16,?1024)??????????4096
_________________________________________________________________
max_pooling1d_4?(MaxPooling1?(None,?8,?1024)???????????0
_________________________________________________________________
dropout_4?(Dropout)??????????(None,?8,?1024)???????????0
_________________________________________________________________
conv1d_5?(Conv1D)????????????(None,?8,?1024)???????????5243904
_________________________________________________________________
batch_normalization_5?(Batch?(None,?8,?1024)???????????4096
_________________________________________________________________
global_max_pooling1d_1?(Glob?(None,?1024)??????????????0
_________________________________________________________________
dropout_5?(Dropout)??????????(None,?1024)??????????????0
_________________________________________________________________
dense_1?(Dense)??????????????(None,?1024)??????????????1049600
_________________________________________________________________
batch_normalization_6?(Batch?(None,?1024)??????????????4096
_________________________________________________________________
dropout_6?(Dropout)??????????(None,?1024)??????????????0
_________________________________________________________________
dense_2?(Dense)??????????????(None,?70)????????????????71750
=================================================================
Total?params:?22,205,622
Trainable?params:?22,193,334
Non-trainable?params:?12,288
_________________________________________________________________
在調整上面討論的超參數時,關注模型的參數計數很有用,它大致代表模型的學習能力總量。
第4步:訓練
現在將讓模型訓練并使用“檢查點”來保存歷史和最佳模型,以便可以在訓練期間的任何時候檢查進度并使用最新模型進行預測。
#?the?path?where?you?want?to?save?all?of?this?model's?files
MODEL_PATH?=?'/home/ubuntu/imgflip/models/conv_model'#?just?make?this?large?since?you?can?stop?training?at?any?time
NUM_EPOCHS?=?48#?batch?size?below?256?will?reduce?training?speed?since#?CPU?(non-GPU)?work?must?be?done?between?each?batch
BATCH_SIZE?=?256#?callback?to?save?the?model?whenever?validation?loss?improves
checkpointer?=?ModelCheckpoint(filepath=MODEL_PATH?+?'/model.h5',?verbose=1,?save_best_only=True)#?custom?callback?to?save?history?and?plots?after?each?epoch
history_checkpointer?=?util.SaveHistoryCheckpoint(MODEL_PATH)#?the?main?training?function?where?all?the?magic?happens!
history?=?model.fit(x_train,?y_train,?validation_data=(x_val,?y_val),?epochs=NUM_EPOCHS,?batch_size=BATCH_SIZE,?callbacks=[checkpointer,?history_checkpointer])
這就是坐下來觀看神奇數字在幾個小時內上升的地方......
Train?on?44274928?samples,?validate?on?903569?samples
Epoch?1/4844274928/44274928?[==============================]?-?16756s?378us/step?-?loss:?1.5516?-?acc:?0.5443?-?val_loss:?1.3723?-?val_acc:?0.5891
Epoch?00001:?val_loss?improved?from?inf?to?1.37226,?saving?model?to?/home/ubuntu/imgflip/models/gen_2019_04_04_03_28_00/model.h5
Epoch?2/4844274928/44274928?[==============================]?-?16767s?379us/step?-?loss:?1.4424?-?acc:?0.5748?-?val_loss:?1.3416?-?val_acc:?0.5979
Epoch?00002:?val_loss?improved?from?1.37226?to?1.34157,?saving?model?to?/home/ubuntu/imgflip/models/gen_2019_04_04_03_28_00/model.h5
Epoch?3/4844274928/44274928?[==============================]?-?16798s?379us/step?-?loss:?1.4192?-?acc:?0.5815?-?val_loss:?1.3239?-?val_acc:?0.6036
Epoch?00003:?val_loss?improved?from?1.34157?to?1.32394,?saving?model?to?/home/ubuntu/imgflip/models/gen_2019_04_04_03_28_00/model.h5
Epoch?4/4844274928/44274928?[==============================]?-?16798s?379us/step?-?loss:?1.4015?-?acc:?0.5857?-?val_loss:?1.3127?-?val_acc:?0.6055
Epoch?00004:?val_loss?improved?from?1.32394?to?1.31274,?saving?model?to?/home/ubuntu/imgflip/models/gen_2019_04_04_03_28_00/model.h5
Epoch?5/481177344/44274928?[..............................]?-?ETA:?4:31:59?-?loss:?1.3993?-?acc:?0.5869
發現當訓練損失/準確性比驗證損失/準確性更差時,這表明該模型學習良好且不過度擬合。
如果使用AWS服務器進行訓練,發現最佳實例為p3.2xlarge。這使用了自2019年4月以來最快的GPU(Tesla V100),并且該實例只有一個GPU,因為模型無法非常有效地使用多個GPU。確實嘗試過使用Keras的multi_gpu_model,但它需要使批量大小更大,以實際實現速度提升,這可能會影響模型的收斂能力,即使使用4個GPU也幾乎不會快2倍。帶有4個GPU的p3.8xlarge的成本是4倍。
第5步:預測
現在有一個模型可以輸出meme標題中下一個字符應該出現的概率,但是如何使用它來實際創建一個完整的meme(表情包)標題?
基本前提是用想要為其生成文本的Memes(表情包標題)初始化一個字符串,然后model.predict為每個字符調用一次,直到模型輸出結束文本字符的|次數與文本框中的文本框一樣多次。對于上面看到的“X All The Y”memes,默認的文本框數為2,初始文本為:
"000000061533??0??"
考慮到模型輸出的70個概率,嘗試了幾種不同的方法來選擇下一個字符:
每次選擇得分最高的角色。這會生成非常單一的結果,因為它每次為給定的Meme(表情包)選擇完全相同的文本,并且它在Meme(表情包)中反復使用相同的單詞。”when you find out your friends are the best party”,它會一遍又一遍地吐出"X All The Y meme"。它喜歡在其他Meme(表情包)中使用"best"和"party"這兩個詞。
給每個角色一個被選中的概率等于模型給出的分數,但只有當分數高于某個閾值時(≥最高分的10%才適用于該模型)。這意味著可以選擇多個字符,但偏向更高的得分字符。這種方法成功地增加了多樣性,但較長的短語有時缺乏凝聚力。這是Futurama Frymemes中的一個:"not sure if she said or just put out of my day"。
給每個角色選擇相同的概率,但前提是它的分數足夠高(≥最高分的10%適用于此模型)。此外使用beam搜索在任何給定時間保留N個文本的運行列表,并使用所有角色分數的乘積而不是最后一個角色的分數。這需要花費N倍的時間來計算,但在某些情況下似乎可以提高句子的凝聚力。
這里選擇使用方法2,因為速度快,效果好。以下是一些隨機生成的例子:
?
在imgflip.com/ai-meme的48個Meme(表情包)中生成。
https://imgflip.com/ai-meme
使用方法2進行運行時預測的代碼如下。Github上的完整實現是一種通用的Beam搜索算法,因此只需將波束寬度增加到1以上即可啟用Beam搜索。
#?min?score?as?percentage?of?the?maximum?score,?not?absolute
MIN_SCORE?=?0.1
int_to_char?=?{v:?k?for?k,?v?in?char_to_int.items()}def?predict_meme_text(template_id,?num_boxes,?init_text?=?''):
??template_id?=?str(template_id).zfill(12)
??final_text?=?''for?char_count?in?range(len(init_text),?SEQUENCE_LENGTH):
????box_index?=?str(final_text.count('|'))
????texts?=?[template_id?+?'??'?+?box_index?+?'??'?+?final_text]
????sequences?=?util.texts_to_sequences(texts,?char_to_int)
????data?=?pad_sequences(sequences,?maxlen=SEQUENCE_LENGTH)
????predictions_list?=?model.predict(data)
????predictions?=?[]for?j?in?range(0,?len(predictions_list[0])):
??????predictions.append({'text':?final_text?+?int_to_char[j],'score':?predictions_list[0][j]
??????})
????predictions?=?sorted(predictions,?key=lambda?p:?p['score'],?reverse=True)
????top_predictions?=?[]
????top_score?=?predictions[0]['score']
????rand_int?=?random.randint(int(MIN_SCORE?*?1000),?1000)for?prediction?in?predictions:#?give?each?char?a?chance?of?being?chosen?based?on?its?scoreif?prediction['score']?>=?rand_int?/?1000?*?top_score:
????????top_predictions.append(prediction)
????random.shuffle(top_predictions)
????final_text?=?top_predictions[0]['text']if?char_count?>=?SEQUENCE_LENGTH?-?1?or?final_text.count('|')?==?num_boxes?-?1:return?final_text
在github中,該文檔對應的代碼如下:
https://github.com/dylanwenzlau/ml-scripts/tree/master/meme_text_gen_convnet
推薦閱讀
“Keras之父發聲:TF 2.0 + Keras 深度學習必知的12件事”
關于圖書
《深度學習之TensorFlow:入門、原理與進階實戰》和《Python帶我起飛——入門、進階、商業實戰》兩本圖書是代碼醫生團隊精心編著的 AI入門與提高的精品圖書。配套資源豐富:配套視頻、QQ讀者群、實例源碼、 配套論壇:http://bbs.aianaconda.com?。更多請見:https://www.aianaconda.com
總結
以上是生活随笔為你收集整理的tensorflow包_在Keras和Tensorflow中使用深度卷积网络生成Meme(表情包)文本的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python图形用户界面pyside_P
- 下一篇: python爬取下拉列表数据_Pytho