Bert模型解读-2

一 前言

从现在的大趋势来看,使用某种模型预训练一个语言模型看起来是一种比较靠谱的方法。从之前AI2的 ELMo,到 OpenAI的fine-tune transformer,再到Google的这个BERT,全都是对预训练的语言模型的应用。

BERT这个模型与其它两个不同的是

1)它在训练双向语言模型时以减小的概率把少量的词替成了Mask或者另一个随机的词。我个人感觉这个目的在于使模型被迫增加对上下文的记忆。至于这个概率,我猜是Jacob拍脑袋随便设的。

2)增加了一个预测下一句的loss。这个看起来就比较新奇了。

BERT模型具有以下两个特点:

第一,是这个模型非常的深,12层,并不宽(wide),中间层只有1024,而之前的Transformer模型中间层有2048。这似乎又印证了计算机图像处理的一个观点——深而窄 比 浅而宽 的模型更好。

第二,MLM(Masked Language Model),同时利用左侧和右侧的词语,这个在ELMo上已经出现了,绝对不是原创。其次,对于Mask(遮挡)在语言模型上的应用,已经被Ziang Xie提出了:[1703.02573] Data Noising as Smoothing in Neural Network Language Models。这也是篇巨星云集的论文:Sida Wang,Jiwei Li(香侬科技的创始人兼CEO兼史上发文最多的NLP学者),Andrew Ng,Dan Jurafsky都是Coauthor。但很可惜的是他们没有关注到这篇论文。用这篇论文的方法去做Masking,相信BRET的能力说不定还会有提升。

二、如何理解BERT模型

[1] BERT 要解决什么问题?

通常情况 transformer 模型有很多参数需要训练。譬如 BERT BASE 模型: L=12, H=768, A=12, 需要训练的模型参数总数是 12 * 768 * 12 = 110M。这么多参数需要训练,自然需要海量的训练语料。如果全部用人力标注的办法,来制作训练数据,人力成本太大。

受《A Neural Probabilistic Language Model》论文的启发,BERT 也用 unsupervised 的办法,来训练 transformer 模型。神经概率语言模型这篇论文,主要讲了两件事儿,1. 能否用数值向量(word vector)来表达自然语言词汇的语义?2. 如何给每个词汇,找到恰当的数值向量?

这篇论文写得非常精彩,深入浅出,要言不烦,而且面面俱到。经典论文,值得反复咀嚼。很多同行朋友都熟悉这篇论文,内容不重复说了。常用的中文汉字有 3500 个,这些字组合成词汇,中文词汇数量高达 50 万个。假如词向量的维度是 512,那么语言模型的参数数量,至少是 512 * 50万 = 256M。

模型参数数量这么大,必然需要海量的训练语料。从哪里收集这些海量的训练语料?《A Neural Probabilistic Language Model》这篇论文说,每一篇文章,天生是训练语料。难道不需要人工标注吗?回答,不需要。


我们经常说,“说话不要颠三倒四,要通顺,要连贯”,意思是上下文的词汇,应该具有语义的连贯性。基于自然语言的连贯性,语言模型根据前文的词,预测下一个将出现的词。如果语言模型的参数正确,如果每个词的词向量设置正确,那么语言模型的预测,就应该比较准确。天下文章,数不胜数,所以训练数据,取之不尽用之不竭。

深度学习四大要素,1. 训练数据、2. 模型、3. 算力、4. 应用。训练数据有了,接下去的问题是模型。

【2】BERT的五个关键词:pre-training, Deep, Bidirectional , Transformert, Language Understanding分别是什么意思?

《A Neural Probabilistic Language Model》这篇论文讲的 Language Model,严格讲是语言生成模型(Language Generative Model),预测语句中下一个将会出现的词汇。语言生成模型能不能直接移用到其它 NLP 问题上去?

譬如,淘宝上有很多用户评论,能否把每一条用户转换成评分?-2、-1、0、1、2,其中 -2 是极差,+2 是极好。假如有这样一条用户评语,“买了一件鹿晗同款衬衫,没想到,穿在自己身上,不像小鲜肉,倒像是厨师”,请问这条评语,等同于 -2,还是其它?

语言生成模型,能不能很好地解决上述问题?进一步问,有没有 “通用的” 语言模型,能够理解语言的语义,适用于各种 NLP 问题?BERT 这篇论文的题目很直白,《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》,一眼看去,就能猜得到这篇文章会讲哪些内容。

