Deep Learning——奇异值分解在协同过滤推荐算法中的应用

奇异值分解在协同过滤推荐算法中的应用

目录

推荐算法介绍

协同过滤算法

电影推荐系统

基于用户(项目)的协同过滤算法

基于模型的协同过滤算法

对缺失的地方进行填充---传统SVD

Funk-SVD

推荐算法介绍

所谓推荐算法,即根据用户的历史信息,去寻找每个用户可能关心的项目。例如淘宝根据用户的历史购买记录,用推荐算法去寻找每个用户关心的产品。抖音根据用户的浏览记录,用推荐算法去寻找每个用户关心的视频等等。

在推荐算法中比较常用的是协同过滤算法。本文主要详细介绍协同过滤是算法。

协同过滤算法

协同过滤算法,一般分为三大类:
1、基于用户的协同过滤算法。
2、基于项目的协同过滤算法。
3、基于模型的协同过滤算法。

在下文中用一个简单的例子,对每类进行具体介绍和算法实现。

电影推荐系统

已知某电影平台的用户观看电影后给出的电影评分数据如下所示:

__ 电影1 电影2 电影3
用户1 1 0 7
用户2 9 6 4
用户3 5 7 0
用户4 0 7 1

为清晰的讲清楚算法的来龙去脉和流程,数据集不采用繁琐的真实数据进而省去数据预处理,把数据集简化为4x3矩阵。

上表的行名是用户名称,列名是电影名称,表里面的数字为用户对电影的评级,评级为1到10级,评级也大说明用户越喜爱这个电影,0代表用户尚未观看电影。

现在的问题就是把用户未观看的电影,且用户可能关心的电影推荐给用户。怎么去衡量用户对未观看电影的评价,就是本推荐算法解决的问题。

对于以上问题。可以分别从两个角度解决问题,第一是用户的角度,既以用户协同过滤寻求推荐算法,第二是项目的角度,既以项目协同过滤寻求推荐算法。

基于用户(项目)的协同过滤算法。

基于用户的协同过滤算法为:以用户为样本点,寻求用户的相似性,根据用户的相似性推荐对应的电影给对应的用户。
基于项目的协同过滤算法为:以项目为样本点,寻求项目的相似性,根据项目的相似性推荐对应的电影给对应的用户。

在刻画两个样本点相似性的方法时,一般有以下三种:
欧氏距离相似度= 1/(1+两个样本的欧式距离);
余弦相似度=0.5 + 0.5( float(inA.TinB) / la.norm(inA)*la.norm(inB));
皮尔逊相关系数相似度= 0.5 + 0.5 * corrcoef(inA, inB, rowvar = 0)[0][1]

需要注意的是,在计算相似性时,不论从哪个角度,都不要计算没有打分的哪一项。

对于采用哪一种协同过滤的算法,依据是计算相似度时的计算量,本例中,若使用用户协同过滤算法,那么有四个样本点,若使用项目协同算法,有三个样本点,所以使用项目协同算法,明显计算相似度时计算量会优于用户协同算法。

所以这里使用项目协同算法。(在实际情况中,用户必然多余电影的数量。也会使用项目协同算法)

算法流程

(1)计算各个项目之间的相似度,本例中计算D(电影1,电影2),D(电影1,电影3),D(电影2,电影3)。
(2)计算没有评级位置(数据集中为0的点)的评级。
a_{1,2}=(a_{1,1}*D(电影1,电影2)+a_{1,3}*D(电影2,电影3))/(D(电影1,电影2)+ D(电影2,电影3) )
a_{3,3}=(a_{3,1}*D(电影1,电影3)+a_{3,2}*D(电影2,电影3))/(D(电影1,电影3)+ D(电影2,电影3) )
a_{4,1}=(a_{4,2}*D(电影1,电影2)+a_{4,3}*D(电影1,电影3))/(D(电影1,电影2)+ D(电影1,电影3) )

具体代码如下:

import numpy as np
def loadExData():
    # 利用SVD提高推荐效果 
    """
    行:用户
    列:电影
    值:用户对电影的评级,0表示未评分
    """
    return np.mat([[1, 0, 7],
           [9, 6, 4],
           [5, 7, 0],
           [0, 7, 1]])


def ecludSim(inA, inB):
    return 1.0 / (1.0 + la.norm(inA - inB))  # 范数的计算方法linalg.norm(),这里的1/(1+距离)表示将相似度的范围放在0与1之间


