【清华时间序列库】无基础可入:面向对象编程的TSLib主程序run.py解析(一)

前言

相信有很多小伙伴是做时间序列预测方向的,但是部分小伙伴可能刚入实验室,属于没有什么代码基础,老师又催着快速上手。哪在初入门时如何最快地跑出实验结果?自然是直接调用大佬写好的库啦!但是身为小白的我们,即使有封装好的东西,对于如何应用在自己的数据集上还是不甚了解,而这个系列的教程也正是为此设计的。

清华时间序列库(TSLib)简介

清华时间序列库 (TSLib) 是由清华大学THUML团队开发的开源GitHub项目 Time Series Library,TSLib能够完成五种任务,分别是长期预测(long-term forecasting)、短期预测(short-term forecasting)、分类(classification)、插值(imputation)和异常检测(anomaly detection)。项目内包括多种模型的代码,具体可见图1,对于我们新手来说不管是跑实验还是学习,都是非常够用滴~

图1:TSLib库中包含的模型目录

图1:TSLib库中包含的模型目录

预计教程

本系列教程预计分为以下几篇:

  1. 【清华时间序列库】主程序 run.py 解析(一)
    目的:让我们初步了解TSLib中大量的可以自己设置的超参的含义是什么。
  2. 【清华时间序列库】如何使用自己的数据集运行TSLib(二)
    目的:了解如果使用自己的数据集跑代码,那么哪些代码需要改,怎么改,以及跑出来的结果在哪里和如何解读。
  3. 【清华时间序列库】TSLib中 XX模型 深入解读(三)
    目的:对模型搭建进行深入解读,以及对于其中数据形状的变化,为什么发生了形状变化,有什么作用等进行详细解读。基本经过深入解读各种模型之后,相信大家对于一些模型基本机制和搭建方法都有了一个了解,再搭建自己的模型就不成问题啦。

下载并打开TSLib

首先,我们从下面两个地址中的任意一个下载TSLib。

GitCode(中文版):https://gitcode.com/gh_mirrors/ti/Time-Series-Library
GitHub(英文版):https://github.com/thuml/Time-Series-Library/tree/main?tab=readme-ov-file

GitCode下载位置如图2所示,点击Download ZIP即可下载。GitHub下载位置如图3所示,点击Code,然后点击Download ZIP即可下载。

图2:GitCode下载位置

图2:GitCode下载位置

图3:GitHub下载位置

图3:GitHub下载位置

下载完成后解压ZIP压缩包,接下来我们使用VS code编译器进行操作。有使用其他编译器的也可以奥,如果是连anaconda都没有安装的小白,请参考以下教程安装anaconda + VSCode,了解怎么配置python环境噢。

Anaconda + VScode配置https://blog.youkuaiyun.com/Hermione201910/article/details/145981138?spm=1001.2014.3001.5501

然后我们打开VSCode,选择上方的file -> open folder,选择刚刚解压好的TSLib文件夹,打开。

图4:VSCode打开TSLib文件夹

图4:VSCode打开TSLib文件夹

看到的界面如图5所示,左边是代码的目录,由于小编的程序是成功运行过的,所以小编的文件比没有跑通过的文件更多噢,例如results、checkpoints等文件夹,如果没有成功运行则可能不会有。

图5:TSLib文件夹目录

图5:TSLib文件夹目录

以下表格详细说明了一些主要文件夹的作用,有的小白可能一开始不太理解内容,可以跳过,到时候再回来看,影响不大。

表1:TSLib文件夹目录

表1:TSLib文件夹目录

解析主程序 Run.py

主程序用到的库

主程序用到的库如图6所示,主要可以分为两类:

第一类是一些常用的、不是TSLib写的包(例如torch、numpy等),但是在写TSLib时用到了,这些我们需要安装好。可以直接通过requirement.txt配置好这些,requirement.txt里面有每个包的版本号,使用它配置则不容易出现版本不兼容的问题。具体配置方法在该系列的第二个教程“如何使用自己的数据集运行TSLib(二)”会说,现在有个印象就可以啦。

第二类是下载TSLib时自带的包,不需要再配置,直接如图6 import导入即可。

图6:主程序用到的库

图6:主程序用到的库

