FCN實(shí)現(xiàn)語義分割-Pytorch(二)

3.2剑勾、數(shù)據(jù)集(Dataset)

Pytorch提供了Dataset和Data Loader來幫助處理數(shù)據(jù)集庇楞。對于語義分割的訓(xùn)練數(shù)據(jù),假設(shè)我們已經(jīng)有了原始圖像及對應(yīng)的標(biāo)簽圖像。為了訓(xùn)練網(wǎng)絡(luò)模型猪落,我們需要原始圖像的Tensor數(shù)據(jù),形狀為(N畴博,C, H, W)笨忌。其中N為樣本數(shù)量,C為通道數(shù)量俱病,H和W表示圖像的像素高度和寬度官疲。同時(shí)需要保證所有訓(xùn)練圖像具有相同的C袱结,H和W。標(biāo)簽數(shù)據(jù)可以使用(N途凫,H垢夹,W)形狀的標(biāo)準(zhǔn)標(biāo)簽,或者使用one-hot encoding標(biāo)簽维费。


圖像標(biāo)簽

3.2.1果元、自定義Dataset

要在pytorch中實(shí)現(xiàn)自定義數(shù)據(jù)集,我們可以繼承pytorch的Dataset,并實(shí)現(xiàn)len(用來返回?cái)?shù)據(jù)數(shù)量)和getItem(用來返回圖像數(shù)據(jù)和標(biāo)簽數(shù)據(jù))方法犀盟。這里我們在訓(xùn)練數(shù)據(jù)集目錄下做一個(gè)npy文件而晒,用來統(tǒng)計(jì)所有訓(xùn)練數(shù)據(jù)。npy文件的形狀為(N, 2), N代表樣本數(shù)量阅畴,2代表圖像和標(biāo)簽倡怎。
我們生成1.jpg, 1.png, 直到237.jpg,237.png的237個(gè)訓(xùn)練樣本的py文件數(shù)據(jù)

data = np.array([[f'raw_data/{x}.jpg', f'groundtruth/{x}.png'] for x in range(1, 237)])
root = os.getcwd() + "/final"
np.save(f"{root}/index.npy", data)

colors數(shù)據(jù)定義標(biāo)簽圖像中贱枣,不同分類的顏色值

colors = torch.tensor([[0, 0, 0],  # background
                   [128, 0, 0], # cat
                   [0, 128, 0]] # cow
                  )

接下來诈胜,我們定義一個(gè)自定義Dataset,讀取圖片我們使用的torchvision庫冯事,并指定返回RGB格式的數(shù)據(jù)焦匈。這里有一些細(xì)節(jié)需要我們注意下:

  • pytorch輸入需要的圖片形狀是(N,C昵仅,H缓熟,W),其中N由DataLoader來生成摔笤,所有自定義Dataset需要返回(C够滑,H,W)形狀的圖像數(shù)據(jù)吕世。一些其它的庫彰触,比如matplot,在顯示圖像時(shí)命辖,需要的數(shù)據(jù)的形狀是(H况毅,W,C)尔艇,我們需要在需要時(shí)尔许,做必要的轉(zhuǎn)換。
  • 如果使用其它庫來讀取圖像數(shù)據(jù)终娃,比如opencv或者Python自身的Image庫味廊,需要注意返回?cái)?shù)據(jù)的形狀及不同維度代表的含義。比如opencv返回的圖像數(shù)據(jù)的形狀是(H,W余佛, C)柠新,且C的順序是BGR(torchvision是RGB)。在使用數(shù)據(jù)之前辉巡,可能需要我們對圖像數(shù)據(jù)做對應(yīng)的轉(zhuǎn)換恨憎。RGB或者BGR并不影響模型計(jì)算,但需要和colors定義的順序保持一致红氯。
  • 不同圖像處理庫框咙,返回?cái)?shù)據(jù)的類型是不同的咕痛。比如opencv返回的是numpy array. 而torchvision返回的數(shù)據(jù)是Tensor痢甘。
  • 圖像數(shù)據(jù)和標(biāo)簽數(shù)據(jù),數(shù)據(jù)類型是有要求的茉贡,這點(diǎn)我們在“訓(xùn)練”章節(jié)或詳細(xì)討論
