機器學習算法之關聯規則挖掘

定義

??關聯分析是一種簡單、實用的分析技術,就是發現存在于大量數據集中的關聯性或相關性,從而描述了一個事物中某些屬性同時出現的規律和模式。
??關聯分析是從大量數據中發現項集之間有趣的關聯和相關聯系。關聯分析的一個典型例子是購物籃分析。該過程通過發現顧客放人其購物籃中的不同商品之間的聯系,分析顧客的購買習慣。通過了解哪些商品頻繁地被顧客同時購買,這種關聯的發現可以幫助零售商制定營銷策略。其他的應用還包括價目表設計、商品促銷、商品的排放和基于購買模式的顧客劃分。
??可從數據庫中關聯分析出形如“由于某些事件的發生而引起另外一些事件的發生”之類的規則。如“67%的顧客在購買啤酒的同時也會購買尿布”,因此通過合理的啤酒和尿布的貨架擺放或捆綁銷售可提高超市的服務質量和效益。又如“‘C語言’課程優秀的同學,在學習‘數據結構’時為優秀的可能性達88%”,那么就可以通過強化“C語言”的學習來提高教學效果。

相關概念

支持度與置信度

關聯規則可以描述成:項集 → 項集。項集X出現的事務次數(亦稱為support count)定義為:

σ(X)=|ti|X?ti,ti∈T|

其中,ti表示某個事務(TID),T表示事務的集合。關聯規則X?Y的支持度(support):

support(X?Y)=σ(X∪Y)|T|

支持度刻畫了項集X∪Y的出現頻次。置信度(confidence)定義如下:

confidence(X?Y)=σ(X∪Y)σ(X)

對概率論稍有了解的人,應該看出來:置信度可理解為條件概率p(Y|X),度量在已知事務中包含了X時包含Y的概率。
??對于靠譜的關聯規則,其支持度與置信度均應大于設定的閾值。那么,關聯分析問題即等價于:對給定的支持度閾值min_sup、置信度閾值min_conf,找出所有的滿足下列條件的關聯規則:

支持度>=min_sup
置信度>=min_conf

把支持度大于閾值的項集稱為頻繁項集(frequent itemset)。因此,關聯規則分析可分為下列兩個步驟:

  • 生成頻繁項集F=X∪Y
  • 在頻繁項集F中,找出所有置信度大于最小置信度的關聯規則X?Y

舉個例子:

TID Items
001 Cola, Egg, Ham
002 Cola, Diaper, Beer
003 Cola, Diaper, Beer, Ham
004 Diaper, Beer

??TID代表交易流水號,Items代表一次交易的商品。我們對這個數據集進行關聯分析,可以找出關聯規則{Diaper}→{Beer}。
??它代表的意義是:購買了Diaper的顧客會購買Beer。這個關系不是必然的,但是可能性很大,這就已經足夠用來輔助商家調整Diaper和Beer的擺放位置了,例如擺放在相近的位置,進行捆綁促銷來提高銷售量。
??1、事務:每一條交易稱為一個事務,例如示例1中的數據集就包含四個事務。
  2、項:交易的每一個物品稱為一個項,例如Cola、Egg等。
  3、項集:包含零個或多個項的集合叫做項集,例如{Cola, Egg, Ham}。
  4、k?項集:包含k個項的項集叫做k-項集,例如{Cola}叫做1-項集,{Cola, Egg}叫做2-項集。
  5、支持度計數:一個項集出現在幾個事務當中,它的支持度計數就是幾。例如{Diaper, Beer}出現在事務 002、003和004中,所以它的支持度計數是3。
  6、支持度:支持度計數除于總的事務數。例如上例中總的事務數為4,{Diaper, Beer}的支持度計數為3,所以它的支持度是3÷4=75%,說明有75%的人同時買了Diaper和Beer。
  7、頻繁項集:支持度大于或等于某個閾值的項集就叫做頻繁項集。例如閾值設為50%時,因為{Diaper, Beer}的支持度是75%,所以它是頻繁項集。
  8、前件和后件:對于規則{Diaper}→{Beer},{Diaper}叫做前件,{Beer}叫做后件。
  9、置信度:對于規則{Diaper}→{Beer},{Diaper, Beer}的支持度計數除于{Diaper}的支持度計數,為這個規則的置信度。例如規則{Diaper}→{Beer}的置信度為3÷3=100%。說明買了Diaper的人100%也買了Beer。
  10、強關聯規則:大于或等于最小支持度閾值和最小置信度閾值的規則叫做強關聯規則。關聯分析的最終目標就是要找出強關聯規則[1] 。

