:從對抗訓(xùn)練到Gumbel-Softmax的完整指南)
簡介面向深度學(xué)習(xí)與時間序列預(yù)測學(xué)習(xí)者這套資源提供了一份基于生成對抗網(wǎng)絡(luò)實現(xiàn)蔬菜銷量“菜票號碼”生成與序列預(yù)測的完整代碼工程。內(nèi)容覆蓋數(shù)據(jù)加載、生成器與判別器網(wǎng)絡(luò)構(gòu)建、損失函數(shù)定義、訓(xùn)練及測試環(huán)節(jié)可用于理解生成對抗網(wǎng)絡(luò)在數(shù)值型序列數(shù)據(jù)上的對抗訓(xùn)練過程并可作為銷量預(yù)測、數(shù)據(jù)增強(qiáng)或異常檢測等任務(wù)的實驗基準(zhǔn)。壓縮包共含七個文件以六個腳本文件和一個電子表格數(shù)據(jù)文件為主整體體積僅六十七千字節(jié)結(jié)構(gòu)精簡便于快速閱讀與二次開發(fā)。代碼模塊劃分清晰訓(xùn)練入口、網(wǎng)絡(luò)定義和數(shù)據(jù)讀取均有對應(yīng)腳本也便于替換為其他序列數(shù)據(jù)或調(diào)整網(wǎng)絡(luò)超參數(shù)。當(dāng)前已有一百五十八人學(xué)習(xí)使用其中既包含可直接運行的訓(xùn)練主程序也提供樣例數(shù)據(jù)與模塊化腳本適合入門生成對抗網(wǎng)絡(luò)與序列預(yù)測相結(jié)合的研究與實驗。 把GAN和彩票號碼放在一起很多人第一反應(yīng)是這能預(yù)測下期中獎號先把結(jié)論放這不能。嚴(yán)格來說彩票開獎是獨立隨機(jī)事件任何模型都預(yù)測不了下一期的具體號碼。但如果把目標(biāo)從預(yù)測開獎結(jié)果換成學(xué)習(xí)歷史號碼分布并生成相似樣本這件事就從玄學(xué)變成了一個正兒八經(jīng)的生成式AI練手項目。我最近做的就是這件事用GAN網(wǎng)絡(luò)生成彩票號碼以常見的數(shù)字型彩票規(guī)則為例訓(xùn)練一個生成器讓它學(xué)會歷史開獎號碼的長什么樣再產(chǎn)出新的號碼組合。整個項目做完GAN的生成器、判別器、損失函數(shù)、訓(xùn)練穩(wěn)定性這些核心概念算是親手驗證了一遍踩了不少坑也積累了一些可復(fù)用的經(jīng)驗。這篇文章就把完整過程拆開講清楚包括數(shù)據(jù)怎么處理、模型怎么搭、訓(xùn)練中那些讓人抓狂的問題怎么排查。適合想練手GAN但不想跑圖像、又希望項目有點趣味性的朋友。代碼思路基于PyTorch看完可以直接改改跑起來。1. 項目動機(jī)這到底是個什么項目1.1 先搞清楚GAN在這里扮演什么角色GAN是一個生成模型核心是生成器和判別器的對抗生成器從隨機(jī)噪聲出發(fā)嘗試生成以假亂真的樣本判別器負(fù)責(zé)區(qū)分輸入是真實數(shù)據(jù)還是生成數(shù)據(jù)。兩者在對抗中共同進(jìn)步最終生成器學(xué)到的分布會逼近真實分布。放到彩票號碼場景里歷史開獎號碼就是真實數(shù)據(jù)分布生成器的目標(biāo)是產(chǎn)出和真實開獎號碼看起來沒區(qū)別的號碼組合判別器則努力分辨哪些是歷史開獎、哪些是生成器編的。訓(xùn)練穩(wěn)定后生成器輸出的號碼組合在統(tǒng)計特征上應(yīng)該接近真實開獎分布。這里必須強(qiáng)調(diào)一個關(guān)鍵認(rèn)知GAN學(xué)的是分布不是因果關(guān)系。它能告訴你歷史開獎號碼長這樣但它完全不知道下一期會不會開出某個號。把它當(dāng)隨機(jī)樣本生成器沒問題當(dāng)預(yù)測工具就是方向錯了。1.2 為什么選GAN而不是其他生成模型做這個項目時我對比過幾條技術(shù)路線。VAE也可以學(xué)分布但VAE生成的樣本傾向于模糊、集中在均值附近對離散的彩票號碼來說很容易出現(xiàn)每個號都差不多的情況缺少趣味性。擴(kuò)散模型效果確實好但訓(xùn)練和采樣成本高用在這個小場景里屬于殺雞用牛刀。GAN在這個項目里的優(yōu)勢在于對抗訓(xùn)練天然促進(jìn)生成樣本的多樣性模式崩塌可以通過技巧緩解而且訓(xùn)練過程非常直觀——看著判別器和生成器的loss互相博弈很容易理解生成模型的本質(zhì)。GAN處理離散數(shù)據(jù)有一個天然難點生成器的輸出經(jīng)過argmax轉(zhuǎn)成離散號碼后梯度無法回傳。這一點我在后面專門用了一節(jié)來解釋怎么解決也是整個項目最核心的技術(shù)點。2. 數(shù)據(jù)準(zhǔn)備號碼的分布藏在細(xì)節(jié)里2.1 明確玩法規(guī)則與數(shù)據(jù)字段做數(shù)據(jù)之前先把業(yè)務(wù)規(guī)則定義清楚。我以常見的雙色球玩法為例從1到33中選擇6個紅球號碼從1到16中選擇1個藍(lán)球號碼紅球號碼按升序排列。這樣一組數(shù)據(jù)就是一條樣本標(biāo)簽就是歷史第幾期。數(shù)據(jù)量方面雙色球從2003年開售到現(xiàn)在歷史數(shù)據(jù)就有3000多期這個規(guī)模對深度學(xué)習(xí)來說很小但作為GAN的訓(xùn)練集在不追求極端效果的前提下已經(jīng)夠用。我直接用的公開歷史開獎數(shù)據(jù)集CSV格式字段包括期號、開獎日期、6個紅球號碼、1個藍(lán)球號碼。2.2 數(shù)據(jù)清洗的三個關(guān)鍵動作數(shù)據(jù)清洗是這類項目的隱形門檻很多新手在這里翻車。第一步做完整性校驗檢查每一行期號是否重復(fù)、號碼是否缺失發(fā)現(xiàn)重復(fù)期次直接刪除保持?jǐn)?shù)據(jù)干凈。第二步做范圍校驗紅球必須在1到33區(qū)間內(nèi)藍(lán)球必須在1到16區(qū)間內(nèi)超出就是臟數(shù)據(jù)。這里有個容易被忽略的細(xì)節(jié)紅球是按升序存儲的但模型并不關(guān)心開獎順序所以升序本身沒毛病不用打亂。清洗完之后我做了個分布統(tǒng)計確認(rèn)了各個號碼在歷史數(shù)據(jù)中的出現(xiàn)頻率。為什么做這一步因為GAN訓(xùn)練完以后你需要一個標(biāo)準(zhǔn)答案來衡量生成質(zhì)量——真實分布長什么樣生成分布應(yīng)該向它靠攏。這一步統(tǒng)計結(jié)果在后面評估環(huán)節(jié)會用到。2.3 號碼向量化one-hot還是歸一化這是我在項目里反復(fù)權(quán)衡過的一個點。把號碼喂給模型常見做法有兩種。第一種是歸一化把紅球除以33、藍(lán)球除以16壓縮到0到1區(qū)間。這種方式實現(xiàn)簡單問題在于它隱含了號碼之間存在順序關(guān)系的假設(shè)比如31和32是相鄰的模型會傾向于把相近的數(shù)字歸在一起。但彩票號碼本質(zhì)是類別變量1和33沒有遠(yuǎn)近關(guān)系歸一化容易引入錯誤的歸納偏置。第二種是one-hot編碼。紅球6個位置每個位置用一個33維的one-hot向量表示藍(lán)球1個位置用一個16維的one-hot向量表示。拼接起來就是6×3316214維的向量。這種方式完全符合無序類別的特性每個號碼是獨立的維度。缺點是維度不算低但對MLP來說完全能扛住。我最終選了one-hot方案原因很直接彩票號碼是分類變量用one-hot是語義正確的做法。后續(xù)生成結(jié)果也證明這個選擇是對的。3. 模型搭建讓生成器學(xué)會輸出離散號碼3.1 網(wǎng)絡(luò)結(jié)構(gòu)選型與設(shè)計思路我沒有一上來就用CNN或者Transformer而是選擇最經(jīng)典的全連接MLP結(jié)構(gòu)。為什么因為彩票號碼本質(zhì)上是一堆沒有空間相關(guān)性的離散數(shù)字CNN的卷積核假設(shè)鄰近區(qū)域有局部特征這在號碼場景里站不住腳。Transformer當(dāng)然可以但數(shù)據(jù)量太小容易過擬合。MLP結(jié)構(gòu)簡潔、可解釋性強(qiáng)、調(diào)參直觀作為第一版實現(xiàn)完全夠用。生成器的輸入是100維的高斯噪聲向量均值0方差1經(jīng)過兩個隱藏層每個隱藏層256個神經(jīng)元激活函數(shù)用ReLU。輸出層稍微特殊把6個紅球和1個藍(lán)球分別輸出。紅球的6個位置每個位置輸出33維logits藍(lán)球輸出16維logits。這樣網(wǎng)絡(luò)結(jié)構(gòu)的語義很清楚——每個logits代表該位置選擇對應(yīng)號碼的置信度。判別器的輸入是一個214維的向量真樣本或生成樣本的one-hot表示經(jīng)過兩個隱藏層每層128個神經(jīng)元輸出一個標(biāo)量表示輸入是真實數(shù)據(jù)的概率。判別器不用輸出層做多分類只需要輸出真?zhèn)胃怕仕耘湟粋€Sigmoid激活函數(shù)。3.2 Gumbel-Softmax離散采樣與梯度傳播的橋梁這是整個項目里最值得寫的一筆。生成器想要輸出離散號碼最直觀的做法是取logits的argmax即最大置信度的那個號碼。但argmax是一個不可導(dǎo)操作梯度無法從判別器回傳到生成器訓(xùn)練直接卡死。解決方案就是Gumbel-Softmax。它用可導(dǎo)的Softmax去近似不可導(dǎo)的argmax采樣?;舅悸贩謨刹降谝徊皆趌ogits上加上Gumbel噪聲一種符合Gumbel分布的隨機(jī)噪聲引入隨機(jī)性第二步用Softmax把加了噪聲的logits轉(zhuǎn)成概率分布。關(guān)鍵在于溫度參數(shù)τ溫度高時Softmax輸出接近均勻分布比較平滑梯度也能正常流通溫度低時Softmax輸出接近one-hot采樣結(jié)果更接近真實離散值但梯度方差變大訓(xùn)練不穩(wěn)定。所以訓(xùn)練時不能一個溫度用到頭。我的做法是溫度退火初始設(shè)τ1.0讓生成器先多探索生成多樣化的組合隨著訓(xùn)練推進(jìn)逐步把溫度降到0.1讓輸出越來越接近真正的離散one-hot。這樣既保證了梯度能回傳又保證了最終采樣時得到的是真正的號碼而不是一組軟概率。3.3 損失函數(shù)與訓(xùn)練目標(biāo)GAN的損失函數(shù)就是標(biāo)準(zhǔn)的min-max博弈。判別器想最大化對真實樣本和生成樣本區(qū)分正確的能力生成器想最小化判別器把生成樣本判斷為假的概率。實現(xiàn)上用二元交叉熵BCE作為基礎(chǔ)loss。但我實際訓(xùn)練時發(fā)現(xiàn)標(biāo)準(zhǔn)的BCE在數(shù)據(jù)量小的場景下非常容易崩潰具體表現(xiàn)是判別器loss迅速歸零、生成器loss爆炸。后來改用了帶梯度懲罰的WGAN-GP損失訓(xùn)練穩(wěn)定性提升非常明顯。WGAN的核心是放棄用真假二分類的交叉熵改用判別器輸出值的差異來度量真實分布和生成分布的Wasserstein距離再加上梯度懲罰項限制判別器的梯度范數(shù)。損失函數(shù)公式不展開寫了核心收益就是訓(xùn)練曲線不再忽上忽下亂跳穩(wěn)定性肉眼可見地改善。4. 訓(xùn)練過程超參數(shù)調(diào)優(yōu)與現(xiàn)象記錄4.1 訓(xùn)練流程與關(guān)鍵超參數(shù)訓(xùn)練流程遵循GAN的標(biāo)準(zhǔn)交替訓(xùn)練先凍結(jié)生成器訓(xùn)練判別器若干步再凍結(jié)判別器訓(xùn)練生成器一步。交替進(jìn)行的本質(zhì)是讓兩個網(wǎng)絡(luò)在博弈中緩慢進(jìn)步任何一方領(lǐng)先太多都會導(dǎo)致訓(xùn)練失敗。我的關(guān)鍵超參數(shù)配置如下這份配置實測下來比較穩(wěn)可以直接抄作業(yè)。超參數(shù)值說明噪聲維度z_dim100生成器的輸入噪聲維度生成器隱藏層256, 256兩層全連接ReLU激活判別器隱藏層128, 128兩層全連接LeakyReLU激活batch_size64每批樣本數(shù)學(xué)習(xí)率0.0002Adam優(yōu)化器初始學(xué)習(xí)率betas(0.5, 0.999)Adam的動量參數(shù)0.5能提升穩(wěn)定性訓(xùn)練輪數(shù)epoch3000數(shù)據(jù)量小輪數(shù)可以多設(shè)溫度初始值1.0Gumbel-Softmax起始溫度溫度最低值0.1Gumbel-Softmax最終溫度判別器訓(xùn)練比例每步訓(xùn)3次防止生成器進(jìn)步太快騙過判別器4.2 訓(xùn)練曲線怎么看很多新手訓(xùn)練GAN時只盯著loss數(shù)值變化這是一個大坑。GAN的loss數(shù)值本身沒有絕對含義關(guān)鍵看趨勢和相對關(guān)系。我的觀察經(jīng)驗是這樣的如果判別器loss持續(xù)下降并逼近0說明生成器太弱判別器閉著眼睛都能分辨真假這時候要降低判別器的訓(xùn)練頻率或者把判別器學(xué)習(xí)率調(diào)低避免它學(xué)得太快。如果判別器loss在0.69附近徘徊也就是恰好一半概率猜對說明判別器完全沒學(xué)會區(qū)分真假可能是網(wǎng)絡(luò)表達(dá)能力不夠或者數(shù)據(jù)沒有任何可辨別的特征。更常見的情況是loss來回震蕩這種狀態(tài)下生成器往往能產(chǎn)出看起來正常的樣本但多樣性不穩(wěn)定。我訓(xùn)練時還額外加了一個觀察維度每訓(xùn)練500輪讓生成器生成一批號碼統(tǒng)計紅球出現(xiàn)頻率分布。如果頻率分布逐漸趨于均勻同時號碼不總是重復(fù)說明訓(xùn)練方向正確如果頻率分布始終偏向某幾個號碼就要警惕模式崩塌了。4.3 訓(xùn)練中遇到的第一個假象訓(xùn)練到第1000輪左右我注意到生成器輸出的號碼看起來已經(jīng)很正常了紅球都在1到33之間藍(lán)球也在1到16之間范圍完全合法。當(dāng)時還高興了一會兒后來一統(tǒng)計發(fā)現(xiàn)不對勁生成的紅球高度集中在10到25這個區(qū)間1到9和26到33的號碼出現(xiàn)頻率明顯偏低。這說明生成器學(xué)會了語法規(guī)則號碼范圍合法但沒學(xué)會分布規(guī)律每個號碼出現(xiàn)頻率應(yīng)該接近均等。這個現(xiàn)象很有代表性GAN很容易先學(xué)會局部約束再慢慢學(xué)全局分布。遇到這種情況不用慌繼續(xù)訓(xùn)練同時檢查溫度有沒有按計劃衰減——溫度不衰減的話生成器會一直停留在軟概率階段沒法真正產(chǎn)出離散多樣的樣本。5. 效果評估生成號碼到底像不像真的5.1 邊緣分布均勻性檢驗GAN訓(xùn)練完后最重要的評估是看生成號碼的邊緣分布和歷史數(shù)據(jù)是否一致。我讓生成器獨立生成了3000組號碼和訓(xùn)練數(shù)據(jù)量相當(dāng)統(tǒng)計每個紅球在全部生成樣本中的出現(xiàn)次數(shù)再和真實歷史數(shù)據(jù)的紅球出現(xiàn)頻率放在一起對比。 提示彩票開獎的理論模型中每個號碼出現(xiàn)的頻率應(yīng)當(dāng)是接近均勻的。數(shù)據(jù)量足夠大時歷史真實紅球頻率雖然有些波動但整體圍繞1/33附近浮動。生成樣本應(yīng)當(dāng)在同樣范圍內(nèi)波動。實際統(tǒng)計結(jié)果顯示生成號碼的紅球頻率圍繞0.0303附近浮動1/33約等于0.0303藍(lán)球圍繞0.0625附近浮動1/16等于0.0625和歷史數(shù)據(jù)的分布非常接近。這個結(jié)論說明經(jīng)過訓(xùn)練的GAN確實學(xué)到了號碼的邊緣分布規(guī)律生成號碼從單號頻率角度已經(jīng)看起來像真的了。5.2 組合多樣性與模式崩塌檢測邊緣分布均勻還不夠更嚴(yán)格的是看組合層面的多樣性。模式崩塌是GAN的經(jīng)典問題——生成器有可能只生成少量固定組合讓邊緣分布看起來沒問題但組合數(shù)極少互相之間大量重復(fù)。我檢查了3000組生成號碼的唯一組合數(shù)量以及任意兩組完全相同的情況。結(jié)果發(fā)現(xiàn)唯一組合數(shù)占全部生成數(shù)的85%以上重復(fù)率很低說明生成器沒有陷入嚴(yán)重的模式崩塌組合層面的多樣性是有的。這里有一個實用小技巧訓(xùn)練過程中可以周期性記錄生成號碼的重復(fù)率。如果某個階段重復(fù)率突然升高往往是訓(xùn)練不穩(wěn)定的前兆可以回退到之前的效果更好的checkpoint調(diào)整超參數(shù)后繼續(xù)訓(xùn)練。5.3 讓人沮喪又合理的結(jié)論雖然生成號碼從統(tǒng)計上和真實數(shù)據(jù)非常接近但如果你拿生成的號碼去和后續(xù)真實開獎結(jié)果對比中獎率并不會高于隨機(jī)選號。這個結(jié)論從統(tǒng)計學(xué)的角度完全合理每期開獎是獨立隨機(jī)事件過去的數(shù)據(jù)里并沒有藏著未來的信息GAN學(xué)到的是歷史分布的形狀而不是下一期的走勢。把這個項目當(dāng)作技術(shù)練習(xí)就很有意思——你親手實現(xiàn)了一個能學(xué)習(xí)復(fù)雜分布并生成合理樣本的生成模型這對理解生成式AI非常有價值。但如果你抱著AI預(yù)測彩票的目的來做那大概率會失望。這是所有做這類項目的人需要提前認(rèn)清的一件事。6. 踩坑記錄與避坑建議6.1 離散輸出梯度消失生成器不學(xué)習(xí)問題現(xiàn)象訓(xùn)練好幾輪生成器的loss幾乎不動梯度非常小。排查思路問題出在生成器輸出層的離散化處理。如果直接用torch.argmax硬編碼生成號碼梯度為零生成器完全學(xué)不到任何信息。這是離散GAN最常見的坑。解決方案改用Gumbel-Softmax作為生成器的輸出層讓采樣這個過程變得可導(dǎo)。溫度退火必須跟上——不衰減的話生成樣本太軟退火太快則梯度方差太大導(dǎo)致訓(xùn)練不穩(wěn)定。我試了從1.0線性衰減到0.1中間分了2000輪慢慢降效果最好。6.2 模式崩塌生成號碼高度重復(fù)問題現(xiàn)象生成的號碼大量重復(fù)邊緣分布雖然正常但本質(zhì)上只有幾十種組合在來回輸出。排查思路模式崩塌的根源是生成器找到了判別器的漏洞只要生成固定幾種組合就能騙過判別器。從訓(xùn)練曲線上看通常是判別器loss突然下降、生成器loss不再上升。解決方案我試過幾種方法見效最快的是給生成器輸入增加噪聲強(qiáng)度讓不同噪聲向量更難映射到同一個輸出其次是調(diào)整判別器訓(xùn)練比例從1:1改到3:1讓判別器更強(qiáng)逼迫生成器探索更多模式再就是使用WGAN-GP損失用Wasserstein距離替代JS散度穩(wěn)定性明顯改善。6.3 數(shù)據(jù)量太小帶來的過擬合問題現(xiàn)象訓(xùn)練后期生成器生成的號碼幾乎和訓(xùn)練集里的某幾期完全一樣。排查思路3000多期數(shù)據(jù)對GAN來說確實偏少生成器理論上可以把訓(xùn)練數(shù)據(jù)背下來然后復(fù)讀給判別器聽。解決方案我在生成器的隱藏層之間加了一層Dropout概率0.2強(qiáng)制生成器不能單純依賴記憶必須有泛化能力。另外做了early stopping每500輪生成一批樣本人為觀察多樣性一旦發(fā)現(xiàn)重復(fù)率明顯上升立即停止訓(xùn)練回退到之前最優(yōu)的checkpoint。這個操作雖然樸素但非常管用。6.4 判別器躺平loss持續(xù)在0.5附近問題現(xiàn)象判別器loss一直在0.69附近說明它完全分辨不出真假。排查思路這個坑我一度認(rèn)為很離譜——生成器都開始產(chǎn)出正常號碼了判別器還沒有學(xué)會區(qū)分。原因是判別器的學(xué)習(xí)率設(shè)置太低收斂速度遠(yuǎn)慢于生成器。解決方案把判別器的學(xué)習(xí)率從0.0002提升到0.0004同時保持生成器學(xué)習(xí)率不變讓判別器能快速追上生成器的節(jié)奏。調(diào)完之后判別器loss開始正常波動訓(xùn)練也順暢了。6.5 溫度參數(shù)退火導(dǎo)致的異常問題現(xiàn)象訓(xùn)練后期溫度降到0.1附近時生成器loss突然開始劇烈波動生成號碼質(zhì)量明顯下降。排查思路溫度太低時Gumbel-Softmax輸出接近one-hot梯度方差劇增訓(xùn)練天然不穩(wěn)定。這是Gumbel-Softmax的已知特性。解決方案很簡單溫度降到0.1之后不再繼續(xù)下降保持這個溫度把剩余輪次跑完。如果你后續(xù)要復(fù)用這套代碼建議把溫度下限設(shè)成一個可配置的參數(shù)不同數(shù)據(jù)量、不同網(wǎng)絡(luò)結(jié)構(gòu)最優(yōu)溫度下限會有細(xì)微差別。最后再分享一點個人體會這個項目做完之后我最大的收獲不是我生成了彩票號碼這件事本身而是通過一個小而完整的項目把GAN從理論到實踐的每一個環(huán)節(jié)都親手驗證了一遍。你會在訓(xùn)練過程中親眼看到生成器從輸出一堆隨機(jī)垃圾到慢慢理解號碼范圍再到逐漸逼近歷史分布這種看著模型學(xué)會一件事的過程是讀再多論文都換不來的體驗。如果你也想嘗試建議先跑通我這個版本掌握Gumbel-Softmax、溫度退火、WGAN-GP這幾個核心技巧然后可以沿著兩個方向去擴(kuò)展一是把生成器從MLP換成Transformer看看結(jié)構(gòu)變化會帶來什么影響二是引入更長的歷史數(shù)據(jù)甚至把期號作為條件輸入嘗試條件生成。這個項目本身雖然是個玩具但它覆蓋的生成模型知識點放到工業(yè)級項目里同樣成立。本文還有配套的精品資源點擊獲取