TORCH02-03:Torch的损失函数与逻辑回归实现

本主题主要梳理损失函数,并同时使用损失函数实现逻辑回归。本主题内容结构:
  1. 逻辑回归模型;
  2. 逻辑回归Torch实现;
  3. 损失函数介绍;


逻辑回归模型

  • 逻辑回归与线性回归是有区别的,只要用来解决分类的问题,逻辑回归把从二分类基本模型分析,通过概率的方式来区分两个类:
    • 属于A的概率,属于B的概率,哪个可能概率大就属于哪一类。

决策模型

  • 逻辑回归的决策模型,使用的还是是线性模型:

    • y = xW +b
  • 为了从形式上接近概率,采用了一个概率密度函数(逻辑分布密度函数)

    • sigmoid(x) = \dfrac{1}{1 + e^{-x}}
  • 从而决策模型为:

    • y = sigmoid(xW +b) = \dfrac{1}{1 + e^{-(xW + b)}}

损失模型

  • 逻辑回归中,损失函数是:
    • Loss =\sum\limits_{i \in \text{数据集}}y_i\ ln(h(x_i)) +(1-y_i)\ln(1-h(x_i))
      • 上述公式被称为交叉熵(Cross Entropy)
      • 其中h(x_i)=sigmoid(x_iW + b)= \dfrac{1}{1+e^{-(x_iW + b)}}

逻辑回归PyTorch实现

  • 采用PyTorch实现,我们还是使用梯度下降方法实现。

  • 首先认识下损失函数的表示。

    • 损失函数的表达与决策函数有关。
  • 数据集:

    • 采用鸢尾花数据集

决策函数的表示

  • 线性函数在torch中已经定义:linear函数;
  • sigmoid函数在torch中已经定义:sigmoid函数;
import sklearn
import sklearn.datasets
import torch

data, target = sklearn.datasets.load_iris(return_X_y=True)

x = torch.Tensor(data[0:100])   # 取前面100个数据样本 
y = torch.Tensor(target[0:100]).view(100,1) # 形状与x线性运算后的形状一样

w = torch.randn(1, 4)    # 注意形状(linear会自动转置) 
b = torch.randn(1)        # w,b是可训练的,就是需要求导或者梯度

y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
print(y_.shape)
sy_ = torch.sigmoid(y_)
print(sy_.shape, y.shape)
torch.Size([100, 1])
torch.Size([100, 1]) torch.Size([100, 1])

损失函数的表示

  • 逻辑回归使用的损失函数是交叉熵,在Torch中也封装了一个函数:
    • 其他损失函数后面专门用一节作为主题介绍。

binary_cross_entropy函数

  • 对数损失函数,没有做逻辑分布函数(sigmoid)运算
    torch.nn.functional.binary_cross_entropy(input, target, weight=None, size_average=None, reduce=None, reduction='mean')
  • 参数说明:
    • input:就是计算出来的y_
    • target:就是原来数据集中标签y
    • reduction: 损失的计算方式:
      • 均值
      • 求和

binary_cross_entropy_with_logits函数

  • 自动做逻辑分布函数(sigmoid函数)运算。
    torch.nn.functional.binary_cross_entropy_with_logits(input, target, weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None)

损失函数的例子

import sklearn
import sklearn.datasets
import torch

data, target = sklearn.datasets.load_iris(return_X_y=True)

x = torch.Tensor(data[0:100])   # 取前面100个数据样本 
y = torch.Tensor(target[0:100]).view(100, 1) # 形状与x线性运算后的形状一样

w = torch.randn(1, 4)    # 注意形状(linear会自动转置) 
b = torch.randn(1)        # w,b是可训练的,就是需要求导或者梯度

y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = torch.sigmoid(y_)

loss_mean = torch.nn.functional.binary_cross_entropy_with_logits(y_, y, reduction="mean")    # 多一个运算,除以样本个数
print(loss_mean)
loss_sum = torch.nn.functional.binary_cross_entropy_with_logits(y_, y, reduction="sum")
print(loss_sum)

# 自己手工做sigmoid运算
loss_mean = torch.nn.functional.binary_cross_entropy(sy_, y, reduction="mean")    # 多一个运算,除以样本个数
print(loss_mean)
loss_sum = torch.nn.functional.binary_cross_entropy(sy_, y, reduction="sum")
print(loss_sum)
tensor(0.3298)
tensor(32.9785)
tensor(0.3298)
tensor(32.9785)

