使用簡單的RNN觀測數字中的規律

本文使用的tensorflow版本:1.4
tensorflow安裝:pip install tensorflow

1、循環神經網絡簡介

開始前,我們先回顧一下,簡單的MLP三層神經網絡模型:

其中x是一個向量,它表示輸入層的值(這里面沒有畫出來表示神經元節點的圓圈);s是一個向量,它表示隱藏層的值(這里隱藏層面畫了一個節點,你也可以想象這一層其實是多個節點,節點數與向量s的維度相同);U是輸入層到隱藏層的權重矩陣;o也是一個向量,它表示輸出層的值;V是隱藏層到輸出層的權重矩陣。
再看下圖中一個簡單的循環神經網絡圖,它由輸入層、一個隱藏層和一個輸出層組成。我們可以看到,循環神經網絡的隱藏層的值s不僅僅取決于當前這次的輸入x,還取決于上一次隱藏層的值s。權重矩陣W就是隱藏層上一次的值作為這一次的輸入的權重。
循環神經網絡
如果我們把上面的圖展開,循環神經網絡也可以畫成下面這個樣子:
循環神經網絡展開

現在看起來就清楚不少了,這個網絡在t時刻接收到輸入Xt之后,隱藏層的值是St,輸出值是ot。關鍵一點是,st的值不僅僅取決于Xt,還取決于St?1。我們可以使用下面的公式來表示循環神經網絡的計算方法:
ot=g(Vst) (1)
st=f(Uxt+Wst?1) (2)
式1是輸出層的計算公式,輸出層是一個全連接層,也就是它的每個節點都和隱藏層的每個節點相連。V是輸出層的權重矩陣,g是激活函數。式2是隱藏層的計算公式,它是循環層。U是輸入x的權重矩陣,W是上一次的值st?1作為這一次的輸入的權重矩陣,f是激活函數。
從上面的公式可以看出,循環層和全連接層的區別就是多了一個權重矩陣W。
若反復把式2代入帶式1,我們將得到:
ot=g(Vst)=g(Vf(Uxt+Wst?1))
=g(Vf(Uxt+Wf(Uxt?1+Wst?2)))
=g(Vf(Uxt+Wf(Uxt?1+Wf(Uxt?2+Wst?3))))
從上面可以看出,循環神經網絡的輸出值otot,是受前面歷次輸入值xt、xt?1、xt?2……的影響的,這就是為什么循環神經網絡可以往前看任意多個輸入值的原因。

2、數據集

為簡單起見,本篇就以簡單的二進制序列作為訓練數據,而不實現具體的論文仿真,主要目的是理解RNN的原理和如何在TensorFlow中構造一個簡單基礎的模型架構。
首先我們看一下實驗數據的構造:
輸入數據X:在時間t,Xt的值有50%的概率為1,50%的概率為0;
輸出數據Y:在實踐t,Yt的值有50%的概率為1,50%的概率為0,除此之外,如果Xt-3 == 1,Yt為1的概率增加50%, 如果Xt-8 == 1,則Yt為1的概率減少25%, 如果上述兩個條件同時滿足,則Yt為1的概率為75%。

如果RNN沒有學習到任何一條依賴,那么Yt為1的概率就是0.625(0.5+0.5*0.5-0.5*0.25),所以所獲得的交叉熵應該是0.66。
如果RNN學習到第一條依賴關系,即Xt-3為1時Yt一定為1。那么,所以最終的交叉熵應該是0.52(-0.5* (0.875 * np.log(0.875) + 0.125 * np.log(0.125)) -0.5 * (0.625* np.log(0.625) + 0.375* np.log(0.375)))。
如果RNN學習到了兩條依賴, 那么有0.25的概率全對,0.5的概率正確率是75%,還有0.25的概率正確率是0.5。所以其交叉熵為0.45(-0.50 * (0.75* np.log(0.75) + 0.25* np.log(0.25)) - 0.25 * (2 * 0.50 * np.log (0.50)) - 0.25 * (0))。

