使用简单的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.jianshu.com/p/75eeaee7f67d

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容