挖掘過程

頻繁項集挖掘

??在進行頻繁項集挖掘之前,我們需要根據現有數據的項集items得到所有可能的項集組合。假設現在有個只包含a,b,c,d四個項集的事務,其可能的項集組合如下:

組合.png

??根據支持度的定義,得到如下的先驗定理:

  • 定理1:如果一個項集是頻繁的,那么其所有的子集(subsets)也一定是頻繁的。

?這個比較容易證明,因為某項集的子集的支持度一定不小于該項集。

  • 定理2:如果一個項集是非頻繁的,那么其所有的超集(supersets)也一定是非頻繁的。

??定理2是上一條定理的逆反定理。根據定理2,可以對項集樹進行如下剪枝:

剪枝1.png

頻繁項集的挖掘算法思路大致如下:

算法1.png
關聯規則挖掘

??關聯規則是由頻繁項集生成的,即對于Fk,找出項集hm,使得規則fk?hm?hm的置信度大于置信度閾值。同樣地,根據置信度定義得到如下定理:

  • 定理3:如果規則X?Y?X不滿足置信度閾值,則X的子集X′,規則X′?Y?X′也不滿足置信度閾值。

根據定理3,可對規則樹進行如下剪枝:

剪枝2.png

生成關聯規則的算法如下:

算法2.png

頻繁項集算法

Apriori

Apriori算法過程
process.png

??挖掘頻繁項集的過程如上圖所示:
??1. 由數據集生成候選項集C1(1表示每個候選項僅有一個數據項);再由C1通過支持度過濾,生成頻繁項集L1(1表示每個頻繁項僅有一個數據項)。
??2. 將L1的數據項兩兩拼接成C2。
??3. 從候選項集C2開始,通過支持度過濾生成L2。L2根據Apriori原理拼接成候選項集C3;C3通過支持度過濾生成L3……直到Lk中僅有一個或沒有數據項為止。
Apriori算法的代碼實現:

package com.qqmaster.com.machineLearning;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.qqmaster.com.machineLearning.node.AprioriNode;

public class Algorithm_Apriori {

    public static void main(String[] args) {
        List<AprioriNode> data = new ArrayList<AprioriNode>();
        String[] names = {"p1", "p2", "P3", "P4", "P5"};
        AprioriNode.attNames = names;
        
        AprioriNode d0 = new AprioriNode();
        boolean[] f0 = {true,true,false,false,true};
        d0.setFeatures(f0);
        
        AprioriNode d1 = new AprioriNode();
        boolean[] f1 = {false,true,false,true,false};
        d1.setFeatures(f1);
        
        AprioriNode d2 = new AprioriNode();
        boolean[] f2 = {false,false,true,true,false};
        d2.setFeatures(f2);
        
        AprioriNode d3 = new AprioriNode();
        boolean[] f3 = {true,true,false,true,false};
        d3.setFeatures(f3);
        
        AprioriNode d4 = new AprioriNode();
        boolean[] f4 = {true,false,true,false,false};
        d4.setFeatures(f4);
        
        AprioriNode d5 = new AprioriNode();
        boolean[] f5 = {false,true,true,false,false};
        d5.setFeatures(f5);
        
        AprioriNode d6 = new AprioriNode();
        boolean[] f6 = {true,false,true,false,false};
        d6.setFeatures(f6);
        
        AprioriNode d7 = new AprioriNode();
        boolean[] f7 = {true,true,true,false,true};
        d7.setFeatures(f7);
        
        AprioriNode d8 = new AprioriNode();
        boolean[] f8 = {true,true,true,false,false};
        d8.setFeatures(f8);

        AprioriNode d9 = new AprioriNode();
        boolean[] f9 = {true,false,true,false,true};
        d9.setFeatures(f9);
              
        data.add(d0);
        data.add(d1);
        data.add(d2);
        data.add(d3);
        data.add(d4);
        data.add(d5);
        data.add(d6);
        data.add(d7);
        data.add(d8);
        data.add(d9);
        for(double support = 0.1;support <= 0.9; support += 0.1){
            System.out.println("support -> " + support);
            Algorithm_Apriori apriori = new Algorithm_Apriori();
            System.out.println("\nFrequent Items -> " + apriori.aprior(data, support));
            System.out.println("\n--------------------------------\n");
        }
        
    }
    