這里的交叉熵的計算可能有些難以理解,不過沒有關系,小編搞了很久,終于明白了其中的緣由,RNN如果能學到規則,也就是說明在某些特定的條件下,即Xt-8Xt-3取不同的值時,Y滿足不同的概率分布,而這個概率分布,由下圖所示:

交叉熵計算

3、數據生成及預處理

根據我們上述定義的規則,我們使用如下的代碼生成數據集:

import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt


def gen_data(size = 1000000):
    """生成數據
    輸入數據X:在時間t,Xt的值有50%的概率為1,50%的概率為0;
    輸出數據Y:在實踐t,Yt的值有50%的概率為1,50%的概率為0,除此之外,如果`Xt-3 == 1`,Yt為1的概率增加50%, 如果`Xt-8 == 1`,則Yt為1的概率減少25%, 如果上述兩個條件同時滿足,則Yt為1的概率為75%。
    """
    X = np.array(np.random.choice(2,size=(size,)))
    Y = []
    for i in range(size):
        threshold = 0.5
        if X[i-3] == 1:
            threshold += 0.5
        if X[i-8] == 1:
            threshold -= 0.25
        if np.random.rand() > threshold:
            Y.append(0)
        else:
            Y.append(1)
    return X,np.array(Y)

接下來,我們要將產生的數據集按照參數進行切分,主要參數是batch_size和num_steps,batch_size 指將數據分成多少塊,而num_steps指輸入rnn_cell中的窗口的大小,即下圖中的n的大小


循環神經網絡
def gen_batch(raw_data, batch_size, num_steps):
    #raw_data是使用gen_data()函數生成的數據,分別是X和Y
    raw_x, raw_y = raw_data
    data_length = len(raw_x)

    # 首先將數據切分成batch_size份,0-batch_size,batch_size-2*batch_size。。。
    batch_partition_length = data_length // batch_size
    data_x = np.zeros([batch_size, batch_partition_length], dtype=np.int32)
    data_y = np.zeros([batch_size, batch_partition_length], dtype=np.int32)
    for i in range(batch_size):
        data_x[i] = raw_x[batch_partition_length * i:batch_partition_length * (i + 1)]
        data_y[i] = raw_y[batch_partition_length * i:batch_partition_length * (i + 1)]

    #因為RNN模型一次只處理num_steps個數據,所以將每個batch_size在進行切分成epoch_size份,每份num_steps個數據。注意這里的epoch_size和模型訓練過程中的epoch不同。 
    epoch_size = batch_partition_length // num_steps

    #x是0-num_steps, batch_partition_length -batch_partition_length +num_steps。。。共batch_size個
    for i in range(epoch_size):
        x = data_x[:, i * num_steps:(i + 1) * num_steps]
        y = data_y[:, i * num_steps:(i + 1) * num_steps]
        yield (x, y)

#這里的n就是訓練過程中用的epoch,即在樣本規模上循環的次數
def gen_epochs(n, num_steps):
    for i in range(n):
        yield gen_batch(gen_data(), batch_size, num_steps)

我們可以用下圖來看一下數據生成的過程,下圖中每一行為一個batch,可以看到這里的batch_size = 3,每一列為一個num_step,下圖中的num_steps為3,那么gen_batch函數每次yield的數據就是下圖虛線中的數據。


數據形式

4、模型構建

使用TensorFlow構建RNN模型,主要就是定義rnn_cell類型,然后將其復用即可。代碼如下:

batch_size = 3
num_classes = 2
state_size = 4
num_steps = 10
learning_rate = 0.2

x = tf.placeholder(tf.int32, [batch_size, num_steps], name='input_placeholder')
y = tf.placeholder(tf.int32, [batch_size, num_steps], name='labels_placeholder')
#RNN的初始化狀態,全設為零。注意state是與input保持一致,接下來會有concat操作,所以這里要有batch的維度。即每個樣本都要有隱層狀態
init_state = tf.zeros([batch_size, state_size])

