当前位置:   article > 正文

K—近邻算法_k近邻算法

k近邻算法

1. K—近邻算法简介


1. K—近邻算法(KNN)的概念

K Nearest Neighbor 算法又称KNN算法,这个算法是机器学习里一个比较经典的算法,总体来说是比较简单的。

1)定义

一个样本与在特征空间中k个最相似(即样本空间中距离最近)的大多数样本属于同一个类别。

来源:KNN算法最早是由Cover和Hart提出的一种分类算法。

2)距离公式

两个样本的距离可以通过如下公式计算,又叫欧式距离:

  • 二维平面上点a(x1, y1)与b(x2, y2)的欧式距离: d 12 = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 d_{12}=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2} d12=(x1x2)2+(y1y2)2
  • 三维空间点a(x1, y1, z1)与b(x2, y2, z2)的欧氏距离: d 12 = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( z 1 − z 2 ) 2 d_{12}=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2} d12=(x1x2)2+(y1y2)2+(z1z2)2
  • n维空间点a(x11, x12,…,x1n)与b(x21, x22,…,x2n)间的欧式距离(两个n维向量): d 12 = ∑ k = 1 n ( x 1 k − x 2 k ) 2 d_{12}=\sqrt{\sum \limits_{k=1}^n(x_{1k}-x_{2k})^2} d12=k=1n(x1kx2k)2

2. 电影类型分析

假设我们现在有很多部电影。

序号电影名称搞笑镜头拥抱镜头打斗镜头电影类型
1功夫熊猫39031喜剧片
2叶问33265动作片
3二次曝光2355爱情片
4代理情人83417爱情片
5新步步惊心83417爱情片
6谍影重重5257动作片
7美人鱼21175喜剧片
8宝贝当家4529喜剧片
9唐人街探案23317

其中9号电影不知道类别,如何去预测?我们就可以用到K近邻算法的思想,其中每一个样本都可以看做是一个n维向量,使用公式 d 12 = ∑ k = 1 n ( x 1 k − x 2 k ) 2 d_{12}=\sqrt{\sum \limits_{k=1}^n(x_{1k}-x_{2k})^2} d12=k=1n(x1kx2k)2 来计算9号电影和其余样本的相似程度。

序号电影名称搞笑镜头拥抱镜头打斗镜头电影类型与9号电影的距离K=5时
1功夫熊猫39031喜剧片21.47相似
2叶问33265动作片52.01
3二次曝光2355爱情片43.42
4代理情人83417爱情片40.57相似
5新步步惊心83417爱情片34.44相似
6谍影重重5257动作片43.87
7美人鱼21175喜剧片18.55相似
8宝贝当家4529喜剧片23.43相似

找出了K个距离最近的电影,此时K=5,就找了5个最相似的电影。

3. KNN算法流程总结

1)计算已知类别数据集中的点与当前点之间的距离

2)按照距离递增次序排序

3)选取与当前点距离最小的K个点

4)统计前K个点所在类别出现的频率

5)返回前K个点出现频率最高的类别作为当前点的预测分类


2. K—近邻算法api初步使用


sklearn.neighbors.KNeighborsClassifier(n_neighbors=5):参数只有一个n_neighbors,为int类型,默认值为5,表示查询默认使用的邻居数。

下面我们用一个案例来使用以下这个函数。

1. 步骤分析

1)获取数据集

2)数据基本处理(该案例中省略)

3)特征工程(该案例中省略)

4)机器学习

5)模型评估(该案例中省略)

2. 代码实现

# 1. 导入模块
from sklearn.neighbors import KNeighborsClassifier

# 2. 构造数据集
x = [[1], [2], [10], [20]] # 特征值
y = [0, 0, 1, 1] # 目标值

# 3. 机器学习 —— 模型训练

# 3.1 实例化api
estimator = KNeighborsClassifier(n_neighbors=1)
# 3.2 使用fit方法进行训练
estimator.fit(x, y)
# 3.3 进行预测
ret = estimator.predict([[1]])

# 打印预测结果
print(ret)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

输出:

[0]
  • 1

3. 距离度量


1. 欧式距离(Euclidean Distance)

1)二维平面上点a(x1, y1)与b(x2, y2)的欧式距离: d 12 = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 d_{12}=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2} d12=(x1x2)2+(y1y2)2

2)三维空间点a(x1, y1, z1)与b(x2, y2, z2)的欧氏距离: d 12 = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( z 1 − z 2 ) 2 d_{12}=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2} d12=(x1x2)2+(y1y2)2+(z1z2)2

3)n维空间点a(x11, x12,…,x1n)与b(x21, x22,…,x2n)间的欧式距离(两个n维向量): d 12 = ∑ k = 1 n ( x 1 k − x 2 k ) 2 d_{12}=\sqrt{\sum \limits_{k=1}^n(x_{1k}-x_{2k})^2} d12=k=1n(x1kx2k)2

2. 曼哈顿距离(Manhattan Distance)

在这里插入图片描述
在曼哈顿街区,要从一个十字路口开车到另一个十字路口,驾驶距离显然不是两点间的直线距离。这个实际驾驶距离就是“曼哈顿距离”,曼哈顿距离也称为“城市街区距离”(City Block distance)。

  • 二维平面两点a(x1, y1)与b(x2, y2)间的曼哈顿距离: d 12 = ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ d_{12}=|x_1-x_2|+|y_1-y_2| d12=x1x2+y1y2

  • n维空间点a(x11, x12,…,x1n)与b(x21, x22,…,x2n)间的曼哈顿距离: d 12 = ∑ k = 1 n ∣ x 1 k − x 2 k ∣ d_{12}=\sum \limits_{k=1}^n|x_{1k}-x_{2k}| d12=k=1nx1kx2k

3. 切比雪夫距离(Chebyshev Distance)

在这里插入图片描述
国际象棋中,国王可以直行、横行、斜行,所以国王走一步可以移动到相邻8个方格中的任意一个。国王从格子(x1, y1)走到格子(x2, y2)最少需要多少步?这个距离就叫切比雪夫距离。

  • 二维平面两点a(x1, y1)与b(x2, y2)间的切比雪夫距离: d 12 = m a x ( ∣ x 1 − x 2 ∣ , ∣ y 1 − y 2 ∣ ) d_{12}=max(|x_1-x_2|,|y_1-y_2|) d12=max(x1x2,y1y2)
  • n维空间点a(x11, x12,…,x1n)与b(x21, x22,…,x2n)间的切比雪夫距离: d 12 = m a x ( ∣ x 1 k − x 2 k ∣ ) d_{12}=max(|x_{1k}-x_{2k}|) d12=max(x1kx2k)

4. 闵可夫斯基距离(Minkowski Distance)

闵氏距离不是一种距离,而是一组距离的定义,是对多个距离度量公式的概括性的表述。两个n维向量a(x11, x12,…,x1n)与b(x21, x22,…,x2n)间的闵可夫斯基距离定义为:

d 12 = ∑ k = 1 n ∣ x 1 k − x 2 k ∣ p p d_{12}=\sqrt[p]{\sum \limits_{k=1}^n|x_{1k}-x_{2k}|^p} d12=pk=1nx1kx2kp

其中p是一个变量参数:

  • 当p=1时,就是曼哈顿距离;
  • 当p=2时,就是欧式距离;
  • 当p → ∞ \to\infty ,就是切比雪夫距离。

根据p的不同,闵氏距离可以表示某一类/种的距离。

1)注意:闵氏距离、曼哈顿距离、欧氏距离、和切比雪夫距离都存在明显的缺点:

  • 二维样本(身高[单位:cm],体重[单位:kg]),现有3个样本:a(180, 50), b(190, 50), c(180, 60)。a与b的闵氏距离,等于a与c的闵氏距离。但实际上身高的10cm并不能和体重的10kg划等号。

2)闵氏距离的特点:

  • 将各个分量的量纲(scale),也就是“单位”相同的看待了;
  • 未考虑各个分量的分布(期望,方差等)可能是不同的。

