GAN的理论推导全过程

生成对抗网络基本概念

要理解生成对抗模型(GAN),首先要了解生成对抗模型可以拆分为两个模块:一个是判别模型,另一个是生成模型。简单来说就是:两个人比赛,看是 A 的矛厉害,还是 B 的盾厉害。比如,我们有一些真实数据,同时也有一把随机生成的假数据。A 拼命地把随手拿过来的假数据模仿成真实数据,并揉进真实数据里。B 则拼命地想把真实数据和假数据区分开。

这里,A 就是一个生成模型,类似于造假币的,一个劲地学习如何骗过 B。而 B 则是一个判别模型,类似于稽查警察,一个劲地学习如何分辨出 A 的造假技巧。

如此这般,随着 B 的鉴别技巧越来越厉害,A 的造假技巧也是越来越纯熟,而一个一流的假币制造者就是我们所需要的。虽然 GAN 背后的思想十分直观与朴素,但我们需要更进一步了解该理论背后的证明与推导。

总的来说,Goodfellow 等人提出来的 GAN 是通过对抗过程估计生成模型的新框架。在这种框架下,我们需要同时训练两个模型,即一个能捕获数据分布的生成模型 G 和一个能估计数据来源于真实样本概率的判别模型 D。生成器 G 的训练过程是最大化判别器犯错误的概率,即判别器误以为数据是真实样本而不是生成器生成的假样本。因此,这一框架就对应于两个参与者的极小极大博弈(minimax game)。在所有可能的函数 G 和 D 中,我们可以求出唯一均衡解,即 G 可以生成与训练样本相同的分布,而 D 判断的概率处处为 1/2,这一过程的推导与证明将在后文详细解释。

当模型都为多层感知机时,对抗性建模框架可以最直接地应用。为了学习到生成器在数据 x 上的分布 P_g,我们先定义一个先验的输入噪声变量P_z(z),然后根据 G(z;θ_g)将其映射到数据空间中,其中 G 为多层感知机所表征的可微函数。我们同样需要定义第二个多层感知机 D(s;θ_d),它的输出为单个标量。D(x) 表示 x 来源于真实数据而不是 P_g的概率。我们训练 D 以最大化正确分配真实样本和生成样本的概率,因此我们就可以通过最小化 log(1-D(G(z))) 而同时训练 G。也就是说判别器 D 和生成器对价值函数 V(G,D) 进行了极小极大化博弈:

img

我们后一部分会对对抗网络进行理论上的分析,该理论分析本质上可以表明如果 G 和 D 的模型复杂度足够(即在非参数限制下),那么对抗网络就能生成数据分布。此外,Goodfellow 等人在论文中使用如下案例为我们简要介绍了基本概念。

img

如上图所示,生成对抗网络会训练并更新判别分布(即 D,蓝色的虚线),更新判别器后就能将数据真实分布(黑点组成的线)从生成分布P_g(G)(绿色实线)中判别出来。下方的水平线代表采样域 Z,其中等距线表示 Z 中的样本为均匀分布,上方的水平线代表真实数据 X 中的一部分。向上的箭头表示映射 x=G(z) 如何对噪声样本(均匀采样)施加一个不均匀的分布 P_g

(a)考虑在收敛点附近的对抗训练:P_gP_{data} 已经十分相似,D 是一个局部准确的分类器。

(b)在算法内部循环中训练 D 以从数据中判别出真实样本,该循环最终会收敛到 D(x)=p_{data}(x)/(p_{data}(x)+p_g(x))

(c)随后固定判别器并训练生成器,在更新 G 之后,D 的梯度会引导 G(z)流向更可能被 D 分类为真实数据的方向。

(d)经过若干次训练后,如果 G 和 D 有足够的复杂度,那么它们就会到达一个均衡点。这个时候 p_g=p_{data},即生成器的概率密度函数等于真实数据的概率密度函数,也即生成的数据和真实数据是一样的。在均衡点上 D 和 G 都不能得到进一步提升,并且判别器无法判断数据到底是来自真实样本还是伪造的数据,即 D(x)= 1/2。

上面比较精简地介绍了生成对抗网络的基本概念,下一节将会把这些概念形式化,并描述优化的大致过程。

概念与过程的形式化

理论完美的生成器