#將輸入轉化為one-hot編碼,兩個類別。[batch_size, num_steps, num_classes]
x_one_hot = tf.one_hot(x, num_classes)
#將輸入unstack,即在num_steps上解綁,方便給每個循環單元輸入。這里可以看出RNN每個cell都處理一個batch的輸入(即batch個二進制樣本輸入)
rnn_inputs = tf.unstack(x_one_hot, axis=1)

#定義rnn_cell的權重參數,
with tf.variable_scope('rnn_cell'):
"""由于tf.Variable() 每次都在創建新對象,所有reuse=True 和它并沒有什么關系。對于get_variable(),來說,如果已經創建的變量對象,就把那個對象返回,如果沒有創建變量對象的話,就創建一個新的。"""
    W = tf.get_variable('W', [num_classes + state_size, state_size])
    b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))
#使之定義為reuse模式,循環使用,保持參數相同
def rnn_cell(rnn_input, state):
    with tf.variable_scope('rnn_cell', reuse=True):
        W = tf.get_variable('W', [num_classes + state_size, state_size])
        b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))
    #定義rnn_cell具體的操作,這里使用的是最簡單的rnn,不是LSTM
    return tf.tanh(tf.matmul(tf.concat([rnn_input, state], 1), W) + b)

state = init_state
rnn_outputs = []
#循環num_steps次,即將一個序列輸入RNN模型
for rnn_input in rnn_inputs:
    state = rnn_cell(rnn_input, state)
    rnn_outputs.append(state)
final_state = rnn_outputs[-1]

#定義softmax層
with tf.variable_scope('softmax'):
    W = tf.get_variable('W', [state_size, num_classes])
    b = tf.get_variable('b', [num_classes], initializer=tf.constant_initializer(0.0))
#注意,這里要將num_steps個輸出全部分別進行計算其輸出,然后使用softmax預測
logits = [tf.matmul(rnn_output, W) + b for rnn_output in rnn_outputs]
predictions = [tf.nn.softmax(logit) for logit in logits]

# Turn our y placeholder into a list of labels
y_as_list = tf.unstack(y, num=num_steps, axis=1)

#losses and train_step
losses = [tf.nn.sparse_softmax_cross_entropy_with_logits(labels=label, logits=logit) for \
          logit, label in zip(logits, y_as_list)]
total_loss = tf.reduce_mean(losses)
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(total_loss)

5、模型訓練

def train_network(num_epochs, num_steps, state_size=4, verbose=True):
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        training_losses = []
        #得到數據,因為num_epochs==5,所以外循環只執行五次
        for idx, epoch in enumerate(gen_epochs(num_epochs, num_steps)):
            training_loss = 0
            #保存每次執行后的最后狀態,然后賦給下一次執行
            training_state = np.zeros((batch_size, state_size))
            if verbose:
                print("\nEPOCH", idx)
            #這是具體獲得數據的部分
            for step, (X, Y) in enumerate(epoch):
                tr_losses, training_loss_, training_state, _ = \
                    sess.run([losses,
                              total_loss,
                              final_state,
                              train_step],
                                  feed_dict={x:X, y:Y, init_state:training_state})
                training_loss += training_loss_
                if step % 100 == 0 and step > 0:
                    if verbose:
                        print("Average loss at step", step,
                              "for last 100 steps:", training_loss/100)
                    training_losses.append(training_loss/100)
                    training_loss = 0

    return training_losses
training_losses = train_network(5,num_steps)
plt.plot(training_losses)
plt.show()

6、模型測試

我們首先將num_steps設置為2,這樣模型肯定學習不到三步前的結果,可以發現交叉熵在0.66附近,與上述結果吻合:


num_steps=2

