常用算法评价指标|盘点与选择

目录

一、前言
二、FM模型简单介绍
三、模型调试阶段
1.数据集调整
2.参数调整

四、模型评估阶段

  • 知识铺垫(混淆矩阵)
  • 准确度(Accuracy)
  • 误差(error)
  • 查准率(precision)
  • 召回率(recall)
  • PRC
  • F1-score
  • ROC曲线和AUC
  • KAPPA系数
  • NDCG

五、总结

一、前言

本文借助FM模型来了解算法的评价标准。因此重点在评价标准,而不在模型本身。
  • 机器学习发展至今,有很多算法供我们选择,但我们如何选择最合适的算法呢?如果需要程序员自己一步步筛选,这是非常耗时间的。那么有没有一种简易的方式,能告诉我们这个模型到底好不好呢?bingo!那就是评价标准!评价标准通常以数字呈现,通过这个数字的大小我们就可以比较算法们的好坏。
  • 在这里我希望大家能带着思考去看本文。是不是误差越小就越好?评价标准能100%说明算法的好坏吗?

二、FM模型的简单介绍

1、FM模型应用意义

FM(Factorization Machine)主要是为了解决数据稀疏的情况下,特征怎样组合的问题。数据稀疏性普遍存在的实际应用场景中,二阶特征系数w{ij}的训练是很困难的.

2、公式与参数

FM模型公式

模型参数

化简后的FM模型公式
  • w_{0}\in \mathbb{R}表示FM的偏置
  • w\in \mathbb{R}^{n}表示FM模型对一阶特征的建模
  • V\in \mathbb{R}^{nk}表示FM模型对二阶特征的建模

3、FM模型重要原理|矩阵分解

  • FM模型是矩阵分解的经典算法,因此在这里对矩阵分解的原理进行讲解。

  • 我们希望的到的结果是:\widehat{w}=V\times V^{T}隐向量矩阵相乘的结果