固定随机种子

在跑机器学习、深度学习模型时,一开始会初始化模型内部的参数,这个初始化是随机的。如果我们不固定随机种子,每次初始化的值则不一样,极有可能跑不出相同的结果。

因此,为了方便复现结果,我们一般会固定随机种子,主程序中的代码如下。其中fix_seed是随机种子,可以根据自己的喜好设置,这里设置的是2021。

fix_seed = 2021
random.seed(fix_seed)
torch.manual_seed(fix_seed)
np.random.seed(fix_seed)

参数设置

模型训练过程中有很多细节,这些细节可以通过超参数设定和调整,例如学习率、优化器、训练轮数等。TSLib中使用了argparse(Python库)让程序从命令行接收用户输入的参数。在这里有一个参数的概念可能会混淆。

机器学习中的参数 VS 命令行参数
机器学习中,人为设定的超参数(hyper-parameters)是用来控制模型训练过程的配置变量,比如学习率或网络层数,而模型内部通过训练数据学到的参数(parameters)则是模型自身的权重或偏置等;

而在代码中,例如add_argument()方法的作用是定义程序运行时需要接收的命令行参数,比如 --name,它告诉程序去生成并解析这个参数,这里的“参数”更多是指程序运行时的输入选项,而非模型中的数学概念。

两者的共同点是都需要人为设定,但前者影响模型性能,后者则控制程序行为。之后主要内容就是介绍使用TSLib需要传输的命令行参数(这些参数里面包括机器学习里的超参概念),下文说参数都指命令行参数奥!

用户传输实验所需参数的具体实现路径如下
首先我们以一个shell脚本中的命令为例。这个命令中规定了数据路径(root_path、data_path)、模型名称(model)、数据集类型(custom)、学习率(learning_rate)等参数,我们通过执行这个带有参数的命令,argparse库内的ArgumentParser类则可以解析这些参数,将它传入代码中,从而完成参数的设置。具体的参数含义后文会有介绍,现在看不到shell也没关系。

python -u run.py \
  --task_name long_term_forecast \
  --is_training 1 \
  --root_path ./dataset/traffic/ \
  --data_path traffic.csv \
  --model_id traffic_96_96 \
  --model $model_name \
  --data custom \
  --features M \
  --seq_len 96 \
  --label_len 48 \
  --pred_len 96 \
  --e_layers 4 \
  --d_layers 1 \
  --factor 3 \
  --enc_in 862 \
  --dec_in 862 \
  --c_out 862 \
  --des 'Exp' \
  --d_model 512 \
  --d_ff 512 \
  --batch_size 16 \
  --learning_rate 0.001 \
  --itr 1
argparse库作用

ArgumentParser是一个类,类是不能直接调用的,需要生成一个实例,以下代码则是 生成一个ArgumentParser实例,并命名为parser。其中的description=‘TimesNet’ 是一个可选参数,用来给这个解析器添加一个描述信息。这个描述会在用户运行程序时,显示在帮助信息里(比如当用户输入 --help 或 -h 时)。

parser = argparse.ArgumentParser(description='TimesNet')
add_argument()方法

在进行超参配置之前,我们需要了解add_argument()方法。add_argument()的作用是告诉程序:我需要一个叫 --name 的参数,让程序生成这个参数。同时我们可以定义这个参数的类型(例如字符串 或 整数),并且给这个参数一个默认值(default)。如果用户没有输入这个参数,则使用默认值。required=False表示这个参数不是必须的,用户可以不输入这个参数。help是帮助信息,当用户输入 --help 或 -h 时,会显示这个参数的描述信息。

add_argument('--name', type=str, required=False, default='long_term_forecast', help='task name')
基本配置

基本配置如以下表格。其中需要注意两个小点。

  1. 虽然参数 is_training 的类型是整数,但是由于代码是通过 if is_training: 的条件判断来决定运行模式。因此,只要 is_training 的值为非零整数,其效果与值为 1 时相同,即会执行训练模型的逻辑。
  2. model参数的选择:在exp文件夹中的exp_basic.py文件中,有一个字典model_dict(图7右下方的框)。这个字典的键是模型名称(类型为字符串),值是相对应的模型文件。我们通过输入模型名称,通过这个字典找到对应的模型文件,这就意味着我们设置的model必须是这个字典的键中的一个。不然就会找不到模型文件奥!
