通过Python来学习人工智能!事半功倍!TensorFlow之入门篇!

image

MNIST数据集介绍

MNIST是一个入门级的计算机视觉数据集,它包含各种手写数字图片:

image

MNIST数据集是含标注信息的,以上图片分别代表5, 0, 4和1。

MNIST数据集的官网是Yann LeCun's website

image

自动下载

首先贴出github地址:https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/tutorials/mnist

新建一个input_data.py的文件:

<pre class="prettyprint hljs elm" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"># Copyright 2015 The TensorFlow Authors. All Rights Reserved.## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.# =============================================================================="""Functions for downloading and reading MNIST data."""from future import absolute_importfrom future import divisionfrom future import print_functionimport gzipimport osimport tempfileimport numpyfrom six.moves import urllibfrom six.moves import xrange # pylint: disable=redefined-builtinimport tensorflow as tffrom tensorflow.contrib.learn.python.learn.datasets.mnist import read_data_sets</pre>

image
image

文件 内容 train-images-idx3-ubyte.gz 训练集图片 - 55000 张 训练图片, 5000 张 验证图片 train-labels-idx1-ubyte.gz 训练集图片对应的数字标签 t10k-images-idx3-ubyte.gz 测试集图片 - 10000 张 图片 t10k-labels-idx1-ubyte.gz 测试集图片对应的数字标签 载下来的数据集被分成两部分:

60000行的训练数据集和10000行的测试数据集。

image
image

把这个数组展开成一个向量,长度是 28x28 = 784。如何展开这个数组(数字间的顺序)不重要,只要保持各个图片采用相同的方式展开。

在MNIST训练数据集中,mnist.train.images 是一个形状为 [60000, 784] 的张量,

第一个维度表示图片的索引,第二个维度表示图片中像素的索引,值介于0和1之间。。

image
image

Softmax Regression模型

我们知道MNIST的每一张图片都表示一个数字,从0到9。我们希望得到给定图片代表每个数字的概率。

比如说,我们的模型可能推测一张包含9的图片代表数字9的概率是80%,但是判断它是8的概率是15%(因为8和9都有上半部分的小圆),判断它是6的概率是5%,然后给予它代表其他数字的概率更小的值。

Softmax Regression是一个简单的模型,很适合用来处理得到一个待分类对象在多个类别上的概率分布。所以,这个模型通常是很多高级模型的最后一步。

Softmax Regression大致分为两步:

<pre class="prettyprint hljs vbnet" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">Step 1: add up the evidence of our input being in certain classes;Step 2: convert that evidence into probabilities.</pre>

为了得到一张给定图片属于某个特定数字类的证据(evidence),我们对图片像素值进行加权求和。

如果这个像素具有很强的证据说明这张图片不属于该类,那么相应的权值为负数,相反如果这个像素拥有有利的证据支持这张图片属于这个类,那么权值是正数。

下面是一个直观的例子,图片中蓝色表示正值,红色表示负值(蓝色区域的形状趋向于数字形状):

image

我们也需要加入一个额外的偏置量(bias),因为输入往往会带有一些无关的干扰量。因此对于给定的输入图片 x 它代表的是数字 i 的证据可以表示为:

image
image
image

如果将这个过程公式化,将得到:

image

如们将这个过程公式化,将得到:

image

也可以简化成:

image

看完之后,是不是觉得不明觉厉?开头可能还能理解,但是后面搬出公式了,是不是觉得莫名其妙了?没错,就是要这种感觉,那继续吧~

Python学习交流群:835017344,这里是python学习者聚集地,有大牛答疑,有资源共享!有想学习python编程的,或是转行,或是大学生,还有工作中想提升自己能力的,正在学习的小伙伴欢迎加入学习。

实现回归模型

为了在Python中进行科学计算工作,我们常常使用一些独立库函数包,例如NumPy来实现复杂的矩阵计算。但是由于Python的运行效率并不够快,所以常常用一些更加高效的语言来实现。但是,这样做会带来语言转换(例如转换回python操作)的开销。

TensorFlow在这方面做了一些优化,可以对你所描述的一系列的交互计算的流程完全独立于Python之外,从而避免了语言切换的开销。

为了使用TensorFlow,我们需要引用该库函数:

<pre class="hljs elm" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import tensorflow as tf</pre>

利用一些符号变量来描述交互计算的过程,创建如下:

<pre class="hljs ini" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">x = tf.placeholder(tf.float32, [None, 784])</pre>

说明下,placeholder函数可以理解为形参,用于定义过程,在执行的时候再赋具体的值;

这里的 x 不是一个特定的值,而是一个占位符,即需要时指定。

我们在TensorFlow运行计算时输入这个值。我们希望能够输入任意数量的MNIST图像,每一张图展平成784维的向量。我们用2维的浮点数张量来表示这些图,这个张量的形状是[None,784(28*28) ]。(这里的None表示此张量的第一个维度可以是任何长度的。)

使用Variable(变量)来表示模型中的权值和偏置,这些参数是可变的。如下:

<pre class="hljs vim" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">W = tf.Variable(tf.zeros([784, 10]))b = tf.Variable(tf.zeros([10]))</pre>

说明下,zeros是创建一个所有的参数为0的tensor对象;

这里的W和b均被初始化为0值矩阵。W的维数为784 * 10,是因为我们需要将一个784维的像素值经过相应的权值之乘转化为10个类别上的evidence值;b是十个类别上累加的偏置值。

现在,实现softmax regression模型仅需要一行代码:

<pre class="hljs vim" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">y = tf.nn.softmax(tf.matmul(x,W) + b)</pre>

其中,matmul函数实现了 x 和 W 的乘积,这里 x 为二维矩阵,所以放在前面。可以看出,在TensorFlow中实现softmax regression模型是很简单的。

image

首先,用 tf.log 计算 y 的每个元素的对数。

接下来,把 y_ 的每一个元素和 tf.log(y) 的对应元素相乘。

最后,用 tf.reduce_sum 计算张量的所有元素的总和。(注意,这里的交叉熵不仅仅用来衡量单一的一对预测和真实值,而是所有100幅图片的交叉熵的总和。对于100个数据点的预测表现比单一数据点的表现能更好地描述我们的模型的性能。)

现在我们知道我们需要我们的模型做什么啦,用TensorFlow来训练它是非常容易的。

因为TensorFlow拥有一张描述你各个计算单元的图,它可以自动地使用反向传播算法(backpropagation algorithm)来有效地确定你的变量是如何影响你想要最小化的那个成本值的。

然后,TensorFlow会用你选择的优化算法来不断地修改变量以降低成本。

<pre class="prettyprint hljs ini" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)</pre>

在这里,使用了一个学习率为0.01的梯度下降算法来最小化代价函数。梯度下降是一个简单的计算方式,即使得变量值朝着减小代价函数值的方向变化。TensorFlow也提供了许多其他的优化算法,仅需要一行代码即可实现调用。

TensorFlow在这里实际上所做的是,它会在后台给描述你的计算的那张图里面增加一系列新的计算操作单元用于实现反向传播算法和梯度下降算法。然后,它返回给你的只是一个单一的操作,当运行这个操作时,它用梯度下降算法训练你的模型,微调你的变量,不断减少成本。

在模型训练之前,还需要对所有的参数进行初始化:

<pre class="hljs ini" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">init = tf.initialize_all_variables()</pre>

可以在一个Session里面运行模型,并且进行初始化:

<pre class="hljs ini" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">sess = tf.Session()sess.run(init)</pre>

接下来,进行模型的训练,这里循环训练1000次:

<pre class="prettyprint hljs groovy" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">for i in range(1000): batch_xs, batch_ys = mnist.train.next_batch(100) sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})</pre>

每一次的循环中,我们取训练数据中的100个随机数据,这种操作成为批处理(batch)。

然后,每次运行train_step时,将之前所选择的数据,填充至所设置的占位符中,作为模型的输入。

使用一小部分的随机数据来进行训练被称为随机训练(stochastic training)- 在这里更确切的说是随机梯度下降训练。

在理想情况下,我们希望用我们所有的数据来进行每一步的训练,因为这能给我们更好的训练结果,但显然这需要很大的计算开销。

所以,每一次训练我们可以使用不同的数据子集,这样做既可以减少计算开销,又可以最大化地学习到数据集的总体特性。

模型的评价

那,怎么评估我们的模型如何呢?

首先得找出那些预测正确的标签。tf.argmax 能给出某个tensor对象在某一维上的其数据最大值所在的索引值。

由于标签向量是由0,1组成,因此最大值1所在的索引位置就是类别标签,比如tf.argmax(y,1)返回的是模型对于任一输入x预测到的标签值,而 tf.argmax(y_,1) 代表正确的标签,我们可以用 tf.equal 来检测我们的预测是否真实标签匹配(索引位置一样表示匹配)。

<pre class="hljs vim" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))</pre>

correct_prediction是一个布尔值的列表,例如 [True, False, True, True]。

可以使用tf.cast()函数将其转换为[1, 0, 1, 1],以方便准确率的计算(以上的是准确率为0.75)。

<pre class="prettyprint hljs ini" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))</pre>

最后,我们来获取模型在测试集上的准确率,

<pre class="prettyprint hljs lisp" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))</pre>

Softmax regression模型由于模型较简单,所以在测试集上的准确率在91%左右,这个结果并不算太好。

通过一些简单的优化,准确率可以达到97%,目前最好的模型的准确率为99.7%。(这里有众多模型在MNIST数据集上的运行结果)。

源码

利用Softmax模型实现手写体识别的完整代码如下:

完善了代码的注释,以便阅读起来更好理解,同时增加输出训练过程:

<pre class="prettyprint hljs vim" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">import input_dataimport tensorflow as tffrom tensorflow.examples.tutorials.mnist import input_datamnist = input_data.read_data_sets("data/", one_hot=True)print("Download Done!")# 设置权重weights和偏置biases作为优化变量,初始值设为0weights = tf.Variable(tf.zeros([784, 10]))biases = tf.Variable(tf.zeros([10]))# 构建模型x = tf.placeholder("float", [None, 784])# 模型的预测值y = tf.nn.softmax(tf.matmul(x, weights) + biases) # 真实值y_real = tf.placeholder("float", [None, 10]) # 预测值与真实值的交叉熵cross_entropy = -tf.reduce_sum(y_real * tf.log(y)) # 使用梯度下降优化器最小化交叉熵train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy) # 比较预测值和真实值是否一致correct_prediction = tf.equal(tf.argmax(y, 1), tf.arg_max(y_real, 1)) # 统计预测正确的个数,取均值得到准确率accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) # 开始训练init = tf.initialize_all_variables()sess = tf.Session()sess.run(init)for i in range(5000): # 每次随机选取100个数据进行训练,即所谓的随机梯度下降 batch_xs, batch_ys = mnist.train.next_batch(100) # 正式执行train_step,用feed_dict的数据取代placeholder sess.run(train_step, feed_dict={x: batch_xs, y_real:batch_ys}) if i % 100 == 0: # 每训练100次后评估模型 print("Step " + str(i) +",Training Accuracy "+ str(sess.run(accuracy, feed_dict={x: mnist.test.images, y_real: mnist.test.labels})))print("Accuarcy on Test-dataset: ", sess.run(accuracy, feed_dict={x: mnist.test.images, y_real: mnist.test.labels}))</pre>

最后执行的结果:

image

多次执行,会发现每次都不一样,但基本都是在91%

image

如果把训练次数增加到10W次,会发现成功率在98%

image

训练过程截图:

image

嗯,TensorFlow的hello world就是这样,花了不少时间介绍Softmax模型,尤其是公式那块看的不懂,但感觉,数学在机器学习里面真的很重要~小结:

本文使用Softmax Regression模型对MNIST数据集进行训练,主要围绕Softmax模型的原理(看不懂篇)以及怎么在TensorFlow里面使用这个模型,更重要的事,从这个模型学习到的设计思想~

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

推荐阅读更多精彩内容