如何用Python和深度神經網絡鎖定即將流失的客戶?

Python深度學習簡明實戰教程來了。別猶豫了,趕緊從零開始,搭建你自己的第一個深度學習模型吧!

想不想了解如何用Python快速搭建深度神經網絡,完成數據分類任務?本文一步步為你展示這一過程,讓你初步領略深度學習模型的強大和易用。

煩惱

作為一名數據分析師,你來到這家跨國銀行工作已經半年了。

今天上午,老板把你叫到辦公室,面色凝重。

你心里直打鼓,以為自己捅了什么簍子。幸好老板的話讓你很快打消了顧慮。

他發愁,是因為最近歐洲區的客戶流失嚴重,許多客戶都跑到了競爭對手那里接受服務了。老板問你該怎么辦?

你脫口而出“做好客戶關系管理?。 ?/p>

老板看了你一眼,緩慢地說“我們想知道哪些客戶最可能在近期流失”。

沒錯,在有魚的地方釣魚,才是上策。

你明白了自己的任務——通過數據鎖定即將流失的客戶。這個工作,確實是你這個數據分析師分內的事兒。

你很慶幸,這半年做了很多的數據動態采集和整理工作,使得你手頭就有一個比較完備的客戶數據集。

下面你需要做的,就是如何從數據中“沙里淘金”,找到那些最可能流失的客戶。

可是,該怎么做呢?

你拿出歐洲區客戶的數據,端詳起來。

客戶主要分布在法國、德國和西班牙。

你手里掌握的信息,包括他們的年齡、性別、信用、辦卡信息等??蛻羰欠褚蚜魇У男畔⒃谧詈笠涣校‥xited)。

怎么用這些數據來判斷顧客是否會流失呢?

以你的專業素養,很容易就判斷出這是一個分類問題,屬于機器學習中的監督式學習。但是,你之前并沒有做過實際項目,該如何著手呢?

別發愁,我一步步給你演示如何用Python和深度神經網絡(或者叫“深度學習”)來完成這個分類任務,幫你鎖定那些即將流失的客戶。

環境

工欲善其事,必先利其器。我們先來安裝和搭建環境。

首先是安裝Python。

請到這個網址下載Anaconda的最新版本。

請選擇左側的Python 3.6版本下載安裝。

其次是新建文件夾,起名為demo-customer-churn-ann,并且從這個鏈接下載數據,放到該文件夾下。

(注:樣例數據來自于匿名化處理后的真實數據集,下載自superdatascience官網。)

打開終端(或者命令行工具),進入demo-customer-churn-ann目錄,執行以下命令:

jupyter notebook

瀏覽器中會顯示如下界面:

點擊界面右上方的New按鈕,新建一個Python 3 Notebook,起名為customer-churn-ann。

準備工作結束,下面我們開始清理數據。

清理

首先,讀入數據清理最常用的pandas和numpy包。

import numpy as np
import pandas as pd

customer_churn.csv里讀入數據:

df = pd.read_csv('customer_churn.csv')

看看讀入效果如何:

df.head()

這里我們使用了head()函數,只顯示前5行。

可以看到,數據完整無誤讀入。但是并非所有的列都對我們預測用戶流失有作用。我們一一甄別一下:

  • RowNumber:行號,這個肯定沒用,刪除
  • CustomerID:用戶編號,這個是順序發放的,刪除
  • Surname:用戶姓名,對流失沒有影響,刪除
  • CreditScore:信用分數,這個很重要,保留
  • Geography:用戶所在國家/地區,這個有影響,保留
  • Gender:用戶性別,可能有影響,保留
  • Age:年齡,影響很大,年輕人更容易切換銀行,保留
  • Tenure:當了本銀行多少年用戶,很重要,保留
  • Balance:存貸款情況,很重要,保留
  • NumOfProducts:使用產品數量,很重要,保留
  • HasCrCard:是否有本行信用卡,很重要,保留
  • IsActiveMember:是否活躍用戶,很重要,保留
  • EstimatedSalary:估計收入,很重要,保留
  • Exited:是否已流失,這將作為我們的標簽數據

