Python机器学习基础教程

读书笔记 专栏收录该内容
12 篇文章 2 订阅

1. 引言

监督学习(supervised learning)的方法中,用户将成对的输入和预期输出提供给算法,算法会找到一种方法,根据给定输入给出预期输出

监督学习算法(supervised learning algorithm):从输入/ 输出对中进行学习的机器学习算法

无监督学习算法(unsupervised learning algorithm):在无监督学习中,只有输入数据是已知的,没有为算法提供输出数据

在机器学习中,每个实体或每一行被称为一个样本(sample)或数据点

而每一列(用来描述这些实体的属性)则被称为特征(feature)

如何构建良好的数据表征, 被称为特征提取(feature extraction)或特征工程(feature engineering)

1.1 机器学习能够解决的问题

  • 我想要回答的问题是什么?已经收集到的数据能够回答这个问题吗?
  • 要将我的问题表示成机器学习问题,用哪种方法最好?
  • 我收集的数据是否足够表达我想要解决的问题?
  • 我提取了数据的哪些特征?这些特征能否实现正确的预测?
  • 如何衡量应用是否成功?
  • 机器学习解决方案与我的研究或商业产品中的其他部分是如何相互影响的?

1.3 scikit-learn

scikit-learn 是一个开源项目,可以免费使用和分发,任何人都可以轻松获取其源代码来查看其背后的原理

安装scikit-learn

scikit-learn 依赖于另外两个Python 包:NumPy 和SciPy。若想绘图和进行交互式开发,还应该安装matplotlib、IPython 和Jupyter Notebook。

pip install numpy scipy matplotlib ipython scikit-learn pandas

1.4 必要的库和工具

Jupyter notebook、Numpy、Pandas略

1.4.3 Scipy

SciPy 是Python 中用于科学计算的函数集合,具有线性代数高级程序、数学函数优化、信号处理、特殊数学函数和统计分布等多项功能

SciPy 中最重要的是scipy.sparse:它可以给出稀疏矩阵(sparse matrice),稀疏矩阵是scikit-learn 中数据的另一种表示方法

from scipy import sparse
import numpy as np

eye = np.eye(4)
print("Numpy array:\n{}".format(eye))

Numpy array:
[[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]]

# 将NumPy数组转换为CSR格式的SciPy稀疏矩阵
# 只保存非零元素
sparse_matrix = sparse.csr_matrix(eye)
print("\nScipy sparse CSR matrix:\n{}".format(sparse_matrix))

Scipy sparse CSR matrix:
(0, 0) 1.0
(1, 1) 1.0
(2, 2) 1.0
(3, 3) 1.0

1.4.4 matplotlib

在Jupyter Notebook 中, 你可以使用%matplotlib notebook%matplotlib inline 命令,将图像直接显示在浏览器中

import matplotlib.pyplot as plt
%matplotlib inline

x = np.linspace(-10,10,100)
y = np.sin(x)
plt.plot(x,y,marker='x')

在这里插入图片描述

1.4.6 mglearn

mglearn是专为本书案例用的库

#安装
pip install mglearn

#导入
import mglearn

1.7 第一个应用:鸢尾花分类

收集每朵鸢尾花的一些测量数据:花瓣的长度和宽度以及花萼的长度和宽度,所有测量结果的单位都是厘米

这些花之前已经被植物学专家鉴定为属于setosa、versicolor 或virginica 三个品种之一

目标是构建一个机器学习模型,可以从这些已知品种的鸢尾花测量数据中进行学习,从而能够预测新鸢尾花的品种

问题判断

  1. 因为我们有已知品种的鸢尾花的测量数据,所以这是一个监督学习问题
  2. 要在多个选项中预测其中一个(鸢尾花的品种),这是一个分类(classification)问题的示例
  3. 可能的输出(鸢尾花的不同品种)叫作类别(class)。数据集中的每朵鸢尾花都属于三个类别之一,所以这是一个三分类问题
  4. 单个数据点(一朵鸢尾花)的预期输出是这朵花的品种。对于一个数据点来说,它的品种叫作标签(label)。

1.7.1 初识数据

from sklearn.datasets import load_iris

iris_dataset = load_iris()
#数据集的简要说明
print(iris_dataset.DESCR[:150])

在这里插入图片描述

