轻量化模型:MobileNet/SqueezeNet/ShuffleNet

MobileNet v1

论文链接:MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications

创新点

轻量化体现在:

  • 深度可分离卷积(Depthwise Separable Convolution)
  • 两个模型压缩参数(宽度乘数和分辨率乘数)
1、深度可分离卷积

将常规卷积分为两部分:一是深度卷积,n 个卷积核和 n 个输入特征图分别卷积,也就是分组卷积;二是 1x1 卷积,将第一步的卷积结果融合起来。之所以使用1x1卷积,是因为深度卷积导致信息流通不畅 ,即输出的 feature map 仅包含输入的 feature map 的一部分,point-wise(1x1) convolution 帮助信息在通道之间流通。深度可分离卷积好处是可以减小计算量和模型尺寸。

如何减小计算量

如下图所示。比如一个输入特征图的尺寸是D_F×D_F×M,与 N 个D_K×D_K×M的卷积核进行卷积,stride=1padding=same,那么输出特性图尺寸为D_F×D_F×N。总计算量为D_F×D_F×D_K×D_K×M×N,每个卷积核和特征图的每个位置进行卷积;总参数量(不计偏置)D_K×D_K×M×N

深度可分离卷积打破输出通道数量和卷积核大小之间的相互作用。深度卷积对 M 个特征图分别进行卷积,对应的卷积核深度就变成了1,所以第一步的计算量为D_F×D_F×D_K×D_K×M,参数数量为D_K×D_K×M;第二步是1x1卷积,即 N 个尺寸为 1x1xM 的卷积核与第一步的 M 个特征图卷积,这步的计算量为 D_F×D_F×M×N,参数数量为 M×N 。总的计算量 D_F×D_F×D_K×D_K×M+D_F×D_F×M×N,总参数D_K×D_K×M+M×N。计算量和参数变为原来的1/N+1/(D_K)^2。当卷积核尺寸为3时,变为1/8或1/9。减小计算量的同时,准确率只会下降一点。

图1 常规卷积与深度可分离卷积过程对比
网络结构

除了第一个卷积层是普通卷积,后面的都是深度可分离卷积。所有层(除了最后一个全连接层)后面都是一个BN层和ReLU层,最后的全连接层后面是softmax层进行分类。此外,深度卷积和 1x1 卷积后面都分别有BN和ReLU。降采样通过stride=2的卷积进行(常规卷积和深度可分离卷积都有)。最后在全连接层前使用平均池化层将空间分辨率降低到1,即压缩成特征向量。如果将深度卷积和1x1卷积计为单独的层,MobileNet有28层。网络总降采样倍数为32。

图2 常规卷积和深度可分离卷积都需要BN层、ReLU层

conv_dwPytorch实现:

import torch.nn as nn

def conv_dw(inp, oup, stride):
    return nn.Sequential(
            #  深度卷积,输入输出通道数相同,使用组卷积,个数等于输入通道数
            nn.Conv2d(inp,inp, kernel_size=3, stride=stride, padding=1, groups=inp, bias=False),
            nn.BatchNorm2d(inp),
            nn.ReLU(inplace=True),
            # 1x1卷积
            nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
            nn.BatchNorm2d(oup),
            nn.ReLU(inplace=True) )

除此之外,MobileNet实现上也非常高效。1×1卷积不需要在内存中重新排序,并可以直接使用GEMM实现,GEMM是最优化的数值线性代数算法之一。 MobileNet在1×1卷积中花费95%的计算时间,其中也有75%的参数。其他参数几乎都在全连接层。

MobileNet模型在TensorFlow使用RMSprop进行训练,与训练大型模型相反,使用较少的正则化、数据增强和参数衰减(L2正则)技术,因为小型模型参数很少,在过拟合方面问题不大。

2、模型压缩系数
宽度乘数:更薄的模型

宽度乘数 α 的作用是在每层均匀地稀疏网络。对于给定层和α,输入通道的数量 M 变为 αM,输出通道的数量 N 变为 αN。总计算量变为:D_F·D_F·D_K·D_K·αM+D_F·D_F·αM·αN
α常用取值[1, 0.75, 0.5, 0.25]

分辨率乘数:减少分辨率

分辨率乘数 ρ 应用于输入图像,并且每个层的特征图随后被相同的乘数减少。在实践中,一般通过设置输入图片分辨率隐式设置 ρ 。ρ∈(0,1)通常隐式设置,使得网络的输入分辨率为 224、192、160 或 128。分辨率乘数具有将计算成本降低 ρ2 的效果。

现在网络核心层深度可分离卷积的计算量表示为宽度乘数 α 和分辨率乘数 ρ 的公式:

ρD_F·ρD_F·D_K·D_K·αM+ρD_F·ρD_F·αM· αN

两个超参数和深度可分离卷积减小计算量对比,第一行显示完整卷积层的Mult-Adds和参数,其中输入特征映射的大小为14×14×512,卷积核 K 的大小为 3×3×512×512。下一行是在前一行的基础改动。

实验结果

  • 与常规卷积相比,计算量大幅减小,准确率只略微减小
  • 模型压缩系数的影响
  • 与经典网络比较

MobileNet v2

论文链接:MobileNetV2: Inverted Residuals and Linear Bottlenecks

创新点

  • 反残差(Inverted residuals)
  • Linear bottlenecks
Inverted Residuals

MobileNet-V1 最大的特点是采用深度可分离卷积(DW)来减少运算量以及参数量,而在网络结构上,没有采用shortcut的方式。Resnet 及 Densenet 等一系列采用 shortcut 的网络的成功,表明了 shortcut 的好处,于是 MobileNet-V2 采用了 shortcut ,将输入和输出相加。

通常的 residuals block 是先经过一个 1x1 的卷积层,把特征图的通道数压缩,再通过 3x3 卷积层,最后经过一个 1x1 的卷积层将特征图通道数扩张回去。而 inverted residuals 是 先“扩张”,后“压缩”。原因是 MobileNet v2是将DW 卷积层替代 residuals block 的常规卷积,这样做会碰到如下问题:DW 卷积层提取到的特征受限于输入的通道数,若是采用以往的 residual block,先“压缩”,再卷积提特征,那么 DW 卷积层可提取得特征就太少了,因此MobileNetV2 反其道而行,一开始先“扩张”,本文实验“扩张”倍数为6。 通常residual block 里面是 “压缩”→“卷积提特征”→“扩张”,MobileNetV2 变成了 “扩张”→“卷积提特征”→ “压缩”,因此称为Inverted residuals

与 ResNet对比:

图3 与ResNet对比

相同点

  • 借鉴 ResNet,都采用了 1x1 -> 3x3 -> 1x1的模式;
  • 同样使用 Shortcut 将输出与输入相加(未在上式画出)

不同点

  • ResNet 使用 标准卷积 提特征,MobileNet 使用 DW卷积 提特征;
  • ResNet 先降维 (0.25倍)、卷积、再升维,而 MobileNet V2 则是 先升维 (6倍)、卷积、再降维
Linear bottlenecks

当采用“扩张”→“卷积提特征”→ “压缩”时,在“压缩”之后会碰到一个问题,那就是 Relu 会破坏特征。为什么这里的Relu会破坏特征呢?这得从 Relu 的性质说起,Relu对于负的输入,输出全为零;而本来特征就已经被“压缩”,再经过Relu的话,又要“损失”一部分特征,因此这里不采用Relu,实验结果表明这样做是正确的,这就称为 Linear bottlenecks

与MobileNet V1对比:

图4 与MobileNet v1对比

相同点

  • 都采用深度可分离卷积提取特征

不同点

  • V2 在 DW 卷积之前新加了一个 PW 卷积“扩张”层,目的是为了提升通道数,获得更多特征;
  • V2 去掉了第二个 PW 的ReLU,即 Linear Bottleneck,目的是防止Relu破坏特征

网络结构

注:文中提到共计采用19个bottleneck,但是这里只有17个。

Conv2d 和 avgpool 和传统CNN里的操作一样;最大的特点是 bottleneck,一个bottleneck由如下三个部分构成:

一个 residuals bottleneck 结构的 Multiply Add 为:
h*w*1*1*k*tk + h/s*w/s* 3*3*1*tk + h/s*w/s*1*1*tk*k'

特别的,针对stride=1 和stride=2,在block上有稍微不同,主要是为了与shortcut的维度匹配,因此,stride=2时,不采用shortcut。 具体如下图:

实验结果

  • MobileNet V2分类时的表现

注:这是在手机的CPU上跑出来的结果(Google pixel 1 for TF-Lite)。

  • 检测时的表现

注:SSDLite是把SSD预测层中所有常规卷积用深度可分离卷替换。与常规SSD相比,SSDLite显着降低了参数量和计算成本。

SqueezeNet

论文链接:SQUEEZENET: ALEXNET-LEVEL ACCURACY WITH 50X FEWER PARAMETERS AND <0.5MB MODEL SIZE

在ImageNet上实现了和AlexNet相同的正确率,但是只使用了1/50的参数。更进一步,使用模型压缩技术,可以将SqueezeNet压缩到0.5MB,是AlexNet的1/510。

