FastText 分析與實踐


一. 前言

自然語言處理(NLP)是機器學習,人工智能中的一個重要領(lǐng)域。文本表達是 NLP中的基礎(chǔ)技術(shù),文本分類則是 NLP 的重要應(yīng)用。在 2016 年, Facebook Research 開源了名為 fasttext[1] 的文本表達和分類的計算庫。 fasttext 是基于文章 [2], [3], [4] 所提出算法的實現(xiàn),針對變形詞匯表達,線性分類優(yōu)化提供了優(yōu)秀的解決方案。 本文試圖梳理 FastText 在文本表達和文本分類方面的工作,并進行實踐。

二. 詞嵌入

1. 背景介紹

詞表達是 NLP 處理中的關(guān)鍵技術(shù)。常見的方式如 one-hot,使用了高維稀疏向量表示詞匯,這樣的特征可以反映詞出現(xiàn)的頻率,卻不能反映詞之間的關(guān)系。與此對應(yīng),詞嵌入技術(shù)將詞匯的上下文關(guān)系嵌入一個低維空間,其中比較有代表性的方法如 word2vec [5], GloVe[6]。本段落將分析 word2vec 的原理。

2. word2vec

word2vec 將詞的上下文關(guān)系嵌入到低維空間。更具體而言,word2vec 將詞的上下文關(guān)系轉(zhuǎn)換為分類關(guān)系,并以此同時訓練詞嵌入向量和 logistic regression 分類器。

1. 邏輯回歸

logistic regression 是經(jīng)典的線性分類模型。廣義上而言,線性模型由三個部分組成, 1. 輸入向量 2. 線性系數(shù) 3. 偏移(bias),而 bias 可以進一步表示成線性系數(shù)。所以,二分類的線性分類問題可以表示為輸入 $x$ 和系數(shù) $w$ 的內(nèi)積結(jié)果 $w^Tx$,結(jié)果的正負決定了數(shù)據(jù)的類別。分類器參數(shù)通過最小化損失函數(shù) $l(y, w^Tx)$來完成,不同的損失函數(shù)定義了不同的分類模型。

下面列舉了svm, lr 的損失函數(shù),其中 $y\in \{-1, 1\}$
logit : $\Sigma_{i=1}^N ln(1 + e^{-y_i w^Tx_i})$
svm : $\Sigma_{i=1}^N max(0, 1- y _iw^Tx_i) + \lambda||w||^2$

logistic regression 也廣泛地應(yīng)用在多分類問題中,通過 softmax 函數(shù)計算數(shù)據(jù)屬于每個類別的概率完成分類。因為分類神經(jīng)網(wǎng)絡(luò)的輸出層通常也設(shè)定為 softmax 函數(shù),所以多分類 lr 也可以表示為淺層神經(jīng)網(wǎng)絡(luò)。

下面我們分析 word2vec 如何將詞的上下文關(guān)系轉(zhuǎn)化為分類任務(wù)。

2. skip-gram

我們將詞的上下文定義為以詞 $w_i$ 為中心,窗口為 $k$ 前后范圍內(nèi)的詞 $C_i = {w_{i-k}, w_{i-k+1}, ..., w_{i-1}, w_{i+1}, ... w_{i+k}}$。

skip-gram 將詞之間的關(guān)系變成了 $|V|$ 多分類問題,其中 $|V|$ 是詞庫大小。每個詞有兩個變量 $x_i, w_i$,前者為詞嵌入向量,后者是線性分類器的系數(shù),在相關(guān)文章中又稱為上下文向量。

skip-gram 用中心詞匯來預(yù)測其上下文,如下圖:

skip-gram.png

3. cbow

Continuous bag of words(cbow) 是與 skip-gram 相對應(yīng)的另一種將上下文轉(zhuǎn)化為分類任務(wù)的方式。

image.png

上圖顯示 cbow 用上下文詞向量的加和結(jié)果來預(yù)測其中心詞匯,其它的方式還有拼接。需要注意的是拼接的方式會導致上下文向量 $c_i$ 維度增大 $2 k$ 倍數(shù), $k$ 為上下文窗口。

