PyTorch實現(xiàn)經(jīng)典網(wǎng)絡(luò)之ResNet

簡介

深度殘差網(wǎng)絡(luò)(Deep residual network, ResNet)的提出是CNN圖像史上的一件里程碑事件磁浇,讓我們先看一下ResNet在ILSVRC和COCO數(shù)據(jù)集上的戰(zhàn)績:

ResNet取得了5項第一,并又一次刷新了CNN模型在ImageNet上的歷史成績猩系。ResNet的主要創(chuàng)新點在于設(shè)計了一種使用了Shortcut Connection的殘差結(jié)構(gòu),使得網(wǎng)絡(luò)可以設(shè)計的很深,有效解決了梯度消失問題并且同時提升了性能犀概。


網(wǎng)絡(luò)退化問題

深度卷積神經(jīng)網(wǎng)絡(luò)在圖像識別領(lǐng)域取得了一系列重大的突破综看。深度神經(jīng)網(wǎng)絡(luò)以端到端的多層方式集成了低級脾拆、中級馒索、高級的特征以及分類器,通過增加網(wǎng)絡(luò)層數(shù)名船,網(wǎng)絡(luò)可以進(jìn)行更加復(fù)雜的特征提取绰上。最近的一些證據(jù)表明網(wǎng)絡(luò)深度對模型的性能至關(guān)重要,在ImageNet數(shù)據(jù)集上的表現(xiàn)良好的模型普遍層數(shù)都較大渠驼。即便是在一些非視覺識別的任務(wù)上蜈块,深度模型也帶來了很大的好處。

隨之而來的一個問題是迷扇,網(wǎng)絡(luò)深度越深性能就一定越好嗎百揭?實際上臭名昭著的梯度消失、爆炸問題從一開始就阻礙了模型收斂谋梭,這使得深度越深的模型越難訓(xùn)練信峻。雖然目前已有的一些手段比如BatchNorm可以有效緩解這個問題,但是隨后出現(xiàn)的網(wǎng)絡(luò)模型退化(degradation)問題卻更加棘手瓮床。隨著網(wǎng)絡(luò)深度增加,準(zhǔn)確率開始趨于飽和并且快速下降产镐。出乎意料的是隘庄,這種下降并不是由于過擬合導(dǎo)致的。作者通過實驗證明癣亚,在一個適當(dāng)?shù)哪P蜕蠁渭兲砑恿烁鄬泳蜁?dǎo)致更高的訓(xùn)練錯誤率丑掺,如下圖所示:

作者分別使用了20層和56層的網(wǎng)絡(luò)結(jié)構(gòu)在CIFAR-10數(shù)據(jù)集上進(jìn)行對比實驗,可以看到隨著網(wǎng)絡(luò)層數(shù)加深述雾,訓(xùn)練錯誤率和測試錯誤率反而越高街州。

殘差結(jié)構(gòu)