    /**
     * Aprior 算法入口
     * 
     * @param data
     * @param support
     * @return
     */
    public List<Set<Integer>> aprior(List<AprioriNode> data, double support){
        List<Set<Integer>> freqItems = new ArrayList<Set<Integer>>();
        
        //1. 獲取meta items
        List<Set<Integer>> metaItems = metaFreqItems(data, support);
        
        //2. 迭代的獲取item集合超集合。
        do{
            freqItems.addAll(metaItems);
            System.out.println("step--" + freqItems);
            metaItems = supFreItems(data, metaItems, support);
        }while(metaItems != null && !metaItems.isEmpty());
        
        return freqItems;
    }

    /**
     * 初始化meta item項集,并根據support進行篩選,剪枝
     * 
     * @param data
     * @param support
     * @return
     */
    private List<Set<Integer>> metaFreqItems(List<AprioriNode> data, 
            double support){

        List<Set<Integer>> metaItems = new ArrayList<Set<Integer>>();

        for(int i=0; i < AprioriNode.attNames.length; i++){
            Set<Integer> set = new HashSet<Integer>();
            set.add(i);
            metaItems.add(set);
        }
        rmWithSupport(data, metaItems,support);
        
        return metaItems;
    }

    /**
     * 
     * @param data
     * @param supFreItems
     * @param support
     * @return
     */
    private List<Set<Integer>> supFreItems(List<AprioriNode> data, 
            List<Set<Integer>> subFreItems, 
            double support){
        if(subFreItems == null || subFreItems.size()<=1){
            return null;
        }

        int size = subFreItems.size();
        List<Set<Integer>> list = new ArrayList<Set<Integer>>();

        for(int i = 0; i < size - 1; i++){
            for(int j = i + 1; j < size; j++){
                Set<Integer> s = mergeSet(subFreItems.get(i), 
                        subFreItems.get(j));
                if(s!=null)
                    if(!list.contains(s))
                        list.add(s);
            }
        }
        rmWithSupport(data,list,support);
        return list;
    }

    /**
     * 求兩個集合的并集
     * 
     * @param set1
     * @param set2
     * @return
     */
    private Set<Integer> mergeSet(Set<Integer> set1, Set<Integer> set2){
        if(set1 == null || set2 == null)
            return null;
        Set<Integer> newSet = new HashSet<>(set1);
        set2.forEach(it -> {
            newSet.add(it);
        });
        if(newSet.size() > set1.size() + 1)
            return null;
        return newSet;
    }

    
    /**
     * 根據支持度,移除不滿足支持度的項集
     * 
     * @param data
     * @param items
     * @param support
     * @return
     */
    private void rmWithSupport(List<AprioriNode> data,
            List<Set<Integer>> items, 
            double support){

        for(int i = items.size() - 1; i >= 0; i--){
            if(support(data, items.get(i)) < support)
                items.remove(i);
        }
    }


    /**
     * 獲取指定item項集的支持度-support
     * @return
     */
    private double support(List<AprioriNode> data, Set<Integer> items){
        if(data == null || data.size() < 1)
            return 0.0;

        int count = 0;
        for(AprioriNode d:data){
            if(isFreqItems(d.getFeatures(), items)) count++;
        }

        return 1.0 * count / data.size();
    }


    /**
     * 判斷集合是否中的item是否均命中。
     * 
     * @param features
     * @param items
     * @return
     */
    private boolean isFreqItems(boolean[] features, Set<Integer> items){

        if(items == null || items.size() < 1) return false;

        for(Integer i : items)
            if(!features[i])  return false;

        return true;
    }
}

挖掘結果展示:
??假設我們現在有以下數據記錄:

TID P1 P2 P3 P4 P5
1 1 1 0 0 1
2 0 1 0 1 0
3 0 1 1 0 0
4 1 1 0 1 0
5 1 0 1 0 0
6 0 1 1 0 0
7 1 0 1 0 0
8 1 1 1 0 1
9 1 1 1 0 0
10 1 0 1 0 1

??根據不同的support,我們會得到不同的頻繁項集挖掘結果。

