当前位置:   article > 正文

知识图谱嵌入表示实战——基于TransE、Pytorch的知识图谱嵌入项目(源代码和运行实例)_transe pytorch

transe pytorch

目录

一、嵌入相关背景知识

1.1 进行知识图谱嵌入的主要原因

1.2 知识图谱嵌入后得到什么?

1.3 嵌入后可以用来做什么?

二、常用嵌入方法

1.1 基础方法介绍

1.2 TransE介绍

三、基于TransE的知识图谱嵌入的项目(代码,运行)

3.1 目录(包含注释)

3.1.1 目录结构

3.2 运行步骤

3.2.1 下载安装依赖包

3.2.2 数据预处理

3.2.3 嵌入保存模型

3.2.4 不足

3.2.5 展望

3.3 代码(复制粘贴)

3.3.1 rel_tirple_change_index.py

3.3.2 rel_triple.txt

3.3.3 TransE_embedding.py(包含注释)

3.3.4  requirements.txt


码字不易,帮忙点个赞哈

一、嵌入相关背景知识

1.1 进行知识图谱嵌入的主要原因

知识图谱嵌入主要是为了方便计算,捕捉语义,结合机器学习,数据压缩,自动学习等。

1. 知识图谱中的关系和实体通常是离散的和稀疏的,不利于计算。通过嵌入可以将它们映射到密集的低维空间,方便计算与推理。

2. 嵌入可以捕捉知识图谱中的语义信息和结构信息,通过嵌入的距离和方向来表示实体和关系之间的相似性。这有利于知识图谱的完成与推理。

3. 嵌入后的向量空间可以直接利用现有的机器学习算法和神经网络来进行训练和推理。这给知识图谱中关系推理和triples分类带来了便利。

4. 嵌入后的向量表示更加紧凑,有利于知识图谱的存储和传输。

5. 嵌入学习可以直接从大规模的知识图谱中学习出实体和关系的潜在表示,这些表示可以捕捉到数据中的重要模式和特征。这能使机器更好地理解知识图谱和进行推理。

1.2 知识图谱嵌入后得到什么?

知识图谱嵌入表示后得到是一系列嵌入矩阵,主要是如下几种:

1. 实体嵌入矩阵(Entity Embedding Matrix):每个实体会被映射到一个低维的密集向量,这些向量组成的矩阵就是实体嵌入矩阵。它包含了实体之间的相似度和关系信息。

2. 关系嵌入矩阵(Relation Embedding Matrix):每个关系会被映射到一个低维的密集向量,这些向量组成的矩阵就是关系嵌入矩阵。它包含了不同关系之间的相似度和语义信息。

3. 实体-关系嵌入矩阵(Entity-Relation Embedding Matrix):同时考虑实体和关系,将它们映射到一个共享的嵌入空间。这可以同时编码实体间的相似度和实体-关系间的语义关联。

4. 三元组嵌入矩阵(Triple Embedding Matrix):直接将知识图谱中的三元组(头实体,关系,尾实体)映射到低维空间,获得三元组的嵌入表示。这可以直接对三元组进行各种机器学习任务。

5. 路径嵌入矩阵(Path Embedding Matrix):对知识图谱中的关系路径(如A-R1->B-R2->C)进行嵌入,获得路径的整体嵌入表示。这可以用于关系推理和路径搜索。

这些矩阵给知识图谱的组成要素赋予了数学上的表达,并编码了它们之间的相关性、相似性与语义信息。

1.3 嵌入后可以用来做什么?

获得知识图谱的嵌入矩阵后,可以进行以下几方面任务:

1. 知识图谱完成(Link Prediction):使用嵌入矩阵计算三元组(head, relation, tail)的打分,从而预测知识图谱中的缺失链接。这可以补全知识图谱,发现新的事实。

2. 关系推理(Relation Inference):可以判断两个实体之间的潜在关系,或判断某个实体到另一个实体的关系路径。这可以产生新的关系猜测。

3. 分类(Classification):可以将嵌入矩阵输入到分类器中,进行实体分类、关系分类以及三元组分类。这可以扩充知识图谱的分类体系。

4. 检索(Retrieval):可以根据输入的查询实体或关系,返回相似的或相关的实体或关系。这可以用于知识图谱中的搜索与问答。