#数据集形状、属性、数据
iris_dataset.data.shape
print(iris_dataset.feature_names)
iris_dataset.data[:5]

(150, 4)

[‘sepal length (cm)’, ‘sepal width (cm)’, ‘petal length (cm)’, ‘petal width (cm)’]

array([[5.1, 3.5, 1.4, 0.2],
[4.9, 3. , 1.4, 0.2],
[4.7, 3.2, 1.3, 0.2],
[4.6, 3.1, 1.5, 0.2],
[5. , 3.6, 1.4, 0.2]])

#标签形状、标签名、数据
iris_dataset.target.shape
print(iris_dataset.target_names)
iris_dataset.target

(150,)

[‘setosa’ ‘versicolor’ ‘virginica’]

上述数字的代表含义由iris[‘target_names’] 数组给出:0 代表setosa,1 代表versicolor,2 代表virginica

合并数据

data = pd.DataFrame(iris_dataset.data,columns=iris_dataset.feature_names)
data['category'] = iris_dataset.target
data

在这里插入图片描述

1.7.2 衡量模型是否成功:训练数据与测试数据

将收集好的带标签数据(此例中是150 朵花的测量数据)分成两部分。

一部分数据用于构建机器学习模型,叫作训练数据(training data)或训练集(training set)。其余的数据用来评估模型性能,叫作测试数据(test data)、测试集(testset)或留出集(hold-out set)。

train_test_split 函数:scikit-learn 中的train_test_split 函数可以打乱数据集并进行拆分。

train_test_split 函数默认是将数据75%分为训练集,25%分为测试集

scikit-learn 中的数据通常用大写的X 表示,而标签用小写的y表示

from sklearn.model_selection import train_test_split
Xtrain,Xtest,ytrain,ytest = train_test_split(iris_dataset.data,iris_dataset.target,random_state=0)

在对数据进行拆分之前,train_test_split 函数利用伪随机数生成器将数据集打乱。为了确保多次运行同一函数能够得到相同的输出,我们利用random_state 参数指定了随机数生成器的种子。这样函数输出就是固定不变的

Xtrain.shape,ytrain.shape
Xtest.shape,ytest.shape

((112, 4), (112,))

((38, 4), (38,))

1.7.3 要事第一:观察数据

在构建机器学习模型之前,通常最好检查一下数据。

检查数据的最佳方法之一就是将其可视化。

一种可视化方法是绘制散点图,但是,计算机屏幕只有两个维度,所以我们一次只能绘制两个特征;

另一种方法是绘制散点图矩阵,从而可以两两查看所有的特征。

# 利用X_train中的数据创建DataFrame
# 利用iris_dataset.feature_names中的字符串对数据列进行标记

iris_dataframe = pd.DataFrame(Xtrain,columns=iris_dataset.feature_names)
grr = pd.plotting.scatter_matrix(iris_dataframe,figsize=(15,15),marker='o',hist_kwds={'bins':20},alpha=0.8,cmap=mglearn.cm3)

在这里插入图片描述
从图中可以看出,利用花瓣和花萼的测量数据基本可以将三个类别区分开。这说明机器学习模型很可能可以学会区分它们。

1.7.4 构建第一个模型:k近邻算法

scikit-learn 中所有的机器学习模型都在各自的类中实现,这些类被称为Estimator类

k 近邻分类算法是在neighbors 模块的KNeighborsClassifier 类中实现的

from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=1)

knn.fit(Xtrain,ytrain)

scikit-learn 中的大多数模型都有很多参数,但多用于速度优化或非常特殊的用途,你无需关注这个字符串表示中的其他参数

1.7.5 做出预测

X_new = np.array([[5,2.9,1,0.2]])

prediction = knn.predict(X_new)
print("Predictinon:{}".format(prediction))
print("Predicted target name:{}".format(iris_dataset.target_names[prediction]))

Predictinon:[0]
Predicted target name:[‘setosa’]

1.7.6 评估模型

print("测试精度:{:.2f}".format(knn.score(Xtest,ytest)))

测试精度:0.97

对于测试集中的鸢尾花,预测有97%是正确的

2. 监督学习

每当想要根据给定输入预测某个结果,并且还有输入/ 输出对的示例时,都应该使用监督学习。

2.1 分类与回归

监督机器学习问题主要有两种——分类(classification)与回归(regression)