接下來我們將num_steps設置為5,這樣模型可以學習到第一條規則,但是無法學習到第二條規則,可以發現交叉熵在0.52附近,與上述結果吻合:


num_steps=5

接下來我們再將num_steps設置為10,這樣模型可以學習到兩條規則,但模型的信息熵并不是在0.45附近,而是在0.52附近:
num_steps=10

7、問題探討

為什么會出現上述的結果呢?這是因為RNN存在梯度消失的問題,RNN的訓練也是反向傳播算法,只不過比基本神經網絡的算法復雜一些,在訓練過程中,根據鏈式法則不斷推倒的過程中,對越前面參數的更新,所涉及的連乘項就會增多,當其中一部分接近于0時,整個更新的值就接近于0,導致對前面的參數的更新幾乎為0,模型輸出對越靠前的輸入的依賴越來越小。

反向傳播

在本例中,我們猜想對前面8步的依賴出現了梯度消失的情況,為了驗證我們的猜想,我們將規則二中Xt-8修改為Xt-5,再次運行代碼,可以發現交叉熵已經接近于我們之前計算的值,說明梯度消失的情況的確存在。
修改規則2

8、模型改進

上述的代碼完整展示了RNN的神經元的運作方式,但是Tensorflow已經提供了相關的函數,直接幫我們構建RNN的模型,我們可以對代碼進行如下改進:

#定義rnn_cell的權重參數,
with tf.variable_scope('rnn_cell'):
    W = tf.get_variable('W', [num_classes + state_size, state_size])
    b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))
#使之定義為reuse模式,循環使用,保持參數相同
def rnn_cell(rnn_input, state):
    with tf.variable_scope('rnn_cell', reuse=True):
        W = tf.get_variable('W', [num_classes + state_size, state_size])
        b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))
    #定義rnn_cell具體的操作,這里使用的是最簡單的rnn,不是LSTM
    return tf.tanh(tf.matmul(tf.concat([rnn_input, state], 1), W) + b)

state = init_state
rnn_outputs = []
#循環num_steps次,即將一個序列輸入RNN模型
for rnn_input in rnn_inputs:
    state = rnn_cell(rnn_input, state)
    rnn_outputs.append(state)
final_state = rnn_outputs[-1]


#----------------------上面是原始代碼,定義了rnn_cell,然后使用循環的方式對其進行復用,簡化之后我們可以直接調用BasicRNNCell和static_rnn兩個函數實現------------------------


cell = tf.contrib.rnn.BasicRNNCell(state_size)
rnn_outputs, final_state = tf.contrib.rnn.static_rnn(cell, rnn_inputs, initial_state=init_state)

我們可以看到static_rnn接受的輸入格式還是[batch_size,n_classes],這樣就需要加一步unstack對數據進行處理。除了使用static_rnn 之外,也可以使用dynamic_rnn,使用dynamic_rnn 時,我們直接將輸入表示成[batch_size, num_steps, features]的三維Tensor即可,但是后面的計算損失的代碼段也需要進行相應的修改,代碼如下:

x = tf.placeholder(tf.int32, [batch_size, num_steps], name='input_placeholder')
y = tf.placeholder(tf.int32, [batch_size, num_steps], name='labels_placeholder')
init_state = tf.zeros([batch_size, state_size])

rnn_inputs = tf.one_hot(x, num_classes)
#注意這里去掉了這行代碼,因為我們不需要將其表示成列表的形式在使用循環去做。
#rnn_inputs = tf.unstack(x_one_hot, axis=1)

cell = tf.contrib.rnn.BasicRNNCell(state_size)
#使用dynamic_rnn函數,動態構建RNN模型
rnn_outputs, final_state = tf.nn.dynamic_rnn(cell, rnn_inputs, initial_state=init_state)

with tf.variable_scope('softmax'):
    W = tf.get_variable('W', [state_size, num_classes])
    b = tf.get_variable('b', [num_classes], initializer=tf.constant_initializer(0.0))