4. 針對多分類的計算優(yōu)化

word2vec 將上下文關(guān)系轉(zhuǎn)化為多分類任務(wù),進而訓練邏輯回歸模型,這里的類別數(shù)量是 $|V|$ 詞庫大小。通常的文本數(shù)據(jù)中,詞庫少則數(shù)萬,多則百萬,在訓練中直接訓練多分類邏輯回歸并不現(xiàn)實。

word2vec [5] 中提供了兩種針對大規(guī)模多分類問題的優(yōu)化手段, negative sampling 和 hierarchical softmax。以 skip-gram 為例,中心詞對上下文的詞類是正面例子,對所有其它的詞則是負面例子。在優(yōu)化中,negative sampling 只更新少量負面類,從而減輕了計算量。hierarchical softmax 將詞庫表示成前綴樹,從樹根到葉子的路徑可以表示為一系列二分類器,一次多分類計算的復(fù)雜度從 $|V|$ 降低到了樹的高度。

3. 總結(jié)

詞嵌入技術(shù)將詞的上下文關(guān)系嵌入到低維空間。word2vec 將詞的局部上下文轉(zhuǎn)化為了多分類任務(wù),從而訓練邏輯回歸模型,并將邏輯回歸模型中的輸入部分作為詞嵌入輸出。

三. FastText 子詞嵌入

1. 背景介紹

詞嵌入技術(shù)在 NLP 領(lǐng)域發(fā)揮了越來越大的作用,相較于 one-hot 特征,詞嵌入具有維度低,體現(xiàn)詞間關(guān)系等特點。但傳統(tǒng)的詞嵌入技術(shù)忽略了詞的微變形特性,如英語中動詞的第三人稱,進行時或過去時變形。針對這個問題, [2] 提出使用子詞來學習詞的表達,每個詞由內(nèi)部的 n-gram 字母串組成。在中文處理中, [7] 提出了類似的算法。

2. FastText 的實現(xiàn)

FastText 的子詞嵌入在 word2vec 的基礎(chǔ)上,引入了子詞這個因素,從而使得詞的微變形關(guān)系也能映射到嵌入空間中。

1. 子詞

在 fasttext 中,每個詞被看做是 n-gram 字母串包。為了區(qū)分前后綴情況,"<", ">" 符號被加到了詞的前后端。除了詞的子串外,詞本身也被包含進了 n-gram 字母串包。以 where 為例,$n=3$ 的情況下,其子串分別為
<wh, whe, her, ere, re>,以及其本身 <where>

注意,這里的 her 與單詞 <her> 是不同的。

現(xiàn)在我們看看 fasttext 具體是怎么計算子串的。

void Dictionary::initNgrams() {
  for (size_t i = 0; i < size_; i++) {
    // 加入前后綴符號 <, > 
    std::string word = BOW + words_[i].word + EOW;
    // 詞本身作為特殊子串加入包中
    words_[i].subwords.push_back(i);
    // 計算并將詞子串加入包中
    computeNgrams(word, words_[i].subwords);
  }
}

// 計算詞子串
void Dictionary::computeNgrams(const std::string& word,
                               std::vector<int32_t>& ngrams,
                               std::vector<std::string>& substrings) const {
  for (size_t i = 0; i < word.size(); i++) {
    std::string ngram;
    if ((word[i] & 0xC0) == 0x80) continue;
    for (size_t j = i, n = 1; j < word.size() && n <= args_->maxn; n++) {
      ngram.push_back(word[j++]);
      // 將 utf-8 編碼數(shù)據(jù)部分推入 ngram
      while (j < word.size() && (word[j] & 0xC0) == 0x80) {
        ngram.push_back(word[j++]);
      }
      if (n >= args_->minn && !(n == 1 && (i == 0 || j == word.size()))) {
        // fasttext 使用 open hash 進行 string->int 轉(zhuǎn)換
        int32_t h = hash(ngram) % args_->bucket;
        ngrams.push_back(nwords_ + h);
        substrings.push_back(ngram);
      }
    }
  }
}

