pytorch之批量歸一化和殘差網(wǎng)絡(luò)

5.10批量歸一化

本節(jié)我們介紹批量歸一化(batch normalization)層,它使較深的神經(jīng)網(wǎng)絡(luò)的訓(xùn)練變得更加容易[1]掌栅。我們對(duì)輸入數(shù)據(jù)做了標(biāo)準(zhǔn)化處理:處理后的任意一個(gè)特征在數(shù)據(jù)集中所有樣本上的均值0半开,標(biāo)準(zhǔn)差為1混滔。標(biāo)準(zhǔn)化處理輸入數(shù)據(jù)使各個(gè)特征的分布相近:這往往更容易訓(xùn)練出有效的模型舶吗。

通常來說征冷,數(shù)據(jù)標(biāo)準(zhǔn)化對(duì)于淺層模型就足夠有效了。進(jìn)行模型訓(xùn)練的進(jìn)行裤翩,當(dāng)每層中參數(shù)更新時(shí)资盅,靠近輸出層的輸出較難出現(xiàn)梯度變化。但對(duì)深層神經(jīng)網(wǎng)絡(luò)來說踊赠,呵扛,即使輸入數(shù)據(jù)已完成規(guī)范,訓(xùn)練中模型參數(shù)的更新仍然很容易造成靠近輸出層輸出的高度變化筐带。這種計(jì)算數(shù)值的不穩(wěn)定性通常令我們難以訓(xùn)練出有效的深度模型今穿。

在模型訓(xùn)練時(shí),批量歸一化利用小批量上的均值和標(biāo)準(zhǔn)差伦籍,不斷調(diào)整神經(jīng)網(wǎng)絡(luò)中間輸出蓝晒,從而使整個(gè)神經(jīng)網(wǎng)絡(luò)在各層的中間輸出的數(shù)值更穩(wěn)定。批量歸一化和下分段將要介紹的殘差網(wǎng)絡(luò)為訓(xùn)練和設(shè)計(jì)深度模型提供了兩類重要的思路帖鸦。

5.10.1批量歸一化層

對(duì)全連接層和卷積層做批量歸一化的方法稍有不同芝薇。下面我們將分別介紹這兩種情況下的批量歸一化。

5.10.1.1對(duì)全連接層做批量歸一化

5.10.1.3預(yù)測(cè)時(shí)的批量歸一化

使用批量歸一化訓(xùn)練時(shí)作儿,我們可以將批量大小設(shè)得大一點(diǎn)洛二,從而使批量?jī)?nèi)樣本的均值和方差的計(jì)算都正確地對(duì)齊。將訓(xùn)練好的模型用于預(yù)測(cè)時(shí)攻锰,我們希望模型對(duì)于任意輸入都有確定的輸出晾嘶。因此,零散的樣本的輸出范圍應(yīng)至少部分歸零一化所需要的隨機(jī)小批量中的均值和方差娶吞。一種常用的方法是通過移動(dòng)平均采樣整個(gè)訓(xùn)練數(shù)據(jù)集的樣本均值和方差垒迂,并在預(yù)測(cè)時(shí)使用。它們的體積相同妒蛇,批量歸一化層在訓(xùn)練模式和預(yù)測(cè)模式下的計(jì)算結(jié)果也是不一樣的机断。

5.10.2從零開始實(shí)現(xiàn)

下面我們自己實(shí)現(xiàn)批量歸一化層。

import time
import torch
from torch import nn, optim
import torch.nn.functional as F

import sys
sys.path.append("..") 
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