参数名类型作用
task_name字符串任务类型,不同的任务类型对数据有不同的处理。
is_training整数是否训练模型,1表示训练模型,并对测试集进行预测和保存结果。0表示不训练模型,直接从checkpoints文件夹中调用已经保存好的模型,对测试集进行预测并保存结果。
model_id字符串这个参数用于生成模型和结果文件的名称,作为实验的标识符。例如,在大多数超参数设置相同且使用“custom”自定义数据集的情况下,可以通过指定不同的 model_id 来区分两次独立的实验运行。
model字符串模型名称,可选:Autoformer、Transformer、TimesNet等,TSLib包内有的都可以选。
# basic config
parser.add_argument('--task_name', type=str, required=False, default='long_term_forecast',
                    help='task name, options:[long_term_forecast, short_term_forecast, imputation, classification, anomaly_detection]')
parser.add_argument('--is_training', type=int, required=False, default=1, help='status')
parser.add_argument('--model_id', type=str, required=False, default='test', help='model id')
parser.add_argument('--model', type=str, required=False, default='DLinear',
                        help='model name, options: [Autoformer, Transformer, TimesNet]')

图7:exp_basic.py中的model_dict

图7:exp_basic.py中的model_dict
数据加载

在数据加载中有两个值得注意的点。

(1)data参数

data参数用于指定数据集类型,TSLib包内有多种数据集,例如M4、ETT、weather等。如图8所示,data_provider文件夹中的data_factory.py文件中有一个字典data_dict,该字典中的键(key)是我们可以选择输入的参数,字典的值(value)是相对应的数据集的类。

这些数据在README.md中的使用方法部分给出了下载链接,可以自行下载。具体哪个数据集文件对应哪种类型,可以通过data_provider文件夹中的data_loader.py文件,这个文件中有多个数据集的类(class),如图9所示。展开一个数据集类,如图10所示,就可以看到在__init__时会输入data_path属性,这个属性就是对应的数据集文件。

好!现在再梳理一下data怎么选择奥,这里讲一下使用TSLib自带的数据集,如果是自己的数据集,下一个教程会讲奥!首先下载TSLib的数据集,确定好使用哪个数据文件,例如我准备使用ETTh1.csv这个数据文件–>在data_loader.py文件中找到这个csv文件对应的类,则是Dataset_ETThour。–>在data_factory.py文件中找到这个类Dataset_ETThour在字典data_dict中对应的键,则是ETTh1(这是字符串类型噢)。那么我们在设置data参数时,则是设为ETTh1即可~

(2)features参数
features参数用于指定预测任务类型,有三种选择:M:多变量预测多变量,S:单变量预测单变量,MS:多变量预测单变量。

M和S都默认输入的变量和输出的变量一样,就是不存在使用单变量风速去预测单变量风电功率,而是单变量风速就会预测单变量风速;如果输入单变量风电功率就会预测风电功率。MS则是输入的是多变量,输出的单变量是输入的多变量的其中一个。

参数名类型作用
data字符串数据集类型
root_path字符串根目录路径,如果是按照我上文所说方式打开的,那么设置为’./'即可。不是的话只要指定Time-Series-Library-main所在文件夹的位置即可。
data_path字符串数据文件的路径,由于读取数据是 root_path和data_path拼接起来的,因此data_path是指进入root_path之后的路径,不要重复奥!
features字符串三种选择。M:多变量预测多变量,S:单变量预测单变量,MS:多变量预测单变量
target字符串在S和MS任务中,需要指定要预测的目标变量。target是目标变量在数据集中的列名
freq字符串指定使用哪种方式去提取时间戳中的特征。例如把月、日、周、星期几提取出来,然后处理成特征,freq指的是使用哪种处理方式,这个内容稍多,之后会详讲
checkpoints字符串训练好的模型保存的路径,默认是./checkpoints/
# data loader
parser.add_argument('--data', type=str, required=False, default='custom', help='dataset type')
parser.add_argument('--root_path', type=str, default='./', help='root path of the data file')
parser.add_argument('--data_path', type=str, default='model_inputs.csv', help='data file')
parser.add_argument('--features', type=str, default='MS',
                    help='forecasting task, options:[M, S, MS]; M:multivariate predict multivariate, S:univariate predict univariate, MS:multivariate predict univariate')