def pearsSim(inA, inB):
    if len(inA) < 3: return 1.0
    return 0.5 + 0.5 * corrcoef(inA, inB, rowvar=0)[0][
        1]  # 皮尔逊相关系数的计算方法corrcoef(),参数rowvar=0表示对列求相似度,这里的0.5+0.5*corrcoef()是为了将范围归一化放到0和1之间


def cosSim(inA, inB):
    num = np.dot(inA.T , inB)
    denom = np.linalg.norm(inA) * np.linalg.norm(inB)
    return 0.5 + 0.5 * (num / denom)  # 将相似度归一到0与1之间


# 基于物品相似度的推荐引擎

def standEst(dataMat, user, simMeas, item):
    """standEst(计算某用户未评分电影中,以对该电影和其他物品评分的电影的物品相似度,然后进行综合评分)

    Args:
        dataMat         训练数据集
        user            用户编号
        simMeas         相似度计算方法
        item            未评分的物品编号
    Returns:
        ratSimTotal/simTotal     评分(0~10之间的值)
    """
    # 得到数据集中的物品数目
    n = np.shape(dataMat)[1]
    # 初始化两个评分值
    simTotal = 0.0
    ratSimTotal = 0.0
    # 遍历行中的每个物品(对用户评过分的物品进行遍历,并将它与其他物品进行比较)
    for j in range(n):
        userRating = dataMat[user, j]
        # 如果某个物品的评分值为0,则跳过这个物品
        if userRating == 0:
            continue
        # 寻找两个用户都评级的物品
        # 变量 overLap 给出的是两个物品当中已经被评分的那个元素的索引ID
        # logical_and 计算x1和x2元素的真值。
        overLap = np.nonzero(np.logical_and(dataMat[:, item].A > 0, dataMat[:, j].A > 0))[0]
        # 如果相似度为0,则两着没有任何重合元素,终止本次循环
        if len(overLap) == 0:
            similarity = 0
        # 如果存在重合的物品,则基于这些重合物重新计算相似度。
        else:
            similarity = simMeas(dataMat[overLap, item], dataMat[overLap, j])
        # print 'the %d and %d similarity is : %f'(iten,j,similarity)
        # 相似度会不断累加,每次计算时还考虑相似度和当前用户评分的乘积
        # similarity  用户相似度,   userRating 用户评分
        simTotal += similarity
        ratSimTotal += similarity * userRating
    if simTotal == 0:
        return 0
    # 通过除以所有的评分总和,对上述相似度评分的乘积进行归一化,使得最后评分在0~5之间,这些评分用来对预测值进行排序
    else:
        return ratSimTotal/simTotal
# recommend()函数,就是推荐引擎,它默认调用standEst()函数,产生了最高的N个推荐结果。
# 如果不指定N的大小,则默认值为3。该函数另外的参数还包括相似度计算方法和估计方法
def recommend(dataMat, simMeas=cosSim, estMethod=standEst):
    retMat=dataMat.copy()
    n=np.shape(dataMat)[0]
    
    for user in range(0,n):
        # 寻找未评级的物品
        # 对给定的用户建立一个未评分的物品列表
        unratedItems = np.nonzero(dataMat[user, :].A == 0)[1]
        # 如果不存在未评分物品,那么就退出函数
        if len(unratedItems) == 0:
            continue
        # 在未评分物品上进行循环
        for item in unratedItems:
            estimatedScore = estMethod(dataMat, user, simMeas, item)
            retMat[user,item]=estimatedScore
            # 按照估计得分,对该列表进行排序并返回。列表逆排序,第一个值就是最大值
    return retMat
test_data=loadExData()
score_data=recommend(test_data)
print(score_data)
[[1 3 7]
 [9 6 4]
 [5 7 6]
 [4 7 1]]

以上就是基于用户的协同过滤算法。这个算法简单实用,效果还可以,但为了更好的提高效果,经常使用下面基于模型的协同过滤算法。

基于模型的协同过滤算法

注:之所以整理这篇文章,就是因为这个地方,网上相关文章介绍的非常乱,很多概念含糊不清。下面就进行详细介绍。

基于模型的协同过滤算法,多种多样,这里主要介绍应用SVD的基于模型的协同过滤。
还是上面的例子。对于用户-电影评分矩阵

A=\left[ \begin{array} {cccc} 1&0&7\\ 9&6&4\\ 5&7&0\\ 0&7&7\\ \end{array} \right]

因为矩阵中为0的地方是未评分的地方,可以看出A矩阵是稀疏矩阵,所以A矩阵此时并不能进行SVD分解。针对评分矩阵A稀疏的问题,有两种解决方式,
其一、对缺失的地方进行填充,传统SVD。
其二、是Funk-SVD算法。