深度網(wǎng)絡(luò)的退化問題表明不是所有的系統(tǒng)都容易優(yōu)化兼丰。假設(shè)我們現(xiàn)在有一個淺層網(wǎng)絡(luò),我們再通過以下方式構(gòu)造一個對應(yīng)的深層網(wǎng)絡(luò)唆缴。這個深層網(wǎng)絡(luò)首先復(fù)制已經(jīng)訓(xùn)練好的淺層網(wǎng)絡(luò)鳍征,其次再往上堆疊更多的恒等映射(Identity mapping)層,即這些新增的層什么都不學(xué)習(xí)面徽。在這種情況下艳丛,這個深層網(wǎng)絡(luò)應(yīng)該至少和淺層網(wǎng)絡(luò)性能一樣,也不應(yīng)該出現(xiàn)退化現(xiàn)象趟紊。但是實驗表明我們目前掌握的方法無法構(gòu)造出這種對應(yīng)的深層網(wǎng)絡(luò)(也有可能是無法在有限時間內(nèi)找到)氮双。
為此,論文作者提出了殘差學(xué)習(xí)來解決網(wǎng)絡(luò)退化問題霎匈。對于一個堆積層結(jié)構(gòu)(由幾層疊加組成)戴差,當(dāng)輸入為x時,傳統(tǒng)方式是期望它學(xué)到的特征為H(x)铛嘱。但是對于殘差網(wǎng)絡(luò)而言造挽,它期望這個堆積層學(xué)到的特征為F(x),其中F(x) = H(x) - x弄痹,即這個堆積層學(xué)到的特征F(x)可以看成是在學(xué)習(xí)實際輸出H(x)和輸入x之間的殘差饭入,所以命名為殘差模塊。那么原始輸出H(x) = F(x)+x肛真。作者認(rèn)為學(xué)習(xí)殘差特征F(x)會比直接學(xué)習(xí)原始特征H(x)更容易谐丢。在極端情況下,當(dāng)殘差F(x)=0時蚓让,此時堆積層僅僅做了恒等映射乾忱,即這些堆積的層不會引起網(wǎng)絡(luò)性能下降。當(dāng)然實際上殘差也不會為0历极,這也會使得殘差結(jié)構(gòu)可以在輸入特征的基礎(chǔ)上學(xué)習(xí)到新的特征窄瘟,從而即加大了網(wǎng)絡(luò)深度并且學(xué)習(xí)了更復(fù)雜的特征,但同時又不會引起網(wǎng)絡(luò)性能下降趟卸。
殘差網(wǎng)絡(luò)結(jié)構(gòu)如下:

殘差結(jié)構(gòu)

其中右邊的曲線就是代表的恒等映射蹄葱,它跳過了2個層,直接從輸入連接到了輸出锄列,有點類似電路中的短路連接(shortcut connection)图云。這種短路連接既不需要額外的參數(shù),也不會增加計算復(fù)雜度邻邮。整個網(wǎng)絡(luò)仍然可以使用SGD算法搭配反向傳播來進(jìn)行端到端的訓(xùn)練竣况。

這里簡單分析一下為什么殘差學(xué)習(xí)相對容易,從直觀上看筒严,讓網(wǎng)絡(luò)直接學(xué)習(xí)x → H(x)-x的映射丹泉,會比讓網(wǎng)絡(luò)直接學(xué)習(xí)x→H(x)的映射所學(xué)的內(nèi)容少情萤。因為殘差一般比較小,學(xué)習(xí)難度小一點摹恨。下面從數(shù)學(xué)的角度來分析這個問題筋岛,殘差模塊可以表示為:
y_l = h(x_l) + F(x_l, W_l)\\ x_{l+1} = f(y_l)
其中x_lx_{l+1}表示第l個殘差單元的輸入和輸出,注意每一個殘差單元一般包含多層結(jié)構(gòu)睬塌。F是殘差函數(shù)泉蝌,表示殘差網(wǎng)絡(luò)學(xué)習(xí)到的殘差。h函數(shù)代表的是恒等映射揩晴,即上圖中的曲線部分勋陪,那么有h(x_l) = x_lf是ReLU激活函數(shù)硫兰∽缬蓿基于上式,我們求得網(wǎng)絡(luò)從淺層l到深層L學(xué)習(xí)到的特征為:
x_L = x_l + \sum_{i=l}^{L-1}F(x_i,W_i)
利用鏈?zhǔn)椒▌t劫映,可以求得反向過程的梯度:
\frac{\partial l o s s}{\partial x_{l}}=\frac{\partial l o s s}{\partial x_{L}} \cdot \frac{\partial x_{L}}{\partial x_{l}}=\frac{\partial l o s s}{\partial x_{L}} \cdot\left(1+\frac{\partial}{\partial x_{l}} \sum_{i=l}^{L-1} F\left(x_{i}, W_{i}\right)\right) \
其中注意看小括號中的部分违孝,其中的1表明短路機(jī)制可以無損地傳播梯度,而另外一項殘差則需要繼續(xù)經(jīng)過鏈?zhǔn)椒▌t求導(dǎo)獲得殘差梯度再傳播泳赋。而殘差梯度也不會那么巧剛好為-1雌桑,這就意味著總體梯度不太可能每次都為0,因此使得網(wǎng)絡(luò)變得更加容易學(xué)習(xí)祖今。
完整的內(nèi)容可以參考論文《Identity Mappings in Deep Residual Networks》校坑。