上述數據列甄別過程,就叫做“特征工程”(Feature Engineering),這是機器學習里面最常用的數據預處理方法。如果我們的數據量足夠大,機器學習模型足夠復雜,是可以跳過這一步的。但是由于我們的數據只有10000條,還需要手動篩選特征。

選定了特征之后,我們來生成特征矩陣X,把剛才我們決定保留的特征都寫進來。

X = df.loc[:,['CreditScore', 'Geography', 'Gender', 'Age', 'Tenure', 'Balance', 'NumOfProducts', 'HasCrCard', 'IsActiveMember', 'EstimatedSalary']]

看看特征矩陣的前幾行:

X.head()

顯示結果如下:

2017-11-19_19-2-2_snapshots-01.jpg

特征矩陣構建準確無誤,下面我們構建目標數據y,也就是用戶是否流失。

y = df.Exited
y.head()
0    1
1    0
2    1
3    0
4    0
Name: Exited, dtype: int64

此時我們需要的數據基本上齊全了。但是我們發現其中有幾列數據還不符合我們的要求。

要做機器學習,只能給機器提供數值,而不能是字符串。可是看看我們的特征矩陣:

X.head()

2017-11-19_19-2-2_snapshots-01.jpg

顯然其中的Geography和Gender兩項數據都不符合要求。它們都是分類數據。我們需要做轉換,把它們變成數值。

在Scikit-learn工具包里面,專門提供了方便的工具LabelEncoder,讓我們可以方便地將類別信息變成數值。

from sklearn.preprocessing import LabelEncoder, OneHotEncoder
labelencoder1 = LabelEncoder()
X.Geography= labelencoder1.fit_transform(X.Geography)
labelencoder2 = LabelEncoder()
X.Gender = labelencoder2.fit_transform(X.Gender)

我們需要轉換兩列,所以建立了兩個不同的labelencoder。轉換的函數叫做fit_transform。

經過轉換,此時我們再來看看特征矩陣的樣子:

X.head()

2017-11-19_19-2-11_snapshots-03.jpg

顯然,Geography和Gender這兩列都從原先描述類別的字符串,變成了數字。

這樣是不是就完事大吉了呢?

不對,Gender還好說,只有兩種取值方式,要么是男,要么是女。我們可以把“是男性”定義為1,那么女性就取值為0。兩種取值只是描述類別不同,沒有歧義。

