DL4J中文文檔/模型/RNN(循環(huán)神經(jīng)網(wǎng)絡(luò))

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)簽)和一行。

Data: Feed Forward vs. RNN
image.gif

?

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())

image.gif

在實(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步的后向傳遞:

Standard Backprop Training
image.gif

?

對(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í)看起來如下:

Truncated BPTT
image.gif

?

請(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)

image.gif

上面的代碼片段將導(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)練:

RNN Training Types
image.gif

?

沒有掩碼和填充藐翎,我們僅限于多對(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ù)組如下所示:

RNN Training Types
image.gif

?

對(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)

image.gif

其中,標(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));

image.gif

假設(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);

image.gif

或者倔既,如果我們只有特征掩碼:一個(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);

image.gif

要理解這里正在發(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));
    }

image.gif

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()方法做了兩件事:

  1. 使用先前存儲(chǔ)狀態(tài)(如果有的話)生成輸出/預(yù)測(cè)(前向傳遞)
  2. 更新存儲(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)行全面的向前傳遞:

RNN Time Step
image.gif

?

我們第一次調(diào)用rnnTimeStep時(shí),兩種方法之間唯一的實(shí)際區(qū)別是存儲(chǔ)了上一個(gè)時(shí)間步的激活/狀態(tài)——這用橙色表示虽缕。但是始藕,下次我們使用rnnTimeStep方法時(shí),這個(gè)存儲(chǔ)狀態(tài)將被用于做出下一個(gè)預(yù)測(cè):

RNN Time Step
image.gif

?

這里有許多重要的區(qū)別:

  1. 在第二個(gè)圖片中(rnnTimeStep的第二次調(diào)用)氮趋,輸入數(shù)據(jù)由單個(gè)時(shí)間步組成伍派,而不是由數(shù)據(jù)的完整歷史組成
  2. 前向傳播是一個(gè)單一的時(shí)間步(與數(shù)百個(gè)或更多)相比。
  3. 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ù))扇救。

為了使用SequenceRecordReaderDataSetIteratorCSVSequenceRecordReader方法,我們首先創(chuàng)建兩個(gè)CSVSequenceRecordReader對(duì)象香嗓,一個(gè)用于輸入迅腔,一個(gè)用于標(biāo)簽:

SequenceRecordReader featureReader = new CSVSequenceRecordReader(1, ",");
SequenceRecordReader labelReader = new CSVSequenceRecordReader(1, ",");

image.gif

這個(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));

image.gif

在這個(gè)特定的方法中,“%d”被替換為相應(yīng)的數(shù)字侈贷,并且使用數(shù)字0到9(包括 )惩歉。

最后,我們可以創(chuàng)建我們的SequenceRecordReaderdataSetIterator:

DataSetIterator iter = new SequenceRecordReaderDataSetIterator(featureReader, labelReader, miniBatchSize, numPossibleLabels, regression);

image.gif

這個(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);

image.gif

miniBatchSizenumPossibleLabels與前面的示例相同。這里询筏,labelIndex指定標(biāo)簽在哪個(gè)列榕堰。例如,如果標(biāo)簽在第五列中嫌套,則使用labelIndex= 4(即,列被索引為0到numColumns-1)圾旨。

對(duì)于單一輸出值的回歸踱讨,我們使用:

DataSetIterator iterRegression = new SequenceRecordReaderDataSetIterator(reader, miniBatchSize, -1, labelIndex, true);

image.gif

同樣,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);

image.gif

此處的參數(shù)與前面的示例相同,除了AlignmentMode.ALIGN_END滋早。這種對(duì)齊模式輸入告訴SequenceRecordReaderDataSetIterator需要兩件事:

  1. 時(shí)間序列可以具有不同的長(zhǎng)度榄审。
  2. 為每個(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);

image.gif

對(duì)準(zhǔn)模式相對(duì)簡(jiǎn)單狰腌。它們指定是否填充較短時(shí)間序列的開始或結(jié)束除破。下面的圖表展示了它如何與掩碼數(shù)組(如本文前面所討論的)一起工作:

Sequence Alignment
image.gif

?

