推薦系統(tǒng)遇上深度學(xué)習(xí)(二十四)--深度興趣進(jìn)化網(wǎng)絡(luò)DIEN原理及實(shí)戰(zhàn)!

在本系列的第十八篇(http://www.lxweimin.com/p/73b6f5d00f46)中,我們介紹了阿里的深度興趣網(wǎng)絡(luò)(Deep Interest Network,以下簡(jiǎn)稱DIN),時(shí)隔一年,阿里再次升級(jí)其模型,提出了深度興趣進(jìn)化網(wǎng)絡(luò)(Deep Interest Evolution Network,以下簡(jiǎn)稱DIEN,論文地址:https://arxiv.org/pdf/1809.03672.pdf),并將其應(yīng)用于淘寶的廣告系統(tǒng)中,獲得了20.7%的CTR的提升。本篇,我們一同來(lái)探秘DIEN的原理及實(shí)現(xiàn)。

1、背景

在大多數(shù)非搜索電商場(chǎng)景下,用戶并不會(huì)實(shí)時(shí)表達(dá)目前的興趣偏好。因此通過設(shè)計(jì)模型來(lái)捕獲用戶的動(dòng)態(tài)變化的興趣,是提升CTR預(yù)估效果的關(guān)鍵。阿里之前的DIN模型將用戶的歷史行為來(lái)表示用戶的興趣,并強(qiáng)調(diào)了用戶興趣的多樣性和動(dòng)態(tài)變化性,因此通過attention-based model來(lái)捕獲和目標(biāo)物品相關(guān)的興趣。雖然DIN模型將用戶的歷史行為來(lái)表示興趣,但存在兩個(gè)缺點(diǎn):
1)用戶的興趣是不斷進(jìn)化的,而DIN抽取的用戶興趣之間是獨(dú)立無(wú)關(guān)聯(lián)的,沒有捕獲到興趣的動(dòng)態(tài)進(jìn)化性
2)通過用戶的顯式的行為來(lái)表達(dá)用戶隱含的興趣,這一準(zhǔn)確性無(wú)法得到保證。

基于以上兩點(diǎn),阿里提出了深度興趣演化網(wǎng)絡(luò)DIEN來(lái)CTR預(yù)估的性能。DIEN模型的主要貢獻(xiàn)點(diǎn)在于:
1)模型關(guān)注電商系統(tǒng)中興趣演化的過程,并提出了新的網(wǎng)絡(luò)結(jié)果來(lái)建模興趣進(jìn)化的過程,這個(gè)模型能夠更精確的表達(dá)用戶興趣,同時(shí)帶來(lái)更高的CTR預(yù)估準(zhǔn)確率。
2)設(shè)計(jì)了興趣抽取層,并通過計(jì)算一個(gè)輔助loss,來(lái)提升興趣表達(dá)的準(zhǔn)確性。
3)設(shè)計(jì)了興趣進(jìn)化層,來(lái)更加準(zhǔn)確的表達(dá)用戶興趣的動(dòng)態(tài)變化性。

接下來(lái),我們來(lái)一起看一下DIEN模型的原理。

2、DIEN模型原理

2.1 模型總體結(jié)構(gòu)

我們先來(lái)對(duì)比一下DIN和DIEN的結(jié)構(gòu)。
DIN的模型結(jié)構(gòu)如下:

DIN

DIEN的模型結(jié)構(gòu)如下:

DIEN

可以看到,DIN和DIEN的最底層都是Embedding Layer,User profile, target AD和context feature的處理方式是一致的。不同的是,DIEN將user behavior組織成了序列數(shù)據(jù)的形式,并把簡(jiǎn)單的使用外積完成的activation unit變成了一個(gè)attention-based GRU網(wǎng)絡(luò)。

2.2 興趣抽取層Interest Extractor Layer

興趣抽取層Interest Extractor Layer的主要目標(biāo)是從embedding數(shù)據(jù)中提取出interest。但一個(gè)用戶在某一時(shí)間的interest不僅與當(dāng)前的behavior有關(guān),也與之前的behavior相關(guān),所以作者們使用GRU單元來(lái)提取interest。GRU單元的表達(dá)式如下:

GRU表達(dá)式

這里我們可以認(rèn)為ht是提取出的用戶興趣,但是這個(gè)地方興趣是否表示的合理呢?文中別出心裁的增加了一個(gè)輔助loss,來(lái)提升興趣表達(dá)的準(zhǔn)確性:

這里,作者設(shè)計(jì)了一個(gè)二分類模型來(lái)計(jì)算興趣抽取的準(zhǔn)確性,我們將用戶下一時(shí)刻真實(shí)的行為e(t+1)作為正例,負(fù)采樣得到的行為作為負(fù)例e(t+1)',分別與抽取出的興趣h(t)結(jié)合輸入到設(shè)計(jì)的輔助網(wǎng)絡(luò)中,得到預(yù)測(cè)結(jié)果,并通過logloss計(jì)算一個(gè)輔助的損失:

2.3 興趣進(jìn)化層Interest Evolution Layer

興趣進(jìn)化層Interest Evolution Layer的主要目標(biāo)是刻畫用戶興趣的進(jìn)化過程。舉個(gè)簡(jiǎn)單的例子:

以用戶對(duì)衣服的interest為例,隨著季節(jié)和時(shí)尚風(fēng)潮的不斷變化,用戶的interest也會(huì)不斷變化。這種變化會(huì)直接影響用戶的點(diǎn)擊決策。建模用戶興趣的進(jìn)化過程有兩方面的好處:
1)追蹤用戶的interest可以使我們學(xué)習(xí)final interest的表達(dá)時(shí)包含更多的歷史信息。
2)可以根據(jù)interest的變化趨勢(shì)更好地進(jìn)行CTR預(yù)測(cè)。

而interest在變化過程中遵循如下規(guī)律:
1)interest drift:用戶在某一段時(shí)間的interest會(huì)有一定的集中性。比如用戶可能在一段時(shí)間內(nèi)不斷買書,在另一段時(shí)間內(nèi)不斷買衣服。
2)interest individual:一種interest有自己的發(fā)展趨勢(shì),不同種類的interest之間很少相互影響,例如買書和買衣服的interest基本互不相關(guān)。

為了利用這兩個(gè)時(shí)序特征,我們需要再增加一層GRU的變種,并加上attention機(jī)制以找到與target AD相關(guān)的interest。

attention的計(jì)算方式如下:

而Attention和GRU結(jié)合起來(lái)的機(jī)制有很多,文中介紹了一下三種:

GRU with attentional input (AIGRU)
這種方式將attention直接作用于輸入,無(wú)需修改GRU的結(jié)構(gòu):

Attention based GRU(AGRU)
這種方式需要修改GRU的結(jié)構(gòu),此時(shí)hidden state的輸出變?yōu)椋?/p>

GRU with attentional update gate (AUGRU)
這種方式需要修改GRU的結(jié)構(gòu),此時(shí)hidden state的輸出變?yōu)?

2.4 模型試驗(yàn)

文章在公共數(shù)據(jù)和自己的數(shù)據(jù)集上都做了實(shí)驗(yàn),并選取了不同的對(duì)比模型:

離線實(shí)驗(yàn)的結(jié)果如下:

DIEN使用了輔助loss和AUGRU結(jié)構(gòu),而BaseModel + GRU + AUGRU與DIEN的不同之處就是沒有增加輔助loss。可以看到,DIEN的實(shí)驗(yàn)效果遠(yuǎn)好于其他模型。

3、DIEN模型實(shí)現(xiàn)

本文模型的實(shí)現(xiàn)參考代碼是:https://github.com/mouna99/dien
本文代碼的地址為:https://github.com/princewen/tensorflow_practice/tree/master/recommendation/Basic-DIEN-Demo
本文數(shù)據(jù)的地址為:https://github.com/mouna99/dien

3.1 數(shù)據(jù)介紹