而Geography就不同了。因為數據集里面可能的國家地區取值有3種,所以就轉換成了0(法國)、1(德國)、2(西班牙)。問題是,這三者之間真的有序列(大?。╆P系嗎?

答案自然是否定的。我們其實還是打算用數值描述分類而已。但是取值有數量的序列差異,就會給機器帶來歧義。它并不清楚不同的取值只是某個國家的代碼,可能會把這種大小關系帶入模型計算,從而產生錯誤的結果。

解決這個問題,我們就需要引入OneHotEncoder。它也是Scikit-learn提供的一個類,可以幫助我們把類別的取值轉變為多個變量組合表示。

咱們這個數據集里,可以把3個國家分別用3個數字組合來表示。例如法國從原先的0,變成(1, 0, 0),德國從1變成(0, 1, 0),而西班牙從2變成(0, 0, 1)。

這樣,再也不會出現0和1之外的數字來描述類別,從而避免機器產生誤會,錯把類別數字當成大小來計算了。

特征矩陣里面,我們只需要轉換國別這一列。因為它在第1列的位置(從0開始計數),因而categorical_features只填寫它的位置信息。

onehotencoder = OneHotEncoder(categorical_features = [1])
X = onehotencoder.fit_transform(X).toarray()

這時候,我們的特征矩陣數據框就被轉換成了一個數組。注意所有被OneHotEncoder轉換的列會排在最前面,然后才是那些保持原樣的數據列。

我們只看轉換后的第一行:

X[0]
array([  1.00000000e+00,   0.00000000e+00,   0.00000000e+00,
         6.19000000e+02,   0.00000000e+00,   4.20000000e+01,
         2.00000000e+00,   0.00000000e+00,   1.00000000e+00,
         1.00000000e+00,   1.00000000e+00,   1.01348880e+05])

這樣,總算轉換完畢了吧?

沒有。

因為本例中,OneHotEncoder轉換出來的3列數字,實際上是不獨立的。給定其中兩列的信息,你自己都可以計算出其中的第3列取值。

好比說,某一行的前兩列數字是(0, 0),那么第三列肯定是1。因為這是轉換規則決定的。3列里只能有1個是1,其余都是0。

如果你做過多元線性回歸,應該知道這種情況下,我們是需要去掉其中一列,才能繼續分析的。不然會落入“虛擬變量陷阱”(dummy variable trap)。

我們刪掉第0列,避免掉進坑里。

X = np.delete(X, [0], 1)

再次打印第一行:

X[0]
array([  0.00000000e+00,   0.00000000e+00,   6.19000000e+02,
         0.00000000e+00,   4.20000000e+01,   2.00000000e+00,
         0.00000000e+00,   1.00000000e+00,   1.00000000e+00,
         1.00000000e+00,   1.01348880e+05])

檢查完畢,現在咱們的特征矩陣處理基本完成。

但是監督式學習,最重要的是有標簽(label)數據。本例中的標簽就是用戶是否流失。我們目前的標簽數據框,是這個樣子的。

y.head()
0    1
1    0
2    1
3    0
4    0
Name: Exited, dtype: int64

它是一個行向量,我們需要把它先轉換成為列向量。你可以想象成把它“豎過來”。

y = y[:, np.newaxis]
y
array([[1],
       [0],
       [1],
       ...,
       [1],
       [1],
       [0]])

這樣在后面訓練的時候,他就可以和前面的特征矩陣一一對應來操作計算了。

既然標簽代表了類別,我們也把它用OneHotEncoder轉換,這樣方便我們后面做分類學習。

onehotencoder = OneHotEncoder()
y = onehotencoder.fit_transform(y).toarray()

此時的標簽變成兩列數據,一列代表顧客存留,一列代表顧客流失。

y
array([[ 0.,  1.],
       [ 1.,  0.],
       [ 0.,  1.],
       ...,
       [ 0.,  1.],
       [ 0.,  1.],
       [ 1.,  0.]])

總體的數據已經齊全了。但是我們不能把它們都用來訓練。

這就好像老師不應該把考試題目拿來給學生做作業和練習一樣。只有考學生沒見過的題,才能區分學生是掌握了正確的解題方法,還是死記硬背了作業答案。

我們拿出20%的數據,放在一邊,等著用來做測試。其余8000條數據用來訓練機器學習模型。

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)

我們看看訓練集的長度:

len(X_train)
8000

再看看測試集的長度:

len(X_test)
2000

確認無誤。

是不是可以開始機器學習了?

可以,但是下面這一步也很關鍵。我們需要把數據進行標準化處理。因為原先每一列數字的取值范圍都各不相同,因此有的列方差要遠遠大于其他列。這樣對機器來說,也是很困擾的。數據的標準化處理,可以在保持列內數據多樣性的同時,盡量減少不同類別之間差異的影響,可以讓機器公平對待全部特征。

我們調用Scikit-learn的StandardScaler類來完成這一過程。

from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)

注意,我們只對特征矩陣做標準化,標簽是不能動的。另外訓練集和測試集需要按照統一的標準變化。所以你看,訓練集上,我們用了fit_transform函數,先擬合后轉換;而在測試集上,我們直接用訓練集擬合的結果,只做轉換。

X_train
array([[-0.5698444 ,  1.74309049,  0.16958176, ...,  0.64259497,
        -1.03227043,  1.10643166],
       [ 1.75486502, -0.57369368, -2.30455945, ...,  0.64259497,
         0.9687384 , -0.74866447],
       [-0.5698444 , -0.57369368, -1.19119591, ...,  0.64259497,
        -1.03227043,  1.48533467],
       ...,
       [-0.5698444 , -0.57369368,  0.9015152 , ...,  0.64259497,
        -1.03227043,  1.41231994],
       [-0.5698444 ,  1.74309049, -0.62420521, ...,  0.64259497,
         0.9687384 ,  0.84432121],
       [ 1.75486502, -0.57369368, -0.28401079, ...,  0.64259497,
        -1.03227043,  0.32472465]])