逻辑回归分类实现

  • 下面的实现方法,没有使用随机梯度,实现的核心关键是:
    1. 迭代梯度计算
    2. 数据预测分类
import sklearn
import sklearn.datasets
import torch

data, target = sklearn.datasets.load_iris(return_X_y=True)

x = torch.Tensor(data[50:150])   # 取前面100个数据样本 (前面50与后面的两个50是可分的,后面两个50线性可分性差点)
y = torch.Tensor(target[0:100]).float().view(100, 1) # 形状与x线性运算后的形状一样

w = torch.randn(1, 4)    
b = torch.randn(1)        

w.requires_grad = True   # 可训练(x,y是不需要训练的)
b.requires_grad = True

epoch = 10000
learn_rate = 0.01

for n in range(epoch):
    # forward:决策模型表示
    y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)   # 计算线性输出
    sy_ = torch.sigmoid(y_)    # 计算逻辑分布运算(输出的值可以作为概率使用)
    # loss:损失函数表示
    loss_mean = torch.nn.functional.binary_cross_entropy(sy_, y, reduction="mean")
    # backward:计算梯度
    loss_mean.backward()
    
    # 更新梯度
    with torch.autograd.no_grad():   # 关闭梯度计算跟踪
        w -= learn_rate * w.grad     # 更新权重梯度
        w.grad.zero_()     # 清空本次计算的梯度(因为梯度是累加计算,不清空就累加)
        b -= learn_rate * b.grad     # 更新偏置项梯度
        b.grad.zero_()   
        # 观察训练过程中的损失下降,与训练集预测的准确性
        if n % 1000 == 0:
            print(F"误差损失值:{loss_mean:10.6f},", end="")
            sy_[sy_ > 0.5] = 1
            sy_[sy_ <= 0.5] = 0

            correct_rate = (sy_ ==  y).float().mean()     # 逻辑值在Torch不给计算平均值,所以需要转换为float类型
            print(F"\t准确率为:{correct_rate*100: 8.2f}%")
    
print("训练完毕!")   # 下面输出的结果与sklearn与tensorflow训练的结果一致
误差损失值:  8.051266,   准确率为:   50.00%
误差损失值:  0.370522,   准确率为:   95.00%
误差损失值:  0.295042,   准确率为:   95.00%
误差损失值:  0.252245,   准确率为:   95.00%
误差损失值:  0.224739,   准确率为:   96.00%
误差损失值:  0.205526,   准确率为:   96.00%
误差损失值:  0.191302,   准确率为:   96.00%
误差损失值:  0.180313,   准确率为:   97.00%
误差损失值:  0.171543,   准确率为:   97.00%
误差损失值:  0.164363,   准确率为:   97.00%
训练完毕!

损失函数

  • torch提供的损失函数有:

    1. binary_cross_entropy
    2. binary_cross_entropy_with_logits
    3. poisson_nll_loss
    4. cosine_embedding_loss
    5. cross_entropy
    6. ctc_loss
    7. hinge_embedding_loss
    8. kl_div
    9. l1_loss
    10. mse_loss
    11. margin_ranking_loss
    12. multilabel_margin_loss
    13. multilabel_soft_margin_loss
    14. multi_margin_loss
    15. nll_loss
    16. smooth_l1_loss
    17. soft_margin_loss
    18. triplet_margin_loss
  • 这些函数都有标准的数学公式,每个函数的使用方式都一样,下面直接列出公式:

binary_cross_entropy与binary_cross_entropy_with_logits函数

  • 这两个函数的本质是一样的,区别在于带后缀的with_logits函数对input数据多一个sigmoid操作。
    • sigmoid函数也称logits函数。
    • 主要用于二分类,比如典型的逻辑回归,例子见上面;

poisson_nll_loss函数

  • 泊松负对数似然损失(Poisson negative log likelihood loss)
  • 泊松分布的函数为:

    • P(x=k)= \dfrac{\lambda ^ {k}}{k!} e ^{- \lambda}
      • \lambda表示单位时间内随机事件发生的次数;
      • 泊松分布的期望与方差都为\lambda
      • k!k的阶乘;
  • 泊松分布与二项分布的关系:

    • 泊松分布是由二项分布推导而来,当二项分布的n很大,p很小的时候,泊松分布可以作为二项分布的近似,这时\lambda=np
  • 函数定义:

    torch.nn.functional.poisson_nll_loss(input, target, log_input=True, full=False, size_average=None, eps=1e-08, reduce=None, reduction='mean')
  • 参数:

    • log_input:逻辑值,用来设置是否对输入做exp指数运算:
      • False:exp^{input} - target * input
      • True:input - target * log(input + eps) :其中eps是无穷小量,用来防止input为0的情况。
    • full:是否添加Stirling近似项
      • target * log(target) - target + 0.5 * log(2 * \pi * target)
  • 损失函数计算公式:

    • target数据服从泊松分布;
    • loss = \sum \limits _{i} ( e^{\bar{y_i}} - y_i * \bar{y_i})
  • 例子代码