support -> 0.1
step--[[0], [1], [2], [3], [4]]
step--[[0], [1], [2], [3], [4], [0, 1], [0, 2], [0, 3], [0, 4], [1, 2], [1, 3], [1, 4], [2, 3], [2, 4]]
step--[[0], [1], [2], [3], [4], [0, 1], [0, 2], [0, 3], [0, 4], [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 4], [1, 2, 4]]
step--[[0], [1], [2], [3], [4], [0, 1], [0, 2], [0, 3], [0, 4], [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 4], [1, 2, 4], [0, 1, 2, 4]]

Frequent Items -> [[0], [1], [2], [3], [4], [0, 1], [0, 2], [0, 3], [0, 4], [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 2, 4], [1, 2, 4], [0, 1, 2, 4]]

--------------------------------

support -> 0.2
step--[[0], [1], [2], [3], [4]]
step--[[0], [1], [2], [3], [4], [0, 1], [0, 2], [0, 4], [1, 2], [1, 3], [1, 4], [2, 4]]
step--[[0], [1], [2], [3], [4], [0, 1], [0, 2], [0, 4], [1, 2], [1, 3], [1, 4], [2, 4], [0, 1, 2], [0, 1, 4], [0, 2, 4]]

Frequent Items -> [[0], [1], [2], [3], [4], [0, 1], [0, 2], [0, 4], [1, 2], [1, 3], [1, 4], [2, 4], [0, 1, 2], [0, 1, 4], [0, 2, 4]]

--------------------------------

support -> 0.30000000000000004
step--[[0], [1], [2]]
step--[[0], [1], [2], [0, 1], [0, 2]]

Frequent Items -> [[0], [1], [2], [0, 1], [0, 2]]

--------------------------------

support -> 0.4
step--[[0], [1], [2]]
step--[[0], [1], [2], [0, 1], [0, 2]]

Frequent Items -> [[0], [1], [2], [0, 1], [0, 2]]

--------------------------------

support -> 0.5
step--[[0], [1], [2]]
step--[[0], [1], [2], [0, 2]]

Frequent Items -> [[0], [1], [2], [0, 2]]

--------------------------------

support -> 0.6
step--[[0], [1], [2]]

Frequent Items -> [[0], [1], [2]]

--------------------------------

support -> 0.7
step--[[0], [2]]

Frequent Items -> [[0], [2]]

--------------------------------

support -> 0.7999999999999999
step--[]

Frequent Items -> []

--------------------------------

support -> 0.8999999999999999
step--[]

Frequent Items -> []

--------------------------------
FP-Growth

算法簡介:
??FP-Growth(Frequent Pattern Growth, 頻繁模式增長)算法是韓家煒等人在2000年提出的關聯分析算法,它采取如下分治策略:將提供頻繁項集的數據庫壓縮到一棵頻繁模式樹(FP-tree),但仍保留項集關聯信息。
??在算法中使用了一種稱為頻繁模式樹(Frequent Pattern Tree)的數據結構。FP-tree是一種特殊的前綴樹,由頻繁項頭表和項前綴樹構成。FP-Growth算法基于以上的結構加快整個挖掘過程。FP-Growth比Apriori算法效率更高,在整個算法執行過程中,只需要遍歷數據集2次,就可完成頻繁模式的發現。

提出背景:
??所周知,Apriori算法[1] 在產生頻繁模式完全集前需要對數據庫進行多次掃描,同時產生大量的候選頻繁集,這就使Apriori算法時間和空間復雜度較大。但是Apriori算法中有一個很重要的性質:頻繁項集的所有非空子集都必須也是頻繁的。但是Apriori算法在挖掘額長頻繁模式的時候性能往往低下,于是Jiawei Han提出了FP-Growth算法。

算法思想:
??FP-Growth算法的描述如下:
??1、對于每個頻繁項,構造它的條件投影數據庫和投影FP-tree。
??2、對每個新構建的FP-tree重復這個過程,直到構造的新FP-tree為空,或者只包含一條路徑。
??3、當構造的FP-tree為空時,其前綴即為頻繁模式;當只包含一條路徑時,通過枚舉所有可能組合并與此樹的前綴連接即可得到頻繁模式。
示例說明:
??假設我們有以下數據,假設現在支持度support=3:

TID P1 P2 P3 P4 P5
1 0 1 0 0 1
2 0 1 0 1 0
3 0 1 1 0 1
4 1 1 0 1 0
5 1 0 1 0 1
6 0 1 1 0 0
7 1 0 1 0 0
8 1 1 1 0 1
9 1 1 0 0 0
10 1 0 1 0 0