parser.add_argument('--target', type=str, default='power', help='target feature in S or MS task')
parser.add_argument('--freq', type=str, default='t',
                    help='freq for time features encoding, options:[s:secondly, t:minutely, h:hourly, d:daily, b:business days, w:weekly, m:monthly], you can also use more detailed freq like 15min or 3h')
parser.add_argument('--checkpoints', type=str, default='./checkpoints/', help='location of model checkpoints')

图8:TSLib包内数据集

图8:TSLib包内数据集

图9:data_loader.py文件中数据集的类

图9:data_loader.py文件中数据集的类

图10:data_loader.py文件中数据集的类展开

图10:data_loader.py文件中数据集的类展开
预测任务配置

这里可能大家会疑惑为什么除了训练长度(seq_len)和预测长度(pred_len)之外,还有一个label_len?这里我们需要弄清楚这三个参数在时间戳上的关系,以及它们在模型中的具体作用。

如图11所示,从(a)图中可以看到,label_len是完全与seq_len的尾巴重叠的,而pred_len则是从seq_len的尾巴开始,完全错开的。从(b)图中可以看到模型的输入样本是seq_len长度的,而输出样本是label_len + pred_len长度的。但是,我们只使用pred_len部分作为我们得到的最终预测结果,label_len仅仅是为了辅助预测奥~

图11:seq_len、label_len、pred_len在时间戳上的关系

图11:seq_len、label_len、pred_len在时间戳上的关系

参数名类型作用
seq_len整数输入序列长度
label_len整数输入序列与输出的预测序列重叠部分,辅助预测。
pred_len整数预测序列长度。
seasonal_patterns字符串只有使用M4数据集才会用得上的参数。
inverse布尔值数据集中默认对数据进行归一化操作,而inverse参数为True代表对预测结果进行反归一化操作,False则不会进行反归一化操作。
# forecasting task
parser.add_argument('--seq_len', type=int, default=96, help='input sequence length')
parser.add_argument('--label_len', type=int, default=48, help='start token length')
parser.add_argument('--pred_len', type=int, default=24, help='prediction sequence length')
parser.add_argument('--seasonal_patterns', type=str, default='Hourly', help='subset for M4')
parser.add_argument('--inverse', action='store_true', help='inverse output data', default=True)
插值和异常值检测配置

由于这个系列前期重点在预测任务上,因此插值和异常值检测先不讲。预测讲完了之后,如果需要再拓展。不过我相信,大家经过这个教程,独立去看插值和异常值检测肯定完全没问题啦~只需要把他们的理论了解清楚就好啦。

模型配置

这里面大部分参数都与模型结构有关。但是需要注意,当我们使用一个模型时,例如只使用DLinear,并不是所有在模型配置中的参数都会用上,只会用上一小部分。

因此,如图12所示,一些参数也会在后面注明仅仅只为某一个模型所设置的参数。这些参数则会在后面关于模型的教程中详细讲解。

在此之前,我们先了解三个需要根据数据集而更改的参数,分别是enc_indec_inc_out。这三个参数都需要设置成数据集的特征数量(即数据集中除了时间戳之外有多少列)。

假设数据集形如以下表格,那么我的enc_in、dec_in、c_out都应该是4。

时间戳温度湿度风速降雨量
2021-01-01 00:00:0010801.20
2021-01-01 00:00:0110801.20
2021-01-01 00:00:0210801.20
# model define
parser.add_argument('--enc_in', type=int, default=4, help='encoder input size')
parser.add_argument('--dec_in', type=int, default=4, help='decoder input size')
parser.add_argument('--c_out', type=int, default=4, help='output size')

图12:模型配置

图12:模型配置
优化算法配置

这里的训练轮数epochs、学习率learning_rate、批次大小batch_size、损失函数loss等都是机器学习中的基本概念。这些我就不再赘述了哈,不理解的需要去巩固一下机器学习基本知识,搜搜教程看看书什么的~

