天池工业蒸汽量中不同梯度下降策略的效果

import pandas as pd
import numpy as np

数据加载

train_data = pd.read_csv("./zhengqi_train.txt",sep='\t')
test_data = pd.read_csv("./zhengqi_test.txt",sep='\t')
test_data
train_data
V0V1V2V3V4V5V6V7V8V9...V29V30V31V32V33V34V35V36V37target
00.5660.016-0.1430.4070.452-0.901-1.812-2.360-0.436-2.114...0.1360.109-0.6150.327-4.627-4.789-5.101-2.608-3.5080.175
10.9680.4370.0660.5660.194-0.893-1.566-2.3600.332-2.114...-0.1280.1240.0320.600-0.8430.1600.364-0.335-0.7300.676
21.0130.5680.2350.3700.112-0.797-1.367-2.3600.396-2.114...-0.0090.3610.277-0.116-0.8430.1600.3640.765-0.5890.633
30.7330.3680.2830.1650.599-0.679-1.200-2.0860.403-2.114...0.0150.4170.2790.603-0.843-0.0650.3640.333-0.1120.206
40.6840.6380.2600.2090.337-0.454-1.073-2.0860.314-2.114...0.1831.0780.3280.418-0.843-0.2150.364-0.280-0.0280.384
..................................................................
28830.190-0.025-0.1380.1610.600-0.2120.7570.584-0.0260.904...0.128-0.2080.809-0.1730.247-0.027-0.3490.5760.6860.235
28840.5070.5570.2960.1830.530-0.2370.7490.5840.5370.904...0.291-0.2870.465-0.3100.7630.498-0.349-0.615-0.3801.042
2885-0.394-0.721-0.4850.0840.1360.0340.6550.614-0.8180.904...0.291-0.1790.2680.5520.7630.498-0.3490.9510.7480.005
2886-0.219-0.282-0.344-0.0490.449-0.1400.5600.583-0.5960.904...0.2161.061-0.0511.0230.8780.610-0.230-0.3010.5550.350
28870.3680.380-0.225-0.0490.3790.0920.5500.5510.2440.904...0.0470.057-0.0420.8470.534-0.009-0.190-0.5670.3880.417

2888 rows × 39 columns

#取所有的特征列
X_train = train_data.iloc[:,:-1]
#最后一列为y 
y_train = train_data[['target']]  #取目标列要用[[]],表示二维
print("X_train:",X_train.shape,"y_train:",y_train.shape)
X_train: (2888, 38) y_train: (2888, 1)

计算Wx+b用的是矩阵乘法(W,X都为矩阵),所以,要吧b搞进矩阵
方法:在X矩阵多加全为1的一列,在W矩阵多初始化以为W0作为b

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jiFYdheM-1688894094478)(D:/360MoveData/Users/9527/Desktop/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0/%25E7%259F%25A9%25E9%2598%25B5%25E4%25B9%2598%25E6%25B3%2595.jpg)]

# X矩阵多加全为1的一列
X_train['bias'] = 1
test_data['bias'] = 1
X_train
V0V1V2V3V4V5V6V7V8V9...V29V30V31V32V33V34V35V36V37bias
00.5660.016-0.1430.4070.452-0.901-1.812-2.360-0.436-2.114...0.1360.109-0.6150.327-4.627-4.789-5.101-2.608-3.5081
10.9680.4370.0660.5660.194-0.893-1.566-2.3600.332-2.114...-0.1280.1240.0320.600-0.8430.1600.364-0.335-0.7301
21.0130.5680.2350.3700.112-0.797-1.367-2.3600.396-2.114...-0.0090.3610.277-0.116-0.8430.1600.3640.765-0.5891
30.7330.3680.2830.1650.599-0.679-1.200-2.0860.403-2.114...0.0150.4170.2790.603-0.843-0.0650.3640.333-0.1121
40.6840.6380.2600.2090.337-0.454-1.073-2.0860.314-2.114...0.1831.0780.3280.418-0.843-0.2150.364-0.280-0.0281
..................................................................
28830.190-0.025-0.1380.1610.600-0.2120.7570.584-0.0260.904...0.128-0.2080.809-0.1730.247-0.027-0.3490.5760.6861
28840.5070.5570.2960.1830.530-0.2370.7490.5840.5370.904...0.291-0.2870.465-0.3100.7630.498-0.349-0.615-0.3801
2885-0.394-0.721-0.4850.0840.1360.0340.6550.614-0.8180.904...0.291-0.1790.2680.5520.7630.498-0.3490.9510.7481
2886-0.219-0.282-0.344-0.0490.449-0.1400.5600.583-0.5960.904...0.2161.061-0.0511.0230.8780.610-0.230-0.3010.5551
28870.3680.380-0.225-0.0490.3790.0920.5500.5510.2440.904...0.0470.057-0.0420.8470.534-0.009-0.190-0.5670.3881