class SSDataset(Dataset):
    def __init__(self, root: str, colors: Tensor, transform=None):
        self.root = root
        self.colors = colors
        self.transform = transform
        self.data_list = np.load(f'{root}/index.npy')

    def __len__(self):
        return len(self.data_list)

    def __getitem__(self, index) -> T_co:
        names = self.data_list[index]
        image_file_path = f'{self.root}/{names[0]}'
        mask_file_path = f'{self.root}/{names[1]}'
        image = torchvision.io.read_image(image_file_path, ImageReadMode.RGB)
        mask = torchvision.io.read_image(mask_file_path, ImageReadMode.RGB)
        if self.transform is not None:
            image = self.transform(image)
            mask = self.transform(mask)
        mask = OneHotEncoder.encode_mask_image(mask, self.colors)
        return image, mask

3.2.2塞栅、one-hot編碼

直接讀取的標(biāo)簽圖像數(shù)據(jù),是無法用于損失計(jì)算的腔丧。我們需要對它進(jìn)行編碼放椰,編碼的結(jié)果可以是典型的(H,W)形狀的標(biāo)簽數(shù)據(jù)愉粤,或者采用one-hot編碼砾医。

one-hot編碼能夠把分類數(shù)據(jù)(cow, cat and etc.)編碼成計(jì)算機(jī)可識別的二值數(shù)據(jù), 它的最大優(yōu)勢是衣厘,編碼后的分類數(shù)據(jù)是公平的如蚜,有利于計(jì)算機(jī)對離散分類數(shù)據(jù)的處理工作。對于語義分割影暴,我們采用one-hot編碼, 好處是如果我們需要對分割后的對象做對象檢測错邦,實(shí)例邊框繪制等操作,one-hot編碼的數(shù)據(jù)更加容易實(shí)現(xiàn)型宙。
我們的one-hot編碼實(shí)現(xiàn)了對圖像的tensor數(shù)據(jù)和score進(jìn)行one-hot編碼的功能.對score進(jìn)行one-hot編碼的功能將用于模型訓(xùn)練和驗(yàn)證撬呢。

class OneHotEncoder():
    @staticmethod
    def encode_mask_image(mask_image: Tensor, colors: Tensor) -> Tensor:
        height, width = mask_image.shape[1:]
        one_hot_mask = torch.zeros([len(colors), height, width], dtype=torch.float)
        for label_index, label in enumerate(colors):
            one_hot_mask[label_index, :, :] = torch.all(mask_image == label[:, None, None], dim=0).float()
        return one_hot_mask

@staticmethod
    def encode_score(score: Tensor) -> Tensor:
        num_classes = score.shape[1]
        label = torch.argmax(score, dim=1)
        pred = F.one_hot(label, num_classes=num_classes)
        return pred.permute(0, 3, 1, 2)

3.2.3、數(shù)據(jù)預(yù)處理-數(shù)據(jù)變換(Transform)

正態(tài)分布

原始的圖像數(shù)據(jù)妆兑,并不總是能符合數(shù)據(jù)處理需求的標(biāo)準(zhǔn)魂拦,我們使用的不同的人工智能框架,也對應(yīng)了不同類型和要求的輸入數(shù)據(jù)格式搁嗓, 所以我們需要對數(shù)據(jù)進(jìn)行預(yù)處理晨另。數(shù)據(jù)預(yù)處理是一個(gè)功能龐大的概念,在本文中谱姓,我們主要使用數(shù)據(jù)預(yù)處理中的數(shù)據(jù)變換功能借尿,處理時(shí)機(jī)被分為兩個(gè)階段:圖像加載階段和數(shù)據(jù)處理階段。

在圖像加載階段,我們面臨了讀取的圖片的大小路翻,長寬比例狈癞,圖片格式不同,以及不同的圖像處理庫帶來的數(shù)據(jù)差異等問題茂契。對應(yīng)的需要做縮放蝶桶,裁減,格式轉(zhuǎn)換掉冶,維度變換等一些列預(yù)處理操作真竖。在上面數(shù)據(jù)集的代碼中,我們預(yù)留了轉(zhuǎn)換參數(shù) transform厌小, 可以根據(jù)需要插入多項(xiàng)預(yù)處理程序恢共。

