基于tensorflow+CNN的搜狐新聞文本分類

2018年10月13日筆記

tensorflow是谷歌google的深度學(xué)習(xí)框架,tensor中文叫做張量厉颤,flow叫做流。
CNN是convolutional neural network的簡稱,中文叫做卷積神經(jīng)網(wǎng)絡(luò)飒焦。
文本分類是NLP(自然語言處理)的經(jīng)典任務(wù)那槽。

0.編程環(huán)境

操作系統(tǒng):Win10
python版本:3.6
集成開發(fā)環(huán)境:jupyter notebook
tensorflow版本:1.6

1.致謝聲明

本文是作者學(xué)習(xí)《使用卷積神經(jīng)網(wǎng)絡(luò)以及循環(huán)神經(jīng)網(wǎng)絡(luò)進(jìn)行中文文本分類》的成果悼沿,感激前輩;
github鏈接:https://github.com/gaussic/text-classification-cnn-rnn

2.配置環(huán)境

使用卷積神經(jīng)網(wǎng)絡(luò)模型要求有較高的機(jī)器配置骚灸,如果使用CPU版tensorflow會花費(fèi)大量時間糟趾。
讀者在有nvidia顯卡的情況下,安裝GPU版tensorflow會提高計算速度50倍。
安裝教程鏈接:https://blog.csdn.net/qq_36556893/article/details/79433298
如果沒有nvidia顯卡义郑,但有visa信用卡蝶柿,請閱讀我的另一篇文章《在谷歌云服務(wù)器上搭建深度學(xué)習(xí)平臺》,鏈接:http://www.reibang.com/p/893d622d1b5a

3.下載并解壓數(shù)據(jù)集

數(shù)據(jù)集下載鏈接: https://pan.baidu.com/s/1cfHHr5aLk76AE7qi7L122g 提取碼: tvgn
壓縮文件夾基于CNN的搜狐新聞文本分類中有8個文件非驮,如下圖所示:

image.png

1.cnn_package.ipynb是本文的代碼文件交汤,讀者可以直接運(yùn)行;
2.getTrainTestDataSet.ipynb文件是獲取訓(xùn)練集測試集數(shù)據(jù)的代碼文件劫笙;
3.sohu_test.txt文件是測試集文件芙扎;
4.sohu_train.txt文件是訓(xùn)練集文件;
5.test_content_list.pickle是作者整理好的測試集文本內(nèi)容文件填大;
6.test_label_list.pickle是作者整理好的測試集文本標(biāo)簽文件纵顾;
7.train_content_list.pickle是作者整理好的訓(xùn)練集文本內(nèi)容文件;
8.train_label_list.pickle是作者整理好的訓(xùn)練集文本標(biāo)簽文件栋盹;

4.完整代碼

完整代碼已經(jīng)在數(shù)據(jù)集文件中給出施逾,即cnn_package.ipynb文件和getTrainTestDataSet.ipynb文件;
getTrainTestDataSet.ipynb文件中代碼的作用是將文本文件轉(zhuǎn)換為二進(jìn)制文件例获,即4個pickle文件汉额;
從工程開發(fā)的角度考慮,本文作者在cnn_package.ipynb文件中封裝了一個類TextClassification榨汤,對于樣本數(shù)量在10萬左右的分類任務(wù)較為適用蠕搜。
后面章節(jié)中將講解實(shí)現(xiàn)細(xì)節(jié)。

5.獲取數(shù)據(jù)

第1行代碼導(dǎo)入庫pandas收壕,起別名pd妓灌;
第2行代碼導(dǎo)入庫pickle;
第4-5行代碼分別讀取訓(xùn)練集文件sohu_train.txt測試集文件sohu_test.txt蜜宪。
第6行代碼將獲取訓(xùn)練集內(nèi)容列表train_content_list虫埂;
第7行代碼將獲取訓(xùn)練集標(biāo)簽列表train_label_list;
第8行代碼將獲取測試集內(nèi)容列表test_content_list圃验;
第9行代碼將獲取測試集標(biāo)簽列表test_label_list掉伏。
第10-17行代碼調(diào)用pickle庫的dump方法將python的對象轉(zhuǎn)換為二進(jìn)制文件。