对缺失的地方进行填充---传统SVD

对于这方法,用什么值去填充又是一个问题,一般都用矩阵中元素的均值进行填充,其理由是对于用户未观看的电影用既不高也不低的均值进行评分。这种方式还是存在两个问题:
一、若矩阵A太稀疏,则这种方式误差很大,A相对稠密时才可以减小误差。
二、现实问题中,用户和电影数量庞大,这一做法的计算量很大。

对于本例A填充完成后为:
A=\left[ \begin{array} {cccc} 1&4&7\\ 9&6&4\\ 5&7&4\\ 4&7&7\\ \end{array} \right]

此时,使用SVD进行去燥降维,(去燥是提高效果准确性,若去燥维则,使用填充数据产生的噪音会影响准确性,降维是因为实际问题数据维数庞大)。其SVD分解为:


A=np.array([[1, 4, 7],
           [9, 6, 4],
           [5, 7, 4],
           [4, 7, 1]])
U,S,VT =np.linalg.svd(A)
Sigma=np.diag(S)
print(A)
print('U=',U)
print('Sigma=',Sigma)
print('VT=',VT)

[[1 4 7]
 [9 6 4]
 [5 7 4]
 [4 7 1]]
U= [[-0.35949466  0.89113483  0.02362996  0.27583316]
 [-0.63196087 -0.33730085  0.66569561  0.2090525 ]
 [-0.53609543  0.04067918 -0.23392266 -0.81007844]
 [-0.42894141 -0.30075249 -0.70821689  0.47327163]]
Sigma= [[17.62761582  0.          0.        ]
 [ 0.          5.70380415  0.        ]
 [ 0.          0.          3.42546037]]
VT= [[-0.59244457 -0.67989919 -0.43214178]
 [-0.55124384 -0.04905129  0.83290108]
 [ 0.58748588 -0.73166321  0.34573007]]

去燥降维的方法为在矩阵的SVD分解中取前k大的奇异值对应的U,Sigma,VT 矩阵,分别设为U_k,Simga_k,VT_k.

k的确定方法为,根据给定的去燥比率,若去燥比率设置为不高于5%,则:

S_square=np.square(S)
Sum=0
k=0
for i in range(3):
    Sum=Sum+S_square[i]
    rate=1-Sum/sum(S_square)
    print(rate)
    k=i
    if(rate<0.05):
        break
print(k)
0.12469622694282334
0.033052897858724783
1

所以当只保留前两个最大的奇异值时,去燥比率就位3.3%,小于5%。

由上面得到,在给定去燥条件的前提下,我们确定了k的值,从而确定了评分矩阵A的降维去燥后的低秩最佳逼近矩阵为:

A \approx A_1=U_2 Sigma_2 VT_2

所以:

A_1 =Sigma[0,0]*np.dot(U[:,0].reshape(-1,1),np.mat(VT[0,:]))+Sigma[1,1]*np.dot(U[:,1].reshape(-1,1),np.mat(VT[1,:]))
print(A_1)
print(A)
[[0.95244684 4.05922338 6.9720154 ]
 [7.66034775 7.66842183 3.21162691]
 [5.47074821 6.41372353 4.27703102]
 [5.42522248 5.22500781 1.83873038]]
[[1 4 7]
 [9 6 4]
 [5 7 4]
 [4 7 1]]

可以看出,去燥后的矩阵和原始矩阵非常相似。也正因如此,用均值去填充的评分变化也不是很大,但是已经发生了变化,这种变化,可以理解为,是由真实评分构成的矩阵的特征改变的(这也是为什么使用SVD的原因),所以如果起始的评分矩阵太过稀疏,真实评分产生的影响就会很小,从而结果的准确性很低。

上面我们得到了去燥后的近似矩阵,然后怎么用这个矩阵呢,在用这个近似矩阵之前,就要先讨论下评分矩阵在SVD分解后,得到的U,Sigma,VT分别有什么物理意义。

由SVD的理论知识得到,对于U矩阵的列向量,是矩阵A^TA的特征向量,对于矩阵VT的行向量是矩阵AA^T的特征向量。所以要搞清楚U和VT矩阵的物理意义,必须要从A^TAAA^T两个矩阵触发。为了讨论一般情况,下面先设A为mxn阶评分矩阵。每一行对应一个用户,每一列对应一部电影。