在 fasttext 中,minn 和 maxn 參數(shù)控制了子串的長度,其默認值分別為 3, 6。再以 where 為例,在默認子串參數(shù)情況下,其子串包內(nèi)容為:

<wh, <whe, <wher, <where, whe, wher, where, where>, her, here, here>, ere, ere>, re> 以及自身 <where>

下面我們看 fasttext 是如何用子串來構(gòu)造詞嵌入的,這里注意每個子串也有自身的嵌入表示。

void Model::computeHidden(const std::vector<int32_t>& input, Vector& hidden) const {
  assert(hidden.size() == hsz_);
  hidden.zero();
  for (auto it = input.cbegin(); it != input.cend(); ++it) {
    if(quant_) {
      // product quantization 優(yōu)化
      // 子串加和
      hidden.addRow(*qwi_, *it);
    } else {
      // 子串加和
      hidden.addRow(*wi_, *it);
    }
  }
  // 平均
  hidden.mul(1.0 / input.size());
}

代碼非常簡明直接,每個詞是其子串嵌入表示的加和平均。

fasttext 的另一個優(yōu)勢,是學習未知詞的表達。以 working 為例,如果這個詞在學習文本中沒有出現(xiàn)過,但文本包含了 work, 和 ing,那么 working 的詞嵌入向量也能夠合理地計算出來。

2. skip-gram

一個詞的嵌入是其子串嵌入的加和平均。當這個嵌入表示計算出來后,后續(xù)步驟與 word2vec 的 skip-gram 相同。下面是 fasttext 的 skip-gram 實現(xiàn)代碼:

void FastText::skipgram(Model& model, real lr,
                        const std::vector<int32_t>& line) {
  std::uniform_int_distribution<> uniform(1, args_->ws);
  for (int32_t w = 0; w < line.size(); w++) {
    // 窗口大小
    int32_t boundary = uniform(model.rng);
    const std::vector<int32_t>& ngrams = dict_->getNgrams(line[w]);
    for (int32_t c = -boundary; c <= boundary; c++) {
      if (c != 0 && w + c >= 0 && w + c < line.size()) {
        // 通過 ngrams 計算 line[w] 的表示,通過邏輯回歸優(yōu)化 ngrams 的嵌入表示
        model.update(ngrams, line[w + c], lr);
      }
    }
  }
}   

我們來詳細看看 Model::update 函數(shù)是怎么用邏輯回歸來優(yōu)化 ngrams 的嵌入表示

void Model::update(const std::vector<int32_t>& input, int32_t target, real lr) {
  assert(target >= 0);
  assert(target < osz_);
  if (input.size() == 0) return;
  // 子串加權(quán)平均得到詞嵌入表示
  computeHidden(input, hidden_);
  
  // 針對分類問題的 3 種處理
  // 1. negative sampling 優(yōu)化
  if (args_->loss == loss_name::ns) {
    loss_ += negativeSampling(target, lr);
  } 
  // 2. hierarchical softmax 優(yōu)化
    else if (args_->loss == loss_name::hs) {
    loss_ += hierarchicalSoftmax(target, lr);
  } 
  // 3. 直接計算多分類邏輯回歸
  // 在文本分類模式下使用
    else {
    loss_ += softmax(target, lr);
  }
  nexamples_ += 1;

  // 邏輯回歸產(chǎn)生了導數(shù),在文本分類情況下導數(shù)向量需要除以子串數(shù)量
  if (args_->model == model_name::sup) {
    grad_.mul(1.0 / input.size());
  }
  // 將導數(shù)直接加到子串向量中
  for (auto it = input.cbegin(); it != input.cend(); ++it) {
    wi_->addRow(grad_, *it, 1.0);
  }
}

在第二段,我們簡單介紹了針對大量多分類的優(yōu)化策略, negative sampling 和 hierarchical sampling。這里我們看看 fasttext 是怎樣實現(xiàn)這些策略。

