【火爐煉AI】深度學(xué)習(xí)005-簡單幾行Keras代碼解決二分類問題

【火爐煉AI】深度學(xué)習(xí)005-簡單幾行Keras代碼解決二分類問題

(本文所使用的Python庫和版本號: Python 3.6, Numpy 1.14, scikit-learn 0.19, matplotlib 2.2, Keras 2.1.6, Tensorflow 1.9.0)

很多文章和教材都是用MNIST數(shù)據(jù)集作為深度學(xué)習(xí)屆的“Hello World”程序早芭,但是這個(gè)數(shù)據(jù)集有一個(gè)很大的特點(diǎn):它是一個(gè)典型的多分類問題(一共有10個(gè)分類),在我們剛剛開始接觸深度學(xué)習(xí)時(shí)妒茬,我倒是覺得應(yīng)該從最簡單的二分類問題著手阱持。

在深度學(xué)習(xí)框架方面撩嚼,目前比較流行的是Tensorflow捆憎,Keras按咒,PyTorch迟隅,Theano等,但是我建議新手入門励七,可以從Keras入手智袭,然后進(jìn)階時(shí)轉(zhuǎn)移到Tensorflow上,實(shí)際上掠抬,Keras的后端是可以支持Tensorflow和Theano吼野,可以說,Keras是在Tensorflow和Theano的基礎(chǔ)上進(jìn)一步封裝两波,更加的簡單實(shí)用瞳步,更容易入門闷哆,通常幾行簡單的代碼就可以解決一個(gè)小型的項(xiàng)目問題。

我這篇博文主要參考了:keras系列︱圖像多分類訓(xùn)練與利用bottleneck features進(jìn)行微調(diào)(三)单起,這篇博文也是參考的Building powerful image classification models using very little data抱怔,但我發(fā)現(xiàn)這兩篇博文有很多地方的代碼跑不起來,主要原因可能是Keras或Tensorflow升級造成的嘀倒,所以我做了一些必要的修改屈留。


1. 準(zhǔn)備數(shù)據(jù)集

最經(jīng)典的二分類數(shù)據(jù)集就是Kaggle競賽中的“貓狗大戰(zhàn)”數(shù)據(jù)集(train set有25K張圖片,test set: 12.5K)测蘑,此處按照原始博文的做法灌危,我從train_set中選取1000張Dog的照片+1000張Cat照片作為我們新的train set,選取400張Dog+400張Cat照片作為新的test set碳胳。所以train和test兩個(gè)文件夾下都有兩個(gè)子文件夾(cats和dogs子文件夾)勇蝙。當(dāng)然,選取是隨機(jī)的挨约,也是用代碼來實(shí)現(xiàn)的味混,準(zhǔn)備小數(shù)據(jù)集的代碼如下:

def dataset_prepare(raw_set_folder,dst_folder,train_num_per_class=1000,test_num_per_class=400):
    '''
    準(zhǔn)備小數(shù)據(jù)集,從原始的raw_set_folder數(shù)據(jù)集中提取train_num_per_class(每個(gè)類別)的照片放入train中烫罩,
    提取val_num_per_class(每個(gè)類別)放入到validation文件夾中
    :param raw_set_folder: 含有貓狗的照片惜傲,這些照片的名稱必須為cat.101.jpg或dog.102.jpg形式
    :param dst_folder: 將選取之后的圖片放置到這個(gè)文件夾中
    :param train_num_per_class:
    :param test_num_per_class:
    :return:
    '''
    all_imgs=glob(os.path.join(raw_set_folder,'*.jpg'))
    img_len = len(all_imgs)
    assert img_len > 0, '{} has no jpg image file'.format(raw_set_folder)

    cat_imgs=[]
    dog_imgs=[]
    for img_path in all_imgs:
        img_name=os.path.split(img_path)[1]
        if img_name.startswith('cat'):
            cat_imgs.append(img_path)
        elif img_name.startswith('dog'):
            dog_imgs.append(img_path)
    random.shuffle(cat_imgs)
    random.shuffle(dog_imgs)
    [ensure_folder_exists(os.path.join(dst_folder,type_folder,class_folder)) for type_folder in ['train','test']
        for class_folder in ['dogs','cats']]
    # 下面的代碼可以進(jìn)一步優(yōu)化。贝攒。盗誊。。
    for cat_img_path in cat_imgs[:train_num_per_class]: # 最開始的N個(gè)圖片作為train
        _, fname = os.path.split(cat_img_path)  # 獲取文件名和路徑
        shutil.copyfile(cat_img_path, os.path.join(dst_folder, 'train', 'cats',fname))
    print('imgs saved to train/cats folder')
    for dog_img_path in dog_imgs[:train_num_per_class]:
        _, fname = os.path.split(dog_img_path)  # 獲取文件名和路徑
        shutil.copyfile(dog_img_path, os.path.join(dst_folder, 'train', 'dogs',fname))
    print('imgs saved to train/dogs folder')
    for cat_img_path in cat_imgs[-test_num_per_class:]: # 最末的M個(gè)圖片作為test
        _, fname = os.path.split(cat_img_path)  # 獲取文件名和路徑
        shutil.copyfile(cat_img_path, os.path.join(dst_folder, 'test', 'cats',fname))
    print('imgs saved to test/cats folder')
    for dog_img_path in dog_imgs[-test_num_per_class:]: # 最末的M個(gè)圖片作為test
        _, fname = os.path.split(dog_img_path)  # 獲取文件名和路徑
        shutil.copyfile(dog_img_path, os.path.join(dst_folder, 'test', 'dogs',fname))
    print('imgs saved to test/dogs folder')
    print('finished...')