5. 标准化欧氏距离(Standardized EuclideanDistance)

标准化欧氏距离是针对欧氏距离的缺点进行的一种改进。思路:既然数据各分量的分布不一样,那就先将各个分量都“标准化”到均值,方差相等。

s k s_k sk表示各个维度的标准差:

  • 标准化欧式距离公式: d 12 = ∑ k = 1 n ( x 1 k − x 2 k s k ) 2 d_{12}=\sqrt{\sum \limits_{k=1}^n(\dfrac{x_{1k}-x_{2k}}{s_k})^2} d12=k=1n(skx1kx2k)2

如果将方差的倒数看成一个权重,也可以称之为加权欧氏距离(Weighted Euclidean distance)。

6. 余弦距离(Consine Distance)

几何中,夹角余弦可用来衡量两个向量方向的差异。机器学习中,借用这一概念来衡量样本之间的差异。

  • 二维空间中向量a(x1, y1)与向量B(x2, y2)的夹角余弦公式: cos ⁡ θ = x 1 x 2 + y 1 y 2 x 1 2 + y 1 2 x 2 2 + y 2 2 \cos \theta=\dfrac{x_1x_2+y_1y_2}{\sqrt{x_1^2+y_1^2}\sqrt{x_2^2+y_2^2}} cosθ=x12+y12 x22+y22 x1x2+y1y2
  • 两个n维样本点a(x11, x12,…,x1n)与b(x21, x22,…,x2n)的夹角余弦为: cos ⁡ θ = ∑ k = 1 n x 1 k x 2 k x 1 2 + y 1 2 x 2 2 + y 2 2 \cos\theta=\dfrac{\sum \limits_{k=1}^nx_{1k}x_{2k}}{\sqrt{x_1^2+y_1^2}\sqrt{x_2^2+y_2^2}} cosθ=x12+y12 x22+y22 k=1nx1kx2k

夹角余弦取值范围为[-1, 1]。余弦越大表示两个向量的夹角越小,余弦越小表示两向量的夹角越大。当两个向量的方向重合时,余弦取最大值1;相反时,取最小值-1。


4. K值的选择


1. 直观解释:

序号电影名称搞笑镜头拥抱镜头打斗镜头电影类型
1功夫熊猫39031喜剧片
2叶问33265动作片
3二次曝光2355爱情片
4代理情人83417爱情片
5新步步惊心83417爱情片
6谍影重重5257动作片
7美人鱼21175喜剧片
8宝贝当家4529喜剧片
9唐人街探案23317
  • K值过小时,容易受到异常点的影响。
  • K值过大会受样本均衡的影响。

就比如有一个电影和唐人街探案的距离很近,但是这个电影是异常的,这时就会得到不准确的结果;如果上述各类型的电影分布很均匀,那么当K值过大时,筛选出的相似电影可能类型分布也很均匀,这时就会出现问题。

2. 概念性解释:

1)选择较小的K值,就相当于用较小领域中的训练实例进行预测,“学习”近似误差会减小,只有与输入实例较近或相似的训练实例才会对预测结果起作用,与此同时带来的问题是“学习”的估计误差会增大,也就是说K值的减小就意味着整体模型变得复杂,容易发生过拟合

2)选择较大的K值,就相当于用较大领域中的训练实例进行预测,其优点是可以减少学习的估计误差,但缺点是学习的近似误差会增大。这时,与输入实例较远(不相似)的训练实例也会对预测器作用,是预测发生错误,且K值的增大就意味着整体的模型变得简单,容易欠拟合

3)K=N(N为训练样本个数),完全不足取,因为此时无论输入实例是什么,都只是简单的预测它属于在训练实例中最多的类,模型过于简单,忽略了训练实例中的大量有用信息。

在实际应用中,K一般取较小值。


5. kd树


问题导入:

实现k近邻算法时,主要考虑的问题是如何对训练数据进行快速k近邻搜索。这在特征空间的维数大及训练数据容量时尤其必要。

k近邻算法最简单的实现是线性扫描(穷举搜索),即要计算输入实例与每一个训练实例的距离。计算并存储好以后,再查找k近邻。当训练集很大时,计算非常耗时。