2888 rows × 39 columns

线性回归+梯度下降

批量梯度下降BGD

损 失 函 数 : J ( θ ) = 1 2 ∑ i = 1 n ( h θ ( x ( i ) ) − y ( i ) ) 2 损失函数:J(\theta) = \frac{1}{2}\sum\limits_{i = 1}^n(h_{\theta}(x^{(i)}) - y^{(i)})^2 :J(θ)=21i=1n(hθ(x(i))y(i))2

批量梯度下降法是最原始的形式,它是指在每次迭代使用所有样本来进行梯度的更新。每次迭代参数更新公式如下:

$$
梯度更新:\theta_j^{n + 1} = \theta_j^{n} - \eta * (h_{\theta}(x) - y )x_j

矩阵写法:
$$

θ n + 1 = θ n − η ∗ X T ( X θ − y ) \theta^{n + 1} = \theta^{n} - \eta * X^T(X\theta -y) θn+1=θnηXT(Xθy)

代码实现

# 先把数据转为np格式
X = X_train.values
y = y_train.values
print(X.shape)
print(y.shape)


#============================定义超参数=====================================
t0 = 5
t1 =  100000

#定义一个函数来调整一下学习率(逆时调整),随着迭代次数的增加,学习率应该变小
def lrate_descent(t):
    return t0 / (t + t1)  #初始学习率lr = 5 / 100000=0.00005
# 随机初始化W矩阵
W = np.random.randn(X.shape[1],1)
#均方差
mse = 0
last_mse = mse + 1
#迭代次数
times = 0
#停止迭代的精度
accuracy = 0.001
#==========================梯度下降=========================
while True:
    if np.abs(mse - last_mse) < accuracy:
        break
    #loss的梯度
    grid = X.T.dot(X.dot(W) - y)
    #学习率
    lrate = lrate_descent(times)
    #梯度更新
    W = W - lrate * grid
    
    #计算均方差
    last_mse = mse
    mse = ((X.dot(W) - y) ** 2).mean()
    times += 1
    print("迭代第{}次,均方差是:{}".format(times,mse))
b = W[-1]
print("最终的迭代{}次,参数矩阵W:{},bias:{}\n".format(times,W,b))
(2888, 39)
(2888, 1)
迭代第1次,均方差是:8.721127701275023
迭代第2次,均方差是:6.269560464833249
...
最终的迭代147次,参数矩阵W:[[ 0.77104869]
 [ 0.12510433]
 [-0.07367257]
...
 [ 0.32888301]],bias:[0.32888301]

#预测
X_test = test_data.values
y_pred = X_test.dot(W)
np.savetxt('./批量梯度下降.txt',y_pred)
print(y_pred.shape)
(1925, 1)

均方差:score:4.6368

SGD随机梯度下降

随机梯度下降法不同于批量梯度下降,随机梯度下降是每次迭代使用一个样本来对参数进行更新。
:
θ n + 1 = θ n − η ∗ X T ( X θ − y ) \theta^{n + 1} = \theta^{n} - \eta * X^T(X\theta -y) θn+1=θnηXT(Xθy)

批量梯度下降算法每次都会使用全部训练样本,因此这些计算是冗余的,因为每次都使用完全相同的样本集。而随机梯度下降算法每次只随机选择一个样本来更新模型参数,因此每次的学习是非常快速的。

# 先把数据转为np格式
X = X_train.values
y = y_train.values
print(X.shape)
print(y.shape)

#============================定义超参数=====================================
t0 = 5
t1 =  100000