網(wǎng)絡(luò)結(jié)構(gòu)

ResNet網(wǎng)絡(luò)結(jié)構(gòu)主要參考了VGG19網(wǎng)絡(luò),在其基礎(chǔ)上通過短路連接加上了殘差單元千诬。ResNet大多使用3x3的卷積核并且遵循以下兩條設(shè)計原則:

  1. 對于同樣的輸出feature map大小耍目,每層擁有同樣數(shù)量的filters。
  2. 當(dāng)feature map的大小降低一半時徐绑,feature map的數(shù)量增加一倍邪驮,以保持網(wǎng)絡(luò)的復(fù)雜度。

ResNet34結(jié)構(gòu)如下:
ResNet網(wǎng)絡(luò)模型圖

上圖中最左邊是VGG-19網(wǎng)絡(luò)傲茄,中間是樸素ResNet-34網(wǎng)絡(luò)毅访,右邊是包含殘差單元的ResNet-34網(wǎng)絡(luò)。其中ResNet相比普通網(wǎng)絡(luò)在每兩層之間添加了短路機(jī)制烫幕,這就形成了殘差學(xué)習(xí)俺抽。虛線表示的是feature map的數(shù)量發(fā)生了變化。

下面是不同深度的ResNet網(wǎng)絡(luò)的架構(gòu)參數(shù)描述表:
ResNet架構(gòu)參數(shù)表

其中以ResNet34為例较曼,紅色部分代表的是不同殘差層的殘差單元的數(shù)量。

殘差單元

上圖中進(jìn)行的是兩層間的殘差學(xué)習(xí)振愿,當(dāng)網(wǎng)絡(luò)更深的時候捷犹,可以進(jìn)行3層之間的殘差學(xué)習(xí)弛饭。下面是不同的殘差單元示意圖:
2種不同的殘差單元

網(wǎng)絡(luò)結(jié)構(gòu)剖析

接下來以ResNet-34為例,一層一層地分析它的結(jié)構(gòu)萍歉,首先從另外一個角度來看一下ResNet-34侣颂。
ResNet-34

我們的輸入圖像是224x224,首先通過1個卷積層枪孩,接著通過4個殘差層憔晒,最后通過Softmax之中輸出一個1000維的向量,代表ImageNet的1000個分類蔑舞。

1.卷積層1

ResNet的第一步是將圖像通過一個名為Conv1的塊拒担,這個塊包含卷積操作、批量歸一化攻询、最大池化操作从撼。

首先是卷積操作,在ResNet架構(gòu)參數(shù)表中可以看到Conv1塊的卷積核的大小是7x7钧栖,并且要注意到低零,這里進(jìn)行卷積操作的時候設(shè)置padding大小為3,stride為2拯杠,故最后輸出的圖像大小為112掏婶。又特征圖數(shù)量為64,故最后輸出包含了64個通道潭陪,最終大小為112x112x64雄妥。下圖展示了完整的計算過程(為了簡化,這里省略掉了批量歸一化操作畔咧,其實它也并不改變輸出的大小茎芭。):
Conv1操作

最大池化操作的時候設(shè)置padding大小為2,步長為2誓沸,池化塊大小為3梅桩,因此得到最后輸出大小為56。完整計算過程見下圖:
最大池化操作

2.殘差層

我們先來解釋一個名詞拜隧,塊宿百。ResNet的每一層都包含若干個塊。這是因為ResNet網(wǎng)絡(luò)深度的加大是通過增加一個塊中的操作來實現(xiàn)的洪添,而總體的層數(shù)仍然保持不變垦页。這里所說的一個塊中的操作通常指的是對輸入進(jìn)行卷積操作、批量歸一化操作以及通過ReLU激活函數(shù)干奢,當(dāng)然除了最后一個塊痊焊,因為它不包含ReLU激活函數(shù)。

塊操作

我們先來描述一下一個塊中的操作是怎樣的?見下圖:
第一個殘差單元中的第一個操作