5. 聚类(Clustering):可以根据嵌入矩阵中的距离和相似性,对实体或关系进行聚类。这可以发现知识图谱中的聚类结构和类别。

6. 可视化(Visualization):可以将高维的嵌入矩阵投影到二维或三维空间中进行可视化。这有利于我们直观地理解知识图谱的结构和嵌入表示。

7. 迁移学习(Transfer Learning):可以利用源知识图谱学习得到的嵌入表示,迁移到目标知识图谱上。这可以节省目标知识图谱的标注数据,加快学习过程。

所以,获得知识图谱的嵌入矩阵后,可以进行关系推理、知识图谱完成、检索、分类、聚类等多种任务。这使得我们可以对知识图谱进行扩充、补全、组织与理解,并产生新的结论或关系。

二、常用嵌入方法

1.1 基础方法介绍

知识图谱常用的嵌入方法主要有TransE、TransH、TransR、RESCAL、DistMult、ComplEx、RotatE、SimplE和TuckER等。这些方法有的基于翻译,有的基于张量分解,有的结合了两者。

1. TransE: Translating Embeddings for Knowledge Graph Completion。这是一种基于翻译的嵌入方法,它假设实体 embedding 空间是低维 Euclidean 空间,关系可以被看作是这个空间中的一个翻译。

2. TransH: Knowledge Graph Embedding by Translating on Hyperplanes。这种方法引入超平面,使得 Embedding 可以在超平面上进行翻译。

3. TransR: Learning Entity and Relation Embeddings for Knowledge Graph Completion。这种方法假设每个关系都有一个专属的Embedding空间,实体和关系的Embedding都在其关系的Embedding空间内完成翻译。

4. RESCAL: Relational Embeddings from Latent Spaces。这是基于张量分解的Embedding方法。它假设三元组(h,r,t)可以由h和t的Latent Embedding以及关系r的Embedding matrice来构成。

5. DistMult: Embedding Entities and Relations for Learning and Inference in Knowledge Bases。这是一种基于tensor分解的翻译方法,它假设三元组(h,r,t)可以由h,r和t的Embedding相乘来构成。

6. ComplEx: Complex Embeddings for Simple Link Prediction。这也是一种基于tensor分解的方法,它使用复数来表示实体和关系的Embedding,并通过三元组(h,r,t)的Embedding点乘来构成预测。

7. RotatE: RotatE: Knowledge Graph Embedding by Relational Rotation in Complex Space。这是一种在复数空间内进行Embedding旋转的方法。它假设实体Embedding存在于复数空间中,而一个关系则对应一个旋转矩阵,通过对实体Embedding施加旋转来完成翻译。

1.2 TransE介绍

TransE模型会学习实体和关系的嵌入向量,并假设嵌入空间满足以下约束:

头实体嵌入 + 关系嵌入 = 尾实体嵌入

H + R = T

参考博文:知识图谱嵌入方法-transE_SU_ZCS的博客-CSDN博客

三、基于TransE的知识图谱嵌入的项目(代码,运行)

3.1 目录(包含注释)

3.1.1 目录结构

1)data里面放的是三元组数据集(rel_triple.txt)和处理三元组的的代码(rel_tirple_change_index.py)

2)TransE_embedding.py是进行知识图谱嵌入的源代码。

3)requirements.txt放的是这个项目所依赖的包

更新一下目录结构,之前忘记放requirements.txt

 

3.2 运行步骤

3.2.1 下载安装依赖包

执行

pip install -r requirements.txt

会自动下载安装

3.2.2 数据预处理

进入data文件夹,然后执行

python rel_tirple_change_index.py

会将三元组信息处理好,然后生成红框中三个文件,

all_ent.txt:全部实体及其索引,

all_rel.txt:全部关系及其索引,

index_rel_triple.txt:三元组对应的索引三元组。

3.2.3 嵌入保存模型

 最后这里输出的是,实体4和实体10的相似度,可以根据自己的需要进行修改。

3.2.4 不足

对于这样的两个三元组:

申请人    申请    车辆备案登记表
申请人    申请    合同汇签表

利用TransE模型进行嵌入学习会出现以下问题:1. 关系推移不成立。根据TransE的关系翻译假设,我们会学习到:

申请人 → e1
申请 → r
车辆备案登记表 → e2
合同汇签表 → e3