我們可以得到所有item的出現頻率:

item p1 p2 p3 p4 p5
frequency 6 7 6 2 4
step 1. 生成Header Table

??去除不滿足support的item,并依據frequency按照從大到小進行排序。我們可以得到如下的table。

item p2 p1 p3 p5
frequency 0.7 0.6 0.6 0.4
step 2. 生成Header Tree

??再次掃描一次數據庫,依據Header Table中的各個屬性的順序,將每個示例加入到Header Tree中,過程如下圖所示:

step 1
step 2
step 3
step 4
step 5
step 6
step 7
step 8
step 9
step 10

??從Header Table出發,將每個相同的屬性串在同一條鏈表上,最終我們可以得到如下結構的FP Tree。

FP Tree
step 3. 挖掘頻繁項集

從FP樹中抽取頻繁項集的三個步驟:
??(1) 從FP樹中獲得條件模式基
??(2)利用每個頻繁項集的條件模式基,構建一顆條件FP樹
??迭代重復步驟(1),(2),直到樹包含一個元素項為止
條件模式基: 以所查找元素項為結尾的路徑集合,每一條路徑都是一條前綴路徑。 根據上圖挖掘的FP-Tree的結果,我們可以得到如下的條件模式基。

頻繁項 前綴路徑
p2 {}7
p1 {p2}3, {}3
p3 {p2}2, {p2,p1}1, {p1}3
p5 {p2}1, {p2,p3}1, {p2,p1,p3}1

結果:

{p2},{p1},{p3},{p5}
{p2, p1},
{p2,p3},{p1,p3}
{p2,p5}

算法實現:

應用

??關聯規則挖掘技術已經被廣泛應用在西方金融行業企業中,它可以成功預測銀行客戶需求。一旦獲得了這些信息,銀行就可以改善自身營銷。銀行天天都在開發新的溝通客戶的方法。各銀行在自己的ATM機上就捆綁了顧客可能感興趣的本行產品信息,供使用本行ATM機的用戶了解。如果數據庫中顯示,某個高信用限額的客戶更換了地址,這個客戶很有可能新近購買了一棟更大的住宅,因此會有可能需要更高信用限額,更高端的新信用卡,或者需要一個住房改善貸款,這些產品都可以通過信用卡賬單郵寄給客戶。當客戶打電話咨詢的時候,數據庫可以有力地幫助電話銷售代表。銷售代表的電腦屏幕上可以顯示出客戶的特點,同時也可以顯示出顧客會對什么產品感興趣。
??再比如市場的數據,它不僅十分龐大、復雜,而且包含著許多有用信息。隨著數據挖掘技術的發展以及各種數據挖掘方法的應用,從大型超市數據庫中可以發現一些潛在的、有用的、有價值的信息來,從而應用于超級市場的經營。通過對所積累的銷售數據的分析,可以得出各種商品的銷售信息。從而更合理地制定各種商品的定貨情況,對各種商品的庫存進行合理地控制。另外根據各種商品銷售的相關情況,可分析商品的銷售關聯性,從而可以進行商品的貨籃分析和組合管理,以更加有利于商品銷售。
??同時,一些知名的電子商務站點也從強大的關聯規則挖掘中的受益。這些電子購物網站使用關聯規則中規則進行挖掘,然后設置用戶有意要一起購買的捆綁包。也有一些購物網站使用它們設置相應的交叉銷售,也就是購買某種商品的顧客會看到相關的另外一種商品的廣告。
??但是在我國,“數據海量,信息缺乏”是商業銀行在數據大集中之后普遍所面對的尷尬。金融業實施的大多數數據庫只能實現數據的錄入、查詢、統計等較低層次的功能,卻無法發現數據中存在的各種有用的信息,譬如對這些數據進行分析,發現其數據模式及特征,然后可能發現某個客戶、消費群體或組織的金融和商業興趣,并可觀察金融市場的變化趨勢。可以說,關聯規則挖掘的技術在我國的研究與應用并不是很廣泛深入。

參考:https://www.cnblogs.com/en-heng/p/5719101.html
https://baike.baidu.com/item/關聯分析/1198018?fr=aladdin
https://www.cnblogs.com/bigmonkey/p/7405555.html

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

推薦閱讀更多精彩內容