// 根據(jù)詞頻構(gòu)構(gòu)建采樣表
void Model::initTableNegatives(const std::vector<int64_t>& counts) {
  real z = 0.0;
  for (size_t i = 0; i < counts.size(); i++) {
    z += pow(counts[i], 0.5);
  }
  // 每個詞類在采樣表中數(shù)量與其詞頻開方成比例
  for (size_t i = 0; i < counts.size(); i++) {
    real c = pow(counts[i], 0.5);
    for (size_t j = 0; j < c * NEGATIVE_TABLE_SIZE / z; j++) {
      negatives.push_back(i);
    }
  }
  std::shuffle(negatives.begin(), negatives.end(), rng);
}

// 負采樣
real Model::negativeSampling(int32_t target, real lr) {
  real loss = 0.0;
  grad_.zero();
  // 負采樣 args_->neg 個類別
  for (int32_t n = 0; n <= args_->neg; n++) {
   if (n == 0) {
      // 將當前詞作為正面例子對 target 類進行二分邏輯回歸訓練
      loss += binaryLogistic(target, true, lr);
    } else {
      // 將當前詞作為負面例子對負采樣類進行二分邏輯回歸訓練
      loss += binaryLogistic(getNegative(target), false, lr);
    }
  }
  return loss;
}

// 負采樣一個類
int32_t Model::getNegative(int32_t target) {
  int32_t negative;
  // 輪詢采樣表
  do {
    negative = negatives[negpos];
    negpos = (negpos + 1) % negatives.size();
  } while (target == negative);
  return negative;
}

下面我們看看 hierarchical softmax 的實現(xiàn)

// 根據(jù)詞頻構(gòu)造前綴樹,因為樹的內(nèi)部節(jié)點數(shù)量為 |V| - 1 所以可以用 |V| * 2 的數(shù)組存儲樹結(jié)構(gòu)
void Model::buildTree(const std::vector<int64_t>& counts) {
  tree.resize(2 * osz_ - 1);
  for (int32_t i = 0; i < 2 * osz_ - 1; i++) {
    tree[i].parent = -1;
    tree[i].left = -1;
    tree[i].right = -1;
    tree[i].count = 1e15;
    tree[i].binary = false;
  }
  for (int32_t i = 0; i < osz_; i++) {
    tree[i].count = counts[i];
  }
  int32_t leaf = osz_ - 1;
  int32_t node = osz_;
  for (int32_t i = osz_; i < 2 * osz_ - 1; i++) {
    int32_t mini[2];
    for (int32_t j = 0; j < 2; j++) {
      if (leaf >= 0 && tree[leaf].count < tree[node].count) {
        mini[j] = leaf--;
      } else {
        mini[j] = node++;
      }
    }
    tree[i].left = mini[0];
    tree[i].right = mini[1];
    tree[i].count = tree[mini[0]].count + tree[mini[1]].count;
    tree[mini[0]].parent = i;
    tree[mini[1]].parent = i;
    tree[mini[1]].binary = true;
  }
  for (int32_t i = 0; i < osz_; i++) {
    std::vector<int32_t> path;
    std::vector<bool> code;
    int32_t j = i;
    while (tree[j].parent != -1) {
      path.push_back(tree[j].parent - osz_);
      code.push_back(tree[j].binary);
      j = tree[j].parent;
    }
    paths.push_back(path);
    codes.push_back(code);
  }
}

// 用 hierarchical softmax 進行多分類計算
real Model::hierarchicalSoftmax(int32_t target, real lr) {
  real loss = 0.0;
  grad_.zero();
  // 詞的 0,1 表示
  const std::vector<bool>& binaryCode = codes[target];
  // 詞到樹根的路徑
  const std::vector<int32_t>& pathToRoot = paths[target];
  for (int32_t i = 0; i < pathToRoot.size(); i++) {
    // 路徑上的節(jié)點對應(yīng)二分類, 0,1 編碼決定分類
    // 用二分邏輯回歸進行訓練
    loss += binaryLogistic(pathToRoot[i], binaryCode[i], lr);
  }
  return loss;
}  

最后我們看一下二分邏輯回歸的實現(xiàn)。

