机器学习——无监督学习


在无监督学习中,学习算法只有输入数据,并需要
从这些数据中提取知识。
本章将研究两种类型的无监督学习: 数据集变换聚类

数据集的无监督变换(unsupervised transformation)是创建数据新的表示的算法,与数据的原始表示相比,新的表示可能更容易被人或其他机器学习算法所理解。无监督变换的一个常见应用是降维(dimensionality reduction),它接受包含许多征的数据的高维表示,并找到表示该数据的一种新方法,用较少的特征就可以概括其重要特性。降维的一个常见应用是为了可视化将数据降为二维。
无监督变换的另一个应用是找到“构成”数据的各个组成部分。这方面的一个例子就是对文本文档集合进行主题提取。这里的任务是找到每个文档中讨论的未知主题,并学习每个文档中出现了哪些主题。这可以用于追踪社交媒体上的话题讨论,比如选举、枪支管制或流行歌手等话题。

与之相反,聚类算法(clustering algorithm)将数据划分成不同的组,每组包含相似的物项。思考向社交媒体网站上传照片的例子。为了方便你整理照片,网站可能想要将同一个人的照片分在一组。但网站并不知道每张照片是谁,也不知道你的照片集中出现了多少个人。明智的做法是提取所有的人脸,并将看起来相似的人脸分在一组。但愿这些人脸对应同一个人,这样图片的分组也就完成了。

预处理和缩放

一些算法(如神经网络和 SVM)对数据缩放非常敏感。因此,通常的做法是对特征进行调节,使数据表示更适合于这些算法。通常来说,这是对数据的一种简单的按特征的缩放和移动。

sklearn.preprocessing

  1. StandardScaler(copy=True, with_mean=True, with_std=True):标准化,确保每个特征的平均值为 0、方差为 1,使所有特征都位于同一量级;
  2. RobustScaler(with_centering=True, with_scaling=True, quantile_range=(25.0, 75.0), copy=True): RobustScaler 的工作原理与StandardScaler 类似,确保每个特征的统计属性都位于同一范围。但 RobustScaler 使用的是中位数和四分位数 ,而不是平均值和方差。这样RobustScaler 会忽略与其他点有很大不同的数据点(比如测量误差)。这些与众不同的数据点也叫异常值(outlier),可能会给其他缩放方法造成麻烦;
  3. MinMaxScaler(feature_range=(0, 1), copy=True):规模化,使所有特征都刚好位于 0 到 1 之间;
  4. normalize(X, norm=’l2’, axis=1, copy=True, return_norm=False):正则化,它对每个数据点进行缩放,使得特征向量的欧式长度等于 1,这意味着每个数据点的缩放比例都不相同(乘以其长度的倒数)。如果只有数据的方向(或角度)是重要的,而特征向量的长度无关紧要,那么通常会使用这种归一化。

实例

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
cancer=load_breast_cancer()
X_train,X_test,y_train,y_test=train_test_split(cancer.data,cancer.target,random_state=1)
print(X_train.shape)
print(X_test.shape)

(426, 30)
(143, 30)

from sklearn.preprocessing import MinMaxScaler
scaler=MinMaxScaler()

然后,使用 fit 方法拟合缩放器(scaler),并将其应用于训练数据。对于 MinMaxScaler 来说, fit 方法计算训练集中每个特征的最大值和最小值。与第 2 章中的分类器和回归器(regressor)不同,在对缩放器调用 fit 时只提供了 X_train ,而不用 y_train :

scaler.fit(X_train)

MinMaxScaler(copy=True, feature_range=(0, 1))

为了应用刚刚学习的变换(即对训练数据进行实际缩放),我们使用缩放器的 transform方法对训练数据和测试数据进行相同的缩放

X_train_scaler=scaler.transform(X_train)
X_test_scaler=scaler.transform(X_test)
from sklearn.svm import SVC
svm=SVC(C=100)
svm.fit(X_train,y_train)
print(svm.score(X_test,y_test))
svm.fit(X_train_scaler,y_train)
print(svm.score(X_test_scaler,y_test))

0.6153846153846154
0.965034965034965

降维、特征提取、流形学习

利用无监督学习进行数据变换最常见的目的就是可视化、压缩数据,以及寻找信息量更大的数据表示以用于进一步的处理。

