如何理解 Graph Convolutional Network(GCN)

从CNN到GCN的联系与区别——GCN从入门到精(fang)通(qi)

1 什么是离散卷积?CNN中卷积发挥什么作用?

了解GCN(图卷积神经网络)之前,必须对离散卷积(具体说就是CNN中的卷积)有个清楚的认识。

如何通俗易懂地解释卷积?离散卷积本质就是一种加权求和。

如图1所示,CNN中的卷积本质上就是利用一个共享参数的卷积核,通过计算中心像素点以及相邻像素点的加权求和构成feature map实现空间特征的提取,其中权重就是卷积核的值。

那么卷积核的值如何确定呢?实际上卷积核值的确定过程就是模型训练的过程:首先随机初始化卷积核的值,然后根据反向传播梯度下降不断优化(即不断更新卷积核的值)直到模型收敛,此时卷积核的值被确定。

卷积核的参数通过优化求出才能实现特征提取的作用,GCN的理论很大一部分工作就是为了引入可以优化的卷积参数。

图1

注:这里的卷积是指深度学习(CNN)中的卷积,与数学中定义的卷积运算严格意义上是有区别的。可以参考该内容卷积神经网络中的卷积与数学中卷积的区别

2 GCN中的Graph指什么?为什么要研究GCN?

CNN在计算机视觉中具有广泛应用,对图片具有强大的特征提取能力。但需要注意的是:CNN处理的图像或者视频数据中的像素点(pixel)是排列很整齐的矩阵(如图2所示,也就是很多论文中提到的Euclidean Structure)。

图2 图像矩阵示意图(Euclidean Structure)

与之相对应,科学研究中还有很多 Non Euclidean Structure的数据,如图3所示,社交网络、信息网络中有很多类似的结构。

图3 社交网络拓扑示意图(Non Euclidean Structure)

实际上,这样的网络结构(Non Euclidean Structure)就是图论中抽象意义上的拓扑图。

所以,Graph Convolutional Network中的Graph是指数学(图论)中的用顶点和边建立相应关系的拓扑图。

那么为什么要研究GCN?原因有三:

  • CNN无法处理Non Euclidean Structure的数据,学术上的表达是传统的离散卷积(如问题1中所述)在Non Euclidean Structure的数据上无法保持平移不变形。通俗理解就是在拓扑图中每个顶点的相邻顶点数目都可能不同,那么当然无法用一个同样尺寸的卷积核来进行卷积运算(图卷积结构的不规则性导致了这一点)。
  • 又希望在这样的数据结构(拓扑图)上有效地提取空间特征来进行机器学习,所以GCN成为了研究的重点。
  • 读到这里大家可能会想,自己的研究问题中没有拓扑结构的网络,那是不是根本就不会用到GCN呢?其实不然,广义上来讲任何数据在赋范空间内都可以建立拓扑关联,谱聚类就是应用了这样的思想谱聚类(spectral clustering)原理总结)。所以说拓扑连接是一种广义的数据结构,GCN有很大的应用空间

综上所述,GCN是要为除CV、NLP之外的任务提供一种处理、研究的模型。

3 提取拓扑图空间特征的两种方式

GCN的本质目的是来提取不规则的拓扑图的空间特征,那么实现这个目标只有graph convolution这一种途径吗?当然不是,vertex domain(spatial domain)和spectral domain实现该目标是两种最主流的方式。

(1)vertex domain(spatial domain)是非常直观的一种方式。顾名思义:提取拓扑图上的空间特征,那么就把每个顶点相邻的neighbors找出来。这里面蕴含的科学问题有二:

a、按照什么条件去找中心vertex的neighbors,也就是如何确定receptive field?

b、确定receptive field,按照什么方式处理包含不同数目neighbors的特征?

根据a,b两个问题设计算法,就可以实现目标了。推荐阅读这篇文章Learning Convolutional Neural Networks for Graphs
图4是其中一张图片,可以看出大致的思路)。

图4 vertex domain提取空间特征示意

