特征选择

特征选择

对一个学习任务来说,给定属性集,其中有些属性可能很关键、很有用,另一些属性则可能没什么用,我们将属性称为“特征”(feature),对当前学习任务有用的属性称为“相关特征”(relevant feature)、没什么用的属性称为“无关特征”(irrelavant feature)。从给定的特征集合中选择出相关特征子集的过程,称为“特征选择”(feature selection)。

【注意】:

  • 特征的相关与无关是相对当前学习任务而言,若更换学习任务则有可能使得原本相关特征变为无关特征。例如姓名特征对于预测年龄几乎没有什么作用,但对于预测性别则有一定的参考价值。
  • 特征选择过程必须确保不丢失重要特征,否则后续学习过程会因为重要信息的缺失而无法获得好的性能。

特征选择是一个重要的“数据预处理”过程,在现实机器学习任务中,获得数据之后通常先进行特征选择,此后再训练学习器,那么为什么要进行特征选择呢?

  • 首先,我们在现实任务中经常会遇到维数灾难问题,这是由于特征过多而造成的,若能从中选择出重要的特征,使得后续学习过程仅需在一部分特征上构建模型,则维数灾难问题会大为减轻。从这个意义上来说,特征选择与降维有相似的动机;事实上,特征选择与降维是处理高维数据的两大主流技术。
  • 去除不相关特征往往会降低学习任务的难度。这就像侦探破案一样,将纷繁复杂的因素抽丝剥茧,只留下关键因素,则真相往往更易看清。

【冗余特征(redundant feature)】:所包含的信息能从其他特征中推演出来。例如,考虑立方体对象,若已有特征“底面长”、“底面宽”,则“底面积”是冗余特征,去除它们会减轻学习过程的负担。但有时冗余特征会降低学习任务的难度,例如若学习目标是估算立方体的体积,则“底面积”这个冗余特征的存在将使得体积的估算更容易;更确切地说,若某个冗余特征恰好对应了完成学习任务所需的“中间概念”,则该冗余特征是有益的。

了解了上述基本概念后,我们该如何从初始的特征集合中选取一个包含所有重要信息的特征子集?

  1. 任何领域知识作为先验假设来选择特征;
  2. 遍历所有可能的子集:这种做法在计算上不可行,因为会遭遇组合爆炸,特征个数稍多就无法进行;
  3. 挑选“候选子集”:首先产生一个“候选子集”,评价出它的好坏,基于评价结果产生下一个候选子集,再对其进行评价。该过程持续进行下去,直至无法找到更好的候选子集为止。

第三个方案涉及两个关键环节:

  • 如何根据评价结果获取下一个候选特征子集?
  • 如何评价候选特征子集的好坏?
子集搜索

第一个环节是“子集搜索”(subset search)问题。

【前向搜索(forward)】:给定特征集合 { a 1 , a 2 , ⋯   , a d } \{a_1, a_2,\cdots, a_d\} {a1,a2,,ad},我们可将每个特征看作一个候选子集,对这 d 个候选单特征子集进行评价,假定 { a 2 } \{a_2\} {a2} 最优,于是将 a 2 {a_2} a2 作为第一轮的选定集;然后,在上一轮的选定集中加入一个特征,构成包含两个特征的候选子集,假定在这 d - 1 个候选两特征子集中 a 2 , a 4 {a_2, a_4} a2,a4 最优,且优于 a 2 {a_2} a2,于是将 a 2 , a 4 {a_2, a_4} a2,a4 作为本轮的选定集。假定在第 k + 1 轮时,最优的候选 (k + 1) 特征子集步入上一轮的选定集,则停止生成候选子集,并将上一轮选定的 k 特征集合作为特征选择结果。

【后向搜索(backward)】:从完整的特征集合开始,每次尝试去掉一个无关特征。

【双向搜索(bidirectional)】:将前向和后向搜索结合起来,每一轮逐渐增加选定相关特征(这些特征在后续轮中将确定不会被去除)、同时减少无关特征。