運(yùn)行該函數(shù)即可完成小數(shù)據(jù)集的構(gòu)建隘弊,下面為Keras創(chuàng)建圖片數(shù)據(jù)流哈踱,為模型的構(gòu)建做準(zhǔn)備。

# 2梨熙,準(zhǔn)備訓(xùn)練集开镣,keras有很多Generator可以直接處理圖片的加載,增強(qiáng)等操作咽扇,封裝的非常好
from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator( # 單張圖片的處理方式邪财,train時(shí)一般都會進(jìn)行圖片增強(qiáng)
        rescale=1. / 255, # 圖片像素值為0-255,此處都乘以1/255质欲,調(diào)整到0-1之間
        shear_range=0.2, # 斜切
        zoom_range=0.2, # 放大縮小范圍
        horizontal_flip=True) # 水平翻轉(zhuǎn)

train_generator = train_datagen.flow_from_directory(# 從文件夾中產(chǎn)生數(shù)據(jù)流
    train_data_dir, # 訓(xùn)練集圖片的文件夾
    target_size=(IMG_W, IMG_H), # 調(diào)整后每張圖片的大小
    batch_size=batch_size,
    class_mode='binary') # 此處是二分類問題树埠,故而mode是binary

# 3,同樣的方式準(zhǔn)備測試集
val_datagen = ImageDataGenerator(rescale=1. / 255) # 只需要和trainset同樣的scale即可嘶伟,不需增強(qiáng)
val_generator = val_datagen.flow_from_directory(
        val_data_dir,
        target_size=(IMG_W, IMG_H),
        batch_size=batch_size,
        class_mode='binary')

上面構(gòu)建的generator就是keras需要的數(shù)據(jù)流怎憋,該數(shù)據(jù)流使用flow_from_directory首先從圖片文件夾(比如train_data_dir)中加載圖片到內(nèi)存中,然后使用train_datagen來對圖片進(jìn)行預(yù)處理和增強(qiáng),最終得到處理完成之后的batch size大小的數(shù)據(jù)流绊袋,這個(gè)數(shù)據(jù)流會無限循環(huán)的產(chǎn)生毕匀,直到達(dá)到一定的訓(xùn)練epoch數(shù)量為止。