import pandas as pd
import pickle

train_df = pd.read_csv('sohu_train.txt', sep='\t', header=None)
test_df = pd.read_csv('sohu_test.txt', sep='\t', header=None)
train_content_list = [k for k in train_df[1]]
train_label_list = [k for k in train_df[0]]
test_content_list = [k for k in test_df[1]]
test_label_list = [k for k in test_df[0]]
with open('train_content_list.pickle', 'wb') as file:
    pickle.dump(train_content_list, file)
with open('train_label_list.pickle', 'wb') as file:
    pickle.dump(train_label_list, file)
with open('test_content_list.pickle', 'wb') as file:
    pickle.dump(test_content_list, file)
with open('test_label_list.pickle', 'wb') as file:
    pickle.dump(test_label_list, file)

6.編寫類TextClassification

認(rèn)為編寫類有困難澳窑,可以先閱讀本文作者的另外一篇文章《基于tensorflow+CNN的新浪新聞文本分類》斧散,鏈接:http://www.reibang.com/p/b1000d5345bb,這一篇文章的代碼沒有進(jìn)行封裝摊聋,而且講解較為詳細(xì)鸡捐。
讀者閱讀下文中的行數(shù)時,可以先把代碼復(fù)制到j(luò)upyter notebook的代碼塊中麻裁。
在代碼塊中按Esc鍵箍镜,進(jìn)入命令模式瞻鹏,代碼塊左邊的豎線會顯示藍(lán)色,如下圖所示:

image.png

