生成对抗网络-改进方法|深度学习(李宏毅)(二十四)

视频地址:
①B站:https://www.bilibili.com/video/BV15W411i7uP?p=2
②油管:https://www.youtube.com/watch?v=KSN4QYgAtao

之前的博客地址:
生成对抗网络-基本概念|深度学习(李宏毅)(二十二)
生成对抗网络-理论部分|深度学习(李宏毅)(二十三)

一、GAN的通用框架

  1. f-divergence

之前说GAN的Discriminator的设计与JS散度有关,事实上可以设计Discriminator和任何f-divergence有关。f-divergence的定义如下:

D_{f}(P||Q)=\int _{x}q(x)f\left (\frac{p(x)}{q(x)} \right )\mathrm{d}x\\ s.t.\; \; f是凸函数且f(1)=0

f-divergence衡量分布PQ之间的差异程度,满足在PQ完全一致的情况下取得最小值为0,在有所差异的情况下值为一个正数,这一点可以通过凸函数的性质得到:

PQ完全一致时:

D_{f}(P||Q)=\int _{x}q(x)f\left (\frac{p(x)}{q(x)} \right )\mathrm{d}x=\int _{x}q(x)f\left (1 \right )\mathrm{d}x=0

PQ有差异时:

D_{f}(P||Q)=\int _{x}q(x)f\left (\frac{p(x)}{q(x)} \right )\mathrm{d}x\\ \geq f\left (\int _{x}q(x)\frac{p(x)}{q(x)}\mathrm{d}x \right )\\ =f(1)=0

事实上KL散度就是f-divergence的一个特例:

  • f(x)=xlogx
    D_{f}(P||Q)=\int _{x}q(x)\frac{p(x)}{q(x)}log\left (\frac{p(x)}{q(x)}\right )\mathrm{d}x=\underset{KL}{\underbrace{\int _{x}p(x)log\left (\frac{p(x)}{q(x)} \right )\mathrm{d}x}}

  • f(x)=-logx
    D_{f}(P||Q)=\int _{x}q(x)\left (-log\left (\frac{p(x)}{q(x)} \right )\right )\mathrm{d}x=\underset{Reverse\; KL}{\underbrace{\int _{x}q(x)log\left (\frac{q(x)}{p(x)} \right )\mathrm{d}x}}

  • f(x)=(x-1)^2
    D_{f}(P||Q)=\int _{x}q(x)\left (\frac{p(x)}{q(x)}-1 \right )^{2}\mathrm{d}x=\underset{Chi\; Square}{\underbrace{\int _{x}\frac{\left (p(x)-q(x) \right )^{2}}{q(x)}\mathrm{d}x}}
  1. Fenchel Conjugate

每一个凸函数都有一个共轭函数(Conjugate Function),记作f^*,公式为:

f^{*}(t)=\underset{x\in dom(f)}{max}\left \{xt-f(x)\right \}

这个函数也就是说要找出给定t的情况下变动x能达到的最大值,限制x必须要在f的定义域内,以下图为例,给定不同的t,代入所有的x能得到的最大值就是f^*(t)

共轭

按照上面的方式能够理解前面的公式。那么f^{*}(t)的图像是什么样子的呢?我们可以按照以下方式来考虑,在给定x,使t变动,那么就能得到一条以x为斜率的直线,对于不同的x就对应着不同的直线:

共轭

而此时,要求f^*(t),就只需要从t的位置坐垂线,这样与垂线相交最上面的直线对应的x就是能使得f^*(t)取得最大值的x

共轭

那么在整个t的定义域上,将位于最上面的直线的片段连接起来就是f^*(t)的图像了,当然这只是便于理解的一种形象化的思路:

共轭

按照上面的思路,可以很直观地看出f^*(t)也是凸函数。

另外,我们按照上面的方式把有关f(x)=xlogx的共轭函数的每条直线都画出来:

f(x)=xlogx

得到的共轭函数的图像像是一个指数函数,其实f(x)=xlogx的共轭函数就是exp(t-1),求解共轭函数可以直接用数学的方法计算出来,对于f(x)=xlogx来说,先把f(x)代入前面共轭函数的表达式:

f^{*}(t)=\underset{x\in dom(f)}{max}\left \{xt-xlogx\right \}

对于括号里面的式子g(x)=xt-xlogx,给定t,需要求解x来使最大化g(x),可以直接对x求导,也就有t-logx-1=0,然后解得x=exp(t-1),也就是说,对于给定的t,能使得g(x)最大的x就是exp(t-1),将x代入原来的式子就可以求得共轭函数了:

f^{*}(t)=exp(t-1)t-exp(t-1)log\left (exp(t-1) \right )=exp(t-1)

另外(在满足f(x)是闭凸函数的条件下,这里就不深究了)有f^*(t)f(x)的共轭函数,f(x)也是f^*(t)的共轭函数,也就是说:

(f^*)^*=f

f^*(t)f(x)互为共轭函数:

f^{*}(t)=\underset{x\in dom(f)}{max}\left \{xt-f(x)\right \}\leftrightarrow f^{**}(x)=\underset{t\in dom(f^{*})}{max}\left \{xt-f^{*}(t)\right \}=f(x)

这叫做二次共轭。

  1. 上述内容与GAN的联系

由上面二次共轭的内容,那么对于f-divergence中的凸函数f(x),就可以用f^{**}(x)来替换它:

D_{f}(P||Q)=\int _{x}q(x)f\left (\frac{p(x)}{q(x)} \right )\mathrm{d}x\\ =\int _{x}q(x)\left (\underset{t\in dom(f^{*})}{max}\left \{\frac{p(x)}{q(x)}t-f^{*}(t)\right \} \right )\mathrm{d}x

现在对于任意的一个D(x),它的输入是x,输出是t,将D(x)代入上面的式子中,一定有以下关系:

D_{f}(P||Q)\geq \int _{x}q(x)\left (\frac{p(x)}{q(x)}D(x)-f^{*}(D(x))\right) \mathrm{d}x\\ =\int _{x}p(x)D(x)\mathrm{d}x-\int _{x}q(x)f^{*}(D(x))\mathrm{d}x

上面的不等式是恒成立的,我们也就得到了D_{f}(P||Q)的下界,来调整函数D(x)使得这个下界取得最大值就可以用这个最大值来近似D_{f}(P||Q)

D_{f}(P||Q)\approx \underset{D}{max}\left \{\int _{x}p(x)D(x)\mathrm{d}x-\int _{x}q(x)f^{*}(D(x))\mathrm{d}x\right \}\\ =\underset{D}{max}\left \{E_{x\sim P}[D(x)]-E_{x\sim Q}[f^{*}(D(x))]\right \}

如此我们就得到了D_{f}(P||Q)的关于PQ的期望的形式,也就是说我们现在可以通过从PQ中采样来完成D(x)的求解。在GAN中分布P也就是P_{data},分布Q也就是P_{G},因此也就有:

D_{f}(P_{data}||P_{G})=\underset{D}{max}\left \{E_{x\sim P_{data}}[D(x)]-E_{x\sim P_{G}}[f^{*}(D(x))]\right \}

能使得P_{data}P_{G}D_{f}(P_{data}||P_{G})最小的G也就是最优化的Generator,我们按照以下公式求解Generator:

G^{*}=\underset{G}{argmin}\; D_{f}(P_{data}||P_{G})\\ =\underset{G}{argmin}\; \underset{D}{max}\left \{E_{x\sim P_{data}}[D(x)]-E_{x\sim P_{G}}[f^{*}(D(x))]\right \}\\ =\underset{G}{argmin}\; \underset{D}{max}\; V(G,D)

这样也就回到了我们之前讲过的GAN的目标函数V(G,D),我们之前说过GAN不一定要极小化JS散度,也可以定义不同的V(G,D)来极小化其他散度,而上面的式子的价值就在于提供了一种定义V(G,D)的方法,即事实上GAN可以用任何f-divergence来作为衡量P_{data}P_{G}的差异的度量,求得这种f-divergence的函数的共轭函数f^{*}(t)就可以得到目标函数V(G,D)

在模型能力较差时使用不同的散度可能会有不同的效果,下面展示了使用KL散度和JS散度来拟合数据的效果:

效果对比

图中显示KL散度更倾向于产生多样化的数据,JS散度的分布则比较狭窄,GAN有时会产生比较狭窄的数据,也就是mode collapse,根据上图可以尝试使用KL散度(但事实上也没什么效果)。

参考链接:https://arxiv.org/abs/1606.00709

  1. Single-step Algorithm

在原来讲过的GAN的训练算法中,我们的做法是在每次迭代中先更新Discriminator的参数多次,然后更新Generator的参数一次,这相当于在一个外循环中嵌套了一个内循环。在f-GAN提出了Single-step的训练算法,也就是在每次迭代中Discriminator和Generator的参数只更新一次,并且是在一次反向传播中更新:

\theta _{D}^{t+1}\leftarrow \theta _{D}^{t}+\eta \nabla_{\theta _{D}}V(\theta _{G}^{t},\theta _{D}^{t})\\ \theta _{G}^{t+1}\leftarrow \theta _{G}^{t}-\eta \nabla_{\theta _{G}}V(\theta _{G}^{t},\theta _{D}^{t})

二、Wasserstein GAN

  1. Wasserstein Distance

Wasserstein GAN是一个GAN的变种,在WGAN中度量分布差异程度的标准不再是各种散度,而是wasserstein distance,也叫earth mover's distance。把两个分布比作两堆土,将其中一堆铲成另一堆土所需移动的土的最小平均距离就是wasserstein distance。举例来说,在下图中的一维空间中,两个分布都集中在一个点上,距离为d,将P移动到Q上的wasserstein distance就是W(P,Q)=d

example

更具体的,如下图,对于两个分布PQ,现在要将P移动到Q,存在多种方案,不同的方案就会有不同的距离:

多种方案

不过总是会存在最优的方案,这个最优方案对应的平均移动距离就是wasserstein distance:

最优方案

可以将每一种移动的方案(每一种方案记作\gamma)表示成一个矩阵的形式,这个矩阵上每一个元素代表从P的位置移动到Q的位置的土的量,这里有两个约束就是矩阵中每一行的和等于对应的P位置的概率x_p,每一列的和等于对应的Q位置的概率x_q

moving plan

\gamma(x_p,x_q)表示从x_p的位置移动到x_q的位置的土的量,那么一个方案\gamma对应的平均移动距离就是:

B(\gamma )=\sum _{x_{p},x_{q}}\gamma(x_p,x_q)\left \|x_{p}-x_{q} \right \|

假设移动方案的取值空间是\Pi,那么wasserstein distance就是穷举所有的方案,其中最小距离的方案对应的距离:

W(P,Q)=\underset{\gamma\in \Pi }{min}\; B(\gamma )

仍然使用演化的例子,可以说明wasserstein distance为什么比JS散度更加优越,在下面的例子中,P_G期待一步步训练最终能够与P_{data}重合,使用JS散度对于中间状态来说其值就全部是log2,这样就缺乏促使像好的方向演化的“动力”,而使用wasserstein distance对于中间状态来说就能得到不一样的值:

演化
  1. 目标函数

在原来的GAN中,可以使用下列式子来定义P_GP_{data}之间的f-divergence:

D_{f}(P_{data}||P_{G})=\underset{D}{max}\left \{E_{x\sim P_{data}}[D(x)]-E_{x\sim P_{G}}[f^{*}(D(x))]\right \}

在WGAN中也有类似的式子来定义P_GP_{data}之间的wasserstein distance:

W(P_{data},P_{G})=\underset{D\in 1-Lipschitz}{max}\left \{E_{x\sim P_{data}}[D(x)]-E_{x\sim P_{G}}[D(x)]\right \}

这个式子中D(x)不是任意的,而是一个1-Lipschitz函数,Lipschitz函数的定义如下:

\left \|f(x_{1})-f(x_{2})\right \|\leq K\left \|x_{1}-x_{2}\right \|

上面的式子中表明f(x)的变化小于K倍的x的变化,这样的限制说明Lipschitz函数是一种变化不剧烈的函数,在K=1f(x)就是1-Lipschitz函数。比如下面图中的两个函数,绿色的就是1-Lipschitz函数,而蓝色的不是:

1-Lipschitz函数

现在说明一下为什么要有这个限制呢。还是以一维的分布为例,在下图中的P_GP_{data}之间的距离为d。如果没有1-Lipschitz函数的限制,为了让W(P_{data},P_{G})公式中大括号内的式子最大,只需要D(x_1)=+\infty ,D(x_2)=-\infty

无1-Lipschitz限制

而如果有1-Lipschitz函数的限制的话,那么为了让W(P_{data},P_{G})公式中大括号内的式子最大,只能取得D(x_1)=k+d ,D(x_2)=k

有1-Lipschitz限制

另外一个角度解释wasserstein distance的优势就是,如下图,原来的GAN中D(x)会有一个sigmoid函数,可能最终得到的D(x)的图像就像下图蓝色的线,有一个明显的问题就是有概率存在的地方是没有梯度的,也就是梯度消失问题,这样就没办法让P_{G}有“动力”移动到P_{data},而对于加了1-Lipschitz限制的wasserstein distance,则可能会得到绿色的线,这样就会有一个梯度来促使P_{G}有“动力”移动到P_{data}