你會發現,許多列的方差比原先小得多。機器學習起來,會更加方便。

數據清理和轉換工作至此完成。

決策樹

如果讀過我的《貸還是不貸:如何用Python和機器學習幫你決策?》一文,你應該有一種感覺——這個問題和貸款審批決策很像啊!既然在該文中,決策樹很好使,我們繼續用決策樹不就好了?

好的,我們先測試一下經典機器學習算法表現如何。

從Scikit-learn中,讀入決策樹工具。然后擬合訓練集數據。

from sklearn import tree
clf = tree.DecisionTreeClassifier()
clf = clf.fit(X_train, y_train)

然后,利用我們建立的決策樹模型做出預測。

y_pred = clf.predict(X_test)

打印預測結果:

y_pred
array([[ 1.,  0.],
       [ 0.,  1.],
       [ 1.,  0.],
       ...,
       [ 1.,  0.],
       [ 1.,  0.],
       [ 0.,  1.]])

這樣看不出來什么。讓我們調用Scikit-learn的classification_report模塊,生成分析報告。

from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))
             precision    recall  f1-score   support
          0       0.89      0.86      0.87      1595
          1       0.51      0.58      0.54       405
avg / total       0.81      0.80      0.81      2000

經檢測,決策樹在咱們的數據集上,表現得還是不錯的。總體的準確率為0.81,召回率為0.80,f1分數為0.81,已經很高了。對10個客戶做流失可能性判斷,它有8次都能判斷正確。

但是,這樣是否足夠?

我們或許可以調整決策樹的參數做優化,嘗試改進預測結果。

或者我們可以采用深度學習。

深度

深度學習的使用場景,往往是因為原有的模型經典機器學習模型過于簡單,無法把握復雜數據特性。

我不準備給你講一堆數學公式,咱們動手做個實驗。

請你打開這個網址

你會看到如下圖所示的深度學習游樂場:

右側的圖形,里面是藍色數據,外圈是黃色數據。你的任務就是要用模型分類兩種不同數據。

你說那還不容易?我一眼就看出來了。

你看出來沒有用。通過你的設置,讓機器也能正確區分,才算數。

圖中你看到許多加減號。咱們就通過操縱它們來玩兒一玩兒模型。

首先,點圖中部上方的"2 HIDDEN LAYERS"左側減號,把中間隱藏層數降低為1。

然后,點擊"2 neurons"上面的減號,把神經元數量減少為1。

把頁面上方的Activation函數下拉框打開,選擇“Sigmoid”。

現在的模型,其實就是經典的邏輯回歸(Logistic Regression)。

點擊左上方的運行按鈕,我們看看執行效果。

由于模型過于簡單,所以機器絞盡腦汁,試圖用一條直線切分二維平面上的兩類節點。

損失(loss)居高不下。訓練集和測試集損失都在0.4左右,顯然不符合我們的分類需求。

下面我們試試增加層數和神經元數量。這次點擊加號,把隱藏層數加回到2,兩層神經元數量都取2。

再次點擊運行。

經過一段時間,結果穩定了下來,你發現這次電腦用了兩條線,把平面切分成了3部分。

測試集損失下降到了0.25左右,而訓練集損失更是降低到了0.2以下。

模型復雜了,效果似乎更好一些。

再接再厲,我們把第一個隱藏層的神經元數量增加為4看看。

點擊運行,不一會兒有趣的事情就發生了。

機器用一條近乎完美的曲線把平面分成了內外兩個部分。測試集和訓練集損失都極速下降,訓練集損失甚至接近于0。

這告訴我們,許多時候模型過于簡單帶來的問題,可以通過加深隱藏層次、增加神經元的方法提升模型復雜度,加以改進。

目前流行的劃分方法,是用隱藏層的數量多少來區分是否“深度”。當神經網絡中隱藏層數量達到3層以上時,就被稱為“深度神經網絡”,或者“深度學習”。

久聞大名的深度學習,原來就是這么簡單。

如果有時間的話,建議你自己在這個游樂場里多動手玩兒一玩兒。你會很快對神經網絡和深度學習有個感性認識。

框架