为了实现这些目的,最简单也最常用的一种算法就是主成分分析。我们也将学习另外两种算法:非负矩阵分解(NMF)t-SNE,前者通常用于特征提取,后者通常用于二维散点图的可视化

主成分分析

主成分分析(principal component analysis,PCA)是一种旋转数据集的方法,旋转后的特征在统计上不相关。在做完这种旋转之后,通常是根据新特征对解释数据的重要性来选择它的一个子集。
在这里插入图片描述
首先找到方差最大的方向,将其标记为“成分1”(Component 1)。这是数据中包含最多信息的方向(或向量),换句话说,沿着这个方向的特征之间最为相关。然后,算法找到与第一个方向正交(成直角)且包含最多信息的方向。

利用这一过程找到的方向被称为主成分(principal component),因为它们是数据方差的主要方向。一般来说,主成分的个数与原始特征相同。

第二张图(右上)显示的是同样的数据,但现在将其旋转,使得第一主成分与 x 轴平行且第二主成分与 y 轴平行。在旋转之前,从数据中减去平均值,使得变换后的数据以零为中心。在 PCA 找到的旋转表示中,两个坐标轴是不相关的,也就是说,对于这种数据表示,除了对角线,相关矩阵全部为零。

我们可以通过仅保留一部分主成分来使用 PCA 进行降维。在这个例子中,我们可以仅保留第一个主成分,正如图 3-3 中第三张图所示(左下)。这将数据从二维数据集降为一维数据集。但要注意,我们没有保留原始特征之一,而是找到了最有趣的方向(第一张图中从左上到右下)并保留这一方向,即第一主成分。

最后,我们可以反向旋转并将平均值重新加到数据中。这样会得到图 3-3 最后一张图中的数据。这些数据点位于原始特征空间中,但我们仅保留了第一主成分中包含的信息。这种变换有时用于去除数据中的噪声影响,或者将主成分中保留的那部分信息可视化。

1、可视化

实例
利用 PCA,我们可以获取到主要的相互作用,并得到稍为完整的图像。我们可以找到前两个主成分,并在这个新的二维空间中用散点图将数据可视化。
在应用 PCA 之前,我们利用 StandardScaler 缩放数据,使每个特征的方差均为 1.

 sklearn.decomposition.PCA(n_components=None, *, copy=True, whiten=False, svd_solver='auto', tol=0.0, iterated_power='auto', random_state=None)
from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import StandardScaler
cancer=load_breast_cancer()

scaler=StandardScaler()
scaler.fit(cancer.data)
X_scaler=scaler.transform(cancer.data)

学习并应用 PCA 变换与应用预处理变换一样简单。我们将 PCA 对象实例化,调用 fit 方法找到主成分,然后调用 transform 来旋转并降维。默认情况下, PCA 仅旋转(并移动)数据,但保留所有的主成分。为了降低数据的维度,我们需要在创建 PCA 对象时指定想要保留的主成分个数:

from sklearn.decomposition import PCA
pca=PCA(n_components=2)
pca.fit(X_scaler)
X_pca=pca.transform(X_scaler)
print(X_scaler.shape)
print(X_pca.shape)

(569, 30)
(569, 2)

现在我们可以对前两个主成分作图:

import mglearn
import matplotlib.pyplot as plt
plt.figure(figsize=(8,8))
mglearn.discrete_scatter(X_pca[:,0],X_pca[:,1],cancer.target)
plt.legend(cancer.target_names)
plt.xlabel('First principal component')
plt.ylabel('Second principal component')

在这里插入图片描述
重要的是要注意, PCA 是一种无监督方法,在寻找旋转方向时没有用到任何类别信息。它只是观察数据中的相关性。对于这里所示的散点图,我们绘制了第一主成分与第二主成分的关系,然后利用类别信息对数据点进行着色。你可以看到,在这个二维空间中两个类别被很好地分离。这让我们相信,即使是线性分类器(在这个空间中学习一条直线)也可以在区分这个两个类别时表现得相当不错。我们还可以看到,恶性点比良性点更加分散,这一点也可以在图 3-4 的直方图中看出来。

在拟合过程中,主成分被保存在 PCA 对象的 components_ 属性中:

import seaborn as sns
plt.figure(figsize=(20,2))
sns.heatmap(pca.components_,cmap='viridis')
plt.xticks(range(len(cancer.feature_names)),cancer.feature_names,rotation=60)
plt.yticks([0,1],["First principal component","Second principal component"],rotation=0)
plt.xlabel("Feature_names")
plt.ylabel("principal component")

在这里插入图片描述
在第一个主成分中,所有特征的符号相同(均为正,但前面我们提到过,箭头指向哪个方向无关紧要)。这意味着在所有特征之间存在普遍的相关性。如果一个测量值较大的话,其他的测量值可能也较大。第二个主成分的符号有正有负,而且两个主成分都包含所有 30 个特征。这种所有特征的混合使得解释图中的坐标轴变得十分困难。

缺点
通常不容易对图中的两个轴做出解释。主成分对应于原始数据中的方向,所以它们是原始特征的组合,但这些组合往往非常复杂。

2、特征提取

PCA 的另一个应用是特征提取。特征提取背后的思想是,可以找到一种数据表示,比给定的原始表示更适合于分析。

我们将给出用 PCA 对图像做特征提取的一个简单应用,即处理 Wild 数据集 Labeled Faces(标记人脸)中的人脸图像。这一数据集包含从互联网下载的名人脸部图像,它包含从 21世纪初开始的政治家、歌手、演员和运动员的人脸图像。我们使用这些图像的灰度版本,并将它们按比例缩小以加快处理速度。

from sklearn.datasets import fetch_lfw_people
people=fetch_lfw_people(min_faces_per_person=20,resize=0.7)

image_shape=people.images[0].shape

fix,axes=plt.subplot(2,5,figsize=(15,8))
for target,image,ax in zip(people.taget,people.images,axes.ravel()):
    ax.imshow(image)
    ax.set_title(people.target_names[target])

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
想要度量人脸的相似度,计算原始像素空间中的距离是一种相当糟糕的方法。用像素表示来比较两张图像时,我们比较的是每个像素的灰度值与另一张图像对应位置的像素灰度值。这种表示与人们对人脸图像的解释方式有很大不同,使用这种原始表示很难获取到面部特征。例如,如果使用像素距离,那么将人脸向右移动一个像素将会发生巨大的变化,得到一个完全不同的表示。我们希望,使用沿着主成分方向的距离可以提高精度。这里我们启用 PCA 的 白化(whitening) 选项,它将主成分缩放到相同的尺度。变换后的结果与使用 StandardScaler 相同。白化不仅对应于旋转数据,还对应于缩放数据使其形状是圆形而不是椭圆。
在这里插入图片描述
我们对训练数据拟合 PCA 对象,并提取前 100 个主成分。然后对训练数据和测试数据进行变换:
在这里插入图片描述

非负矩阵分解

非负矩阵分解(non-negative matrix factorization,NMF)是另一种无监督学习算法,其目的在于提取有用的特征。它的工作原理类似于 PCA,也可以用于降维。与 PCA 相同,我们试图将每个数据点写成一些分量的加权求和。但在 PCA 中,我们想要的是正交分量,并且能够解释尽可能多的数据方差;而在 NMF 中,我们希望分量和系数均为非负,也就是说,我们希望分量和系数都大于或等于 0。因此,这种方法只能应用于每个特征都是非负的数据,因为非负分量的非负求和不可能变为负值。

将数据分解成非负加权求和的这个过程,对由多个独立源相加(或叠加)创建而成的数据特别有用,比如多人说话的音轨或包含多种乐器的音乐。在这种情况下,NMF 可以识别出组成合成数据的原始分量。总的来说,与 PCA 相比,NMF 得到的分量更容易解释,因为负的分量和系数可能会导致难以解释的抵消效应(cancellation effect)。

sklearn.decomposition.NMF(n_components=None, *, init=None, solver='cd', beta_loss='frobenius', tol=0.0001, max_iter=200, random_state=None, alpha=0.0, l1_ratio=0.0, verbose=0, shuffle=False)
from mglearn.datasets import make_signals
S=make_signals()
plt.figure(figsize=(10,1))
plt.plot(S,'-')
plt.xlabel('Time')
plt.ylabel('Signal')

在这里插入图片描述

import numpy as np
A=np.random.RandomState(0).uniform(size=(100,3))
X=np.dot(S,A.T)
print(X.shape)
print(S.shape)