梯度
  1. 算法

我们想要求解一个1-Lipschitz函数D(x),来让W(P_{data},P_{G})公式中大括号内的式子最大,但是如果直接微分来做的话就不满足1-Lipschitz函数的限制,在实际中的做法是使用weight clipping的做法,也就是强制Discriminator的参数wc-c之间:

After parameter update, if w>c,then w=c; if w<-c, then w=-c.

Weight clipping的做法并不能保证满足D(x)是1-Lipschitz函数的限制,但是可以保证D(x)是K-Lipschitz,不过并没有关系,因为按照这样的方法求解出来的最大值是与wasserstein distance成正比的:

{\color{Red}K}W(P_{data},P_{G})=\underset{D\in {\color{Red}K}-Lipschitz}{max}\left \{E_{x\sim P_{data}}[D(x)]-E_{x\sim P_{G}}[D(x)]\right \}

如果不做weight clipping的话还是会使得D(x)没有限制,会产生下图中的后果,而如果做了weight clipping就会使D(x)有一定限制,从而可以使D(x)有一定的梯度来促使P_{G}有“动力”移动到P_{data}

weight clipping

不过要注意的是,使用weight clipping以后只能保证D(x)满足K-Lipschitz的限制,而不能保证所有满足K-Lipschitz的限制的D(x)对应参数w都在c-c之间,也就是说不满足在c-c之间的某些参数w也是有可能满足K-Lipschitz的。有关weight clipping的方法就不再深究,因为后面会介绍更加合适的训练方法。

WGAN训练的算法与原来的GAN的训练有很多相似之处,只需要做一点改动即可:

Initialize:
  初始化D的参数\theta _{d}G的参数\theta _{g}


Step1 学习D
  ①从数据库中随机抽样m个样本\left \{x^{1},x^{2},\cdots ,x^{m}\right \}
  ②从一个分布(比如高斯分布或者均匀分布)中采样m个噪声样本(noise sample)\left \{z^{1},z^{2},\cdots ,z^{m}\right \}
  ③获得G生成的数据\left \{\tilde{x}^{1},\tilde{x}^{2},\cdots ,\tilde{x}^{m}\right \},其中\tilde{x}^{i}=G(z^{i})
  ④目标函数记作\tilde{V},通过最大化(梯度上升)\tilde{V}来更新\theta _{d},也就是\theta _{d}\leftarrow \theta _{d}+\eta \nabla \tilde{V}(\theta _{d}),目标函数\tilde{V}为:
{\color{Red}{\tilde{V}=\frac{1}{m}\sum_{i=1}^{m}D(x^{i})-\frac{1}{m}\sum_{i=1}^{m}D(\tilde{x}^{i})}}
  ⑤{\color{Red}{\mathrm{weight\;clipping}}}


