剖析CNN句子分類任務

前沿:第一次在簡書上留下印跡。畢業(yè)后的第一篇文章,貢獻給了簡書圆裕,只因被它的簡約風格所吸引陋桂。今天我分享的主題是"CNN如何應用在句子分類任務上"逆趣。我主要從思路和源碼的角度進行分析。

一嗜历、 任務介紹

句子分類任務宣渗,主要是將某個query句子,進行類別的劃分梨州;例如美食類痕囱、娛樂類、音樂類暴匠、學習類鞍恢、旅游類等多種類別。本文,主要介紹如何構(gòu)建出一個end2end的深度學習模型帮掉,不需要人為手工進行特征工程的設計弦悉,以期達到90%+精度的句子分類需求。

源碼Github鏈接 https://github.com/yzx1992/sentence_classification (順便幫忙start一下)

源碼基于Tensorflow1.1版本蟆炊。 只展示了部分Demo數(shù)據(jù)集合稽莉,還望讀者理解(建議:自己構(gòu)建爬蟲代碼,自給自足最靠譜)涩搓。

train set: 1000條
test set: 500條

二污秆、設計思路

由于句子分類任務中,"類別類型"只對句子中某個字詞或幾個字詞較為敏感缩膝。例如:"我們?nèi)コ院5讚瓢桑? 若模型能夠捕捉到"海底撈"這三個字的特征信息混狠,那么毫無疑問,句子將應該被分到美食這個類別上疾层。

因此将饺,我們可以先將句子切成 "字" 級別或 "詞級別"(python中可以采用jieba包進行切詞)。值得說明的是痛黎,若訓練集較小予弧,"詞"級別的效果會更好一些;另外湖饱,添加pre-train的embbeding效果會有幾個點的提升掖蛤。然而,詞級別所對應的詞向量矩陣較大井厌,參數(shù)較多蚓庭,最終訓練得到的模型參數(shù)較大(即,字典的size較大仅仆,所需要訓練的embbed_matrix 參數(shù)較多器赞,很容易擠爆顯存,出行OOM的情況墓拜。例如港柜,中文高頻詞字典取10w ,word_emb的維度取200維,單單一個embbed_matrix的參數(shù)就有10w*200=2000w個參數(shù)咳榜,2000w *4個Byte(float浮點型占4個字節(jié))約等于80M)夏醉。 實際我跑的句子分類任務中,詞模型的參數(shù)大小在300M左右涌韩,字模型則可以控制在10M左右畔柔。我當時做任務的訓練語料達100W+, 因此,詞模型和字模型臣樱,最終精確率释树、召回率肠槽、F1等各項指標的表現(xiàn)差別不大。

下文奢啥,主要以"字"模型來進行分析秸仙。

采用CNN框架,利用不同size的卷積核(kernel)來捕捉句子的ngram特征(1個字桩盲,2個字寂纪,3個字,4個字等)赌结,值得注意的是捞蛋,相同size的卷積核,通常會包含多個柬姚,通過不同初始化卷積核的參數(shù)拟杉,來達到多維度獲取句子ngram的語意信息。實驗中我設置為100量承,即num_filtes=100搬设。此外,大部分分類任務采用較粗粒度的特征就可以搞定(當然撕捍,取決于你類別體系的粒度拿穴,可以分為一級標簽,二級標簽忧风,三級標簽等等)默色,為了降低模型參數(shù),可以采用max pooling的方式狮腿,來保留ngram中最明顯的特征腿宰;例如對于"我們?nèi)コ院5讚瓢桑? 3-gram的信息包含"0 0 我"、"0 我 們"缘厢、"我們?nèi)?酗失、"們 去 吃"、"去吃海"昧绣、"吃 海 底"、"海 底 撈 " …… 等等捶闸,對于3-gram的所有特征夜畴,我們當然希望模型學到只留下"海 底 撈"一個就好。

最終删壮,對于單個query贪绘,我們總共抽取了100個1-gram, 100個2-gram, 100個3-gram,100個4-gram特征央碟,即400個特征税灌。