上面用到了ImageDataGenerator來進(jìn)行圖片增強(qiáng)癌别,里面的參數(shù)說明為:(可以參考Keras的官方文檔

rotation_range是一個(gè)0~180的度數(shù)皂岔,用來指定隨機(jī)選擇圖片的角度。

width_shift和height_shift用來指定水平和豎直方向隨機(jī)移動的程度规个,這是兩個(gè)0~1之間的比例凤薛。

rescale值將在執(zhí)行其他處理前乘到整個(gè)圖像上姓建,我們的圖像在RGB通道都是0255的整數(shù)诞仓,這樣的操作可能使圖像的值過高或過低,所以我們將這個(gè)值定為01之間的數(shù)速兔。

shear_range是用來進(jìn)行剪切變換的程度

zoom_range用來進(jìn)行隨機(jī)的放大

horizontal_flip隨機(jī)的對圖片進(jìn)行水平翻轉(zhuǎn)墅拭,這個(gè)參數(shù)適用于水平翻轉(zhuǎn)不影響圖片語義的時(shí)候

fill_mode用來指定當(dāng)需要進(jìn)行像素填充,如旋轉(zhuǎn)涣狗,水平和豎直位移時(shí)谍婉,如何填充新出現(xiàn)的像素


2. 構(gòu)建并訓(xùn)練Keras模型

由于Keras已經(jīng)封裝了很多Tensorflow的函數(shù),所以在使用上更加簡單容易镀钓,當(dāng)然穗熬,如果想調(diào)整里面的結(jié)構(gòu)和參數(shù)等,也比較麻煩一些丁溅,所以對于高手唤蔗,想要調(diào)整模型的結(jié)構(gòu)和自定義一些函數(shù),可以直接用Tensorflow.

2.1 Keras模型的構(gòu)建

不管是Keras模型還是Tensorflow模型窟赏,我個(gè)人認(rèn)為其構(gòu)建都包括兩個(gè)部分:模型的搭建和模型的配置妓柜,所以可以從這兩個(gè)方面來建立一個(gè)小型的模型。代碼如下:

# 4涯穷,建立Keras模型:模型的建立主要包括模型的搭建棍掐,模型的配置
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras import optimizers
def build_model(input_shape):
    # 模型的搭建:此處構(gòu)建三個(gè)CNN層+2個(gè)全連接層的結(jié)構(gòu)
    model = Sequential()
    model.add(Conv2D(32, (3, 3), input_shape=input_shape))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Conv2D(32, (3, 3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Conv2D(64, (3, 3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))

    model.add(Flatten())
    model.add(Dense(64))
    model.add(Activation('relu'))
    model.add(Dropout(0.5)) # Dropout防止過擬合
    model.add(Dense(1)) # 此處雖然是二分類,但是不能用Dense(2)拷况,因?yàn)楹竺娴腶ctivation是sigmoid作煌,這個(gè)函數(shù)只能輸出一個(gè)值,即class_0的概率
    model.add(Activation('sigmoid')) #二分類問題用sigmoid作為activation function
    
    # 模型的配置
    model.compile(loss='binary_crossentropy', # 定義模型的loss func赚瘦,optimizer粟誓,
                  optimizer=optimizers.RMSprop(lr=0.0001),
                  metrics=['accuracy'])# 主要優(yōu)化accuracy
    # 二分類問題的loss function使用binary_crossentropy,此處使用準(zhǔn)確率作為優(yōu)化目標(biāo)
    return model # 返回構(gòu)建好的模型

這個(gè)函數(shù)就搭建了模型的結(jié)構(gòu)蚤告,對模型進(jìn)行了配置努酸,主要配置了loss function, optimzer, 優(yōu)化目標(biāo)等,當(dāng)然可以做更多其他配置杜恰。

此處获诈,為了簡單說明仍源,只是建立了三層卷積層+兩層全連接層的小型網(wǎng)絡(luò)結(jié)構(gòu),當(dāng)然舔涎,對于一些比較簡單的圖像問題笼踩,這個(gè)小型模型也能解決。如果需要構(gòu)建更為復(fù)雜的模型亡嫌,只需要自定義這個(gè)函數(shù)嚎于,修改里面的模型構(gòu)建和配置方法即可。

2.2 模型的訓(xùn)練

由于此處我們使用generator來產(chǎn)生數(shù)據(jù)流挟冠,故而訓(xùn)練時(shí)要使用fit_generator函數(shù)于购。代碼如下:

model=build_model(input_shape=(IMG_W,IMG_H,IMG_CH)) # 輸入的圖片維度
# 模型的訓(xùn)練
model.fit_generator(train_generator, # 數(shù)據(jù)流
                    steps_per_epoch=train_samples_num // batch_size, 
                    epochs=epochs,
                    validation_data=val_generator,
                    validation_steps=val_samples_num // batch_size)

由于我在自己的筆記本上訓(xùn)練,沒有獨(dú)立顯卡知染,更沒有英偉達(dá)那么NB的顯卡肋僧,故而速度很慢,但是的確能運(yùn)行下去控淡。運(yùn)行的具體結(jié)果可以去我的github上看嫌吠。

-------------------------------------輸---------出--------------------------------

Epoch 1/20
62/62 [==============================] - 136s 2s/step - loss: 0.6976 - acc: 0.5015 - val_loss: 0.6937 - val_acc: 0.5000
Epoch 2/20
62/62 [==============================] - 137s 2s/step - loss: 0.6926 - acc: 0.5131 - val_loss: 0.6846 - val_acc: 0.5813
Epoch 3/20
62/62 [==============================] - 152s 2s/step - loss: 0.6821 - acc: 0.5544 - val_loss: 0.6735 - val_acc: 0.6100

。掺炭。辫诅。

Epoch 18/20
62/62 [==============================] - 140s 2s/step - loss: 0.5776 - acc: 0.6880 - val_loss: 0.5615 - val_acc: 0.7262
Epoch 19/20
62/62 [==============================] - 143s 2s/step - loss: 0.5766 - acc: 0.6971 - val_loss: 0.5852 - val_acc: 0.6800
Epoch 20/20
62/62 [==============================] - 140s 2s/step - loss: 0.5654 - acc: 0.7117 - val_loss: 0.5374 - val_acc: 0.7450

--------------------------------------------完-------------------------------------

從訓(xùn)練后的loss和acc上可以大致看出,loss在不斷減小涧狮,acc也不斷增大炕矮,趨勢比較平穩(wěn)。

此處我們可以將訓(xùn)練過程中的loss和acc繪圖勋篓,看看他們的變化趨勢吧享。

# 畫圖,將訓(xùn)練時(shí)的acc和loss都繪制到圖上
import matplotlib.pyplot as plt
%matplotlib inline
def plot_training(history):
    plt.figure(12)
    
    plt.subplot(121)
    train_acc = history.history['acc']
    val_acc = history.history['val_acc']
    epochs = range(len(train_acc))
    plt.plot(epochs, train_acc, 'b',label='train_acc')
    plt.plot(epochs, val_acc, 'r',label='test_acc')
    plt.title('Train and Test accuracy')
    plt.legend()
    
    plt.subplot(122)
    train_loss = history.history['loss']
    val_loss = history.history['val_loss']
    epochs = range(len(train_loss))
    plt.plot(epochs, train_loss, 'b',label='train_loss')
    plt.plot(epochs, val_loss, 'r',label='test_loss')
    plt.title('Train and Test loss')
    plt.legend()
 
    plt.show()
image

很明顯譬嚣,由于epoch次數(shù)太少钢颂,acc和loss都沒有達(dá)到平臺期,后續(xù)可以增大epoch次數(shù)來達(dá)到一個(gè)比較好的結(jié)果拜银。在原始博文中殊鞭,作者在50個(gè)epoch之后達(dá)到了約80%左右的準(zhǔn)確率,此處我20個(gè)epoch后的準(zhǔn)確率為74%尼桶。

2.3 預(yù)測新樣本

單張圖片的預(yù)測

模型訓(xùn)練好之后操灿,就需要用來預(yù)測新的圖片,看看它能不能準(zhǔn)確的給出結(jié)果泵督。預(yù)測函數(shù)為:

# 用訓(xùn)練好的模型來預(yù)測新樣本
from PIL import Image
from keras.preprocessing import image
def predict(model, img_path, target_size):
    img=Image.open(img_path) # 加載圖片
    if img.size != target_size:
        img = img.resize(target_size)

    x = image.img_to_array(img) 
    x *=1./255 # 相當(dāng)于ImageDataGenerator(rescale=1. / 255)
    x = np.expand_dims(x, axis=0) # 調(diào)整圖片維度
    preds = model.predict(x) # 預(yù)測
    return preds[0]

用這個(gè)函數(shù)可以預(yù)測單張圖片:

predict(model,'E:\PyProjects\DataSet\FireAI\DeepLearning/FireAI005/cat11.jpg',(IMG_W,IMG_H))

predict(model,'E:\PyProjects\DataSet\FireAI\DeepLearning//FireAI005/dog4.jpg',(IMG_W,IMG_H))

-------------------------------------輸---------出--------------------------------

array([0.14361556], dtype=float32)

array([0.9942463], dtype=float32)

--------------------------------------------完-------------------------------------

可以看出趾盐,對于單張圖片cat11.jpg得到的概率為0.14,而dog4.jpg的概率為0.99,可以看出第0個(gè)類別是dog救鲤,第1個(gè)類別是cat久窟,模型能夠很好的區(qū)分開來。

多張圖片的預(yù)測

如果想用這個(gè)模型來預(yù)測一個(gè)文件夾中的所有圖片本缠,那么該怎么辦了斥扛?

# 預(yù)測一個(gè)文件夾中的所有圖片
new_sample_gen=ImageDataGenerator(rescale=1. / 255)
newsample_generator=new_sample_gen.flow_from_directory(
        'E:\PyProjects\DataSet\FireAI\DeepLearning',
        target_size=(IMG_W, IMG_H),
        batch_size=16,
        class_mode=None,
        shuffle=False)
predicted=model.predict_generator(newsample_generator)
print(predicted)

-------------------------------------輸---------出--------------------------------

Found 4 images belonging to 2 classes.
[[0.14361556]
[0.5149474 ]
[0.71455824]
[0.9942463 ]]

--------------------------------------------完-------------------------------------

上面的結(jié)果中第二個(gè)0.5149對應(yīng)的應(yīng)該是cat,應(yīng)該小于0.5丹锹,這個(gè)預(yù)測是錯(cuò)誤的稀颁,不過粗略估計(jì)正確率有3/4=75%。

2.4 模型的保存和加載

模型一般要及時(shí)保存到硬盤上楣黍,防止數(shù)據(jù)丟失匾灶,下面是保存的代碼:

# 模型保存
# model.save_weights('E:\PyProjects\DataSet\FireAI\DeepLearning//FireAI005/FireAI005_Model.h5') # 這個(gè)只保存weights,不保存模型的結(jié)構(gòu)
model.save('E:\PyProjects\DataSet\FireAI\DeepLearning//FireAI005/FireAI005_Model2.h5') # 對于一個(gè)完整的模型锡凝,應(yīng)該要保存這個(gè)
# 模型的加載粘昨,預(yù)測
from keras.models import load_model
saved_model=load_model('E:\PyProjects\DataSet\FireAI\DeepLearning//FireAI005/FireAI005_Model2.h5')

predicted=saved_model.predict_generator(newsample_generator)
print(predicted) # saved_model的結(jié)果和前面的model結(jié)果一致,表面模型正確保存和加載

此處得到的結(jié)果和上面model預(yù)測的結(jié)果一模一樣窜锯,表明模型被正確保存和加載。

########################小**********結(jié)###############################

1芭析,本篇文章講解了:準(zhǔn)備一個(gè)簡單的小數(shù)據(jù)集锚扎,從數(shù)據(jù)集中建立數(shù)據(jù)流,將該數(shù)據(jù)流引入到Keras的模型中進(jìn)行訓(xùn)練馁启,并使用訓(xùn)練后的模型進(jìn)行新圖片的預(yù)測驾孔,然后將模型進(jìn)行保存,加載保存好的模型到內(nèi)存中惯疙。

2翠勉,此處使用的模型是我們自己搭建的,結(jié)構(gòu)比較簡單霉颠,只有三層卷積層和兩層全連接層对碌,故而模型的準(zhǔn)確率不太高,而且此處由于時(shí)間關(guān)系蒿偎,我只訓(xùn)練了20個(gè)epoch朽们,訓(xùn)練并沒有達(dá)到平臺期。

#################################################################


注:本部分代碼已經(jīng)全部上傳到(我的github)上诉位,歡迎下載骑脱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市苍糠,隨后出現(xiàn)的幾起案子叁丧,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拥娄,死亡現(xiàn)場離奇詭異坷衍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)条舔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門枫耳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人孟抗,你說我怎么就攤上這事迁杨。” “怎么了凄硼?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵铅协,是天一觀的道長。 經(jīng)常有香客問我摊沉,道長狐史,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任说墨,我火速辦了婚禮骏全,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘尼斧。我一直安慰自己姜贡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布棺棵。 她就那樣靜靜地躺著楼咳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烛恤。 梳的紋絲不亂的頭發(fā)上母怜,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音缚柏,去河邊找鬼苹熏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛船惨,可吹牛的內(nèi)容都是我干的柜裸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼粱锐,長吁一口氣:“原來是場噩夢啊……” “哼疙挺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起怜浅,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤铐然,失蹤者是張志新(化名)和其女友劉穎蔬崩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搀暑,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沥阳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年吠昭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了漱牵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晌杰,死狀恐怖桂敛,靈堂內(nèi)的尸體忽然破棺而出功炮,到底是詐尸還是另有隱情,我是刑警寧澤术唬,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布薪伏,位于F島的核電站,受9級特大地震影響粗仓,放射性物質(zhì)發(fā)生泄漏嫁怀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一借浊、第九天 我趴在偏房一處隱蔽的房頂上張望塘淑。 院中可真熱鬧,春花似錦巴碗、人聲如沸朴爬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至母赵,卻和暖如春逸爵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凹嘲。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工师倔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人周蹭。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓趋艘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親凶朗。 傳聞我的和親對象是個(gè)殘疾皇子瓷胧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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