【问题】:上述策略都是贪心的,因为它们仅考虑使本轮选定集最优,例如在第三轮假定选择 a5 优于 a6,于是选定集为 { a 2 , a 4 , a 5 } \{a_2, a_4, a_5\} {a2,a4,a5},然而在第四轮却可能是 { a 2 , a 4 , a 6 , a 8 } \{a_2, a_4, a_6, a_8\} {a2,a4,a6,a8} 比所有的 { a 2 , a 4 , a 5 , a i } \{a_2, a_4, a_5, a_i\} {a2,a4,a5,ai} 都更优。遗憾的是,若不进行穷举搜索,则这样的问题无法避免。

无论是前向、后向还是双向搜索都是贪心策略,这让我想起吴恩达老师的深度学习课程中所讲的集束搜索策略。集束搜索策略是指什么呢?我们在特征子集搜索的每一步过程中多选择几个特征,例如按照最优的结果降序排列,我们选择最前面的 3 个特征。假设选择的是 { a 2 } , { a 4 } , { a 5 } \{a_2\}, \{a_4\}, \{a_5\} {a2},{a4},{a5},然后分别以这三个子集作为起点出发,继续寻找最优的 3 个特征子集,那么此时可获得 9 个特征子集(有可能会重复,例如 { a 2 , a 4 } , { a 4 , a 2 } \{a_2, a_4\}, \{a_4, a_2\} {a2,a4},{a4,a2}),再从这 9 个特征子集中选择最优的 3 个特征子集进入到下一轮搜索过程。也就是说,最终我们可以得到 3 个特征子集,而 3 就是集束搜索的集束宽,这个参数我们可以自行设置,参数越大计算开销也越大,但获取最优解的概率也越高。

关于集束搜索的详细内容可以参考吴恩达老师深度学习课程的笔记 传送门-3.3 集束搜索(Beam Search)

子集评价

第二个环节是“子集评价”(subset evaluation)问题。

我们可以用决策树算法中的信息增益准则作为评价准则。其实,特征子集是对数据集的一个划分。因此除了信息熵之外,其他能判断划分差异的机制,例如不合度量、相关系数等,稍加调整即可用于特征子集评价。

此外,也可以直接用学习器训练当前特征子集的最终评分作为该特征子集的评价标准。后续要说明的包裹式以及嵌入式特征选择方法就是以学习器的性能作为特征子集的评价标准。


将特征子集搜索机制与子集评价机制相结合,即可得到特征选择方法。例如将前向搜索与信息熵相结合,这显然与决策树算法非常相似。其他的特征选择方法未必像决策树特征选择这么明显,但它们在本质上都是显式或隐式地结合了某种子集搜索机制和子集评价机制。

【常见的特征选择方法】:

  • 过滤式(filter)
  • 包裹式(wrapper)
  • 嵌入式(embedding)

过滤式选择

过滤式方法先按照某种规则对数据集进行特征选择,然后再训练学习器,特征选择过程与后续学习器无关,这相当于先用特征选择过程对初始特征进行“过滤”,再用过滤后的特征来训练模型。

【某种规则】:按照发散性或相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,从而选择满足条件的特征。

  • 特征的发散性:如果一个特征不发散,例如方差接近于 0,也就是说样本在该特征上基本没有差异,那么这个特征对于样本的区分并没有什么用。
  • 特征与目标的相关性:特征与目标的相关性越高说明特征的变动对目标的影响较大,因此我们应当优先选择与目标相关性高的特征。

方差选择法

计算各个特征的方差,然后根据阈值选择方差大于阈值的特征,或者指定待选择的特征数 k,然后选择 k 个最大方差的特征。

方差选择的依据是什么?举个极端的例子,在多分类问题中,如果某特征只有一个取值,那么该特征对分类结果没有任何意义,因为不管取什么值都为 1,单凭该特征是无法区分样本的分类。

需要注意的是,方差选择法只有在特征是离散型时才适用,如果是连续型则需要离散化后才能使用。此外,该方法在实际问题中效果并非很好,参考如下数据集:

A B Y
1 1 0
2 1 0
3 1 0
1 2 1
2 2 1
3 2 1

特征 A 的方差 4 要大于特征 B 的方差 1.5,但特征 A 对最终分类结果 Y 的区分度明显没有特征 B 好。单看这 6 条数据,特征 A 几乎没有办法区分 Y 是 0 还是 1。因此我们需要明确一个概念,特征值的方差越大不一定对分类结果有更好的区分。关键原因是特征值的方差仅仅只考虑自身的取值,而没有结合最终的分类结果。