(2000, 100)
(2000, 3)

from sklearn.decomposition import NMF
nmf=NMF(n_components=3,random_state=0)
S_=nmf.fit_transform(X)

pca=PCA(n_components=3)
H=pca.fit_transform(X)

models=[X,S,S_,H]
names=['observation','sources','nmf','pca']
fig,axes=plt.subplots(4,figsize=(10,6),gridspec_kw={'hspace':1.5})
for image,ax,name in zip(models,axes,names):
    ax.plot(image[:,:3],'-')
    ax.set_title(name)

在这里插入图片描述

用t-SNE进行流行学习

有一类用于可视化的算法叫作流形学习算法(manifold learning
algorithm),它允许进行更复杂的映射,通常也可以给出更好的可视化。其中特别有用的一个就是 t-SNE 算法。

流形学习算法主要用于可视化,因此很少用来生成两个以上的新特征。其中一些算法(包括 t-SNE)计算训练数据的一种新表示,但不允许变换新数据。这意味着这些算法不能用于测试集:更确切地说,它们只能变换用于训练的数据。

流形学习对探索性数据分析是很有用的,但如果最终目标是监督学习的话,则很少使用。t-SNE 背后的思想是找到数据的一个二维表示,尽可能地保持数据点之间的距离。t-SNE 首先给出每个数据点的随机二维表示,然后尝试让在原始特征空间中距离较近的点更加靠近,原始特征空间中相距较远的点更加远离。t-SNE 重点关注距离较近的点,而不是保持距离较远的点之间的距离。换句话说,它试图保存那些表示哪些点比较靠近的信息。

https://zhuanlan.zhihu.com/p/28967965

from sklearn.datasets import load_digits
import matplotlib.pyplot as plt 
digits=load_digits()

fig,axes=plt.subplots(2,5,figsize=(10,5),subplot_kw={'xticks':(),'yticks':()})
for ax,image in zip(axes.ravel(),digits.images,):
    ax.imshow(image)

在这里插入图片描述

from sklearn.decomposition import PCA 
pca=PCA(n_components=2)
digits_pca=pca.fit_transform(digits.data)
digits_pca[:5]
len(digits.data)

聚类算法

聚类(clustering)是将数据集划分成组的任务,这些组叫作簇(cluster)。其目标是划分数据,使得一个簇内的数据点非常相似且不同簇内的数据点非常不同。与分类算法类似,聚类算法为每个数据点分配(或预测)一个数字,表示这个点属于哪个簇。

1、K-means

算法步骤:
(1) 首先我们选择一些类/组,并随机初始化它们各自的中心点。中心点是与每个数据点向量长度相同的位置。这需要我们提前预知类的数量(即中心点的数量)。
(2) 计算每个数据点到中心点的距离,数据点距离哪个中心点最近就划分到哪一类中。
(3) 计算每一类中中心点作为新的中心点。
(4) 重复以上步骤,直到每一类中心在每次迭代后变化不大为止。也可以多次随机初始化中心点,然后选择运行结果最好的一个。

缺点:

  • 事先需要知道分成几类

  • 对异常值敏感,可以用K-medains解决

  • 对初值敏感,可以用二分K-means算法、K-means++算法解决

  • 对簇形状要求较高 可以使用DBSCA算法解决

     sklearn.cluster.KMeans(n_clusters=8, *, init='k-means++', n_init=10, max_iter=300, tol=0.0001, precompute_distances='deprecated', verbose=0, random_state=None, copy_x=True, n_jobs='deprecated', algorithm='auto')
    

KMeans类的主要参数有:

  • n_clusters: 即k值,一般需要多试一些值以获得较好的聚类效果。k值好坏的评估标准在下面会讲。

  • max_iter:
    最大的迭代次数,一般如果是凸数据集的话可以不管这个值,如果数据集不是凸的,可能很难收敛,此时可以指定最大的迭代次数让算法可以及时退出循环。

  • n_init:用不同的初始化质心运行算法的次数。由于K-Means是结果受初始值影响的局部最优的迭代算法,因此需要多跑几次以选择一个较好的聚类效果,默认是10,一般不需要改。如果你的k值较大,则可以适当增大这个值。

  • init:即初始值选择的方式,可以为完全随机选择’random’,优化过的’k-means++’或者自己指定初始化的k个质心。一般建议使用默认的’k-means++’。

  • algorithm:有“auto”, “full” or “elkan”三种选择。”full”就是我们传统的K-Means算法, “elkan”是elkan K-Means算法。默认的”auto”则会根据数据值是否是稀疏的,来决定如何选择”full”和“elkan”。一般数据是稠密的,那么就是 “elkan”,否则就是”full”。一般来说建议直接用默认的”auto”

  • random_state:表示产生随机数的方法。默认情况下的缺省值为None,此时的随机数产生器是np.random所使用的RandomState实例。