#定义一个函数来调整一下学习率(逆时调整),随着迭代次数的增加,学习率应该变小
def lrate_descent(t):
    return t0 / (t + t1)  #初始学习率lr = 5 / 100000=0.00005
# 随机初始化W矩阵
W = np.random.randn(X.shape[1],1)
#均方差
mse = 0
last_mse = mse + 1
#迭代次数
times = 0
#停止迭代的精度
accuracy = 0.001
#==========================梯度下降=========================
while True:
    if np.abs(mse - last_mse) < accuracy:
        break
    #=====================打乱数据============================================
    index = np.arange(X.shape[0])
    #打乱索引值
    np.random.shuffle(index)
    #numpy的花式索引,把样本打乱给成index的排序
    X = X[index]
    y = y[index]
    #=======================================================================================
    #================随机抽取样本来更新梯度=========================================
    for i in range(X.shape[0]):
        # 抽取一个样本
        X_i = X[[i]]
        y_i = y[[i]]
        #loss的梯度
        grid = X_i.T.dot(X_i.dot(W) - y_i)
        #学习率
        lrate = lrate_descent(times)
        #梯度更新
        W = W - lrate * grid

    #计算均方差
    last_mse = mse
    mse = ((X.dot(W) - y) ** 2).mean()
    times += 1
    print("迭代第{}次,均方差是:{}".format(times,mse))
b = W[-1]
print("最终的迭代{}次,参数矩阵W:{},bias:{}\n".format(times,W,b))
(2888, 39)
(2888, 1)
迭代第1次,均方差是:20.908397509630017
...
最终的迭代129次,参数矩阵W:[[ 0.21884206]
 [ 0.52453898]
...
 [-0.14529283]
 [ 0.68916026]],bias:[0.68916026]

预测:

X2 = test_data.values
y_pred  = X2.dot(W)
print(y_pred.shape)
np.savetxt('./随机梯度下降.txt',y_pred)
(1925, 1)

均方差:score:4.8463

MBGD小批量梯度下降

小批量梯度下降,是对批量梯度下降以及随机梯度下降的一个折中办法。其思想是:每次迭代使用总样本中的一部分(batch_size)样本来对参数进行更新。这里我们假设 batch_size = 32,样本数 n = 1000 。实现了更新速度与更新次数之间的平衡。每次迭代参数更新公式如下:
θ j n + 1 = θ j n − η ∗ 1 b a t c h _ s i z e ∑ i = 1 b a t c h _ s i z e ( h θ ( x ( i ) ) − y ( i ) ) x j ( i ) \theta_j^{n + 1} = \theta_j^{n} - \eta *\frac{1}{batch\_size}\sum\limits_{i = 1}^{batch\_size} (h_{\theta}(x^{(i)}) - y^{(i)} )x_j^{(i)} θjn+1=θjnηbatch_size1i=1batch_size(hθ(x(i))y(i))xj(i)
相对于随机梯度下降算法,小批量梯度下降算法降低了收敛波动性, 即降低了参数更新的方差,使得更新更加稳定。相对于全量梯度下降,其提高了每次学习的速度。并且其不用担心内存瓶颈从而可以利用矩阵运算进行高效计算。

一般情况下,小批量梯度下降是梯度下降的推荐变体,特别是在深度学习中。每次随机选择2的幂数个样本来进行学习,例如:8、16、32、64、128、256。因为计算机的结构就是二进制的。但是也要根据具体问题而选择,实践中可以进行多次试验, 选择一个更新速度与更次次数都较适合的样本数。

在这选择batch_size = 32

# 先把数据转为np格式
X = X_train.values
y = y_train.values
print(X.shape)
print(y.shape)

#总样本
n = X.shape[0]
batch_size = 32
#总batch数
num_batches = int(n / batch_size) # 90
#============================定义超参数=====================================
t0 = 5
t1 =  100000

#定义一个函数来调整一下学习率(逆时调整),随着迭代次数的增加,学习率应该变小
def lrate_descent(t):
    return t0 / (t + t1)  #初始学习率lr = 5 / 100000=0.00005