在命令模式下鹿寨,點(diǎn)擊L鍵新博,會顯示代碼行數(shù)。
推薦博客《Text-CNN 文本分類》從模型原理上輔助理解脚草,鏈接:https://blog.csdn.net/chuchus/article/details/77847476
本文作者解釋每行代碼含義如下:
第1-15行代碼導(dǎo)入程序運(yùn)行必需的庫赫悄;
第16-27行代碼定義文本設(shè)置類TextConfig,經(jīng)過本文作者實(shí)踐馏慨,這是1種較為合適的編碼方式埂淮;
第30-42行代碼定義類中方法config,實(shí)例化TextConfig對象后写隶,將其中的參數(shù)賦值給模型對象倔撞;
第44-62行代碼定義對象實(shí)例化方法,如果參數(shù)數(shù)量是2個慕趴,其中第1個參數(shù)是內(nèi)容列表content_list痪蝇,第2個參數(shù)是標(biāo)簽列表label_list,則調(diào)用sklearn.preprocessing庫的train_test_split方法劃分訓(xùn)練集冕房、測試集躏啰;
如果參數(shù)數(shù)量是4個,其中第1個參數(shù)是訓(xùn)練集內(nèi)容列表train_content_list耙册,第2個參數(shù)是訓(xùn)練集標(biāo)簽列表train_label_list给僵,第3個參數(shù)是測試集內(nèi)容列表test_content_list,第4個參數(shù)是測試集標(biāo)簽列表test_label_list详拙,則都將它們保存為對象的屬性帝际;
第63行表示調(diào)用第65-67行代碼的類中方法autoGetNumClasses,即自動獲取類別的數(shù)量饶辙,并賦值給對象的屬性num_classes蹲诀;
第69-73行代碼定義類中方法getVocabularyList,將列表中的字符串合并為1個字符串a(chǎn)llContent_str畸悬,調(diào)用collections庫的Counter方法侧甫,把a(bǔ)llContent_str作為參數(shù),即對字符串中的字符做統(tǒng)計計數(shù)蹋宦,最后返回出現(xiàn)次數(shù)排名前vocabulary_size的數(shù),即前5000的數(shù)咒锻;
第75-84行代碼定義類中方法prepareData冷冗,即準(zhǔn)備數(shù)據(jù),根據(jù)數(shù)據(jù)的實(shí)際情況調(diào)整模型屬性:詞匯表大小vocab_size惑艇、序列長度seq_length蒿辙、字轉(zhuǎn)id字典word2id_dict拇泛、標(biāo)簽編碼對象labelEncoder;
第86-87行代碼定義類中方法content2idList思灌,即文本內(nèi)容轉(zhuǎn)換為id列表俺叭;
第89-92行代碼定義類中方法content2X,將文本內(nèi)容列表content_list轉(zhuǎn)換為特征矩陣X泰偿;
第94-97行代碼定義類中方法label2Y熄守,將文本標(biāo)簽列表label_list轉(zhuǎn)換為預(yù)測目標(biāo)值Y,具體方法是先調(diào)用LabelEncoder對象的transform方法做標(biāo)簽編碼耗跛,然后調(diào)用kr.utils.to_categorical方法做Ont-Hot編碼裕照;
第99-118行代碼定義類中方法buildModel,即搭建卷積神經(jīng)網(wǎng)絡(luò)模型调塌,再次提醒晋南,理解此部分代碼有困難,可以先理解本文作者沒有封裝的代碼羔砾,鏈接:http://www.reibang.com/p/b1000d5345bb负间;
第120-145行代碼定義類中方法trainModel,即訓(xùn)練模型姜凄;
模型總共迭代訓(xùn)練num_iteration次唉擂,即5000次,每隔250次打印步數(shù)step檀葛、模型在測試集的損失值loss_value和準(zhǔn)確率accuracy_value
第147-153行代碼定義類中方法predict玩祟,傳入?yún)?shù)是文本內(nèi)容content_list,方法返回結(jié)果是標(biāo)簽列表label_list屿聋;
第155-162行代碼定義類中方法predictAll空扎,此方法作用是避免預(yù)測的樣本過多導(dǎo)致顯存不足,所以分批預(yù)測润讥,每批100個樣本转锈,使用列表的extend方法將每批的預(yù)測結(jié)果合并;
第164-170行代碼定義類中方法printConfusionMatrix楚殿,即打印模型在測試集的預(yù)測混淆矩陣撮慨;
第172-204行代碼定義類中方法printReportTable,即打印模型在測試集的預(yù)測報告表脆粥。

from sklearn.model_selection import train_test_split
import pickle
from collections import Counter
import tensorflow.contrib.keras as kr
from sklearn.preprocessing import LabelEncoder
import tensorflow as tf
import random
import numpy as np
import pandas as pd
from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_recall_fscore_support
import warnings
warnings.filterwarnings('ignore')
import time

class TextConfig():
    vocab_size = 5000
    seq_length = 600
    embedding_dim = 64  # 詞向量維度
    num_filters = 256  # 卷積核數(shù)目
    kernel_size = 5  # 卷積核尺
    hidden_dim = 128  # 全連接層神經(jīng)元
    dropout_keep_prob = 0.5  # dropout保留比例
    learning_rate = 1e-3  # 學(xué)習(xí)率
    batch_size = 32  # 每批訓(xùn)練大小
    num_iteration = 5000 #迭代次數(shù)
    print_per_batch = num_iteration / 20 #打印間隔