创新点

  • Fire Moudle
  • 使用模型压缩方法进一步压缩网络
Fire Moudle

SqueezeNet 的核心在于 Fire module,Fire module 由两层构成,分别是 squeeze 层和expand 层,如下图所示。与 inception 系列的思想非常接近,首先 squeeze 层,就是 1x1 卷积,其卷积核数要少于上一层 feature map 数,这个操作是“压缩”; Expand 层分别用 1x1 和 3x3 卷积,然后 concat,这个操作在 inception 系列里面也有。

具体操作:
首先,HxWxM 的 feature map 经过 Squeeze 层,得到 S1 个 feature map,这里的 S1 均是小于 M 的,以达到压缩的目的;然后,HxWxS1 的特征图输入到 Expand 层,分别经过 1x1 卷积层和 3x3 卷积层进行卷积,再将结果进行 concat,得到 Fire module 的输出,为 HM(e1+e3) 的 feature map。三个可调参数:S1,e1,e3,分别代表卷积核的个数,同时也表示对应输出 feature map 的维数,在文中提出的 SqueezeNet 结构中,e1=e3=4s1。

网络结构

网络结构设计思想,同样与 VGG 的类似,堆叠的使用卷积操作,只不过这里堆叠的是 Fire module(图中用红框部分)。

进一步压缩
模型压缩方法

为了得到一个参数非常少但同时保持准确性的模型,一个明智的方法是采用现有的CNN模型并以有损的方式压缩它。常用的模型压缩技术有:

  1. 奇异值分解(singular value decomposition (SVD))预训练模型
  2. 网络剪枝(Network Pruning):用零替换预训练模型低于某个阈值的参数以形成稀疏矩阵,并最终在稀疏CNN上执行一些训练迭代
  3. 深度压缩(Deep compression):使用网络剪枝,量化和 huffman 编码
  4. 使用硬件加速器(hardware accelerator)

注:标题中的0.5M参数是 Squeezenet 经过 Deep compression 后的结果。

ShuffleNet V1

论文链接:ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices

创新点

ShuffleNet 可以看作是ResNet的压缩版本,主要思想是:

  • 使用 group convolution
  • 使用 channel shuffle
分组卷积(group convolution)

Group convolution 自 Alexnet 就有,当时因为硬件限制而采用分组卷积;之后在 2016 年的 ResNeXt 中,表明采用 group convolution 可获得高效的网络;再有 Xception 和 MobileNet 均采用 depth-wise convolution, 这些都是最近出来的一系列轻量化网络模型。

常规卷积

假设有输入feature map,尺寸为 H*W*C,与 k 个 h*w*C 的卷积核卷积,输出尺寸为 H'*W'*k

分组卷积

而Group convolution的实质就是将卷积分为 g 个独立的组,分别计算。即:

  • 把 input feature 分为 g 组,每组的大小为 H*W*C/g
  • 把 kernel 也分为 g 组,每组有 k/g 个 h*w*C/g 的卷积核
  • 按顺序,每组 input feature 和 kernel 分别做普通卷积,输出 g 组 H'*W'*k/g 的特征图,一共有 g 组,总输出 H'*W'*k

注:深度可分离卷积的第一步的就是分组卷积的一个特例,分组数等于输入通道数。

于是可以把 pointwise convolution(1x1)用 pointwise group convolution 代替,将卷积运算限制在每个Group内来降低计算量。

Channel Shuffle

由于采用分组卷积,group与group之间的几乎没有联系,影响了网络的准确率。因此 Xception,MobileNet等网络采用密集的 1x1 卷积,因为要保证 group convolution之后不同组的特征图之间的信息交流。但是达到上面那个目的,我们不一定非要采用 dense pointwise convolution,于是提出了 channel shuffle 来加强 group 之间的联系。同时channel shuffle是可导的,可以实现 end-to-end 一次性训练网络。

具体方法为:把各组的 channel 平均分为 g(下图 g=3)份,然后依次序的重新构成 feature map。

在程序上实现 channel shuffle 是非常容易的:假定将输入层分为 g 组,总通道数为 g*n,首先将总通道数那个维度拆分为 g*n 两个维度,然后将这两个维度转置变成 n*g ,最后重新 reshape 成一个维度。

网络结构

(a) 加入Depthwise的ResNet bottleneck结构,(b)和(c)是加入Group convolution和Channel Shuffle的ShuffleNet的结构

