量化交易軟件:神經(jīng)網(wǎng)絡(luò)變得輕松12舍棄
概述
自從本系列文章開始以來(lái),赫茲量化已在研究各種神經(jīng)網(wǎng)絡(luò)模型方面取得了長(zhǎng)足的進(jìn)步。 但學(xué)習(xí)過(guò)程總是在沒(méi)有我們參與的情況下進(jìn)行的。 與此同時(shí),總是希望以某種方式幫助神經(jīng)網(wǎng)絡(luò)改進(jìn)訓(xùn)練效果,這也可能會(huì)設(shè)計(jì)神經(jīng)網(wǎng)絡(luò)收斂。 在本文中,我們將研究一種名為舍棄的方法。

編輯切換為居中
1. 舍棄:提升神經(jīng)網(wǎng)絡(luò)收斂性的一種方法
在訓(xùn)練神經(jīng)網(wǎng)絡(luò)時(shí),會(huì)將大量特征饋入每個(gè)神經(jīng)元,且很難評(píng)估每個(gè)獨(dú)立特征的影響。 結(jié)果就是,某些神經(jīng)元的誤差會(huì)被其他神經(jīng)元的調(diào)整值抹平,這些誤差從而會(huì)在神經(jīng)網(wǎng)絡(luò)輸出處累積。 這會(huì)導(dǎo)致訓(xùn)練在某個(gè)局部最小值處停止,且誤差較大。 這種效應(yīng)涉及特征檢測(cè)器的協(xié)同適應(yīng),其中每個(gè)特征的影響會(huì)隨環(huán)境而變化。 當(dāng)環(huán)境分解成單獨(dú)的特征,且可以分別評(píng)估每個(gè)特征的影響時(shí),很可能會(huì)有相反的效果。
2012年,多倫多大學(xué)的一組科學(xué)家提議從學(xué)習(xí)過(guò)程中隨機(jī)排除一些神經(jīng)元,作為復(fù)雜協(xié)同適應(yīng)問(wèn)題的解決方案 [12]。 訓(xùn)練中減少特征的數(shù)量,會(huì)增加每個(gè)特征的重要性,且特征的數(shù)量和質(zhì)量構(gòu)成的持續(xù)變化降低了它們協(xié)同適應(yīng)的風(fēng)險(xiǎn)。 此方法稱為舍棄。 有時(shí)拿這種方法的應(yīng)用與決策樹進(jìn)行比較:通過(guò)舍棄一些神經(jīng)元,赫茲量化在每次訓(xùn)練迭代中獲得一個(gè)含有其自身權(quán)重的新神經(jīng)網(wǎng)絡(luò)。 根據(jù)組合規(guī)則,這樣的網(wǎng)絡(luò)具有很大的可變性。

編輯切換為居中
在神經(jīng)網(wǎng)絡(luò)操作期間評(píng)估所有特征和神經(jīng)元,從而我們能得到所分析環(huán)境當(dāng)前狀態(tài)的最準(zhǔn)確和獨(dú)立的評(píng)估。
作者在他們的文章(12)中談及使用該方法來(lái)提高預(yù)訓(xùn)練模型品質(zhì)的可能性。
從數(shù)學(xué)的角度來(lái)看,赫茲量化可以這樣描述這個(gè)過(guò)程:以給定的概率 p 從過(guò)程中舍棄每個(gè)獨(dú)立的神經(jīng)元。 換句話說(shuō),神經(jīng)元能夠參與神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)過(guò)程的概率為 q = 1-p 。
由含有正態(tài)分布的偽隨機(jī)數(shù)生成器來(lái)判定將被排除的神經(jīng)元列表。 這種方式可以實(shí)現(xiàn)最大程度地統(tǒng)一排除神經(jīng)元。 赫茲量化將生成一個(gè)練習(xí)向量,其大小與輸入序列相等。 向量中的 "1" 將會(huì)參與訓(xùn)練,且 "0" 則為排除元素。
然而,排除已分析特征無(wú)疑會(huì)導(dǎo)致神經(jīng)元激活函數(shù)輸入量的減少。 為了補(bǔ)償這種影響,赫茲量化將每個(gè)特征的值乘以系數(shù) 1/q 。 該系數(shù)將提升該數(shù)值,因?yàn)楦怕?q 始終在 0 到 1 之間。

編輯
,
其中:
d — 舍棄結(jié)果向量的元素,q — 在訓(xùn)練過(guò)程中用到的神經(jīng)元概率,x — 掩碼向量的元素,n — 輸入序列的元素.
在學(xué)習(xí)過(guò)程中的前饋驗(yàn)算過(guò)程中,誤差梯度乘以上述函數(shù)的導(dǎo)數(shù)。 如您所見,在舍棄的情況下,反饋驗(yàn)算與前饋驗(yàn)算類似,均采用前饋驗(yàn)算的掩碼向量。