2.1.1 分类问题

目标:预测类别标签(class label),这些标签来自预定义的可选列表。

分类问题有时可分为二分类和多分类

二分类(binary classification):在两个类别之间进行区分的一种特殊情况

通常将其中一个类别称为正类(positive class),另一个类别称为反类(negative class)

多分类(multiclass
classification):在两个以上的类别之间进行区分

2.1.2 回归问题

目标:预测一个连续值,编程术语叫作浮点数(floating-point number),数学术
语叫作实数(real number)

区分分类任务和回归任务有一个简单方法,就是问一个问题:输出是否具有某种连续性

如果在可能的结果之间具有连续性,那么它就是一个回归问题。

2.2 泛化、过拟合与欠拟合

泛化:如果一个模型能够对没见过的数据做出准确预测,我们就说它能够从训练集泛化(generalize)到测试集

过拟合(overfitting):构建一个对现有信息量来说过于复杂的模型。

如果你在拟合模型时过分关注训练集的细节,得到了一个在训练集上表现很好、但不能泛化到新数据上的模型,那么就存在过拟合。

欠拟合(underfitting):选择过于简单的模型。

我们的模型越复杂,在训练数据上的预测结果就越好。但是,如果我们的模型过于复杂,我们开始过多关注训练集中每个单独的数据点,模型就不能很好地泛化到新数据上。

二者之间存在一个最佳位置,可以得到最好的泛化性能。这就是我们想要的模型。在这里插入图片描述

模型复杂度与数据集大小的关系

数据集中包含的数据点的变化范围越大,在不发生过拟合的前提下,你可以使用的模型就越复杂

收集更多数据,适当构建更复杂的模型,对监督学习任务往往特别有用。现实世界中,尽力收集最多数据,可能比模型调参更为有效

2.3 监督学习算法

2.3.2 k近邻

1. k近邻分类

mglearn.plots.plot_knn_classification(n_neighbors=3)

在这里插入图片描述

#数据集分割
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=0)

#knn实例化
from sklearn.neighbors import KNeighborsClassifier
clf = KNeighborsClassifier(n_neighbors=3)

#训练模型
clf.fit(X_train,y_train)

#模型预测
clf.predict(X_test)

#模型得分
clf.score(X_test,y_test)

KNeighborsClassifier(n_neighbors=3)

array([1, 0, 1, 0, 1, 0, 0])

0.8571428571428571

2. 分析KNeighborsClassifier

查看决策边界(decision boundary),即算法对类别0 和类别1 的分界线。
在这里插入图片描述
使用更少的邻居对应更高的模型复杂度,而使用更多的邻居对应更低的模型复杂度

使用乳腺癌数据集训练模型,查看近邻个数对于结果的影响

from matplotlib import pyplot as plt
%matplotlib inline
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer

#导入数据
cancer = load_breast_cancer()
X = cancer.data
y = cancer.target

#拆分数据集
X_train,X_test,y_train,y_test = train_test_split(
    X,y,random_state=0)

neighbors_setting = range(1,11)
train_accuracy = []
test_accuracy = []

#设置不同邻居时,得到得分
for n_neighbors in neighbors_setting:
    clf = KNeighborsClassifier(n_neighbors=n_neighbors)
    clf.fit(X_train,y_train)
    train_accuracy.append(clf.score(X_train,y_train))
    test_accuracy.append(clf.score(X_test,y_test))

plt.rcParams['font.sans-serif'] = 'SimHei'
#得分进行画图
plt.plot(neighbors_setting,train_accuracy,label="训练精度")
plt.plot(neighbors_setting,test_accuracy,label="测试精度")
plt.xlabel("近邻n值")
plt.ylabel("精度")
plt.legend()

在这里插入图片描述

3. k近邻回归

from sklearn.neighbors import KNeighborsRegressor

X,y = mglearn.datasets.make_wave(n_samples=40)
# 将wave数据集分为训练集和测试集
X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=0)

# 模型实例化,并将邻居个数设为3
reg = KNeighborsRegressor(n_neighbors=3)
# 利用训练数据和训练目标值来拟合模型
reg.fit(X_train,y_train)

#模型预测
reg.predict(X_test)
#模型得分
reg.score(X_test,y_test)

4. 分析KNeighborsRegressor