// 用一個正/負面例子更新二分邏輯回歸模型
real Model::binaryLogistic(int32_t target, bool label, real lr) {
  real score = sigmoid(wo_->dotRow(hidden_, target));
  real alpha = lr * (real(label) - score);
  grad_.addRow(*wo_, target, alpha);
  wo_->addRow(hidden_, target, alpha);
  if (label) {
    return -log(score);
  } else {
    return -log(1.0 - score);
  }
}
     

3. cbow

第二段講解了 cbow 的原理是用中心詞的上下文來預(yù)測中心詞, 這里我們看看 fasttext 是如何實現(xiàn) cbow 的。

void FastText::cbow(Model& model, real lr,
                    const std::vector<int32_t>& line) {
  std::vector<int32_t> bow;
  std::uniform_int_distribution<> uniform(1, args_->ws);
  for (int32_t w = 0; w < line.size(); w++) {
    int32_t boundary = uniform(model.rng);
    bow.clear();
    for (int32_t c = -boundary; c <= boundary; c++) {
      // 將上下文的子字符串加入包中
      if (c != 0 && w + c >= 0 && w + c < line.size()) {
        const std::vector<int32_t>& ngrams = dict_->getNgrams(line[w + c]);
        bow.insert(bow.end(), ngrams.cbegin(), ngrams.cend());
      }
    }
    // 用所有子字符串來預(yù)測中心詞,從而更新參數(shù)
    model.update(bow, line[w], lr);
  }
}

fasttext 的處理非常簡潔,將上下文的子串全部加和平均作為輸入去預(yù)測中心詞。

3. 總結(jié)

fasttext 利用子詞改良了詞嵌入的質(zhì)量,在嵌入學習中考慮了詞的內(nèi)部結(jié)構(gòu)。在具體實現(xiàn)中, fasttext 用子詞向量的加和平均表示詞向量, 提供了 skip-gram 和 cbow 模式訓練詞嵌入表達。

四. FastText 線性文本分類優(yōu)化

1. 背景介紹

對于文本分類而言,線性分類器往往能夠達到非常優(yōu)秀,媲美深度模型的效果。通常,線性模型的參數(shù)數(shù)量與詞庫大小相關(guān),導致模型規(guī)模巨大。 fasttext 針對線性分類器模型進行了諸多優(yōu)化,在不顯著損失分類器精度的情況下,減少了內(nèi)存使用和計算時間。

2. fasttext 分類模型架構(gòu)

fasttext 的詞嵌入是通過分類學習完成的,所以詞嵌入和文本分類模型可以用下圖統(tǒng)一表示。

Model Architecture.png

其中 $x_1, ..., x_N$ 表示一個文本中的 ngram 向量,一個文本的表示是所有 ngram 的加和平均。這和前文中提到的 cbow 相似,cbow 用上下文的 ngram 去預(yù)測中心詞,而此處用全部的 ngram 去預(yù)測指定類別。

與前文詞嵌入模型一樣, fasttext 模型在進行文本分類監(jiān)督訓練時,既學習詞嵌入表達,也學習分類器線性系數(shù)。

3. 優(yōu)化

1. 子空間量化

product quantization [8] 是一種保存數(shù)據(jù)間距離的壓縮技術(shù)。PQ 用一個碼本來近似數(shù)據(jù),與傳統(tǒng)的 keams 訓練碼本不同的是, PQ 將數(shù)據(jù)空間劃分為 k 個子空間,并分別用 kmeans 學習子空間碼本。數(shù)據(jù)的近似和重建均在子空間完成,最終拼接成結(jié)果。

在 fasttext 中,子空間碼本大小為 256,可以用 1 byte 表示。子空間的數(shù)量在 [2, d/2] 間取值。

除了用 PQ 對數(shù)據(jù)進行量化壓縮,fasttext 還提供了對分類系數(shù)的 PQ 量化選項。

PQ 的優(yōu)化能夠在不影響分類其表現(xiàn)的情況下,將分類模型壓縮為原大小的 $\frac{1}{10}$。

2. 裁剪字典內(nèi)容

fasttext 提供了一個誘導式裁剪字典的算法,保證裁剪后的字典內(nèi)容覆蓋了所有的文章。具體而言,fasttext 存有一個保留字典,并在線處理文章,如果新的文章沒有被保留字典涵蓋,則從該文章中提取一個 norm 最大的詞和其子串加入字典中。