通常,這時候你可以接多層全連接,一般取1~3層菱涤,每層可接tanh或者relu等激活函數(shù)苞也,來達到非線性的效果(如果沒添加非線性的激活函數(shù),多層全連接等價于單層粘秆,數(shù)學上如迟,矩陣求解可知--> W2 (W1 X))=WX (其中,X表示特征向量攻走,W表示參數(shù)向量)

最后殷勘,映射到9維的向量空間,即得到每個類別對應的score得分昔搂,再經(jīng)過softmax函數(shù)作用之后玲销,將各個類別的score得分轉(zhuǎn)化為歸一化概率分布(即,各個類別的概率值相加等于1)

三摘符、走進代碼

1)首先看data.py 數(shù)據(jù)處理部分:

采用迭代器的設計思路贤斜,每次從文件讀取mini_batch個訓練樣本。迭代器設計的優(yōu)勢在于议慰,每次只讀取mini_batch個句子到內(nèi)存中蠢古,而不是把所有訓練集一股腦扔到內(nèi)存。對于那種千萬級個sample以上的文件别凹,采用迭代器草讶,可大大降低內(nèi)存的開銷,提高執(zhí)行效率炉菲。

def BatchIter(data_path, batch_size) 函數(shù):

data_path: train set 或 valid set的路徑

batch_size: 模型梯度的更新參用mini_batch進行堕战,防止單個異常樣本,梯度波動太大拍霜。

def BatchIter(data_path, batch_size):
0    #print(data_path)
1    with open(data_path, 'r') as f:
2        sample_num = 0
3        samples = []
4        for line in f:
5            line = line.strip()
6            samples.append(line)
7            sample_num += 1
8            if sample_num == batch_size:
9                a = zip(*[s.split("\t") for s in samples])  
10               l = list(a[0])
11                x = [s.strip() for s in a[1]]
12                x = np.array(text2list(x))
13                y = []
14                for i in l:
15                    y.append([0]*9)
16                    y[len(y)-1][int(i)-1] = 1
17                batch_data = list(zip(x, y))
18                batch_data = np.array(batch_data)
19                yield batch_data
20                sample_num = 0
21                samples = []

其中嘱丢,第9~12行代碼:
a = zip(*[s.split("\t") for s in samples]) 表示合并batch_size個樣本。
單個樣本對構(gòu)造為: label_id \t word_id1 word_id2 word_id3 …… 即祠饺,句子所屬標簽越驻,句子以單字表示對應的id序列,兩者以\t分隔符進行分割道偷。
eg:

    case1:  4   32 33 1571 20 57 58 0 0
    case2:  1   108 287 916 917 101 572 0 0
    case3:  2   318 319 95 646 647 319 192 95 

即缀旁,max_len=8, batch_size=3
此時,值對應如下:

    a=[('4', '1', '2'), ('32 33 1571 20 57 58 0 0', '108 287 916 917 101 572 0 0', '318 319 95 646 647 319 192 95')]
    l=['4', '1', '2']
    x=['32 33 1571 20 57 58 0 0', '108 287 916 917 101 572 0 0', '318 319 95 646 647 319 192 95']
    x=[[ 32   33 1571   20   57   58    0    0]
       [ 108  287  916  917  101  572    0    0]
       [ 318  319   95  646  647  319  192   95]]

第13~16行代碼: 本份代碼只針對9個類別進行闡述勺鸦,若你任務的分類類別有x種并巍,則把9改成x。
這幾句代碼换途,對batch個句子的label標簽構(gòu)造成one-hot向量懊渡。方便后續(xù)求預測lable和真實label的交叉熵刽射,即損失函數(shù)Loss。

例如:case1的標簽是4, 即對于9維度的向量剃执,其第3個位置置1誓禁,其他位置置0,(位置索引是從index=0開始的)忠蝗,即现横, case1對應的ground_true的向量標簽為 [0, 0, 0, 1, 0, 0, 0, 0, 0] 。同理阁最,case2的標簽是1戒祠,對應[1, 0, 0, 0, 0, 0, 0, 0, 0] ,case3的標簽是2速种,則對應 [0, 1, 0, 0, 0, 0, 0, 0, 0]姜盈。