在这里插入图片描述
仅使用单一邻居,训练集中的每个点都对预测结果有显著影响,预测结果的图像经过所有数据点,这导致预测结果非常不稳定。

考虑更多的邻居之后,预测结果变得更加平滑,但对训练数据的拟合也不好

优点、缺点和参数

KNeighbors 分类器使用较小的邻居个数(比如3 个或5个)往往可以得到比较好的结果,但应
该调节这个参数

优点:模型很容易理解,构建模型的速度通常很快

缺点

  1. 不能处理具有很多特征的数据集
  2. 预测速度可能会比较慢

2.3.3 线性模型

1. 用于回归的线性模型

一般公式如下:
y ^ = w [ 0 ] ∗ x [ 0 ] + w [ 1 ] ∗ x [ 1 ] + . . . + w [ p ] ∗ x [ p ] + b \hat{y} = w[0]*x[0] + w[1]*x[1] + ... + w[p]*x[p] + b y^=w[0]x[0]+w[1]x[1]+...+w[p]x[p]+b

x[0] 到x[p] 表示单个数据点的特征(本例中特征个数为p+1),w 和b 是学习模型的参数,ŷ 是模型的预测结果

在这里插入图片描述

用于回归的线性模型可以表示为这样的回归模型:对单一特征的预测结果是一条直线,两个特征时是一个平面,或者在更高维度(即更多特征)时是一个超平面

对于有多个特征的数据集而言,线性模型可以非常强大。特别地,如果特征数量大于训练数据点的数量,任何目标y都可以(在训练集上)用线性函数完美拟合

2. 线性回归(又名普通最小二乘法,OLS)

线性回归作用:寻找参数w 和b,使得对训练集的预测值与真实的回归目标值y之间的均方误差最小。

均方误差(mean squared error)是预测值与真实值之差的平方和除以样本数。

线性回归没有参数,这是一个优点,但也因此无法控制模型的复杂度

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)

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("斜率w: {}".format(lr.coef_))
print("截距b: {}".format(lr.intercept_))

斜率w: [0.39390555]
截距b: -0.031804343026759746

intercept_ 属性是一个浮点数,而coef_ 属性是一个NumPy 数组,每个元素对应一个输入特征

对于一维数据集来说,过拟合的风险很小,因为模型非常简单(或受限)。
然而,对于更高维的数据集(即有大量特征的数据集),线性模型将变得更加强大,过拟合的可能性也会变大。

例如波士顿房价数据集:

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. 岭回归(L2)

意义:每个特征对输出的影响应尽可能小(即斜率很小),同时仍给出很好的预测结果

正则化,是指对模型做显式约束,以避免过拟合

岭回归用到的这种被称为L2 正则化

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 在训练集上的分数要低于LinearRegression,但在测试集上的分数更高

线性回归对数据存在过拟合,Ridge 是一种约束更强的模型,所以更不容易过拟合。

复杂度更小的模型意味着在训练集上的性能更差,但泛化性能更好

Ridge函数通过调整alpha值(默认为1),来找到训练集性能与泛化能力之间的平衡——alpha越大,系数越趋向于0,泛化性能越强,训练集降低

ridge = Ridge(alpha=10).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.79
Test set score: 0.64

对于非常小的alpha 值,系数几乎没有受到限制,我们得到一个与LinearRegression (相当于alpha=0)类似的模型

ridge = Ridge(alpha=0.1).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.93
Test set score: 0.77

在这里插入图片描述
由上图,对于alpha=10,系数大多在-3 和3 之间。
对于alpha=1 的Ridge 模型,系数要稍大一点。
对于alpha=0.1,点的范围更大。
对于没有做正则化的线性回归(即alpha=0),点的范围很大,许多点都超出了图像的范围。
在这里插入图片描述
由于岭回归是正则化的,因此它的训练分数要整体低于线性回归的训练分数。但岭回归的测试分数要更高,特别是对较小的子数据集。

如果有足够多的训练数据,正则化变得不那么重要,并且岭回归和线性回归将具有相同的性能。可以这样认为,当数据量较小时,才适合使用岭回归替代线性回归

4. lasso(L1)

lasso也是约束系数使其接近于0,但用到的方法不同,叫作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: {}".format(np.sum(lasso.coef_ != 0)))

Training set score: 0.29
Test set score: 0.21
Number of features used: 4