(1) 二阶特征组成矩阵V:(矩阵V的第i行表示:第i维特征x_{i}的隐向量v_{i}

$V$矩阵


(2)V矩阵转置

$V$矩阵的转置


(3)多项式模型的二阶特征w_{ij}组成的方阵W
W=\begin{bmatrix} w_{11}&w_{12}& \cdots &w_{1n}\\ w_{21} &w_{22} & \cdots &w_{2n} \\ \cdots &\cdots &\cdots &\cdots \\ w_{n1} &w_{n2} &\cdots &w_{nn} \end{bmatrix}

  • 方阵W的上三角的元素(不包括对角线上的元素),即为多项式模型的二阶特征系数w_{ij}
  • 注意: 为什么只是上三角?因为必须i<j

(4)<V_{i},V_{j}>组成方阵 \hat{w}
\hat{w}= V\times V^j = \begin{bmatrix} v_{1}^T\\ v_{2}^T\\ \cdots \\ v_{n}^T \end{bmatrix}\times \begin{bmatrix} v_{1} &v_{2} &\cdots & v_{n} \end{bmatrix}
= \begin{bmatrix} v_{1}^Tv_{1} & v_{1}^Tv_{2} &\cdots & v_{1}^Tv_{n}\\ v_{2}^Tv_{1} & v_{2}^Tv_{2} &\cdots &v_{2}^Tv_{n} \\ \cdots & \cdots &\cdots &\cdots \\ v_{n}^Tv_{1} &v_{n}^Tv_{2} &\cdots &v_{n}^Tv_{n} \end{bmatrix}
= \begin{bmatrix} v_{1}^Tv_{1} & \hat{w}_{12} &\cdots & \hat{w}_{1n}\\ \hat{w}_{21} & v_{2}^Tv_{2} &\cdots &\hat{w}_{2n} \\ \cdots & \cdots &\cdots &\cdots \\ \hat{w}_{n1} &\hat{w}_{n2} &\cdots &v_{n} ^Tv_{n} \end{bmatrix}

  • 方阵\hat{W}的上三角的元素(不包括对角线),即为FM模型的二阶特征系数:<v_{i}v_{j}>
  • 注意:i<j
  • FM模型只关心互异特征之间的关系(i<j),所以对角线可以任意取值。
矩阵分解结论:

当超参数k充分大,对于任意正定的实矩阵\hat{W}\in R_{n\times n},均存在实矩阵\hat{V}\in R_{n\times k}使得\widehat{w}=V\times V^{T}


  • 补充: 本次实验我们用FM模型对电影等级评价进行分类。采用的损失函数为logit逻辑函数,采用SGD优化目标函数

在这里对FM模型其他方面不过多阐述,详情移步


 

三、模型调试阶段

1.数据集调试

  • Q: 数据集越大越好吗?
    A:一般情况下,数据集越大越好,但这个一般有成立前提。
    这个答案放在10年前、20年前,答案毋庸置疑是yes。当时很流行一句话“不是拥有最好的算法的人会成功,而是拥有最多数据的人能成功。”但是这句话成立有前提条件
    前提条件①特征值x包含了足够多的信息来预测y。
    举个例子:房价预测模型,如果数据集包括了很多房子,但是特征值只有房子大小,其他特征如地段、周围配套一概不知,无疑这个数据集即使很大但也无法无法很好地提升算法性能。
    前提条件②所使用的算法具有较强的学习能力(参数比较多的),能够更好地处理大的数据集。
    综上所述:一个高性能的学习算法搭配拥较多的特征值信息的大数据集,才是数据集越大越好成立的前提条件。

(1)数据集的划分

由于我们是百万级别的数据,因此我们采用留出法(hold-out)将数据集划分为训练集和测试集。
留出法直接将数据集D划分为两个互斥的部分,其中一部分作为训练集S ,另一部分用作测试集T。训练集和测试集的比例为70%:30%

  • 2万个数据保持与原始数据分布的一致性,避免出现额外偏差,采用若干次随机划分,避免单次使用留出法的不稳定性。
from sklearn.model_selection import train_test_split
X=df.drop('rating', axis=1)
Y=df['rating']
X_train,X_val,Y_train,Y_val=train_test_split(X, Y, test_size=0.3, random_state=3)

(2)调整数据集大小

学习率为0.001,数据集为2000

学习率为0.001,数据集为20000

我们可以得到这样的结论:

  • 在数据集样本数量较少时(2000个),改变迭代次数的作用明显。.误差变化很大,整体不稳定
  • 在数据集样本数量较大时(2万个)改变迭代次数作用不大。总体上看,在迭代次数为5左右达到局部最优。并大约在迭代次数5之后达到稳定不变。
  • 在我们误差极值差不多的前提前,为了追求稳定性,我们选择2万的数据。

 

2.参数调整

根据以往的经验,这里我主要对学习率迭代数进行调整。

(1) 调试方法 1 其他参数不变 调整迭代数(学习率为0.001,数据集为20000)

(学习率为0.001,数据集为20000)

结论:在较大的数据集下,不改变其他参数的前提下,迭代次数1-10对模型准确度和误差的影响较大。迭代数超过10后影响不大。


(2)调试方法2 调整学习率

迭代数、学习率与训练误差的关系

迭代数、学习率与泛化误差的关系

  • 可以看出训练误差和泛化误差差别不大过拟合的可能性很低。因此,我们观察泛化误差,寻找最优参数搭配即可。
  • 总体上看,学习率为0.005的泛化误差平均值最小。因此,考虑选择学习率为0.005。同时可以看到学习率为0.005训练次数为72的时候,泛化误差最小。
  • 虽然迭代数为10之后影响不大,但是我们在细微的变化中选择最优的
  • 因此,目前得到的最优参数搭配:数据集20000,迭代数72 ,学习率0.005.

 

四、模型评估阶段

1、知识铺垫|混淆矩阵

混淆矩阵
  • True Positive (真正,TP)被模型预测为正的正样本
  • True Negative(真负 , TN)被模型预测为负的负样本
  • False Positive (假正, FP)被模型预测为正的负样本
  • False Negative(假负 , FN)被模型预测为负的正样本

FM模型运行结果

 

2、准确度(Accuracy)

准确度公式
  • 准确度(Accuracy)用来计算预测正确的样本占所有样本的比重
  • 是我们最常用的评价指标,一般来说这个数值越高越好。


    FM模型准确度,数据集20000,学习率0.005 迭代数72

评价结果:从准确度上看,FM模型在电影评价分类上表现水平较为一般。


 

3、误差(error)

(1) 知识铺垫:(经验误差|泛化误差|欠拟合|过拟合)
  • 学习器在训练集上的误差成为“训练误差”或“经验误差”。在新样本上的误差称为“泛化误差”。我们更看重泛化误差,因为泛化误差越小,代表这个个模型的泛化能力越好。
  • 然而可能会存在这种情况:某个模型训练误差很小,泛化误差很大。就是说在训练集上表现非常优秀,近乎完美的预测/区分了所有的数据,但是在新的测试集上却表现平平。这时候就出现了模型对于训练集出现了过拟合的现象,对于测试集出现了欠拟合的现象。
  • 点击过拟合和欠拟合了解更多。

(2) 对于分类问题,我们常采用“logit逻辑函数”
logit函数
  • 谈到Logit我们不得不谈到sigmoid
    函数在跳跃点上从0瞬间跳跃到1,这个瞬间跳跃过程有时很难处理。幸 好,另一个函数也有类似的性质,而且数学上更易处理,这就是Sigmoid函数
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

'''计算logit损失函数:L = log(1 + e**(y_hat * y))'''
def logit(y, y_hat):
    return np.log(1 + np.exp(-y * y_hat))

(3) 评估结论

详细的评估过程其实已经在参数调整里说明过了,这里不再复述。泛化误差与训练误差皆在0.67左右,过拟合可能性低,但高达0.67的泛化误差,仍然说明了这个FM模型在电影评分分类表现不佳。


 

4、查准率(Precision)

  • 计算公式:
    P=\frac{TP}{TP+FP}
  • 查准率是预测为真中确实为真所占的比重。


    查准率

FM模型结果

评估结果

  • 我们可以看到学习率为0.005,迭代数为50时,PRECISION达到了局部最优,相较于别的学习率,学习率0.005在PRECISION上表现不错。
  • 整体而言PRECISION值在0.54左右,确实为真占预测为真的比重超过一半,FM模型性能在电影评分分类上还算一般。

 

5、召回率(Recall)

  • 召回率是预测为真占确实为真的比重。
  • 计算公式:
    R=\frac{TP}{TP+FN}

评估结果

  • 从召回率看,如果我们追求稳定性,就选择学习率为0.01的,平均值在0.38左右。如果我们追求高的recall,我们就选你学习率为0.0001,迭代数为1的参数搭配。
  • 整体而言,RECALL在0.4左右,预测为真占确实为真的比重较低,性能一般。

TIPS:查准率和召回率的关系
查准率和查全率是一对矛盾的度量,一般而言,查准率高时,查全率往往偏低;而查全率高时,查准率往往偏低。

那么问题来了。如何在precision 和recall中权衡?
当然是具体问题具体分析。主要看你目的,但我们会希望尽量避免极端情况。

举个例子:

  • 癌症筛查,一般会要求高的查准度,并不要求高的查全率。因为,高的查全率,仅仅说明更多的人被测出可能得癌症,但是却不能精确地指出真正得癌症的人,不够精确的可能会给测试者带来心理上的负担,最后让测试者虚惊一场。
  • 但如果是病毒筛查,一般会要求高的查全率的,因为“宁肯误杀、不肯错放”,不小心放出去的携带病毒者,可能会导致悲剧的后果。
  • 如果你先上述过程太麻烦,懒癌患者首选:PRC和F1曲线

 

6、PRC曲线

  • PRC (全称就是Precision Recall Curve)
  • 以查准率为Y轴,查全率为X轴做的图。它是综合评价整体结果的评估指标。
  • 在进行比较时,若一个学习器的PR曲线被另一个学习器的曲线完全包住,则可断言后者的性能优于前者.

评估结果

PRC

从PRC曲线上看,我们试图找均衡点,(0.59,0.3),但仍然离右上角有些远,说明FM模型对于电影评分分类较为一般。


 

7、F1 -score

  • F1-scores是一个综合考虑了Precison和recall的指标,它被定义为精确率和召回率调和平均数
    比P-R曲线的平衡点更为常用
  • 相较于P-R曲线,F1能够避免极端情况,即能避免P极高,R极其低,或者P极低,R极高的情况,因为这两个值其中有一个很低,F1就会很低,一目了然,我们就能知道模型综合优劣。
  • 计算公式:
    F1=2\times \frac{PR}{P+R}

评估结果


  • 从F1-score来看,学习率为0.01时,FM分类器较为稳定。
  • 但是F1-score的极大值出现在学习率0.0001,迭代数为1次时,但是极差大,稳定性较低。
  • 参数选择:若最求高的F1-score,就选择学习率为0.0001的,若求稳,就选择学习率为0.01的。
  • 模型评估:F1-score的整体平均值在0.44左右。FM模型在电影评分分类上性能表现一般。

 

8、ROC&AUC

  • ROC全称是“受试者工作特征”(Receiver Operating Characteristic)曲线,ROC曲线以“真正例率”(TPR)为Y轴,以“假正例率”(FPR)为X轴,对角线对应于“随机猜测”模型,而(0,1)则对应“理想模型”。

TPR=\frac{TP}{TP+FN}
FPR=\frac{FP}{TN+FP}

  • 从形式上看TPR就是我们上面提到的查全率Recall,而FPR的含义就是:所有确实为“假”的样本中,被误判真的样本。
  • 若一个学习器的ROC曲线被另一个学习器的曲线包住,那么我们可以断言后者性能优于前者
  • 若两个学习器的ROC曲线发生交叉,则难以一般性断言两者孰优孰劣。此时若要进行比较,那么可以比较ROC曲线下的面积,即AUC,面积大的曲线对应的分类器性能更好。

  • AUC(Area Under Curve)的值为ROC曲线下面的面积,若分类器的性能极好,则AUC为1。但现实生活中不会有如此完美的模型,一般AUC均在0.5到1之间,AUC越高,模型的区分能力越好。
  • 点击全面了解ROC曲线了解更多

评估结果


  • 从ROC曲线看,我们可以看出最优零界点距离(0,1)还是有些远,因此用FM模型来对电影评分分类效果不佳。
  • AUC值约为0.57,起码大于了0.5,说明FM模型对于电影评分的分类还是可以用的,只是性能一般

 

9、KAPPA系数

  • Kappa系数用于一致性检验,也可以用于衡量分类精度

  • Kappa计算结果为-1~1,但通常kappa是落在 0~1 间,可分为五组来表示不同级别的一致性:
    ①0.0~0.20 极低的一致性(slight)
    ②0.21~0.40 一般的一致性(fair)
    ③0.41~0.60 中等的一致性(moderate)
    ④0.61~0.80 高度的一致性(substantial)
    ⑤0.81~1几乎完全一致(almost perfect)

  • 计算公式:
    K=\frac{P_{0}-P_{e}}{1-P_{e}}

  • 解释:
    P_{0}是每一类正确分类的样本数量之和除以总样本数,也就是总体分类精度
    ②假设每一类的真实样本个数分别为a_{1},a_{2},...,a_{m},而预测出来的每一类的样本个数分别为b_{1},b_{2},...,b_{m},总样本个数为n,则有P_{e}=\frac{a_{1}\times b_{1}+a_{2}+b_{2}+\cdots +a_{m}\times b_{m}}{n\times n}
  • 点击Kappa系数在大数据评测中的运用了解更多

评估结果

Kappa系数: 0.1431773928163418

KAPPA系数越为0.14,处于0~0.2的区间,证明FM模型对于电影评分的分类具有极低的一致性。


 

10、NDCG

(1)含义与要点
  • NDCG(Normalized Discounted Cummulative Gain)用中文表达为归一化折损累计增益
  • 高关联度的结果比一般关联度的结果更影响最终的指标得分;
  • 有高关联度的结果出现在更靠前的位置的时候,指标会越高;
(2) 评价结果
test-ndcg

test-ndcg

结论:我们可以看到学习率为0.005在迭代次数为250时,ndcg值达到最大,但这个0.365258仍不够看。而NDCG是0到1的数,越接近1说明模型性能越好。因此,我们的FM模型电在影评分的二分类场景下表现较差。


(3)分解与推进

TIP:要理解NDCG,我们需从CG到DCG再到NDCG.

1)累积增益CG,推荐系统中CG表示将每个推荐结果相关性的分值累加后作为整个推荐列表的得分:CG_{k}=\sum_{i=1}^{k}rel_{i}

  • 解释rel_{i}表示位置i的相关性,k表示推荐列表的大小。
  • 缺点:只考虑到了相关性的关联程度,没有考虑到位置的因素。即B_{1}B_{2}B_{3}B_{1}B_{2}B_{3}没有区别
  • 基于上述的缺点,我们需要引入DCG