編輯
在神經(jīng)網(wǎng)絡(luò)的操作過(guò)程中,掩碼向量用 “1” 填充,這允許數(shù)值在兩個(gè)方向上平滑傳遞。
實(shí)際上,系數(shù) 1/q 在整個(gè)訓(xùn)練期間都是恒定的,因此我們可以輕松地一次性計(jì)算該系數(shù),然后將其代替 “1” 寫入掩碼張量當(dāng)中。 因此,在每次訓(xùn)練迭代中,赫茲量化可以排除系數(shù)的重新計(jì)算操作,并將其乘以掩碼 “1”。
2. 實(shí)現(xiàn)
如今,我們已研究過(guò)理論方面,我們來(lái)繼續(xù)研究如何在函數(shù)庫(kù)中實(shí)現(xiàn)此方法的變體。 赫茲量化遇到的第一件事是實(shí)現(xiàn)兩種不同算法。 其一在訓(xùn)練過(guò)程需要,而第二個(gè)則用于生產(chǎn)。 相應(yīng)地,我們需要根據(jù)每種獨(dú)立情況,為神經(jīng)元明確指出應(yīng)采用的算法。 為此目的,我們將在基準(zhǔn)神經(jīng)元級(jí)別引入 bTrain 標(biāo)志。 該標(biāo)志值對(duì)于訓(xùn)練 應(yīng)設(shè)為 true,而對(duì)于測(cè)試 則設(shè)為 false。
class CNeuronBaseOCL ? ?: ?public CObject ?{ protected: ? bool ? ? ? ? ? ? ? bTrain; ? ? ? ? ? ? ///< Training Mode Flag
以下輔助方法將控制該標(biāo)志值。
? virtual void ? ? ?TrainMode(bool flag) ? ? ? ? ? ? { ?bTrain=flag; ? ? ? ? ? ?}///< Set Training Mode Flag ? ? ? virtual bool ? ? ?TrainMode(void) ? ? ? ? ? ? ? ? ?{ ?return bTrain; ? ? ? ? ?}///< Get Training Mode Flag ? ?
該標(biāo)志特意在基準(zhǔn)神經(jīng)元級(jí)別實(shí)現(xiàn)。 如此在以后開發(fā)時(shí)能夠啟用舍棄相關(guān)的代碼。
2.1. 為我們的模型創(chuàng)建一個(gè)新類
為了實(shí)現(xiàn)舍棄算法,赫茲量化來(lái)創(chuàng)建新的 CNeuronDropoutOCL 類,它將包含在我們的模型當(dāng)中作為單獨(dú)的層。 新類將直接繼承自 CNeuronBaseOCL 基準(zhǔn)神經(jīng)元類。 在受保護(hù)模塊中聲明變量:
OutProbability — 指定神經(jīng)元的舍棄概率。
OutNumber ?— 神經(jīng)元的舍棄數(shù)量。
dInitValue ?— 掩碼向量初始化值;在本文的理論部分,該系數(shù)被指定為 1/q。
另外,聲明兩個(gè)指向類的指針:
DropOutMultiplier ?— 舍棄向量。
PrevLayer ?— 指向上一層對(duì)象的指針;它在測(cè)試和實(shí)際應(yīng)用時(shí)會(huì)用到。
class CNeuronDropoutOCL ? ?: ?public ? CNeuronBaseOCL ?{ protected: ? CNeuronBaseOCL ? ?*PrevLayer; ? double ? ? ? ? ? ?OutProbability; ? double ? ? ? ? ? ?OutNumber; ? CBufferDouble ? ? *DropOutMultiplier; ? double ? ? ? ? ? ?dInitValue; //--- ? virtual bool ? ? ?feedForward(CNeuronBaseOCL *NeuronOCL); ? ? ? ? ? ? ? ///<\brief Feed Forward method of calling kernel ::FeedForward().@param NeuronOCL Pointer to previous layer. ? virtual bool ? ? ?updateInputWeights(CNeuronBaseOCL *NeuronOCL) {return true;} ? ? ? ?///< Method for updating weights.@param NeuronOCL Pointer to previous layer. //--- ? int ? ? ? ? ? ? ? RND(void) ? { xor128; return (int)((double)(Neurons()-1)/UINT_MAX*rnd_w); ?} ? ///< Generates a random neuron position to turn off public: ? ? ? ? ? ? ? ? ? ? CNeuronDropoutOCL(void); ? ? ? ? ? ? ? ? ? ?~CNeuronDropoutOCL(void); //--- ? virtual bool ? ? ?Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl, uint numNeurons,double out_prob, ENUM_OPTIMIZATION optimization_type); ? ?///< Method of initialization class.@param[in] numOutputs Number of connections to next layer.@param[in] myIndex Index of neuron in layer.@param[in] open_cl Pointer to #COpenCLMy object. #param[in] numNeurons Number of neurons in layer #param[in] out_prob Probability of neurons shutdown @param optimization_type Optimization type (#ENUM_OPTIMIZATION)@return Boolen result of operations. //--- ? virtual int ? ? ? getOutputIndex(void) ? ? ? ? ?{ ?return (bTrain ? Output.GetIndex() : PrevLayer.getOutputIndex()); ? ? ? ? ? ? } ?///< Get index of output buffer @return Index ? virtual int ? ? ? getGradientIndex(void) ? ? ? ?{ ?return (bTrain ? Gradient.GetIndex() : PrevLayer.getGradientIndex()); ? ? ? ? ?} ?///< Get index of gradient buffer @return Index ? //--- ? virtual int ? ? ? getOutputVal(double &values[]) ? { ?return (bTrain ? Output.GetData(values) : PrevLayer.getOutputVal(values)); } ?///< Get values of output buffer @param[out] values Array of data @return number of items ? virtual int ? ? ? getOutputVal(CArrayDouble *values) ? { ?return (bTrain ? Output.GetData(values) : PrevLayer.getOutputVal(values)); } ?///< Get values of output buffer @param[out] values Array of data @return number of items ? virtual int ? ? ? getGradient(double &values[]) ? ?{ ?return (bTrain ? Gradient.GetData(values) : PrevLayer.getGradient(values)); ? ?} ?///< Get values of gradient buffer @param[out] values Array of data @return number of items ? virtual CBufferDouble ? *getOutput(void) ? ? ? ? ? { ?return (bTrain ? Output : PrevLayer.getOutput()); ? ? ?} ? ? ? ? ? ? ? ? ///< Get pointer of output buffer @return Pointer to object ? virtual CBufferDouble ? *getGradient(void) ? ? ? ? { ?return (bTrain ? Gradient : PrevLayer.getGradient()); ?} ? ? ? ? ? ? ? ? ///< Get pointer of gradient buffer @return Pointer to object //--- ? virtual bool ? ? ?calcInputGradients(CNeuronBaseOCL *NeuronOCL); ? ? ? ? ?///< Method to transfer gradient to previous layer by calling kernel ::CalcHiddenGradient(). @param NeuronOCL Pointer to next layer. ? //--- ? virtual bool ? ? ?Save(int const file_handle);///< Save method @param[in] file_handle handle of file @return logical result of operation ? virtual bool ? ? ?Load(int const file_handle);///< Load method @param[in] file_handle handle of file @return logical result of operation //--- ? virtual int ? ? ? Type(void) ? ? ? ?const ? ? ? ? ? ? ? ? ? ? ?{ ?return defNeuronDropoutOCL; ? ? ? ? ? ? ? ?}///< Identificator of class.@return Type of class ?};
您必須熟悉類方法的清單,因?yàn)樗鼈兌紩?huì)覆蓋父類的方法。 唯一排除在外的是 RND 方法,它用來(lái)生成均勻分布的偽隨機(jī)數(shù)。 在文章的第十三部分中已講述過(guò)該方法的算法。 在我們的神經(jīng)網(wǎng)絡(luò)里,為了確保所有對(duì)象中數(shù)值的最大可能隨機(jī)性,偽隨機(jī)序列生成器在實(shí)現(xiàn)時(shí)以宏替換來(lái)定義全局變量。
#define xor128 rnd_t=(rnd_x^(rnd_x<<11)); \ ? ? ? ? ? ? ? rnd_x=rnd_y; \ ? ? ? ? ? ? ? rnd_y=rnd_z; \ ? ? ? ? ? ? ? rnd_z=rnd_w; \ ? ? ? ? ? ? ? rnd_w=(rnd_w^(rnd_w>>19))^(rnd_t^(rnd_t>>8)) uint rnd_x=MathRand(), rnd_y=MathRand(), rnd_z=MathRand(), rnd_w=MathRand(), rnd_t=0;
所提議算法將生成一個(gè)范圍在 [0,UINT_MAX=4294967295] 內(nèi)的整數(shù)序列。 因此,在偽隨機(jī)序列生成器方法中,宏替換執(zhí)行之后,將結(jié)果值常規(guī)化為序列的大小。
int ? ? ? ? ? ? ? RND(void) ? { xor128; return (int)((double)(Neurons()-1)/UINT_MAX*rnd_w); ?}
如果您閱讀過(guò)本系列中的早前文章,您可能已經(jīng)注意到,在以前的版本中,赫茲量化沒(méi)有覆蓋來(lái)自其他對(duì)象的操控類數(shù)據(jù)緩沖區(qū)的方法。 當(dāng)神經(jīng)元訪問(wèn)上一層或下一層的數(shù)據(jù)時(shí),這些方法可在神經(jīng)網(wǎng)絡(luò)的各層之間交換數(shù)據(jù)。
選擇該解決方案是為了在實(shí)際應(yīng)用中優(yōu)化神經(jīng)網(wǎng)絡(luò)的運(yùn)行。 不要忘記僅在神經(jīng)網(wǎng)絡(luò)訓(xùn)練時(shí)才會(huì)用到舍棄層。 在測(cè)試和以后的應(yīng)用期間,會(huì)禁用此算法。 通過(guò)覆蓋數(shù)據(jù)緩沖區(qū)的訪問(wèn)方法,赫茲量化啟用略過(guò)舍棄層。 所有被覆蓋的方法都應(yīng)遵循相同的原理。 取代復(fù)制數(shù)據(jù),赫茲量化實(shí)現(xiàn)了用上一層緩沖區(qū)替換舍棄層緩沖區(qū)。 因此,在以后的操作期間,含有舍棄層的神經(jīng)網(wǎng)絡(luò)在速度上可比沒(méi)有舍棄層的類似網(wǎng)絡(luò),而我們?cè)谟?xùn)練階段已獲得了神經(jīng)元舍棄的所有優(yōu)勢(shì)。
virtual int ? ? ? getOutputIndex(void) ? ? { ?return (bTrain ? Output.GetIndex() : PrevLayer.getOutputIndex()); ? ? ?}
在附件中可找到所有類方法的完整代碼。
2.2. 前饋
傳統(tǒng)上,赫茲量化在 feedForward 方法中實(shí)現(xiàn)前饋驗(yàn)算。 在方法伊始,檢查接收到的指向神經(jīng)網(wǎng)絡(luò)上一層的指針,和指向 OpenCL 對(duì)象的指針的有效性。 此后,保存上一層所用的激活函數(shù),和指向上一層對(duì)象的指針。 對(duì)于神經(jīng)網(wǎng)絡(luò)實(shí)際操作模式,舍棄層的前饋驗(yàn)算到此結(jié)束。 以后嘗試從下一層訪問(wèn)該層將激活上述替換數(shù)據(jù)緩沖區(qū)的機(jī)制。
bool CNeuronDropoutOCL::feedForward(CNeuronBaseOCL *NeuronOCL) ?{ ? if(CheckPointer(OpenCL)==POINTER_INVALID || CheckPointer(NeuronOCL)==POINTER_INVALID) ? ? ?return false; //--- ? activation=(ENUM_ACTIVATION)NeuronOCL.Activation(); ? PrevLayer=NeuronOCL; ? if(!bTrain) ? ? ?return true;
后續(xù)迭代僅與神經(jīng)網(wǎng)絡(luò)訓(xùn)練模式相關(guān)。 首先,生成一個(gè)掩碼向量,在其中,赫茲量化需定義在此步驟中舍棄的神經(jīng)元。 將掩碼寫入 DropOutMultiplier 緩沖區(qū)中,檢查之前創(chuàng)建對(duì)象的可用性,并在必要時(shí)創(chuàng)建一個(gè)新對(duì)象。 用初始值初始化緩沖區(qū)。 為了降低計(jì)算量,赫茲量化以遞增的因子 1/q 來(lái)初始化緩沖區(qū)。
? if(CheckPointer(DropOutMultiplier)==POINTER_INVALID) ? ? ?DropOutMultiplier=new CBufferDouble(); ? if(!DropOutMultiplier.BufferInit(NeuronOCL.Neurons(),dInitValue)) ? ? ?return false; ? for(int i=0;i<OutNumber;i++) ? ? { ? ? ?uint p=RND(); ? ? ?double val=DropOutMultiplier.At(p); ? ? ?if(val==0 || val==DBL_MAX) ? ? ? ?{ ? ? ? ? i--; ? ? ? ? continue; ? ? ? ?} ? ? ?if(!DropOutMultiplier.Update(RND(),0)) ? ? ? ? return false; ? ? }
緩沖區(qū)初始化后,規(guī)劃一個(gè)循環(huán),而其重復(fù)次數(shù)等于要舍棄的神經(jīng)元數(shù)量。 緩沖區(qū)中隨機(jī)選擇的元素將以零值替換。 為避免在一個(gè)單元內(nèi)兩次寫入 “0” 的風(fēng)險(xiǎn),在循環(huán)內(nèi)部實(shí)現(xiàn)額外檢查。
生成掩碼后,直接在 GPU 內(nèi)存中創(chuàng)建一個(gè)緩沖區(qū),并傳輸數(shù)據(jù)。