經(jīng)過Conv1層之后薄啥,我們的輸入變?yōu)榱?6x56辕羽,接著通過查看ResNet架構(gòu)參數(shù)表中可得,使用的是[3x3,64]的卷積核垄惧,輸出大小是56x56刁愿。我們需要注意的是,在一個block中進(jìn)行的操作是不會改變輸入大小的到逊。這是因為我們設(shè)置padding為1铣口,并且步長也設(shè)置為1。所以得到的輸出大小與輸入一致觉壶。

接下來我們展示一下一個包含2層的完整殘差單元的計算示意圖如下(卷積核為2x[3x3,64]):
第一層中的第一個殘差單元

上圖的左半部分代表的是實際計算過程脑题,右圖對應(yīng)的是ResNet模型框架圖中的部分。

同理掰曾,3個殘差單元堆疊起來之后的計算示意圖如下(卷積核為3x[3x3,64]):

ResNet網(wǎng)絡(luò)結(jié)構(gòu)圖中的其他層也類似旭蠕,只要知道其中一層的殘差單元計算方式,我們很容易就可以推廣到整個網(wǎng)絡(luò)結(jié)構(gòu)中去旷坦。

如果我們仔細(xì)觀察每一層的第一個操作掏熬,我們會發(fā)現(xiàn)第一個操作使用的stride設(shè)置為2,而其余操作的stride設(shè)置為1秒梅。這意味著網(wǎng)絡(luò)是通過增大步長來進(jìn)行下采樣的旗芬,而不是像傳統(tǒng)CNN網(wǎng)絡(luò)那樣通過池化操作來進(jìn)行。實際上捆蜀,只有Conv1層中使用了一個最大池化操作疮丛,以及在ResNet末尾的全連接層之前執(zhí)行了一個平均池化操作。
降維操作

上圖的紅色部分代表的是第三和第四層中的第一個殘差單元辆它,藍(lán)色部分代表的殘差單元中的第一個塊操作誊薄,可以看到stride設(shè)置為2,而其余均為默認(rèn)值1锰茉。

再看一下上圖呢蔫,模型架構(gòu)中的虛線代表的是要改變輸入的維度,對于短路連接飒筑,當(dāng)輸入和輸出維度一致時片吊,可以直接將輸入加到輸出上。但是當(dāng)維度不一致時协屡,這就不能直接相加俏脊。注意看ResNet網(wǎng)絡(luò)模型圖,每個不同顏色代表的不同的層肤晓,不同層之間的輸入和輸出大小是不一樣的爷贫,因此不能直接相加认然,實際上每個不同層所做的第一個操作就是降低維度。關(guān)于降低維度主要有兩種策略:

  1. 采用zero-padding增加維度沸久,此時一般要先做一個downsamp季眷,可以采用strde=2的pooling余蟹,這樣不會增加參數(shù)卷胯。
  2. 采用新的映射(Projection Shortcut),一般采用1x1的卷積威酒,這樣會增加參數(shù)窑睁,也會增加計算量。

下面展示一下Projection Shortcut方式的計算過程葵孤。以下圖為例担钮,輸入為56x56x64,輸出為28x28x128尤仍,選擇3x3大小的卷積核箫津,通過設(shè)置stride為2,padding為1宰啦,得到輸出大小為28x28苏遥。


padding方式

接著采用1x1的卷積,stride設(shè)置為1赡模,padding設(shè)置為0田炭,得到的輸出大小為28x28。


Projection Shortcut

經(jīng)過上述2個操作之后漓柑,每層中的第一個殘差單元的整體計算流程如下教硫,此時殘差輸出和Projetion Shortcut的輸出大小是一致的,可以直接相加辆布。
第二層的第一個殘差單元

下面這張示意圖展示了ResNet第二層的整體計算過程瞬矩。


第二層計算示意圖

接下來的3、4層計算流程也是一樣的锋玲,就不再贅述景用。

實驗結(jié)果

下圖是ResNet與其他模型在ImageNet數(shù)據(jù)集上的結(jié)果對比,可以看到ResNet-152在Top-1和Top-5的錯誤率上均達(dá)到了SOTA嫩絮,再仔細(xì)觀察下ResNet網(wǎng)絡(luò)自身之間的對比丛肢,也可以發(fā)現(xiàn)隨著層數(shù)的增加,錯誤率持續(xù)降低剿干,可見ResNet有效地解決了層數(shù)增加帶來的副作用蜂怎。
ResNet與其他網(wǎng)絡(luò)結(jié)果對比