Lasso在训练集与测试集上存在欠拟合,我们发现模型只用到了105 个特征中的4个

Lasso 通过alpha控制系数趋向于0的强度,减小alpha,同时增加max_iter 的值(运行迭代的最大次数),可以降低欠拟合。

# 我们增大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: {}".format(np.sum(lasso001.coef_ != 0)))

Training set score: 0.90
Test set score: 0.77
Number of features used: 33

在这里插入图片描述
由上图,在alpha=1 时,我们发现不仅大部分系数都是0(我们已经知道这一点),而且其他系数也都很小。将alpha 减小至0.01,我们得到图中向上的三角形,大部分特征等于0。alpha=0.0001 时,我们得到正则化很弱的模型,大部分系数都不为0,并且还很大。为了便于比较,图中用圆形表示Ridge 的最佳结果。alpha=0.1 的Ridge 模型的预测性能与alpha=0.01 的Lasso 模型类似,但Ridge 模型的所有系数都不为0。

实践中,在两个模型中一般首选岭回归。但如果特征很多,你认为只有其中几个是重要的,那么选择Lasso 可能更好。

5. 用于二分类的线性模型

y ^ = w [ 0 ] ∗ x [ 0 ] + w [ 1 ] ∗ x [ 1 ] + . . . + w [ p ] ∗ x [ p ] + b > 0 \hat{y} = w[0]*x[0] + w[1]*x[1] + ... + w[p]*x[p] + b> 0 y^=w[0]x[0]+w[1]x[1]+...+w[p]x[p]+b>0

如果函数值小于0,我们就预测类别-1;如果函数值大于0,我们就预测类别+1。

与回归线性模型的区别

  • 用于回归的线性模型,输出ŷ 是特征的线性函数,是直线、平面或超平面(对于更高维的数据集)。
  • 对于用于分类的线性模型,(二元)线性分类器是利用直线、平面或超平面,来分开两个类别的分类器

学习线性模型有很多种算法。这些算法的区别在于:

  • 系数和截距的特定组合对训练数据拟合好坏的度量方法;
  • 是否使用正则化,以及使用哪种正则化方法。

最常见的两种线性分类算法是Logistic 回归(logistic regression)和线性支持向量机(linear support vector machine, 线性SVM)

虽然LogisticRegression的名字中含有回归(regression),但它是一种分类算法, 并不是回归算法, 不应与LinearRegression 混淆。

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("{}".format(clf.__class__.__name__))
    ax.set_xlabel("Feature 0")
    ax.set_ylabel("Feature 1")
axes[0].legend()

在这里插入图片描述
对于两个模型:

  1. 都默认使用L2 正则化
  2. 决定正则化强度的权衡参数叫作C。C 值越大,对应的正则化越弱,将尽可能将训练集拟合到最好;C 值较小,那么模型更强调使系数向量(w)接近于0
  3. 较小的C 值可以让算法尽量适应“大多数”数据点,而较大的C 值更强调每个数据点都分类正确的重要性。
    在这里插入图片描述
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)
LR = LogisticRegression().fit(X_train,y_train)
print("Training set score: {:.3f}".format(LR.score(X_train, y_train)))
print("Test set score: {:.3f}".format(LR.score(X_test, y_test)))

Training set score: 0.946
Test set score: 0.958

此时C值为默认值1,在训练集和测试集上都达到95%的精度。尝试增大C 来拟合一个更灵活的模型:

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

Training set score: 0.948
Test set score: 0.965

使用C=100 可以得到更高的训练集精度,也得到了稍高的测试集精度,即更复杂的模型应该性能更好。

设置C=0.01,可以看到使用正则化更强的模型时会发生什么:

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

Training set score: 0.934
Test set score: 0.930

已经欠拟合的模型继续向左移动,训练集和测试集的精度都比采用默认参数时更小。

在这里插入图片描述

与用于回归的线性模型一样,模型的主要差别在于penalty 参数,这个参数会影响正则化,也会影响模型是使用所有可用特征还是只选择特征的一个子集。

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

种常见方法:“一对其余”(one-vs.-rest)法,对每个类别都学习一个二分类模型,将这个类别与所有其他类别尽量分开,这样就生成了与类别个数一样多的二分类模型

  • 4
    点赞
  • 6
    评论
  • 29
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页

打赏作者

adamlay

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值