根據(jù)github中提供的數(shù)據(jù),解壓后的文件如下:
uid_voc.pkl: 用戶名對(duì)應(yīng)的id
mid_voc.pkl: item對(duì)應(yīng)的id
cat_voc.pkl:category對(duì)應(yīng)的id
item-info:item對(duì)應(yīng)的category信息
reviews-info:用于進(jìn)行負(fù)采樣的數(shù)據(jù)
local_train_splitByUser:訓(xùn)練數(shù)據(jù),一行格式為:label、用戶名、目標(biāo)item、 目標(biāo)item類別、歷史item、歷史item對(duì)應(yīng)類別。
local_test_splitByUser:測(cè)試數(shù)據(jù),格式同訓(xùn)練數(shù)據(jù)

3.2 代碼實(shí)現(xiàn)

本文的代碼主要包含以下幾個(gè)文件:
rnn.py:對(duì)tensorflow中原始的rnn進(jìn)行修改,目的是將attention同rnn進(jìn)行結(jié)合。
vecAttGruCell.py: 對(duì)GRU源碼進(jìn)行修改,將attention加入其中,設(shè)計(jì)AUGRU結(jié)構(gòu)
data_iterator.py:數(shù)據(jù)迭代器,用于數(shù)據(jù)的不斷輸入
utils.py:一些輔助函數(shù),如dice激活函數(shù)、attention score計(jì)算等
model.py:DIEN模型文件
train.py:模型的入口,用于訓(xùn)練數(shù)據(jù)、保存模型和測(cè)試數(shù)據(jù)

好了,接下來(lái)我們介紹一些關(guān)鍵的代碼:

輸入數(shù)據(jù)介紹

輸入的數(shù)據(jù)有用戶id、target的item id、target item對(duì)應(yīng)的cateid、用戶歷史行為的item id list、用戶歷史行為item對(duì)應(yīng)的cate id list、歷史行為的長(zhǎng)度、歷史行為的mask、目標(biāo)值、負(fù)采樣的數(shù)據(jù)。

對(duì)于每一個(gè)用戶的歷史行為,代碼中選取了5個(gè)樣本作為負(fù)樣本。

self.mid_his_batch_ph = tf.placeholder(tf.int32,[None,None],name='mid_his_batch_ph')
self.cat_his_batch_ph = tf.placeholder(tf.int32,[None,None],name='cat_his_batch_ph')
self.uid_batch_ph = tf.placeholder(tf.int32,[None,],name='uid_batch_ph')
self.mid_batch_ph = tf.placeholder(tf.int32,[None,],name='mid_batch_ph')
self.cat_batch_ph = tf.placeholder(tf.int32,[None,],name='cat_batch_ph')
self.mask = tf.placeholder(tf.float32,[None,None],name='mask')
self.seq_len_ph = tf.placeholder(tf.int32,[None],name='seq_len_ph')
self.target_ph = tf.placeholder(tf.float32,[None,None],name='target_ph')
self.lr = tf.placeholder(tf.float64,[])
self.use_negsampling = use_negsampling
if use_negsampling:
    self.noclk_mid_batch_ph = tf.placeholder(tf.int32, [None, None, None], name='noclk_mid_batch_ph')
    self.noclk_cat_batch_ph = tf.placeholder(tf.int32, [None, None, None], name='noclk_cat_batch_ph')

輸入數(shù)據(jù)轉(zhuǎn)換為對(duì)應(yīng)的embedding

接下來(lái),輸入數(shù)據(jù)將轉(zhuǎn)換為對(duì)應(yīng)的embedding:

with tf.name_scope("Embedding_layer"):
    self.uid_embeddings_var = tf.get_variable("uid_embedding_var",[n_uid,EMBEDDING_DIM])
    tf.summary.histogram('uid_embeddings_var', self.uid_embeddings_var)
    self.uid_batch_embedded = tf.nn.embedding_lookup(self.uid_embeddings_var,self.uid_batch_ph)

    self.mid_embeddings_var = tf.get_variable("mid_embedding_var",[n_mid,EMBEDDING_DIM])
    tf.summary.histogram('mid_embeddings_var',self.mid_embeddings_var)
    self.mid_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var,self.mid_batch_ph)
    self.mid_his_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var,self.mid_his_batch_ph)
    if self.use_negsampling:
        self.noclk_mid_his_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var,
                                                                   self.noclk_mid_batch_ph)

    self.cat_embeddings_var = tf.get_variable("cat_embedding_var", [n_cat, EMBEDDING_DIM])
    tf.summary.histogram('cat_embeddings_var', self.cat_embeddings_var)
    self.cat_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.cat_batch_ph)
    self.cat_his_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.cat_his_batch_ph)
    if self.use_negsampling:
        self.noclk_cat_his_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var,
                                                                   self.noclk_cat_batch_ph)
   