2)折损累计增益(DCG)
DCG, Discounted 的CG,就是在每一个CG的结果上除以一个折损值。目的就是为了让排名越靠前的结果越能影响最后的结果

DCG_{K}=\sum_{i=1}^{k}\frac{2^{rel_{i}}-1}{log_{2}(i+1)}

  • 作用
    ①推荐结果的相关性越大,DCG越大。
    ②若相关性好的排在推荐列表前面,则推荐效果越好,DCG越大。
  • 缺点
    DCG针对不同的推荐列表之间很难进行横向评估,而我们评估一个推荐系统不可能仅使用一个用户的推荐列表及相应结果进行评估,而是对整个测试集中的用户及其推荐列表结果进行评估。 因此在用户与用户之间,DCG没有直接的可比性,所以我们要对它们进行归一化处理

3)归一化折损累计增益NDCG
对于不同用户的推荐列表的评估分数就进行归一化,就是NDCG,normalize后的DCG
NDCG_{K}=\frac{DCG_{K}}{IDCG_{K}}
IDCG为理想情况下最大的DCG值:
IDCG_{K}=\sum_{i=1}^{\left |REL \right |}\frac{2^{rel_{i}}-1}{log_{2}(i+1)}
其中 |REL| 表示,结果按照相关性从大到小的顺序排序,取前K个结果组成的集合。也就是按照最优的方式对结果进行排序。

 