import sklearn
import sklearn.datasets
import torch

data, target = sklearn.datasets.load_iris(return_X_y=True)

x = torch.Tensor(data[0:100])   # 取前面100个数据样本 
y = torch.Tensor(target[0:100]).view(100, 1) # 形状与x线性运算后的形状一样

w = torch.randn(1, 4)    # 注意形状(linear会自动转置) 
b = torch.randn(1)        # w,b是可训练的,就是需要求导或者梯度

y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = torch.sigmoid(y_)

loss = torch.nn.functional.poisson_nll_loss(sy_, y)   # 默认均值:比较常采用
print(loss)

# 手工计算的效果(log_input = True)
loss_manual = sy_.exp() - y * sy_
loss_manual = loss_manual.mean()
print(loss_manual)
tensor(1.0067)
tensor(1.0067)

cosine_embedding_loss函数

  • 用来度量两个向量是否相似。主要用于半监督学习与学习非线性嵌入。

  • 函数定义:

    torch.nn.functional.cosine_embedding_loss(
        input1,     # 向量1
        input2,     # 向量2
        target,     # 标签
        margin=0, size_average=None, reduce=None, reduction='mean') → Tensor
  • 计算公式:
    • loss(x,y) = \begin{cases} 1-cos(x_1, x_2)& 如 y =1\\ max(0, cos(x_1, x_2)-margin )&如y=-1 \\ \end{cases}
      • 其中:cos(x_1, x_2)是向量x_1,x_2夹角的余弦。
      • margin的取值范围[-1, 1]