class TextClassification():
    def config(self):
        textConfig = TextConfig()
        self.vocab_size = textConfig.vocab_size
        self.seq_length = textConfig.seq_length
        self.embedding_dim = textConfig.embedding_dim
        self.num_filters = textConfig.num_filters
        self.kernel_size = textConfig.kernel_size
        self.hidden_dim = textConfig.hidden_dim
        self.dropout_keep_prob = textConfig.dropout_keep_prob
        self.learning_rate = textConfig.learning_rate
        self.batch_size = textConfig.batch_size
        self.print_per_batch = textConfig.print_per_batch
        self.num_iteration = textConfig.num_iteration
    
    def __init__(self, *args):
        self.config()
        if len(args) == 2:
            content_list = args[0]
            label_list = args[1]
            train_X, test_X, train_y, test_y = train_test_split(content_list, label_list)
            self.train_content_list = train_X
            self.train_label_list = train_y
            self.test_content_list = test_X
            self.test_label_list = test_y
            self.content_list = self.train_content_list + self.test_content_list
        elif len(args) == 4:
            self.train_content_list = args[0]
            self.train_label_list = args[1]
            self.test_content_list = args[2]
            self.test_label_list = args[3]
            self.content_list = self.train_content_list + self.test_content_list
        else:
            print('false to init TextClassification object')
        self.autoGetNumClasses()
    
    def autoGetNumClasses(self):
        label_list = self.train_label_list + self.test_label_list
        self.num_classes = np.unique(label_list).shape[0]
    
    def getVocabularyList(self, content_list, vocabulary_size):
        allContent_str = ''.join(content_list)
        counter = Counter(allContent_str)
        vocabulary_list = [k[0] for k in counter.most_common(vocabulary_size)]
        return ['PAD'] + vocabulary_list

    def prepareData(self):
        vocabulary_list = self.getVocabularyList(self.content_list, self.vocab_size)
        if len(vocabulary_list) < self.vocab_size:
            self.vocab_size = len(vocabulary_list)
        contentLength_list = [len(k) for k in self.train_content_list]
        if max(contentLength_list) < self.seq_length:
            self.seq_length = max(contentLength_list)
        self.word2id_dict = dict([(b, a) for a, b in enumerate(vocabulary_list)])
        self.labelEncoder = LabelEncoder()
        self.labelEncoder.fit(self.train_label_list)

    def content2idList(self, content):
        return [self.word2id_dict[word] for word in content if word in self.word2id_dict]

    def content2X(self, content_list):
        idlist_list = [self.content2idList(content) for content in content_list]
        X = kr.preprocessing.sequence.pad_sequences(idlist_list, self.seq_length)
        return X

    def label2Y(self, label_list):
        y = self.labelEncoder.transform(label_list)
        Y = kr.utils.to_categorical(y, self.num_classes)
        return Y

    def buildModel(self):
        tf.reset_default_graph()
        self.X_holder = tf.placeholder(tf.int32, [None, self.seq_length])
        self.Y_holder = tf.placeholder(tf.float32, [None, self.num_classes])
        embedding = tf.get_variable('embedding', [self.vocab_size, self.embedding_dim])
        embedding_inputs = tf.nn.embedding_lookup(embedding, self.X_holder)
        conv = tf.layers.conv1d(embedding_inputs, self.num_filters, self.kernel_size)
        max_pooling = tf.reduce_max(conv, reduction_indices=[1])
        full_connect = tf.layers.dense(max_pooling, self.hidden_dim)
        full_connect_dropout = tf.contrib.layers.dropout(full_connect, keep_prob=self.dropout_keep_prob)
        full_connect_activate = tf.nn.relu(full_connect_dropout)
        softmax_before = tf.layers.dense(full_connect_activate, self.num_classes)
        self.predict_Y = tf.nn.softmax(softmax_before)
        cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(labels=self.Y_holder, logits=softmax_before)
        self.loss = tf.reduce_mean(cross_entropy)
        optimizer = tf.train.AdamOptimizer(self.learning_rate)
        self.train = optimizer.minimize(self.loss)
        self.predict_y = tf.argmax(self.predict_Y, 1)
        isCorrect = tf.equal(tf.argmax(self.Y_holder, 1), self.predict_y)
        self.accuracy = tf.reduce_mean(tf.cast(isCorrect, tf.float32))

    def trainModel(self):
        self.prepareData()
        self.buildModel()
        init = tf.global_variables_initializer()
        self.session = tf.Session()
        self.session.run(init)
        train_X = self.content2X(self.train_content_list)
        train_Y = self.label2Y(self.train_label_list)
        test_X = self.content2X(self.test_content_list)
        test_Y = self.label2Y(self.test_label_list)
        startTime = time.time()
        for i in range(self.num_iteration):
            selected_index = random.sample(list(range(len(train_Y))), k=self.batch_size)
            batch_X = train_X[selected_index]
            batch_Y = train_Y[selected_index]
            self.session.run(self.train, {self.X_holder: batch_X, self.Y_holder: batch_Y})
            step = i + 1
            if step % self.print_per_batch == 0 or step == 1:
                selected_index = random.sample(list(range(len(test_Y))), k=200)
                batch_X = test_X[selected_index]
                batch_Y = test_Y[selected_index]
                loss_value, accuracy_value = self.session.run([self.loss, self.accuracy],\
                    {self.X_holder: batch_X, self.Y_holder: batch_Y})
                used_time = time.time() - startTime
                print('step:%d loss:%.4f accuracy:%.4f used time:%.2f seconds' %
                      (step, loss_value, accuracy_value, used_time))

    def predict(self, content_list):
        if type(content_list) == str:
            content_list = [content_list]
        batch_X = self.content2X(content_list)
        predict_y = self.session.run(self.predict_y, {self.X_holder:batch_X})
        predict_label_list = self.labelEncoder.inverse_transform(predict_y)
        return predict_label_list

    def predictAll(self):
        predict_label_list = []
        batch_size = 100
        for i in range(0, len(self.test_content_list), batch_size):
            content_list = self.test_content_list[i: i + batch_size]
            predict_label = self.predict(content_list)
            predict_label_list.extend(predict_label)
        return predict_label_list

    def printConfusionMatrix(self):
        predict_label_list = self.predictAll()
        df = pd.DataFrame(confusion_matrix(self.test_label_list, predict_label_list),
                     columns=self.labelEncoder.classes_,
                     index=self.labelEncoder.classes_)
        print('\n Confusion Matrix:')
        print(df)

    def printReportTable(self):
        predict_label_list = self.predictAll()
        reportTable = self.eval_model(self.test_label_list,
                                 predict_label_list,
                                 self.labelEncoder.classes_)
        print('\n Report Table:')
        print(reportTable)
        
    def eval_model(self, y_true, y_pred, labels):
        # 計算每個分類的Precision, Recall, f1, support
        p, r, f1, s = precision_recall_fscore_support(y_true, y_pred)
        # 計算總體的平均Precision, Recall, f1, support
        tot_p = np.average(p, weights=s)
        tot_r = np.average(r, weights=s)
        tot_f1 = np.average(f1, weights=s)
        tot_s = np.sum(s)
        res1 = pd.DataFrame({
            u'Label': labels,
            u'Precision': p,
            u'Recall': r,
            u'F1': f1,
            u'Support': s
        })
        res2 = pd.DataFrame({
            u'Label': ['總體'],
            u'Precision': [tot_p],
            u'Recall': [tot_r],
            u'F1': [tot_f1],
            u'Support': [tot_s]
        })
        res2.index = [999]
        res = pd.concat([res1, res2])
        return res[['Label', 'Precision', 'Recall', 'F1', 'Support']]