该算法的目标是令生成器生成与真实数据几乎没有区别的样本,即一个造假一流的 A,就是我们想要的生成模型。数学上,即将随机变量生成为某一种概率分布,也可以说概率密度函数为相等的:P_G(x)=P_{data}(x)。这正是数学上证明生成器高效性的策略:即定义一个最优化问题,其中最优生成器 G 满足 P_G(x)=P_{data}(x)。如果我们知道求解的 G 最后会满足该关系,那么我们就可以合理地期望神经网络通过典型的 SGD 训练就能得到最优的 G。

最优化问题

正如最开始我们了解的警察与造假者案例,定义最优化问题的方法就可以由以下两部分组成。首先我们需要定义一个判别器 D 以判别样本是不是从 P_{data}(x) 分布中取出来的,因此有:

img

其中 E 指代取期望。这一项是根据「正类」(即辨别出 x 属于真实数据 data)的对数损失函数而构建的。最大化这一项相当于令判别器 D 在 x 服从于 data 的概率密度时能准确地预测 D(x)=1,即:

img

另外一项是企图欺骗判别器的生成器 G。该项根据「负类」的对数损失函数而构建,即:

img

因为 x<1 的对数为负,那么如果最大化该项的值,则需要令均值 D(G(z))≈0,因此 G 并没有欺骗 D。为了结合这两个概念,判别器的目标为最大化:

img

给定生成器 G,其代表了判别器 D 正确地识别了真实和伪造数据点。给定一个生成器 G,上式所得出来的最优判别器可以表示为

img

(下文用D_G*表示)。定义价值函数为:

img

然后我们可以将最优化问题表述为:

img

现在 G 的目标已经相反了,当 D=D_G*时,最优的 G 为最小化前面的等式。在论文中,作者更喜欢求解最优化价值函的 G 和 D 以求解极小极大博弈:

img

对于 D 而言要尽量使公式最大化(识别能力强),而对于 G 又想使之最小(生成的数据接近实际数据)。整个训练是一个迭代过程。其实极小极大化博弈可以分开理解,即在给定 G 的情况下先最大化 V(D,G) 而取 D,然后固定 D,并最小化 V(D,G) 而得到 G。其中,给定 G,最大化 V(D,G) 评估了P_GP_{data}之间的差异或距离。

最后,我们可以将最优化问题表达为:

img

上文给出了 GAN 概念和优化过程的形式化表达。通过这些表达,我们可以理解整个生成对抗网络的基本过程与优化方法。当然,有了这些概念我们完全可以直接在 GitHub 上找一段 GAN 代码稍加修改并很好地运行它。但如果我们希望更加透彻地理解 GAN,更加全面地理解实现代码,那么我们还需要知道很多推导过程。比如什么时候 D 能令价值函数 V(D,G) 取最大值、G 能令 V(D,G) 取最小值,而 D 和 G 该用什么样的神经网络(或函数),它们的损失函数又需要用什么等等。总之,还有很多理论细节与推导过程需要我们进一步挖掘。

理论推导

在原 GAN 论文中,度量生成分布与真实分布之间差异或距离的方法是 JS 散度,而 JS 散度是我们在推导训练过程中使用 KL 散度所构建出来的。所以这一部分将从理论基础出发再进一步推导最优判别器和生成器所需要满足的条件,最后我们将利用推导结果在数学上重述训练过程。这一部分为我们下一部分理解具体实现提供了强大的理论支持。

KL 散度

在信息论中,我们可以使用香农熵(Shannon entropy)来对整个概率分布中的不确定性总量进行量化:

img

如果我们对于同一个随机变量 x 有两个单独的概率分布 P(x) 和 Q(x),我们可以使用 KL 散度(Kullback-Leibler divergence)来衡量这两个分布的差异:

img

在离散型变量的情况下,KL 散度衡量的是,当我们使用一种被设计成能够使得概率分布 Q 产生的消息的长度最小的编码,发送包含由概率分布 P 产生的符号的消息时,所需要的额外信息量。

KL 散度有很多有用的性质,最重要的是它是非负的。KL 散度为 0 当且仅当 P 和 Q 在离散型变量的情况下是相同的分布,或者在连续型变量的情况下是 『几乎处处』 相同的。因为 KL 散度是非负的并且衡量的是两个分布之间的差异,它经常 被用作分布之间的某种距离。然而,它并不是真的距离因为它不是对称的:对于某些 P 和 Q,D_{KL}(P||Q) 不等于 D_{KL}(Q||P)。这种非对称性意味着选择 D_{KL}(P||Q)还是 D_{KL}(Q||P)影响很大。