接下來(lái),將item的id對(duì)應(yīng)的embedding 以及 item對(duì)應(yīng)的cateid的embedding進(jìn)行拼接,共同作為item的embedding.:

self.item_eb = tf.concat([self.mid_batch_embedded,self.cat_batch_embedded],1)
self.item_his_eb = tf.concat([self.mid_his_batch_embedded,self.cat_his_batch_embedded],2)

if self.use_negsampling:
    self.noclk_item_his_eb = tf.concat(
        [self.noclk_mid_his_batch_embedded[:, :, 0, :], self.noclk_cat_his_batch_embedded[:, :, 0, :]], -1)
    self.noclk_item_his_eb = tf.reshape(self.noclk_item_his_eb,
                                        [-1, tf.shape(self.noclk_mid_his_batch_embedded)[1], EMBEDDING_DIM * 2]) # 負(fù)采樣的item選第一個(gè)

    self.noclk_his_eb = tf.concat([self.noclk_mid_his_batch_embedded, self.noclk_cat_his_batch_embedded], -1)

第一層GRU

接下來(lái),我們要將用戶行為歷史的item embedding輸入到dynamic rnn中,同時(shí)計(jì)算輔助loss:

with tf.name_scope('rnn_1'):
    rnn_outputs,_ = dynamic_rnn(GRUCell(HIDDEN_SIZE),inputs = self.item_his_eb,sequence_length=self.seq_len_ph,dtype=tf.float32,scope='gru1')
    tf.summary.histogram("GRU_outputs",rnn_outputs)

aux_loss_1 = self.auxiliary_loss(rnn_outputs[:,:-1,:],self.item_his_eb[:,1:,:],self.noclk_item_his_eb[:,1:,:],self.mask[:,1:],stag="gru")
self.aux_loss = aux_loss_1

輔助loss的計(jì)算其實(shí)是一個(gè)二分類模型,代碼如下:

def auxiliary_loss(self,h_states,click_seq,noclick_seq,mask,stag=None):
    mask = tf.cast(mask,tf.float32)
    click_input = tf.concat([h_states,click_seq],-1)
    noclick_input = tf.concat([h_states,noclick_seq],-1)
    click_prop_ = self.auxiliary_net(click_input,stag=stag)[:,:,0]
    noclick_prop_ = self.auxiliary_net(noclick_input,stag=stag)[:,:,0]
    click_loss_ = -tf.reshape(tf.log(click_prop_),[-1,tf.shape(click_seq)[1]]) * mask
    noclick_loss_ = - tf.reshape(tf.log(1.0 - noclick_prop_), [-1, tf.shape(noclick_seq)[1]]) * mask
    loss_ = tf.reduce_mean(click_loss_ + noclick_loss_)
    return loss_

def auxiliary_net(self,input,stag='auxiliary_net'):
    bn1 = tf.layers.batch_normalization(inputs=input, name='bn1' + stag, reuse=tf.AUTO_REUSE)
    dnn1 = tf.layers.dense(bn1, 100, activation=None, name='f1' + stag, reuse=tf.AUTO_REUSE)
    dnn1 = tf.nn.sigmoid(dnn1)
    dnn2 = tf.layers.dense(dnn1, 50, activation=None, name='f2' + stag, reuse=tf.AUTO_REUSE)
    dnn2 = tf.nn.sigmoid(dnn2)
    dnn3 = tf.layers.dense(dnn2, 2, activation=None, name='f3' + stag, reuse=tf.AUTO_REUSE)
    y_hat = tf.nn.softmax(dnn3) + 0.00000001
    return y_hat