满足:e1 + r = e2 且 e1 + r = e3

1)实体嵌入没有区分度。根据上面的例子,申请人这个实体对于申请关系来说,翻译结果是车辆备案登记表和合同汇签表两个完全不同的实体。这会造成申请人这个实体的嵌入表示缺乏区分度,错把两个不同实体的值累加在一起。

2)无法处理多义性关系。申请这个关系在两个三元组中的含义是不同的,分别是车辆备案登记表的申请和合同汇签表的申请。但是TransE模型会学习到同一个申请关系嵌入,无法区分这两个含义,造成误导。

3)嵌入空间受限。TransE要求实体与关系嵌入都在同一个嵌入空间中,这限制了模型表达的能力。有些复杂的关系翻译无法在同一个嵌入空间中准确表示。所以,对于这样的包含多义关系和同一关系的多个翻译结果的三元组集,TransE模型会出现关系推移失败、实体嵌入缺乏区分度、无法处理多义关系以及嵌入空间受限等问题。

后续优化:

1. 使用不同向量空间表示实体与关系嵌入,如TransH,TransR等模型。

2. 在关系上添加额外的sensitive特征以区分不同的关系含义,如TransD,TransF等模型。

3. 不再假定简单的关系翻译,采用更复杂的关系建模方法,如ComplEx,RotatE等模型。

4. 结合图神经网络等方法,利用知识图谱的全局拓扑结构信息进行嵌入学习,如R-GCN,CompGCN等模型。

3.2.5 展望

1. 知识图谱补全:我们可以利用实体与关系的嵌入来预测知识图谱中缺失的三元组或实体,实现知识图谱的自动扩展。

2. 关系推理:通过关系的嵌入,我们可以得出隐含的关系或预测两个实体之间最可能的关系路径。这可以应用在推荐系统和问答系统中。

3. 实体相关性与聚类:利用实体的嵌入,我们可以计算任意两个实体之间的相关性或相似性,并可以对实体进行聚类。这在实体分类和检索等任务中很有用。

4. 命题实体识别:通过对句子或文档中出现的实体进行嵌入并与知识图谱中的实体嵌入进行比较,我们可以识别出命题实体,并链接到知识图谱中。这可以用于增强问答系统和信息抽取的实体识别精度。

5. 路径搜索:通过实体和关系的嵌入,我们可以在知识图谱中搜索最相关的两实体之间的路径。这可用于两个实体的关系推理和路径规划等任务。

6. 表示学习:实体和关系的嵌入本身就是连续的特征表示, Encoding了实体与关系之间的语义与依赖关系,这些表示可以应用到各种机器学习模型中,用于关系分类、三元组分类等自然语言处理任务。

3.3 代码(复制粘贴)

3.3.1 rel_tirple_change_index.py

  1. with open(file="rel_triple.txt") as f:
  2. lines = f.readlines()
  3. entities = dict() # 存储头实体与索引
  4. relations = dict() # 存储关系与索引
  5. ent_idx = 0 # 记录当前索引
  6. rel_idx = 0
  7. for line in lines:
  8. items = line.strip().split()
  9. head = items[0]
  10. relation = items[1]
  11. tail = items[2]
  12. if head not in entities: # 首次遇到头实体
  13. entities[head] = ent_idx
  14. ent_idx += 1
  15. if tail not in entities: # 首次遇到尾实体
  16. entities[tail] = ent_idx
  17. ent_idx += 1
  18. if relation not in relations: # 首次遇到关系
  19. relations[relation] = rel_idx
  20. rel_idx += 1
  21. with open('index_rel_triple.txt', 'w') as f:
  22. for line in lines:
  23. items = line.strip().split()
  24. head = items[0]
  25. relation = items[1]
  26. tail = items[2]
  27. f.write(f'{entities[head]} {relations[relation]} {entities[tail]}\n')
  28. """
  29. 实体和关系也可以采用不同的索引空间,保持各自索引的唯一性。
  30. """
  31. with open('all_ent.txt', 'w') as f:
  32. for key, value in sorted(entities.items(), key=lambda item: item[1]):
  33. f.write(f'{value} {key} \n')
  34. with open('all_rel.txt', 'w') as f:
  35. for key, value in sorted(relations.items(), key=lambda item: item[1]):
  36. f.write(f'{value} {key} \n')