nll_loss与cross_entropy函数

  • 负对数似然损失函数。用来做C个类别的分类损失函数。

  • 实际上这两个函数本质是一样的。

    • cross_entropy多做了log_softmax运算
  • 函数定义:

    torch.nn.functional.nll_loss(input, target, weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
  • 参数说明:

    • weight是一个1-D的张量(Tensor),张量的长度与类别数相同,用来加权每个分类的类别。
    • input:是2-D数据(N,C):N表示数量,C表示分类类别;
    • target:是1-D数据,长度为N即可。
  • 提示:

    • 因为是多分类问题,所以对于输出的结果应该是one-hot,比如2的one-hot就是[0,0,1,0,0,0],假设最大标签是5。
    • 所以target必须是LongTensor类型的张量。
  • 例子代码

import sklearn
import sklearn.datasets
import torch

data, target = sklearn.datasets.load_iris(return_X_y=True)

x = torch.Tensor(data)   #  (150,4)
y = torch.Tensor(target).long()   # (150)

w = torch.randn(3, 4)    # 注意形状(linear会自动转置) :3表示类别数据(输出的长度)
b = torch.randn(3)        # w,b是可训练的,就是需要求导或者梯度

y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = y_.log_softmax(dim=1)
# print(sy_.shape)

loss = torch.nn.functional.nll_loss(y_, y)   # 默认均值:比较常采用
print(loss)
loss = torch.nn.functional.nll_loss(sy_, y)   #
print(loss)

# cross_entropy交叉熵函数(多运算了一个sigmoid运算)
loss_mamual = torch.nn.functional.cross_entropy(y_, y)     # nll_loss就是cross_entrop编码,自动采用softmax的one-hotb
print(loss_mamual)

tensor(-9.1227)
tensor(7.1684)
tensor(7.1684)

cross_entropy的补充

  • cross_entropy函数的计算公式是:

    • loss(x, target) = -log( \dfrac{e ^ {x[target]} }{ \sum \limits _i e ^ {x[i]}} ) = -x[target] + log(\sum \limits _i e ^{x[i]})
  • 下面就是cross_entropy的手工实现函数:

    • 只使用了一个样本测试,类别是5个类别。
import torch
import math


data_input = torch.FloatTensor([[1.0, 2.0, 3.0, 4.0, 5.0]])  # 计算的y,

data_target = torch.LongTensor([2])  # 改下标不能超过上面的维数-1,这是损失函数的计算过程决定的

loss_out = torch.nn.functional.cross_entropy(data_input,  data_target)
print("输入的数据集:", data_input)
print("输出的数据集:", data_target)
print("cross_entropy函数输出的结果:", loss_out)

# 下面是交叉熵函数的手工计算过程

result_1 = 0.0

# 计算第一部分:−𝑥[𝑡𝑎𝑟𝑔𝑒𝑡]
for row in range(data_input.size()[0]):    # 行循环(表示样本与对应的标签),这里其实是1
    result_1 -= data_input[row][data_target[row]]

result_2 = 0.0

# 计算第二部分:∑𝑒𝑥[𝑖]
for row in range(data_input.size()[0]):    # 行循环(表示样本与对应的标签),这里其实是1
    for col in range(data_input.size()[1]):
        result_2 += math.exp(data_input[row][col])

# 最终的结果
print("手工计算结果:", result_1 + math.log(result_2))   #


输入的数据集: tensor([[1., 2., 3., 4., 5.]])
输出的数据集: tensor([2])
cross_entropy函数输出的结果: tensor(2.4519)
手工计算结果: tensor(2.4519)

nll_loss函数的补充

  • nll_loss函数名字叫负对数似然函数,实际上根本没有做任何对数运算,其计算公式如下:

    • loss(x, target) = x[target]

    • 直接把x当成对数概率,并最终取x[target]作为这个类别的损失,最后的损失就是所有样本的损失。

  • 注意:

    • 一般nll_loss会与log_softmax一起使用,本质也就等于cross_entropy损失函数。
import sklearn
import sklearn.datasets
import torch

data, target = sklearn.datasets.load_iris(return_X_y=True)

x = torch.Tensor(data)   #  (150,4)
y = torch.Tensor(target).long()   # (150)

w = torch.randn(3, 4)    # 注意形状(linear会自动转置) :3表示类别数据(输出的长度)
b = torch.randn(3)        # w,b是可训练的,就是需要求导或者梯度

y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
# sy _ = y_.log_softmax(dim=1)
sy_ = y_ 
loss = torch.nn.functional.nll_loss(sy_, y)   
print(loss)

# 手工计算
y_one = torch.nn.functional.one_hot(y).float()    # 做了个单热编码,方便矩阵运算,否则就要取下标。
re = sy_ *  y_one
# re = re.log()
re = -re
re =re.sum(dim=1)
print(re.mean())
tensor(0.0533)
tensor(0.0533)

mse_loss损失函数

  • 最直观的损失函数:均方差损失,计算公式如下:

    • loss(x, target) = \dfrac{1}{N} \sum \limits _{i \in N} (x_i - target_i) ^ 2
  • 函数说明:

    torch.nn.functional.mse_loss(input, target, size_average=None, reduce=None, reduction='mean') → Tensor
  • 例子代码
import sklearn
import sklearn.datasets
import torch

data, target = sklearn.datasets.load_iris(return_X_y=True)

x = torch.Tensor(data)   #  (150,4)
y = torch.Tensor(target).view(150,1)   # (150)

w = torch.randn(1, 4)    # 注意形状(linear会自动转置) :3表示类别数据(输出的长度)
b = torch.randn(1)        # w,b是可训练的,就是需要求导或者梯度

y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)

loss = torch.nn.functional.mse_loss(y_, y)   
print(loss)

# 手工计算
loss_manual = ((y -y_) ** 2).mean()
print(loss_manual)
tensor(14.2841)
tensor(14.2841)

l1_loss函数

  • 这个函数从字面上理解,应该是L1范数度量的距离误差,与均方差损失函数属于同一性质的损失函数。函数公式为:

    • loss(x, target) = \dfrac{1}{N} \sum \limits _{i \in N} | x_i - target_i |
  • 函数的定义

    torch.nn.functional.l1_loss(input, target, size_average=None, reduce=None, reduction='mean') → Tensor
  • 使用例子
import sklearn
import sklearn.datasets
import torch

data, target = sklearn.datasets.load_iris(return_X_y=True)

x = torch.Tensor(data)   #  (150,4)
y = torch.Tensor(target).view(150, 1) # (150, 1)

w = torch.randn(1, 4)    # 注意形状(linear会自动转置) :3表示类别数据(输出的长度)
b = torch.randn(1)        # w,b是可训练的,就是需要求导或者梯度

y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)  
sy_ = y_.sigmoid()    
# sy_ = y_

loss = torch.nn.functional.l1_loss(sy_, y)   # 
print(loss)

