深度解析Pytorch Seq2Seq Toturials(1)
Seq2Seq模型,又稱之為Encoder-Decoder模型。主要應用在機器翻譯(Machine Translation)、圖文轉換(Image Caption)等領域。這個模型2014年發表,並不算是一個特別新的模型,但是相對於傳統的深度學習網路模式(輸入-輸出-loss-bp),這種類型的網路範式仍然是一個很特殊的東西,不仔細研究很容易迷惑。
這個model大體應該長成下圖這個樣子。
我估計凡是讀過論文的人,應該都不會陌生這個圖,而且直觀上講,甚至這個圖還有點好懂!大體思路就是跑一個RNN做encoder,把一句話轉化成一個語義向量,然後用一個RNN做decoder,把語義向量解碼成一個句子。
OK,道理我都懂,但是這個奇葩的東西怎麼搞呢?
仔細觀察,其實不難發現,Encoder部分其實就是一個正常的RNN系的模型,沒有太大不同,幾乎完全一樣,所以可以照搬。問題是後面那個decoder,這也是一個RNN,但是好像又和普通RNN有區別,什麼區別呢,這種區別我總結如下:
- 普通RNN走第一步之前,有一個初始的hide state,一般取一個0向量,代表之前並沒有隱藏狀態,所以從這一步才是輸入的開始。現在,decoder的初始hide state是encoder生成的語義向量。
- 上一步小兒科,還好理解,實現上處理也不複雜。問題是,你decoder的RNN每一個時間步上總需要有個輸入吧,原來encoder的時候,你可以一個個單詞輸入,decoder的時候可沒有單詞輸入啊,恰恰相反,我們目標就是要預測這些單詞,他們應該是輸出!
看到這裡,我估計大概基本80%的人已經明白問題的癥結所在了吧。
OK,帶著這種疑惑,我看了一下Pytorch官方的Toturial里是怎麼實現這個事情的。
下面我整理一下seq2seq的pytorch Toturial代碼實現,細節均在代碼中注釋給出。
交代一下任務,這是一個機器翻譯的代碼,翻譯發生在英文和法文之間。
數據預處理
先做一下數據的處理吧,數據的格式大概長成這個樣子。
很清晰,有木有,一句英文對著一句法文,中間 隔開。
Toturial里首先做了這樣一件事情。
SOS_token = 0 # 開始標記EOS_token = 1 # 結束標記class Lang: def __init__(self, name): self.name = name self.word2index = {} # 單詞->序號 self.word2count = {} # 單詞數 self.index2word = {0: "SOS", 1: "EOS"} # 序號->單詞 self.n_words = 2 # Count SOS and EOS def addSentence(self, sentence): # 讀進一個句子,構造語言類,實際通過不斷調用下面讀進單詞實現的 for word in sentence.split( ): self.addWord(word) def addWord(self, word): if word not in self.word2index: self.word2index[word] = self.n_words self.word2count[word] = 1 self.index2word[self.n_words] = word self.n_words += 1 else: self.word2count[word] += 1def normalizeString(s): s = unicodeToAscii(s.lower().strip()) s = re.sub(r"([.!?])", r" 1", s) s = re.sub(r"[^a-zA-Z.!?]+", r" ", s) return sdef readLangs(lang1, lang2, reverse=False): print("Reading lines...") # Read the file and split into lines lines = open(data/%s-%s.txt % (lang1, lang2), encoding=utf-8). read().strip().split(
) # Split every line into pairs and normalize pairs = [[normalizeString(s) for s in l.split( )] for l in lines] # 注意,這裡的pairs實際上就是一個存有多個語言對的list,其中每個元素又是一個list,存著英文一句,法文一句兩個元素 # Reverse pairs, make Lang instances if reverse: pairs = [list(reversed(p)) for p in pairs] input_lang = Lang(lang2) output_lang = Lang(lang1) else: input_lang = Lang(lang1) output_lang = Lang(lang2) return input_lang, output_lang, pairs
建立一個語言類,比如這裡就是法語和英語兩個類,這個class的主要任務就是統計一下語料庫里每個單詞出現的次數,建立起單詞到序號的索引,序號到單詞的反向索引,因為我們輸入模型的都需要時數值,這相當於我們做了一個單詞到序號的雙向的映射。
normlizeString主要是清洗數據中的非字元部分,統一格式,正則啥的就不細說了。
readLangs是要把兩種語言的文本數據載入進來。這裡可以支持互相翻譯,reverse就是起這個功能,別的沒了。
生成 language 對象
精心構造的語言class沒用啊,所以下一步就是把語言類實例化。
MAX_LENGTH = 10eng_prefixes = ( "i am ", "i m ", "he is", "he s ", "she is", "she s", "you are", "you re ", "we are", "we re ", "they are", "they re ")def filterPair(p): return len(p[0].split( )) < MAX_LENGTH and len(p[1].split( )) < MAX_LENGTH and p[1].startswith(eng_prefixes)def filterPairs(pairs): return [pair for pair in pairs if filterPair(pair)]def prepareData(lang1, lang2, reverse=False): input_lang, output_lang, pairs = readLangs(lang1, lang2, reverse) print("Read %s sentence pairs" % len(pairs)) pairs = filterPairs(pairs) # 過濾句子長度10以上的 print("Trimmed to %s sentence pairs" % len(pairs)) print("Counting words...") for pair in pairs: # 構造language instance input_lang.addSentence(pair[0]) output_lang.addSentence(pair[1]) print("Counted words:") print(input_lang.name, input_lang.n_words) print(output_lang.name, output_lang.n_words) return input_lang, output_lang, pairsinput_lang, output_lang, pairs = prepareData(eng, fra, True)print(random.choice(pairs))
注意這裡規定了一個MAX_LENGTH = 10,意思是我們拋棄了長句,只看長度10以內的句子。為了簡單起見,目前只用eng_prefixes中出現的句子,也就是目標翻譯「我是」「你是」之類的簡單句。畢竟是個Toturial嘛。
到這一步,我們就獲得了兩個語言的language instance,這其中包含了每個單詞的次數統計,以及每種語言單詞和序號的映射關係。
[1406.1078] Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation
Translation with a Sequence to Sequence Network and Attention
推薦閱讀:
※讓我們一起來學習CNTK吧
※python3機器學習經典實例-第五章構建推薦引擎20
※降維技術解析:PCA, t-SNE and Auto Encoders
※IBM機器學習CTO給2190知乎網友的一封信
※機器學習速成課程 使用 TensorFlow API