DL4J中的循環(huán)神經(jīng)網(wǎng)絡(luò)
本文概述了在DL4J中如何使用循環(huán)神經(jīng)網(wǎng)絡(luò)的具體訓(xùn)練特征和實(shí)用性。本文假定對(duì)循環(huán)神經(jīng)網(wǎng)絡(luò)及其使用有一定了解,而不是對(duì)遞歸神經(jīng)網(wǎng)絡(luò)的介紹莹捡,并且假定你對(duì)它們的使用和術(shù)語(yǔ)有一些熟悉。
內(nèi)容
- 基礎(chǔ):數(shù)據(jù)和網(wǎng)絡(luò)配置
- RNN訓(xùn)練特征
- 通過時(shí)間截?cái)嗟姆聪騻鞑?/li>
- 掩碼:一對(duì)多、多對(duì)一和序列分類
- 訓(xùn)練后的掩碼與序列分類
- RNN層與其它層結(jié)合
- 測(cè)試時(shí)間:一步一步的預(yù)測(cè)
- 導(dǎo)入時(shí)間序列數(shù)據(jù)
- 示例
基礎(chǔ):數(shù)據(jù)和網(wǎng)絡(luò)配置
DL4J 目前支持以下類型的循環(huán)神經(jīng)網(wǎng)絡(luò)
- GravesLSTM (長(zhǎng)短期記憶)
- BidirectionalGravesLSTM(雙向格拉夫長(zhǎng)短期記憶)
- BaseRecurrent
每種網(wǎng)絡(luò)的Java文檔都是可用的, GravesLSTM,BidirectionalGravesLSTM, BaseRecurrent
用于RNN的數(shù)據(jù)
暫時(shí)考慮一個(gè)標(biāo)準(zhǔn)的前饋網(wǎng)絡(luò)(DL4J中的多層感知機(jī)或“密連層”)。這些網(wǎng)絡(luò)期望輸入和輸出數(shù)據(jù)是二維的:即,具有“形狀”的數(shù)據(jù)[numExamples,inputSize]玖喘。這意味著進(jìn)入前饋網(wǎng)絡(luò)的數(shù)據(jù)具有“numExamples”行/示例,其中每行由“inputSize”列組成羊始。單個(gè)示例將具有形狀[1,inputSize]冬三,但是在實(shí)踐中敌蚜,為了計(jì)算和優(yōu)化效率纷跛,我們通常使用多個(gè)示例。類似地刁品,標(biāo)準(zhǔn)前饋網(wǎng)絡(luò)的輸出數(shù)據(jù)也是二維的,具有形狀[numExamples,outputSize]浩姥。
相反挑随,RNN的數(shù)據(jù)是時(shí)間序列。因此勒叠,他們有3個(gè)維度:一個(gè)額外的時(shí)間維度眯分。輸入數(shù)據(jù)因此具有形狀[numExamples,inputSize,timeSeriesLength],輸出數(shù)據(jù)具有形狀[numExamples,outputSize,timeSeriesLength]。這意味著畏陕,我們的INDArray中的數(shù)據(jù)被布置成如此 使得位置(i盐固,j鱼蝉,k)的值是 小批量中第i個(gè)示例的第k個(gè)時(shí)間步驟的第j個(gè)值利术。該數(shù)據(jù)布局如下所示。
當(dāng)使用類CSVSequenceRecordReader導(dǎo)入時(shí)間序列數(shù)據(jù)時(shí)痊剖,數(shù)據(jù)文件中的每一行表示一個(gè)時(shí)間步驟,用第一行(或頭行后第一行(如果存在)中的最早觀察到的時(shí)間序列 及csv的最后一行中的最新觀察來表示捣郊。每個(gè)特征時(shí)間序列是CSV文件的單獨(dú)列。例如牺陶,如果你在時(shí)間序列中有五個(gè)特征歧蕉,每個(gè)特征具有120個(gè)觀察值,以及大小為53的訓(xùn)練和測(cè)試集框沟,那么將有106個(gè)輸入csv文件(53個(gè)輸入貌虾,53個(gè)標(biāo)簽)。53個(gè)輸入CSV文件將分別有五列和120行斩熊。標(biāo)簽CSV文件將有一列(標(biāo)簽)和一行。
?
RnnOutputLayer (循環(huán)神經(jīng)網(wǎng)絡(luò)輸出層)
循環(huán)神經(jīng)網(wǎng)絡(luò)輸出層是用作具有許多循環(huán)神經(jīng)網(wǎng)絡(luò)系統(tǒng)(用于回歸和分類任務(wù))的最后層的一種層愉阎。循環(huán)神經(jīng)網(wǎng)絡(luò)輸出層處理諸如評(píng)分計(jì)算、給定損失函數(shù)時(shí)的錯(cuò)誤計(jì)算(預(yù)測(cè)與實(shí)際)等問題吓笙。在功能上狗准,它與“標(biāo)準(zhǔn)”O(jiān)utputLayer類(與前饋網(wǎng)絡(luò)一起使用)非常相似;但是它同時(shí)輸出(并且期望作為標(biāo)簽/目標(biāo))三維時(shí)間序列數(shù)據(jù)集。
循環(huán)神經(jīng)網(wǎng)絡(luò)輸出層的配置遵循與其他層相同的設(shè)計(jì):例如烤蜕,將多層網(wǎng)絡(luò)中的第三層設(shè)置為循環(huán)神經(jīng)網(wǎng)絡(luò)輸出層以進(jìn)行分類:
.layer(2, new RnnOutputLayer.Builder(LossFunction.MCXENT).activation(Activation.SOFTMAX)
.weightInit(WeightInit.XAVIER).nIn(prevLayerSize).nOut(nOut).build())
在實(shí)踐中使用循環(huán)神經(jīng)網(wǎng)絡(luò)輸出層可以在示例中看到橱鹏,鏈接到本文末尾。
RNN 訓(xùn)練特征
通過時(shí)間截?cái)嗟姆聪騻鞑?/h3>
訓(xùn)練神經(jīng)網(wǎng)絡(luò)(包括RNNs)會(huì)在計(jì)算上非常苛刻莉兰。對(duì)于循環(huán)神經(jīng)網(wǎng)絡(luò)挑围,當(dāng)我們處理長(zhǎng)序列時(shí)尤其如此,即具有許多時(shí)間步長(zhǎng)的訓(xùn)練數(shù)據(jù)糖荒。
為了降低循環(huán)神經(jīng)網(wǎng)絡(luò)中每個(gè)參數(shù)更新的計(jì)算復(fù)雜度杉辙,提出了截?cái)喾聪騻鞑r(shí)間算法(BPTT)〈范洌總而言之蜘矢,對(duì)于給定的計(jì)算能力,它允許我們更快地訓(xùn)練網(wǎng)絡(luò)(通過執(zhí)行更頻繁的參數(shù)更新)综看。建議在輸入序列長(zhǎng)的時(shí)候使用截?cái)嗟腂PTT(通常超過幾百個(gè)時(shí)間步長(zhǎng))硼端。
考慮當(dāng)訓(xùn)練具有長(zhǎng)度為12個(gè)時(shí)間步長(zhǎng)的時(shí)間序列的循環(huán)神經(jīng)網(wǎng)絡(luò)時(shí)會(huì)發(fā)生什么。這里寓搬,我們需要進(jìn)行12步的正向傳遞珍昨,計(jì)算誤差(基于預(yù)測(cè)的與實(shí)際的),并且進(jìn)行12步的后向傳遞:
?
對(duì)于12個(gè)時(shí)間步長(zhǎng)句喷,在上面的圖像中镣典,這不是問題。然而唾琼,考慮到輸入時(shí)間序列是10000個(gè)或更多的時(shí)間步長(zhǎng)兄春。在這種情況下,對(duì)于每個(gè)參數(shù)更新的每個(gè)正向和向后傳遞锡溯,通過時(shí)間的標(biāo)準(zhǔn)反向傳播將需要10000個(gè)時(shí)間步驟赶舆。這計(jì)算要求當(dāng)然是非常大的。
在實(shí)踐中祭饭,截?cái)嗟腂PTT將前向和后向傳播分成一組較小的前向/后向傳播操作芜茵。這些向前/向后傳播片斷的長(zhǎng)度是由用戶設(shè)置的參數(shù)。例如倡蝙,如果我們使用長(zhǎng)度為4個(gè)時(shí)間步長(zhǎng)的截?cái)郆PTT九串,學(xué)習(xí)看起來如下:
?
請(qǐng)注意,截?cái)郆PTT和標(biāo)準(zhǔn)BPTT的總體復(fù)雜度大致相同——在前向/反向傳播中寺鸥,它們時(shí)間步數(shù)量都相同猪钮。然而,使用這種方法胆建,我們得到3個(gè)參數(shù)更新烤低,而不是一個(gè)近似相同的工作量。然而笆载,成本并不完全相同扑馁,每個(gè)參數(shù)更新都有少量的開銷涯呻。
截?cái)郆PTT的缺點(diǎn)是在截?cái)嗟腂PTT中學(xué)習(xí)的依賴的長(zhǎng)度可以短于完整的BPTT。這是很容易看到的:考慮上面的圖像檐蚜,TBPTT長(zhǎng)度為4魄懂。假設(shè)在時(shí)間步驟10沿侈,網(wǎng)絡(luò)需要存儲(chǔ)來自時(shí)間步驟0的一些信息闯第,以便做出準(zhǔn)確的預(yù)測(cè)。在標(biāo)準(zhǔn)的BPTT中缀拭,這是可以的:梯度可以從時(shí)間10到時(shí)間0沿著展開的網(wǎng)絡(luò)一路向后流動(dòng)咳短。在截?cái)嗟腂PTT中,這是有問題的:時(shí)間步10的梯度沒有返回到足夠遠(yuǎn)的地方蛛淋,導(dǎo)致存儲(chǔ)所需信息的所需參數(shù)更新咙好。這種折衷通常是值得的,并且(只要適當(dāng)?shù)卦O(shè)置截?cái)郆PTT長(zhǎng)度)褐荷,截?cái)郆PTT在實(shí)踐中工作得很好勾效。
在DL4J中使用截?cái)嗟腂PTT非常簡(jiǎn)單:只需將下列代碼添加到網(wǎng)絡(luò)配置中(最后,在網(wǎng)絡(luò)配置的最后.build()之前)
.backpropType(BackpropType.TruncatedBPTT)
.tBPTTLength(100)
上面的代碼片段將導(dǎo)致任意網(wǎng)絡(luò)訓(xùn)練(即叛甫,對(duì)MultiLayerNetwork.fit() 方法的調(diào)用)使用長(zhǎng)度為100步的片段的截?cái)郆PTT层宫。
一些值得注意的事情:
- 默認(rèn)情況下(如果不手動(dòng)指定反向傳播類型),DL4J將使用BackpropType.Standard(即其监,全BPTT)萌腿。
- tBPTTLength配置參數(shù)設(shè)置截?cái)嗟腂PTT傳遞的長(zhǎng)度。通常抖苦,這是在50到200個(gè)時(shí)間步長(zhǎng)的某個(gè)地方毁菱,不過取決于應(yīng)用程序和數(shù)據(jù)。
- 截?cái)嗟腂PTT長(zhǎng)度通常是總時(shí)間序列長(zhǎng)度(即锌历,200對(duì)序列長(zhǎng)度1000)的一部分贮庞,但是當(dāng)使用TBPTT(例如,具有兩個(gè)序列的小批—一個(gè)長(zhǎng)度為100和另一個(gè)長(zhǎng)度為1000——以及TBPTT長(zhǎng)度為200 -將正確工作)時(shí)究西,在同一個(gè)小批次中時(shí)可變長(zhǎng)度時(shí)間序列是可以的贸伐。
掩碼:一對(duì)多、多對(duì)一和序列分類
基于填充和掩碼的思想DL4J支持RNN的一些相關(guān)的訓(xùn)練特征怔揩。填充和掩碼允許我們支持訓(xùn)練情況捉邢,包括一對(duì)多、多對(duì)一商膊,還支持可變長(zhǎng)度時(shí)間序列(在同一小批量中)伏伐。
假設(shè)我們想訓(xùn)練一個(gè)具有不會(huì)在每一個(gè)時(shí)間步長(zhǎng)發(fā)生輸入或輸出的循環(huán)神經(jīng)網(wǎng)絡(luò)。這個(gè)例子(對(duì)于一個(gè)例子)在下面的圖像中顯示晕拆。DL4J支持所有這些情況的網(wǎng)絡(luò)訓(xùn)練:
?
沒有掩碼和填充藐翎,我們僅限于多對(duì)多的情況(上面材蹬,左邊):即,(a)所有示例都具有相同的長(zhǎng)度吝镣,(b)示例在所有時(shí)間步驟都有輸入和輸出堤器。
填充背后的思想很簡(jiǎn)單∧┘郑考慮在相同的小批量中兩個(gè)長(zhǎng)度分別為50和100時(shí)間步的時(shí)間序列闸溃。訓(xùn)練數(shù)據(jù)是矩形陣列;因此拱撵,我們填充(即辉川,向其添加零)較短的時(shí)間序列(對(duì)于輸入和輸出),使得輸入和輸出都具有相同的長(zhǎng)度(在本示例中:100個(gè)時(shí)間步驟)拴测。
當(dāng)然乓旗,如果這是我們?nèi)克龅模鼤?huì)在訓(xùn)練過程中產(chǎn)生問題集索。因此屿愚,除了填充,我們使用掩碼機(jī)制务荆。掩碼背后的思想很簡(jiǎn)單:我們有兩個(gè)額外的數(shù)組妆距,記錄輸入或輸出是否實(shí)際出現(xiàn)在給定時(shí)間步長(zhǎng)和示例中,或者輸入/輸出是否只是填充蛹含。
回想一下毅厚,對(duì)于RNN,我們的小批量處理數(shù)據(jù)具有3維浦箱,分別具有輸入和輸出的形狀[miniBatchSize吸耿、inputSize、timeSeriesLength]和[miniBatchSize酷窥、outputSize咽安、timeSeriesLength]。然后填充數(shù)組是二維的蓬推,輸入和輸出都具有形狀[miniBatchSize妆棒,timeSeriesLength],每個(gè)時(shí)間序列和示例的值是0 (‘a(chǎn)bsent’) or 1 (‘present’)沸伏。用于輸入和輸出的掩碼數(shù)組被存儲(chǔ)在單獨(dú)的數(shù)組中糕珊。
對(duì)于單個(gè)示例,輸入和輸出掩碼數(shù)組如下所示:
?
對(duì)于“不需要掩碼”的情況毅糟,我們可以等效地使用所有1的掩碼數(shù)組红选,這將給出與完全沒有掩碼數(shù)組相同的結(jié)果陶珠。還要注意靖榕,在學(xué)習(xí)RNN時(shí)可以使用零、一或兩個(gè)掩碼數(shù)組——例如隔节,多對(duì)一的情況可以只針對(duì)輸出使用掩碼數(shù)組沥曹。
在實(shí)踐中:這些填充數(shù)組通常在數(shù)據(jù)導(dǎo)入階段創(chuàng)建(例如呵萨,由SequenceRecordReaderDatasetIterator(稍后討論)創(chuàng)建)窄锅,并且包含在DataSet對(duì)象中藻肄。如果DataSet包含掩碼數(shù)組,多層網(wǎng)絡(luò)擬合將在訓(xùn)練期間自動(dòng)使用它們间学。如果它們不存在殷费,則不使用掩碼功能。
帶有掩碼的評(píng)估與評(píng)分
當(dāng)進(jìn)行評(píng)分和評(píng)估時(shí)(也就是說菱鸥,當(dāng)評(píng)估RNN分類器的精度)時(shí)宗兼,掩碼數(shù)組也是重要的躏鱼。例如氮采,考慮多對(duì)一的情況:每個(gè)示例只有一個(gè)輸出,并且任何評(píng)估都應(yīng)該考慮這一點(diǎn)染苛。
使用(輸出)掩碼數(shù)組的評(píng)估可以在評(píng)估時(shí)使用鹊漠,通過把它傳遞到以下方法:
Evaluation.evalTimeSeries(INDArray labels, INDArray predicted, INDArray outputMask)
其中,標(biāo)簽是實(shí)際輸出(3d時(shí)間序列)茶行,預(yù)測(cè)的是網(wǎng)絡(luò)預(yù)測(cè)(3d時(shí)間序列躯概,與標(biāo)簽的形狀相同),并且outputMask是用于輸出的2d掩碼數(shù)組畔师。注意娶靡,評(píng)估不需要輸入掩碼數(shù)組。
得分計(jì)算也將利用掩碼數(shù)組看锉,通過MultiLayerNetwork.score(DataSet) 方法姿锭。同樣,如果DataSet包含輸出掩碼數(shù)組伯铣,那么在計(jì)算網(wǎng)絡(luò)的得分(損失函數(shù)-均方誤差呻此、負(fù)對(duì)數(shù)似然等)時(shí)將自動(dòng)使用它。
訓(xùn)練后的掩碼與序列分類
序列分類是掩碼的一種常用方法腔寡。其思想是焚鲜,盡管我們有一個(gè)序列(時(shí)間序列)作為輸入,但我們只希望為整個(gè)序列提供一個(gè)單一的標(biāo)簽(而不是在序列中的每個(gè)時(shí)間步提供一個(gè)標(biāo)簽)放前。
然而忿磅,RNN通過設(shè)計(jì)輸出序列,輸入序列的長(zhǎng)度相同凭语。對(duì)于序列分類葱她,掩碼允許我們?cè)谧詈蟮臅r(shí)間步用這個(gè)單一標(biāo)簽訓(xùn)練網(wǎng)絡(luò),我們本質(zhì)上告訴網(wǎng)絡(luò)除了最后的時(shí)間步之外實(shí)際上沒有任何標(biāo)簽數(shù)據(jù)叽粹。
現(xiàn)在览效,假設(shè)我們已經(jīng)訓(xùn)練了我們的網(wǎng)絡(luò)却舀,并且希望從時(shí)間序列輸出數(shù)組獲得最后的預(yù)測(cè)時(shí)間步。我們?cè)撛趺醋瞿兀?/p>
為了得到最后一個(gè)時(shí)間步锤灿,有兩個(gè)案例需要注意挽拔。首先,當(dāng)只有一個(gè)示例時(shí)但校,實(shí)際上不需要使用掩碼數(shù)組:我們只需要獲得輸出數(shù)組中的最后一個(gè)時(shí)間步:
INDArray timeSeriesFeatures = ...;
INDArray timeSeriesOutput = myNetwork.output(timeSeriesFeatures);
int timeSeriesLength = timeSeriesOutput.size(2); //Size of time dimension
INDArray lastTimeStepProbabilities = timeSeriesOutput.get(NDArrayIndex.point(0), NDArrayIndex.all(), NDArrayIndex.point(timeSeriesLength-1));
假設(shè)分類(不過螃诅,回歸的過程相同),上面的最后一行給出了最后一個(gè)時(shí)間步的概率状囱,即序列分類的類概率术裸。
稍微復(fù)雜一點(diǎn)的情況是,在一個(gè)小批(特征數(shù)組)中有多個(gè)示例亭枷,其中每個(gè)示例的長(zhǎng)度不同袭艺。(如果所有長(zhǎng)度相同:我們可以使用與上面相同的過程)。
在這個(gè)“可變長(zhǎng)度”的情況下叨粘,我們需要分別為每個(gè)示例獲取最后一個(gè)時(shí)間步猾编。如果數(shù)據(jù)流管道中的每個(gè)示例都有時(shí)間序列長(zhǎng)度,那么就變得簡(jiǎn)單了:我們只是迭代示例升敲,用該示例的長(zhǎng)度替換上面代碼中的timeSeriesLength
答倡。
如果我們沒有直接的時(shí)間序列的長(zhǎng)度,我們需要從掩碼數(shù)組中提取它們驴党。
如果我們有一個(gè)標(biāo)簽掩碼數(shù)組(它是一個(gè)one-hot向量瘪撇,像每個(gè)時(shí)間序列[0,0港庄,01,1,0]):
INDArray labelsMaskArray = ...;
INDArray lastTimeStepIndices = Nd4j.argMax(labelMaskArray,1);
或者倔既,如果我們只有特征掩碼:一個(gè)快速和粗爆的方法就是使用這個(gè):
INDArray featuresMaskArray = ...;
int longestTimeSeries = featuresMaskArray.size(1);
INDArray linspace = Nd4j.linspace(1,longestTimeSeries,longestTimeSeries);
INDArray temp = featuresMaskArray.mulColumnVector(linspace);
INDArray lastTimeStepIndices = Nd4j.argMax(temp,1);
要理解這里正在發(fā)生的事情,請(qǐng)注意攘轩,最初我們有一個(gè)特征掩碼叉存,如[1,1,1,1,0],我們希望從中獲得最后一個(gè)非零元素度帮。我們映射[1,1,1,1,0] 到 [1歼捏,2,3笨篷,4瞳秽,0],然后得到最大的元素(這是最后一個(gè)時(shí)間步長(zhǎng))率翅。
在這兩種情況下练俐,我們都可以做到以下幾點(diǎn):
int numExamples = timeSeriesFeatures.size(0);
for( int i=0; i<numExamples; i++ ){
int thisTimeSeriesLastIndex = lastTimeStepIndices.getInt(i);
INDArray thisExampleProbabilities = timeSeriesOutput.get(NDArrayIndex.point(i), NDArrayIndex.all(), NDArrayIndex.point(thisTimeSeriesLastIndex));
}
RNN層與其它層結(jié)合
DL4J中的RNN層可以與其他層類型相結(jié)合。例如冕臭,可以在同一網(wǎng)絡(luò)中組合DenseLayer(密連層)和LSTM(長(zhǎng)短記錄單元)層腺晾,或者組合用于視頻的卷積(CNN)層和LSTM層燕锥。
當(dāng)然,DenseLayer(密連層)和卷積層不處理時(shí)間序列數(shù)據(jù)——他們期望不同類型的輸入悯蝉。為了解決這個(gè)問題归形,我們需要使用層預(yù)處理器功能:例如,CnnToRnnPreProcessor和FeedForwardToRnnPreprocessor類鼻由。請(qǐng)參見這里所有預(yù)處理器暇榴。幸運(yùn)的是,在大多數(shù)情況下蕉世,DL4J配置系統(tǒng)將根據(jù)需要自動(dòng)添加這些預(yù)處理器蔼紧。然而,可以手動(dòng)添加預(yù)處理器(覆蓋每個(gè)層自動(dòng)添加的預(yù)處理器)狠轻。
例如奸例,為了手動(dòng)添在1層和2層之間添加預(yù)處理器,請(qǐng)將下列內(nèi)容添加到網(wǎng)絡(luò)配置中:.inputPreProcessor(2, new RnnToFeedForwardPreProcessor())
測(cè)試時(shí)間:一步一步的預(yù)測(cè)
與其他類型的神經(jīng)網(wǎng)絡(luò)一樣哈误,可以使用MultiLayerNetwork.output()和MultiLayerNetwork.feedForward()
方法生成對(duì)RNNs的預(yù)測(cè)哩至。這些方法在許多情況下是有用的躏嚎;然而蜜自,這些方法的局限性在于,我們只能對(duì)時(shí)間序列從零開始每次生成預(yù)測(cè)卢佣。
例如重荠,考慮我們希望在實(shí)時(shí)系統(tǒng)中生成預(yù)測(cè)的情況,其中這些預(yù)測(cè)基于大量的歷史虚茶。在這種情況下戈鲁,使用輸出/前饋方法是不切實(shí)際的,因?yàn)樗鼈冊(cè)诿看握{(diào)用整個(gè)數(shù)據(jù)歷史時(shí)進(jìn)行完全的正向傳遞嘹叫。如果我們希望在每個(gè)時(shí)間步長(zhǎng)對(duì)單個(gè)時(shí)間步長(zhǎng)進(jìn)行預(yù)測(cè)婆殿,那么這些方法可能既(a)非常耗性能,又(b)浪費(fèi)罩扇,因?yàn)樗鼈円槐橛忠槐榈剡M(jìn)行相同的計(jì)算婆芦。
對(duì)于這些情況,多層網(wǎng)絡(luò)提供了四種方法:
rnnTimeStep(INDArray)
rnnClearPreviousState()
rnnGetPreviousState(int layer)
rnnSetPreviousState(int layer, Map<String,INDArray> state)
rnnTimeStep()方法被設(shè)計(jì)成允許有效地執(zhí)行前向傳遞(預(yù)測(cè))喂饥,一次執(zhí)行一個(gè)或多個(gè)步驟消约。與輸出/前饋方法不同,rnnTimeStep方法在被調(diào)用時(shí)跟蹤RNN層的內(nèi)部狀態(tài)员帮。重要的是要注意或粮,rnnTimeStep和輸出/feedForward方法的輸出應(yīng)該是相同的(對(duì)于每個(gè)時(shí)間步),無論我們是一次全部做出這些預(yù)測(cè)(輸出/feedForward)捞高,還是每次生成一個(gè)或多個(gè)步驟(rnnTimeStep)氯材。因此渣锦,唯一的區(qū)別應(yīng)該是計(jì)算成本。
總之氢哮,MultiLayerNetwork.rnnTimeStep()方法做了兩件事:
- 使用先前存儲(chǔ)狀態(tài)(如果有的話)生成輸出/預(yù)測(cè)(前向傳遞)
- 更新存儲(chǔ)的狀態(tài)泡挺,存儲(chǔ)最后一個(gè)時(shí)間步的激活(準(zhǔn)備下次rnnTimeStep被調(diào)用時(shí)使用)
例如,假設(shè)我們想使用RNN來預(yù)測(cè)天氣命浴,提前一小時(shí)(基于前面100小時(shí)的天氣作為輸入)娄猫。如果我們要使用輸出方法,在每一小時(shí)生闲,我們需要輸入整整100個(gè)小時(shí)的數(shù)據(jù)媳溺,以預(yù)測(cè)101個(gè)小時(shí)的天氣。然后碍讯,為了預(yù)測(cè)102小時(shí)的天氣悬蔽,我們需要輸入100小時(shí)(或101小時(shí))的數(shù)據(jù),103小時(shí)等等捉兴。
或者蝎困,我們可以使用rnnTimeStep方法。當(dāng)然倍啥,如果我們要在進(jìn)行第一次預(yù)測(cè)之前利用全部100個(gè)小時(shí)的歷史禾乘,我們?nèi)匀恍枰M(jìn)行全面的向前傳遞:
?
我們第一次調(diào)用rnnTimeStep時(shí),兩種方法之間唯一的實(shí)際區(qū)別是存儲(chǔ)了上一個(gè)時(shí)間步的激活/狀態(tài)——這用橙色表示虽缕。但是始藕,下次我們使用rnnTimeStep方法時(shí),這個(gè)存儲(chǔ)狀態(tài)將被用于做出下一個(gè)預(yù)測(cè):
?
這里有許多重要的區(qū)別:
- 在第二個(gè)圖片中(rnnTimeStep的第二次調(diào)用)氮趋,輸入數(shù)據(jù)由單個(gè)時(shí)間步組成伍派,而不是由數(shù)據(jù)的完整歷史組成
- 前向傳播是一個(gè)單一的時(shí)間步(與數(shù)百個(gè)或更多)相比。
- rnnTimeStep方法返回后剩胁,內(nèi)部狀態(tài)將自動(dòng)更新诉植。因此,可以以與時(shí)間102相同的方式進(jìn)行時(shí)間103的預(yù)測(cè)昵观。等等晾腔。
但是,如果希望開始對(duì)新的(完全獨(dú)立的)時(shí)間序列進(jìn)行預(yù)測(cè)索昂,則必須(而且很重要)使用MultiLayerNetwork.rnnClearPreviousState()
方法手動(dòng)清除存儲(chǔ)的狀態(tài)建车。這將重置網(wǎng)絡(luò)中所有循環(huán)層的內(nèi)部狀態(tài)。
如果需要存儲(chǔ)或設(shè)置用于預(yù)測(cè)的RNN的內(nèi)部狀態(tài)椒惨,則可以針對(duì)每一層分別使用rnnGetPreviousState和rnnSetPreviousState方法缤至。例如,在序列化(網(wǎng)絡(luò)保存/加載)期間,這可能是有用的领斥,因?yàn)閞nnTimeStep方法中的內(nèi)部網(wǎng)絡(luò)狀態(tài)在缺省情況下沒有被保存嫉到,并且必須單獨(dú)保存和加載。注意月洛,這些GET/SET狀態(tài)方法返回并接受一個(gè)由激活類型作為鍵的映射何恶。例如,在LSTM模型中嚼黔,有必要存儲(chǔ)輸出激活和存儲(chǔ)單元狀態(tài)细层。
其他一些注意事項(xiàng):
我們可以同時(shí)為多個(gè)獨(dú)立的示例/預(yù)測(cè)使用rnnTimeStep方法。在上面的天氣示例中唬涧,例如疫赎,我們希望使用相同的神經(jīng)網(wǎng)絡(luò)對(duì)多個(gè)地點(diǎn)進(jìn)行預(yù)測(cè)。這與訓(xùn)練和前向傳遞/輸出方法相同:多行(輸入數(shù)據(jù)中的維度0)用于多個(gè)示例碎节。
如果沒有設(shè)置歷史/存儲(chǔ)狀態(tài)(即捧搞,最初或調(diào)用rnnClearPreviousState之后),則使用默認(rèn)初始化(零)狮荔。這是與訓(xùn)練過程相同的方法胎撇。
-
rnnTimeStep可以同時(shí)用于任意數(shù)量的時(shí)間步-不只是一個(gè)時(shí)間步。然而殖氏,重要的是要注意:
- 對(duì)于單個(gè)時(shí)間步預(yù)測(cè):數(shù)據(jù)是二維的晚树,形狀是 [numExamples,nIn]受葛;在這種情況下题涨,輸出也是二維的,形狀是[numExamples总滩,nOut]。
- 對(duì)于多個(gè)時(shí)間步預(yù)測(cè):數(shù)據(jù)是三維的巡雨,具有形狀[numExamples,nIn,numTimeSteps]闰渔;輸出將具有形狀[numExamples,nOut,numTimeSteps]。同樣铐望,最后的時(shí)間步激活和以前一樣被存儲(chǔ)冈涧。
在rnnTimeStep的調(diào)用之間不可能改變示例的數(shù)量(換句話說,如果rnnTimeStep的第一次使用是針對(duì)例如3個(gè)示例正蛙,則所有后續(xù)的調(diào)用必須具有3個(gè)示例)督弓。在重置內(nèi)部狀態(tài)之后(使用rnnClearPreviousState()),任何數(shù)量的示例都可以用于rnnTimeStep的下一次調(diào)用乒验。
rnnTimeStep方法不改變參數(shù)愚隧,只在訓(xùn)練完成后才使用網(wǎng)絡(luò)。
rnnTimeStep方法與包含單個(gè)和堆疊/多個(gè)RNN層的網(wǎng)絡(luò)以及結(jié)合其他層類型(例如卷積層或密連層)的網(wǎng)絡(luò)一起工作锻全。
RnnOutputLayer 層類型不具有任何內(nèi)部狀態(tài)狂塘,因?yàn)樗鼪]有任何循環(huán)連接录煤。
導(dǎo)入時(shí)間序列數(shù)據(jù)
RNN的數(shù)據(jù)導(dǎo)入是復(fù)雜的,因?yàn)槲覀冇卸喾N不同類型的數(shù)據(jù)可用于RNN:一對(duì)多荞胡、多對(duì)一妈踊、可變長(zhǎng)度時(shí)間序列等。本節(jié)將描述當(dāng)前實(shí)現(xiàn)的DL4J的數(shù)據(jù)導(dǎo)入機(jī)制泪漂。
這里描述的方法利用SequenceRecordReaderDataSetIterator類廊营,以及來自DataVec的CSVSequenceRecordReader類。此方法目前允許你從文件中加載被(制表符萝勤、逗號(hào)等)分隔的數(shù)據(jù)赘风,其中每個(gè)時(shí)間序列位于單獨(dú)的文件中。該方法還支持:
- 可變長(zhǎng)度時(shí)間序列輸入
- 一對(duì)多和多對(duì)一數(shù)據(jù)加載(輸入和標(biāo)簽在不同文件中)
- 用于分類的從索引到one-hot表示(即“2”到[0,0,1,0])的標(biāo)簽轉(zhuǎn)換
- 跳過數(shù)據(jù)文件開始時(shí)的固定/指定行數(shù)(即注釋或頭行)
注意在所有情況下,數(shù)據(jù)文件中的每一行代表一個(gè)時(shí)間步垂谢。
(除了下面的例子诀姚,你可能會(huì)發(fā)現(xiàn)這些單元測(cè)試是有用處的。)
示例1:在單獨(dú)文件中同一長(zhǎng)度瞬捕、輸入和標(biāo)簽的時(shí)間序列
假設(shè)在我們的訓(xùn)練數(shù)據(jù)中有10個(gè)時(shí)間序列,由20個(gè)文件表示:每個(gè)時(shí)間序列有10個(gè)文件用于輸入舵抹,而輸出/標(biāo)簽有10個(gè)文件》净ⅲ現(xiàn)在,假設(shè)這20個(gè)文件都包含相同數(shù)量的時(shí)間步(即惧蛹,相同的行數(shù))扇救。
為了使用SequenceRecordReaderDataSetIterator和CSVSequenceRecordReader方法,我們首先創(chuàng)建兩個(gè)CSVSequenceRecordReader對(duì)象香嗓,一個(gè)用于輸入迅腔,一個(gè)用于標(biāo)簽:
SequenceRecordReader featureReader = new CSVSequenceRecordReader(1, ",");
SequenceRecordReader labelReader = new CSVSequenceRecordReader(1, ",");
這個(gè)特定的構(gòu)造函數(shù)需要跳過的行數(shù)(這里跳過的1行)和分隔符(這里使用逗號(hào)字符)。
第二靠娱,我們需要初始化這兩個(gè)讀取器沧烈,告訴他們從哪里獲取數(shù)據(jù)。我們使用一個(gè)InputSplit對(duì)象來實(shí)現(xiàn)這一點(diǎn)像云。假設(shè)我們的時(shí)間序列被編號(hào)锌雀,文件名為“myInput_0.csv”、“myInput_1.csv”迅诬、“myLabels_0.csv”等等腋逆。一種方法是使用NumberedFileInputSplit:
featureReader.initialize(new NumberedFileInputSplit("/path/to/data/myInput_%d.csv", 0, 9));
labelReader.initialize(new NumberedFileInputSplit(/path/to/data/myLabels_%d.csv", 0, 9));
在這個(gè)特定的方法中,“%d”被替換為相應(yīng)的數(shù)字侈贷,并且使用數(shù)字0到9(包括 )惩歉。
最后,我們可以創(chuàng)建我們的SequenceRecordReaderdataSetIterator:
DataSetIterator iter = new SequenceRecordReaderDataSetIterator(featureReader, labelReader, miniBatchSize, numPossibleLabels, regression);
這個(gè)DataSetIterator可以被傳送到MultiLayerNetwork.fit() 方法來訓(xùn)練網(wǎng)絡(luò)。
miniBatchSize參數(shù)指定每個(gè)小批量中的實(shí)例數(shù)量(時(shí)間序列)柬泽。例如慎菲,對(duì)于總共10個(gè)文件,miniBatchSize為5將給我們兩個(gè)數(shù)據(jù)集锨并,每個(gè)數(shù)據(jù)集包含2個(gè)小批(DataSet對(duì)象)露该,每個(gè)小批中包含5個(gè)時(shí)間序列。
注意:
- 對(duì)于分類問題: numPossibleLabels是你的數(shù)據(jù)集中類別的數(shù)量 第煮。 使用 regression = false解幼。
- 標(biāo)簽數(shù)據(jù):每行一個(gè)值,作為一個(gè)分類索引
- 標(biāo)簽數(shù)據(jù)將自動(dòng)轉(zhuǎn)換為one-hot表示包警。
- 對(duì)于回歸問題: numPossibleLabels不被使用(設(shè)為任何值) 并使用 regression = true撵摆。
- 輸入和標(biāo)簽中的值可以是任意的(不像分類:可以有任意數(shù)量的輸出)。
- 當(dāng)regression = true 不進(jìn)行標(biāo)簽的處理害晦。
示例2:在同一文件中同一長(zhǎng)度特铝、輸入和標(biāo)簽的時(shí)間序列。
根據(jù)上一個(gè)示例壹瘟,假設(shè)我們的輸入數(shù)據(jù)和標(biāo)簽不是一個(gè)單獨(dú)的文件鲫剿,而是在同一個(gè)文件中。但是稻轨,每個(gè)時(shí)間序列仍然在一個(gè)單獨(dú)的文件中灵莲。
從DL4J 0.4-rc3.8開始,這種方法對(duì)輸出具有單列的限制(分類索引或單個(gè)實(shí)值回歸輸出)
在這種情況下殴俱,我們創(chuàng)建并初始化單個(gè)讀取器政冻。同樣,我們跳過一個(gè)標(biāo)題行线欲,并將格式指定為逗號(hào)分隔明场,并假設(shè)我們的數(shù)據(jù)文件名為“myData_0.csv”, …, “myData_9.csv”:
SequenceRecordReader reader = new CSVSequenceRecordReader(1, ",");
reader.initialize(new NumberedFileInputSplit("/path/to/data/myData_%d.csv", 0, 9));
DataSetIterator iterClassification = new SequenceRecordReaderDataSetIterator(reader, miniBatchSize, numPossibleLabels, labelIndex, false);
miniBatchSize
和numPossibleLabels
與前面的示例相同。這里询筏,labelIndex
指定標(biāo)簽在哪個(gè)列榕堰。例如,如果標(biāo)簽在第五列中嫌套,則使用labelIndex= 4(即,列被索引為0到numColumns-1)圾旨。
對(duì)于單一輸出值的回歸踱讨,我們使用:
DataSetIterator iterRegression = new SequenceRecordReaderDataSetIterator(reader, miniBatchSize, -1, labelIndex, true);
同樣,numPossibleLabels參數(shù)不用于回歸砍的。
示例3:不同長(zhǎng)度的時(shí)間序列(多對(duì)多)
根據(jù)前兩個(gè)示例痹筛,假設(shè)對(duì)于每個(gè)示例,輸入和標(biāo)簽具有相同的長(zhǎng)度,但是這些長(zhǎng)度在時(shí)間序列之間不同帚稠。
我們可以使用相同的方法(CSVSequenceRecordReader和SequenceRecordReaderDataSetIterator)谣旁,不過使用不同的構(gòu)造函數(shù):
DataSetIterator variableLengthIter = new SequenceRecordReaderDataSetIterator(featureReader, labelReader, miniBatchSize, numPossibleLabels, regression, SequenceRecordReaderDataSetIterator.AlignmentMode.ALIGN_END);
此處的參數(shù)與前面的示例相同,除了AlignmentMode.ALIGN_END滋早。這種對(duì)齊模式輸入告訴SequenceRecordReaderDataSetIterator需要兩件事:
- 時(shí)間序列可以具有不同的長(zhǎng)度榄审。
- 為每個(gè)示例把輸入和標(biāo)簽對(duì)齊,以使它們的最后值出現(xiàn)在同一時(shí)間步杆麸。
注意搁进,如果特征和標(biāo)簽總是具有相同的長(zhǎng)度(如示例3中的假設(shè)),那么兩個(gè)對(duì)齊模式(AlignmentMode.ALIGN_END 和 AlignmentMode.ALIGN_START)將給出相同的輸出昔头。對(duì)齊模式選項(xiàng)將在下一節(jié)中解釋饼问。
還要注意:在數(shù)據(jù)數(shù)組中,可變長(zhǎng)度時(shí)間序列總是在時(shí)間0處開始:如果要求揭斧,將在時(shí)間序列結(jié)束之后添加填充莱革。
與上面的示例1和2不同,上面的variableLengthIter實(shí)例生成的DataSet對(duì)象還將包括輸入和掩碼數(shù)組讹开,如本文前面所述盅视。
例4:多對(duì)一和一對(duì)多數(shù)據(jù)
我們還可以使用示例3中的AlignmentMode功能來實(shí)現(xiàn)多對(duì)一RNN序列分類器。在這里萧吠,讓我們假設(shè):
- 輸入和標(biāo)簽在單獨(dú)的被分隔的文件中左冬。
- 標(biāo)簽文件包含一個(gè)單行(時(shí)間步)(用于分類的類索引,或用于回歸的一個(gè)或多個(gè)數(shù)字)纸型。
- 示例之間的輸入長(zhǎng)度可以(可選地)不同拇砰。
實(shí)際上,與示例3相同的方法可以做到這一點(diǎn):
DataSetIterator variableLengthIter = new SequenceRecordReaderDataSetIterator(featureReader, labelReader, miniBatchSize, numPossibleLabels, regression, SequenceRecordReaderDataSetIterator.AlignmentMode.ALIGN_END);
對(duì)準(zhǔn)模式相對(duì)簡(jiǎn)單狰腌。它們指定是否填充較短時(shí)間序列的開始或結(jié)束除破。下面的圖表展示了它如何與掩碼數(shù)組(如本文前面所討論的)一起工作:
?
一對(duì)多個(gè)案例(類似于上面的最后一個(gè)案例,但只有一個(gè)輸入)是通過使用AlignmentMode.ALIGN_START實(shí)現(xiàn)琼腔。
注意瑰枫,在包含不同長(zhǎng)度的時(shí)間序列的訓(xùn)練數(shù)據(jù)的情況下,標(biāo)簽和輸入將針對(duì)每個(gè)示例單獨(dú)對(duì)齊丹莲,然后按要求填充更短的時(shí)間序列:
?
可用的層
GravesBidirectionalLSTM
雙向長(zhǎng)短記憶單元循環(huán)神經(jīng)網(wǎng)絡(luò)光坝,基于Graves:循環(huán)神經(jīng)網(wǎng)絡(luò)有監(jiān)督序列標(biāo)簽 http://www.cs.toronto.edu/~graves/phd.pdf
雙向?qū)影b器可以使任何循環(huán)層雙向,特別是GravesLSTM甥材。注意盯另,這個(gè)層增加了兩個(gè)方向的輸出,這兩個(gè)方向轉(zhuǎn)換成雙向的“ADD”模式洲赵。
gateActivationFunction
public Builder gateActivationFunction(String gateActivationFn)
LSTM門的激活函數(shù)鸳惯。注意:這應(yīng)該被限制在范圍0-1:sigmoid或hard sigmoid商蕴,例如
- 參數(shù) gateActivationFn 是 LSTM 門的激活函數(shù)
gateActivationFunction
public Builder gateActivationFunction(Activation gateActivationFn)
LSTM門的激活函數(shù)。注意:這應(yīng)該被限制在范圍0-1:sigmoid或hard sigmoid芝发,例如
- 參數(shù) gateActivationFn 是 LSTM 門的激活函數(shù)
gateActivationFunction
public Builder gateActivationFunction(IActivation gateActivationFn)
LSTM門的激活函數(shù)绪商。注意:這應(yīng)該被限制在范圍0-1:sigmoid或hard sigmoid,例如
- 參數(shù) gateActivationFn 是 LSTM 門的激活函數(shù)
GravesLSTM
長(zhǎng)短記憶單元循環(huán)神經(jīng)網(wǎng)絡(luò)辅鲸,基于Graves:循環(huán)神經(jīng)網(wǎng)絡(luò)有監(jiān)督序列標(biāo)簽 http://www.cs.toronto.edu/~graves/phd.pdf
用于在CUDA (Nvidia) GPUs上進(jìn)行更快的網(wǎng)絡(luò)訓(xùn)練
LSTM
無窺視孔連接的LSTM循環(huán)神經(jīng)網(wǎng)絡(luò)層格郁。支持CUDNN加速。詳見https://deeplearning4j.org/cudnn
RnnLossLayer
循環(huán)神經(jīng)網(wǎng)絡(luò)損失層瓢湃。
處理各種目標(biāo)(損失)函數(shù)的梯度等計(jì)算理张。
這里是分布密連組件。因此绵患,輸出激活大小等于輸入大小雾叭。
輸入和輸出激活與其他RNN層相同:分別具有三維形狀 [miniBatchSize,nIn,timeSeriesLength] 和 [miniBatchSize,nOut,timeSeriesLength]。
注意落蝙,RnnLossLayer還具有配置激活功能的選項(xiàng)织狐。
nIn
public Builder nIn(int nIn)
- 參數(shù) lossFunction 是損失層的損失函數(shù)
RnnOutputLayer
和標(biāo)簽的形狀[minibatch,nOut,sequenceLength]。它還支持掩碼數(shù)組筏勒。
注意移迫,RnnOutputLayer也可用于一維CNN層,其中也有[minibatch,nOut,sequenceLength]激活/標(biāo)簽形狀 管行。
build
public RnnOutputLayer build()
- 參數(shù) lossFunction 是輸出層的損失函數(shù)
Bidirectional
雙向是一個(gè)“包裝器”層:它封裝任何單向RNN層厨埋,使其成為雙向的。
注意捐顷,支持多種不同的模式——這些模式指定了應(yīng)該如何從參數(shù)中組合激活荡陷,這里不共享——封裝的RNN層有2個(gè)單獨(dú)的副本,每個(gè)都有單獨(dú)的參數(shù)迅涮。
getNOut
public long getNOut()
該模式枚舉定義了如何將前向和后向網(wǎng)絡(luò)的激活組合起來废赞。
添加:輸出 =前向+反向(元素添加)
乘: 輸出=前向x反向(元素的乘法)
平均值:輸出=0.5(前向+反向)
連接:連接激活。
其中“前向”是前向RNN的激活叮姑,“反向”是反向RNN的激活唉地。在所有情況下,除了連接传透,輸出激活大小與被這個(gè)層包裹的標(biāo)準(zhǔn)RNN大小相同耘沼。在連接情況下,輸出激活大兄煅巍(一維度)比標(biāo)準(zhǔn)RNN的激活數(shù)組大2倍耕拷。
getUpdaterByParam
public IUpdater getUpdaterByParam(String paramName)
獲取給定參數(shù)的更新器。通常托享,所有使用的更新器都是一樣的骚烧,但這不一定是這樣的。
- 參數(shù) paramName 是參數(shù)名稱
- 返回 IUpdater
LastTimeStep
LastTimeStep是一個(gè)“包裝器”層:它包裝任何RNN(或CNN1D)層闰围,并在前向傳播期間提取出最后一個(gè)時(shí)間步赃绊,并將其作為行向量(每個(gè)示例)返回。也就是說羡榴,對(duì)于3D(時(shí)間序列)輸入(形狀[minibatch碧查,layerSize,timeSeriesLength])校仑,我們采用最后一個(gè)時(shí)間步并將其返回為形狀[minibatch忠售,layerSize]的2D數(shù)組。
注意迄沫,最后的時(shí)間步操作考慮了任何掩碼數(shù)組(如果存在):因此稻扬,可變長(zhǎng)度時(shí)間序列(在同一小批中)在這里如預(yù)期那樣被處理。
SimpleRnn
簡(jiǎn)單的RNN-AKA“vanilla”RNN是最簡(jiǎn)單的循環(huán)神經(jīng)網(wǎng)絡(luò)層羊瘩。
注意泰佳,其他體系結(jié)構(gòu)(LSTM等)通常更有效,特別是對(duì)于較長(zhǎng)的時(shí)間序列尘吗;然而逝她,SimpleRnn的計(jì)算速度非常快睬捶,因此在數(shù)據(jù)集中的時(shí)間依賴的長(zhǎng)度只有幾步長(zhǎng)的情況下可以考慮使用黔宛。
翻譯:風(fēng)一樣的男子
如果您覺得我的文章給了您幫助,請(qǐng)為我買一杯飲料吧擒贸!以下是我的支付寶臀晃,意思一下我將非常感激!