現(xiàn)受限玻爾茲曼機(jī):從原理到實(shí)踐)
簡(jiǎn)介本資源是一份面向機(jī)器學(xué)習(xí)初學(xué)者與研究者的受限玻爾茲曼機(jī)RBMMATLAB實(shí)現(xiàn)代碼包聚焦無(wú)監(jiān)督特征學(xué)習(xí)與概率生成建模任務(wù)適用于圖像數(shù)據(jù)如手寫(xiě)數(shù)字的隱層表示訓(xùn)練與采樣生成。壓縮包共29個(gè)文件包含7個(gè)核心MATLAB腳本如RBM.m、trainRBM.m、gibbs.m、8個(gè)預(yù)訓(xùn)練模型參數(shù)文件.mat格式含RBM50.mat、RBM200.mat等不同隱單元規(guī)模、10張可視化結(jié)果圖.jpg涵蓋權(quán)重圖、重構(gòu)圖及損失曲線以及訓(xùn)練/驗(yàn)證數(shù)據(jù)文本digitstrain.txt等和說(shuō)明文檔README.md。資源大小為8.86MB結(jié)構(gòu)清晰模塊分工明確訓(xùn)練主流程、Gibbs采樣、Sigmoid激活、權(quán)重可視化等功能均獨(dú)立封裝便于理解RBM前向傳播、對(duì)比散度CD訓(xùn)練及采樣機(jī)制。目前已有592人學(xué)習(xí)下載可直接運(yùn)行main.m復(fù)現(xiàn)經(jīng)典RBM訓(xùn)練過(guò)程并基于K5/K10/K20等預(yù)存模型快速開(kāi)展特征提取或生成實(shí)驗(yàn)。1. 從理論到實(shí)踐為什么選擇MATLAB實(shí)現(xiàn)RBM如果你正在機(jī)器學(xué)習(xí)或深度學(xué)習(xí)的入門(mén)階段尤其是對(duì)神經(jīng)網(wǎng)絡(luò)中的生成模型感興趣那么“受限玻爾茲曼機(jī)”這個(gè)名字你一定不陌生。它不像卷積神經(jīng)網(wǎng)絡(luò)那樣直接處理圖像也不像循環(huán)神經(jīng)網(wǎng)絡(luò)那樣擅長(zhǎng)序列但它在特征學(xué)習(xí)、協(xié)同過(guò)濾、降維乃至深度信念網(wǎng)絡(luò)的構(gòu)建中扮演著奠基者的角色。很多教程和論文會(huì)告訴你RBM的數(shù)學(xué)原理但當(dāng)你真正想動(dòng)手跑通一個(gè)例子看看它如何從一堆隨機(jī)數(shù)中“學(xué)習(xí)”出數(shù)據(jù)的潛在結(jié)構(gòu)時(shí)往往會(huì)卡在實(shí)現(xiàn)這一步。這時(shí)MATLAB就成為了一個(gè)極具吸引力的選擇。為什么是MATLAB對(duì)于算法研究者、在校學(xué)生以及需要快速驗(yàn)證想法的工程師來(lái)說(shuō)MATLAB提供了一個(gè)近乎“一站式”的環(huán)境。你不需要花大量時(shí)間去配置復(fù)雜的Python深度學(xué)習(xí)框架如TensorFlow或PyTorch及其依賴(lài)也不用擔(dān)心版本沖突。MATLAB的語(yǔ)法直觀矩陣運(yùn)算原生高效并且內(nèi)置了豐富的可視化工具這對(duì)于理解RBM這種基于概率和能量模型的“黑箱”學(xué)習(xí)過(guò)程至關(guān)重要。你可以實(shí)時(shí)看到權(quán)重矩陣的變化、重構(gòu)誤差的下降曲線甚至可視化學(xué)習(xí)到的特征這種即時(shí)反饋對(duì)加深理解有巨大幫助。當(dāng)然MATLAB并非生產(chǎn)部署的首選但其在算法原型開(kāi)發(fā)、教學(xué)演示和科研探索中的便捷性是其他工具難以比擬的。本文將帶你從零開(kāi)始在MATLAB中實(shí)現(xiàn)一個(gè)標(biāo)準(zhǔn)的二值受限玻爾茲曼機(jī)。我們不會(huì)止步于一個(gè)能運(yùn)行的“玩具代碼”而是會(huì)深入每一步背后的動(dòng)機(jī)解釋為什么參數(shù)要這樣初始化、為什么采樣要那樣做、對(duì)比散度算法究竟在干什么。我會(huì)分享在調(diào)試過(guò)程中遇到的典型“坑”比如學(xué)習(xí)率設(shè)置不當(dāng)導(dǎo)致的震蕩、權(quán)重初始化太小造成的學(xué)習(xí)停滯以及如何正確評(píng)估一個(gè)RBM模型是否真的學(xué)到了東西。無(wú)論你是想完成課程作業(yè)還是為更復(fù)雜的深度模型打基礎(chǔ)這篇內(nèi)容都將提供一條清晰的、可復(fù)現(xiàn)的路徑。2. 受限玻爾茲曼機(jī)核心原理速覽與MATLAB建模思路在動(dòng)手寫(xiě)代碼之前我們必須對(duì)RBM有一個(gè)清晰、直觀的理解這決定了我們?nèi)绾斡肕ATLAB的數(shù)據(jù)結(jié)構(gòu)和運(yùn)算來(lái)刻畫(huà)它。你可以把RBM想象成一個(gè)兩層、對(duì)稱(chēng)連接的神經(jīng)網(wǎng)絡(luò)但它沒(méi)有通常意義上的“輸出層”。底層叫可見(jiàn)層代表我們能看到的數(shù)據(jù)比如一張二值化圖片的像素。頂層叫隱藏層是我們希望機(jī)器自動(dòng)學(xué)習(xí)到的、能夠解釋數(shù)據(jù)規(guī)律的特征。每個(gè)可見(jiàn)層神經(jīng)元和每個(gè)隱藏層神經(jīng)元之間都有連接權(quán)重但同層神經(jīng)元之間沒(méi)有任何連接這就是“受限”的含義——它讓概率計(jì)算變得可處理。RBM是一個(gè)基于能量的模型。它為系統(tǒng)的每一種狀態(tài)即每一組具體的可見(jiàn)向量v和隱藏向量h的取值定義一個(gè)“能量”能量越低的狀態(tài)出現(xiàn)的概率越高。這個(gè)能量函數(shù)是核心E(v, h) -b*v - c*h - v*W*h這里v是可見(jiàn)層向量h是隱藏層向量b是可見(jiàn)層的偏置c是隱藏層的偏置W是連接權(quán)重矩陣。公式里的三項(xiàng)分別代表了可見(jiàn)單元自身、隱藏單元自身以及它們之間交互對(duì)能量的貢獻(xiàn)。我們的目標(biāo)是調(diào)整參數(shù)W, b, c使得訓(xùn)練數(shù)據(jù)即我們觀測(cè)到的v對(duì)應(yīng)的能量盡可能低也就是讓模型認(rèn)為這些數(shù)據(jù)出現(xiàn)的概率高。那么給定可見(jiàn)層狀態(tài)v隱藏層神經(jīng)元h_j取值為1的概率是多少由于層內(nèi)無(wú)連接這個(gè)計(jì)算是獨(dú)立并行的公式非常簡(jiǎn)潔P(h_j1 | v) sigmoid(c_j sum_i(v_i * W_{ij}))在MATLAB里這就是一個(gè)sigmoid函數(shù)1./(1exp(-x))的應(yīng)用。給定隱藏層狀態(tài)h計(jì)算可見(jiàn)層神經(jīng)元v_i取值為1的概率也是對(duì)稱(chēng)的P(v_i1 | h) sigmoid(b_i sum_j(h_j * W_{ij}))這種給定一層、另一層條件獨(dú)立且可并行計(jì)算的性質(zhì)是RBM能夠進(jìn)行高效吉布斯采樣的基礎(chǔ)也是后續(xù)對(duì)比散度算法的關(guān)鍵。我們的學(xué)習(xí)目標(biāo)是最大化訓(xùn)練數(shù)據(jù)的似然概率。通過(guò)推導(dǎo)權(quán)重的更新規(guī)則可以表示為ΔW learning_rate * ( v_i h_j_data - v_i h_j_model )尖括號(hào)代表求期望。v_i h_j_data是當(dāng)可見(jiàn)層固定為訓(xùn)練數(shù)據(jù)時(shí)隱藏層根據(jù)上述概率采樣或取其概率值即“均值場(chǎng)”然后計(jì)算v_i和h_j的關(guān)聯(lián)。這代表了數(shù)據(jù)驅(qū)動(dòng)的“正能量”它試圖降低訓(xùn)練數(shù)據(jù)的能量。v_i h_j_model則是模型本身定義的分布下的期望計(jì)算它理論上需要從模型分布中采樣直到平衡這非常耗時(shí)。Hinton提出的“對(duì)比散度”算法巧妙地解決了這個(gè)問(wèn)題用訓(xùn)練數(shù)據(jù)初始化可見(jiàn)層然后進(jìn)行少數(shù)幾步通常是1步即CD-1吉布斯采樣可見(jiàn)→隱藏→可見(jiàn)來(lái)近似這個(gè)模型期望。這構(gòu)成了我們MATLAB實(shí)現(xiàn)的核心迭代循環(huán)。在MATLAB中建模我們將把W定義為一個(gè)[n_visible, n_hidden]的矩陣b是[n_visible, 1]的列向量c是[n_hidden, 1]的列向量。訓(xùn)練數(shù)據(jù)data是一個(gè)[n_samples, n_visible]的矩陣每一行是一個(gè)樣本。所有的運(yùn)算都將利用MATLAB高效的矩陣乘法來(lái)實(shí)現(xiàn)避免低效的循環(huán)。3. MATLAB環(huán)境準(zhǔn)備與數(shù)據(jù)預(yù)處理實(shí)戰(zhàn)工欲善其事必先利其器。在開(kāi)始編寫(xiě)RBM核心算法前我們需要確保MATLAB環(huán)境就緒并將數(shù)據(jù)處理好。這個(gè)過(guò)程看似簡(jiǎn)單卻藏著不少初學(xué)者容易忽略的細(xì)節(jié)直接影響到后續(xù)訓(xùn)練的成敗。首先確保你有一個(gè)能正常運(yùn)行的MATLAB版本。對(duì)于RBM這類(lèi)基礎(chǔ)算法其實(shí)對(duì)版本要求并不高R2016a之后的版本都完全夠用。關(guān)鍵在于你需要熟悉如何在MATLAB中組織你的項(xiàng)目。我強(qiáng)烈建議為這個(gè)RBM項(xiàng)目單獨(dú)創(chuàng)建一個(gè)文件夾比如命名為MyRBM_Project。在這個(gè)文件夾里你可以創(chuàng)建以下文件主腳本train_rbm.mRBM核心函數(shù)rbm.m以及可能的數(shù)據(jù)加載和可視化腳本。這樣做的好處是路徑清晰便于管理也方便你后續(xù)擴(kuò)展成深度信念網(wǎng)絡(luò)。接下來(lái)是數(shù)據(jù)。RBM的可見(jiàn)層單元默認(rèn)是二值的0或1。因此我們使用的數(shù)據(jù)必須是二值化的或者可以合理地解釋為概率。一個(gè)經(jīng)典的數(shù)據(jù)集是MNIST手寫(xiě)數(shù)字但它的像素值是0到255的灰度。直接使用是不行的。常見(jiàn)的預(yù)處理方法是設(shè)定一個(gè)閾值如128大于閾值置1否則置0。但更推薦的方法是將其歸一化到[0, 1]區(qū)間即將每個(gè)像素值除以255。此時(shí)像素值可以解釋為該像素點(diǎn)“激活”為1的概率。我們的RBM代碼將能夠處理這種連續(xù)概率值此時(shí)稱(chēng)為“伯努利-高斯RBM”的可見(jiàn)層但采樣時(shí)仍按此概率進(jìn)行二值化。在MATLAB中加載和預(yù)處理MNIST數(shù)據(jù)可以借助一些開(kāi)源工具箱但為了理解本質(zhì)我們可以用一個(gè)小型人造數(shù)據(jù)集開(kāi)始。假設(shè)我們想學(xué)習(xí)一個(gè)簡(jiǎn)單的“十字”圖案。我們可以創(chuàng)建一些5x5的二值圖像其中中心行和中心列的像素為1其余為0。在MATLAB中我們可以這樣生成數(shù)據(jù)% 生成合成數(shù)據(jù)十字圖案 n_samples 1000; % 1000個(gè)樣本 img_size 5; % 5x5圖像 data zeros(n_samples, img_size*img_size); % 初始化數(shù)據(jù)矩陣 for i 1:n_samples img zeros(img_size); img(ceil(img_size/2), :) 1; % 中心行置1 img(:, ceil(img_size/2)) 1; % 中心列置1 % 添加少量隨機(jī)噪聲使數(shù)據(jù)更真實(shí) img img 0.1 * randn(img_size); % 添加高斯噪聲 img (img 0.5); % 二值化閾值0.5 data(i, :) img(:); % 展平為行向量并存入數(shù)據(jù)矩陣 end % 檢查數(shù)據(jù)維度應(yīng)該是 1000 x 25 disp(size(data));注意在實(shí)際科研中你可能會(huì)用到loadMNISTImages等函數(shù)來(lái)讀取真實(shí)MNIST數(shù)據(jù)。但無(wú)論數(shù)據(jù)來(lái)源如何預(yù)處理的核心原則是一致的確保數(shù)據(jù)矩陣的每一行是一個(gè)樣本每一列是一個(gè)特征可見(jiàn)單元且值在[0,1]區(qū)間內(nèi)。對(duì)于非二值數(shù)據(jù)務(wù)必進(jìn)行歸一化。數(shù)據(jù)準(zhǔn)備好后我們還需要思考一個(gè)問(wèn)題需不需要?jiǎng)澐钟?xùn)練集和測(cè)試集對(duì)于RBM這種無(wú)監(jiān)督學(xué)習(xí)模型我們通常用所有數(shù)據(jù)來(lái)訓(xùn)練以學(xué)習(xí)數(shù)據(jù)的分布。評(píng)估則可以通過(guò)查看模型重構(gòu)數(shù)據(jù)的能力或者用學(xué)習(xí)到的特征作為下游任務(wù)如分類(lèi)的輸入。因此在初始實(shí)現(xiàn)階段我們可以用全部數(shù)據(jù)訓(xùn)練。但一個(gè)好的習(xí)慣是始終保留一小部分?jǐn)?shù)據(jù)作為“驗(yàn)證集”用于監(jiān)控訓(xùn)練過(guò)程是否過(guò)擬合雖然RBM的過(guò)擬合風(fēng)險(xiǎn)相對(duì)較低但仍可能發(fā)生。最后初始化一個(gè)隨機(jī)數(shù)種子是個(gè)好習(xí)慣這能確保你的實(shí)驗(yàn)結(jié)果是可復(fù)現(xiàn)的。在MATLAB中可以使用rng(42)42是一個(gè)常用種子來(lái)固定隨機(jī)數(shù)生成器。這對(duì)于調(diào)試和對(duì)比不同參數(shù)的效果至關(guān)重要。4. RBM核心算法實(shí)現(xiàn)權(quán)重初始化與對(duì)比散度有了清晰的理論認(rèn)識(shí)和準(zhǔn)備好的數(shù)據(jù)我們現(xiàn)在可以著手實(shí)現(xiàn)RBM最核心的部分參數(shù)初始化和對(duì)比散度訓(xùn)練循環(huán)。這是將數(shù)學(xué)公式轉(zhuǎn)化為可運(yùn)行代碼的關(guān)鍵一步其中每一步的設(shè)計(jì)都有其道理。首先我們初始化參數(shù)W, b, c。初始化不當(dāng)會(huì)導(dǎo)致訓(xùn)練初期梯度消失或爆炸。一個(gè)廣泛使用的經(jīng)驗(yàn)法則是從均值為0、標(biāo)準(zhǔn)差較小的正態(tài)分布中隨機(jī)初始化權(quán)重W。標(biāo)準(zhǔn)差通常設(shè)為0.01或1/sqrt(n_visible)。偏置b和c可以初始化為0。在MATLAB中我們可以這樣寫(xiě)function [W, b, c] rbm_init(n_visible, n_hidden) % 初始化RBM參數(shù) % n_visible: 可見(jiàn)層單元數(shù) % n_hidden: 隱藏層單元數(shù) % 返回: 權(quán)重矩陣 W, 可見(jiàn)層偏置 b, 隱藏層偏置 c % 權(quán)重初始化使用較小的隨機(jī)值打破對(duì)稱(chēng)性 std_dev 0.01; % 或者 1/sqrt(n_visible) W std_dev * randn(n_visible, n_hidden); % 偏置初始化為0 b zeros(n_visible, 1); c zeros(n_hidden, 1); end接下來(lái)是核心的訓(xùn)練函數(shù)它實(shí)現(xiàn)了對(duì)比散度算法。我們將遵循CD-1的流程并采用“小批量”隨機(jī)梯度下降來(lái)加速訓(xùn)練并增加穩(wěn)定性。函數(shù)的輸入包括數(shù)據(jù)、隱藏單元數(shù)、學(xué)習(xí)率、訓(xùn)練輪次和批量大小。function [W, b, c, errors] train_rbm(data, n_hidden, learning_rate, n_epochs, batch_size) % 使用對(duì)比散度(CD-1)訓(xùn)練RBM % data: 訓(xùn)練數(shù)據(jù)每行一個(gè)樣本值在[0,1] % n_hidden: 隱藏層單元數(shù) % learning_rate: 學(xué)習(xí)率 % n_epochs: 訓(xùn)練輪次 % batch_size: 批量大小 % 返回: 訓(xùn)練好的參數(shù) W, b, c以及每輪的重構(gòu)誤差 [n_samples, n_visible] size(data); num_batches ceil(n_samples / batch_size); % 初始化參數(shù) [W, b, c] rbm_init(n_visible, n_hidden); errors zeros(n_epochs, 1); % 記錄每輪的平均重構(gòu)誤差 for epoch 1:n_epochs err_sum 0; % 打亂數(shù)據(jù)順序 shuffled_idx randperm(n_samples); data_shuffled data(shuffled_idx, :); for batch 1:num_batches % 獲取當(dāng)前小批量數(shù)據(jù) batch_start (batch-1)*batch_size 1; batch_end min(batch*batch_size, n_samples); batch_data data_shuffled(batch_start:batch_end, :); batch_size_curr size(batch_data, 1); % 將數(shù)據(jù)矩陣轉(zhuǎn)置便于后續(xù)矩陣運(yùn)算 (n_visible x batch_size) v0 batch_data; % 初始可見(jiàn)層狀態(tài)概率值 % --- 正向傳播計(jì)算隱藏層概率并采樣 --- % h0_prob: 給定v0時(shí)隱藏層激活的概率 (n_hidden x batch_size) h0_prob sigmoid(bsxfun(plus, c, W * v0)); % 等價(jià)于 c W*v0 h0_state h0_prob rand(size(h0_prob)); % 二值采樣 % --- 計(jì)算數(shù)據(jù)相關(guān)的統(tǒng)計(jì)量 --- % positive_associations: v_i h_j_data 的近似 positive_associations v0 * h0_state / batch_size_curr; positive_visible_bias mean(v0, 2); % v_i_data positive_hidden_bias mean(h0_prob, 2); % 這里用概率也可以用采樣后的狀態(tài) % --- 負(fù)相從模型中采樣CD-1--- % 從h0_state重構(gòu)可見(jiàn)層 v1_prob sigmoid(bsxfun(plus, b, W * h0_state)); % 重構(gòu)的可見(jiàn)層概率 v1_state v1_prob rand(size(v1_prob)); % 采樣得到v1 % 從v1_state再次計(jì)算隱藏層 h1_prob sigmoid(bsxfun(plus, c, W * v1_state)); % h1_state h1_prob rand(size(h1_prob)); % CD-1通常這里不采樣直接用概率 % --- 計(jì)算模型相關(guān)的統(tǒng)計(jì)量 --- % negative_associations: v_i h_j_model 的近似 (CD-1) negative_associations v1_state * h1_prob / batch_size_curr; negative_visible_bias mean(v1_state, 2); negative_hidden_bias mean(h1_prob, 2); % --- 參數(shù)更新 --- W W learning_rate * (positive_associations - negative_associations); b b learning_rate * (positive_visible_bias - negative_visible_bias); c c learning_rate * (positive_hidden_bias - negative_hidden_bias); % --- 計(jì)算當(dāng)前批次的重構(gòu)誤差用于監(jiān)控--- % 使用v0和重構(gòu)的v1_prob之間的交叉熵或均方誤差 reconstruction_error -sum(sum(v0 .* log(v1_prob 1e-10) (1-v0) .* log(1-v1_prob 1e-10))) / batch_size_curr; err_sum err_sum reconstruction_error; end % 記錄本輪平均誤差 errors(epoch) err_sum / num_batches; % 可選每若干輪打印一次進(jìn)度 if mod(epoch, 10) 0 fprintf(Epoch %d, Reconstruction Error: %f\n, epoch, errors(epoch)); end end end % Sigmoid輔助函數(shù) function y sigmoid(x) y 1 ./ (1 exp(-x)); end這段代碼有幾個(gè)關(guān)鍵點(diǎn)需要解釋矩陣運(yùn)算與維度注意我們始終將數(shù)據(jù)以列向量的形式堆疊成矩陣n_visible x batch_size。這樣W * v0就能一次性計(jì)算所有樣本的隱藏層輸入極大提升了效率。bsxfun函數(shù)用于處理偏置向量與矩陣的加法對(duì)于新版MATLAB直接使用運(yùn)算符即可自動(dòng)廣播。采樣策略在正向傳播中我們根據(jù)概率h0_prob進(jìn)行了二值采樣得到h0_state。這是“隨機(jī)”RBM的標(biāo)準(zhǔn)做法。也有一種變體叫“均值場(chǎng)”方法直接使用概率值而不采樣這通常會(huì)使訓(xùn)練更穩(wěn)定但可能丟失一些隨機(jī)性。在負(fù)相中CD-1算法通常只對(duì)可見(jiàn)層進(jìn)行采樣得到v1_state而對(duì)第二次的隱藏層使用概率h1_prob來(lái)計(jì)算梯度這被證明是更有效的。重構(gòu)誤差我們使用二進(jìn)制交叉熵作為重構(gòu)誤差它衡量了原始數(shù)據(jù)v0概率值與重構(gòu)數(shù)據(jù)v1_prob概率值之間的差異。添加一個(gè)極小值1e-10是為了避免對(duì)數(shù)為負(fù)無(wú)窮。這個(gè)誤差是監(jiān)控訓(xùn)練進(jìn)程的重要指標(biāo)它應(yīng)該隨著訓(xùn)練輪次增加而穩(wěn)步下降。批量更新我們不是用一個(gè)樣本更新一次參數(shù)而是用一個(gè)小批量batch_size的數(shù)據(jù)計(jì)算平均梯度后再更新。這能減少參數(shù)更新的方差使訓(xùn)練過(guò)程更平滑也更能利用MATLAB的矩陣運(yùn)算優(yōu)勢(shì)。5. 訓(xùn)練過(guò)程監(jiān)控、調(diào)參與常見(jiàn)問(wèn)題排查代碼寫(xiě)好了直接運(yùn)行可能不會(huì)一帆風(fēng)順。訓(xùn)練一個(gè)RBM就像烹飪火候?qū)W習(xí)率、食材比例網(wǎng)絡(luò)結(jié)構(gòu)、時(shí)間訓(xùn)練輪次都需要細(xì)心把控。這一節(jié)我們來(lái)聊聊如何監(jiān)控訓(xùn)練過(guò)程調(diào)整關(guān)鍵參數(shù)并解決那些讓你抓狂的典型問(wèn)題。首先學(xué)習(xí)率是首要超參數(shù)。學(xué)習(xí)率太大權(quán)重更新會(huì)“過(guò)沖”導(dǎo)致重構(gòu)誤差劇烈震蕩甚至發(fā)散變成NaN。學(xué)習(xí)率太小訓(xùn)練會(huì)慢如蝸牛誤差下降曲線幾乎是一條平線。對(duì)于RBM一個(gè)常見(jiàn)的起始值是0.01或0.1。我個(gè)人的經(jīng)驗(yàn)是可以先設(shè)為0.1觀察前幾十輪的重構(gòu)誤差。如果誤差爆炸立刻降到0.01或0.05。如果誤差下降非常緩慢可以嘗試增大到0.2。一個(gè)更穩(wěn)健的策略是使用衰減的學(xué)習(xí)率比如每50輪將學(xué)習(xí)率乘以0.95。其次隱藏層單元數(shù)決定了模型的容量。單元太少模型無(wú)法捕捉數(shù)據(jù)的復(fù)雜結(jié)構(gòu)重構(gòu)誤差會(huì)停留在一個(gè)較高的平臺(tái)。單元太多則可能導(dǎo)致過(guò)擬合模型會(huì)記住訓(xùn)練數(shù)據(jù)的噪聲而非一般規(guī)律。對(duì)于我們的5x5十字圖案25個(gè)可見(jiàn)單元隱藏單元數(shù)可以從10到50之間嘗試。一個(gè)粗略的經(jīng)驗(yàn)法則是隱藏單元數(shù)可以與可見(jiàn)單元數(shù)在同一數(shù)量級(jí)或略少。你可以通過(guò)觀察“權(quán)重可視化”來(lái)輔助判斷如果很多隱藏單元學(xué)習(xí)到的權(quán)重圖看起來(lái)是重復(fù)或模糊的可能意味著隱藏單元過(guò)多或訓(xùn)練不足。批量大小影響梯度估計(jì)的噪聲和訓(xùn)練速度。較小的批量如10 20會(huì)帶來(lái)噪聲更大的更新有時(shí)有助于跳出局部最優(yōu)但訓(xùn)練不穩(wěn)定。較大的批量如100 200能提供更平滑的梯度估計(jì)訓(xùn)練更穩(wěn)定但可能內(nèi)存消耗更大。對(duì)于MNIST60000樣本批量大小128或256是常見(jiàn)選擇。對(duì)于我們的合成數(shù)據(jù)1000樣本批量大小64或128比較合適。訓(xùn)練輪次需要通過(guò)監(jiān)控重構(gòu)誤差來(lái)決定。在訓(xùn)練開(kāi)始時(shí)誤差應(yīng)該快速下降。隨著輪次增加下降速度會(huì)變慢最終趨于平穩(wěn)。你可以繪制誤差隨輪次變化的曲線。當(dāng)曲線在連續(xù)多輪比如50輪內(nèi)不再有明顯下降變化小于一個(gè)閾值如1e-5就可以考慮停止訓(xùn)練了。設(shè)置一個(gè)最大輪次如500作為保險(xiǎn)?,F(xiàn)在讓我們把訓(xùn)練和監(jiān)控腳本整合起來(lái)% 主腳本訓(xùn)練并監(jiān)控RBM clear; close all; clc; % 1. 生成或加載數(shù)據(jù) % 這里使用第3節(jié)生成的合成數(shù)據(jù)假設(shè)變量data已存在 % 或者加載MNIST等 % load(mnist_train.mat); % 假設(shè)數(shù)據(jù)在變量 train_x 中且已歸一化到[0,1] % data train_x; % 2. 設(shè)置超參數(shù) n_hidden 20; % 隱藏層單元數(shù) learning_rate 0.1; % 初始學(xué)習(xí)率 n_epochs 200; % 訓(xùn)練輪次 batch_size 64; % 批量大小 % 3. 訓(xùn)練RBM [W_trained, b_trained, c_trained, errors] train_rbm(data, n_hidden, learning_rate, n_epochs, batch_size); % 4. 可視化訓(xùn)練過(guò)程 figure; plot(1:n_epochs, errors, b-, LineWidth, 1.5); xlabel(訓(xùn)練輪次 (Epoch)); ylabel(平均重構(gòu)誤差); title(RBM訓(xùn)練誤差曲線); grid on; % 5. 可視化學(xué)習(xí)到的權(quán)重特征 % 將權(quán)重矩陣的每一列對(duì)應(yīng)一個(gè)隱藏單元重塑為圖像尺寸 img_size sqrt(size(W_trained, 1)); % 假設(shè)是方形圖像 if img_size floor(img_size) % 確保可以重塑 figure; for i 1:min(25, n_hidden) % 最多顯示25個(gè)特征 subplot(5,5,i); w_img reshape(W_trained(:, i), img_size, img_size); imagesc(w_img); colormap(gray); axis image off; title(sprintf(Hid %d, i)); end sgtitle(RBM學(xué)習(xí)到的隱藏層特征權(quán)重列); end運(yùn)行這段代碼后你會(huì)得到兩張圖。第一張是誤差曲線它應(yīng)該是一條平滑下降的曲線。如果曲線震蕩嘗試降低學(xué)習(xí)率。如果曲線幾乎水平嘗試增大學(xué)習(xí)率或檢查權(quán)重初始化是否過(guò)小。第二張圖是權(quán)重可視化。每個(gè)子圖代表一個(gè)隱藏單元與所有可見(jiàn)單元連接的權(quán)重。對(duì)于一個(gè)學(xué)習(xí)良好的、處理圖像數(shù)據(jù)的RBM這些權(quán)重圖應(yīng)該看起來(lái)像“邊緣檢測(cè)器”或“局部斑點(diǎn)”它們代表了模型從數(shù)據(jù)中提取的基礎(chǔ)特征。如果所有圖都是模糊的噪聲說(shuō)明模型可能沒(méi)有學(xué)到有意義的東西需要檢查數(shù)據(jù)、學(xué)習(xí)率或訓(xùn)練輪次。常見(jiàn)問(wèn)題與排查重構(gòu)誤差為NaN或Inf這幾乎總是因?yàn)閷W(xué)習(xí)率太大導(dǎo)致權(quán)重更新爆炸。立即降低學(xué)習(xí)率例如降到原來(lái)的1/10。同時(shí)檢查sigmoid函數(shù)的輸入是否過(guò)大導(dǎo)致指數(shù)運(yùn)算溢出。可以在sigmoid函數(shù)中加入數(shù)值穩(wěn)定處理y 1./(1 exp(-max(min(x, 50), -50)));將輸入限制在[-50, 50]區(qū)間。誤差不下降檢查數(shù)據(jù)確保數(shù)據(jù)已正確歸一化到[0,1]。打印幾行數(shù)據(jù)看看。檢查初始化權(quán)重初始化標(biāo)準(zhǔn)差std_dev不能為0或太小。嘗試0.01或0.1。檢查學(xué)習(xí)率學(xué)習(xí)率可能太小。嘗試增大。檢查梯度更新在訓(xùn)練循環(huán)中打印positive_associations和negative_associations的范數(shù)看看梯度是否非零且方向合理。訓(xùn)練速度慢確保你使用了矩陣運(yùn)算而不是在循環(huán)中對(duì)每個(gè)樣本單獨(dú)計(jì)算。對(duì)于大數(shù)據(jù)集可以考慮將數(shù)據(jù)轉(zhuǎn)換為single單精度類(lèi)型以減少內(nèi)存占用和加速計(jì)算但要注意精度損失。模型過(guò)擬合雖然RBM作為生成模型過(guò)擬合表現(xiàn)不如判別模型明顯但如果隱藏單元過(guò)多它可能會(huì)過(guò)度記憶訓(xùn)練數(shù)據(jù)。除了減少隱藏單元可以引入權(quán)重衰減即在梯度更新中加入一個(gè)懲罰項(xiàng)W W lr * (positive_associations - negative_associations - weight_cost * W)其中weight_cost是一個(gè)小的正數(shù)如0.0002。6. 模型評(píng)估與應(yīng)用重構(gòu)、采樣與特征提取訓(xùn)練完成后我們?cè)趺粗肋@個(gè)RBM模型是好是壞它除了能降低一個(gè)叫“重構(gòu)誤差”的數(shù)字還能做什么這部分我們將探討RBM的三個(gè)核心應(yīng)用數(shù)據(jù)重構(gòu)、隨機(jī)采樣和特征提取并在MATLAB中實(shí)現(xiàn)它們。這才是模型價(jià)值的真正體現(xiàn)。6.1 數(shù)據(jù)重構(gòu)與可視化重構(gòu)是檢驗(yàn)RBM學(xué)習(xí)質(zhì)量最直觀的方式。給定一個(gè)測(cè)試樣本我們讓RBM進(jìn)行一次“正向傳播”數(shù)據(jù)→隱藏層和一次“反向傳播”隱藏層→可見(jiàn)層得到重構(gòu)后的數(shù)據(jù)。比較原始數(shù)據(jù)和重構(gòu)數(shù)據(jù)可以直觀看出模型抓住了哪些主要特征丟失了哪些細(xì)節(jié)。% 假設(shè)我們有一個(gè)測(cè)試樣本 test_sample (1 x n_visible 的行向量) test_sample data(1, :); % 取第一個(gè)訓(xùn)練樣本作為示例 % 將行向量轉(zhuǎn)為列向量以便計(jì)算 v_test test_sample; % 正向傳播得到隱藏層概率/狀態(tài) h_prob sigmoid(c_trained W_trained * v_test); % h_state h_prob rand(size(h_prob)); % 采樣得到隱藏狀態(tài)這里我們用概率 % 反向傳播重構(gòu)可見(jiàn)層 v_recon_prob sigmoid(b_trained W_trained * h_prob); % 使用概率h_prob進(jìn)行重構(gòu) % 如果想得到二值重構(gòu)可以采樣v_recon v_recon_prob rand(size(v_recon_prob)); % 可視化比較 img_size sqrt(length(v_test)); if img_size floor(img_size) figure; subplot(1,2,1); imagesc(reshape(v_test, img_size, img_size)); title(原始圖像); colormap(gray); axis image off; subplot(1,2,2); imagesc(reshape(v_recon_prob, img_size, img_size)); title(RBM重構(gòu)圖像 (概率)); colormap(gray); axis image off; end % 計(jì)算該樣本的重構(gòu)誤差交叉熵 recon_err_single -sum(v_test .* log(v_recon_prob1e-10) (1-v_test).*log(1-v_recon_prob1e-10)); fprintf(單個(gè)樣本重構(gòu)誤差: %f\n, recon_err_single);一個(gè)好的RBM其重構(gòu)圖像應(yīng)該能清晰保留原始圖像的主體結(jié)構(gòu)。對(duì)于十字圖案重構(gòu)結(jié)果應(yīng)該依然是一個(gè)清晰的十字。如果重構(gòu)結(jié)果模糊或失真嚴(yán)重說(shuō)明模型沒(méi)有充分學(xué)習(xí)到數(shù)據(jù)的分布。6.2 吉布斯采樣與生成新樣本RBM作為一個(gè)生成模型最酷的能力是從其學(xué)到的分布中“幻想”出新的、與訓(xùn)練數(shù)據(jù)類(lèi)似但又不完全相同的樣本。這個(gè)過(guò)程通過(guò)吉布斯采樣實(shí)現(xiàn)從一個(gè)隨機(jī)初始的可見(jiàn)層狀態(tài)開(kāi)始交替地對(duì)隱藏層和可見(jiàn)層進(jìn)行采樣經(jīng)過(guò)足夠多的步驟后采樣得到的可見(jiàn)層狀態(tài)就來(lái)自于模型分布。function generated_sample rbm_gibbs_sample(W, b, c, n_steps, img_size) % 從訓(xùn)練好的RBM中通過(guò)吉布斯采樣生成樣本 % n_steps: 采樣步數(shù)越多樣本越接近模型分布 % img_size: 生成圖像的尺寸用于可視化 n_visible size(W, 1); n_hidden size(W, 2); % 隨機(jī)初始化可見(jiàn)層可以全0.5或隨機(jī)二值 v rand(n_visible, 1) 0.5; % 隨機(jī)二值初始化 % v 0.5 * ones(n_visible, 1); % 或用概率0.5初始化 for step 1:n_steps % 給定v采樣h h_prob sigmoid(c W * v); h h_prob rand(n_hidden, 1); % 給定h采樣v v_prob sigmoid(b W * h); v v_prob rand(n_visible, 1); end generated_sample v; % 最終采樣得到的可見(jiàn)層狀態(tài) % 可視化生成的樣本 if nargin 5 ~isempty(img_size) figure; imagesc(reshape(generated_sample, img_size, img_size)); colormap(gray); axis image off; title(sprintf(吉布斯采樣生成樣本 (步數(shù)%d), n_steps)); end end % 使用訓(xùn)練好的模型生成一個(gè)樣本 gen_sample rbm_gibbs_sample(W_trained, b_trained, c_trained, 1000, img_size);采樣步數(shù)n_steps需要足夠大以確保馬爾可夫鏈達(dá)到平穩(wěn)分布。通??梢詮?00步開(kāi)始嘗試觀察生成的樣本是否穩(wěn)定。如果模型訓(xùn)練得好生成的樣本應(yīng)該看起來(lái)像訓(xùn)練數(shù)據(jù)例如一個(gè)模糊但可辨的十字形狀。這是一個(gè)強(qiáng)有力的證明表明RBM確實(shí)捕捉到了數(shù)據(jù)的本質(zhì)特征。6.3 特征提取將RBM作為預(yù)處理工具RBM的隱藏層激活可以看作是對(duì)輸入數(shù)據(jù)的一種“編碼”或“特征表示”。這些特征通常比原始數(shù)據(jù)更具代表性和區(qū)分度。我們可以用訓(xùn)練好的RBM為下游任務(wù)如分類(lèi)提取特征。% 為整個(gè)數(shù)據(jù)集提取隱藏層特征 % data_matrix 是 n_samples x n_visible 的數(shù)據(jù)矩陣 data_matrix data; % 假設(shè)是我們的訓(xùn)練數(shù)據(jù) n_samples size(data_matrix, 1); % 將數(shù)據(jù)轉(zhuǎn)置為 n_visible x n_samples 以便矩陣乘法 data_for_feat data_matrix; hidden_features_prob sigmoid(bsxfun(plus, c_trained, W_trained * data_for_feat)); % hidden_features_prob 的維度是 n_hidden x n_samples % 轉(zhuǎn)置回來(lái)得到 n_samples x n_hidden 的特征矩陣 extracted_features hidden_features_prob; % 現(xiàn)在extracted_features 可以作為新的特征輸入到邏輯回歸、SVM等分類(lèi)器中。 fprintf(提取的特征矩陣維度: %d x %d\n, size(extracted_features));這里我們使用了隱藏層的概率值h_prob作為特征而不是采樣得到的二值狀態(tài)。概率值包含了更多信息通常作為特征效果更好。這些特征維度更低n_hidden維且是原始數(shù)據(jù)的一種非線性變換往往能提升后續(xù)分類(lèi)器的性能。注意在深度信念網(wǎng)絡(luò)中正是通過(guò)堆疊多個(gè)RBM將每一層的隱藏層特征作為下一層的輸入從而逐層學(xué)習(xí)到越來(lái)越抽象的特征表示。我們這里實(shí)現(xiàn)的單層RBM是構(gòu)建更復(fù)雜深度模型的基礎(chǔ)模塊。7. 性能優(yōu)化、擴(kuò)展與踩坑實(shí)錄當(dāng)你跑通了一個(gè)基礎(chǔ)的RBM并看到了初步結(jié)果后可能會(huì)想如何讓它跑得更快、更穩(wěn)、處理更復(fù)雜的數(shù)據(jù)這一節(jié)我將分享一些進(jìn)階的優(yōu)化技巧、處理非二值數(shù)據(jù)的方法以及我在實(shí)踐中踩過(guò)的一些“坑”。7.1 性能優(yōu)化向量化與并行化我們之前的實(shí)現(xiàn)已經(jīng)充分利用了MATLAB的矩陣運(yùn)算這是最大的性能優(yōu)勢(shì)。但仍有優(yōu)化空間使用單精度數(shù)據(jù)如果內(nèi)存和精度允許將數(shù)據(jù)和參數(shù)轉(zhuǎn)換為single類(lèi)型可以加速計(jì)算并減少內(nèi)存占用??梢栽跀?shù)據(jù)加載后使用data single(data);并在初始化參數(shù)時(shí)使用randn(..., single)。預(yù)分配數(shù)組在訓(xùn)練循環(huán)中我們?yōu)槊總€(gè)批次計(jì)算h0_prob,v1_prob等。確保這些中間變量不會(huì)在循環(huán)中動(dòng)態(tài)改變大小這有助于MATLAB的JIT即時(shí)編譯優(yōu)化。利用GPU對(duì)于大規(guī)模數(shù)據(jù)和大網(wǎng)絡(luò)MATLAB支持使用GPU加速。你可以使用gpuArray將數(shù)據(jù)和參數(shù)轉(zhuǎn)移到GPU上。例如data_gpu gpuArray(data);然后在GPU上進(jìn)行所有矩陣運(yùn)算。這通常能帶來(lái)數(shù)量級(jí)的加速但需要你有兼容的NVIDIA GPU和Parallel Computing Toolbox。7.2 處理實(shí)值數(shù)據(jù)高斯-伯努利RBM我們的RBM假設(shè)可見(jiàn)層是二值的。但現(xiàn)實(shí)中很多數(shù)據(jù)是實(shí)值的比如像素灰度值、音頻波形等。這時(shí)可以使用高斯-伯努利RBM。其核心改變?cè)谟诳梢?jiàn)層單元的條件分布從伯努利分布變?yōu)楦咚狗植颊龖B(tài)分布。這意味著可見(jiàn)層單元v_i的條件均值是sigmoid(b_i sum_j W_{ij} h_j)的函數(shù)但方差需要額外學(xué)習(xí)或固定。數(shù)據(jù)需要標(biāo)準(zhǔn)化為零均值和單位方差或某個(gè)固定方差這是高斯分布的要求。權(quán)重更新公式需要做相應(yīng)調(diào)整。在MATLAB中實(shí)現(xiàn)時(shí)一個(gè)常見(jiàn)的簡(jiǎn)化是固定可見(jiàn)層方差為1。此時(shí)可見(jiàn)層采樣公式變?yōu)関_i ~ N( mean_i, 1 )其中mean_i b_i sum_j W_{ij} h_j。 而重構(gòu)誤差則從交叉熵變?yōu)榫秸`差。這需要對(duì)train_rbm函數(shù)中的采樣和重構(gòu)誤差計(jì)算部分進(jìn)行修改。這是一個(gè)重要的擴(kuò)展方向讓你能處理MNIST原始灰度圖等數(shù)據(jù)。7.3 我的踩坑實(shí)錄與經(jīng)驗(yàn)技巧學(xué)習(xí)率與權(quán)重初始化的耦合我曾遇到過(guò)模型完全不學(xué)習(xí)的情況誤差曲線是一條直線。排查后發(fā)現(xiàn)是因?yàn)闄?quán)重初始化標(biāo)準(zhǔn)差std_dev設(shè)得太小如1e-5導(dǎo)致sigmoid函數(shù)的輸入始終在0附近梯度極小。無(wú)論學(xué)習(xí)率多大更新都微乎其微。經(jīng)驗(yàn)權(quán)重初始化的尺度要與學(xué)習(xí)率匹配。通常用1/sqrt(n_visible)或0.01是安全的起點(diǎn)。“死神經(jīng)元”問(wèn)題在訓(xùn)練過(guò)程中某些隱藏單元可能因?yàn)槌跏紮?quán)重不利或?qū)W習(xí)過(guò)程中梯度始終很小導(dǎo)致其激活概率永遠(yuǎn)接近0或1不再對(duì)學(xué)習(xí)有貢獻(xiàn)。這被稱(chēng)為“死神經(jīng)元”。對(duì)策可以監(jiān)控隱藏層的平均激活度。如果某個(gè)單元的平均激活度在整個(gè)訓(xùn)練集上長(zhǎng)期接近0或1可以考慮在偏置更新中加入一個(gè)“稀疏性”懲罰鼓勵(lì)平均激活度接近一個(gè)目標(biāo)值如0.1或者簡(jiǎn)單地重新初始化這個(gè)神經(jīng)元的權(quán)重。CD-K中K的選擇我們使用了CD-1即只進(jìn)行一次吉布斯采樣。理論上K越大CD-3 CD-10對(duì)模型期望的近似越好但計(jì)算成本也越高。在實(shí)踐中CD-1對(duì)于很多問(wèn)題已經(jīng)足夠好并且是訓(xùn)練深度信念網(wǎng)絡(luò)時(shí)的標(biāo)準(zhǔn)選擇。經(jīng)驗(yàn)除非你追求極致的模型性能并且有充足的計(jì)算資源否則CD-1是性?xún)r(jià)比最高的選擇。重構(gòu)誤差下降但生成樣本質(zhì)量差有時(shí)誤差曲線看起來(lái)很好但用吉布斯采樣生成的樣本卻是一團(tuán)噪聲。這可能是因?yàn)槟P拖萑肓四硞€(gè)局部最優(yōu)或者學(xué)習(xí)到的分布是多峰的而采樣過(guò)程沒(méi)有充分混合。排查嘗試增加吉布斯采樣的步數(shù)如5000步并使用不同的隨機(jī)種子初始化。同時(shí)檢查訓(xùn)練數(shù)據(jù)的多樣性是否足夠。MATLAB版本與函數(shù)兼容性代碼中使用了bsxfun函數(shù)這在舊版MATLAB中是必須的。但在R2016b及以后版本MATLAB引入了隱式擴(kuò)展可以直接使用、-、.*、./等運(yùn)算符對(duì)維度匹配的數(shù)組和向量進(jìn)行操作。如果你在新版MATLAB中看到關(guān)于bsxfun的警告可以安全地將其替換為直接運(yùn)算符。例如h0_prob sigmoid(bsxfun(plus, c, W * v0));可以寫(xiě)成h0_prob sigmoid(c W * v0);MATLAB會(huì)自動(dòng)將列向量c擴(kuò)展到與矩陣W*v0相同的列數(shù)。本文還有配套的精品資源點(diǎn)擊獲取