这种方法主要的缺点如下:
c、每个顶点提取出来的neighbors不同,使得计算处理必须针对每个顶点
d、提取特征的效果可能没有卷积好

(2)spectral domain就是GCN的理论基础了。这种思路就是希望借助图谱的理论来实现拓扑图上的卷积操作。从整个研究的时间进程来看:首先研究GSP(graph signal processing)的学者定义了graph上的Fourier Transformation, 进而定义了graph上的convolution,最后与深度学习结合提出了Graph Convolutional Network。

认真读到这里,脑海中应该会浮现出一系列问题:

Q1 什么是Spectral graph theory?(解释待定)

Spectral graph theory请参考,简单来说就是借助于图的拉普拉斯矩阵的特征值和特征向量来研究图的性质

Q2 GCN为什么要利用Spectral graph theory?

这应该是看论文过程中读不懂的核心问题了,要理解这个问题需要大量的数学定义及推导,没有一定的数学功底难以驾驭(我也才疏学浅,很难回答好这个问题)。

所以,先绕过这个问题,来看Spectral graph实现了什么,再进行探究为什么?

4 什么是拉普拉斯矩阵?为什么GCN要用拉普拉斯矩阵?

Graph Fourier Transformation及Graph Convolution 的定义都用到图的拉普拉斯矩阵,那么首先来介绍一下拉普拉斯矩阵。

对于图G=(V,E), 其Laplacian 矩阵的定义为L=D-A, 其中L是Laplacian矩阵,D是顶点的度矩阵(对角矩阵),对角线上元素依次为各个顶点的度,A是图的邻接矩阵。看图5的示例,就能很快知道Laplacian 矩阵的计算方法。

图5 Laplacian 矩阵的计算方法

这里要说明的是:常用的拉普拉斯矩阵实际有三种

  • L=D-A 定义的Laplacian矩阵更专业的名称叫Combinatorial Laplacian
  • L^{sys} = D^{-\frac{1}{2}}LD^{-\frac{1}{2}},定义的叫Symmetric normalized Laplacian, 很多GCN的论文中应用的是这种拉普拉斯矩阵
  • L^{rw}=D^{-1}L 定义的叫Random walk normalized Laplacian,有读者的留言说看到了Graph Convolution与Diffusion相似之处,当然从Random walk normalized Laplacian就能看出了两者确有相似之处(其实两者只差一个相似矩阵的变换, 可以参考Diffusion-Convolutional Neural Networks
    以及下图)

不需要相关内容的读者可以略过此部分

Diffusion(扩散) Graph Convolution 与 Spectral Graph Convolution 相似性证明

其实维基本科对Laplacian matrix的定义上写得很清楚,国内的一些介绍中只有第一种定义。这让我在最初看文献的过程中感到一些的困惑,特意写下来,帮助大家避免再遇到类似的问题。

为什么GCN要用拉普拉斯矩阵?

拉普拉斯矩阵矩阵有很多良好的性质,这里写三点我感触到的和GCN有关之处

  • 拉普拉斯矩阵是对称矩阵,可以进行特征分解(谱分解),这就和GCN的spectral domain对应上了
  • 拉普拉斯矩阵只在中心顶点和一阶相连(就是直接相连)的顶点上(1-hop neighbor)有非0元素,其余之处均为0
  • 通过拉普拉斯算子与拉普拉斯矩阵进行类比(详见第6节)

superbrother 拉普拉斯矩阵与拉普拉斯算子的关系

5 拉普拉斯矩阵的谱分解(特征分解)

GCN的核心基于拉普拉斯矩阵的谱分解,文献中对于这部分内容没有讲解太多,初学者可能会遇到不少误区,所以先了解一下特征分解。

矩阵的谱分解,特征分解,对角化都是同一个概念特征分解_百度百科)。

不是所有的矩阵都可以特征分解,其充要条件为n阶方阵存在n个线性无关特征向量