此外需要注意两个地方,一个是num_workers,这个参数设置的越大,数据加载越快,消耗的CPU资源也越多,但是需要注意,不要超过自己本地的CPU线程数。另一个是use_amp,设置为True则代表使用自动混合精度训练,会优化前向传播中的计算,减少显存占用并加速训练,但是它需要 GPU 支持 float16 运算。默认是不开的哈。

参数名类型作用
num_workers整数数据加载器线程数
itr整数实验次数
train_epochs整数一次实验中的训练轮数
batch_size整数训练批次大小
patience整数早停次数
learning_rate浮点数优化器学习率
des字符串实验描述
loss字符串损失函数
lradj字符串学习率调整方式
use_amp布尔值是否使用自动混合精度训练
parser.add_argument('--num_workers', type=int, default=10, help='data loader num workers')
parser.add_argument('--itr', type=int, default=5, help='experiments times')
parser.add_argument('--train_epochs', type=int, default=100, help='train epochs')
parser.add_argument('--batch_size', type=int, default=32, help='batch size of train input data')
parser.add_argument('--patience', type=int, default=3, help='early stopping patience')
parser.add_argument('--learning_rate', type=float, default=0.001, help='optimizer learning rate')
parser.add_argument('--des', type=str, default='test', help='exp description')
parser.add_argument('--loss', type=str, default='MSE', help='loss function')
parser.add_argument('--lradj', type=str, default='type1', help='adjust learning rate')
parser.add_argument('--use_amp', action='store_true', help='use automatic mixed precision training', default=False)
其他配置

剩余的配置是GPU配置、数据增强以及TimeXer的专属参数,这些我们可以都使用默认值,暂时先不需要动它。跳过不影响跑自己数据集奥。

运行设备选择

大家都知道用GPU跑模型可以跑得更快,但是要想使用GPU得保证自己的笔记本有独立显卡奥,并且还需要安装cuda,否则只能使用CPU。

这部分代码首先检测了是否能够GPU运行,不能的话自动切换CPU,再将相关的实验参数进行展示。

args = parser.parse_args()
# args.use_gpu = True if torch.cuda.is_available() and args.use_gpu else False
args.use_gpu = True if torch.cuda.is_available() else False

print(torch.cuda.is_available())

if args.use_gpu and args.use_multi_gpu:
    args.devices = args.devices.replace(' ', '')
    device_ids = args.devices.split(',')
    args.device_ids = [int(id_) for id_ in device_ids]
    args.gpu = args.device_ids[0]

print('Args in experiment:')
print_args(args)

任务选择

这部分太简单了,就是根据我们的命令行参数task_name,对应上其相应的任务。Exp_Long_Term_Forecast、Exp_Short_Term_Forecast等都是类(class),每个类里写好了这一类任务的所有操作(例如训练、测试、获取数据、建模等)。我们只需要实例化这个类,就可以直接调用类里的操作了。

if args.task_name == 'long_term_forecast':
    Exp = Exp_Long_Term_Forecast
elif args.task_name == 'short_term_forecast':
    Exp = Exp_Short_Term_Forecast
elif args.task_name == 'imputation':
    Exp = Exp_Imputation
elif args.task_name == 'anomaly_detection':
    Exp = Exp_Anomaly_Detection
elif args.task_name == 'classification':
    Exp = Exp_Classification
else:
    Exp = Exp_Long_Term_Forecast

训练和测试模型

setting的作用就是保存结果的时候,先生成setting的文件夹,将结果保存在文件夹中。从代码里可以看到这个文件夹名里包括任务名称、模型名称、数据集名称等等实验细节。这样做的好处是批量跑代码是所有结果都可以妥善保存。

!但是需要注意,如果在setting中的几个参数全都一样的话,那跑第二个实验保存结果时会覆盖前一个实验的结果,这种情况下记得更改model_id以做区分噢,还记得我们说的model_id可以作为实验的标识符吗~

train函数和test函数
在学习机器学习理论的过程中,我们都知道数据一般划分为训练集、验证集、测试集。其中训练集用来训练模型内部参数;验证集用来调超参、检验模型收敛情况;测试集用来评估模型效果。

