进入 NLP 世界的最佳桥樑:写给所有人的自然语言处理与深度学习入门指南(下)

深度学习 3 步骤

深度学习以及 NLP 领域的学问博大精深,但一般来说,当你想要实际动手写出一个神经网路的时候,有 3 个基本步骤可以 follow:

image

用深度学习框架 Keras 来实作深度学习的基本 3 步骤 (图片来源

  1. 定义神经网路的架构
  2. 决定如何衡量模型的表现
  3. 训练模型并挑选最好的结果

接下来你会看到,大约 80 % 的程式码会花在实作第一个步骤。剩馀 2 个步骤在使用 Keras 的情况下非常容易就能实现;但后面我们也会谈到,你将花 80 % 的时间在最后一个步骤上面。

首先,先让我们进入第一步骤。

定义神经网路的架构

在实作之前,先让我们回顾一下前面段落看到的模型架构:

image

本文用来实现假新闻分类的神经网路架构

从左到右扫过一遍,你可以很清楚地发现我们需要以下 5 个元素来完成这个模型:

  • 两个新闻标题(两个长度为 20 的数字序列)
  • 一个词嵌入层:将数字序列转换为词向量序列
  • 一个 LSTM 层:读入前层的词向量并萃取标题语义
  • 一个串接层:将两个新闻标题的处理结果(也是向量)串接成一个向量
  • 一个全连接层:将前层的向量转换为 3 个分类的预测机率

有些层我们已经在前面章节看过 Keras 的实现,比方说词嵌入层以及 LSTM 层。剩下的串接层以及全连结层在 Keras 也都有现成的模组可供使用。

另外值得一提的是,图上的每个层(Layer)以及向量右下的灰字都对应了底下 Python 程式码裡头的变数名称:

image

灰字代表程式码裡头对应的变数名称

因此,如果等等你不了解底下某个特定的变数所代表的意义,可以回来利用这张架构图来釐清概念。

以下就是此模型的 Keras 实作:

  • 基本参数设置,有几个分类

NUM_CLASSES = 3 # 在语料库裡有多少词彙 MAX_NUM_WORDS = 10000 # 一个标题最长有几个词彙 MAX_SEQUENCE_LENGTH = 20 # 一个词向量的维度 NUM_EMBEDDING_DIM = 256 # LSTM 输出的向量维度 NUM_LSTM_UNITS = 128

  • 建立孪生 LSTM 架构(Siamese LSTM) from keras import Input from keras.layers import Embedding, \ LSTM, concatenate, Dense from keras.models import Model ##### 分别定义 2 个新闻标题 A & B 为模型输入 ##### 两个标题都是一个长度为 20 的数字序列 top_input = Input( shape=(MAX_SEQUENCE_LENGTH, ), dtype='int32') bm_input = Input( shape=(MAX_SEQUENCE_LENGTH, ), dtype='int32') ##### 词嵌入层 ##### 经过词嵌入层的转换,两个新闻标题都变成 ##### 一个词向量的序列,而每个词向量的维度 ##### 为 256 embedding_layer = Embedding( MAX_NUM_WORDS, NUM_EMBEDDING_DIM) top_embedded = embedding_layer( top_input) bm_embedded = embedding_layer( bm_input) ##### LSTM 层 ##### 两个新闻标题经过此层后 ##### 为一个 128 维度向量 shared_lstm = LSTM(NUM_LSTM_UNITS) top_output = shared_lstm(top_embedded) bm_output = shared_lstm(bm_embedded) ##### 串接层将两个新闻标题的结果串接单一向量 ##### 方便跟全连结层相连 merged = concatenate( [top_output, bm_output], axis=-1) ##### 全连接层搭配 Softmax Activation ##### 可以回传 3 个成对标题 ##### 属于各类别的可能机率 dense = Dense( units=NUM_CLASSES, activation='softmax') predictions = dense(merged) ##### 我们的模型就是将数字序列的输入,转换 ##### 成 3 个分类的机率的所有步骤 / 层的总和 model = Model( inputs=[top_input, bm_input], outputs=predictions)

这段程式码的确不短,但有将近一半是我写给你的注解。而且这段程式码的逻辑跟上面的架构图一模一样,只差架构图是从左到右、程式码是从上到下而已。

为了确保用 Keras 定义出的模型架构跟预期相同,我们也可以将其画出来:

from keras.utils import plot_model plot_model( model, to_file='model.png', show_shapes=True, show_layer_names=False, rankdir='LR')

image

除了模型架构以外,我们还可以看到所有层的输入 / 输出张量(Tensor)的维度。在 Keras 里头,张量的第 1 个维度通常为样本数(比方说 5 则新闻标题),而 None 则代表可以指定任意值。

最重要的是,这个用 Keras 定义出来的模型,跟我们之前想像中的孪生神经网路可以说是一模一样:

image

我没有骗你,对吧?

现在你应该发现,只要拥有前面几章学到的 NLP 知识以及基础 Python 程式能力,要建立一个像这样看似複杂的孪生 LSTM(Siamese LSTM)神经网路其实也并没有那麽困难。

事实上,使用 Keras 建立深度学习模型这件事情感觉上就像是在玩叠叠乐一样,一层加上一层:

image

一位研究生利用 Keras 做深度学习的心得 (图片来源

全连接层

唯一没有在前面章节提到的是全连接层(Fully Connected Layer)以及其使用的 Softmax 函式

全连接层顾名思义,代表该层的每个神经元(Neuron)都会跟前一层的所有神经元享有连结:

image

因为只需要预测 3 个分类,本文的全连接层只有 3 个神经元

而为了确认我们计算的参数量无误,还可以使用 model.summary() 来看每一层的参数量以及输出的张量(Tensor)长相:

model.summary()

image

全连接层在最下面。而因为其与前一层「紧密」连接的缘故,它在 Keras 里头被称为 Dense 层。它也是最早出现、最简单的神经网路层之一。

Param # 则纪录了每一层所包含的模型参数(Parameters)。在机器学习的过程中,这些参数都会不断地被调整,直到能让模型能做出很好的预测。词嵌入层有最多的参数,因为我们要为 字典裡头的每个词彙都建立一个 256 维度的词向量,因此参数量为 10,000 * 256。

这张表另外一个值得注意的地方是所有层的 Output Shape 的第一个维度都是 None。而 None 代表著可以是任意的数字。

在 Keras 裡头,第一个维度代表著样本数(#Samples),比方说前 9,527 笔新闻标题 A 的数字序列的 shape 应该要是 (9527, 20)

x1_train[:9527].shape

嗯,结果跟我们想像的一样。

而之所以每层的样本数为 None 是因为 Keras 为了因应在不同场合会丢入不同数量的样本需求。比方说,在训练时你可能会一次丢 32 笔资料给模型训练,但在预测的时候一次只丢 16 笔资料。

Softmax 函式

Softmax 函式一般都会被用在整个神经网路的最后一层上面,比方说我们这次的全连接层。

Softmax 函式能将某层中的所有神经元裡头的数字作正规化(Normalization):将它们全部压缩到 0 到 1 之间的范围,并让它们的和等于 1。

image

Softmax 能将多个数字作正规化,让它们的值为 1 (图片来源

因为

  1. 所有数值都位于 0 到 1 之间
  2. 所有数值相加等于 1

这两个条件恰好是机率(Probability)的定义,Softmax 函式的运算结果可以让我们将每个神经元的值解释为对应分类(Class)的发生机率。

以我们的假新闻分类任务来说的话,每个值就各代表以下分类的发生机率:

  • 不相关: 0.46
  • 新闻 B 同意新闻 A:0.34
  • 新闻 B 不同意新闻 B:0.20

如果现在是在做预测且我们只能选出一个分类当作答案的话,我们可以说这次的分类结果最有可能是「不相关」这个类别,因为其发生机率最高。

在定义好模型以后,我们就可以进入下个步骤:定义衡量模型好坏的指标。

决定如何衡量模型的表现

为了让机器自动「学习」,我们得给它一个损失函数(Loss Function)

给定一个正确解答 y 以及模型预测的结果 y_head,我们的模型透过损失函数就能自动计算出现在的预测结果跟正解的差距为多少。

透过损失函数的回馈,模型会尽全力修正参数,以期将此损失函数的值下降到最低(也就是让预测结果 y_head 跟正解 y 越来越接近)。

image

图中的抛物线即为损失函数 J(w)。当参数 w 有不同值时,损失函数的值也有所不同。模型会持续修正参数 w 以期最小化损失函数 (图片来源

那你会问,在假新闻分类裡头,我们应该使用什麽损失函数呢?

我们在将正解做 One-hot Encoding 一节有稍微提到,我们会希望

  • 正确的分类的机率分佈 P1(例:[1, 0, 0]
  • 模型预测出的机率分佈 P2(例:[0.7, 0.2, 0.1]

这 2 个机率分佈的「差距」越小越好。而能计算 2 个机率分佈之间的差距的交叉熵(Cross Entropy)就是这次的分类问题中最适合的损失函数。

image

交叉熵能帮我们计算两个机率分佈的差距,适合作为分类问题的损失函数 (图片来源

在 Keras 里头,我们可以这样定义模型的损失函数:

model.compile( optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])

categorical_crossentropy 即是我们刚刚所说的交叉熵,而 accuracy 则是准确度,会被我们用来在训练过程中了解模型的表现情况。

精准度的定义为:

虽然有了交叉熵来当作我们模型的损失函数,但是实际上模型要如何更新裡头的参数呢?我们需要一个优化器(Optimizer)来做到这件事情。

image

不同优化器透过调整参数来降低损失函数的情形,就像是在想办法往溜滑梯的低处滑一样 (图片来源

虽然我们有很多种优化器,但它们基本上都是从梯度下降法(Gradient Descent)延伸而来。

在上图的不同位置,梯度下降法会重新计算每个参数对损失函数的梯度(斜率)。接著梯度下降法会利用该梯度来修正参数,使得使用新参数算出来的损失函数的值能够持续往下降。

不同优化器则有各自往下滑的秘方,比方说自动调整 Learning rate

现在就先让我们使用 RMSProp 优化器。而在有了损失函数以及优化器以后,我们就可以正式开始训练模型了!

训练模型并挑选最好的结果

这步骤很直观,我们就是实际使用 model.fit 来训练刚刚定义出来的孪生 LSTM 模型:

######## 决定一次要放多少成对标题给模型训练
BATCH_SIZE = 512
######## 决定模型要看整个训练资料集几遍
NUM_EPOCHS = 10
######## 实际训练模型 history = model.fit(
######## 输入是两个长度为 20 的数字序列 x=[x1_train, x2_train], y=y_train, batch_size=BATCH_SIZE, epochs=NUM_EPOCHS,
######## 每个 epoch 完后计算验证资料集
########上的 Loss 以及准确度 validation_data=( [x1_val, x2_val], y_val ),
######## 每个 epoch 随机调整训练资料集
######## 裡头的数据以让训练过程更稳定 shuffle=True )

这边特别值得拿出来提的是以下两个参数:

  • BATCH_SIZE
  • NUM_EPOCHS

依照我们前面对损失函数(Loss Function)的说明,理论上模型是把训练资料集裡头的 32 万笔资料全部看完一遍之后,再更新一次参数以降低损失函数。

但是这样太旷日废时,训练可能要花很久才能完成。

image

实务上都是每次只放入几笔训练数据,让模型看完这些资料后就做一次参数的更新。而这个「几笔」,就是 BATCH_SIZE

依照 BATCH_SIZE 的大小,梯度下降(Gradient Descent, 后称 GD)可以概括为 3 个类别:

  • GD(BATCH_SIZE = 训练资料集大小,且这时不称为 batch)
  • Mini-batch GD(BATCH_SIZE 通常为一个较小的 2 的倍数)
  • SGD(BATCH_SIZE = 1)
image

想像损失函数是个越往裡面值就越低的碗,梯度下降就是要想办法到达中心点 (图片来源

如上图所示,下方的 GD 因为在每次更新参数前都会看完训练资料集裡头所有的数据,因此它更新参数的方向是最可靠的。但要往前走一步就就得看完 32 万笔数据,未免成本也太大。

另一个极端是上方的 SGD:模型每看完 1 个训练数据就尝试更新权重,而因为单一一笔训练数据并不能很好地代表整个训练资料集,前进的方向非常不稳定。

image

随机梯度下降(SGD)与 Mini-batch 梯度下降的比较 (图片来源

因此我们常常採用的是中庸之道: Mini-batch GD 的方式来训练模型,而这靠的是指定 model.fit 函式裡头的 batch_size

NUM_EPOCHS 则很容易理解:你希望模型不只将 32 万笔的训练数据都看过一遍,而是每一笔资料还要多看过好几次,以让模型确确实实地从它们身上学到东西。NUM_EPOCHS = 10 的意思就代表模型会重複看整个训练资料集 10 次。

接著让我们看看 Keras 的训练过程:

image

利用 Keras 训练神经网路的过程

因为模型的目标就是要最小化损失函数(Loss Function),你可以观察到当模型看过越多训练资料集(Training Set)的数据以后,损失值(loss)就越低,分类的准确度(acc)则越高。

这代表我们的模型越来越熟悉训练资料集裡头的数据,因此在训练资料集裡头的表现越来越好。

如果依照准确度以及损失值分别画图的话则会长这样:

image

很明显地,我们的神经网路有过适(Overfittng)的问题:儘管在训练资料集表现得非常好(准确度超过 90 %、损失小于 0.2),在从没看过的验证资料集的表现就相对逊色不少。且在第 6 个 epoch 之后验证资料集的准确度 val_acc 就没什麽在上升,验证集的损失 val_loss 则已经逐渐上升。

这代表模型利用从训练资料集学到的模式(Pattern)还无法非常精准地预测没见过的事物。

image

用 Keras 来实作深度学习的基本 3 步骤

如同我们在这章节一开头所说的,虽然第 3 步骤:「训练模型并挑选最好的结果」的 Keras 实作非常简单(基本上就是 model.fit( ...)),但实际上在一个机器学习 / 深度学习专案裡头,你将会花 80 % 的时间在这个步骤裡头调整参数,想办法找到一个最棒的模型。

儘管如此,我们现在最想知道的还是这个模型在真实世界(也就是测试资料集)到底能表现多好,因此先让我们试著拿这个简单模型来做预测吧!

进行预测并提交结果

就跟我们对训练 / 验证资料集做的资料前处理一样,要对测试资料集(Test Set)做预测,我们得先将裡头的文本数据通通转换成能够丢进模型的数字序列资料。

首先,让我们把测试资料集读取进来:

import pandas as pd test = pd.read_csv( TEST_CSV_PATH, index_col=0) test.head(3)

tid1 tid2 title1_zh title2_zh title1_en title2_en
id
--- --- --- --- --- --- ---
321187 167562 59521 萨拉赫人气爆棚!埃及总统大选未参选获百万选票 现任总统压力山大 辟谣!里昂官方否认费基尔加盟利物浦,难道是价格没谈拢? egypt 's presidential election failed to win m... Lyon! Lyon officials have denied that Felipe F...
321190 167564 91315 萨达姆被捕后告诫美国的一句话,发人深思 10大最让美国人相信的荒诞谣言,如蜥蜴人掌控着美国 A message from Saddam Hussein after he was cap... The Top 10 Americans believe that the Lizard M...
321189 167563 167564 萨达姆此项计划没有此国破坏的话,美国还会对伊拉克发动战争吗 萨达姆被捕后告诫美国的一句话,发人深思 Will the United States wage war on Iraq withou... A message from Saddam Hussein after he was cap...

测试资料集跟训练资料集的唯一差别只在没有 label 栏位,因此我们只需要将当初在资料前处理章节使用的步骤原封不动地套用在测试资料集即可。

你可以趁机複习一下有哪些步骤:

####### 以下步骤分别对新闻标题 A、B 进行
####### 文本断词
/ Word Segmentation test['title1_tokenized'] = \ test.loc[:, 'title1_zh'] \ .apply(jieba_tokenizer) test['title2_tokenized'] = \ test.loc[:, 'title2_zh'] \ .apply(jieba_tokenizer)
######## 将词彙序列转为索引数字的序列
x1_test = tokenizer \ .texts_to_sequences( test.title1_tokenized) x2_test = tokenizer \ .texts_to_sequences( test.title2_tokenized) # 为数字序列加入 zero padding x1_test = keras \ .preprocessing \ .sequence \ .pad_sequences( x1_test, maxlen=MAX_SEQUENCE_LENGTH) x2_test = keras \ .preprocessing \ .sequence \ .pad_sequences( x2_test, maxlen=MAX_SEQUENCE_LENGTH)
######## 利用已训练的模型做预测
predictions = model.predict( [x1_test, x2_test])

这些步骤现在对你来说应该都已经不再陌生。

让我们看一下从模型得到的预测结果长什麽样子:

predictions[:5]

image

跟我们之前讨论过的一样,模型针对每一笔成对新闻标题的输入,会回传给我们 3 个分类的机率值。

现在,我们只要将机率值最大的类别当作答案,并将这个结果转回对应的文本标籤即可上传到 Kaggle:

index_to_label = {v: k for k, v in label_to_index.items()} test['Category'] = [index_to_label[idx] for idx in np.argmax(predictions, axis=1)] submission = test \ .loc[:, ['Category']] \ .reset_index() submission.columns = ['Id', 'Category'] submission.head()

image

得到上面的 DataFrame 以后,我们可以将其储存成 CSV 并上传到 kaggle,而结果如下:

image

我们的 NLP 模型第一次的结果

如果你还记得我们在用直觉找出第一条底线的章节内容的话,就会知道这并不是应该多好的预测结果,但的确比多数票决好了一点点。

不过不需要操之过急,因为任何机器学习专案都是一个持续重複改善的迴圈。在第一次预测就做出完美结果的情况很少,重点是持续改善。

image

在第一次提交结果以后,我们还可以做非常多事情来尝试改善模型效能:

  • 改变字典词彙量、序列长度
  • 改变词向量的维度
  • 尝试预先训练的词向量ELMoGloVe
  • 调整 LSTM 层的输出维度
  • 使用不同优化器、调整 Learning rate
  • 改变神经网路架构如使用 GRU 层
  • ...

能改善准确度的方式不少,但因为牵涉范围太广,请容许我把它们留给你当做回家作业。

走到这裡代表你已经完整地经历了一个 NLP 专案所需要的大部分步骤。在下一节.让我们回顾一下在这趟旅程中你所学到的东西。

我们是怎麽走到这裡的

在这趟 NLP 旅程里头,我们学会了不少东西。

现在的你应该已经了解:

  • NLP 中常见的数据前处理以及实践方法
  • 词向量以及词嵌入的基本概念
  • 神经网路常见的元件如全连接层、简单 RNN 以及 LSTM
  • 能读多个资料来源的孪生神经网路架构
  • 如何用 Keras 建构一个完整的神经网路
  • 深度学习 3 步骤:建模、定义衡量指标以及训练模型
  • 梯度下降、优化器以及交叉熵等基本概念
  • 如何利用已训练模型对新数据做预测

呼,这可真了不起,值得庆祝!

image

能阅读到这裡,我相信你对深度学习以及 NLP 领域是抱著不少兴趣的。而为了让你在阅读本文以后能够继续探索这个世界,在下一章节我则会介绍 3 门非常推荐的线上课程。

最后,我则会在文末总结一下自己的心得。

现在,先看看有哪些课程吧!

3 门推荐的线上课程

为了奠定 NLP 的基础,这一个月我一边複习旧教材,一边看了不少教学文章以及线上课程。

截至目前,我认为有 3 个 CP 值十分高的课程值得推荐给你:

  1. 台大电机系李宏毅教授的深度学习课程
    • 奠定理论基础
  2. Coursera 的 Deep Learning 专项课程
    • 理论 70 % + 实作 30 %
  3. Deep Learning with Python
    • 注重程式实作

这边说的 CP 值高(对,我知道你最爱 CP 值)指的是能用最少的时间、精力以及金钱来确确实实地学好 NLP 的理论及实作基础。

image

李宏毅教授的 Youtube 播放清单 (图片来源

李宏毅教授的机器学习课程内行的都知道,大概是全世界最好、最完整的 Deep Learning 中文学习资源。李教授在课程中广徵博引学术论文,但却同时非常浅显易懂。你可以在这边看到教授所有的 Youtube 课程播放清单

就我所知,教授在台大上课很注重实作,有不少作业需要完成,但因为线上只有影片可以查看,因此我将其分类为「奠定理论基础」。

image

Deep Learning Specialization (图片来源

原 Google Brain 的吴恩达教授Deep Learning 专项课程则是 Coursera 上最受欢迎的深度学习课程。跟我们这篇文章最相关的 NLP 技术则被涵盖在该专项课程的最后一堂课:Sequence Models

我在大约一年前完成包含卷积神经网路 CNN 的前四堂课,而因为课程上线已有一段时间,现在影片大都有简体或繁体中文的字幕,不太需要烦恼听不懂英文。

image

Deep Learning with Python Video Edition 的作者 François Chollet 为软体工程师出身,设计出知名深度学习框架 Keras,目前则在 Google AI 工作。

该书以 Programmer 的角度出发,提供了利用 Keras 实现各种 NLP 任务的范例,十分适合在熟悉深度学习理论后想要实作的人阅读。

就算你不想花钱买书或是订阅 O'Relly Online,你也可以在他有 5,000 多颗星的 Github Repo deep-learning-with-python-notebooks 看到跟该课程相关的所有 Jupyter Notebooks。

image

这些课程可以说是帮助我完成这篇文章以及 Kaggle 竞赛的最大功臣,而我也希望能透过这篇文章的微薄之力让你知道他们的存在,并随著他们继续你从这里开始的 NLP 探险。

当然,除了以上 3 堂课程,你还可以在由浅入深的深度学习资源整理一文看到更多我整理的深度学习资源。你也可以直接前往 Github Repo 查看。

结语:从掌握基础到运用巨人之力

网路上多的是专业的 NLP 教学文章或论文探讨,但平易近人的中文文章却少之又少。

在文章开头我说:

希望这篇文章能成为你前往自然语言处理世界的最佳桥樑。

这野心听起来很狂妄,但至少我已经尝试用最平易近人的词彙向你介绍这个 NLP 世界的一丁点基础知识,而我也希望你真的学到了些什麽、获得些启发。

现在深度学习以及 NLP 领域实在发展太快,就算一个人有兴趣也常常不知从何开始学起。

事实上,NLP 的发展速度还在加快,而这既是好消息也是坏消息。

image

NLP 如果是辆衝往未来的火车的话,深度学习就是它的引擎,而我们的数据是它的燃料。另外,多数人还没有登上这台火车

这边说的 NLP,其实更适合用人工智慧取代。

对还没掌握机器学习 / 深度学习 / NLP 知识的人来说,这些技术只会离自己越来越远,最后远到只会在新闻报导或科幻小说上看到,儘管被这些技术驱动的庞大系统每天影响著他们的生活。

image

至于那些已经掌握这些知识的人,透过运用如迁移学习等巨人之力,就连一般人也能做到以前凭自己力量做不到的事情。

比方说利用 Google 在今年 11 月公开的庞大语言代表模型 BERT,我不费吹灰之力就在本文的 Kaggle 竞赛达到 85 % 的正确率,距离第一名 3 %,总排名前 30 %。

image

我们之前设计的 LSTM 模型则仅有 67 % 准确度。

并不是说只要用新的语言代表模型就好,这篇学的东西都不重要。事实上正好相反:正是因为有了此篇的 NLP 基础知识,才让我得以顺利地运用该巨人之力。如果你有兴趣深入了解,可以接著阅读进击的 BERT:NLP 界的巨人之力与迁移学习,用最直观的方式理解并实际运用这个巨人之力。

image

BERT 是在 NLP 领域裡家喻户晓的语言代表模型 (图片来源

深度学习以及 NLP 领域发展快速,但你总要从某个地方开始好好地学习基础,而且越快开始越好。

所以我留给你的最后一个问题就是:

你,打算什麽时候出发?

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