对于任意n维向量x,Ax是一个m维向量,其第一个元素,可以看作是第一个用户对其所有电影评分的一个加权和,第二个元素为第二个用户对其所有电影的评分的一个加权和。当x固定为衡量每个电影本身某一维度的质量(如音乐的质量)的权重时,x对于用户无关,只取决于电影的音乐本身,那么Ax的第一个元素为用户一对音乐本身的偏好度,Ax的第二个元素为用户二对因为本身的偏好度。这个音乐偏好度和电影无关,只取决于用户本身。

设y=Ax,则从上面的分析,x固定为衡量每个电影本身音乐的质量的权重时,y时衡量用户本身的音乐偏好度。此时在分析A^T,A^T的行代表电影,列代表用户。A^Ty是一个n维向量,其第一个元素是电影一的所有用户评分的一个加权和,其第二个元素是电影二的所有用户评分的一个加权和,因为权重y是各个用户对音乐本身的偏好,所以,A^Ty表示的向量维电影音乐的质量。所以此时A^Ty=\lambda x\lambda的存在是因为权重没有归一化处理。

所以有A^TAx= \lambda x

所以,对于A^TA的每个特征向量都可以理解为电影本身某一维度上的质量分,衡量电影本身不同维度上的特征,和用户没有关系,这里用音乐维度只是方便理解,实际情况中,这些维度并没有真正的解释意义。

所以,根据上面的分析,U的列向量是衡量电影本身某一维度的质量和特征,和用户无关,且越排在前面的向量越能表示电影本身的特征,同理VT的行向量是衡量用户本身某一维度的质量和特征,和电影无关,且越排在前面的向量越能表示用户本身的特征。所以去前k个时,舍掉的都是一些噪音特征。

以上就是这个近似矩阵SVD分解的物理意义,有了这个意义,下面分析怎么使用这个近似矩阵的SVD分解。

对于一个新的用户,其用户评分向量维p=[1,4,0],其中0为未评分,则根绝SVD分解的意义,计算向量pVT_k^T \Sigma_k^{-1}(实际上计算的是U_k中(去噪低维空间里)与p对应的向量。),其就是新用户p在去噪低维空间中对应的向量。

然后再用相似度,在U_k中寻找与p最像的向量\alpha,找到\alpha后,在反推出\alpha对应的用户,这样就找到了,去过噪音之后的相似用户,从而用相似用户的评分去填充新用户未评分的电影评分。

上面过程的代码实现如下:

p=[1,4,0]
U_k=U[:,0:2]
Sigma_k=Sigma[0:2,0:2]
VT_k=VT[0:2,:]
p_new=np.dot(np.dot(p,VT_k.T),np.mat(Sigma_k).I)
p_new=np.array(p_new)[0] 
#这里用余弦相似度
print(cosSim(p_new,U_k[0,:]))
print(cosSim(p_new,U_k[1,:]))
print(cosSim(p_new,U_k[2,:]))
print(cosSim(p_new,U_k[3,:]))
0.38816907259497146
0.9964791723066383
0.88728868904969
0.9999984582439839

所以在低维空间中,U的第一个行向量和p最接近,所以用户1和新用户最像,所以用用户1的评分去填充新用户未评分的评分,得到p=[1,4,7]

同理如果给的不是新用户评分的向量,而是新电影的评分向量,也可以用相同的方法填充未评分的位置。

从上面的分析不难看出,这种传统的基于SVD的算法,要求待分解的评分矩阵是稠密的,若果是稀疏的,结果准确率会下降,但实际情况评分矩阵就是稀疏的,所以,需要下面的方法继续优化。

Funk-SVD

因为基于传统的SVD的协同过滤存在很大的问题,所以引入Funk-SVD算法,下面对其详细介绍。

由上面传统的SVD分析,得到评分矩阵的近似分解为:
A \approx A_k=U_k \Sigma_k VT_k

因为\Sigma_k是k阶对角矩阵,所以有\Sigma_k=\sqrt{\Sigma_k}*\sqrt{\Sigma_k},此时\sqrt{\Sigma_k}也是对角矩阵。所以A的近似分解可化简为:
A \approx A_k=U_k \Sigma_k VT_k=U_k\sqrt{\Sigma_k}*\sqrt{\Sigma_k}VT_k=PQ

其中,P是mxk阶矩阵,仅与电影本身相关,Q是kxn阶矩阵,仅与用户本身相关。

P^T=[p_1,p_2,..p_k],Q=[q_1,q_2,...q_n],则:
A \approx PQ

所以PQ矩阵第i低行,第j列的元素r_{i,j}=p_i^Tq_j

所以,问题就转化为一个优化问题,A的每个元素和矩阵PQ的每个元素之差最小化,既min||A-PQ||_F=min\sum_{i,j} (a_{i,j}-p_i^Tq_j)^2