在李弘毅的讲解中,KL 散度可以从极大似然估计中推导而出。若给定一个样本数据的分布 P_{data}(x)和生成的数据分布 P_G(x;θ),那么 GAN 希望能找到一组参数θ使分布 P_g(x;θ)P_{data}(x)之间的距离最短,也就是找到一组生成器参数而使得生成器能生成十分逼真的图片。

现在我们可以从训练集抽取一组真实图片来训练 P_G(x;θ)分布中的参数 θ 使其能逼近于真实分布。因此,现在从P_{data}(x)中抽取 m 个真实样本 {x^1,x^2,…,x^?},即 x 中的第 i 个样本。对于每一个真实样本,我们可以计算 P_G(x^i;θ),即在由 θ 确定的生成分布中,x^i样本所出现的概率。因此,我们就可以构建似然函数:

img

其中∏代表累乘、P_G(x^i;θ)代表第 i 个样本在生成分布出现的概率。从该似然函数可知,我们抽取的 m 个真实样本在 P_G(x;θ)分布中全部出现的概率值可以表达为 L。又因为若 P_G(x;θ)分布和 P_{data}(x)分布相似,那么真实数据很可能就会出现在 P_G(x;θ) 分布中,因此 m 个样本都出现在P_G(x;θ)分布中的概率就会十分大。

下面我们就可以最大化似然函数 L 而求得离真实分布最近的生成分布(即最优的参数θ):

img

在上面的推导中,我们希望最大化似然函数 L。若对似然函数取对数,那么累乘 ∏ 就能转化为累加 ∑,并且这一过程并不会改变最优化的结果。因此我们可以将极大似然估计化为求令log[P_G(x;θ)]期望最大化的 θ,而期望E[logP_G(x;θ)]可以展开为在 x 上的积分形式:∫P_data(x)logP_G(x;θ)dx。又因为该最优化过程是针对 θ 的,所以我们添加一项不含 θ 的积分并不影响最优化效果,即可添加-∫P_data(x)logP_data(x)dx。添加该积分后,我们可以合并这两个积分并构建类似 KL 散度的形式。该过程如下:

img

这一个积分就是 KL 散度的积分形式,因此,如果我们需要求令生成分布P_G(x;θ)​ 尽可能靠近真实分布 P_{data}(x) ​的参数 θ,那么我们只需要求令 KL 散度最小的参数 θ。若取得最优参数 θ,那么生成器生成的图像将显得非常真实。

推导存在的问题

下面,我们必须证明该最优化问题有唯一解 G*,并且该唯一解满足P_G=P_{data}​。不过在开始推导最优判别器和最优生成器之前,我们需要了解 Scott Rome 对原论文推导的观点,他认为原论文忽略了可逆条件,因此最优解的推导不够完美。

在 GAN 原论文中,有一个思想和其它很多方法都不同,即生成器 G 不需要满足可逆条件。Scott Rome 认为这一点非常重要,因为实践中 G 就是不可逆的。而很多证明笔记都忽略了这一点,他们在证明时错误地使用了积分换元公式,而积分换元却又恰好基于 G 的可逆条件。Scott 认为证明只能基于以下等式的成立性:

img

该等式来源于测度论中的 Radon-Nikodym 定理,它展示在原论文的命题 1 中,并且表达为以下等式:

img

我们看到该讲义使用了积分换元公式,但进行积分换元就必须计算 G^{(-1)}​,而 G 的逆却并没有假定为存在。并且在神经网络的实践中,它也并不存在。可能这个方法在机器学习和统计学文献中太常见了,因此我们忽略了它。

最优判别器

在极小极大博弈的第一步中,给定生成器 G,最大化 V(D,G) 而得出最优判别器 D。其中,最大化 V(D,G) 评估了 P_G ​P_{data} ​之间的差异或距离。因为在原论文中价值函数可写为在 x 上的积分,即将数学期望展开为积分形式:

img

其实求积分的最大值可以转化为求被积函数的最大值。而求被积函数的最大值是为了求得最优判别器 D,因此不涉及判别器的项都可以看作为常数项。如下所示,P_{data}(x)P_G(x)都为标量,因此被积函数可表示为a*D(x)+b*log(1-D(x))

img

若令判别器 D(x) 等于 y,那么被积函数可以写为:

img

为了找到最优的极值点,如果 a+b≠0,我们可以用以下一阶导求解:

img

如果我们继续求表达式 f(y) 在驻点的二阶导:

img

其中a,b∈(0,1)​。因为一阶导等于零、二阶导小于零,所以我们知道a/(a+b) ​为极大值。若将 a=P_{data}(x)​b=P_G(x) ​代入该极值,那么最优判别器D(x)=P_{data}(x)/(P_{data}(x)+P_G(x))​