游樂場背后使用的引擎,就是Google的深度學習框架Tensorflow。

所謂框架,就是別人幫你構造好的基礎軟件應用。你可以通過調用它們,避免自己重復發明輪子,大幅度節省時間,提升效率。

支持Python語言的深度學習的框架有很多,除了Tensorflow外,還有PyTorch, Theano和MXNet等。

我給你的建議是,找到一個你喜歡的軟件包,深入學習使用,不斷實踐來提升自己的技能。千萬不要跟別人爭論哪個深度學習框架更好。一來蘿卜白菜各有所愛,每個人都有自己的偏好;二來深度學習的江湖水很深,言多有失。說錯了話,別的門派可能會不高興喲。

我比較喜歡Tensorflow。但是Tensorflow本身是個底層庫。雖然隨著版本的更迭,界面越來越易用。但是對初學者來說,許多細節依然有些過于瑣碎,不容易掌握。

初學者的耐心有限,挫折過多容易放棄。

幸好,還有幾個高度抽象框架,是建立在Tensorflow之上的。如果你的任務是應用現成的深度學習模型,那么這些框架會給你帶來非常大的便利。

這些框架包括Keras, TensorLayer等。咱們今天將要使用的,叫做TFlearn。

它的特點,就是長得很像Scikit-learn。這樣如果你熟悉經典機器學習模型,學起來會特別輕松省力。

實戰

閑話就說這么多,下面咱們繼續寫代碼吧。

寫代碼之前,請回到終端下,運行以下命令,安裝幾個軟件包:

pip install tensorflow
pip install tflearn

執行完畢后,回到Notebook里。

我們呼叫tflearn框架。

import tflearn

然后,我們開始搭積木一樣,搭神經網絡層。

首先是輸入層。

net = tflearn.input_data(shape=[None, 11])

注意這里的寫法,因為我們輸入的數據,是特征矩陣。而經過我們處理后,特征矩陣現在有11列,因此shape的第二項寫11。

shape的第一項,None,指的是我們要輸入的特征矩陣行數。因為我們現在是搭建模型,后面特征矩陣有可能一次輸入,有可能分成組塊輸入,長度可大可小,無法事先確定。所以這里填None。tflearn會在我們實際執行訓練的時候,自己讀入特征矩陣的尺寸,來處理這個數值。

下面我們搭建隱藏層。這里我們要使用深度學習,搭建3層。

net = tflearn.fully_connected(net, 6, activation='relu')
net = tflearn.fully_connected(net, 6, activation='relu')
net = tflearn.fully_connected(net, 6, activation='relu')

activation剛才在深度學習游樂場里面我們遇到過,代表激活函數。如果沒有它,所有的輸入輸出都是線性關系。

Relu函數是激活函數的一種。它大概長這個樣子。

如果你想了解激活函數的更多知識,請參考后文的學習資源部分。

隱藏層里,每一層我們都設置了6個神經元。其實至今為之,也不存在最優神經元數量的計算公式。工程界的一種做法,是把輸入層的神經元數量,加上輸出層神經元數量,除以2取整。咱們這里就是用的這種方法,得出6個。

搭好了3個中間隱藏層,下面我們來搭建輸出層。

net = tflearn.fully_connected(net, 2, activation='softmax')
net = tflearn.regression(net)

這里我們用兩個神經元做輸出,并且說明使用回歸方法。輸出層選用的激活函數為softmax。處理分類任務的時候,softmax比較合適。它會告訴我們每一類的可能性,其中數值最高的,可以作為我們的分類結果。

積木搭完了,下面我們告訴TFlearn,以剛剛搭建的結構,生成模型。

model = tflearn.DNN(net)

有了模型,我們就可以使用擬合功能了。你看是不是跟Scikit-learn的使用方法很相似呢?

model.fit(X_train, y_train, n_epoch=30, batch_size=32, show_metric=True)

注意這里多了幾個參數,我們來解釋一下。

  • n_epoch:數據訓練幾個輪次。
  • batch_size:每一次輸入給模型的數據行數。
  • show_metric:訓練過程中要不要打印結果。

以下就是電腦輸出的最終訓練結果。其實中間運行過程看著更激動人心,你自己試一下就知道了。