考虑过拟合问题,我们引入正则项。最后优化问题化简如下:
min(J(p_{1,1},p_{1,2}...p_{m,k},q_{1,1},q_{1,2}...q_{k,n}))=\min \sum_{i,j}( (a_{i,j}-p_i^Tq_j)^2+ \lambda_1 ||p_i||^2+\lambda_2||q_j||^2 )

求解这个优化问题使用梯度下降的方法。

梯度下降

梯度下降算法的介绍在这里不展开了,这里直接求梯度:

\frac{\partial J}{\partial p_{i,k}}=-2\sum_{j=1}^n[ q_{k,j}(a_{i,j}-\sum_{k=1}^K p_{i,k}q_{k,j})+2 \lambda_1 p_{i,k}]

\frac{\partial J}{\partial q_{k,j}}=-2\sum_{j=1}^n[p_{i,k}(a_{i,j}-\sum_{k=1}^K p_{i,k}q_{k,j})+ 2\lambda_2 q_{k,j}]

所以梯度下降的迭代公式是:

\hat{p_{i,k}}=p_{i,k} - \alpha \sum_{j=1}^n{[ -2q_{k,j}(a_{i,j}-\sum_{k=1}^K p_{i,k}q_{k,j})+2 \lambda_1 p_{i,k}]}

\hat{q_{k,j}}=q_{k,j} - \alpha \sum_{i=1}^m{ [ -2p_{i,k}(a_{i,j}-\sum_{k=1}^K p_{i,k}q_{k,j})+ 2\lambda_2 q_{k,j}]}

其中\alpha为学习效率,

代码实现为:

"""
@输入参数:
R:M*N 的评分矩阵
K:隐特征向量维度
max_iter: 最大迭代次数
alpha:步长
lamda:正则化系数

@输出:
分解之后的 P,Q
P:初始化用户特征矩阵 M*K
Q:初始化物品特征矩阵 N*K,Q 的转置是 K*N
"""

# 给定超参数
K = 5
max_iter = 5000000
alpha = 0.0002
lamda = 1

# 核心算法
def LMF_grad_desc(R, K=2, max_iter=1000, alpha=0.0001, lamda=0.002):
    # 定义基本维度参数
    M = len(R)
    N = len(R[0])

    # P、Q 的初始值随机生成
    P = np.random.rand(M, K)
    Q = np.random.rand(N, K)
    Q = Q.T

    # 开始迭代
    for steps in range(max_iter):
        # 对所有的用户 u,物品 i 做遍历,然后对对应的特征向量 Pu、Qi 做梯度下降
        for u in range(M):
            for i in range(N):
                # 对于每一个大于 0 的评分,求出预测评分误差 e_ui
                if R[u][i] > 0:
                    e_ui = np.dot(P[u,:], Q[:,i]) - R[u][i]
                    # 代入公式,按照梯度下降算法更新当前的 Pu、Qi
                    for k in range(K):
                        P[u][k] = P[u][k] - alpha * (2 * e_ui * Q[k][i] + 2 * lamda * P[u][k])
                        Q[k][i] = Q[k][i] - alpha * (2 * e_ui * P[u][k] + 2 * lamda * Q[k][i])

        # u,i 遍历完成,所有的特征向量更新完成,可以得到 P、Q,可以计算预测评分矩阵
        predR = np.dot(P, Q)

        # 计算当前损失函数(所有的预测误差平方后求和)
        cost = 0
        for u in range(M):
            for i in range(N):
                # 对于每一个大于 0 的评分,求出预测评分误差后,将所有的预测误差平方后求和
                if R[u][i] > 0:
                    cost += (np.dot(P[u,:], Q[:,i]) - R[u][i]) ** 2
                    # 加上正则化项
                    for k in range(K):
                        cost += lamda * (P[u][k] ** 2 + Q[k][i] ** 2)
        if cost < 0.0001:
            # 当前损失函数小于给定的值,退出迭代
            break

    return P, Q.T, cost
P, Q , cost=LMF_grad_desc(A,K,max_iter,alpha,lamda)
print(A)
print(np.dot(P,Q.T))
print(cost)
[[1 4 7]
 [9 6 4]
 [5 7 4]
 [4 7 1]]
[[1.00366321 3.9974722  6.98747162]
 [8.98683502 6.00055608 3.99691414]
 [4.99780352 6.99263191 3.99742585]
 [3.99994045 6.98860325 1.00426985]]
0.7409324229378083

以上就是梯度下降法的推导和实现。

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

推荐阅读更多精彩内容