这个题目有五个关键词,分别是 Pre-training、Deep、Bidirectional、Transformers、和 Language Understanding。其中 pre-training 的意思是,确实存在通用的语言模型,先用文章预训练通用模型,然后根据具体应用,用supervised 训练数据,精加工(fine tuning)模型,使之适用于具体应用。为了区别于针对语言生成的Language model,作者给通用的语言模型取了个名字,叫做语言表征模型(Language Representation Model)。

能实现语言表征目标的模型,可能会有很多种,具体用哪一种呢?作者提议,用 Deep Bidirectional Transformers 模型。假如给一个句子 “能实现语言表征[mask]的模型”,遮盖住其中“目标”一词。从前往后预测[mask],也就是用“能/实现/语言/表征”,来预测[mask];或者,从后往前预测[mask],也就是用“模型/的”,来预测[mask],称之为单向预测 unidirectional。单向预测,不能完整地理解整个语句的语义。于是研究者们尝试双向预测。把从前往后,与从后往前的两个预测,拼接在一起 [mask1/mask2],这就是双向预测 bi-directional。细节参阅《Neural Machine Translation by Jointly Learning to Align and Translate》。

BERT的作者认为,bi-directional 仍然不能完整地理解整个语句的语义,更好的办法是用上下文全向来预测[mask],也就是用“能/实现/语言/表征/../的/模型”, 来预测[mask]。 BERT作者把上下文全向的预测方法,称之为deep bi-directional。 如何来实现上下文全向预测呢?BERT的作者建议使用Transformer模型,这个模型由《Attention Is All You Need》一文发明。

这个模型的核心是聚焦机制,对于一个语句,可以同时启用多个聚焦点,而不必局限于从前往后的,或者从后往前的,序列串处理。不仅要正确地选择模型的结构,而且还要正确地训练模型的参数,这样才能保障模型能够准确地理解语句的语义。BERT用了两个步骤,试图去正确地训练模型的参数。第一步是把一篇文章中,15%的词汇遮盖,让模型根据上下文全向地预测被遮盖的词。假如有1万篇文章,每篇文章平均有100个词汇,随机遮盖15%的词汇,模型的任务是正确地预测这15万个被遮盖的词汇。通过全向预测被遮盖的词汇,来初步训练Transformer模型的参数。然后,用第二步继续训练模型的参数。譬如从上述1万篇文章中,挑选20万对语句,总共40万条语句。挑选语句对的时候,其中2*10万对语句是连续的两条上下文语句,另外2*10万对语句不是连续的语句,然后让Transformer模型来识别这20万对语句,哪些是连续的,哪些不连续。

这两步训练结合在一起,称之为预训练pre-training. 训练结束后的Transformer模型,包括它的参数,是作者期待的通用的语言表征模型。

三、BERT模型解析

首先来看下谷歌AI团队做的这篇论文。

BERT的新语言表示模型,代表Transformer的双向编码器表示。与最近的其他语言表示模型不同,BERT旨在通过联合调节所有层中的上下文来预先训练深度双向表示。因此预训练的BERT表示可以通过一个额外的输出层进行微调,适用于广泛任务的最先进模型的构建,比如问答任务和语言推理,无需针对具体任务做大幅构架修改。

论文作者认为现有的技术严重制约了预训练表示的能力,其主要局限在于标准语言模型是单向的,这使得在模型的预训练中可以使用的架构类型很有限。

在论文中,作者通过提出BERT:即Transformer的双向编码来改进基于架构微调的方法。

BERT提出一种新的预训练目标:遮蔽语言模型(masked language model, MLM),来克服上文提到的单向性局限,MLM的灵感来自Cloze任务。MLM随机遮蔽模型输入中的一些token,目标在于仅基于遮蔽词的语境来预测其原始词汇id.

与从左到右的语言模型预训练不同,MLM目标允许表征融合左右两侧的语境,从而预训练一个深度双向Transformer, 除了遮蔽语言模型之外,本文作者还引入了一个‘下一句预测’(next sentence prediction)任务,可以和MLM共同预训练文本对的表示。

论文主要贡献在于:

1) 证明了双向预训练对语言表示的重要性。与之前使用的单向语言模型进行预训练不同,BERT使用遮蔽语言模型来实现预训练的深度双向表示。

2)论文表明,预训练的表示免去了许多工程任务需要针对特定任务修改体系架构的需求。BERT是第一个基于微调的表示模型,它在大量的句子级和token 级任务上实现了最先进的性能,强于许多面向特定任务体系架构的系统。