【代码实现】:sklearn。

>>> from sklearn.preprocessing import VarianceThreshold
>>> X = [[0, 2, 0, 3], [0, 1, 4, 3], [0, 1, 1, 3]]
>>> selector = VarianceThreshold()
>>> selector.fit_transform(X)
array([[2, 0],
       [1, 4],
       [1, 1]])

若不传给 VarianceThreshold() 阈值,则默认移除方差为 0 的特征。同样,我们也可以给 VarianceThreshold() 传递阈值:

>>> X = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 1]]
>>> sel = VarianceThreshold(threshold=(0.16))
>>> sel.fit_transform(X)
array([[0, 1],
       [1, 0],
       [0, 0],
       [1, 1],
       [1, 0],
       [1, 1]])

关于 VarianceThreshold() 的实现可参考官方 API 传送门

不借助 sklearn 自行实现方差选择法,那么该如何编写代码呢?思路非常简单,先计算每一个特征的方差值,然后依次比对方差值与阈值,选择方差值大于阈值的特征。

>>> def variance_select(data, threshold=0):
...     variance_list = np.var(data, axis=0)
...     result, ind = [], 0
...     for variance in variance_list:
...         if variance > threshold:
...             result.append(ind)
...             ind += 1
...     return np.array(data)[:, result]
...    
>>> variance_select(X, 0.16)
array([[0, 1],
       [1, 0],
       [0, 0],
       [1, 1],
       [1, 0],
       [1, 1]])

相关系数法

计算各个特征对目标值的相关系数及相关系数的 P 值。

在机器学习中我们一般采用皮尔逊相关系数来测量两个序列的线性关系,也就是说皮尔逊相关系数只能检测出线性关系,那么对于分类问题的适用性就远低于回归问题,因此相关系数法常用于回归问题。

为什么皮尔逊相关系数只能测量线性关系呢?具体解释可参考这篇博文 传送门

【代码实现】:我们以 sklearn.datasets 中的波士顿房价数据集为例。

import numpy as np
import pandas as pd
from sklearn.datasets import load_boston


dataset_boston = load_boston()
dataset = dataset_boston.data
labels = dataset_boston.target

# 我们把 label 也加到 dataset 中
dataset_all = np.column_stack((dataset, labels))
columns = [name for name in dataset_boston.feature_names]
columns.append('label')

df_dataset = pd.DataFrame(data=dataset, columns=columns)
df_dataset.corr(method='pearson')

波士顿房价皮尔逊相关系数表.png

通过 df_dataset.corr(method=‘pearson’) 这句指令,我们可以看到各特征两两之间的皮尔逊相关系数。当然我们更关注的是特征与最终要预测的对象(房价)的相关系数。

除了使用 pandas 的 corr(method=‘pearson’) 方法之外,我们还可以使用 scipy.stats.pearsonr() 方法。

>>> from scipy.stats import pearsonr
>>> pearsonr(dataset[:, 0], labels)
(-0.38830460858681154, 1.1739870821945733e-19)
>>> pearsonr(dataset[:, 1], labels)
(0.3604453424505432, 5.713584153081686e-17)

上述代码分别计算 CRIM、ZN 和 label 之间的相关系数,可以看到输出结果的第一项与 corr(method=‘pearson’) 计算的结果相同,不同的是 pearsonr() 方法还多输出了一项 1.1739870821945733e-19 和 5.713584153081686e-17。

scipy.stats.pearsonr() 对给定两个数据序列会返回相关系数值和 p 值所组成的元组。也就是说 1.1739870821945733e-19 和 5.713584153081686e-17 就是这个 p 值,那么 p 值有什么用呢?p 值是该数据序列产生于一个不相关系统的概率,p 值越高,我们越不能信任这个相关系数。