最后我们可以将价值函数表达式写为:

img

如果我们令 D(x)=P_{data}/(P_{data}+P_G)​,那么我们就可以令价值函数 V(G,D) 取极大值。因为 f(y) 在定义域内有唯一的极大值,最优 D 也是唯一的,并且没有其它的 D 能实现极大值。

其实该最优的 D 在实践中并不是可计算的,但在数学上十分重要。我们并不知道先验的P_{data}(x)​,所以我们在训练中永远不会用到它。另一方面,它的存在令我们可以证明最优的 G 是存在的,并且在训练中我们只需要逼近 D。

最优生成器

当然 GAN 过程的目标是令 P_G=P_{data}​。这对最优的 D 意味着什么呢?我们可以将这一等式代入D_G*​的表达式中:

img

这意味着判别器已经完全困惑了,它完全分辨不出 P_{data}​ 和 P_G 的区别,即判断样本来自 P_{data} ​P_G ​的概率都为 1/2。基于这一观点,GAN 作者证明了 G 就是极小极大博弈的解。该定理如下:

「当且仅当P_G=P_{data},训练标准 C(G)=maxV(G,D) 的全局最小点可以达到。」

以上定理即极大极小博弈的第二步,求令 V(G,D) 最小的生成器 G(其中 G代表最优的判别器)。之所以当 P_G(x)=P_{data}(x) 可以令价值函数最小化,是因为这时候两个分布的 JS 散度 JSD(P_{data}(x) || P_G(x))等于零,这一过程的详细解释如下。

原论文中的这一定理是「当且仅当」声明,所以我们需要从两个方向证明。首先我们先从反向逼近并证明 C(G) 的取值,然后再利用由反向获得的新知识从正向证明。设 P_G=P_{data}(反向指预先知道最优条件并做推导),我们可以反向推出:

img

该值是全局最小值的候选,因为它只有在 P_G=P_{data} 的时候才出现。我们现在需要从正向证明这一个值常常为最小值,也就是同时满足「当」和「仅当」的条件。现在放弃 P_G=P_{data}的假设,对任意一个 G,我们可以将上一步求出的最优判别器 D* 代入到 C(G)=maxV(G,D) 中:

img

因为已知 -log4 为全局最小候选值,所以我们希望构造某个值以使方程式中出现 log2。因此我们可以在每个积分中加上或减去 log2,并乘上概率密度。这是一个十分常见并且不会改变等式的数学证明技巧,因为本质上我们只是在方程加上了 0。

img

采用该技巧主要是希望能够构建成含 log2 和 JS 散度的形式,上式化简后可以得到以下表达式:

img

因为概率密度的定义,P_GP_{data} 在它们积分域上的积分等于 1,即:

此外,根据对数的定义,我们有:

img

因此代入该等式,我们可以写为:

img

现在,如果读者阅读了前文的 KL 散度(Kullback-Leibler divergence),那么我们就会发现每一个积分正好就是它。具体来说:

img

KL 散是非负的,所以我们马上就能看出来 -log4 为 C(G) 的全局最小值。

如果我们进一步证明只有一个 G 能达到这一个值,因为 P_G=P_{data}将会成为令 C(G)=−log4的唯一点,所以整个证明就能完成了。

从前文可知 KL 散度是非对称的,所以 C(G) 中的KL(P_{data} || (P_{data}+P_G)/2) ​左右两项是不能交换的,但如果同时加上另一项 KL(P_G || (P_{data}+P_G)/2)​,它们的和就能变成对称项。这两项 KL 散度的和即可以表示为 JS 散度(Jenson-Shannon divergence):

img

假设存在两个分布 P 和 Q,且这两个分布的平均分布M=(P+Q)/2​那么这两个分布之间的 JS 散度为 P 与 M 之间的 KL 散度加上 Q 与 M 之间的 KL 散度再除以 2。

JS 散度的取值为 0 到 log2。若两个分布完全没有交集,那么 JS 散度取最大值 log2;若两个分布完全一样,那么 JS 散度取最小值 0。

因此 C(G) 可以根据 JS 散度的定义改写为:

img

这一散度其实就是 Jenson-Shannon 距离度量的平方。根据它的属性:当 P_G=P_{data} ​时,JSD(P_{data}||P_G) ​为 0。综上所述,生成分布当且仅当等于真实数据分布式时,我们可以取得最优生成器。

收敛