一對(duì)多個(gè)案例(類似于上面的最后一個(gè)案例,但只有一個(gè)輸入)是通過使用AlignmentMode.ALIGN_START實(shí)現(xiàn)琼腔。

注意瑰枫,在包含不同長(zhǎng)度的時(shí)間序列的訓(xùn)練數(shù)據(jù)的情況下,標(biāo)簽和輸入將針對(duì)每個(gè)示例單獨(dú)對(duì)齊丹莲,然后按要求填充更短的時(shí)間序列:

Sequence Alignment
image.gif

?

可用的層


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) 

image.gif

LSTM門的激活函數(shù)鸳惯。注意:這應(yīng)該被限制在范圍0-1:sigmoid或hard sigmoid商蕴,例如

  • 參數(shù) gateActivationFn 是 LSTM 門的激活函數(shù)

gateActivationFunction

public Builder gateActivationFunction(Activation gateActivationFn) 

image.gif

LSTM門的激活函數(shù)。注意:這應(yīng)該被限制在范圍0-1:sigmoid或hard sigmoid芝发,例如

  • 參數(shù) gateActivationFn 是 LSTM 門的激活函數(shù)

gateActivationFunction

public Builder gateActivationFunction(IActivation gateActivationFn) 

image.gif

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) 

image.gif
  • 參數(shù) lossFunction 是損失層的損失函數(shù)

RnnOutputLayer

[源碼]

和標(biāo)簽的形狀[minibatch,nOut,sequenceLength]。它還支持掩碼數(shù)組筏勒。

注意移迫,RnnOutputLayer也可用于一維CNN層,其中也有[minibatch,nOut,sequenceLength]激活/標(biāo)簽形狀 管行。

build

public RnnOutputLayer build() 

image.gif
  • 參數(shù) lossFunction 是輸出層的損失函數(shù)

Bidirectional

[源碼]

雙向是一個(gè)“包裝器”層:它封裝任何單向RNN層厨埋,使其成為雙向的。

注意捐顷,支持多種不同的模式——這些模式指定了應(yīng)該如何從參數(shù)中組合激活荡陷,這里不共享——封裝的RNN層有2個(gè)單獨(dú)的副本,每個(gè)都有單獨(dú)的參數(shù)迅涮。

getNOut

public long getNOut() 

image.gif

該模式枚舉定義了如何將前向和后向網(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) 

image.gif

獲取給定參數(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)一樣的男子

image

如果您覺得我的文章給了您幫助,請(qǐng)為我買一杯飲料吧擒贸!以下是我的支付寶臀晃,意思一下我將非常感激!

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末酗宋,一起剝皮案震驚了整個(gè)濱河市积仗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜕猫,老刑警劉巖寂曹,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異回右,居然都是意外死亡隆圆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門翔烁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渺氧,“玉大人,你說我怎么就攤上這事蹬屹÷卤常” “怎么了白华?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)贩耐。 經(jīng)常有香客問我弧腥,道長(zhǎng),這世上最難降的妖魔是什么潮太? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任管搪,我火速辦了婚禮,結(jié)果婚禮上铡买,老公的妹妹穿的比我還像新娘更鲁。我一直安慰自己,他們只是感情好奇钞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布澡为。 她就那樣靜靜地躺著,像睡著了一般蛇券。 火紅的嫁衣襯著肌膚如雪缀壤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天纠亚,我揣著相機(jī)與錄音塘慕,去河邊找鬼。 笑死蒂胞,一個(gè)胖子當(dāng)著我的面吹牛图呢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骗随,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛤织,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了鸿染?” 一聲冷哼從身側(cè)響起指蚜,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涨椒,沒想到半個(gè)月后摊鸡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蚕冬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年免猾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囤热。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猎提,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出旁蔼,到底是詐尸還是另有隱情锨苏,我是刑警寧澤疙教,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蚓炬,受9級(jí)特大地震影響松逊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肯夏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望犀暑。 院中可真熱鬧驯击,春花似錦、人聲如沸耐亏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)广辰。三九已至暇矫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間择吊,已是汗流浹背李根。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留几睛,地道東北人房轿。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像所森,于是被迫代替她去往敵國(guó)和親囱持。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容