不使用已有的方法自行实现相关系数的计算,依据相关系数的计算公式:
ρ = Cov ⁡ ( X , Y ) σ X σ Y C o v ( X , Y ) = ∑ ( x − m x ) ( y − m y ) \rho=\frac{\operatorname{Cov}(X, Y)}{\sigma_{X} \sigma_{Y}} \quad Cov(X, Y) = \sum\left(x-m_{x}\right)\left(y-m_{y}\right) ρ=σXσYCov(X,Y)Cov(X,Y)=(xmx)(ymy)
其中, m x m_x mx m y m_y my 分别是向量 x 和 y 的均值。

【代码实现】:

def corr(vector_A, vector_B):
    if vector_A.shape[0] != vector_B.shape[0]:
        raise Exception('The Vector must be the same size')
        
    vector_A_mean, vector_B_mean = np.mean(vector_A), np.mean(vector_B)
    vector_A_diff, vector_B_diff = vector_A - vector_A_mean, vector_B - vector_B_mean
    molecule = np.sum(vector_A_diff * vector_B_diff)
    denominator = np.sqrt(np.sum(vector_A_diff**2) * np.sum(vector_B_diff**2))
    return molecule / denominator

相关系数的取值在 -1 到 1 之间,-1 代表负相关、1 代表正相关、0 代表不相关。

>>> corr(np.array([1, 2, 3, 4, 5]), np.array([1, 4, 7, 10, 13]))
1.0
>>> corr(np.array([1, 2, 3, 4, 5]), np.array([13, 10, 7, 4, 1]))
-1.0
>>> corr(np.array([1, 2, 3, 4, 5]), np.array([7, 10, 4, 13, 1]))
-0.3

通过上述示例可以发现,特征与预测值的相关系数值越接近 -1 或 1 时,特征的变化趋势与预测值的变化趋势具有高度的一致性(反向或同向),也就是说这些特征对预测值产生的影响也越大,因此,我们优先选择相关系数绝对值大的特征。

卡方检验法

检验定性自变量对定性因变量的相关性。关于卡方检验的介绍可参考这篇文章 卡方检验原理及应用。需要注意的是,卡方检验适用于分类问题。

【代码实现】:因为卡方检验适用于分类问题,因此以 sklearn.datasets 中的鸢尾花数据集为例。

from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2


dataset_iris = load_iris()
dataset = dataset_iris.data
labels = dataset_iris.target

model_sk = SelectKBest(score_func=chi2, k=3)
model_sk.fit(dataset, labels)
print(model_sk.scores_)
# 输出:array([ 10.81782088,   3.7107283 , 116.31261309,  67.0483602 ])
print(model_sk.pvalues_)
# 输出:array([4.47651499e-03, 1.56395980e-01, 5.53397228e-26, 2.75824965e-15])

卡方值越大,表明特征与预测结果的相关性也越大,同时 p 值也相应较小,因此我们优先选择卡方值大的特征。

互信息法

评价定性自变量对定性因变量的相关性。

Relief

Relief(Relevant Features)是一种著名的过滤式特征选择方法,该方法设计了一个“相关统计量”来度量特征的重要性。该统计量是一个向量,其每个分量分别对应于一个初始特征,而特征子集的重要性则是由子集中每个特征所对应的相关统计量分量之和来决定。

【选择方式】:

  • 指定一个阈值 r,然后选择比 r 大的相关统计量分量所对应的特征即可;
  • 指定要选取的特征个数 k,然后选择相关统计量分量最大的 k 个特征。

【关键】:如何确定相关统计量。

给定训练集 { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯   , ( x n , y n ) } \{(x_1, y_1), (x_2, y_2), \cdots, (x_n, y_n)\} {(x1,y1),(x2,y2),,(xn,yn)},对每个实例 x i x_i xi,Relief 先在 x i x_i xi 的同类样本中寻找其最近邻 x i , n h x_{i,nh} xi,nh,称为“猜中近邻”(near-hit),再从 x i x_i xi 的异类样本中寻找其最近邻 x i , n m x_{i,nm} xi,nm 称为“猜错近邻”(near-miss),然后,相关统计量对应于特征 j 的分量为
δ j = ∑ i = 1 n − d i f f ( x i j , x i , n h j ) 2 + d i f f ( x i j , x i , n m j ) 2 \delta^j = \sum_{i=1}^n -diff(x_i^j, x_{i,nh}^j)^2 + diff(x_i^j, x_{i, nm}^j)^2 δj=i=1ndiff(xij,xi,nhj)2+diff(xij,xi,nmj)2
其中 x a j x_a^j xaj 表示样本 x a x_a xa 在特征 j 上的取值, d i f f ( x a j , x b j ) diff(x_a^j, x_b^j) diff(xaj,xbj) 取决于特征 j 的类型:

  • 若特征 j 为离散型,则 x a j = x b j x_a^j = x_b^j xaj=xbj 时, d i f f ( x a j , x b j ) = 0 diff(x_a^j, x_b^j) = 0 diff(xaj,xbj)=0,否则为 1;
  • 若特征 j 为连续型,则 d i f f ( x a j , x b j ) = ∣ x a j − x b j ∣ diff(x_a^j, x_b^j) = |x_a^j - x_b^j| diff(xaj,xbj)=xajxbj,注意 x a j , x b j x_a^j, x_b^j xaj,xbj 已规范化到 [0, 1] 区间。