torchvision.transforms包中已經(jīng)包含了大量預(yù)置的預(yù)處理程序,我們也可以定制自己的預(yù)處理程序. 如果使用的圖片處理庫時(shí)PIL或者opencv, torchvision的tranform包璧亚,提供了對PIL image或ndarray轉(zhuǎn)換成Tensor的ToTensor轉(zhuǎn)換讨韭,方便我們應(yīng)對不同類型的數(shù)據(jù)。這里我們以圖片縮放和長寬比統(tǒng)一為例癣蟋,加入Resize和CenterCrop兩個(gè)預(yù)處理程序透硝。

transform = T.Compose([
            T.Resize(224),
            T.CenterCrop(224)
        ])
ss_dataset  = SSDataset(root, colors, transform)

經(jīng)過上述預(yù)處理程序,dataset返回的數(shù)據(jù)都將被裁減成224*224大小的數(shù)據(jù)疯搅。

在數(shù)據(jù)處理階段濒生,我們需要對數(shù)據(jù)進(jìn)行歸一化和正態(tài)分布,以便提升收斂速度幔欧。torchvison.transorm,提供了正態(tài)分布的預(yù)處理程序Normalize罪治,其中需要均方差(mean)和標(biāo)準(zhǔn)差(std)兩個(gè)參數(shù)。我們有兩種方式來使用這個(gè)處理程序:

  • 先進(jìn)行歸一化處理琐馆,再使用其它程序標(biāo)準(zhǔn)的經(jīng)驗(yàn)方差和標(biāo)準(zhǔn)差來進(jìn)行正態(tài)分布計(jì)算
  • 對訓(xùn)練數(shù)據(jù)計(jì)算其均方差和標(biāo)準(zhǔn)差规阀,再使用它們進(jìn)行正態(tài)分布計(jì)算

這里我們使用第一種方式,歸一化和正態(tài)分布獨(dú)立成兩個(gè)過程瘦麸。Torchvisontion提供的其它格式圖像數(shù)據(jù)轉(zhuǎn)換成Tensor的ToTensor方法谁撼,內(nèi)部做了歸一化處理,我們當(dāng)然可以先把我們的Tensor轉(zhuǎn)換成(H滋饲,W厉碟,C)格式的ndarray再使用這個(gè)處理程序進(jìn)行歸一化, 但這樣的操作顯然不太優(yōu)雅屠缭。 這里我們直接使用顏色的最大值255作為除數(shù)箍鼓,直接對Tensor數(shù)據(jù)進(jìn)行歸一化處理。均方差和標(biāo)準(zhǔn)差我們使用imagenet提供的數(shù)值:

transform = T.Compose([
    T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
…
input = transform(input/255.)

數(shù)據(jù)處理階段的預(yù)處理程序呵曹,我們選擇提到dataset外面款咖,在訓(xùn)練之前使用何暮。那么在測試,驗(yàn)證和預(yù)測之前铐殃,需要注意必須要使用同樣的數(shù)據(jù)預(yù)處理程序來對數(shù)據(jù)做預(yù)處理海洼。

3.2.4、數(shù)據(jù)加載器(Data Loader)

交叉驗(yàn)證

pytorch 使用數(shù)據(jù)加載器來(DataLoader)來間接使用數(shù)據(jù)集富腊。通常完整的數(shù)據(jù)集會(huì)被份組成訓(xùn)練集坏逢,驗(yàn)證集和測試集三部分,為了簡化赘被,我們把數(shù)據(jù)集中的數(shù)據(jù)按0.8是整, 0.2的比例,拆分成訓(xùn)練集和驗(yàn)證集二部分民假。

data_len = len(ss_dataset)
indices = list(range(data_len))
split = int(data_len * 0.8)
train_indices, val_indices = indices[:split], indices[split:]
train_data = Subset(ss_dataset, train_indices)
val_data = Subset(ss_dataset, val_indices)

BATCH_SIZE = 4
train_loader = DataLoader(
    train_data,
    BATCH_SIZE,
    shuffle=True,
    drop_last=True,
    num_workers=4,
    pin_memory=True,
)
val_loader = DataLoader(
    val_data,
    1,
    shuffle=False,
    drop_last=True,
    num_workers=2,
    pin_memory=False,
)

3.2.5浮入、交叉驗(yàn)證(Cross Validation)

Cross Validation

拆分了訓(xùn)練集和驗(yàn)證集的數(shù)據(jù)集,使得用于訓(xùn)練和驗(yàn)證的數(shù)據(jù)規(guī)模變得更小阳欲,不利于模型訓(xùn)練舵盈。交叉驗(yàn)證把樣本數(shù)據(jù)分成k份, 稱為k fold陋率,選擇其中一份用作驗(yàn)證球化,其它用作訓(xùn)練。之后再選擇另一份用作驗(yàn)證瓦糟,如此循環(huán)指定的次數(shù)或者全部fold都被作為驗(yàn)證集訓(xùn)練過筒愚。
交叉驗(yàn)證通常在樣本數(shù)量較少時(shí)有用,當(dāng)有大量樣本時(shí)菩浙,通常不做交叉驗(yàn)證巢掺。

3.3、訓(xùn)練(Train)

至此劲蜻,我們已經(jīng)準(zhǔn)備好了網(wǎng)絡(luò)模型陆淀,數(shù)據(jù)集,并且使用數(shù)據(jù)加載器對數(shù)據(jù)集進(jìn)行了拆分先嬉。接下來轧苫,我們使用數(shù)據(jù)集對網(wǎng)絡(luò)模型進(jìn)行訓(xùn)練,用以獲取理想的權(quán)重值。
為了訓(xùn)練網(wǎng)絡(luò)模型疫蔓,我們需要關(guān)注已下幾個(gè)方面:學(xué)習(xí)準(zhǔn)則含懊,優(yōu)化和指標(biāo)

3.3.1、學(xué)習(xí)準(zhǔn)則(Criterion)

學(xué)習(xí)準(zhǔn)則是模型如何學(xué)習(xí)的評估標(biāo)準(zhǔn)衅胀。對于語義分割網(wǎng)絡(luò)岔乔,我們的學(xué)習(xí)準(zhǔn)則需要能夠?qū)Χ鄠€(gè)分類進(jìn)行損失評估,常用的損失函數(shù)有Softmax, SVM , CrossEntropy滚躯。這里我們選用CrossEntropyLoss作為學(xué)習(xí)準(zhǔn)則, 并在前向計(jì)算后通過學(xué)習(xí)準(zhǔn)則計(jì)算損失雏门。

Pytorch提供了CrossEntropyLoss的實(shí)現(xiàn)嘿歌,是Softmax和CrossEntropy的組合,其內(nèi)部實(shí)現(xiàn)首先使用Softmax把打分轉(zhuǎn)換成不同分類的概率茁影,再使用log函數(shù)把概率轉(zhuǎn)換成熵值搅幅,最后使用NLLLoss計(jì)算損失值。

criterion = nn.CrossEntropyLoss()
…
# forward
score = model(input)
loss = criterion(score, target)
train_loss += loss.item()

3.3.2呼胚、優(yōu)化(Optimizer)

優(yōu)化是通過學(xué)習(xí)準(zhǔn)則茄唐,優(yōu)化模型參數(shù),使得學(xué)習(xí)準(zhǔn)則中的損失可以隨著學(xué)習(xí)達(dá)到降低的效果蝇更。常用的優(yōu)化函數(shù)有SGD沪编,Adam。

這里我們選用SGD作為優(yōu)化函數(shù)年扩,配合動(dòng)量來避免局部最優(yōu)解, 并在反向傳播時(shí)優(yōu)化模型權(quán)重值蚁廓。SGD通過backward來計(jì)算梯度,并調(diào)整權(quán)重值厨幻。對于權(quán)重參數(shù)相嵌,pytorch要求參數(shù)必須是requires_grad=True且is_leaf=False才會(huì)計(jì)算梯度。在權(quán)重初始化章節(jié)况脆,我們的初始化方式饭宾,默認(rèn)已經(jīng)正確設(shè)置了這兩個(gè)屬性。另外格了,在模型訓(xùn)練上看铆,我們通常先使用一個(gè)較大的學(xué)習(xí)率,用以加快收斂速度盛末,之后使用較小的學(xué)習(xí)率弹惦,在小范圍內(nèi)尋找最優(yōu)權(quán)重值。為此我們使用了lr_scheduler悄但,用以在訓(xùn)練過程中動(dòng)態(tài)降低學(xué)習(xí)率棠隐。

optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, momentum=momentum)
scheduler = lr_scheduler.StepLR(optimizer, step_size=Trainer.STEP_SIZE, gamma=0.5)
…
# backward
optimizer.zero_grad()
loss.backward()
optimizer.step()
…
scheduler.step()