3.3.2 rel_triple.txt

  1. 申请单位领导 审批 车辆备案
  2. 申请单位领导 请求意见 保卫处
  3. 保卫处领导 通知办结 申请人
  4. 保卫处领导 执行完结 车辆备案
  5. 申请人 执行完结 车辆备案
  6. 申请人 申请 车辆备案登记表
  7. 车辆备案登记表 属于 车辆备案
  8. 申请人 申请 合同汇签表
  9. 合同汇签表 属于 党政办合同会签
  10. 招标办公室领导 请求审批 党政办合同会签
  11. 审计处领导 请求审批 党政办合同会签
  12. 计划财务处领导 请求审批 党政办合同会签
  13. 法律事务办公室领导 请求审核 党政办合同会签
  14. 学校办公室领导 审批 党政办合同会签
  15. 校领导 盖章 党政办合同会签
  16. 学校办公室领导 执行完结 党政办合同会签
  17. 申请人 申请 设备入网申请表
  18. 申请人 完结 设备入网
  19. 申请单位领导 审核 设备入网申请表
  20. 招标办领导 通知办结 申请人
  21. 申请人 执行完结 招标办采购
  22. 活动登记 申请审批 申请单位领导
  23. 申请单位领导 申请审批 保卫处领导
  24. 保卫处领导 通知办结 保卫处相关人员
  25. 保卫处领导 通知办结 申请人
  26. 校史馆主管校领导 申请审批 党政办领导

