监督学习算法——线性模型

本篇文章承接上篇文章
监督学习算法——K近邻

线性模型

线性模型是在实践中广泛应用的一类模型,几十年来被广泛研究,它可以追溯到一百多年前。线性模型利用输入特征的线性函数进行预测,稍后会对此进行解释。

1.用于回归的线性模型

mglearn.plots.plot_linear_regression_wave()
w[0]: 0.393906  b: -0.031804

线性模型对wave数据集的预测结果

我们在图中添加了坐标网络,便于理解直线的含义。从w(0)可以看出,斜率应该在0.4左右,在图像中也可以直观地确认这一点。截距是指预测直线与y轴地交点:比0略下,
也可以在图像中确认。
用于回归地线性模型可以表示为这样地回归模型:对单一特征地预测结果是一条直线,两个特征时是一个平面,或者在更高维度(即更多特征)时是一个超平面。

如果将直线地预测结果与下图的预测结果进行比较,你会发现直线的预测能力非常受限。似乎数据的所有细节都丢失了。从某种意义上来说,这种说法是正确的。假设目标y是特征的线性组合,这是一个有多个特征的数据集而言,线性模型可以非常强大。特别地,如果特征数量大于训练数据点地数量,任何目标y都可以(在训练集上)用线性函数完美拟合。

2.线性回归

线性回归,或者普通最小二乘法,是回归问题最简单也最经典的线性方法。线性回归寻找参数w和b,使得对训练集的预测值与真是的回归目标值y之间的均方误差最小
。均方误差是预测值与真实值只差的平方和除以样本数。线性回归没有参数,这是一个优点,但也因此无法控制模型的复杂度。

from sklearn.linear_model import LinearRegression
X,y=mglearn.datasets.make_wave(n_samples=60)
X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=42)
lr=LinearRegression().fit(X_train,y_train)
print("lr.coef_:{}".format(lr.coef_))
print("lr.intercept_:{}".format(lr.intercept_))
lr.coef_:[0.39390555]
lr.intercept_:-0.031804343026759746

会注意到在运行结果的结尾处有下划线。一位内scikit_learn总是将从训练数据中得出的值保存在以下划线结尾的属性中。这是为了将其与用户设置的参数区分开。

intercept_属性是一个浮点数,而coef_属性是一个Numpy数组,每一个元素对应一个输入特征。由wave数据集中只有一个输入特征,所以lr.coef_中只有一个元素。

print("Training set score:{:.2f}".format(lr.score(X_train,y_train)))
print("Test set score:{:.2f}".format(lr.score(X_test,y_test)))
Training set score:0.67
Test set score:0.66

R²约为0.66,测试集和训练集上的分数非常接近。这说明可能存在欠拟合,而不是过拟合。对于一个以为数据集来说,过拟合的风险很小,因为模型非常简单。
对于更高维的数据集来说,线性模型将变得更加强大,过拟合的可能性也会变大。

来看一下,LinearRegression在更复杂的数据集上的表现,比如波士顿房价数据集。

X,y=mglearn.datasets.load_extended_boston()
X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=0)
lr=LinearRegression().fit(X_train,y_train)
print("Training set score:{:.2f}".format(lr.score(X_train,y_train)))
print("Test set score:{:.2f}".format(lr.score(X_test,y_test)))
Training set score:0.95
Test set score:0.61

训练集和测试集之间的性能差异是过拟合的明显标志,因此我们应该试图寻找一个可以控制复杂度的模型。标准线性回归的代替方法之一就是岭回归。

3.岭回归

它的预测公式与普通最小二乘法相同。但是在岭回归中,对系数(w)的选择不仅在训练数集上得到好的预测结果,而且还要拟合附加约束。我们还希望系数尽量小。换句话说,
w的所有元素都应接近于0。直观上来看,这意味着每个特征对输出的影响尽可能小(即斜率很小),同时,仍给出很好的预测结果。这种约束是所谓的正则化的一个例子。正则化是指对模型做出显式约束,以避免过拟合。
岭回归用到这种被称为L2正则化。
岭回归在linear_model.Ridge中实现。