AUGRU

我們首先需要計(jì)算attention的score,然后將其作為GRU的一部分輸入:

with tf.name_scope('Attention_layer_1'):
    att_outputs,alphas = din_fcn_attention(self.item_eb,rnn_outputs,ATTENTION_SIZE,self.mask,
                                           softmax_stag=1,stag='1_1',mode='LIST',return_alphas=True)

    tf.summary.histogram('alpha_outputs',alphas)

接下來(lái),就是AUGRU的結(jié)構(gòu),這里我們需要設(shè)計(jì)一個(gè)新的VecAttGRUCell結(jié)構(gòu),相比于GRUCell,修改的地方如下:

上圖中左側(cè)是GRU的源碼,右側(cè)是VecAttGRUCell的代碼,我們主要修改了call函數(shù)中的代碼,在GRU中,hidden state的計(jì)算為:

new_h = u * state + (1 - u) * c

AUGRU中,hidden state的計(jì)算為:

u = (1.0 - att_score) * u
new_h = u * state + (1 - u) * c

代碼中給出的hidden state計(jì)算可能與文中有些出入,不過核心的思想都是,對(duì)于attention score大的,保存的當(dāng)前的c就多一些。

設(shè)計(jì)好了新的GRU Cell,我們就能計(jì)算興趣的進(jìn)化過程:

with tf.name_scope('rnn_2'):
    rnn_outputs2,final_state2 = dynamic_rnn(VecAttGRUCell(HIDDEN_SIZE),inputs=rnn_outputs,
                                            att_scores=tf.expand_dims(alphas,-1),
                                            sequence_length = self.seq_len_ph,dtype=tf.float32,
                                            scope="gru2"
                                            )
    tf.summary.histogram("GRU2_Final_State",final_state2)

得到興趣進(jìn)化的結(jié)果final_state2之后,需要與其他的embedding進(jìn)行拼接,得到全聯(lián)接層的輸入:

inp = tf.concat([self.uid_batch_embedded,self.item_eb,self.item_his_eb_sum,self.item_eb * self.item_his_eb_sum,final_state2],1)

全聯(lián)接層得到最終輸出

最后我們通過一個(gè)多層神經(jīng)網(wǎng)絡(luò),得到最終的ctr預(yù)估值:

def build_fcn_net(self,inp,use_dice=False):
    bn1 = tf.layers.batch_normalization(inputs=inp,name='bn1')
    dnn1 = tf.layers.dense(bn1,200,activation=None,name='f1')

    if use_dice:
        dnn1 = dice(dnn1,name='dice_1')
    else:
        dnn1 = prelu(dnn1,'prelu1')

    dnn2 = tf.layers.dense(dnn1,80,activation=None,name='f2')
    if use_dice:
        dnn2 = dice(dnn2,name='dice_2')
    else:
        dnn2 = prelu(dnn2,name='prelu2')

    dnn3 = tf.layers.dense(dnn2,2,activation=None,name='f3')
    self.y_hat = tf.nn.softmax(dnn3) + 0.00000001

    with tf.name_scope('Metrics'):
        ctr_loss = -tf.reduce_mean(tf.log(self.y_hat) * self.target_ph)
        self.loss = ctr_loss
        if self.use_negsampling:
            self.loss += self.aux_loss
        tf.summary.scalar('loss',self.loss)
        self.optimizer = tf.train.AdamOptimizer(learning_rate=self.lr).minimize(self.loss)

        self.accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.round(self.y_hat),self.target_ph),tf.float32))
        tf.summary.scalar('accuracy',self.accuracy)

    self.merged = tf.summary.merge_all()

這樣,一個(gè)DIEN的模型就設(shè)計(jì)好了,其中的細(xì)節(jié)還是很多的,希望大家都能動(dòng)手實(shí)現(xiàn)一下!

參考文獻(xiàn)

1、https://blog.csdn.net/friyal/article/details/83115900
2、https://arxiv.org/pdf/1809.03672.pdf
3、https://github.com/mouna99/dien

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,616評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,112評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,355評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,727評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,928評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,165評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,585評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,892評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容