Training Step: 7499  | total loss: ?[1m?[32m0.39757?[0m?[0m | time: 0.656s
| Adam | epoch: 030 | loss: 0.39757 - acc: 0.8493 -- iter: 7968/8000
Training Step: 7500  | total loss: ?[1m?[32m0.40385?[0m?[0m | time: 0.659s
| Adam | epoch: 030 | loss: 0.40385 - acc: 0.8487 -- iter: 8000/8000
--

我們看到訓練集的損失(loss)大概為0.4左右。

打開終端,我們輸入

tensorboard --logdir=/tmp/tflearn_logs/

然后在瀏覽器里輸入http://localhost:6006/

可以看到如下界面:

這是模型訓練過程的可視化圖形,可以看到準確度的攀升和損失降低的曲線。

打開GRAPHS標簽頁,我們可以查看神經網絡的結構圖形。

我們搭積木的過程,在此處一目了然。

評估

訓練好了模型,我們來嘗試做個預測吧。

看看測試集的特征矩陣第一行。

X_test[0]
array([ 1.75486502, -0.57369368, -0.55204276, -1.09168714, -0.36890377,
        1.04473698,  0.8793029 , -0.92159124,  0.64259497,  0.9687384 ,
        1.61085707])

我們就用它來預測一下分類結果。

y_pred = model.predict(X_test)

打印出來看看:

y_pred[0]
array([ 0.70956731,  0.29043278], dtype=float32)

模型判斷該客戶不流失的可能性為0.70956731。

我們看看實際標簽數據:

y_test[0]
array([ 1.,  0.])

客戶果然沒有流失。這個預測是對的。

但是一個數據的預測正確與否,是無法說明問題的。我們下面跑整個測試集,并且使用evaluate函數評價模型。

score = model.evaluate(X_test, y_test)
print('Test accuarcy: %0.4f%%' % (score[0] * 100))
Test accuarcy: 84.1500%

在測試集上,準確性達到84.15%,好樣的!

希望在你的努力下,機器做出的準確判斷可以幫助銀行有效鎖定可能流失的客戶,降低客戶的流失率,繼續日進斗金。

說明

你可能覺得,深度學習也沒有什么厲害的嘛。原先的決策樹算法,那么簡單就能實現,也可以達到80%以上的準確度。寫了這么多語句,深度學習結果也無非只提升了幾個百分點而已。

首先,準確度達到某種高度后,提升是不容易的。這就好像學生考試,從不及格到及格,付出的努力并不需要很高;從95分提升到100,卻是許多人一輩子也沒有完成的目標。

其次,在某些領域里,1%的提升意味著以百萬美元計的利潤,或者幾千個人的生命因此得到拯救。

第三,深度學習的崛起,是因為大數據的環境。在許多情況下,數據越多,深度學習的優勢就越明顯。本例中只有10000條記錄,與“大數據”的規模還相去甚遠。

學習資源

如果你對深度學習感興趣,推薦以下學習資源。

首先是教材。

第一本是Deep Learning,絕對的經典。

第二本是Hands-On Machine Learning with Scikit-Learn and TensorFlow: Concepts, Tools, and Techniques to Build Intelligent Systems,深入淺出,通俗易懂。

其次是MOOC。

推薦吳恩達(Andrew Ng)教授在Coursera上的兩門課程。

一門是機器學習。這課推出有年頭了,但是非常有趣和實用。具體的介紹請參考拙作《機器學習哪里有這么玄?》以及《如何用MOOC組合掌握機器學習?》。

一門是深度學習。這是個系列課程,包括5門子課程。今年推出的新課,自成體系,但是最好有前面那門課程作為基礎。

討論

你對深度學習感興趣嗎?之前有沒有做過深度學習項目?你掌握了哪些深度學習框架?有沒有什么建議給初學者?歡迎留言,把心得分享給大家,我們一起交流討論。

喜歡請點贊。還可以微信關注和置頂我的公眾號“玉樹芝蘭”(nkwangshuyi)

如果你對數據科學感興趣,不妨閱讀我的系列教程索引貼《如何高效入門數據科學?》,里面還有更多的有趣問題及解法。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容