但是拉普拉斯矩阵都是半正定对称矩阵(半正定矩阵本身就是对称矩阵,半正定矩阵_百度百科
, 此处这样写为了和下面的性质对应,避免混淆),有如下三个性质:

  • 对称矩阵一定有n个线性无关的特征向量
  • 半正定矩阵的特征值一定非负
  • 对称矩阵的特征向量相互正交,即所有特征向量构成的矩阵为正交矩阵

由上可知,拉普拉斯矩阵一定可以谱分解,且分解后有特殊形式。

对于拉普拉斯矩阵其谱分解为:
L=U \begin{pmatrix} \lambda_1& & \\ & \ddots& \\ & & \lambda_n \end{pmatrix}U^{-1}
其中U=(\overrightarrow{u_1}, \overrightarrow{u_2}, ..., \overrightarrow{u_n}),是单位特征向量组成的矩阵,\overrightarrow{u_i}是列向量。\begin{pmatrix} \lambda_1& & \\ & \ddots& \\ & & \lambda_n \end{pmatrix}是n个特征值构成的对角矩阵。
由于U是正交矩阵,即UU^{T}=E

因为UU^{-1}=E (这是定义)
所以U^{T}=U^{-1}

进而这里的特征分解又可以写成:
L=U \begin{pmatrix} \lambda_1& & \\ & \ddots& \\ & & \lambda_n \end{pmatrix}U^{T}

6 如何从传统的傅立叶变换、卷积 类比 到Graph上的傅立叶变换及卷积?

把传统的傅立叶变换以及卷积迁移到Graph上来,核心就是把拉普拉斯算子的特征函数e^{-i \omega t}变为Graph对应的拉普拉斯矩阵的特征向量。

(1)推广傅里叶变换

参考论文The Emerging Field of Signal Processing on Graphs

(a) Graph上的傅里叶变换
传统的傅立叶变换定义为

F(\omega) =\mathscr{F}[f(t)]= \int f(t)e^{-i \omega t}dt

F(\omega)是信号f(t) 与基函数e^{-i \omega t}的积分,那么为什么要找e^{-i \omega t}作为基函数呢?从数学上看,e^{-i \omega t}是拉普拉斯算子的特征函数(满足特征方程),\omega 就和特征值有关

广义的特征方程定义为A \mathbf{x} = \lambda \mathbf{x}
其中,A(一个矩阵)是一种变换(对向量\mathbf{x}变换),\mathbf{x}是特征向量或者特征函数(无穷维的向量),\lambda是特征值。

e^{-i \omega t}满足:
\Delta e^{-i \omega t} = \frac{\partial^2 }{\partial t^2}e^{-i \omega t}=- \omega ^ {2}e^{-i \omega t}

当然e^{-i \omega t}就是变换\Delta的特征函数\omega和特征值密切相关。

那么,可以联想了,处理Graph问题的时候,用到拉普拉斯矩阵(拉普拉斯矩阵就是拉普拉斯算子,想了解更多可以参考Discrete Laplace operator),自然就去找拉普拉斯矩阵的特征向量了。

L是拉普拉斯矩阵,\mathbf{x}是其特征向量,自然满足下式

L \mathbf{x} = \lambda \mathbf{x}
离散积分就是一种内积形式,仿照上述内容定义Graph的傅立叶变换

F(\lambda_{l}) = \hat{f}(\lambda_{l})=\sum_{i=1}^{N}f(i)u_{l}^{*}(i)
f 是Graph上的N维向量,f(i)与Graph的顶点一一对应,u_{l}(i)表示第l个特征向量的第i个分量。那么特征值(频率)\lambda_{l}下的f的Graph傅立叶变换就是与\lambda_{l}对应的特征向量u_{l}进行内积运算

注:上述的内积运算是在复数空间中定义的,所以采用了u_{l}^{*}(i),也就是特征向量u_l的共轭。

利用矩阵乘法将Graph上的傅立叶变换推广到矩阵形式

f在Graph上傅立叶变换的矩阵形式为:\hat{f}=U^T f , \;(a)
式中:U^T的定义与第五节中的相同