對于,case1來說,當你經(jīng)過最后一層全連接的輸出配阵,得到一個維度為9的向量馏颂,即對應各個類別的score得分。取argmax后棋傍,該值即對應最終句子分類的label id救拉,例如對于case1來說,argmax(9維向量的概率分布 or 9維向量的score得分)=2瘫拣,表示case1最后預測的score向量為[y0, y1, y2亿絮,y3, y4, y5, y6, y7, y8]。這時候預測與真實值就有了誤差麸拄,采用交叉熵的方式求解其對應的誤差表征派昧,最后采用梯度下降算法(即反向傳播back-propagation algorithm),來更新參數(shù)拢切,使得預測值更加靠近ground True的label值蒂萎。

    y=[[0, 0, 0, 1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0]]

第17行~19行,對x和y進行zip淮椰,方便訓練樣本與label標簽一一對應五慈,最后再轉(zhuǎn)成numpy數(shù)組的形式,準備訓練的時候主穗,feed給模型泻拦。

    batch_data=[(array([  32,   33, 1571,   20,   57,   58,    0,    0]), [0, 0, 0, 1, 0, 0, 0, 0, 0]), 
     (array([108, 287, 916, 917, 101, 572,   0,   0]), [1, 0, 0, 0, 0, 0, 0, 0, 0]), (array([318, 319,  95, 646, 647, 319, 192,  95]), [0, 1, 0, 0, 0, 0, 0, 0, 0])]
    
    batch_data=[[array([  32,   33, 1571,   20,   57,   58,    0,    0]) list([0, 0, 0, 1, 0, 0, 0, 0, 0])]
               [array([108, 287, 916, 917, 101, 572,   0,   0]) list([1, 0, 0, 0, 0, 0, 0, 0, 0])]
               [array([318, 319,  95, 646, 647, 319, 192,  95]) list([0, 1, 0, 0, 0, 0, 0, 0, 0])]]

yield 是一個類似return的關鍵字,只不過其返回的是個生成器黔牵,不是具體的值。

在train_multi_gpu.py腳本中爷肝,Train()則可以方便地調(diào)用data.py函數(shù)猾浦,從而得到batch個訓練樣本與其所對應的label值

    train_iter = BatchIter(train_path, batch_size) #返回可迭代對象
    for train_batch in train_iter:  
        x_batch, y_batch = zip(*train_batch)   #返回batch個數(shù)據(jù)  

2)直接進入模型代碼陆错,轉(zhuǎn)移到text_cnn.py 這個函數(shù)。

def inference(input_x, input_y, sequence_length,
vocab_size, embedding_size, filter_sizes, num_filters,
x_size, cpus, l2_reg_lambda=0.0, dropout_keep_prob=0.5)

input_x: 表示batch個輸入序列的字id金赦,shape=[batch,max_len] batch表示mini_batch的大小音瓷,max_len,表現(xiàn)句子的最大長度夹抗,(大于max_len的句子截斷绳慎,小于max_len的句子補0)

input_y: 表示batch個ground True label的向量(如上所述,向量維度為9漠烧,總共分為9個類別) 杏愤,shape=[batch,9]

embedding_size: 即,每個字用多少維向量來表示已脓,通常維度取100~300即可珊楼。

filter_sizes:[2,3,4,5],表示卷積核的大小(kernel_size)取寬度=embbedding_size度液,高度分別取2厕宗,3,4堕担,5已慢。即捕獲句子2-gram,3-gram,4-gram,5-gram的特征。這里大家可能會有疑惑霹购,為什么寬度一定要取embbeding_size佑惠,而不能像圖像(CV)一樣,來個33或者55的卷積核厕鹃? 對于句子而言兢仰,我們最主要的是想捕捉n-gram的特征,例如捕獲句子"我們?nèi)コ院5讚瓢桑?剂碴,我們主要想捕獲3-gram的特征就OK了把将。如果卷積核的寬度不取embbeding_size的維度,則會破壞字向量的語意信息忆矛,自然也就談不上ngram特征了袭艺。

num_filters: 本文設置為100屁置,即不同kernel_size的卷積核,取100個。通過不同初始化參數(shù)避凝,來達到多角度,更加全面的獲取相對應ngram的信息避消。