从上式中可以看出,若 ` x i x_i xi 与其猜中近邻 x i , n h x_{i,nh} xi,nh 在特征 j 上的距离小于 x i x_i xi 与其猜错近邻 x i , n m x_{i, nm} xi,nm 的距离,则说明特征 j 对区分同类与异类样本是有益的,于是增大特征 j 所对应的统计量分量;反之,若则说明特征 j 起负面作用,于是减小特征 j 所对应的统计量分量。

最后,对基于不同样本得到的估计结果进行平均,就得到各特征的相关统计量分量,分量值越大,则对应特征的分类能力就越强。

实际上 Relief 只需在数据集的采样上而不必在整个数据集上估计相关统计量。Relief 的时间开销随采样次数以及原始特征数呈线性增长,因此是一个运行效率很高的过滤式特征选择算法。

Relief 是为二分类问题设计的,其扩展变体 Relief-F 能处理多分类问题。

【Relief-F】:假定数据集 D 中的样本来自 |Y| 个类别。对实例 x i x_i xi,若它属于第 k 类,则 Relief-F 先在第 k 类的样本中寻找 x i x_i xi 的最近邻实例 x i , n h x_{i, nh} xi,nh 并将其作为猜中近邻,然后在第 k 类之外的每个类中找到一个 x i x_i xi 的最近邻实例走位猜错近邻,记为 x i , l , n m ( l = 1 , 2 , ⋯   , ∣ Y ∣ ; l ≠ k ) x_{i,l,nm}(l = 1, 2, \cdots, |Y|; l \neq k) xi,l,nm(l=1,2,,Y;l̸=k)。于是,相关统计量对应于特征 j 的分量为
δ j = ∑ i = 1 n − d i f f ( x i j , x i , n h j ) 2 + ∑ l ≠ k ( p l × d i f f ( x i j , x i , l , n m j ) 2 ) \delta^j = \sum_{i=1}^n -diff(x_i^j, x_{i,nh}^j)^2 + \sum_{l \neq k}(p_l \times diff(x_i^j, x_{i,l,nm}^j)^2) δj=i=1ndiff(xij,xi,nhj)2+l̸=k(pl×diff(xij,xi,l,nmj)2)
其中, p l p_l pl 为第 l 类样本在数据集 D 中所占的比例。

包裹式选择

包裹式特征选择与过滤式特征选择不考虑后续学习器不同,直接把最终将要使用的学习器的性能作为特征子集的评价准则。换言之,包裹式特征选择的目的就是为给定学习器选择最有利于其性能、“量身定做”的特征子集。

【与过滤式选择的区别】:

  • 包裹式特征选择方法直接针对给定学习器进行优化,因此,从最终学习器性能来看,包裹式特征选择比过滤式特征选择更好;
  • 但另一方面,由于在特征选择过程中需多次训练学习器,因此包裹式特征选择的计算开销通常比过滤式特征选择大得多。

LVW(Las Vegas Wrapper)

LVW 是一个典型的包裹式特征选择方法,它在拉斯维加斯(Las Vegas method)框架下使用随机策略来进行子集搜索,并以最终分类器的误差为特征子集评价准则。

【算法】:

  • 输入:数据集 D;特征集 A;学习算法 Σ \varSigma Σ;停止条件控制参数 T。
  • 输出:特征子集 A*。
  • 过程:
  1. 初始化误差 E 为正无穷,d = |A|,A* = A,t = 0;
  2. 进入循环,循环停止条件为 while t < T;
  3. 随机产生特征子集 A’,设置 d’ = |A’|;
  4. 选择特征子集对应部分的数据集 D A ′ D^{A&#x27;} DA,使用交叉验证法来估计学习器 Σ \varSigma Σ 的误差。误差是特征子集 A’ 上的误差,若它比当前特征子集 A 上的误差更小,或误差相当但 A’ 中包含的特征数更少,则执行(a),否则执行(b)。
    • (a):t = 0,E = E’,d = d’,A* = A’;
    • (b):t = t + 1
  5. 输出特征子集 A*。

【注意】:由于 LVW 算法中特征子集搜索采用了随机策略,而每次特征子集评价都需要训练学习器,计算开销很大,因此算法设置了停止条件控制参数 T。然而,整个 LVW 算法是基于拉斯维加斯方法框架,若初始特征数很多(即 |A| 很大)、T 设置较大,则算法可能运行很长时间都达不到停止条件。换言之,若有运行时间限制,则有可能给不出解。

另外还有一个经典的算法——蒙特卡罗方法。这两个以著名赌城名字命名的随机化方法的主要区别是:若有时间限制,则拉斯维加斯方法或者给出满足要求的解,或者不给出解;而蒙特卡罗方法一定会给出解,虽然给出的解未必满足要求;若无时间限制,则两者都能给出满足要求的解。

嵌入式选择

嵌入式特征选择是将特征选择过程与学习器训练过程融为一体,两者在同一个优化过程中完成,即在学习器训练过程中自动地进行了特征选择。

基于惩罚项的特征选择法

给定数据集 D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯ &ThinSpace; , ( x n , y n ) } D = \{(x_1, y_1), (x_2, y_2), \cdots, (x_n, y_n)\} D={(x1,y1),(x2,y2),,(xn,yn)},其中 x ∈ R d , y ∈ R x \in R^d, y \in R xRd,yR。我们考虑最简单的线性回归模型,以平方误差为损失函数,则优化目标为
m i n w ∑ i = 1 n ( y i − w T x i ) 2 min_w \sum_{i=1}^n(y_i - w^Tx_i)^2 minwi=1n(yiwTxi)2
当样本特征很多,而样本数相对较少时,上式很容易陷入过拟合。为了缓解过拟合问题,可对上式引入正则化项。

  • 使用 L2 范数正则化,则称为“岭回归”(ridge regression)。

m i n w ∑ i = 1 n ( y i − w T x i ) 2 + λ ∣ ∣ w ∣ ∣ 2 2 min_w \sum_{i=1}^n(y_i - w^Tx_i)^2 + \lambda ||w||_2^2 minwi=1n(yiwTxi)2+λw22
通过引入 L2 范数正则化,确能显著降低过拟合的风险。

  • 使用 L1 范数正则化,则称为 LASSO(Least Absolute Shrinkage and Selection Operator)。

m i n w ∑ i = 1 n ( y i − w T x i ) 2 + λ ∣ ∣ w ∣ ∣ 1 min_w \sum_{i=1}^n(y_i - w^Tx_i)^2 + \lambda ||w||_1 minwi=1n(yiwTxi)2+λw1
L1 范数和 L2 范数正则化都有助于降低过拟合风险,但 L1 范数比 L2 范数更易于获得“稀疏”(sparse)解,即它求得的 w 会有更少的非零向量。

同时使用 L1 范数和 L2 范数,即可避免过拟合,同时也实现了降维,并筛选出相应的特征。
m i n w ∑ i = 1 n ( y i − w T x i ) 2 + λ 1 ∣ ∣ w ∣ ∣ 1 + λ 2 ∣ ∣ w ∣ ∣ 2 2 min_w \sum_{i=1}^n(y_i - w^Tx_i)^2 + \lambda_1 ||w||_1 + \lambda_2||w||_2^2 minwi=1n(yiwTxi)2+λ1w1+λ2w22

基于树模型的特征选择法

决策树可用于特征选择,树节点的划分特征所组成的集合就是选择出的特征子集。

参考

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值