现在,该论文的主要部分已经得到了证明:即P_G=P_{data}​ 为 maxV(G,D) 的最优点。此外,原论文还有额外的证明白表示:给定足够的训练数据和正确的环境,训练过程将收敛到最优 G,我们并不详细讨论这一块。

重述训练过程

下面是推导的最后一步,我们会重述整个参数优化过程,并简要介绍实际训练中涉及的各个过程。

1.参数优化过程

若我们需要寻找最优的生成器,那么给定一个判别器 D,我们可以将 maxV(G,D) 看作训练生成器的损失函数 L(G)。既然设定了损失函数,那么我们就能使用 SGD、Adam 等优化算法更新生成器 G 的参数,梯度下降的参数优化过程如下:

img

其中求 L(G) 对θ_G 的偏导数涉及到求 max{V(G,D)} 的偏导数,这种对 max 函数求微分的方式是存在且可用的。

现在给定一个初始 G_0,我们需要找到令 V(G_0,D)最大的 D_{0^*},因此判别器更新的过程也就可以看作损失函数为-V(G,D) 的训练过程。并且由前面的推导可知,V(G,D) 实际上与分布P_{data}(x)P_G(x) 之间的 JS 散度只差了一个常数项。因此这样一个循环对抗的过程就能表述为:

  • 给定 G_0,最大化 V(G_0,D) 以求得D_0*,即 max[JSD(P_{data}(x)||P_{G_0}(x)]​
  • 固定D_0*,计算θ_G1 ← θ_G0 −η(dV(G,D_0*) /dθ_G)以求得更新后的 G_1
  • 固定 G_1​,最大化 V(G_1,D_0*)​ 以求得D_1*​,即 max[JSD(P_{data}(x)||P_{G1}(x)]​
  • 固定 D_1* ,计算θ_{G2} ← θ_{G1} −η(dV(G,D_0*) /dθ_G)以求得更新后的 G_2
  • 。。。

2.实际训练过程

根据前面价值函数 V(G,D) 的定义,我们需要求两个数学期望,即 E[log(D(x))] 和 E[log(1-D(G(z)))],其中 x 服从真实数据分布,z 服从初始化分布。但在实践中,我们是没有办法利用积分求这两个数学期望的,所以一般我们能从无穷的真实数据和无穷的生成器中做采样以逼近真实的数学期望。

若现在给定生成器 G,并希望计算 maxV(G,D) 以求得判别器 D,那么我们首先需要从P_{data}(x)采样 m 个样本{x^1,x^2,…,x^m},从生成器 P_G(x) 采样 m 个样本。

img

因此最大化价值函数 V(G,D) 就可以使用以下表达式近似替代:

img

若我们需要计算上述的极大化过程,可以采用等价形式的训练方法。若我们有一个二元分类器 D(参数为 θ_d​),当然该分类器可以是深度神经网络,那么极大化过程的输出就为该分类器 D(x)。现在我们从 P_{data}(x) ​抽取样本作为正样本,从 P_G(x) ​抽取样本作为负样本,同时将逼近负 V(G,D) 的函数作为损失函数,因此我们就将其表述为一个标准的二元分类器的训练过程:

img

在实践中,我们必须使用迭代和数值计算的方法实现极小极大化博弈过程。在训练的内部循环中完整地优化 D 在计算上是不允许的,并且有限的数据集也会导致过拟合。因此我们可以在 k 个优化 D 的步骤和一个优化 G 的步骤间交替进行。那么我们只需慢慢地更新 G,D 就会一直处于最优解的附近,这种策略类似于 SML/PCD 训练的方式。

综上,我们可以描述整个训练过程,对于每一次迭代:

  • 从真实数据分布 P_{data }抽取 m 个样本
  • 从先验分布 P_{prior}(z) ​抽取 m 个噪声样本
  • 将噪声样本投入 G 而生成数据
img

,通过最大化 V 的近似而更新判别器参数θ_d,即极大化

img

,且判别器参数的更新迭代式为

img

以上是学习判别器 D 的过程。因为学习 D 的过程是计算 JS 散度的过程,并且我们希望能最大化价值函数,所以该步骤会重复 k 次。

  • 从先验分布 P_{prior}(z)中抽取另外 m 个噪声样本{z^1,...,z^m}
  • 通过极小化 V^{tilde} 而更新生成器参数θ_g,即极大化
img

,且生成器参数的更新迭代式为

img

以上是学习生成器参数的过程,这一过程在一次迭代中只会进行一次,因此可以避免更新太多而令 JS 散度上升。

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