from sklearn.linear_model import Ridge
ridge=Ridge().fit(X_train,y_train)
print("Training set score:{:.2f}".format(ridge.score(X_train,y_train)))
print("Test set score:{:.2f}".format(ridge.score(X_test,y_test)))
Training set score:0.89
Test set score:0.75

可以看出,Ridge在训练集上的分数就要低于Lineargression,但在测试集上分数更高。说明线性回归的数据存在过拟合。Ridge是一种约束更强的模型,所以更不容易
过拟合。复杂度更小的模型意味着训练集上的性能更差,但泛化性能更好。由于我们只对泛化性能感兴趣,所以应该选择Ridge。

ridge10 = Ridge(alpha=10).fit(X_train, y_train)
print("Training set score: {:.2f}".format(ridge10.score(X_train, y_train)))
print("Test set score: {:.2f}".format(ridge10.score(X_test, y_test)))
Training set score: 0.79
Test set score: 0.64

减少alpha可以让系数收到的限制更小,可以在下图向右移动。对于非常小的alpha值,系数几乎没有受到限制,我们得到一个与LinearRegression类似的模型:

ridge01 = Ridge(alpha=0.1).fit(X_train, y_train)
print("Training set score: {:.2f}".format(ridge01.score(X_train, y_train)))
print("Test set score: {:.2f}".format(ridge01.score(X_test, y_test)))
Training set score: 0.93
Test set score: 0.77

这里alpha=0.1效果还不错,我们可以尝试进一步减小alpha以提高泛化性能。
我们还可以查看alpha取不同的值时coef_属性,从而更加定性地理解alpha参数是如何改变改变模型的。

plt.plot(ridge.coef_, 's', label="Ridge alpha=1")
plt.plot(ridge10.coef_, '^', label="Ridge alpha=10")
plt.plot(ridge01.coef_, 'v', label="Ridge alpha=0.1")

plt.plot(lr.coef_, 'o', label="LinearRegression")
plt.xlabel("Coefficient index")
plt.ylabel("Coefficient magnitude")
xlims = plt.xlim()
plt.hlines(0, xlims[0], xlims[1])
plt.xlim(xlims)
plt.ylim(-25, 25)
plt.legend()
<matplotlib.legend.Legend at 0x1339851a590>

在不同alpha值得岭回归与线性回归的系数比较

这里x轴对应conef_的元素:x=0对应第一个特征的系数,x=1对应第二个特征的系数,以此类托,一直到x=100.y轴表示该系数的具体位置。这里需要记住的是,对于alpha=10,系数大多在-3和3之间。对于alpha
=1的Ridge模型,系数要稍微大一点。对于alpha=0.1,点的范围更大。

mglearn.plots.plot_ridge_n_samples()

岭回归和线性回归在波士顿房价数据集上的学习曲线

正如所预计的那样,无论是岭回归还是线性回归,所有数据集大小对应的训练分数都要高于测试分数。由于领回归是正则化的,因此它的训练分数要整体低于线性回归的训练分数。

4.lasso

还有一种线性回归叫lasso,与岭回归相同,使用lasso也是约束系数使其接近于0,但用到的方法不同,叫做L1正则化。L1正则化的结果是,使用lasso时,某些系数刚好为0.
这说明某些特征被完全忽略。

from sklearn.linear_model import Lasso

lasso = Lasso().fit(X_train, y_train)
print("Training set score: {:.2f}".format(lasso.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lasso.score(X_test, y_test)))
print("Number of features used:", np.sum(lasso.coef_ != 0))
Training set score: 0.29
Test set score: 0.21
Number of features used: 4