代碼實踐

網(wǎng)絡(luò)模型定義相關(guān)代碼,主要定義了BasicBlock類置尔,即包含2個卷積塊的殘差單元杠步;Bottleneck類,即包含了3個卷積塊的殘差單元;以及ResNet類幽歼,定義了整個網(wǎng)絡(luò)結(jié)構(gòu)朵锣。完整代碼如下:

import torch
import torch.nn as nn
import torch.nn.functional as F

class BasicBlock(nn.Module):
    # 2層的殘差單元
    expansion = 1
    def __init__(self, in_planes, planes, stride=1):
        super(BasicBlock, self).__init__()

        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        # 第二個卷積操作不改變維度和輸出大小,因為stride=1 padding=1
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)

        self.shortcut = nn.Sequential()
        # 如果步長不為1,或者輸入與輸出通道不一致甸私,則需要進(jìn)行Projection Shortcut操作
        if stride != 1 or in_planes != self.expansion*planes:
            # Projection Shortcut
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(self.expansion*planes)
            )

    def forward(self, x):
        # 依次通過兩個卷積層诚些,和shortcut連接層,再累加起來皇型。
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out

class Bottleneck(nn.Module):
    # 3層的殘差單元
    expansion = 4
    def __init__(self, in_planes, planes, stride=1):
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3,
                               stride=stride, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)
        self.conv3 = nn.Conv2d(planes, self.expansion *
                               planes, kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(self.expansion*planes)

        self.shortcut = nn.Sequential()
        if stride != 1 or in_planes != self.expansion*planes:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, self.expansion*planes,
                          kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(self.expansion*planes)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = F.relu(self.bn2(self.conv2(out)))
        out = self.bn3(self.conv3(out))
        out += self.shortcut(x)
        out = F.relu(out)
        return out

class ResNet(nn.Module):
    def __init__(self, config):
        super(ResNet, self).__init__()
        self._config = config
        # 默認(rèn)輸入通道為64
        self.in_channels = 64

        # 代表ResNet中的Conv1卷積層
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(64)

        # 分別代表ResNet中的4層
        self.layer1 = self._make_layer(config['block_type'], 64, config['num_blocks'][0], stride=1)
        self.layer2 = self._make_layer(config['block_type'], 128, config['num_blocks'][1], stride=2)
        self.layer3 = self._make_layer(config['block_type'], 256, config['num_blocks'][2], stride=2)
        self.layer4 = self._make_layer(config['block_type'], 512, config['num_blocks'][3], stride=2)
        self.linear = nn.Linear(512 * config['block_type'].expansion, config['num_classes'])

    def _make_layer(self, block, planes, num_blocks, stride):
        strides = [stride] + [1]*(num_blocks-1)
        layers = []
        for stride in strides:
            layers.append(block(self.in_channels, planes, stride))
            self.in_channels = planes * block.expansion
        return nn.Sequential(*layers)

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.layer1(out)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = F.avg_pool2d(out, 4)
        out = out.view(out.size(0), -1)
        out = self.linear(out)
        return out

    def saveModel(self):
        torch.save(self.state_dict(), self._config['model_name'])

    def loadModel(self, map_location):
        state_dict = torch.load(self._config['model_name'], map_location=map_location)
        self.load_state_dict(state_dict, strict=False)

配置模型參數(shù)定義ResNet-18網(wǎng)絡(luò)诬烹,設(shè)置batch size為500,訓(xùn)練輪次20弃鸦,采用Adam優(yōu)化算法绞吁,學(xué)習(xí)率設(shè)置為0.0001。
測試相關(guān)代碼如下:

import torch
from ResNet.network import ResNet
from ResNet.network import BasicBlock
from ResNet.network import Bottleneck
from ResNet.trainer import Trainer
from ResNet.dataloader import LoadCIFAR10
from ResNet.dataloader import Construct_DataLoader
from torch.autograd import Variable

resnet_config = \
{
    'block_type': BasicBlock,
    'num_blocks': [2,2,2,2], #ResNet18
    'num_epoch': 20,
    'batch_size': 500,
    'lr': 1e-3,
    'l2_regularization':1e-4,
    'num_classes': 10,
    'device_id': 0,
    'use_cuda': True,
    'model_name': '../TrainedModels/ResNet18.model'
}

if __name__ == "__main__":
    ####################################################################################
    # ResNet 模型
    ####################################################################################
    train_dataset, test_dataset = LoadCIFAR10(True)
    # define ResNet model
    resNet = ResNet(resnet_config)

    ####################################################################################
    # 模型訓(xùn)練階段
    ####################################################################################
    # 實例化模型訓(xùn)練器
    trainer = Trainer(model=resNet, config=resnet_config)
    # 訓(xùn)練
    trainer.train(train_dataset)
    # 保存模型
    trainer.save()

    ####################################################################################
    # 模型測試階段
    ####################################################################################
    resNet.eval()
    if resnet_config['use_cuda']:
        resNet.loadModel(map_location=torch.device('cpu'))
        resNet = resNet.cuda()
    else:
        resNet.loadModel(map_location=lambda storage, loc: storage.cuda(resnet_config['device_id']))

    correct = 0
    total = 0
    for images, labels in Construct_DataLoader(test_dataset, resnet_config['batch_size']):
        images = Variable(images)
        labels = Variable(labels)
        if resnet_config['use_cuda']:
            images = images.cuda()
            labels = labels.cuda()

        y_pred = resNet(images)
        _, predicted = torch.max(y_pred.data, 1)
        total += labels.size(0)
        temp = (predicted == labels.data).sum()
        correct += temp
    print('Accuracy of the model on the test images: %.2f%%' % (100.0 * correct / total))

測試結(jié)果

訓(xùn)練和測試都是在CIFAR-10小型圖像數(shù)據(jù)集上進(jìn)行唬格,經(jīng)過20次迭代之后家破,在訓(xùn)練集上得到97.96%的準(zhǔn)確率,在測試集上得到81.41%的準(zhǔn)確率购岗。通過參數(shù)調(diào)整還可以達(dá)到更高的準(zhǔn)確率汰聋。
ResNet-18在CIFAR-10訓(xùn)練集和測試集上的準(zhǔn)確率

完整代碼見https://github.com/HeartbreakSurvivor/ClassicNetworks/tree/master/ResNet


參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末藕畔,一起剝皮案震驚了整個濱河市马僻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌注服,老刑警劉巖韭邓,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異溶弟,居然都是意外死亡女淑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門辜御,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸭你,“玉大人,你說我怎么就攤上這事擒权「ぞ蓿” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵碳抄,是天一觀的道長愉老。 經(jīng)常有香客問我,道長剖效,這世上最難降的妖魔是什么嫉入? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任焰盗,我火速辦了婚禮,結(jié)果婚禮上咒林,老公的妹妹穿的比我還像新娘熬拒。我一直安慰自己,他們只是感情好垫竞,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布澎粟。 她就那樣靜靜地躺著,像睡著了一般件甥。 火紅的嫁衣襯著肌膚如雪捌议。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天引有,我揣著相機(jī)與錄音,去河邊找鬼倦逐。 笑死譬正,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的檬姥。 我是一名探鬼主播曾我,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼健民!你這毒婦竟也來了抒巢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤秉犹,失蹤者是張志新(化名)和其女友劉穎蛉谜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崇堵,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡型诚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鸳劳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狰贯。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖赏廓,靈堂內(nèi)的尸體忽然破棺而出涵紊,到底是詐尸還是另有隱情,我是刑警寧澤幔摸,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布摸柄,位于F島的核電站,受9級特大地震影響抚太,放射性物質(zhì)發(fā)生泄漏塘幅。R本人自食惡果不足惜昔案,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望电媳。 院中可真熱鬧踏揣,春花似錦、人聲如沸匾乓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拼缝。三九已至娱局,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間咧七,已是汗流浹背衰齐。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留继阻,地道東北人耻涛。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像瘟檩,于是被迫代替她去往敵國和親抹缕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容