Step2 学习G
  ①同样从一个分布(比如高斯分布或者均匀分布)中采样m个噪声样本(noise sample)\left \{z^{1},z^{2},\cdots ,z^{m}\right \}
  ②目标函数同样记作\tilde{V},通过最小化(梯度下降)\tilde{V}来更新\theta _{g},也就是\theta _{g}\leftarrow \theta _{g}-\eta \nabla \tilde{V}(\theta _{g}),目标函数\tilde{V}为:
{\color{Red}{\tilde{V}=-\frac{1}{m}\sum_{i=1}^{m}D(G(z^{i})}}

同样地每次迭代中Step1重复多次,Step2重复一次,不同的是WGAN的D(x)不会在最后一层加一个sigmoid函数了。

  1. 其他issue

这里对比了WGAN和原来的GAN的效果的对比图,可以看到WGAN生成的效果是明显要好一些的:

效果对比

还有另一点要说的是在WGAN中Discriminator的loss是可以用来衡量P_GP_{data}之间的差异的,这是因为Discriminator的loss代表的是wasserstein distance,而原来的GAN的Discriminator的loss是不可以用来衡量分布之间的差异的(原因之前说过了:1.采样;2.数据是高维空间中的低维流形)。这里放一些效果图,在下面的图中可以看出随着wasserstein distance(也就是Discriminator的loss)的下降,生成的效果是越来越好的:

效果

这一点的意义就在于以前我们没有一个衡量的标准来说明一个Generator生成的对象好还是不好(虽然图像好坏可以肉眼大概评估一下,但是一些其他的对象可能就很难衡量了),但是现在我们可以通过Discriminator的loss表示的wasserstein distance来作为一个评估的手段了。

WGAN论文链接:https://arxiv.org/abs/1701.07875

三、Improved WGAN

  1. Gradient Penalty

对于1-Lipschitz函数,存在以下等价关系:

D\in 1-Lipschitz\Leftrightarrow \left \|\nabla_{x}D(x)\right \|\leq 1\; for\; all\; x

也就是说满足1-Lipschitz的D(x)x的梯度总是小于等于1的,受这一点的启发,我们可以修改WGAN的目标函数如下:

W(P_{data},P_{G})\approx {\color{Red}{\underset{D}{max}}}\left \{E_{x\sim P_{data}}[D(x)]-E_{x\sim P_{G}}[D(x)]{\color{Red}{-\lambda \int _{x}max(0,\nabla_{x}D(x)-1)\mathrm{d}x}}\right \}

上面的式子在\nabla_{x}D(x)>1时会产生loss,调节\lambda的大小可以使得D(x)x的梯度尽可能地小于等于1。使用这种损失函数就可以舍弃weight clipping的做法,不再需要对D做限制也可以学习到一个能够尽可能满足1-Lipschitz的限制的D

在实际操作中我们不可能对所有x进行积分,因此我们要改写上面的惩罚项为一个依某种惩罚分布P_{penalty}的期望,这样就可以使用采样的方式来计算了:

W(P_{data},P_{G})\approx \underset{D}{max}\left \{E_{x\sim P_{data}}[D(x)]-E_{x\sim P_{G}}[D(x)]{\color{Red}{-\lambda E_{x\sim P_{penalty}}[max(0,\nabla_{x}D(x)-1)]}}\right \}

直觉上这个P_{penalty}应该是个均匀分布,但事实上在Improved WGAN的论文中对P_{penalty}有特殊的设计。具体的,得到一个采样样本的过程如下图所示,首先从P_{data}抽取一个点,然后从P_{G}抽取另一个点,然后连接起来得到一条线,线段的中间就是采样点:

penalty分布

原论文中解释这样的分布的用意在于,如果我们想要让所有的x都满足1-Lipschitz,这是intractable的,如果只让这P_{data}P_{G}之间的x满足1-Lipschitz的话是可以实现的而且是比较合理的,因为这个区域内的点影响P_{G}如何移向P_{data},并且实验中取得不错的效果。

另外实际上论文中使用的损失函数也不是上面那一个,而是下面这个:

W(P_{data},P_{G})\approx \underset{D}{max}\left \{E_{x\sim P_{data}}[D(x)]-E_{x\sim P_{G}}[D(x)]{\color{Red}{-\lambda E_{x\sim P_{penalty}}[(\nabla_{x}D(x)-1)^{2}]}}\right \}

也就是说在实践中并非要让\nabla_{x}D(x)尽可能地小,而是要让P_{data}P_{G}之间区域的xD(x)的梯度,尽可能地接近1。这样的做法在直观上是很容易解释的,由损失函数可以看出我们期待要让P_{data}分布中的xD(x)尽可能大,而要让P_{G}分布中的xD(x)尽可能小,那么中间区域的xD(x)的梯度就越大越好,同时\nabla_{x}D(x)又要是小于等于1的,因此最终解出来的最优的D(x)x的梯度总是倾向于1

梯度接近1

仅仅惩罚大梯度也是可以work的,不过上面的方式在实验中收敛地更快且效果更好。

  1. 对比实验

下面的图对比了weight clipping和gradient penalty中的权重的分布,发现weight clipping的权重大多集中在clipping的位置:

对比

下面的图对比了使用weight clipping和gradient penalty去拟合黄点数据分布的效果,明显gradient penalty效果好一些:

对比

下面的图对比了不同GAN的效果,发现Improved WGAN的效果是最好的:

对比
对比
  1. 文本生成

将句子按照独热编码的方式表示成一个矩阵就可以用来做句子的生成,由于句子长短不一,可以添加一个使用一个null的token来填补空白位置:

句子的表示

对于GAN产生的句子,其中很难产生1,而是对于一个词来说一般都是一个大的数,伴随一些接近于0的数,导致生成的数据和真实的数据是没有重叠的,因而使用传统的GAN的话,量出来的JS散度永远都是log2,而WGAN就可以解决这个问题,使用wasserstein distance来度量差异的话就是比较合理的:

问题

下面展示一些文本生成的结果:

文本生成

还可以用来做唐诗的生成:

文本生成

Improved WGAN的论文链接:https://arxiv.org/abs/1704.00028

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

推荐阅读更多精彩内容