为了提高KNN搜索的效率,可以考虑使用特殊的结构存储训练数据,以减小计算距离的次数。


5.1 构造kd树


1. 什么是kd树?

kd树是一种对k维空间中的实例点进行存储,以便对其进行快速检索的树形数据结构。

  • 本质:二叉树(一般是平衡二叉树),表示对k维空间的一个划分;
  • 构造过程:不断地用垂直于坐标轴的超平面将k维空间切分,形成k维超矩形区域;
  • 注意:kd树的每一个节点对应于一个k维超矩形区域;平衡的kd树搜索时未必是最优的。

2. 构造kd树

先输入k维空间数据集 T = {x1, x2, …, xN},其中 xi = (xi(1), xi(2), …, xi(k))T,再输出kd树:

1)开始:构造根节点。

  • 选取方差最大的一组特征作为坐标轴,假设是 x i ( 1 ) x_i^{(1)} xi(1)对应的一组特征,以训练集中的所有数据 x ( 1 ) x^{(1)} x(1)坐标中的中位数作为切分点,将超矩形区域切分成两个子区域,以该切分点作为根节点;
  • 由根节点生出深度为1的左右子节点,左节点对应坐标小于切分点,右节点对应坐标大于切分点。

2)重复

  • 对深度为 j j j的节点,选择 x ( I ) x^{(I)} x(I)为切分坐标轴, I = j m o d    k + 1 I = j\mod k+1 I=jmodk+1,以该节点区域中所有实例 x ( I ) x^{(I)} x(I)坐标的中位数作为切分点,将该区域划分为两个子区域;
  • 生成深度为 j + 1 j+1 j+1的左右子节点。左节点对应坐标小于切分点,右节点对应坐标大于切分点。

3)结束

  • 直到两个子区域没有实例时停止。

3. 上例题:以一个二维的样本空间为例

输入:训练集 T = { ( 2 , 3 ) , ( 5 , 4 ) , ( 9 , 6 ) , ( 4 , 7 ) , ( 8 , 1 ) , ( 7 , 2 ) } T=\{(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)\} T={(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)}

x ( 1 ) x^{(1)} x(1)轴对应的数: 2 , 5 , 9 , 4 , 8 , 7 2,5,9,4,8,7 2,5,9,4,8,7 x ( 2 ) x^{(2)} x(2)轴对应的数: 3 , 4 , 6 , 7 , 1 , 2 3,4,6,7,1,2 3,4,6,7,1,2

1)开始:

  • 经过计算可知,x轴对应的这一串数字(这项特征)方差较大,选用x轴中的中位数5或7做切分点,这里我们选择7,切分整个区域。
    在这里插入图片描述

2)重复:

  • x ( 2 ) x^{(2)} x(2)为坐标轴,选择中位数,左边区域为4,右边区域两个任取一个,这里取较大数6。故左边区域切分点为 ( 5 , 4 ) (5,4) (5,4),右边区域切分点为 ( 9 , 6 ) (9,6) (9,6)
    在这里插入图片描述

  • 划分左边区域:此时 I = 1 I=1 I=1,重新从最开始选取的轴 x ( 1 ) x^{(1)} x(1)开始切。以 x ( 1 ) x^{(1)} x(1)为坐标轴,选择中位数,此时上下两个区域都只剩一个点了,全选上作为切分点;
    在这里插入图片描述

  • 划分右边区域:同上,以 x ( 1 ) x^{(1)} x(1)为左边轴,选取切分点(只有一个)。
    在这里插入图片描述

3)输出kd树:

在这里插入图片描述


5.2 搜索kd树


输入:已构造的kd树,目标点x。
输出:x的最近邻点。

1. 基本流程:

1)寻找“当前最近点”

  • 从根节点出发,递归访问kd树,找出包含x的最近邻点;
  • 以此叶节点为“当前最近点”。