如你所见,Lasso在训练集与测试集上的表现都很差。这表示存在欠拟合,我们发现模型只用到了105 个特征中的4个。与Ridge类似,Lasso 也有一个正则化参数alpha,可以控制系数趋向于0的强度。在上一个例子中,我们用的是默认值alpha=1.0。为了降低欠拟合,我们尝试减小alpha。这么做的同时,我们还需要增加max _iter的值(运行迭代的最大次数) :

#我们增大max_iter的值,否则模型会向我们做出提示
lasso001 = Lasso(alpha=0.01, max_iter=100000).fit(X_train, y_train)
print("Training set score: {:.2f}".format(lasso001.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lasso001.score(X_test, y_test)))
print("Number of features used:", np.sum(lasso001.coef_ != 0))
Training set score: 0.90
Test set score: 0.77
Number of features used: 33

alpha值变小,我们可以拟合一个更复杂的模型,在训练集和测试集上的表现也更好。模型性能比使用Ridge时略好一点,而且我们只用到了105个特征中的33个。

lasso00001 = Lasso(alpha=0.0001, max_iter=100000).fit(X_train, y_train)
print("Training set score: {:.2f}".format(lasso00001.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lasso00001.score(X_test, y_test)))
print("Number of features used:", np.sum(lasso00001.coef_ != 0))
Training set score: 0.95
Test set score: 0.64
Number of features used: 96
plt.plot(lasso.coef_, 's', label="Lasso alpha=1")
plt.plot(lasso001.coef_, '^', label="Lasso alpha=0.01")
plt.plot(lasso00001.coef_, 'v', label="Lasso alpha=0.0001")

plt.plot(ridge01.coef_, 'o', label="Ridge alpha=0.1")
plt.legend(ncol=2, loc=(0, 1.05))
plt.ylim(-25, 25)
plt.xlabel("Coefficient index")
plt.ylabel("Coefficient magnitude")
Text(0, 0.5, 'Coefficient magnitude')

不同alpha值得lasso回归与岭回归得系数比较

在alpha=1时,我们发现不仅大部分系数都是0 (我们已经知道这一点),而且其他系数也都很小。将alpha减小至0.01,我们得到图中向上的三角形,大部分特征等于0。alpha=0.0001时,我们得到正则化很弱的模型,大部分系数都不为0,并且还很大。为了便于比较,图中用圆形表示Ridge的最佳结果。alpha=0.1的Ridge模型的预测性能与alpha=0.01的Lasso模型类似,但Ridge模型的所有系数都不为0。
在实践中,在两个模型中一般首选岭回归。但如果特征很多,你认为只有其中几个是重要的,那么选择Lasso可能更好。同样,如果你想要一个容易解释的模型,Lasso 可以给出更容易理解的模型,因为它只选择了一部分输入特征。scikit-learn 还提供了ElasticNet类,结合了Lasso和Ridge的惩罚项。在实践中,这种结合的效果最好,不过代价是要调节两个参数: 一个用于L1正则化,一个用于L2正则化。

对于用于回归的线性模型,输出g是特征的线性函数,是直线、平面或超平面(对于更高维的数据集)。对于用于分类的线性模型,决策边界是输入的线性函数。换句话说,(二元)线性分类器是利用直线、平面或超平面来分开两个类别的分类器
学习线性模型有很多种算法。这些算法的区别在于以下两点:
系数和截距的特定组合对训练数据拟合好坏的度量方法;
是否使用正则化,以及使用哪种正则化方法。
不同的算法使用不同的方法来度量“对训练集拟合好坏”。由于数学上的技术原因,不可能调节w和b使得算法产生的误分类数量最少。对于我们的目的,以及对于许多应用而言,上面第一点(称为损失函数)的选择并不重要。最常见的两科线性分类算法是Lgisics回归(ogiste resin)利线性支持向量机(linear spport vector machine, 线性SVM),前者在Linear_model.Lgisicegestenin中实现,后者在Sv.LinerSC (SVC 代表支持向量分类器)中实现。虽然LogisticRegression的名宇中含有回归Lgrsp但它是种分类算法, 并不是回归算法,不应与LinearRegression混淆。
我们可以将LogisticRegress和LinearSVC应用到forge数错集上,并将线性模型我到的决策边界可视化:

from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
X, y = mglearn.datasets.make_forge()
fig, axes = plt.subplots(1, 2, figsize=(10, 3))
for model, ax in zip([LinearSVC(), LogisticRegression()], axes):
    clf = model.fit(X, y)
    mglearn.plots.plot_2d_separator(clf, X, fill=False, eps=0.5,
                                    ax=ax, alpha=.7)
    mglearn.discrete_scatter(X[:, 0], X[:, 1], y, ax=ax)
    ax.set_title(clf.__class__.__name__)
    ax.set_xlabel("Feature 0")
    ax.set_ylabel("Feature 1")
axes[0].legend()
E:\Anaconda3\lib\site-packages\sklearn\svm\_base.py:1244: ConvergenceWarning: Liblinear failed to converge, increase the number of iterations.
  warnings.warn(





<matplotlib.legend.Legend at 0x1339a2b2fb0>

线性SVM和Logistic回归在forge数据集上得决策边界

在这张图中,forge 数据集的第一一个特征位于x轴,第二个特征位于y轴,与前面相同。
图中分别展示了LinearSVC 和LogisticRegression得到的决策边界,都是直线,将顶部归为类别1的区域和底部归为类别0的区域分开了。换句话说,对于每个分类器而言,位于黑线上方的新数据点都会被划为类别1,而在黑线下方的点都会被划为类别0。
两个模型得到了相似的决策边界。注意,两个模型中都有两个点的分类是错误的。两个模型都默认使用L2正则化,就像Ridge对回归所做的那样。对于LogisticRegression和LinearSVC,决定正则化强度的权衡参数叫作C。C值越大,对应的正则化越弱。换句话说,如果参数C值较大,那么LogisticRegression和LinearSVC将尽可能将训练集拟合到最好,而如果C值较小,那么模型更强调使系数向量(w)接近于0。参数C的作用还有另一个有趣之处。较小的C值可以让算法尽量适应“大多数”数据点,而较大的C值更强调每个数据点都分类正确的重要性。下面是使用LinearSVC的图示:

mglearn.plots.plot_linear_svc_regularization()

不同C值得线性SVM在forge数据集上得决策边界

from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, stratify=cancer.target, random_state=42)
logreg = LogisticRegression().fit(X_train, y_train)
print("Training set score: {:.3f}".format(logreg.score(X_train, y_train)))
print("Test set score: {:.3f}".format(logreg.score(X_test, y_test)))
Training set score: 0.948
Test set score: 0.958