ShuffleNet的基本单元是在一个残差单元的基础上改进而成的。如图a所示,这是一个包含3层的残差单元:首先是1x1卷积,然后是3x3的depthwise convolution(DWConv,主要是为了降低计算量),这里的3x3卷积是瓶颈层(bottleneck),紧接着是1x1卷积,最后是一个短路连接,将输入直接加到输出上。现在,进行如下的改进:将密集的1x1卷积替换成1x1的group convolution,不过在第一个1x1卷积之后增加了一个channel shuffle操作。值得注意的是3x3卷积后面没有增加channel shuffle,按paper的意思,对于这样一个残差单元,一个channel shuffle操作是足够了。还有就是3x3的depthwise convolution之后没有使用ReLU激活函数。改进之后如图b所示。对于残差单元,如果stride=1时,此时输入与输出shape一致可以直接相加,而当stride=2时,通道数增加,而特征图大小减小,此时输入与输出不匹配。一般情况下可以采用一个1x1卷积将输入映射成和输出一样的shape。但是在ShuffleNet中,却采用了不一样的策略,如图c所示:对原输入采用stride=2的3x3 avg pool,这样得到和输出一样大小的特征图,然后将得到特征图与输出进行连接(concat),而不是相加。这样做的目的主要是降低计算量与参数大小。

代码实现:https://www.jianshu.com/p/84b2a4590ec8

实验结果

注:ShuffleNet s× 表示通道数放缩到s倍。

  • 与MobileNet对比
  • 倍数与分组数