7.調(diào)用類中方法

第1-10行代碼調(diào)用pickle庫的load方法讀取pickle文件中的數(shù)據(jù)砌溺;
第11-14行代碼實(shí)例化TextClassification對象;
第15行代碼調(diào)用模型對象的trainModel方法变隔,即做模型訓(xùn)練规伐;
第16行代碼調(diào)用模型對象的printConfusionMatrix方法,即打印混淆矩陣匣缘;
第17行代碼調(diào)用模型對象的printReportTable方法猖闪,即打印報告表鲜棠;

import pickle

with open('train_content_list.pickle', 'rb') as file:
    train_content_list = pickle.load(file)
with open('train_label_list.pickle', 'rb') as file:
    train_label_list = pickle.load(file)
with open('test_content_list.pickle', 'rb') as file:
    test_content_list = pickle.load(file)
with open('test_label_list.pickle', 'rb') as file:
    test_label_list = pickle.load(file)
model = TextClassification(train_content_list,
                           train_label_list,
                           test_content_list,
                           test_label_list)
model.trainModel()
model.printConfusionMatrix()
model.printReportTable()

上面一段代碼的運(yùn)行結(jié)果如下圖所示,警告部分不影響程序運(yùn)行

image.png

image.png

7.總結(jié)

1.本文是作者第7個NLP小型項(xiàng)目培慌,數(shù)據(jù)共有36000條豁陆。
2.分類模型的評估指標(biāo)F1score為0.95左右,總體來說這個分類模型很優(yōu)秀吵护,能夠投入實(shí)際應(yīng)用盒音。
3.本文進(jìn)行了類的封裝,小型中文文本分類項(xiàng)目經(jīng)過數(shù)據(jù)處理得到內(nèi)容列表content_list和標(biāo)簽列表label_list之后何址,即可直接使用類做模型訓(xùn)練和預(yù)測里逆,并且得到詳細(xì)的預(yù)測結(jié)果報告表。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末用爪,一起剝皮案震驚了整個濱河市原押,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌偎血,老刑警劉巖诸衔,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異颇玷,居然都是意外死亡笨农,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門帖渠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谒亦,“玉大人,你說我怎么就攤上這事空郊》菡校” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵狞甚,是天一觀的道長锁摔。 經(jīng)常有香客問我,道長哼审,這世上最難降的妖魔是什么谐腰? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮涩盾,結(jié)果婚禮上十气,老公的妹妹穿的比我還像新娘。我一直安慰自己旁赊,他們只是感情好桦踊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著终畅,像睡著了一般籍胯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上离福,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天杖狼,我揣著相機(jī)與錄音,去河邊找鬼妖爷。 笑死蝶涩,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的絮识。 我是一名探鬼主播绿聘,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼次舌!你這毒婦竟也來了熄攘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤彼念,失蹤者是張志新(化名)和其女友劉穎挪圾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逐沙,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哲思,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吩案。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棚赔。...
    茶點(diǎn)故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖徘郭,靈堂內(nèi)的尸體忽然破棺而出靠益,到底是詐尸還是另有隱情,我是刑警寧澤崎岂,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布捆毫,位于F島的核電站,受9級特大地震影響冲甘,放射性物質(zhì)發(fā)生泄漏绩卤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一江醇、第九天 我趴在偏房一處隱蔽的房頂上張望濒憋。 院中可真熱鬧,春花似錦陶夜、人聲如沸凛驮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽黔夭。三九已至宏胯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間本姥,已是汗流浹背肩袍。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留婚惫,地道東北人氛赐。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像先舷,于是被迫代替她去往敵國和親艰管。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評論 2 355

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

  • 我想和你去一座村寨蒋川,只帶一抹真摯的情懷 與一桿鋤頭對話牲芋,訴盡半生流離的感慨 遠(yuǎn)處霓虹閃爍,頭頂?shù)拇稛熆澙@飄來 抓一...
    信仰先生閱讀 412評論 2 0
  • 如何真正懂得生活,我以為心靈的真實(shí)懒构、自由和堅強(qiáng)是最重要的餐济。 我們何時才能真正面對內(nèi)心的恐懼和逃避?包括生活胆剧、工作上...
    肥肥姑娘閱讀 692評論 0 52
  • 從神舟飛船升向太空的酒泉絮姆,到孔孟先哲的故鄉(xiāng)齊魯大地,不到2000公里的行程秩霍,我們卻似乎有了很大的心理跨度篙悯。 濟(jì)南,...
    DFZXQJB閱讀 285評論 0 2
  • 今天考完試之后在烏賊世界里面玩Salmon run铃绒,因?yàn)橥孢@個可以抽扭蛋得到錢和衣服鸽照,所以我們也稱這個為打工。我現(xiàn)...
    鈐魚擺擺閱讀 480評論 1 0