3.3.3 TransE_embedding.py(包含注释)

  1. """
  2. 1. 定义DBPDataset类表示数据集,重写__getitem__方法返回三元组。
  3. 2. 定义KGEmb模型表示知识图谱Embedding,包含实体与关系Embedding层。
  4. 3. 构建DataLoader加载数据集,定义优化器与TransE损失函数进行训练。
  5. 4. 训练结束后,ent_emb与rel_emb分别为实体与关系Embedding结果。
  6. 5. 将模型参数保存为model.pkl文件,用于重新加载模型。
  7. """
  8. import torch
  9. from torch import nn
  10. from torch.utils.data import Dataset, DataLoader
  11. from scipy.spatial.distance import cosine
  12. # 加载数据集
  13. class DBPDataset(Dataset):
  14. def __init__(self, data_path):
  15. triples = []
  16. with open(data_path) as f:
  17. for line in f:
  18. head, rel, tail = line.strip().split()
  19. triples.append((int(head), int(rel), int(tail)))
  20. self.triples = triples
  21. def __len__(self):
  22. return len(self.triples)
  23. def __getitem__(self, idx):
  24. head, rel, tail = self.triples[idx]
  25. #将输入的头实体head,关系rel和尾实体tail构建成pytorch的LongTensor类型。
  26. return torch.LongTensor([head, rel, tail])
  27. # Embedding模型
  28. class KGEmb(nn.Module):
  29. def __init__(self, n_ent, n_rel, dim=100):
  30. """
  31. :param n_ent: 实体的总数,即数据集中不重复的实体数量。
  32. :param n_rel: 关系的总数,即数据集中不重复的关系数量。
  33. :param dim: Embedding的维度,即每个实体/关系的向量表示长度。
  34. """
  35. super().__init__()
  36. self.ent_emb = nn.Embedding(n_ent, dim)
  37. self.rel_emb = nn.Embedding(n_rel, dim)
  38. def forward(self, x):
  39. """
  40. :param x:
  41. :return:头实体Embedding、关系Embedding和尾实体Embedding。
  42. """
  43. head, rel, tail = x[:, 0], x[:, 1], x[:, 2]
  44. head_emb = self.ent_emb(head)
  45. rel_emb = self.rel_emb(rel)
  46. tail_emb = self.ent_emb(tail)
  47. return head_emb, rel_emb, tail_emb
  48. # 训练代码
  49. dataset = DBPDataset('./data/index_rel_triple.txt')
  50. #设置固定的随机种子,使每次初始化产生的随机参数相同
  51. torch.manual_seed(123)
  52. model = KGEmb(n_ent=10000, n_rel=100)
  53. """
  54. 1. Adam优化器更新Embedding使loss下降
  55. 2. DataLoader生成batch数据输入模型
  56. 3. 打乱数据顺序使模型泛化能力提高
  57. """
  58. #使用Adam优化器,对模型中的所有可训练参数进行更新,通过model.parameters()获得模型的所有可训练参数(Embedding)
  59. opt = torch.optim.Adam(model.parameters())
  60. # 使用DataLoader加载dataset数据集,每批数据大小为8192条三元组,shuffle=True:每个epoch打乱数据顺序
  61. loader = DataLoader(dataset, batch_size=8192, shuffle=True)
  62. for epoch in range(200):
  63. """
  64. opt.zero_grad() 的作用是:清空优化器opt中保存的梯度值。
  65. 每一次的 loss.backward() 都会累加当前的梯度到已经存储的梯度值。如果不清空梯度,梯度就会不断累加,最终的梯度值错误。
  66. 所以,在每次迭代(每个batch)开始之前,需要调用 opt.zero_grad() 清空优化器中的梯度值,确保本次迭代计算的梯度值正确。
  67. """
  68. for step, x in enumerate(loader):
  69. opt.zero_grad()
  70. h, r, t = model(x)
  71. loss = torch.sum(h + r - t) # TransE损失函数
  72. loss.backward() # 反向传播,计算梯度值
  73. opt.step() # 优化器更新参数
  74. # ent_emb:
  75. # - 行数为实体总数n,列表示每个实体的Embedding
  76. # - 列数为Embedding维度d,一般在50到200之间
  77. # - 所以ent_emb的shape为[n, d],是一个n行d列的矩阵
  78. # - 可以通过ent_emb[ent]访问第ent个实体的Embedding向量
  79. # rel_emb:
  80. # - 行数为关系总数m,列表示每个关系的Embedding
  81. # - 列数同样为Embedding维度d
  82. # - 所以rel_emb的shape为[m, d],是一个m行d列的矩阵
  83. # - 可以通过rel_emb[rel]访问第rel个关系的Embedding向量
  84. # NumPy矩阵是一种用于科学计算的二维数组,支持各种矩阵运算与操作
  85. # 得到Embedding结果,ent_emb:实体Embedding矩阵(numpy类型),rel_emb:关系Embedding矩阵(numpy类型)
  86. ent_emb = model.ent_emb.weight.detach().numpy()
  87. rel_emb = model.rel_emb.weight.detach().numpy()
  88. #保存的不仅仅是Embedding权重,还包括优化器状态,超参数等信息。当我们重新加载这个模型时,它的全部变量都会恢复到保存时的状态,可以直接使用或继续训练。
  89. torch.save(model.state_dict(), '../model.pkl') # 保存模型
  90. ent1_emb = ent_emb[4] # 实体ent1的Embedding
  91. ent2_emb = ent_emb[10] # 实体ent2的Embedding
  92. sim = cosine(ent1_emb, ent2_emb) # 两个实体的相似度
  93. """
  94. - sim > 0.8 : 两个实体语义非常相似,可以认为实体1和实体2表达相同或相近的概念。
  95. - 0.5 < sim < 0.8 : 两个实体语义较相似,在某种程度上表达相近的概念,但也有一定差异。
  96. - 0.3 < sim < 0.5 : 两个实体语义一般,既有一定相似度但差异也比较大。表达的概念只是有部分重叠或相关。
  97. - sim < 0.3 : 两个实体语义不太相似,差异较大。表达的概念有较大差异。
  98. """
  99. print(sim)