3) BERT 刷新了11项NLP任务性能记录,本文还报告了BERT的模型简化研究(ablation study),表明模型的双向性是一项重要的新成果。相关代码和预训练模型都已经公布。

BERT目前已经刷新的11项自然语言处理任务的最新记录包括:将GLUE基准推至80.4%(绝对改进7.6%),MultiNLI准确度达到86.7% (绝对改进率5.6%),将SQuAD v1.1问答测试F1得分纪录刷新为93.2分(绝对提升1.5分),超过人类表现2.0分。

论文的核心:详解BERT模型架构

本节介绍BERT模型架构和具体实现,并介绍预训练任务,这是这篇论文的核心创新。

模型架构

BERT的模型架构是基于Vaswani et al. (2017) 中描述的原始实现multi-layer bidirectional Transformer编码器,并在tensor2tensor库中发布。由于Transformer的使用最近变得无处不在,论文中的实现与原始实现完全相同,因此这里将省略对模型结构的详细描述。

在这项工作中,论文将层数(即Transformer blocks)表示为L, 将隐藏大小表示为H,将Self-attention heads 的数量表示为A,在所有情况下,将feed-forward/filter的大小设置为4H,即H=768时 为3072,H=1024时 为4096。 论文主要展示了两种模型大小的结果:

BERT_base: L=12, H=768, A=12, Total Parameters = 110M

BERT_Large: L = 24, H = 1024,A=16, Total Parameters = 340M

为了进行比较,论文选择了BERT_large, 它与OpenAI GPT 具有相同的模型大小,然而,重要的是BERT Transformer 使用双向self-attention, 而GPT Transformer 使用受限制的self-attention,其中每个token只能处理其左侧的上下文。研究团队注意到,在文献中,双向Transformer 通常被称为‘Transformer encoder’, 而左侧上下文被称为‘Transformer decoder’.因为它可用于文本生成,BERT,OpenAI GPT和Elmo之间的比较如图1所示。

图1 :不同的预训练模型结构。BERT使用双向Transformer。OpenAI GPT使用从左到右的Transformer。ELMo使用独立的从左到右与从右到左的LSTM然后拼接作为下游任务的特征。所以其中只有BERT的表示是联合了从左到右与从右到左的上下文

图1:预训练模型架构的差异。BERT使用双向Transformer。OpenAI GPT使用从左到右的Transformer。ELMo使用经过独立训练的从左到右和从右到左LSTM的串联来生成下游任务的特征。三个模型中,只有BERT表示在所有层中共同依赖于左右上下文。

BERT 模型框架( 根据OpenAI GPT的架构图做的改动,以便读者更清楚的了解整个过程 )


整体分为两个过程:1.预训练过程(左边图)预训练过程是一个multi-task learning,迁移学习的任务,目的是学习输入句子的向量。2微调过程(右边图)可基于少量监督学习样本,加入Feedword神经网络,实现目标。因为微调阶段学习目标由简单的feedward神经网络构成,且用少量标注样本,所以训练时间短。

输入表示

论文输入表示(input representation)能够在一个token序列中明确地表示单个文本句子或一对文本句子(例如:[question, answer])。对于给定token,其输入表示通过对相应的token, segment 和positional embeddings 进行求和来构造。 图2是输入表示的直观表示:

图2:BERT输入表示。输入嵌入是token embeddings, segmentation embeddings和position emebeddings的总和。

BERT 输入表示

具体如下:

1)使用WordPiece嵌入和30,000个token 的词汇表,用##表示分词。
2)使用学习的positional embeddings, 支持的序列长度最多为512个token.
3)  每个序列的第一个token始终是特殊分类嵌入([CLS]),对应于该token的最终隐藏状态(即Transformer的输出)被用作分类任务的聚合序列表示。对于非分类任务,将忽略此向量。
4) 句子对被打包成一个序列,以两种方式区分句子,首先,用特殊标记([SEP])将它们分开,其次,添加一个learned sentence A 嵌入到第一个句子的每一个token中,一个sentence B 嵌入到第二个句子的每个token中。
5)对于单个句子输入,只是用sentenceA 嵌入。

关键创新:预训练任务

与Peters el al.(2018)和Radford et al.(2018)不同,论文不使用传统的从左到右或者从右到左的语言模型来预测BERT,相反,使用两个新的无监督预测任务对BERT进行预训练。