E:\Anaconda3\lib\site-packages\sklearn\linear_model\_logistic.py:458: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(

C=1的默认值给出了相当好的性能,在训练集和测试集上都达到了95%的精度。但由于训练集和测试集的性能非常接近,所以模型很可能是欠拟合的。我们都尝试增大C来拟合一个更灵活的模型:

logreg100 = LogisticRegression(C=100).fit(X_train, y_train)
print("Training set score: {:.3f}".format(logreg100.score(X_train, y_train)))
print("Test set score: {:.3f}".format(logreg100.score(X_test, y_test)))
Training set score: 0.948
Test set score: 0.958


E:\Anaconda3\lib\site-packages\sklearn\linear_model\_logistic.py:458: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
logreg001 = LogisticRegression(C=0.01).fit(X_train, y_train)
print("Training set score: {:.3f}".format(logreg001.score(X_train, y_train)))
print("Test set score: {:.3f}".format(logreg001.score(X_test, y_test)))
Training set score: 0.934
Test set score: 0.930


E:\Anaconda3\lib\site-packages\sklearn\linear_model\_logistic.py:458: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(
plt.plot(logreg.coef_.T, 'o', label="C=1")
plt.plot(logreg100.coef_.T, '^', label="C=100")
plt.plot(logreg001.coef_.T, 'v', label="C=0.001")
plt.xticks(range(cancer.data.shape[1]), cancer.feature_names, rotation=90)
xlims = plt.xlim()
plt.hlines(0, xlims[0], xlims[1])
plt.xlim(xlims)
plt.ylim(-5, 5)
plt.xlabel("Feature")
plt.ylabel("Coefficient magnitude")
plt.legend()
<matplotlib.legend.Legend at 0x13394d87250>

不同C值得Logistic回归在乳腺癌数据集上学到的系数

for C, marker in zip([0.001, 1, 100], ['o', '^', 'v']):
    lr_l1 = LogisticRegression(C=C, solver='liblinear', penalty="l1").fit(X_train, y_train)
    print("Training accuracy of l1 logreg with C={:.3f}: {:.2f}".format(
          C, lr_l1.score(X_train, y_train)))
    print("Test accuracy of l1 logreg with C={:.3f}: {:.2f}".format(
          C, lr_l1.score(X_test, y_test)))
    plt.plot(lr_l1.coef_.T, marker, label="C={:.3f}".format(C))

plt.xticks(range(cancer.data.shape[1]), cancer.feature_names, rotation=90)
xlims = plt.xlim()
plt.hlines(0, xlims[0], xlims[1])
plt.xlim(xlims)
plt.xlabel("Feature")
plt.ylabel("Coefficient magnitude")

plt.ylim(-5, 5)
plt.legend(loc=3)
Training accuracy of l1 logreg with C=0.001: 0.91
Test accuracy of l1 logreg with C=0.001: 0.92
Training accuracy of l1 logreg with C=1.000: 0.96
Test accuracy of l1 logreg with C=1.000: 0.96
Training accuracy of l1 logreg with C=100.000: 0.99
Test accuracy of l1 logreg with C=100.000: 0.98





<matplotlib.legend.Legend at 0x133966dab00>

对于不同C值,L1惩罚的logistic回归在乳腺癌数据集上学到的系数

用二分类的线性模型与用于回归的线性模型由许多相似之处。与用于回归的线性模型一样,模型的主要差别在于penatly参数,这个参数会影响正则化,也会影响模型是使用所有可用特征还是只选择特征的一个子集。

6.用于多分类的线性模型

许多线性分类模型只适用于二分类问题,不能轻易推广到多类别问题(除了Logistic 回归)。将二分类算法推广到多分类算法的一一种常见方法是“一对其余”(one-vs-rest) 方法。在“一对其余”方法中,对每个类别都学习一个二分类模型,将这个类别与所有其他类别尽量分开,这样就生成了与类别个数一样多的二分类模型。在测试点上运行所有二类分类器来进行预测。在对应类别上分数最高的分类器‘胜出”,将这个类别标签返回作为预测结果。
每个类别都对应一一个二类分类器,这样每个类别也都有一个系数(w) 向量和一个截距(b)。下面给出的是分类置信方程,其结果中最大值对应的类别即为预测的类别标签:多分类Logistic回归背后的数学与“一对其余”方法稍有不同,但它也是对每个类别都有一个系数向量和-一个截距,也使用了相同的预测方法。
我们将“一对其余”方法应用在一个简单的三分类数据集上。我们用到了一个二维数据集,每个类别的数据都是从一个高斯分布中采样得出的:

from sklearn.datasets import make_blobs
X, y = make_blobs(random_state=42)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
plt.legend(["Class 0", "Class 1", "Class 2"])
<matplotlib.legend.Legend at 0x1339b5e2b90>

包含三个类别的二维玩具数据集

linear_svm = LinearSVC().fit(X, y)
print("Coefficient shape: ", linear_svm.coef_.shape)
print("Intercept shape: ", linear_svm.intercept_.shape)
Coefficient shape:  (3, 2)
Intercept shape:  (3,)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
line = np.linspace(-15, 15)
for coef, intercept, color in zip(linear_svm.coef_, linear_svm.intercept_,
                                  mglearn.cm3.colors):
    plt.plot(line, -(line * coef[0] + intercept) / coef[1], c=color)
plt.ylim(-10, 15)
plt.xlim(-10, 8)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
plt.legend(['Class 0', 'Class 1', 'Class 2', 'Line class 0', 'Line class 1',
            'Line class 2'], loc=(1.01, 0.3))
<matplotlib.legend.Legend at 0x1339756af50>

三个“一对其余”分类器学到的决策边界

你可以看到,训练集中所有属于类别0的点都在与类别0对应的直线上方,这说明它们位于这个二类分类器属于“类别0”的那一侧。属于类别0的点位于与类别2对应的直线上方,这说明它们被类别2的二类分类器划为“其余”。属于类别0的点位于与类别1对应的直线左侧,这说明类别1的二元分类器将它们划为“其余”。因此,这一区域的所有点都会被最终分类器划为类别0(类别0的分类器的分类置信方程的结果大于0,其他两个类别对应的结果都小于0)。
但图像中间的三角形区域属于哪一个类别呢,3个二类分类器都将这一区域内的点划为“其余”。这里的点应该划归到哪-个 类别呢?答案是分 类方程结果最大的那个类别, 即最接近的那条线对应的类别。
下面的例子给出了二维空间中所有区域的预测结果:

mglearn.plots.plot_2d_classification(linear_svm, X, fill=True, alpha=.7)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
line = np.linspace(-15, 15)
for coef, intercept, color in zip(linear_svm.coef_, linear_svm.intercept_,
                                  mglearn.cm3.colors):
    plt.plot(line, -(line * coef[0] + intercept) / coef[1], c=color)
plt.legend(['Class 0', 'Class 1', 'Class 2', 'Line class 0', 'Line class 1',
            'Line class 2'], loc=(1.01, 0.3))
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
Text(0, 0.5, 'Feature 1')

三个“一对其余”分类器得到的多酚类决策边界

7.优点、缺点和参数

线性模型的主要参数是正则化参数,在回归模型中叫作alpha,在LinearSVc和Logistic-Regression中叫作C。alpha 值较大或c值较小,说明模型比较简单。特别是对于回归模型而言,调节这些参数非常重要。通常在对数尺度上对C和alpha进行搜索。你还需要确定的是用L1正则化还是L2正则化。如果你假定只有几个特征是真正重要的,那么你应该用L1正则化,否则应默认使用L2正则化。如果模型的可解释性很重要的话,使用L1也会有帮助。由于LI只用到几个特征,所以更容易解释哪些特征对模型是重要的,以及这些特征的作用。
线性模型的训练速度非常快,预测速度也很快。这种模型可以推广到非常大的数据集,对稀疏数据也很有效。如果你的数据包含数十万甚至上百万个样本,你可能需要研究如何使用LogisticRegression和Ridge模型的solver=‘sag’ 选项,在处理大型数据时,这一-选项比默认值要更快。其他选项还有SGDClassifier类和SGDRegressor类,它们对本节介绍的线性模型实现了可扩展性更强的版本。
线性模型的另一个优点在f,利用我们之间见过的用于回归和分类的公式,理解如何进
行预测是相对比较容易的。不幸的是, 往往并不完全清楚系数为什么是这样的。如果你的数据集中包含高度相关的特征,这问题尤为突出。在这种情况下,可能很难对系数如果特征数最大干样本数量,线性模型的表现通常都很好。它也常用于非常大的数据集,
是为训练其他模型并不可行。但在更低维的空间中,其他模型的泛化性能可能更好。

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值