缺点

  • Shuffle channel在实现的时候需要大量的指针跳转和Memory set,这本身就是极其耗时的;同时又特别依赖实现细节,导致实际运行速度不会那么理想。
  • Shuffle channel规则是人工设计出来的,不是网络自己学出来的。这不符合网络通过负反馈自动学习特征的基本原则,又陷入人工设计特征的老路(如sift/HOG等。

ShuffleNet V2

论文链接:ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design

正如标题那样,是实验引导的高效CNN结构设计,文章的观点和实验都比较新颖。

ShuffleNet v2 的优异性能

高效CNN网络设计

时间消耗分析

目前衡量模型复杂度的一个通用指标是FLOPs,具体指的是卷积层的 multiply-add 数量,但是这却是一个间接指标,因为它不完全等同于速度。如上图中的(c)和(d),可以看到相同FLOPs的两个模型,其速度却存在差异。这种不一致主要归结为两个原因:首先影响速度的不仅仅是FLOPs,如内存使用量(memory access cost, MAC),这不能忽略,对于GPUs来说可能会是瓶颈;模型的并行程度也影响速度,并行度高的模型速度相对更快。另外一个原因,模型在不同平台上的运行速度是有差异的,如GPU和ARM,而且采用不同的库也会有影响。

下图中分析了ShuffleNet v1与MobileNet v2这两个移动端流行网络在GPU/ARM两种平台下的时间消耗分布。

从上图中可看出Conv等计算密集型操作占了其时间的绝大多数,但其它像Elemwise/Data IO等内存读写密集型操作也占了相当比例的时间,因此像以往那样一味以FLOPs来作为指导准则来设计CNN网络是不完备的,虽然它可以反映出占大比例时间的Conv操作。

准则一:输入输出通道数目相同时,卷积计算所需的MAC最小

假设一个1x1卷积层的输入特征通道数是c1,尺寸是h和w,输出特征通道数是c2,那么这样一个1x1卷积层的FLOPs就是下面式子所示:
B=1*1*c_1*c_2*h*w=hwc_1c_2

假设内存足够大,那么内存使用量:
MAC=h*w*c_1+h*w*c_2+1*1*c_1*c_2
分别是输入特征图、输出特征图、卷积核权重的使用量。

根据均值不等式可以得到公式(1)。把MAC和B代入式子1,就得到(c1-c2)2 >= 0,因此等式成立的条件是 c1=c2,也就是输入特征通道数和输出特征通道数相等时,在给定FLOPs前提下,MAC取最小值。

实验也证实了这个观点,改变c1, c2的比例,固定FLOPs时,1:1时速度最快。

准则二:过多的分组卷积会加大MAC

像MobileNet、ShuffleNet、Xception其实都借鉴了卷积的group操作来加速模型,这是因为group操作可以大大减少FLOPs,因此即便适当加宽网络也不会使得FLOPs超过原来不带group的卷积操作,这样就能带来比较明显的效果提升(ResNeXt就是这样的例子)。但是,FLOPs不怎么增加并不代表速度变快,相同层数情况下,ResNeXt的速度要慢许多,差不多只有相同层数的ResNet速度的一半,但是相同层数的ResNeXt的参数量、FLOPs和ResNet是基本差不多的。这就引出了group操作所带来的速度上的影响。

和前面同理,一个带group操作的1x1卷积的FLOPs如下所示,多了一个除数g,g表示group数量。这是因为每个卷积核都只和c1/g个通道的输入特征做卷积,一共c2/g个卷积核,总输出不变:
B=1*1*c_1/g*c_2/g*h*w*g=hwc_1c_2/g

此时MAC为:
MAC = h*w*c_1+h*w*c_2+1*1*c_1/g*c_2 \\ = hw(c_1 + c_2) +c_1c_2/g \\ = hwc_1 +Bg/c_1+B/hw

可见,在B不变时,g越大,MAC也越大。实验也证实:c表示c1+c2的和,通过控制这个参数可以使得每个实验的FLOPs相同,可以看出随着g的不断增大,c也不断增大,这和前面说的在基本不影响FLOPs的前提下,引入group操作后可以适当增加网络宽度吻合。从速度上看,group数量的增加对速度的影响还是很大的,原因就是group数量的增加带来MAC的增加,进而带来速度的降低。

准则三:网络碎片化会降低并行度,即模型中的分支数量越多,模型速度越慢

网络结构设计上,文章用了一个词:fragment,翻译过来就是分裂的意思,可以简单理解为网络的支路数量。为了研究fragment对模型速度的影响,作者做了Table3这个实验,其中2-fragment-series表示一个block中有2个卷积层串行,也就是简单的叠加;4-fragment-parallel表示一个block中有4个卷积层并行,类似Inception的整体设计。可以看出在相同FLOPs的情况下,单卷积层(1-fragment)的速度最快。因此模型支路越多(fragment程度越高)对于并行计算越不利,这样带来的影响就是模型速度变慢,比如Inception、NASNET-A这样的网络。

准则四:Element-wise(元素级)操作会消耗较多的时间

Element-wise类型操作虽然FLOPs非常低,但是带来的时间消耗还是非常明显的。比如ReLU和Add,虽然FLOPs较小,但是却需要较大的MAC。这里实验发现如果将ResNet中残差单元中的ReLU和shortcut移除的话,速度有20%的提升。

结论
  • 1x1卷积平衡输入和输出的通道数;
  • 组卷积要谨慎使用,注意分组数;
  • 避免网络的碎片化;
  • 减少元素级运算

在ShuffleNet v1的操作中违反了四个设计准则:首先它使用了bottleneck 1x1 group conv与module最后的1x1 group conv pointwise模块,使得input channels数目与output channels数目差别较大,违反了上述规则一与规则二;其次由于它整体网络结构中过多的group conv操作的使用从而违反了上述规则三;最后类似于Residual模块中的大量Element-wise sum的使用则进而违反了上述规则四。

ShuffleNet v2中弃用了1x1的group convolution操作,而直接使用了input/output channels数目相同的1x1普通conv。它更是提出了一种ChannelSplit新的类型操作,将module的输入channels分为两部分,一部分直接向下传递,另外一部分则进行真正的向后计算。到了module的末尾,直接将两分支上的output channels数目级连起来,从而规避了原来ShuffleNet v1中Element-wise sum的操作。然后我们再对最终输出的output feature maps进行RandomShuffle操作,从而使得各channels之间的信息相互交通。

网络结构

上图是关于ShuffleNet v1和ShuffleNet v2的结构对比,其中(a)和(b)是ShuffleNet v1的两种不同block结构,同理(c)和(d)是ShuffleNet v2的两种不同block结构。从(a)和(c)的对比可以看出首先(c)在开始处增加了一个channel split操作,这个操作将输入特征的通道分成c-c’和c’,c’在文章中采用c/2,这主要是和前面第1点发现对应。然后(c)中取消了1x1卷积层中的group操作,这和前面第2点发现对应,同时前面的channel split其实已经算是变相的group操作了。其次,channel shuffle的操作移到了concat后面,和前面第3点发现对应,同时也是因为第一个1x1卷积层没有group操作,所以在其后面跟channel shuffle也没有太大必要。最后是将element-wise add操作替换成concat,这个和前面第4点发现对应。多个(c)结构连接在一起的话,channel split、concat和channel shuffle是可以合并在一起的。(b)和(d)的对比也是同理,只不过因为(d)的开始处没有channel split操作,所以最后concat后特征图通道数翻倍,可以结合后面Table5的具体网络结构来看。

Table5是ShuffleNet v2的具体网络结构示意图,不同stage的输出通道倍数关系和前面描述的吻合,每个stage都是由Figure3(c)(d)所示的block组成,block的具体数量对于Figure5中的Repeat列。

实验结果

明显看到ShuffleNet v2在精度和速度上的优势。

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

推荐阅读更多精彩内容