3.3.4  requirements.txt

  1. appnope==0.1.3
  2. asttokens==2.2.1
  3. attrs==22.2.0
  4. ax-platform==0.3.1
  5. backcall==0.2.0
  6. botorch==0.8.3
  7. certifi==2023.5.7
  8. chardet==3.0.4
  9. charset-normalizer==3.1.0
  10. click==8.1.3
  11. comm==0.1.3
  12. ConfigSpace==0.6.1
  13. contextlib2==21.6.0
  14. contourpy==1.0.7
  15. cycler==0.11.0
  16. data==0.4
  17. dataclasses==0.6
  18. debugpy==1.6.7
  19. decorator==5.1.1
  20. distlib==0.3.6
  21. exceptiongroup==1.1.1
  22. execnet==1.9.0
  23. executing==1.2.0
  24. filelock==3.11.0
  25. fonttools==4.39.4
  26. funcsigs==1.0.2
  27. future==0.18.3
  28. gensim==3.8.3
  29. googletrans==3.0.0
  30. gpytorch==1.9.1
  31. graphviz==0.20.1
  32. greenlet==2.0.2
  33. h11==0.9.0
  34. h2==3.2.0
  35. hpack==3.0.0
  36. hpbandster==0.7.4
  37. hstspreload==2023.1.1
  38. httpcore==0.9.1
  39. httpx==0.13.3
  40. hyperframe==5.2.0
  41. idna==2.10
  42. igraph==0.10.4
  43. importlib-metadata==6.1.0
  44. importlib-resources==5.12.0
  45. iniconfig==2.0.0
  46. ipykernel==6.22.0
  47. ipython==8.12.0
  48. ipywidgets==8.0.6
  49. isodate==0.6.1
  50. jedi==0.18.2
  51. Jinja2==3.1.2
  52. joblib==1.2.0
  53. jupyter_client==8.1.0
  54. jupyter_core==5.3.0
  55. jupyterlab-widgets==3.0.7
  56. kiwisolver==1.4.4
  57. -e git+ssh://git@github.com/uma-pi1/kge.git@f4fa52dde2fac11c60e0f6554190e386185fd7cb#egg=libkge
  58. linear-operator==0.3.0
  59. llvmlite==0.39.1
  60. MarkupSafe==2.1.2
  61. matplotlib==3.7.1
  62. matplotlib-inline==0.1.6
  63. mock==5.0.1
  64. more-itertools==9.1.0
  65. mpmath==1.3.0
  66. multipledispatch==0.6.0
  67. nest-asyncio==1.5.6
  68. netifaces==0.11.0
  69. networkx==3.1
  70. nltk==3.5
  71. numba==0.56.4
  72. numpy==1.19.1
  73. opt-einsum==3.3.0
  74. packaging==23.0
  75. pandas==1.1.1
  76. parso==0.8.3
  77. path==16.6.0
  78. path.py==12.5.0
  79. patsy==0.5.3
  80. pexpect==4.8.0
  81. pickleshare==0.7.5
  82. Pillow==9.5.0
  83. pip==23.1.2
  84. platformdirs==3.2.0
  85. plotly==5.14.1
  86. pluggy==1.0.0
  87. prompt-toolkit==3.0.38
  88. psutil==5.9.4
  89. ptyprocess==0.7.0
  90. pure-eval==0.2.2
  91. Pygments==2.14.0
  92. pyparsing==3.0.9
  93. pyro-api==0.1.2
  94. pyro-ppl==1.8.4
  95. Pyro4==4.82
  96. pytest==7.2.2
  97. pytest-shutil==1.7.0
  98. python-dateutil==2.8.2
  99. pytz==2023.3
  100. PyYAML==6.0
  101. pyzmq==25.0.2
  102. rdflib==5.0.0
  103. regex==2023.5.5
  104. requests==2.31.0
  105. rfc3986==1.5.0
  106. scikit-learn==1.2.2
  107. scipy==1.9.3
  108. serpent==1.41
  109. setuptools==65.5.1
  110. six==1.16.0
  111. smart-open==6.3.0
  112. sniffio==1.3.0
  113. SQLAlchemy==1.4.47
  114. stack-data==0.6.2
  115. statsmodels==0.13.5
  116. sympy==1.11.1
  117. tenacity==8.2.2
  118. termcolor==2.2.0
  119. texttable==1.6.7
  120. threadpoolctl==3.1.0
  121. tomli==2.0.1
  122. torch==1.13.0
  123. torch-geometric==2.3.1
  124. torchaudio==0.7.0
  125. torchvision==0.8.1
  126. torchviz==0.0.2
  127. tornado==6.2
  128. tqdm==4.65.0
  129. traitlets==5.9.0
  130. typeguard==2.13.3
  131. typing_extensions==4.5.0
  132. tzdata==2023.3
  133. urllib3==2.0.2
  134. virtualenv==20.21.0
  135. wcwidth==0.2.6
  136. wheel==0.38.4
  137. widgetsnbextension==4.0.7
  138. zipp==3.15.0

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/549541
推荐阅读
相关标签
  

闽ICP备14008679号