(b)Graph上的傅立叶逆变换(频域(\omega)到时域(t))
类似地,传统的傅立叶逆变换是对频率\omega求积分
\mathscr{F}^{-1}[F(\omega)] = \frac{1}{2\Pi}\int F(\omega)e^{i \omega t}d\omega

迁移到Graph上变为对特征值\lambda_{l}求和
f(i)=\sum_{i=1}^{N}\hat{f}(\lambda_{l})u_{l}(i)

利用矩阵乘法将Graph上的傅立叶变换推广到矩阵形式

f在Graph上傅立叶逆变换的矩阵形式为:f=U\hat{f} , \;(b)

式中:U的定义与第五节中的相同

(2)推广卷积
在上面的基础上,利用卷积定理类比来将卷积运算,推广到Graph上。

由卷积定理可知,两个函数的卷积等于各个函数傅立叶变换的乘积的逆变换,即f * g=\mathscr{F}^{-1}[\mathscr{F}(f) \cdot \mathscr{F}(g)]

将上述过程推广到Graph:

  • f的傅立叶变换为 \hat{f}=U^Tf
  • 卷积核h的傅立叶变换\hat{h}写成对角矩阵形式\begin{pmatrix} \hat{h} (\lambda_1)& & \\ & \ddots& \\ & & \hat{h} (\lambda_n) \end{pmatrix}

则有
(f * h)_{G} = \mathscr{F}^{-1}[\hat{h} \cdot \hat{f}] = \mathscr{F}^{-1}[\begin{pmatrix} \hat{h} (\lambda_1)& & \\ & \ddots& \\ & & \hat{h} (\lambda_n) \end{pmatrix} U^Tf]

存疑:可能是满足交换律?不确定

进一步得到
(f * h)_{G} = U \begin{pmatrix} \hat{h} (\lambda_1)& & \\ & \ddots& \\ & & \hat{h} (\lambda_n) \end{pmatrix} U^Tf , \;(1)
注:通过乘以U对等式右边做逆变换。

很多论文中的Graph卷积公式为:
(f * h)_{G} = U ((U^Th) \odot (U^Tf)),\;(2)
式(2)中,\odot 表示Hadamard product(哈达马积),对于两个维度相同的向量、矩阵、张量进行对应位置的逐元素乘积运算。

式(1)和式(2)是完全相同的。

7 为什么拉普拉斯矩阵的特征向量可以作为傅里叶变换的基?特征值表示频率?

(1)为什么拉普拉斯矩阵的特征向量可以作为傅里叶变换的基?
傅里叶变换一个本质理解就是:把任意一个函数表示成了若干个正交函数(由sin,cos 构成)的线性组合。

图6 傅立叶逆变换图示

通过第六节中(b)式也能看出,**graph傅里叶变换也把graph上定义的任意向量,表示成了拉普拉斯矩阵特征向量的线性组合,即: **

那么:为什么graph上任意的向量f 都可以表示成这样的线性组合?

原因在于 (\overrightarrow{u_1}, \overrightarrow{u_2}, ..., \overrightarrow{u_n}) 是graph上n维空间中的n个线性无关的正交向量,由线性代数的知识可以知道:n维空间中n个线性无关的向量可以构成空间的一组基,而且拉普拉斯矩阵的特征向量还是一组正交基。

(2)怎么理解拉普拉斯矩阵的特征值表示频率?

在graph空间上无法可视化展示“频率”这个概念,那么从特征方程上来抽象理解。

将拉普拉斯矩阵Ln个非负实际特征值,从小到大排列为\lambda_1, \lambda_2, ..., \lambda_n,而且最小的特征值\lambda_1=0,因为n维的全为1向量对应的特征值为0.(这一句不太懂)。

从特征方程的数学理解来看:
L\mathbf{u}=\lambda \mathbf{u}
在有Graph确定的n维空间中,越小的特征值\lambda_l表明:拉普拉斯矩阵L其对应的基u_l上的分量、“信息”越少,那么当然就是可以忽略的低频部分了。