logits = tf.reshape(
            tf.matmul(tf.reshape(rnn_outputs, [-1, state_size]), W) + b,
            [batch_size, num_steps, num_classes])
predictions = tf.nn.softmax(logits)

losses = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
total_loss = tf.reduce_mean(losses)
train_step = tf.train.AdagradOptimizer(learning_rate).minimize(total_loss)

9、完整代碼

本文的完整代碼如下:

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt


def gen_data(size=100000):
    """
    生成數據:
        輸入數據X:在時間t,Xt的值有50%的概率為1,50%的概率為0;
        輸出數據Y:在實踐t,Yt的值有50%的概率為1,50%的概率為0,除此之外,如果`Xt-3 == 1`,Yt為1的概率增加50%, 如果`Xt-8 == 1`,則Yt為1的概率減少25%, 如果上述兩個條件同時滿足,則Yt為1的概率為75%。
    """
    X = np.random.choice(2,(size,))
    Y = []
    for i in range(size):
        threshold = 0.5
        # 判斷X[i-3]和X[i-8]是否為1,修改閾值
        if X[i-3] == 1:
            threshold += 0.5
        if X[i-8] == 1:
            threshold -= 0.25
        # 生成隨機數,以threshold為閾值給Yi賦值
        if np.random.rand() > threshold:
            Y.append(0)
        else:
            Y.append(1)
    return X,np.array(Y)


def gen_batch(raw_data,batch_size,num_steps):
    # raw_data是使用gen_data()函數生成的數據,分別是X和Y
    raw_x,raw_y = raw_data
    data_length = len(raw_x)

    # 首先將數據切分成batch_size份,0-batch_size,batch_size-2*batch_size。。。
    batch_partition_length = data_length // batch_size
    data_x = np.zeros([batch_size,batch_partition_length],dtype=np.int32)
    data_y = np.zeros([batch_size,batch_partition_length],dtype=np.int32)

    # 因為RNN模型一次只處理num_steps個數據,所以將每個batch_size在進行切分成epoch_size份,每份num_steps個數據。注意這里的epoch_size和模型訓練過程中的epoch不同。
    for i in range(batch_size):
        data_x[i] = raw_x[i*batch_partition_length:(i+1)*batch_partition_length]
        data_y[i] = raw_y[i*batch_partition_length:(i+1)*batch_partition_length]

    # x是0-num_steps, batch_partition_length -batch_partition_length +num_steps。。。共batch_size個
    epoch_size = batch_partition_length // num_steps
    for i in range(epoch_size):
        x = data_x[:,i*num_steps:(i+1)*num_steps]
        y = data_y[:,i*num_steps:(i+1)*num_steps]
        yield (x,y)


def gen_epochs(n,num_steps):
    '''這里的n就是訓練過程中用的epoch,即在樣本規模上循環的次數'''
    for i in range(n):
        yield gen_batch(gen_data(),batch_size,num_steps=num_steps)


batch_size = 5
num_steps = 10
state_size = 10
n_classes = 2
learning_rate = 0.1

x = tf.placeholder(tf.int32,[batch_size,num_steps])
y = tf.placeholder(tf.int32,[batch_size,num_steps])

#RNN的初始化狀態,全設為零。注意state是與input保持一致,接下來會有concat操作,所以這里要有batch的維度。即每個樣本都要有隱層狀態
init_state = tf.zeros([batch_size,state_size])

#將輸入轉化為one-hot編碼,兩個類別。[batch_size, num_steps, num_classes]
x_one_hot = tf.one_hot(x,n_classes)
#將輸入unstack,即在num_steps上解綁,方便給每個循環單元輸入。這里可以看出RNN每個cell都處理一個batch的輸入(即batch個二進制樣本輸入)
rnn_inputs = tf.unstack(x_one_hot,axis=1)
#定義rnn_cell的權重參數,
with tf.variable_scope('rnn_cell'):
    W = tf.get_variable('W',[n_classes +state_size,state_size])
    b = tf.get_variable('b',[state_size],initializer=tf.constant_initializer(0.0))

