NLP中各框架对变长序列的处理全解
?PaperWeekly 原創 ·?作者|海晨威
學校|同濟大學碩士生
研究方向|自然語言處理
在 NLP 中,文本數據大都是變長的,為了能夠做 batch 的訓練,需要 padding 到相同的長度,并在實際訓練中忽略 padding 部分的影響。
在不同的深度學習框架中,對變長序列的處理,本質思想都是一致的,但具體的實現方式有較大差異,下面針對 Pytorch、Keras 和 TensorFlow 三大框架,以 LSTM 模型為例,說明各框架對 NLP 中變長序列的處理方式和注意事項。
PyTorch
在 pytorch 中,是用的 torch.nn.utils.rnn 中的 pack_padded_sequence 和 pad_packed_sequence 來處理變長序列,前者可以理解為對 padded 后的 sequence 做 pack(打包/壓緊),也就是去掉 padding 位,但會記錄每個樣本的有效長度信息;后者是逆操作,對 packed 后的 sequence 做 pad,恢復到相同的長度。
def?pack_padded_sequence(input,?lengths,?batch_first=False,?enforce_sorted=True):...if?enforce_sorted:sorted_indices?=?Noneelse:lengths,?sorted_indices?=?torch.sort(lengths,?descending=True)sorted_indices?=?sorted_indices.to(input.device)batch_dim?=?0?if?batch_first?else?1input?=?input.index_select(batch_dim,?sorted_indices)...不過在使用過程中,要格外注意 pack_padded_sequence 的 enforce_sorted 參數和 pad_packed_sequence 的 total_length 參數。
1.1 pack_padded_sequence
下面是 pack_padded_sequence 函數的部分 Pytorch 源碼,input 就是輸入的一個 batch 的 tensor,lengths 是這個 batch 中每個樣本的有效長度。
在 pack_padded_sequence 處理之后,會得到一個 PackedSequence 的數據,其除了記錄 Tensor data 之外,還會記錄 batch_sizes, sorted_indices 和 unsorted_indices,其中 batch_sizes 是將輸入按照有效長度排序之后,每個時間步對應的 batch 大小,后面會有例子;sorted_indices 就是對輸入 lengths 排序后的索引,unsorted_indices 是用來將排序數據恢復到原始順序的索引。
在 pack_padded_sequence 中,enforce_sorted 默認設置為 True,也就是說輸入的 batch 數據要事先按照長度排序,才能輸入,實際上,更簡單的方式是,將其設置為 False,從上面的代碼中也可以看出,Pytorch 會自動給我們做排序。
注:torch1.1 及之后才有 enforce_sorted 參數,因此 torch1.1 之后才有自動排序功能。
一個簡單的例子:
# input_tensor shape:batch_size=2,time_step=3,dim=1 input_tensor?=?torch.FloatTensor([[4,?0,?0],?[5,?6,?0]]).resize_(2,?3,?1) seq_lens?=?torch.IntTensor([1,?2]) x_packed?=?nn_utils.rnn.pack_padded_sequence(input_tensor,?seq_lens,?batch_first=True,?enforce_sorted=False)輸出的 x_packed 為:
PackedSequence(data=tensor([[5.],[4.],[6.]]),?batch_sizes=tensor([2,?1]),?sorted_indices=tensor([1,?0]),?unsorted_indices=tensor([1,?0]))在上面的例子中,首先,經過 pack_padded_sequence 內部按有效長度逆序排列之后,輸入數據會變成:
[[5,?6,?0], [4,?0,?0]]PackedSequence 中的 data 是按照 time_step 這個維度,也就是按列來記錄數據的,但是不包括 padding 位
該圖僅作為理解參考,圖片來自:
https://www.cnblogs.com/lindaxin/p/8052043.html
batch_sizes 記錄的每列有幾個數據是有效的,也就是每列有效的 batch_size 長度,但是不包括為 0 的長度,因此上面例子中,x_packed 的 batch_sizes=tensor([2, 1]),因此,每個 time_step 只需要傳入對應 batch_size 個數據即可,可以減少計算量。
要注意的是,batch_sizes 這個 tensor 的長度是 2,而 input_tensor 的 time_step 是 3,因為 batch_sizes 不包含都是 padding 的時間步,也就是上面的第三列,因此后面的 pad_packed_sequence 要注意設置 total_length 參數。
1.2 pad_packed_sequence
下面是 pad_packed_sequence 函數的部分 Pytorch 源碼,輸入 sequence 是? PackedSequence 型數據。pad_packed_sequence 實際上就是做一個 padding 操作和根據索引恢復數據順序操作。
def?pad_packed_sequence(sequence,?batch_first=False,?padding_value=0.0,?total_length=None):max_seq_length?=?sequence.batch_sizes.size(0)if?total_length?is?not?None:max_seq_length?=?total_length...這里要注意的一個參數是 total_length,它是 sequence 需要去被 padding 的長度,我們期望的一般都是 padding 到和輸入序列一樣的 time_step 長度 ,但是PackedSequence 型數據并沒有記錄這個數據,因此它用的是 sequence.batch_sizes.size(0),也就是 batch_sizes 這個 tensor 的長度。
上面已經提到,batch_sizes 不包含都是 padding 的時間步,這樣,如果整個 batch 中的每條記錄有都做padding,那 batch_sizes 這個 tensor 的長度就會小于 time_step ,就像上面代碼中的例子。
這時如果沒有設置 total_length,pad_packed_sequence 就不會 padding 到我們想要的長度。
可能你在實際使用時,不設置 total_length 參數也沒有出現問題,那大概率是因為你的每個 batch 中,都有至少一條記錄沒有 padding 位,也就是它的每一步都是有效位,那 sequence.batch_sizes.size(0) 就等于 time_step。
1.3 使用方式
為了方便使用,這里將 pack_padded_sequence,LSTM 和 pad_packed_sequence 做了一個封裝,參數和原始 LSTM 一樣,唯一的區別是使用中要輸入 seq_lens 數據。
class?MaskedLSTM(Module):def?__init__(self,?input_size,?hidden_size,?num_layers=1,?bias=True,?batch_first=False,?dropout=0.,?bidirectional=False):super(MaskedLSTM,?self).__init__()self.batch_first?=?batch_firstself.lstm?=?LSTM(input_size,?hidden_size,?num_layers=num_layers,?bias=bias,batch_first=batch_first,?dropout=dropout,?bidirectional=bidirectional)def?forward(self,?input_tensor,?seq_lens):#?input_tensor?shape:?batch_size*time_step*dim?,?seq_lens:?(batch_size,)??when?batch_first?=?Truetotal_length?=?input_tensor.size(1)?if?self.batch_first?else?input_tensor.size(0)x_packed?=?pack_padded_sequence(input_tensor,?seq_lens,?batch_first=self.batch_first,?enforce_sorted=False)y_lstm,?hidden?=?self.lstm(x_packed)y_padded,?length?=?pad_packed_sequence(y_lstm,?batch_first=self.batch_first,?total_length=total_length)return?y_padded,?hidden小總結:
使用 pack_padded_sequence 和 pad_packed_sequence 之后,LSTM 輸出對應的 padding 位是全 0 的,隱藏層輸出 (h_n,c_n) 都是不受 padding 影響的,都是 padding 前最后一個有效位的輸出,而且對單向/雙向 LSTM 都是沒有影響的,因為 padding 位不參與運算,即減少了不必要的計算,又避免了 padding 位對輸出的影響。
Keras
在 keras 中,自帶有 Masking 層,簡單方便,使用了一個 mask 操作則可以貫穿后面的整個模型,實際的過程是把一個布爾型的 mask 矩陣一直往下游傳遞下去,當然這個矩陣的維度會根據當前層的維度情況重新調整,以使其能在下游層中被使用。
確實方便,但也因此丟失了靈活性,如果使用了 mask,則后面層都要支持mask,否則會報異常,這對于一些不支持 mask 的層,例如 Flatten、AveragePooling1D 等等,并不是很友好。
keras 中對于變長序列的處理,一般使用 Masking 層,如果需要用到 Embedding 層,那可以直接在 Embedding 中設置 mask_zero=True,就不需要再加 Masking 層了,但本質上都是建了布爾型的 mask 矩陣并往下游傳遞下去。
下面是 Masking 和 Embedding 層的定義:
Masking(mask_value=0.,input_shape=(time_step,feature_size)) Embedding(input_dim,?output_dim,?mask_zero=False,?input_length=None)下面是 Embedding 層中的 mask 計算函數,如果 mask_zero 設置為 True,那這里會計算 mask 矩陣并往后傳遞,如果要繼續深入其傳遞的機制,建議看 keras 源碼,也可以參考一下這個:keras 源碼分析之 Layer [1]
#?Embedding?層中的mask計算函數 def?compute_mask(self,?inputs,?mask=None):if?not?self.mask_zero:return?Noneoutput_mask?=?K.not_equal(inputs,?0)return?output_mask不過要注意的一點是,mask_zero 設置為 True,輸入通過 Embedding 后,padding 位所對應的向量并不是全 0,仍然是一個隨機的向量,和 mask_zero 的值沒有關系,mask_zero 只是影響是否計算 mask 矩陣。但是有了 mask 矩陣之后,padding 位都不會被計算,因此,其對應向量的值并不重要。
2.1 使用方式
input?=?keras.layers.Input((time_step,feature_size)) mask?=?keras.layers.Masking(mask_value=0,?input_shape=(time_step,feature_size))(input) lstm_output?=?keras.layers.LSTM(hidden_size,?return_sequences=True)(mask) model?=?Model(input,?lstm_output)或:
keras 模型中,Masking 之后的層,只要支持 mask,都不用再手動創建 mask 了,當然,如果是自己定義的層,要支持 mask,需要設置 supports_masking=True,并實現自己的 compute_mask 函數。
要注意的是,和 pytorch、TF 有些不一樣的地方,對于有了 Masking 層之后的 LSTM,padding 位的輸出不會是全 0,而是最后一位有效位的輸出,也就是 padding 位輸出都復制了最后有效位的輸出。
Embedding 層和 Masking 層都有 mask 功能,但與 Masking 層不同的是,Embedding 它只能過濾 0,不能指定其他字符。
TensorFlow
在 TF (tf 1.x) 中是通過 dynamic_rnn 來實現變長序列的處理,它和 pytorch 的 pack_padded_sequence 一樣,也有 sequence_length 參數,但它相對比 pytorch 更方便,不用手動去 pack 和 pad,只要傳遞 sequence_length 參數,其他都由 dynamic_rnn 來完成。
但是 TF 中 dynamic_rnn 計算的循環次數仍然是 time_steps 次,并沒有帶來計算效率上的提升。sequence length 的作用只是在每個序列達到它的實際長度后,把后面時間步的輸出全部置成零、狀態全部置成實際長度那個時刻的狀態。
這一點可以參考:
https://www.zhihu.com/question/52200883
3.1 使用方式
#?靜態圖定義部分 basic_cell?=?tf.nn.rnn_cell.LSTMCell(hidden_size) X?=?tf.placeholder(tf.float32,?shape=[None,?time_step,?dim]) seq_length?=?tf.placeholder(tf.int32,?[None]) outputs,?states?=?tf.nn.dynamic_rnn(basic_cell,?X,?dtype=tf.float32,?sequence_length=seq_length)以上是從應用和代碼的角度,介紹了 Pytorch、Keras 和 TensorFlow 三大框架對變長數據的處理和使用方式,但這不僅僅適用于 NLP 領域,只是在 NLP 中變長數據更為常見,希望能幫助你在工程實踐中更好地去處理變長的數據。
參考文獻
[1]https://blog.csdn.net/u012526436/article/details/98206560
更多閱讀
#投 稿?通 道#
?讓你的論文被更多人看到?
如何才能讓更多的優質內容以更短路徑到達讀者群體,縮短讀者尋找優質內容的成本呢?答案就是:你不認識的人。
總有一些你不認識的人,知道你想知道的東西。PaperWeekly 或許可以成為一座橋梁,促使不同背景、不同方向的學者和學術靈感相互碰撞,迸發出更多的可能性。?
PaperWeekly 鼓勵高校實驗室或個人,在我們的平臺上分享各類優質內容,可以是最新論文解讀,也可以是學習心得或技術干貨。我們的目的只有一個,讓知識真正流動起來。
?????來稿標準:
? 稿件確系個人原創作品,來稿需注明作者個人信息(姓名+學校/工作單位+學歷/職位+研究方向)?
? 如果文章并非首發,請在投稿時提醒并附上所有已發布鏈接?
? PaperWeekly 默認每篇文章都是首發,均會添加“原創”標志
?????投稿郵箱:
? 投稿郵箱:hr@paperweekly.site?
? 所有文章配圖,請單獨在附件中發送?
? 請留下即時聯系方式(微信或手機),以便我們在編輯發布時和作者溝通
????
現在,在「知乎」也能找到我們了
進入知乎首頁搜索「PaperWeekly」
點擊「關注」訂閱我們的專欄吧
關于PaperWeekly
PaperWeekly 是一個推薦、解讀、討論、報道人工智能前沿論文成果的學術平臺。如果你研究或從事 AI 領域,歡迎在公眾號后臺點擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。
總結
以上是生活随笔為你收集整理的NLP中各框架对变长序列的处理全解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为手机找回网站登录(华为手机找回登陆入
- 下一篇: 苹果发布 iOS / iPadOS 18