2、均值漂移聚类

均值漂移聚类是基于滑动窗口的算法,来找到数据点的密集区域。这是一个基于质心的算法,通过将中心点的候选点更新为滑动窗口内点的均值来完成,来定位每个组/类的中心点。然后对这些候选窗口进行相似窗口进行去除,最终形成中心点集及相应的分组。
具体步骤:

  1. 确定滑动窗口半径r,以随机选取的中心点C半径为r的圆形滑动窗口开始滑动。均值漂移类似一种爬山算法,在每一次迭代中向密度更高的区域移动,直到收敛。
  2. 每一次滑动到新的区域,计算滑动窗口内的均值来作为中心点,滑动窗口内的点的数量为窗口内的密度。在每一次移动中,窗口会想密度更高的区域移动。
  3. 移动窗口,计算窗口内的中心点以及窗口内的密度,知道没有方向在窗口内可以容纳更多的点,即一直移动到圆内密度不再增加为止。
  4. 步骤一到三会产生很多个滑动窗口,当多个滑动窗口重叠时,保留包含最多点的窗口,然后根据数据点所在的滑动窗口进行聚类。

3、DBSCAN

DBSCAN(density-based spatial clustering of applications with noise,即“具有噪声的基于密度的空间聚类应用”)。

DBSCAN 的主要优点是它不需要用户先验地设置簇的个数,可以划分具有复杂形状的簇,还可以找出不属于任何簇的点,DBSCAN 比凝聚聚类和 k 均值稍慢,但仍可以扩展到相对较大的数据集。

DBSCAN 的原理是识别特征空间的“拥挤”区域中的点,在这些区域中许多数据点靠近在一起。这些区域被称为特征空间中的密集(dense)区域。DBSCAN 背后的思想是,簇形成数据的密集区域,并由相对较空的区域分隔开。

在密集区域内的点被称为核心样本(core sample,或核心点),它们的定义如下。DBSCAN有两个参数: min_samples 和 eps 。如果在距一个给定数据点 eps 的距离内至少有 min_samples 个数据点,那么这个数据点就是核心样本。DBSCAN 将彼此距离小于 eps 的核心样本放到同一个簇中。

算法首先任意选取一个点,然后找到到这个点的距离小于等于 eps 的所有的点。如果距起始点的距离在 eps 之内的数据点个数小于 min_samples ,那么这个点被标记为噪声(noise),也就是说它不属于任何簇。如果距离在 eps 之内的数据点个数大于 min_samples ,则这个点被标记为核心样本,并被分配一个新的簇标签。然后访问该点的所有邻居(在距离 eps 以内)。如果它们还没有被分配一个簇,那么就将刚刚创建的新的簇标签分配给它们。如果它们是核心样本,那么就依次访问其邻居,以此类推。簇逐渐增大,直到在簇的 eps 距离内没有更多的核心样本为止。然后选取另一个尚未被访问过的点,并重复相同的过程。

DBSCAN的主要优点有:

1) 可以对任意形状的稠密数据集进行聚类,相对的,K-Means之类的聚类算法一般只适用于凸数据集。

2) 可以在聚类的同时发现异常点,对数据集中的异常点不敏感。

3) 聚类结果没有偏倚,相对的,K-Means之类的聚类算法初始值对聚类结果有很大影响。

DBSCAN的主要缺点有:

1)如果样本集的密度不均匀、聚类间距差相差很大时,聚类质量较差,这时用DBSCAN聚类一般不适合。

2) 如果样本集较大时,聚类收敛时间较长,此时可以对搜索最近邻时建立的KD树或者球树进行规模限制来改进。