x_size:暫時沒用到下翎,略。

l2_reg_lambda: L2,正則化懲罰系數(shù)采驻。目的审胚,防止模型因參數(shù)過于復雜:出現(xiàn)過擬合現(xiàn)象匈勋。通常,會在損失函數(shù)上膳叨,加上參數(shù)的懲罰洽洁,本文暫時取0,可根據(jù)模型效果菲嘴,自行調(diào)(lian)參 (dan)饿自。

dropout_keep_prob:本文取0.5,讓每個神經(jīng)元以50%的概率不工作龄坪,即處于睡眠狀態(tài)昭雌,不進行前向score傳播,也不進行反向error傳遞悉默。 目的:減少神經(jīng)元之間復雜的共適應性城豁,提高模型的泛化能力。

import tensorflow as tf

TOWER_NAME = 'CNN'

def _variable_on_cpu(name, shape, initializer, cpus):
    with tf.device('/gpu:6' ):
        var = tf.get_variable(name, shape, initializer=initializer)
    return var

def inference(input_x, input_y, sequence_length,
        vocab_size, embedding_size, filter_sizes, num_filters,
        x_size, cpus, l2_reg_lambda=0.0, dropout_keep_prob=0.5):
 
0    l2_loss = tf.constant(0.0)
1    with tf.variable_scope("embedding") as scope:
2        W = _variable_on_cpu("W", [vocab_size+22, embedding_size],
3                tf.random_uniform_initializer(-1.0, 1.0), cpus)
4        #print vocab_size,input_x
5        embedded_chars = tf.nn.embedding_lookup(W, input_x)
6        embedded_chars_expanded = tf.expand_dims(embedded_chars, -1)
7    pooled_outputs = []
8    for i, filter_size in enumerate(filter_sizes):
9        with tf.variable_scope("conv-maxpool-%s" % filter_size) as scope :
10            # Convolution Layer
11           filter_shape = [filter_size, embedding_size, 1, num_filters]
12            W = _variable_on_cpu("W", filter_shape, tf.truncated_normal_initializer(stddev=0.1), cpus)
13            b = _variable_on_cpu('b', [num_filters],tf.constant_initializer(0.1), cpus)
14            conv = tf.nn.conv2d(embedded_chars_expanded,
15                               W,
16                               strides=[1, 1, 1, 1],
17                               padding="VALID",
18                               name="conv")
19     
20            # Apply nonlinearity
21            h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu")
22            # Maxpooling over the outputs
23            pooled = tf.nn.max_pool(h,
24                                  ksize=[1, sequence_length - filter_size + 1, 1, 1],
25                                  strides=[1, 1, 1, 1],
26                                  padding='VALID',
27                                  name="pool")
28      
29            pooled_outputs.append(pooled)
30    # Combine all the pooled features
31    with tf.variable_scope("combine") as scope :
32        num_filters_total = num_filters * len(filter_sizes)
33        h_pool = tf.concat( pooled_outputs,3)
34        h_pool_flat = tf.reshape(h_pool, [-1, num_filters_total], name="encode")
35    # Add dropout
36    with tf.variable_scope("dropout") as scope :
37        h_drop = tf.nn.dropout(h_pool_flat, dropout_keep_prob, name="h_drop")
38 
39    # 4 Hidden layer to map all the pooled features
40    with tf.variable_scope("output") as scope:
41        W = _variable_on_cpu("W1", [num_filters_total, 9], tf.truncated_normal_initializer(stddev=0.1), cpus)
42        b = _variable_on_cpu('b1', [9], tf.constant_initializer(0.1), cpus)
43        l2_loss += tf.nn.l2_loss(W)
44        l2_loss += tf.nn.l2_loss(b)
45        scores = tf.nn.xw_plus_b(h_drop, W, b, name='scores')
46        probs = tf.nn.softmax(scores, name='probs')
47        predictions = tf.argmax(scores, 1, name = 'predictions')
48
49    # CalculateMean cross-entropy loss
50    with tf.variable_scope("loss") as scope :
51        losses = tf.nn.softmax_cross_entropy_with_logits(labels=input_y,logits=scores)
52        loss = tf.reduce_mean(losses) + l2_reg_lambda*l2_loss
53
54    # Accuracy
55    with tf.name_scope("accuracy"):
56        correct = tf.equal(predictions, tf.argmax(input_y, 1))
57        accuracy = tf.reduce_mean(tf.cast(correct, "float"), name="accuracy")
58    return loss, accuracy