def batch_norm(is_training, X, gamma, beta, moving_mean, moving_var, eps, momentum):
    # 判斷當(dāng)前模式是訓(xùn)練模式還是預(yù)測(cè)模式
    if not is_training:
        # 如果是在預(yù)測(cè)模式下绣夺,直接使用傳入的移動(dòng)平均所得的均值和方差
        X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
    else:
        assert len(X.shape) in (2, 4)
        if len(X.shape) == 2:
            # 使用全連接層的情況吏奸,計(jì)算特征維上的均值和方差
            mean = X.mean(dim=0)
            var = ((X - mean) ** 2).mean(dim=0)
        else:
            # 使用二維卷積層的情況,計(jì)算通道維上(axis=1)的均值和方差乐导。這里我們需要保持
            # X的形狀以便后面可以做廣播運(yùn)算
            mean = X.mean(dim=0, keepdim=True).mean(dim=2, keepdim=True).mean(dim=3, keepdim=True)
            var = ((X - mean) ** 2).mean(dim=0, keepdim=True).mean(dim=2, keepdim=True).mean(dim=3, keepdim=True)
        # 訓(xùn)練模式下用當(dāng)前的均值和方差做標(biāo)準(zhǔn)化
        X_hat = (X - mean) / torch.sqrt(var + eps)
        # 更新移動(dòng)平均的均值和方差
        moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
        moving_var = momentum * moving_var + (1.0 - momentum) * var
    Y = gamma * X_hat + beta  # 拉伸和偏移
    return Y, moving_mean, moving_var

接下來,我們自定義一個(gè)BatchNorm層浸颓。它保存參與求梯度和迭代的拉伸參數(shù)gamma和轉(zhuǎn)換參數(shù)beta物臂,同時(shí)也維護(hù)移動(dòng)平均得到的均值和方差旺拉,剎車能夠在模型預(yù)測(cè)時(shí)被使用。BatchNorm實(shí)例所需指定的num_features該實(shí)例所需指定的num_dims參數(shù)對(duì)于全連接層和卷積層來說分別為2和4棵磷。

class BatchNorm(nn.Module):
    def __init__(self, num_features, num_dims):
        super(BatchNorm, self).__init__()
        if num_dims == 2:
            shape = (1, num_features)
        else:
            shape = (1, num_features, 1, 1)
        # 參與求梯度和迭代的拉伸和偏移參數(shù)蛾狗,分別初始化成0和1
        self.gamma = nn.Parameter(torch.ones(shape))
        self.beta = nn.Parameter(torch.zeros(shape))
        # 不參與求梯度和迭代的變量,全在內(nèi)存上初始化成0
        self.moving_mean = torch.zeros(shape)
        self.moving_var = torch.zeros(shape)

    def forward(self, X):
        # 如果X不在內(nèi)存上仪媒,將moving_mean和moving_var復(fù)制到X所在顯存上
        if self.moving_mean.device != X.device:
            self.moving_mean = self.moving_mean.to(X.device)
            self.moving_var = self.moving_var.to(X.device)
        # 保存更新過的moving_mean和moving_var, Module實(shí)例的traning屬性默認(rèn)為true, 調(diào)用.eval()后設(shè)成false
        Y, self.moving_mean, self.moving_var = batch_norm(self.training, 
            X, self.gamma, self.beta, self.moving_mean,
            self.moving_var, eps=1e-5, momentum=0.9)
        return Y

5.10.2.1使用批量歸一化層的LeNet

下面我們修改5.5節(jié)(卷積神經(jīng)網(wǎng)絡(luò)(LeNet))介紹的LeNet模型沉桌,從而應(yīng)用批量歸一化層。我們?cè)谒械木矸e層或全連接層之后算吩,激活層之前加入批量歸一化層留凭。

net = nn.Sequential(
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            BatchNorm(6, num_dims=4),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2), # kernel_size, stride
            nn.Conv2d(6, 16, 5),
            BatchNorm(16, num_dims=4),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2),
            d2l.FlattenLayer(),
            nn.Linear(16*4*4, 120),
            BatchNorm(120, num_dims=2),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            BatchNorm(84, num_dims=2),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )

下面我們訓(xùn)練修改后的模型。

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)

lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)

輸出:

training on  cuda
epoch 1, loss 0.0039, train acc 0.790, test acc 0.835, time 2.9 sec
epoch 2, loss 0.0018, train acc 0.866, test acc 0.821, time 3.2 sec
epoch 3, loss 0.0014, train acc 0.879, test acc 0.857, time 2.6 sec
epoch 4, loss 0.0013, train acc 0.886, test acc 0.820, time 2.7 sec
epoch 5, loss 0.0012, train acc 0.891, test acc 0.859, time 2.8 sec

最后我們查看第一個(gè)批量歸一化層學(xué)習(xí)到的拉伸參數(shù)gamma和轉(zhuǎn)換參數(shù)beta偎巢。