3) 调参相对于传统的K-Means之类的聚类算法稍复杂,主要需要对距离阈值ϵ,邻域样本数阈值MinPts联合调参,不同的参数组合对最后的聚类效果有较大影响。

 sklearn.cluster.DBSCAN(eps=0.5, *, min_samples=5, metric='euclidean', metric_params=None, algorithm='auto', leaf_size=30, p=None, n_jobs=None)
from sklearn.datasets import make_moons
from sklearn.cluster import KMeans
from sklearn.cluster import DBSCAN
X,y=make_moons(n_samples=200,noise=0.05,random_state=0)
kmeans=KMeans(n_clusters=2)
dbscan=DBSCAN()
cluster_kmeans=kmeans.fit_predict(X)
cluster_dbscan=dbscan.fit_predict(X)
import matplotlib.pyplot as plt
figure,axes=plt.subplots(1,2,figsize=(8,4))
axes[0].scatter(X[:,0],X[:,1],c=cluster_kmeans)
axes[1].scatter(X[:,0],X[:,1],c=cluster_dbscan)
axes[0].set_title('kmeans')
axes[1].set_title('dbscan')

在这里插入图片描述
K-means并未实现很好的分离,他对簇的形状要求很高,适用于椭球形的簇,而DBSCAN完全没有实现分离,将所有点归为一类。

X[:5]

array([[ 0.81680544, 0.5216447 ],
[ 1.61859642, -0.37982927],
[-0.02126953, 0.27372826],
[-1.02181041, -0.07543984],
[ 1.76654633, -0.17069874]])
原因是没有X中样本点数据较小,eps=0.5过大,将所有点分成一簇了,解决办法:使用 StandardScaler 或 MinMaxScaler 对数据进行缩放之后,有时会更容易找到 eps 的较好取值,因为使用这些缩放技术将确保所有特征具有相似的范围。

from sklearn.preprocessing import StandardScaler
scaler=StandardScaler()
scaler.fit(X)
X_scaler=scaler.transform(X)
cluster_dbscan=dbscan.fit_predict(X_scaler)

fig=plt.figure(figsize=(4,4))
plt.scatter(X[:,0],X[:,1],c=cluster_dbscan)
plt.title('dbscan'

在这里插入图片描述
或者减小eps的值,令eps=0.2,则
在这里插入图片描述
增大 eps (在图中从左到右),更多的点会被包含在一个簇中。这让簇变大,但可能也会导致多个簇合并成一个。增大 min_samples (在图中从上到下),核心点会变得更少,更多的点被标记为噪声。

参数 eps 在某种程度上更加重要,因为它决定了点与点之间“接近”的含义。将 eps 设置得非常小,意味着没有点是核心样本,可能会导致所有点都被标记为噪声。将 eps 设置得非常大,可能会导致所有点形成单个簇。

设置 min_samples 主要是为了判断稀疏区域内的点被标记为异常值还是形成自己的簇。如果增大 min_samples ,任何一个包含少于 min_samples 个样本的簇现在将被标记为噪声。因此, min_samples 决定簇的最小尺寸。

4、层次聚类

基于数据点的相似性,通过最近的数据组合一层一层的对数据进行聚类,从而形成树形聚类结构

  • 自底向上:凝聚聚类(Agglomerative)
  • 自上而下:分裂算法(Divisive)

凝聚聚类

算法首先声明每个点是自己的簇,然后合并两个最相似的簇,直到满足某种停止准则
为止。

簇间距离度量:首先计算Ci与Cj簇间距离矩阵Dij,计算方法有欧氏距离’edclidean’、曼哈顿距离’mantatan’、夹角余弦’cosine’等(计算样本点间距离),计算簇间距离的方法有:

  • 最小距离:Dij中的最小值 ‘ward’

  • 最大距离:Dij中的最大值 ‘complete’

  • 平均距离: 1 n i n j s u m ( s u m ( D i , j ) ) \frac{1}{n_{i}n_{j}}sum(sum(D_{i,j})) ninj1sum(sum(Di,j)) ‘avgerage’

     sklearn.cluster.AgglomerativeClustering(n_clusters=2, *, affinity='euclidean', memory=None, connectivity=None, compute_full_tree='auto', linkage='ward', distance_threshold=None)
    
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值