代碼第0~6行:
W:word_embbeding matrix shape=[vocab_size+22, embedding_size] , 其中抄课,22可以隨便換個正整數(shù)唱星,防止id索引word_embbeding matrix越界。
embbed_chars: batch個句子的詞向量表征跟磨。 shape=[batch, max_len, embedding_size]
embedded_chars_expanded:shape=[batch, max_len, embedding_size,1] 间聊,在最末尾增加一個維度,因為TF的卷積操作抵拘,要求參數(shù)必須是4維哎榴。

代碼第8~29行:
第8行:對每個不同size的卷積核(kernel)進行遍歷,這里filter_size=2,3,4,5僵蛛,這里以filter_size=3 尚蝌、max_len=50 、num_filters=100 來進行分析充尉。

filter_shape: 卷積核的維度 shape=[3,embedding_size,1,100]
w: 卷積核參數(shù) shape=[3,embedding_size,1,100] ,采用標準差為0.1的正太分布進行參數(shù)初始化飘言。
b: 偏置參數(shù), shape=[100]驼侠,初始化為0.1

tf.nn.conv2d(input,filters,strides,padding)
input: shape=[batch, max_len, embedding_size,1] 以圖像進行類比:batch_size姿鸿,對應圖片的數(shù)量,可以暫時忽略這個維度倒源。max_len苛预, 類比圖像的高度,embedding_size笋熬,類比圖像的寬度热某,1,類比圖像的通道數(shù)(1表示單通道的灰色圖像)
filters: shape=[3,embedding_size,1,100] 3代表卷積核的高度,embedding_siez表示卷積核的寬度昔馋,1芜繁,類比圖像的通道數(shù),100表征卷積核的數(shù)目绒极。

padding: string類型,值為“SAME” 和 “VALID”蔬捷,表示的是卷積的形式垄提,是否考慮邊界≈芄眨”SAME”是考慮邊界铡俐,不足的時候用0去填充周圍,”VALID”則不考慮.

最終conv: shape=[batch, max_len-filter_size+1, 1, 100] = [batch, 100-3+1, 1, 100]

經(jīng)過一層relu非線性函數(shù)激活后妥粟,再進行max pooling
此時审丘,pooled:shape=[batch,1,100]

因為pooled_outputs添加了4種不同size卷積核的特征輸出,即pooled_outputs=[[batch,1,100] ,[batch,1,100] ,[batch,1,100] ,[batch,1,100] ]

代碼第31~37行:
首先勾给,對pooled_outputs進行最后一個維度的拼接滩报。即h_pool:shape=[batch,1,100*4]
然后,在flatten播急,得到h_pool_flat: shape=[batch,400]
再進行dropout操作脓钾,得到最終句子的特征。

代碼第40~57:
采用單層全連接桩警,將CNN捕獲到的400維特征向量可训,映射到9個類別, 得到每個類別的得分。
值得注意的是捶枢,如果訓練語料足夠多握截,大50w以上,可以嘗試3層的全連接層烂叔,增大模型的容量谨胞,泛化效果可以更好一些。
此時长已,scores: shape=[batch,9] 9個類別
進行softmax操作畜眨,將9個類別的socre得分映射成概率分布。

probs: shape=[batch,9] 术瓮,9個類別的概率值相加=1
predictions即為最終預測label的id shape=[batch,]

采用交叉熵來作為損失函數(shù)LOSS
值得注意的是:tf.nn.softmax_cross_entropy_with_logits(labels,logits) 交叉熵計算函數(shù)輸入中的logits并不是softmax或sigmoid的輸出康聂,而是未經(jīng)過非線性函數(shù)前的得分scores,因為該函數(shù)內(nèi)部會對score進行sigmoid或softmax操作胞四。
此時losses shape=[batch,9]
進行最后一個維度的求和恬汁,即,累加9個類別的損失辜伟。loss shape=[batch,]