net[1].gamma.view((-1,)), net[1].beta.view((-1,))

輸出:

(tensor([ 1.2537,  1.2284,  1.0100,  1.0171,  0.9809,  1.1870], device='cuda:0'),
 tensor([ 0.0962,  0.3299, -0.5506,  0.1522, -0.1556,  0.2240], device='cuda:0'))

5.10.3簡(jiǎn)潔實(shí)現(xiàn)

與我們剛剛自己定義的BatchNorm類比例蔼夜,Pytorch中nn模塊定義的BatchNorm1dBatchNorm2d類使用起來更加簡(jiǎn)單,同時(shí)分別為全連接層和卷積層压昼,都需要指定輸入的num_features參數(shù)值求冷。下面我們用PyTorch實(shí)現(xiàn)使用規(guī)模歸一化的LeNet。

net = nn.Sequential(
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            nn.BatchNorm2d(6),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2), # kernel_size, stride
            nn.Conv2d(6, 16, 5),
            nn.BatchNorm2d(16),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2),
            d2l.FlattenLayer(),
            nn.Linear(16*4*4, 120),
            nn.BatchNorm1d(120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.BatchNorm1d(84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )

使用同樣的超參數(shù)進(jìn)行訓(xùn)練窍霞。

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)

lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)

輸出:

training on  cuda
epoch 1, loss 0.0054, train acc 0.767, test acc 0.795, time 2.0 sec
epoch 2, loss 0.0024, train acc 0.851, test acc 0.748, time 2.0 sec
epoch 3, loss 0.0017, train acc 0.872, test acc 0.814, time 2.2 sec
epoch 4, loss 0.0014, train acc 0.883, test acc 0.818, time 2.1 sec
epoch 5, loss 0.0013, train acc 0.889, test acc 0.734, time 1.8 sec

小結(jié)

  • 在模型訓(xùn)練時(shí)匠题,批量歸一化利用小批量上的均值和標(biāo)準(zhǔn)差,不斷調(diào)整神經(jīng)網(wǎng)絡(luò)的中間輸出但金,從而使整個(gè)神經(jīng)網(wǎng)絡(luò)在各層的中間輸出的數(shù)值更穩(wěn)定韭山。
  • 對(duì)全連接層和卷積層做批量歸一化的方法稍有不同。
  • 批量歸一化層和替代層一樣傲绣,在訓(xùn)練模式和預(yù)測(cè)模式的計(jì)算結(jié)果是不一樣的掠哥。
  • PyTorch提供了BatchNorm類方便使用。

5.11殘差網(wǎng)絡(luò)(ResNet)

讓我們先思考一個(gè)問題:對(duì)神經(jīng)網(wǎng)絡(luò)模型添加新的層秃诵,充分訓(xùn)練后的模型是否只可能更有效地降低訓(xùn)練誤差续搀?理論上,原模型解的空間只是新模型解的空間的子空間菠净。也就是說禁舷,如果我們能將新添加的層訓(xùn)練成恒等映射f (x )=x,新模型和原模型將同樣有效毅往。由于新模型可能會(huì)帶來更優(yōu)的解來擬合訓(xùn)練數(shù)據(jù)集牵咙,因此添加層似乎更容易降低訓(xùn)練誤差。而在實(shí)踐中攀唯,添加過多的層后訓(xùn)練誤差往往不降反升洁桌。甚至利用批量歸一化帶來的數(shù)值穩(wěn)定性使訓(xùn)練深層模型更加容易,該問題仍然存在侯嘀。針對(duì)這一問題另凌,何愷明等人提出了殘差網(wǎng)絡(luò)(ResNet)[1] 谱轨。它在2015年的ImageNet圖像識(shí)別挑戰(zhàn)賽奪魁,并深刻影響了后來的深度神經(jīng)網(wǎng)絡(luò)的設(shè)計(jì)吠谢。

5.11.2殘差塊

殘差映射在實(shí)際中經(jīng)常更容易優(yōu)化土童。以本節(jié)開頭提到的恒等映射作為我們希望學(xué)出的理想映射我們只需將圖5.9中右圖虛線框內(nèi)上方的重組運(yùn)算(如仿射)的權(quán)重和偏差參數(shù)學(xué)成0,那么f (x)即為恒等映射工坊。