任务1: Masked LM

从直觉上看,研究团队有理由相信,深度双向模型比elft-to-right模型或者right-to-left模型的浅层连接更强大。遗憾的是,标准条件语言模型只能从左到右或者从右到左训练,因为双向条件作用将允许每个单词在多层上下文中 间接地‘see itself’.

为了训练一个深度双向表示(deep bidirectional representiation),研究团队采用了一种简单的方法,即随机屏蔽(masking)部分输入token, 然后只预测那些被屏蔽的token.论文也将这个过程称为‘masked LM’(MLM),尽管在文献中它经常被称为Cloze任务(Taylor, 1953)

在这个例子中,与masked token对应的最终隐藏向量被输入到词汇表上的输出softmax中,就像在标准LM中一样。在团队所有实验中,随机屏蔽了每个序列中的15%的wordpiece token.  与去噪的自动编码器相反,只预测masked words而不是重建整个输入。

虽然这确实能让团队获得双向预训练模型,但是这种方法有两个缺点。首先,预训练和finetuning之间不匹配,因为在fine tuning期间从未看到[mask] token,为了解决这个问题,团队并不能总是用实际的[MASK] token 替换被 ‘masked’的词汇。相反,训练数据生成器随机选择15%的token, 例如在这个句子‘my dog is hairy’中,它选择的token是‘hairy’,然后执行以下过程:

数据生成器将执行以下操作,而不是始终使用[MASK]替换所有单词:
1)80%的时间:用[MASK]标记替换单词,例如,my dog is hairy → my dog is [MASK]
2)10%的时间:用一个随机的单词替换该单词,例如,my dog is hairy → my dog is apple
3)10%的时间:保持单词不变,例如,my dog is hairy → my dog is hairy. 这样做的目的是将表示偏向于实际观察到的单词。

Transformer encoder 不知道它将被要求预测哪些单词或哪些单词已被随机单词替换,因此它被迫保持每个输入token的分布式上下文表示。此外,因为随机替换只发生在所有tokende 1.5%(15%的10%),这似乎不会损害模型的语言理解能力

使用MLM的第二个缺点是每个batch只预测了15%的token, 这表明模型可能需要更多的预训练步骤才能收敛。团队证明MLM的收敛速度略慢于 left-to-right的模型(预测每个token),但MLM模型在实验上获得的提升远远超过增加的训练成本。

任务2:下一句预测

许多重要的下游任务,如问答(QA)和自然语言推理(NLI)都是基于理解两个句子之间关系,这并没有通过语言模型直接获得。

在为了训练一个理解句子的模型关系,预先训练一个二进制华的下一句预测任务,这一任务可以从任何单语句语料库中生成。具体地说:当选择句子A和B作为预训练样本时,B 有50%的可能是A的下一个句子,也有50%的可能是来自语料库的随机句子。例如:

Input = [CLS] the man went to [MASK] store [SEP]

he bought a gallon [MASK] milk [SEP]

Label = IsNext

Input = [CLS] the man [MASK] to the store [SEP]

penguin [MASK] are flight ##less birds [SEP]

Label = NotNext

团队完全随机地选择了NotNext语句,最终的预训练模型在此任务上实现了97%-98%的准确率。

预训练阶段参数

(1)256个句子作为一个batch,每个句子最多512个token。
(2)迭代100万步。
(3)总共训练样本超过33亿。
(4)迭代40个epochs。
(5)用adam学习率, 1 = 0.9, 2 = 0.999。
(6)学习率头一万步保持固定值,之后线性衰减。
(7)L2衰减,衰减参数为0.01。
(8)drop out设置为0.1。
(9)激活函数用GELU代替RELU。
(10)Bert base版本用了16个TPU,Bert large版本用了64个TPU,训练时间4天完成。

(论文定义了两个版本,一个是base版本,一个是large版本。Large版本(L=24, H=1024, A=16, Total Parameters=340M)。base版本( L=12, H=768, A=12, Total Pa- rameters=110M)。L代表网络层数,H代表隐藏层数,A代表self attention head的数量。)

微调阶段

微调阶段根据不同任务使用不同网络模型。在微调阶段,大部分模型的超参数跟预训练时差不多,除了batchsize,学习率,epochs。

训练参数:

Batch size: 16, 32

Learning rate (Adam): 5e-5, 3e-5, 2e-5

Number of epochs: 3, 4

实验结果