五、总结

1、评价总结

FM第1次迭代,当前损失值为:0.6732
FM第21次迭代,当前损失值为:0.6723
FM第41次迭代,当前损失值为:0.6723
FM第61次迭代,当前损失值为:0.6723
FM在测试集的分类准确率为: 60.25%
FM在训练集的分类准确率为: 59.75%
训练集roc: 56.54%
混淆矩阵: 
 [[6558 1265]
 [4370 1806]]
验证集roc: 56.77%
混淆矩阵: 
 [[2803  608]
 [1777  812]]
查准率: 0.571830985915493
查全率: 0.31363460795674003
查准率与查全率的调和平均: 0.4050885507607882
ndcg指标: 0.35131512781803936
AUC: 0.5676938797625974
Kappa系数: 0.1431773928163418
海明距离: 0.3975
程序运行了: 0:04:53.367329 秒

Process finished with exit code 0

从准确率看,FM性能在电影评分分类里表现一般,查准率高于查全率,确实为真占预测为真的比重高于预测为真占确实为真的比重。ndcg在0.35左右,KAPPA 系数在0.143左右,FM模型在电影评分分类中性能表现不佳。AUC与PRC表现差距不大,证明正负样本分布均匀。总的来说,这么多评价指标都指向了一个结果:FM模型在电影评分分类中性能表现一般。


 