# 手工计算
loss_manual = (y - sy_).abs().mean()
print(loss_manual)
tensor(0.6954)
tensor(0.6954)

kl_div函数

  • The Kullback-Leibler divergence_ Loss.

    • 也称KL距离,一种不同于几何距离的度量方式,用来度量两个概率的差异的距离。

    •   相对熵(relative entropy),又被称为Kullback-Leibler散度(Kullback-Leibler divergence)或信息散度(information divergence),是两个概率分布(probability distribution)间差异的非对称性度量 。
      - >   在在信息理论中,相对熵等价于两个概率分布的信息熵(Shannon entropy)的差值。
      - >  相对熵是一些优化算法,例如最大期望算法(Expectation-Maximization algorithm, EM)的损失函数 。此时参与计算的一个概率分布为真实分布,另一个为理论(拟合)分布,相对熵表示使用理论分布拟合真实分布时产生的信息损耗 。

  • 计算公式:

    • 信息熵:H(x) = - \sum \limits _{i=1} ^ N p(x_i) log \ p(x_i)

    • 散度:D_{KL}(p | q) = \sum \limits _{i=1} ^ N p(x_i) (log \ p(x_i) - log \ q(x_i)) = \sum \limits _{i=1} ^ N p(x_i) log \dfrac{p(x_i)}{q(x_i)}

    • Torch中封装的公式:

      • loss(x, target) = target (log (target) - x):target=0的情况总体看成0,只考虑target为1的情况
      • loss(x, target) = target (- x)
  • 函数定义

    torch.nn.functional.kl_div(input, target, size_average=None, reduce=None, reduction='mean')
    
  • 参数说明:

    • reduction参数:batchmean最后的均值使用batch_size,mean使用输出的个数;
      • 注意batch_size与输出总数是有差别的。如果是(N,1)维度,则没有差别。
  • 例子代码

import sklearn
import sklearn.datasets
import torch

data, target = sklearn.datasets.load_iris(return_X_y=True)

x = torch.Tensor(data[:100])   #  (150,4)
y = torch.Tensor(target[:100]).view(100, 1) # (150, 1)

w = torch.randn(1, 4)    # 注意形状(linear会自动转置) :3表示类别数据(输出的长度)
b = torch.randn(1)        # w,b是可训练的,就是需要求导或者梯度

y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)  
sy_ = y_
loss = torch.nn.functional.kl_div(sy_, y, reduction="batchmean")   
print(loss)

# 手工计算(本质与nll_loss函数一样:nll_loss支持多类)
loss_manual = - y * (sy_)
print(loss_manual.mean())

tensor(-0.9451)
tensor(-0.9451)

hinge_embedding_loss函数

  • 用来测试两个输入的数据是否相似。

    • y的取值为-1或者1
  • 函数定义

    torch.nn.functional.hinge_embedding_loss(input, target, margin=1.0, size_average=None, reduce=None, reduction='mean') → Tensor
  • 函数公式:

    • loss(x,y) = \begin{cases} x& 如 y =1\\ max(0, 1 -x )&如y=-1 \\ \end{cases}
  • 例子代码:

import sklearn
import sklearn.datasets
import torch

data, target = sklearn.datasets.load_iris(return_X_y=True)
target[target == 0] = -1 
x = torch.Tensor(data[:100])   #  (150,4)
y = torch.Tensor(target[:100]).view(100, 1) # (150, 1)
w = torch.randn(1, 4)    # 注意形状(linear会自动转置) :3表示类别数据(输出的长度)
b = torch.randn(1)        # w,b是可训练的,就是需要求导或者梯度

y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)  
sy_ = y_
# print(y_)
# sy_ = y_.sigmoid()
loss = torch.nn.functional.hinge_embedding_loss(sy_, y, reduction="mean")   # 默认均值:比较常采用(多一个sigmoid运算)
print(loss)

# 手工计算(本质与nll_loss函数一样:nll_loss支持多类)
loss_manual[0:50] = 1 - sy_[0:50]
loss_manual[loss_manual< 0] = 0

loss_manual[50:100] = sy_[50:100]
# loss_manual[loss_manual <0] = 0
print(loss_manual.mean())


tensor(0.3959)
tensor(0.3959)

其他损失函数

  • 其他损失函数是基于多分类与其他目的的变种函数,这些损失函数在特定的需求理解会更加容易。
    • 比如:soft_margin_loss是基于SVM的软距离提出的一种损失优化方法。

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

推荐阅读更多精彩内容