因此,在初看代码时,看到train函数就下意识的觉得它代表训练模型的阶段,看到test函数也下意识的觉得它代表测试阶段。又看到判断条件是is_training,下意识的觉得is_training仅仅代表训练阶段。但是这可就大错特错了!这也提醒我们,根据代码 仔细了解命令行参数的重要性,可千万不要望文生义呀!

首先,回顾前文,is_training为1,代表即训练模型,又保存测试集的预测结果。而is_training为0,则代表从文件中调用已经训练好的模型,保存测试集结果。

而调用train函数 exp.train(setting)只能得到训练好的模型。调用test函数 exp.test(setting)才能得到测试集预测结果。

因此在is_training为1时,既有exp.train(setting),也有 exp.test(setting)
而在is_training为0时,只有 exp.test(setting)

for ii in range(args.itr):
    # setting record of experiments
    exp = Exp(args)  # set experiments
    setting = '{}_{}_{}_{}_ft{}_sl{}_ll{}_pl{}_dm{}_nh{}_el{}_dl{}_df{}_expand{}_dc{}_fc{}_eb{}_dt{}_{}_{}'.format(
        args.task_name,
        args.model_id,
        args.model,
        args.data,
        args.features,
        args.seq_len,
        args.label_len,
        args.pred_len,
        args.d_model,
        args.n_heads,
        args.e_layers,
        args.d_layers,
        args.d_ff,
        args.expand,
        args.d_conv,
        args.factor,
        args.embed,
        args.distil,
        args.des, ii)

    print('>>>>>>>start training : {}>>>>>>>>>>>>>>>>>>>>>>>>>>'.format(setting))
    exp.train(setting)

    print('>>>>>>>testing : {}<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<'.format(setting))
    exp.test(setting)
    torch.cuda.empty_cache()

结束语

到这里就基本完成对主程序run.py的解读啦~ 祝大家学习顺利~

### 时间序列分析中的常用 在编程和数据分析领域,处理时间序列数据通常依赖于特定的来简化复杂操作并提高效率。以下是几个广泛使用的 Python 和 R 中的时间序列: #### 1. **Python时间序列** - **Pandas**: Pandas 是个强大的工具包,用于数据操作和分析。它提供了 `DatetimeIndex` 和 `Period` 对象支持高效的时间序列索引和操作[^4]。 - **Statsmodels**: Statsmodels 提供了广泛的统计模型实现,其中包括 ARIMA (AutoRegressive Integrated Moving Average),这是时间序列预测的经典方法之[^5]。 - **Prophet**: Facebook 开发的个开源 Prophet,专为商业用途设计,能够自动检测节假日效应以及趋势变化,适合非技术背景的数据分析师使用[^6]。 ```python from statsmodels.tsa.arima.model import ARIMA import pandas as pd data = pd.read_csv('time_series_data.csv', parse_dates=['date_column']) model = ARIMA(data['value'], order=(5,1,0)) results = model.fit() print(results.summary()) ``` - **TensorFlow/Keras**: 如果涉及深度学习建模,则可以利用 TensorFlow 或 Keras 来构建循环神经网络(RNN)、长短时记忆网络(LSTM),这些对于捕捉长期依赖关系非常有效[^7]。 #### 2. **R 的时间序列** - **forecast package**: 这是个由 Rob Hyndman 维护的强大包,包含了多种经典的时间序列算法如 ETS、ARIMA 及其扩展版本 TBATS 等[^8]。 ```r library(forecast) ts_data <- ts(read.csv("time_series_data.csv")$value, frequency=12) fit_arima <- auto.arima(ts_data) summary(fit_arima) plot(forecast(fit_arima, h=10)) ``` - **zoo/xts packages**: zoo 和 xts 主要提供灵活的时间序列类定义和支持各种日期格式的操作功能[^9]。 --- ### 数据预处理注意事项 当应用上述任何之前,需注意对类别型变量进行适当编码。例如,在某些情况下可能需要采用 one-hot 编码或将分类特征转换成哑变量形式以便更好地融数值计算框架之中[^1]。 此外,如果涉及到加密保护敏感信息存储场景下可考虑调用 Windows 平台上的 Data Protection API (DPAPI)[^3] ,不过这更多属于安全范畴而非直接针对时间序列本身的技术讨论范围之内。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值