import time
import torch
from torch import nn, optim
import torch.nn.functional as F

import sys
sys.path.append("..") 
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

class Residual(nn.Module):  # 本類已保存在d2lzh_pytorch包中方便以后使用
    def __init__(self, in_channels, out_channels, use_1x1conv=False, stride=1):
        super(Residual, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1, stride=stride)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1)
        if use_1x1conv:
            self.conv3 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.bn2 = nn.BatchNorm2d(out_channels)

    def forward(self, X):
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        return F.relu(Y + X)

下面我們來查看輸入和輸出形狀一致的情況献汗。

blk = Residual(3, 3)
X = torch.rand((4, 3, 6, 6))
blk(X).shape # torch.Size([4, 3, 6, 6])

我們也可以在增加輸出通道數(shù)的同時(shí)減半輸出的高和寬。

blk = Residual(3, 6, use_1x1conv=True, stride=2)
blk(X).shape # torch.Size([4, 6, 3, 3])

5.11.2 ResNet模型

ResNet的前兩層跟之前介紹的GoogLeNet中的一樣:在輸出通道數(shù)為64王污,步幅為2的7×7卷積層后接步幅為2的3×3的最大池化層罢吃。不同之處在于ResNet每個(gè)卷積層后增加的批量歸一化層。

net = nn.Sequential(
        nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
        nn.BatchNorm2d(64), 
        nn.ReLU(),
        nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

ResNet則使用4個(gè)由殘差塊組成的模塊玉掸,每個(gè)模塊使用多個(gè)相同輸出通道數(shù)的殘差塊刃麸。第一個(gè)模塊的通道數(shù)同輸入通道數(shù)一致。由于之前已經(jīng)使用了步幅為2的最大池化層司浪,所以無須對(duì)準(zhǔn)高和寬泊业。之后的每個(gè)模塊在第一個(gè)殘差塊里將上一個(gè)模塊的通道數(shù)翻倍,成為高和寬減半啊易。

注意吁伺,此處對(duì)第一個(gè)模塊做了特別處理。

def resnet_block(in_channels, out_channels, num_residuals, first_block=False):
    if first_block:
        assert in_channels == out_channels # 第一個(gè)模塊的通道數(shù)同輸入通道數(shù)一致
    blk = []
    for i in range(num_residuals):
        if i == 0 and not first_block:
            blk.append(Residual(in_channels, out_channels, use_1x1conv=True, stride=2))
        else:
            blk.append(Residual(out_channels, out_channels))
    return nn.Sequential(*blk)

接著我們?yōu)镽esNet加入所有殘差塊租谈。這里每個(gè)模塊使用兩個(gè)殘差塊篮奄。

net.add_module("resnet_block1", resnet_block(64, 64, 2, first_block=True))
net.add_module("resnet_block2", resnet_block(64, 128, 2))
net.add_module("resnet_block3", resnet_block(128, 256, 2))
net.add_module("resnet_block4", resnet_block(256, 512, 2))

最后,與GoogLeNet一樣割去,加入平均池化層后接上全連接層輸出窟却。

net.add_module("global_avg_pool", d2l.GlobalAvgPool2d()) # GlobalAvgPool2d的輸出: (Batch, 512, 1, 1)
net.add_module("fc", nn.Sequential(d2l.FlattenLayer(), nn.Linear(512, 10))) 

1個(gè)×1卷積層),加上最開始的卷積層和最后的全連接層呻逆,共計(jì)18層夸赫。這個(gè)模型通常也被稱為ResNet-18。通過配置不同的通道數(shù)和模塊里的殘差塊數(shù)可以得到不同的ResNet模型咖城,例如更深的含152層的ResNet-152茬腿。雖然ResNet的主體架構(gòu)跟GoogLeNet的類似,但ResNet結(jié)構(gòu)更簡(jiǎn)單宜雀,修改也更方便切平。

在訓(xùn)練ResNet之前,我們來觀察一下輸入形狀在ResNet不同模塊之間的變化辐董。

X = torch.rand((1, 1, 224, 224))
for name, layer in net.named_children():
    X = layer(X)
    print(name, ' output shape:\t', X.shape)