其实图像压缩就是这个原理,把像素矩阵特征分解后,把小的特征值(低频部分)全部变成0,PCA降维也是同样的,把协方差矩阵特征分解后,按从大到小取出前K个特征值对应的特征向量作为新的“坐标轴”。

Graph Convolution的理论告一段落了,下面开始Graph Convolution Network

8 Deep Learning中的Graph Convolution

Deep learning 中的Graph Convolution直接看上去会和第6节推导出的图卷积公式有很大的不同,但是万变不离其宗,(1)式是推导的本源。

第1节的内容已经解释得很清楚:Deep learning 中的Convolution 就是要设计含有trainable共享参数的kernel,从(1)式看很直观:graph convolution中的卷积参数就是diag(\hat{h}(\lambda_l))

  • 第一代GCN

Spectral Networks and Locally Connected Networks on Graphs中简单粗暴地把diag(\hat{h}(\lambda_l))变成了卷积核 diag(\theta_l),也就是:

y_{output} = \sigma (Ug_{\theta}(\Lambda)U^Tx) , \; (3)
(为避免混淆,本文中称g_{\theta}(\Lambda)是卷积核,Ug_{\theta}(\Lambda)U^Tx的运算结果为卷积运算矩阵)
g_{\theta}(\Lambda) =\begin{pmatrix} \theta_1& & \\ & \ddots & \\ & & \theta_n \end{pmatrix}
式(3)就是标准的第一代GCN中的layer了, 其中\sigma (\cdot)是激活函数,\Theta = (\theta_1, \theta_2, ..., \theta_n),就跟三层神经网络中的weight一样是任意的参数,通过初始化赋值然后利用误差反向传播进行调整,x就是graph上对应于每个顶点的feature vector(由数据集提取特征构成的向量)。

第一代的参数方法存在着一些弊端,主要在于:
(1) 每一次前向传播,都要计算 U, diag(\theta_l), U^T三者的矩阵乘积,特别是对于大规模的graph,计算的代价较高,也就是论文中,O(n^2)的计算复杂度
(2) 卷积核不具有spatial localization(这个在第9节中进一步阐述)
(3) 卷积核需要n个参数

由于以上的缺点第二代的卷积核设计应运而生。

  • 第二代GCN

Convolutional Neural Networks on Graphs with Fast Localized Spectral Filtering,把\hat{h}(\lambda_l)巧妙设计成了\sum_{j=0}^{K}\alpha_j \lambda_{l}^{j},也就是:
y_{output} = \sigma (Ug_{\theta}(\Lambda)U^Tx) , \; (4)
g_{\theta}(\Lambda) =\begin{pmatrix} \sum_{j=0}^{K}\alpha_j \lambda_{1}^{j}& & \\ & \ddots & \\ & & \sum_{j=0}^{K}\alpha_j \lambda_{n}^{j} \end{pmatrix}

上面的公式仿佛还什么都看不出来,下面利用矩阵乘法进行变换,来一探究竟。
g_{\theta}(\Lambda) =\begin{pmatrix} \sum_{j=0}^{K}\alpha_j \lambda_{1}^{j}& & \\ & \ddots & \\ & & \sum_{j=0}^{K}\alpha_j \lambda_{n}^{j} \end{pmatrix}=\sum_{j=0}^{K}\alpha_j \Lambda^{j}
进而可以导出:
U\sum_{j=0}^{K}\alpha_j\Lambda^{j}U^T=\sum_{j=0}^{K}\alpha_j U \Lambda^{j}U^T=\sum_{j=0}^{K}\alpha_jL^{j}

小说明:
上述等式成立的原因是L^2 = U \Lambda U^T U \Lambda U^T=U\Lambda^{2}U^T,其中U^TU=E

(4)式就变成了:
y_{output} = \sigma (\sum_{j=0}^{K} \alpha_j L^j x),\;(5)
其中(\alpha_1, \alpha_2, ..., \alpha_K)是任意的参数,通过初始化赋值然后利用误差反向传播进行调整。