#使之定義為reuse模式,循環使用,保持參數相同
def rnn_cell(rnn_input,state):
    with tf.variable_scope('rnn_cell',reuse=True):
        W = tf.get_variable('W', [n_classes + state_size, state_size])
        b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))

    # 定義rnn_cell具體的操作,這里使用的是最簡單的rnn,不是LSTM
    return tf.tanh(tf.matmul(tf.concat((rnn_input,state),1),W)+b)


state = init_state
rnn_outputs = []

#循環num_steps次,即將一個序列輸入RNN模型
for rnn_input in rnn_inputs:
    state = rnn_cell(rnn_input,state)
    rnn_outputs.append(state)
final_state = rnn_outputs[-1]

#cell = tf.contrib.rnn.BasicRNNCell(state_size)
#rnn_outputs,final_state = tf.contrib.rnn.static_rnn(cell,rnn_inputs,initial_state=init_state)

# rnn_inputs = x_one_hot
# rnn_outputs,final_state = tf.nn.dynamic_rnn(cell,rnn_inputs,initial_state=init_state)

#定義softmax層
with tf.variable_scope('softmax'):
    W = tf.get_variable('W',[state_size,n_classes])
    b = tf.get_variable('b',[n_classes])
#注意,這里要將num_steps個輸出全部分別進行計算其輸出,然后使用softmax預測
logits = [tf.matmul(rnn_output,W)+b for rnn_output in rnn_outputs]
predictions = [tf.nn.softmax(logit) for logit in logits]
# Turn our y placeholder into a list of labels
y_as_lists = tf.unstack(y,num=num_steps,axis=1)

#losses and train_step
losses = [tf.nn.sparse_softmax_cross_entropy_with_logits(labels=label,logits=logit) for label,logit in zip(y_as_lists,predictions)]
total_loss = tf.reduce_mean(losses)
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(total_loss)
#使用動態rnn時改為下面的代碼
# logits = tf.reshape(
#             tf.matmul(tf.reshape(rnn_outputs, [-1, state_size]), W) + b,
#             [batch_size, num_steps, n_classes])
# predictions = tf.nn.softmax(logits)
#
# losses = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
# total_loss = tf.reduce_mean(losses)
# train_step = tf.train.AdagradOptimizer(learning_rate).minimize(total_loss)

def train_network(num_epochs,num_steps,state_size,verbose=True):
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        training_losses = []
        # 得到數據
        for idx,epoch in enumerate(gen_epochs(num_epochs,num_steps)):
            training_loss = 0
            # 保存每次執行后的最后狀態,然后賦給下一次執行
            training_state = np.zeros((batch_size,state_size))
            if verbose:
                print('\EPOCH', idx)
            # 這是具體獲得數據的部分
            for step,(X,Y) in enumerate(epoch):
                tr_losses, training_loss_, training_state, _ = \
                    sess.run([losses,
                              total_loss,
                              final_state,
                              train_step],
                             feed_dict={x: X, y: Y, init_state: training_state})
                training_loss += training_loss_
                if step % 100 == 0 and step > 0:
                    if verbose:
                        print("Average loss at step", step,
                              "for last 100 steps:", training_loss / 100)
                    training_losses.append(training_loss / 100)
                    training_loss = 0

            return training_losses

training_losses = train_network(1, num_steps,state_size)
plt.plot(training_losses)
plt.show()

10、參考文獻

使用TensorFlow實現RNN模型入門篇1
深度學習系列(4):循環神經網絡(RNN)https://plushunter.github.io/2017/04/23/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E7%B3%BB%E5%88%97%EF%BC%884%EF%BC%89%EF%BC%9A%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%EF%BC%88RNN%EF%BC%89/
RNN, LSTM 理解:http://www.lxweimin.com/p/75eeaee7f67d

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容