輸出:

0  output shape:     torch.Size([1, 64, 112, 112])
1  output shape:     torch.Size([1, 64, 112, 112])
2  output shape:     torch.Size([1, 64, 112, 112])
3  output shape:     torch.Size([1, 64, 56, 56])
resnet_block1  output shape:     torch.Size([1, 64, 56, 56])
resnet_block2  output shape:     torch.Size([1, 128, 28, 28])
resnet_block3  output shape:     torch.Size([1, 256, 14, 14])
resnet_block4  output shape:     torch.Size([1, 512, 7, 7])
global_avg_pool  output shape:     torch.Size([1, 512, 1, 1])
fc  output shape:     torch.Size([1, 10])

5.11.3獲取數(shù)據(jù)和訓(xùn)練模型

下面我們?cè)贔ashion-MNIST數(shù)據(jù)集上訓(xùn)練ResNet悴品。

batch_size = 256
# 如出現(xiàn)“out of memory”的報(bào)錯(cuò)信息,可減小batch_size或resize
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)

lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)

輸出:

training on  cuda
epoch 1, loss 0.0015, train acc 0.853, test acc 0.885, time 31.0 sec
epoch 2, loss 0.0010, train acc 0.910, test acc 0.899, time 31.8 sec
epoch 3, loss 0.0008, train acc 0.926, test acc 0.911, time 31.6 sec
epoch 4, loss 0.0007, train acc 0.936, test acc 0.916, time 31.8 sec
epoch 5, loss 0.0006, train acc 0.944, test acc 0.926, time 31.5 sec

小結(jié)

  • 殘差塊通過跨層的數(shù)據(jù)通道從而能夠訓(xùn)練出有效的深度神經(jīng)網(wǎng)絡(luò)。
  • ResNet深刻影響了后來的深度神經(jīng)網(wǎng)絡(luò)的設(shè)計(jì)苔严。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末菇存,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子邦蜜,更是在濱河造成了極大的恐慌,老刑警劉巖亥至,帶你破解...
    沈念sama閱讀 211,496評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悼沈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡姐扮,警方通過查閱死者的電腦和手機(jī)絮供,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茶敏,“玉大人壤靶,你說我怎么就攤上這事【” “怎么了贮乳?”我有些...
    開封第一講書人閱讀 157,091評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)恬惯。 經(jīng)常有香客問我向拆,道長(zhǎng),這世上最難降的妖魔是什么酪耳? 我笑而不...
    開封第一講書人閱讀 56,458評(píng)論 1 283
  • 正文 為了忘掉前任浓恳,我火速辦了婚禮,結(jié)果婚禮上碗暗,老公的妹妹穿的比我還像新娘颈将。我一直安慰自己,他們只是感情好言疗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,542評(píng)論 6 385
  • 文/花漫 我一把揭開白布晴圾。 她就那樣靜靜地躺著,像睡著了一般洲守。 火紅的嫁衣襯著肌膚如雪疑务。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,802評(píng)論 1 290
  • 那天梗醇,我揣著相機(jī)與錄音知允,去河邊找鬼。 笑死叙谨,一個(gè)胖子當(dāng)著我的面吹牛温鸽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,945評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼涤垫,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼姑尺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蝠猬,我...
    開封第一講書人閱讀 37,709評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤切蟋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后榆芦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柄粹,經(jīng)...
    沈念sama閱讀 44,158評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,502評(píng)論 2 327
  • 正文 我和宋清朗相戀三年匆绣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驻右。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,637評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡崎淳,死狀恐怖堪夭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拣凹,我是刑警寧澤森爽,帶...
    沈念sama閱讀 34,300評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站嚣镜,受9級(jí)特大地震影響拗秘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜祈惶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,911評(píng)論 3 313
  • 文/蒙蒙 一雕旨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捧请,春花似錦凡涩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至可款,卻和暖如春育韩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闺鲸。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工筋讨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人摸恍。 一個(gè)月前我還...
    沈念sama閱讀 46,344評(píng)論 2 360
  • 正文 我出身青樓悉罕,卻偏偏與公主長(zhǎng)得像赤屋,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子壁袄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,500評(píng)論 2 348

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