2)回溯

  • 回溯过程中,若该节点比“当前最近点”距离目标点更近,更新“当前最近点”;
  • 当前最近点一定存在于该节点一个子节点对应的区域,检查子节点的父节点的另一子节点对应的区域是否有更近的点。

3)结束

  • 当退回到根节点时,搜索结束,最后的“当前最近点”即为x的最近邻点。

2. 实例:

现有kd树:在这里插入图片描述

在这里插入图片描述

1)输入目标点 x = ( 2.1 , 3.1 ) x=(2.1,3.1) x=(2.1,3.1),求最近邻点。

在这里插入图片描述

  • 查找“当前最近邻点”:

先找到根节点 x ( 7 , 2 ) x(7,2) x(7,2),因为目标节点 ( 2.1 , 3.1 ) (2.1,3.1) (2.1,3.1)的横坐标小于7,所以先从kd树的左子树(左边区域)开始搜索;因为目标节点的纵坐标小于4,所以从下边区域开始搜索;下边区域只有一个点 ( 2 , 3 ) (2,3) (2,3),目标点 ( 2.1 , 3.1 ) (2.1,3.1) (2.1,3.1)就在点 ( 2 , 3 ) (2,3) (2,3)的右边区域中,故 ( 2 , 3 ) (2,3) (2,3)就是当前的最近邻点。

  • 回溯:

以目标点为圆心,到当前最近邻点 ( 2 , 3 ) (2,3) (2,3)的距离为半径画圆(如果是多维空间,就是画一个超球面了)。先回溯到目标节点的父节点,也就是当前最近邻点 ( 2 , 3 ) (2,3) (2,3),发现圆和 ( 2 , 3 ) (2,3) (2,3)划分的左半部分平面(不含分界线)是有交集的,所以要先搜索左半部分平面;发现左半部分平面没有一个点,重新回溯到 ( 2 , 3 ) (2,3) (2,3);再回溯到当前最近邻点的父节点 ( 5 , 4 ) (5,4) (5,4),计算这个节点到目标点的距离,发现比圆的半径大,不更新数据;随后发现图中圆和 ( 5 , 4 ) (5,4) (5,4)划分的上半部分平面(不含分界线)是没有交集的,说明不可能从 ( 5 , 4 ) (5,4) (5,4)上半部分所对应的子区域中,找到更近的点;回溯到根节点,发现图中圆和根节点划分的右半部分区域也是没有交集的,所以最近邻点也不可能在根节点划分的右半部分,也不可能是根节点。

  • 结束:

回溯到了根节点,停止回溯,找到最近邻点 ( 2 , 3 ) (2,3) (2,3)

2)输入目标点 x = ( 2 , 4.5 ) x=(2,4.5) x=(2,4.5),求最近邻点。

在这里插入图片描述

  • 查找“当前最近邻点”:

和1中的步骤一样,先找到了当前最近邻点 ( 4 , 7 ) (4,7) (4,7)

  • 回溯:

以目标点为圆心,到当前最近邻点的距离为半径画圆。先回溯到目标节点的父节点 ( 4 , 7 ) (4,7) (4,7),发现圆和 ( 4 , 7 ) (4,7) (4,7)所划分的右半部分平面是有交集的,先搜索右半部分平面;发现没有一个点,重新回溯到 ( 4 , 7 ) (4,7) (4,7);再回溯到父节点 ( 5 , 4 ) (5,4) (5,4),计算该节点到目标点的距离,发现比原来的圆半径小,更新最近邻点,重新画圆;随后发现,重新画的圆和 ( 5 , 4 ) (5,4) (5,4)划分的下半部分区域有交集,搜索下半部分区域,发现只有一个点;计算父节点的下半部分子区域中的子节点到目标点的距离,发现该距离比原来的圆半径要小,更新最近邻点为 ( 2 , 3 ) (2,3) (2,3),并重新画圆;

在这里插入图片描述

直接回溯到根节点,计算根节点到目标点的距离,发现比图中圆的半径要大,不更新数据;随后发现图中圆和根节点划分的右半部分区域没有交集,所以最近邻点不可能在根节点划分的右半部分,也不可能是根节点。

  • 结束:

回溯到了根节点,停止回溯,找到最近邻点 ( 2 , 3 ) (2,3) (2,3)


5.3 kd树代码实现


适用于n维向量:

import math
import numpy as np


class Node:
    def __init__(self, data, lchild = None, rchild = None):
        self.data = data
        self.lchild = lchild
        self.rchild = rchild


class KdTree:
    def __int__(self):
        self.kdTree = None

    def create(self, dataSet, depth): # 构造kd树
        if len(dataSet) > 0: # 还有实例,就往下分
            m, n = np.shape(dataSet)    # 求出样本的行和列
            midIndex = m // 2           # 求出中位数的索引
            if depth == 0: # 深度等于0时说明是根节点,轴已经选好,不用再判断
                arr = list(dataSet.var(axis=0))  # 先转成list,不然无法使用index函数
                max_value = max(arr)  # 确定方差最大的那一列
                axis = arr.index(max_value)  # 确定初始划分坐标
            else:
                axis = depth % n        # 判断以哪个轴划分数据,由于索引从0开始,所以不用+1
            sortedDataSet = sorted(dataSet, key=lambda x: x[axis]) # 按照指定轴进行排序
            node = Node(sortedDataSet[midIndex]) # 在树中放入节点
            node.lchild = self.create(sortedDataSet[: midIndex], depth + 1) # 递归构造左子树
            node.rchild = self.create(sortedDataSet[midIndex + 1 :], depth + 1) # 递归构造右子树
            return node # 返回根节点
        else:
            return None

    def preOrder(self, node): # 前序遍历二叉树
        if node != None:
            print(node.data)
            self.preOrder(node.lchild)
            self.preOrder(node.rchild)

    def search(self, node, x): # 搜索kd树
        self.nearestPoint = None  # 保存当前最近点
        self.nearestValue = math.inf  # 保存当前最近距离(圆的半径)math.inf是无穷大
        def travel(node, depth = 0): # 递归搜索
            if node != None: # 递归终止条件
                if depth == 0:  # 深度等于0时说明是根节点,轴已经选好,不用再判断
                    arr = list(dataSet.var(axis=0))  # 先转成list,不然无法使用index函数
                    max_value = max(arr)  # 确定方差最大的那一列
                    axis = arr.index(max_value)  # 确定初始划分坐标
                else:
                    axis = depth % len(x)  # 判断以哪个轴划分数据,由于索引从0开始,所以不用+1
                if x[axis] < node.data[axis]: # 如果数据小于节点对应坐标数据,则往左节点搜索
                    travel(node.lchild, depth + 1)
                else:
                    travel(node.rchild, depth + 1)
            else:
                return

            # 递归完毕后,开始回溯
            distNodeAndX = self.dist(x, node.data) # 目标点和当前最近点的距离
            if self.nearestValue > distNodeAndX: # 更新最近邻点
                self.nearestPoint = node.data
                self.nearestValue = distNodeAndX

            if abs(x[axis] - node.data[axis]) < self.nearestValue: # 确定是否要去子节点的区域找最近邻点(圆的判断)
                if x[axis] < node.data[axis]:
                    travel(node.rchild, depth + 1)
                else:
                    travel(node.lchild, depth + 1)

        travel(node)
        return self.nearestPoint # 返回最近邻点

    def dist(self, x1, x2): # 欧式距离计算
        return ((x1 - x2) ** 2).sum() ** 0.5


if __name__ == '__main__':
    # 准备数据
    dataSet = np.array([[2, 3],
                        [5, 4],
                        [9, 6],
                        [4, 7],
                        [8, 1],
                        [7, 2]])
    x = [2, 4.5] # 目标点

    # 处理数据
    kdtree = KdTree() # 初始化kd树
    tree_root = kdtree.create(dataSet, 0) # 接收根节点(根节点深度为0)

    # 前序遍历
    kdtree.preOrder(tree_root) # 检查kd树是否正确建立

    # 搜索最近邻点
    print("最近邻点:", kdtree.search(tree_root, x))
  • 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
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95

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

闽ICP备14008679号