correct 返回True,False列表 shape=[batch,]
accuracy 返回準確率 shape=[batch,]

OK氓侧,不出意外的話脊另,你的模型應該順利run起來了! 記得GPU訓練约巷,否則你得等到猴年馬月偎痛!
不過,你可以縮小batch的值独郎,調(diào)整訓練集的大小踩麦,在本地電腦run看看代碼是否有bug,或者修改模型框架氓癌,封裝成自己看起來順眼的API 谓谦!

  1. 測試模型效果,目光移步到evaluation.py 腳本贪婉。
    記得反粥,修改好數(shù)據(jù)路徑和模型路徑。
    對于一個query疲迂,可以根據(jù)top函數(shù)才顿,輸出top 1或者top3的指標,分別包含recall_rate尤蒿、precision_rate娜膘、F1指標。
    加載模型你可

          saver=tf.train.import_meta_graph(FLAGS.model_dir+'/model.ckpt-24.meta')  
          sess.graph.get_tensor_by_name('dev_x:0') #根據(jù)tensor的名字加載變量
    

也可以采用

ckpt = tf.train.get_checkpoint_state(checkpoint_path)
if ckpt and  tf.train.checkpoint_exists(ckpt.model_checkpoint_path):
    print("Reloading model parameters..")
    _model.saver.restore(sess=session, save_path=ckpt.model_checkpoint_path)

四优质、總結(jié)

  1. 一定要統(tǒng)計好數(shù)據(jù)分布竣贪,即各個類別的數(shù)據(jù)分布應該保持在相同數(shù)量級別。否則巩螃,模型容易 "劍走偏鋒"演怎,一直傾向于輸出某個類別。 數(shù)據(jù)才是決定最終的指標的上限1芊ΑR!

  2. 對于類別體系較為復雜的分類拍皮,需要捕捉更加細粒度的特征歹叮,直接分詞+max pooling 效果可能會欠佳∶保可以拼接"字"特征+ "詞"特征咆耿,最好也引入ner(實體特征)等多維度信息。

3)對于短文本爹橱,也可以引入Bi-LSTM 直接捕獲整個句子的信息萨螺,直接暴力采用最后一個step的隱藏層即可,(拼接前向隱藏層和后向隱藏層),注意一定要mask掉padding 0帶來的誤差影響慰技。

4)如果是中長文本椭盏,超過100個詞,采用Bi-LSTM框架 則需要加入attention機制吻商,來挑選關鍵的詞掏颊。

5)……

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市艾帐,隨后出現(xiàn)的幾起案子蚯舱,更是在濱河造成了極大的恐慌,老刑警劉巖掩蛤,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異陈肛,居然都是意外死亡揍鸟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門句旱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阳藻,“玉大人,你說我怎么就攤上這事谈撒⌒饶啵” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵啃匿,是天一觀的道長蛔外。 經(jīng)常有香客問我,道長溯乒,這世上最難降的妖魔是什么夹厌? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮裆悄,結(jié)果婚禮上矛纹,老公的妹妹穿的比我還像新娘。我一直安慰自己光稼,他們只是感情好或南,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著艾君,像睡著了一般采够。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上冰垄,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天吁恍,我揣著相機與錄音,去河邊找鬼。 笑死冀瓦,一個胖子當著我的面吹牛伴奥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播翼闽,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拾徙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了感局?” 一聲冷哼從身側(cè)響起尼啡,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎询微,沒想到半個月后崖瞭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡撑毛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年书聚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藻雌。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡雌续,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胯杭,到底是詐尸還是另有隱情驯杜,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布做个,位于F島的核電站鸽心,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏居暖。R本人自食惡果不足惜再悼,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望膝但。 院中可真熱鬧冲九,春花似錦、人聲如沸跟束。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冀宴。三九已至灭贷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間略贮,已是汗流浹背甚疟。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工仗岖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人览妖。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓轧拄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親讽膏。 傳聞我的和親對象是個殘疾皇子檩电,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

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