字典裁剪能夠有效將模型的數(shù)量減少,甚至到原有的 $\frac{1}{100}$。

4. 總結(jié)

fasttext 利用 Product Quantization 對字典中的 詞嵌入向量進行了壓縮,并使用誘導式字典方法,構(gòu)造涵蓋全部文本的字典。兩者結(jié)合,能夠在不明顯損害分類算法表現(xiàn)的情況下,將分類模型大小減小數(shù)百倍 。

五. 實踐

本段我們對詞嵌入進行實踐。詞嵌入的比較有兩種方式,直接比較是驗證詞表達保存了人為標注的詞間關(guān)系,間接比較則是通過使用嵌入表達向量進行進一步學習,比如情緒預(yù)測[5],通過模型的表現(xiàn)判斷詞嵌入的質(zhì)量。

我們用中文維基數(shù)據(jù) [9] 進行了訓練,fasttext 的參數(shù)使用默認值, epoch 設(shè)置為50。此外 facebook 還提供了訓練好的多國語言表達 [10].

以下我們分別用 nn(最近鄰) 對中文維基訓練結(jié)果進行實踐:

// 乒乓球的近似詞匯
Query word? 乒乓球
壁球 0.837808
曲棍球 0.792717
網(wǎng)球 0.792332
排球 0.789665
手球 0.780589
田徑 0.780279
桌球 0.778775
舉重 0.776161
沙灘排球 0.775708
乒乓球隊 0.772797
// "男乒乓球" 并不存在, fasttext 仍然可以得到合理結(jié)果
Query word? 男乒乓球
乒乓球 0.767142
中國男子乒乓球隊 0.725763
兵乓球 0.691391
體操 0.688922
張怡寧 0.688578
陳若琳 0.681761
跳水隊 0.678217
打乒乓球 0.677547
王楠 0.671578
吳敏霞 0.669105

六. 總結(jié)

fasttext 是 facebook 開源的關(guān)于文本表達和文本分類的計算庫。fasttext 結(jié)合詞的子串信息計算詞表達,提高了對微變形詞匯的學習。針對文本分類模型, fasttext 使用了子空間量化和字典裁剪的策略,在不損失模型精度的情況下,將模型大小縮減數(shù)百倍。

在 fasttext 之上,可以做進一步優(yōu)化,一個方向是在文本分類模型中,文本表達用類似 tf-idf 的方式對詞進行加權(quán)平均。在字典裁剪算法上,一個涵蓋所有文本的詞并不一定有區(qū)分能力,比如 "the" 這個單詞,可以嘗試從保留區(qū)分能力的視角來保留字典。

七. 引用

[1] https://github.com/facebookresearch/fastText
[2] Bojanowski, Piotr and Grave, Edouard and Joulin, Armand and Mikolov, Tomas. "Enriching Word Vectors with Subword Information".
[3] Joulin, Armand and Grave, Edouard and Bojanowski, Piotr and Mikolov, Tomas. "Bag of Tricks for Efficient Text Classification".
[4]Joulin, Armand and Grave, Edouard and Bojanowski, Piotr and Douze, Matthijs and Jegou, Herve and Mikolov, Tomas. "FastText.zip: Compressing text classification models".
[5] Mikolov, Tomas, et al. "Efficient Estimation of Word Representations in Vector Space".
[6] Jeffrey Pennington, Richard Socher, and Christopher D. Manning. 2014. "GloVe: Global Vectors for Word Representation".
[7] Xinxiong Chen, Lei Xu, Zhiyuan Liu, Maosong Sun, and Huanbo Luan. 2015. Joint learning of character and word embeddings. In Proc. IJCAI.
[8] Product quantization for nearest neighbor search. H Jegou, M Douze, C Schmid. IEEE Transactions on Pattern Analysis and Machine Intelligence
[9] https://dumps.wikimedia.org/zhwiki/
[10] https://github.com/facebookresearch/fastText/blob/master/pretrained-vectors.md

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

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