# 随机初始化W矩阵
W = np.random.randn(X.shape[1],1)
#均方差
mse = 0
last_mse = mse + 1
#迭代次数
times = 0
#停止迭代的精度
accuracy = 0.001
#==========================梯度下降=========================
while True:
    if np.abs(mse - last_mse) < accuracy:
        break
    #=====================打乱数据============================================
    index = np.arange(X.shape[0])
    #打乱索引值
    np.random.shuffle(index)
    #numpy的花式索引,把样本打乱给成index的排序
    X = X[index]
    y = y[index]
    #=======================================================================================
    #================按batch数来迭代更新========================================
    for i in range(num_batches):
        # 抽取一批样本(32个)
        X_batch = X[i * batch_size : (i + 1)*batch_size]
        y_batch = y[i * batch_size : (i + 1)*batch_size]
        #loss的梯度
        grid = X_batch.T.dot(X_batch.dot(W) - y_batch)
        #学习率
        lrate = lrate_descent(times)
        #梯度更新
        W = W - lrate * grid
    #计算均方差
    last_mse = mse
    mse = ((X.dot(W) - y) ** 2).mean()
    times += 1
    print("迭代第{}次,均方差是:{}".format(times,mse))
b = W[-1]
print("最终的迭代{}次,参数矩阵W:{},bias:{}\n".format(times,W,b))
(2888, 39)
(2888, 1)
迭代第1次,均方差是:14.126515548387038
...
迭代第121次,均方差是:0.19214031423470773
最终的迭代121次,参数矩阵W:[[ 0.38160319]
 [ 0.59695493]
...
 [ 0.04837792]],bias:[0.04837792]

# 进行预测
X2 = test_data.values
y_pred  = X2.dot(W)
np.savetxt('./小批量梯度下降.txt',y_pred)

score:0.7879

总结:

  梯度下降法(Gradient Descent)是一个算法,但不是像多元线性回归那样是一个具体做回归任务的算法,而是一个非常通用的优化算法来帮助一些机器学习算法(都是无约束最优化问题)求解出最优解, 所谓的通用就是很多机器学习算法都是用梯度下降,甚至深度学习也是用它来求解最优解。所有优化算法的目的都是期望以最快的速度把模型参数θ求解出来,梯度下降法就是一种经典常用的优化算法。

三种梯度下降策略的特点总结:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A5PWmUVK-1688894094480)(D:/360MoveData/Users/9527/Desktop/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0.assets/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20230708210910.png)]

BGD:

优点:
  (1)一次迭代是对所有样本进行计算,此时利用矩阵进行操作,实现了并行。
  (2)由全数据集确定的方向能够更好地代表样本总体,从而更准确地朝向极值所在的方向。当目标函数为凸函数时,BGD一定能够得到全局最优。
缺点:
  (1)当样本数目 n 很大时,每迭代一步都需要对所有样本计算,训练过程会很慢。

从迭代的次数上来看,BGD迭代的次数相对较少。其迭代的收敛曲线示意图可以表示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GQ8QCMXD-1688894094482)(D:/360MoveData/Users/9527/Desktop/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0.assets/13-BGD.jpeg)]

SGD:

优点:
  (1)由于不是在全部训练数据上的更新计算,而是在每轮迭代中,随机选择一条数据进行更新计算,这样每一轮参数的更新速度大大加快。
  缺点:
  (1)准确度下降。由于即使在目标函数为强凸函数的情况下,SGD仍旧无法做到线性收敛。
  (2)可能会收敛到局部最优,由于单个样本并不能代表全体样本的趋势。

为什么SGD收敛速度比BGD要快:

  • 我们假设有30W个样本,对于BGD而言,每次迭代需要计算30W个样本才能对参数进行一次更新,需要求得最小值可能需要多次迭代(假设这里是10)。
  • 而对于SGD,每次更新参数只需要一个样本,因此若使用这30W个样本进行参数更新,则参数会被迭代30W次,而这期间,SGD就能保证能够收敛到一个合适的最小值上了。
  • 也就是说,在收敛时,BGD计算了 10×30W 次,而SGD只计算了 1×30W 次。

从迭代的次数上来看,SGD迭代的次数较多,在解空间的搜索过程就会盲目一些。其迭代的收敛曲线示意图可以表示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PCeZ31ul-1688894094483)(D:/360MoveData/Users/9527/Desktop/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0.assets/14-SGD.jpeg)]

MBGD:

相对于随机梯度下降算法,小批量梯度下降算法降低了收敛波动性, 即降低了参数更新的方差,使得更新更加稳定。相对于全量梯度下降,其提高了每次学习的速度。并且其不用担心内存瓶颈从而可以利用矩阵运算进行高效计算。

一般情况下,小批量梯度下降是梯度下降的推荐变体,特别是在深度学习中。每次随机选择2的幂数个样本来进行学习,例如:8、16、32、64、128、256。因为计算机的结构就是二进制的。但是也要根据具体问题而选择,实践中可以进行多次试验, 选择一个更新速度与更次次数都较适合的样本数。

MBGD梯度下降迭代的收敛曲线更加温柔一些:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XUebr7TQ-1688894094484)(D:/360MoveData/Users/9527/Desktop/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0.assets/12-%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E6%96%B9%E5%BC%8F.jpeg)]

梯度下降优化

不管用上面三种哪一种,都存在一些挑战与问题,我们可以从以下几点进行优化:

  1. 选择一个合理的学习速率很难。如果学习速率过小,则会导致收敛速度很慢。如果学习速率过大,那么其会阻碍收敛,即在极值点附近会振荡。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cuT6y2nk-1688894094485)(D:/360MoveData/Users/9527/Desktop/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0.assets/15-%E5%90%88%E9%80%82%E5%AD%A6%E4%B9%A0%E7%8E%87.jpeg)]

  2. 学习速率调整,试图在每次更新过程中, 改变学习速率。从经验上看,**学习率在一开始要保持大些来保证收敛速度,在收敛到最优点附近时要小些以避免来回震荡。**比较简单的学习率调整可以通过 **学习率衰减(Learning Rate Decay)**的方式来实现。
    假 设 初 始 化 学 习 率 为 η 0 , 在 第 t 次 迭 代 时 的 学 习 率 η t 。 假设初始化学习率为 \eta_0,在第 t 次迭代时的学习率 \eta_t。 η0tηt
    常用的衰减方式为可以设置为 按迭代次数 进行衰减,迭代次数越大,学习率越小![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-skVtDqD4-1688894094485)(D:/360MoveData/Users/9527/Desktop/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0.assets/16-%E8%B0%83%E6%95%B4%E5%AD%A6%E4%B9%A0%E7%8E%87.jpeg)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ohMRw5r9-1688894094486)(D:/360MoveData/Users/9527/Desktop/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0.assets/17-%E8%B0%83%E6%95%B4%E5%AD%A6%E4%B9%A0%E7%8E%87.jpeg)]

  3. 模型所有的参数每次更新都是使用相同的学习速率。如果数据特征是稀疏的,或者每个特征有着不同的统计特征与空间,那么便不能在每次更新中每个参数使用相同的学习速率,那些很少出现的特征应该使用一个相对较大的学习速率。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-elJmgOZS-1688894094486)(D:/360MoveData/Users/9527/Desktop/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0.assets/18-%E7%89%B9%E5%BE%81%E5%AD%A6%E4%B9%A0%E7%8E%87.jpg)]

  4. 对于非凸目标函数,容易陷入那些次优的局部极值点中,如在神经网路中。那么如何避免呢。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-voJauC0m-1688894094486)(D:/360MoveData/Users/9527/Desktop/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0/%E5%90%84%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E5%AE%9E%E7%8E%B0.assets/10-%E5%85%A8%E5%B1%80%E6%9C%80%E4%BC%98%E5%8C%96.png)]

    简单的问题,一般使用随机梯度下降即可解决。在深度学习里,对梯度下降进行了很多改进,比如:自适应梯度下降。

  5. 轮次和批次

    轮次:epoch,轮次顾名思义是把我们已有的训练集数据学习多少轮,迭代多少次。

    批次:batch,批次这里指的的我们已有的训练集数据比较多的时候,一轮要学习太多数据, 那就把一轮次要学习的数据分成多个批次,一批一批数据的学习。

    就好比,你要背诵一片《赤壁赋》,很长。你在背诵的时候,一段段的背诵,就是批次batch。花费了一天终于背诵下来了,以后的9天,每天都进行一轮背诵复习,这就是轮次epoch。这样,《赤壁赋》的背诵效果,就非常牢固了。

    在进行,机器学习训练时,我们也要合理选择轮次和批次~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学AI不秃头

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值