2、方法总结

  • Q:回答我们开头的问题,评价标准能100%说明模型的好坏吗
    A:评价标准反映了任务需求,在对比不同的模型能力时,使用不同的评价标准会导致不同的评价结果,这意味者模型的好坏时相对的,什么样的模型是好的,不仅仅取决于算法和数据,还决定于任务需求,如我们前面提到的癌症筛查、病毒筛查。垃圾短信分类也同理。

  • Q:误差越小越好吗?
    A:不是的,训练误差和泛化误差这些我就不谈了,除了过拟合的风险,我们还有一个就是因为数据选取了较多符合模型预测的,比如我们想要一个模型预测出1的准确率达到99%,那么有些人的数据里可能就放99个1,1个-1,所以别看误差小~这实际上是模型在“欺骗你”

    迭代数为25 学习率为0.001 数据为2000的欺骗结果


  • 那么具体到底如何选择评价标准呢?

①关于precision和recall的两者的权衡
两者的权衡主要参考任务需求
举个例子,垃圾短信分类。如果我们不希望漏掉任何重要短信时,我们可以选择高的recall,相对低的precision。如果我们痛恶垃圾短信,重要信息被漏掉也无妨,那就选择高的precision,低的recall。

②关于PRC和F1的选择。
PRC的均衡点是Precision、recall的简单算数平均数。而F1是Precision recall的调和平均数。这意味着,相比PRC,F1更加能避免极端情况。precision和recall 其中一个值很低,F1值就会低,而PRC只要其中一个值很高,另一个值很低也不影响PRC的值。因为:\frac{0.1+0.9}{2}\frac{0.9+0.1}{2} \frac{0.5+0.5}{2}在PRC曲线里都一样。因此,关于PRC和F1两者的选择,更取决于你在不在乎极端情况的出现。

关于PRC和ROC的选择
ROC和PRC在模型性能评估上效果都差不多,但需要注意的是,在正负样本分布得极不均匀(highly skewed datasets)的情况下,PRC比ROC能更有效地反应分类器的好坏。点击这了解更多

希望本篇文章能够对你有所帮助,共勉 : )


代码来自GitHub
https://gith<wbr>ub.com/moren<wbr>jiujiu/FM/bl<wbr>ob/master/FM<wbr>_Demo.ipynb

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

推荐阅读更多精彩内容