3.3.3、指標(biāo)(Metrics)

在模型訓(xùn)練檐嚣,驗(yàn)證和測試階段助泽,我們需要獲取一些指標(biāo)參數(shù),用來反饋結(jié)果是否理想净嘀。常見的指標(biāo)有Loss报咳,Precision,Recall, F Measure, Pixel Accuracy, Mean Accuracy, Mean IU挖藏,F(xiàn)requency Weighted IU暑刃, Jaccard Similarity等。

# metrics
pred = OneHotEncoder.encode_score(score)
cm = Trainer.confusion_matrix(target, pred)
acc = torch.diag(cm).sum().item() / torch.sum(cm).item()
train_acc += acc
iu = torch.diag(cm) / (cm.sum(dim=1) + cm.sum(dim=0) - torch.diag(cm))
mean_iu += torch.nanmean(iu).item()

if verbose and iteration % iterations_per_epoch == 0:
    print(f'epoch {epoch + 1} / {epochs}: loss: {train_loss/iterations_per_epoch:.5f}, accuracy:{train_acc/iterations_per_epoch:.5f}, mean IU:{mean_iu/iterations_per_epoch:.5f}')
    train_loss = 0.0
    train_acc = 0.0
    mean_iu = 0.0

3.3.3膜眠、混淆矩陣(Confusion Matrix)

混淆矩陣

Confusion Matrix可以輔助計(jì)算諸如Precision, Recall, F Measure等多個(gè)指標(biāo)值岩臣。這里我們使用Confusion Matrix來計(jì)算Pixel Accuracy和Mean IU溜嗜。

@staticmethod
def confusion_matrix(target: Tensor, input: Tensor) -> Tensor:
    if target.dim() != input.dim():
        raise IOError('target and input must has same dimension')
    if 4 == target.dim():
        y_true = torch.flatten(target.permute(1, 0, 2, 3), 1, 3).int()
        y_pred = torch.flatten(input.permute(1, 0, 2, 3), 1, 3).int()
    elif 3 == target.dim():
        y_true = torch.flatten(target, 1, 2).int()
        y_pred = torch.flatten(input, 1, 2).int()
    else:
        raise IOError('target and input must be a 3D or 4D matrix')
    
    n_classes = y_true.shape[0]
    cm = torch.zeros((n_classes, n_classes))
    for i in range(n_classes):
        for j in range(n_classes):
            num = torch.sum((y_true[i] & y_pred[j]).int())
            cm[i, j] += num
    return cm

現(xiàn)在,數(shù)據(jù)讀取架谎,學(xué)習(xí)準(zhǔn)則炸宵,優(yōu)化和指標(biāo)結(jié)合起來的訓(xùn)練代碼是這樣子的:

class Trainer(object):
    def __init__(self, model: torch.nn.Module, transform, train_loader: DataLoader, val_loader: DataLoader, class_names, class_colors):
        self.model = model
        self.transform = transform
        self.visualizer = Visualizer(class_names, class_colors)
        self.acc_thresholds = 0.95
        self.best_mean_iu = 0
        self.train_loader = train_loader
        self.val_loader = val_loader

    def train(self, epochs=50, learning_rate=1e-3, momentum=0.7, step_size=2, verbose=True):
        start_time = datetime.now()
        print(f'start training at {start_time}')
        iterations_per_epoch = 20
        device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        optimizer = torch.optim.SGD(self.model.parameters(), lr=learning_rate, momentum=momentum)
        scheduler = lr_scheduler.StepLR(optimizer, step_size=step_size, gamma=0.5)
        criterion = nn.CrossEntropyLoss()

        train_loss = 0.0
        train_acc = 0.0
        train_iu = 0.0
                data_len = len(self.train_loader)
        self.model.to(device)
        self.model.train()
        for epoch in range(epochs):
            lr_current = optimizer.param_groups[0]['lr']
            print(f'learning rate:{lr_current}')
            for batch_index, data in enumerate(self.train_loader):
                iteration = batch_index + epoch * data_len+ 1
                input = Variable(self.transform(data[0].float()/255).to(device))
                target = data[1].float().to(device)

                # forward
                score = self.model(input)
                loss = criterion(score, target)
                train_loss += loss.item()

                # backward
                optimizer.zero_grad()
                loss.backward()
                optimizer.step()

                # metrics
                pred = OneHotEncoder.encode_score(score)
                cm = Trainer.confusion_matrix(target, pred)
                acc = torch.diag(cm).sum().item() / torch.sum(cm).item()
                train_acc += acc
                iu = torch.diag(cm) / (cm.sum(dim=1) + cm.sum(dim=0) - torch.diag(cm))
                train_iu += torch.nanmean(iu).item()

                if verbose and iteration % iterations_per_epoch == 0:
                                        mean_acc = train_acc/iterations_per_epoch
                                        mean_iu = train_iu/iterations_per_epoch
                    print(f'epoch {epoch + 1} / {epochs}: loss: {train_loss/iterations_per_epoch:.5f}, accuracy:{mean_acc:.5f}, mean IU:{mean_iu:.5f}')
                    train_loss = 0.0
                    train_acc = 0.0
                    train_iu = 0.0

            scheduler.step()
        end_time = datetime.now()
        print(f'end training at {end_time}')
    
    @staticmethod
    def confusion_matrix(target: Tensor, input: Tensor) -> Tensor
        if target.dim() != input.dim():
            raise IOError('target and input must has same dimension')
        if 4 == target.dim():
            y_true = torch.flatten(target.permute(1, 0, 2, 3), 1, 3).int()
            y_pred = torch.flatten(input.permute(1, 0, 2, 3), 1, 3).int()
        elif 3 == target.dim():
            y_true = torch.flatten(target, 1, 2).int()
            y_pred = torch.flatten(input, 1, 2).int()
        else:
            raise IOError('target and input must be a 3D or 4D matrix')

        n_classes = y_true.shape[0]
        cm = torch.zeros((n_classes, n_classes))
        for i in range(n_classes):
            for j in range(n_classes):
                num = torch.sum((y_true[i] & y_pred[j]).int())
                cm[i, j] += num
        return cm
    …
}

這里需要注意的地方是從DataLoader取到的數(shù)據(jù),除了預(yù)處理程序谷扣,我們還對輸入數(shù)據(jù)和標(biāo)簽數(shù)據(jù)做了類型轉(zhuǎn)換土全,這是因?yàn)槲覀兪褂玫木W(wǎng)絡(luò)模型,優(yōu)化器会涎,對輸入數(shù)據(jù)是有類型要求的裹匙。這里我們使用的網(wǎng)絡(luò)模型和損失函數(shù)需要輸入是float類型的數(shù)據(jù),所以通過DataLoader獲取的數(shù)據(jù)末秃,我們都進(jìn)行了類型轉(zhuǎn)換概页。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市练慕,隨后出現(xiàn)的幾起案子惰匙,更是在濱河造成了極大的恐慌,老刑警劉巖铃将,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件项鬼,死亡現(xiàn)場離奇詭異,居然都是意外死亡麸塞,警方通過查閱死者的電腦和手機(jī)秃臣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門涧衙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哪工,“玉大人,你說我怎么就攤上這事弧哎⊙惚龋” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵撤嫩,是天一觀的道長偎捎。 經(jīng)常有香客問我,道長序攘,這世上最難降的妖魔是什么茴她? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮程奠,結(jié)果婚禮上丈牢,老公的妹妹穿的比我還像新娘。我一直安慰自己瞄沙,他們只是感情好己沛,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布慌核。 她就那樣靜靜地躺著,像睡著了一般申尼。 火紅的嫁衣襯著肌膚如雪垮卓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天师幕,我揣著相機(jī)與錄音粟按,去河邊找鬼。 笑死霹粥,一個(gè)胖子當(dāng)著我的面吹牛钾怔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蒙挑,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼宗侦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了忆蚀?” 一聲冷哼從身側(cè)響起矾利,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎馋袜,沒想到半個(gè)月后男旗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡欣鳖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年察皇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泽台。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡什荣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怀酷,到底是詐尸還是另有隱情稻爬,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布蜕依,位于F島的核電站桅锄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏样眠。R本人自食惡果不足惜友瘤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望檐束。 院中可真熱鬧辫秧,春花似錦、人聲如沸厢塘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至抓半,卻和暖如春喂急,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背笛求。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工廊移, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人探入。 一個(gè)月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓狡孔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蜂嗽。 傳聞我的和親對象是個(gè)殘疾皇子苗膝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359

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