如前文所述,BERT在11项NLP任务中刷新了性能表现记录!在这一节中,团队直观呈现BERT在这些任务的实验结果,具体的实验设置和比较请阅读原论文.

图3

图3:我们的面向特定任务的模型是将BERT与一个额外的输出层结合而形成的,因此需要从头学习最小数量的参数。在这些任务中,a和b是序列级任务,而c和d是token级任务。在图中, E表示输入嵌入,Ti表示tokeni的上下文表示,[CLS]是用于分类输出的特殊符号,[SEP]是用于分隔非连续token序列的特殊符号。

图4

图4: GLUE 测试结果,由GLUE评估服务器给出。每个任务下方的数字表示训练样例的数量。‘平均’一栏中的数据与GLUE官方评分稍有不同,因为我们排除了有问题的WNL集。BERT和OpenAI GPT 的结果是单模型,单任务下的数据,所有结果来自于https://gluebenchmark.com/leaderboardhttps://blog.openai.com/language-unsupervised/



图5

图5:SQuAD结果。BERT集成是使用不同训练检查点和fine-tuning  seed的7X系统。


图6

图6: CoNLL-2003命名实体识别结果。超参数又开发集选择,得出的开发和测试分数是使用这些超参数进行五次随机重启的平均值。

四,BERT 模型的影响

BERT是一个语言表征模型(language representation model),通过超大数据、巨大模型、和极大的计算开销训练而成,在11个自然语言处理的任务中取得了最优(state-of-the-art, SOTA)结果。或许你已经猜到了此模型出自何方,没错,它产自谷歌。估计不少人会调侃这种规模的实验已经基本让一般的实验室和研究员望尘莫及了,但它确实给我们提供了很多宝贵的经验:

1)深度学习就是表征学习 (Deep learning is representation learning):"We show that pre-trained representations eliminate the needs of many heavily engineered task-specific architectures". 在11项BERT刷出新境界的任务中,大多只在预训练表征(pre-trained representation)微调(fine-tuning)的基础上加一个线性层作为输出(linear output layer)。在序列标注的任务里(e.g. NER),甚至连序列输出的依赖关系都先不管(i.e. non-autoregressive and no CRF),照样秒杀之前的SOTA,可见其表征学习能力之强大。

2)规模很重要(Scale matters):"One of our core claims is that the deep bidirectionality of BERT, which is enabled by masked LM pre-training, is the single most important improvement of BERT compared to previous work". 这种遮挡(mask)在语言模型上的应用对很多人来说已经不新鲜了,但确是BERT的作者在如此超大规模的数据+模型+算力的基础上验证了其强大的表征学习能力。这样的模型,甚至可以延伸到很多其他的模型,可能之前都被不同的实验室提出和试验过,只是由于规模的局限没能充分挖掘这些模型的潜力,而遗憾地让它们被淹没在了滚滚的paper洪流之中。

3)预训练价值很大(Pre-training is important):"We believe that this is the first work to demonstrate that scaling to extreme model sizes also leads to large improvements on very small-scale tasks, provided that the model has been sufficiently pre-trained". 预训练已经被广泛应用在各个领域了(e.g. ImageNet for CV, Word2Vec in NLP),多是通过大模型大数据,这样的大模型给小规模任务能带来的提升有几何,作者也给出了自己的答案。BERT模型的预训练是用Transformer做的,但我想换做LSTM或者GRU的话应该不会有太大性能上的差别,当然训练计算时的并行能力就另当别论了。

对BERT模型的观点

0. high-performance的原因其实还是归结于两点,除了模型的改进,更重要的是用了超大的数据集(BooksCorpus 800M + English Wikipedia 2.5G单词)和超大的算力(对应于超大模型)在相关的任务上做预训练,实现了在目标任务上表现的单调增长

1. 这个模型的双向和Elmo不一样,大部分人对他这个双向在novelty上的contribution 的大小有误解,我觉得这个细节可能是他比Elmo显著提升的原因。Elmo是拼一个左到右和一个右到左,他这个是训练中直接开一个窗口,用了个有顺序的cbow。

2. 可复现性差:有钱才能为所欲为(Reddit对跑一次BERT的价格讨论)

参考文献:

1.  知乎:如何评价谷歌最新的BERT模型

2. https://gluebenchmark.com/leaderboard

3. https://blog.openai.com/language-unsupervised/


转自: https://blog.csdn.net/qq_39521554/article/details/83062188

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

推荐阅读更多精彩内容