式(5)所设计的卷积核其有点在于:
(1)卷积核只有K个参数,一般K远小于n,参数的复杂度被大大降低了。
(2)矩阵变换后,神奇地发现不需要做特征分解了,直接用拉普拉斯矩阵L进行变换。然而由于要计算L^j,计算复杂度还是O(n^2)
(3)卷积核具有很好的spatial localization,特别地,K就是卷积核的receptive field,也就是说每次卷积会将中心顶点K-hop neighbor上的feature进行加权求和,权系数就是\alpha_k

更直观地看,K=1就是对每个顶点上一阶neighbor的feature进行加权求和,如下图所示:

图7 k=1的graph convolution示意

同理,K=2的情形如下图所示:


图8 k=2的graph convolution示意

注:上图只是以一个顶点作为实例,GCN每一次卷积对所有的顶点都完成了图示的操作。

  • 利用Chebyshev多项式递归计算卷积核

在第二代GCN中,Ln \times n的矩阵,所以L^{j}的计算复杂度还是O(n^2)Wavelets on graphs via spectral graph theory提出了利用Chebyshev多项式拟合卷积核的方法,来降低计算复杂度。卷积核g_{\theta}(\Lambda)可以利用截断(truncated)的shifted Chebyshev多项式来逼近。(这里本质上应该寻找Minimax(极小值) Polynomial Approximation, 但是作者说直接利用Chebyshev Polynomial的效果也很好)

g_{\theta}(\Lambda)=\sum_{k=0}^{K-1} \beta_kT_k(\tilde\Lambda)
其中\beta_k是Chebyshev多项式的系数,T_k(\tilde\Lambda)是取\tilde\Lambda=2\Lambda / \lambda_{max}-I的Chebyshev多项式,进行这个shift变换的原因是Chebyshev多项式的输入要在[-1, 1]之间。

由Chebyshev多项式的性质,可以得到如下的递推公式
T_k(\tilde\Lambda)x=2\tilde\Lambda T_{k-1}(\tilde\Lambda)x - T_{k-2}(\tilde\Lambda)x, \; (6)

T_0(\tilde\Lambda)=I, T_1(\tilde\Lambda)=\tilde\Lambda

其中,x的定义同上,是n维的由每个顶点的特征构成的向量(当然,也可以是n * m的特征矩阵,这时每个顶点都有m个特征,但是m远小于n

这个时候不难发现:式(6)的运算不再有矩阵乘积了,只需要计算矩阵与向量的乘积即可。计算一次 T_k(\tilde\Lambda)x的复杂度是O(|E|)E是图中边的集合,则整个运算的复杂度是O(K|E|)。当graph是稀疏图的时候,计算加速尤为明显,这个时候复杂度远低于O(n^2)

上面的讲述是GCN最基础的思路,很多论文中的GCN结构是在上述思路的基础上进行了一些简单数学变换。理解了上述内容,就可以做到“万变不离其宗”

9 在GCN中的Local Connectivity和Parameter Sharing

CNN中有两大核心思想:网络局部连接,卷积核参数共享

那么我们不禁会联想:这两点在GCN中是怎样的呢?以下图的graph结构为例来探究一下

graph结构示意
  • GCN中的Local Connectivity

(a)如果利用第一代GCN,根据式(3)卷积运算矩阵(Ug_{\theta}(\Lambda)U^T)即为

第一代卷积核示意

这个时候,可以发现这个卷积核没有local的性质,因为该卷积核得到的运算矩阵在所有位置上都有非0元素。以第一个顶点为例,如果考虑一阶local关系的话,那么卷积核中第一行应该只有[1,1],[1,2],[1,5]这三个位置的元素非0。换句话说,这是一个global全连接的卷积核。

(b)如果是第二代GCN,根据式(5)当 K=1卷积运算矩阵即为

第二代卷积核示意 K=1

当K=2,卷积运算矩阵即为


第二代卷积核示意 K=2

看一下图的邻接结构,卷积运算矩阵的非0元素都在localize的位置上

参考文献

如何理解 Graph Convolutional Network(GCN)?
快速了解GCN(图卷积神经网络)综述综述综述

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容