TimesNet处理UEA数据集,用在InceptionTime上

class UEAloader(Dataset):
    """
    Dataset class for datasets included in:
        Time Series Classification Archive (www.timeseriesclassification.com)
    Argument:
        limit_size: float in (0, 1) for debug
    Attributes:
        all_df: (num_samples * seq_len, num_columns) dataframe indexed by integer indices, with multiple rows corresponding to the same index (sample).
            Each row is a time step; Each column contains either metadata (e.g. timestamp) or a feature.
        feature_df: (num_samples * seq_len, feat_dim) dataframe; contains the subset of columns of `all_df` which correspond to selected features
        feature_names: names of columns contained in `feature_df` (same as feature_df.columns)
        all_IDs: (num_samples,) series of IDs contained in `all_df`/`feature_df` (same as all_df.index.unique() )
        labels_df: (num_samples, num_labels) pd.DataFrame of label(s) for each sample
        max_seq_len: maximum sequence (time series) length. If None, script argument `max_seq_len` will be used.
            (Moreover, script argument overrides this attribute)
    """

    def __init__(self, root_path, file_list=None, limit_size=None, flag=None):
        self.root_path = root_path
        self.all_df, self.labels_df = self.load_all(root_path, file_list=file_list, flag=flag)
        self.all_IDs = self.all_df.index.unique()  # all sample IDs (integer indices 0 ... num_samples-1)

        if limit_size is not None:
            if limit_size > 1:
                limit_size = int(limit_size)
            else:  # interpret as proportion if in (0, 1]
                limit_size = int(limit_size * len(self.all_IDs))
            self.all_IDs = self.all_IDs[:limit_size]
            self.all_df = self.all_df.loc[self.all_IDs]

        # use all features
        self.feature_names = self.all_df.columns
        self.feature_df = self.all_df

        # pre_process
        normalizer = Normalizer()
        self.feature_df = normalizer.normalize(self.feature_df)
        print(len(self.all_IDs))

    def load_all(self, root_path, file_list=None, flag=None):
        """
        Loads datasets from csv files contained in `root_path` into a dataframe, optionally choosing from `pattern`
        Args:
            root_path: directory containing all individual .csv files
            file_list: optionally, provide a list of file paths within `root_path` to consider.
                Otherwise, entire `root_path` contents will be used.
        Returns:
            all_df: a single (possibly concatenated) dataframe with all data corresponding to specified files
            labels_df: dataframe containing label(s) for each sample
        """
        # Select paths for training and evaluation
        if file_list is None:
            data_paths = glob.glob(os.path.join(root_path, '*'))  # list of all paths
        else:
            data_paths = [os.path.join(root_path, p) for p in file_list]
        if len(data_paths) == 0:
            raise Exception('No files found using: {}'.format(os.path.join(root_path, '*')))
        if flag is not None:
            data_paths = list(filter(lambda x: re.search(flag, x), data_paths))
        input_paths = [p for p in data_paths if os.path.isfile(p) and p.endswith('.ts')]
        if len(input_paths) == 0:
            raise Exception("No .ts files found using pattern: '{}'".format(pattern))

        all_df, labels_df = self.load_single(input_paths[0])  # a single file contains dataset

        return all_df, labels_df

    def load_single(self, filepath):
        df, labels = load_data.load_from_tsfile_to_dataframe(filepath, return_separate_X_and_y=True,
                                                             replace_missing_vals_with='NaN')
        labels = pd.Series(labels, dtype="category")
        self.class_names = labels.cat.categories
        labels_df = pd.DataFrame(labels.cat.codes,
                                 dtype=np.int8)  # int8-32 gives an error when using nn.CrossEntropyLoss

        lengths = df.applymap(
            lambda x: len(x)).values  # (num_samples, num_dimensions) array containing the length of each series

        horiz_diffs = np.abs(lengths - np.expand_dims(lengths[:, 0], -1))

        if np.sum(horiz_diffs) > 0:  # if any row (sample) has varying length across dimensions
            df = df.applymap(subsample)

        lengths = df.applymap(lambda x: len(x)).values
        vert_diffs = np.abs(lengths - np.expand_dims(lengths[0, :], 0))
        if np.sum(vert_diffs) > 0:  # if any column (dimension) has varying length across samples
            self.max_seq_len = int(np.max(lengths[:, 0]))
        else:
            self.max_seq_len = lengths[0, 0]

        # First create a (seq_len, feat_dim) dataframe for each sample, indexed by a single integer ("ID" of the sample)
        # Then concatenate into a (num_samples * seq_len, feat_dim) dataframe, with multiple rows corresponding to the
        # sample index (i.e. the same scheme as all datasets in this project)

        df = pd.concat((pd.DataFrame({col: df.loc[row, col] for col in df.columns}).reset_index(drop=True).set_index(
            pd.Series(lengths[row, 0] * [row])) for row in range(df.shape[0])), axis=0)

        # Replace NaN values
        grp = df.groupby(by=df.index)
        df = grp.transform(interpolate_missing)

        return df, labels_df

    def instance_norm(self, case):
        if self.root_path.count('EthanolConcentration') > 0:  # special process for numerical stability
            mean = case.mean(0, keepdim=True)
            case = case - mean
            stdev = torch.sqrt(torch.var(case, dim=1, keepdim=True, unbiased=False) + 1e-5)
            case /= stdev
            return case
        else:
            return case

    def __getitem__(self, ind):
        return self.instance_norm(torch.from_numpy(self.feature_df.loc[self.all_IDs[ind]].values)), \
               torch.from_numpy(self.labels_df.loc[self.all_IDs[ind]].values)

    def __len__(self):
        return len(self.all_IDs)

比较关键的几个函数:

  1. 从tsfile文件中读取dataframe:
from sktime.utils import load_data
load_data.load_from_tsfile_to_dataframe
  1. 将数据集中categories变为分类标签:
labels = pd.Series(labels, dtype="category")
        self.class_names = labels.cat.categories
        labels_df = pd.DataFrame(labels.cat.codes,
                                 dtype=np.int8)

pd.Series.cat.codes 是 Pandas 库中 Series 对象的一个方法,用于将分类变量转换为分类编码(Category Codes)。分类变量是指具有有限可能取值的变量,例如性别、职业等。在机器学习中,经常需要将分类变量转换为数值编码,以便于机器学习算法的处理和分析。
pd.Series.cat.codes 方法可以将 Series 对象中的分类变量转换为整数编码。该方法返回一个包含整数编码的 Series 对象,其中每个不同的分类变量都被映射到一个唯一的整数值。例如,如果一个 Series 对象包含了不同的颜色分类变量,那么 pd.Series.cat.codes 方法将会将这些分类变量映射为 0、1、2、3 等整数编码,以便于进一步的处理和分析。
下面是一个示例代码:
python
Copy code
import pandas as pd
# 创建一个包含分类变量的Series对象
colors = pd.Series([“red”, “blue”, “green”, “blue”, “green”, “red”])
# 将分类变量转换为分类编码
color_codes = colors.astype(‘category’).cat.codes
# 输出转换后的编码
print(color_codes)
在上述示例中,colors.astype(‘category’).cat.codes 将包含颜色分类变量的 Series 对象转换为分类编码。最终输出的编码为:0 1 2 1 2 0。其中,“red” 被映射为 0,“blue” 被映射为 1,“green” 被映射为 2。

  1. 统一数据长度:
    lengths = df.applymap(
        lambda x: len(x)).values  # (num_samples, num_dimensions) array containing the length of each series

    horiz_diffs = np.abs(lengths - np.expand_dims(lengths[:, 0], -1))
   	if np.sum(horiz_diffs) > 0:  # if any row (sample) has varying length across dimensions
        df = df.applymap(subsample)

def subsample(y, limit=256, factor=2):
    """
    If a given Series is longer than `limit`, returns subsampled sequence by the specified integer factor
    """
    if len(y) > limit:
        return y[::factor].reset_index(drop=True)
    return y

在这段代码中,df.applymap() 被用于对数据框中的每个元素应用一个 lambda 函数,该函数用于计算字符串的长度。具体地,len(x) 计算字符串 x 的长度,而 df.applymap(lambda x: len(x)) 对数据框中的每个字符串元素都应用了这个函数,生成了一个包含每个字符串长度的数据框。
接着,.values 被用于将该数据框转换为一个 Numpy 数组,其中每个元素都是一个字符串的长度。这个 Numpy 数组的形状为 (num_samples, num_dimensions),其中 num_samples 是数据集中的样本数,num_dimensions 是每个时间序列的维度或特征数。
最后,np.expand_dims(lengths[:, 0], -1) 被用于创建一个形状为 (num_samples, 1) 的数组,其中每个元素都是第一列时间序列的长度。这个数组将用于计算每个时间序列长度与第一个时间序列长度的差异。np.abs(lengths - np.expand_dims(lengths[:, 0], -1)) 计算了每个时间序列长度与第一个时间序列长度的绝对差异。这个数组的形状与 lengths 相同,即 (num_samples, num_dimensions)。

在这段代码中,y 是一个 Pandas Series 对象,表示一个时间序列的标签或类别。[::factor] 通过 Python 的切片操作,将 y 中的每隔 factor 个元素保留一个元素,从而完成下采样。例如,如果 factor 为2,则将 y 中的每隔一个元素保留一个元素,即将 y 的采样率减半。
.reset_index(drop=True) 将下采样后的 Series 对象的索引重新排序,并将其从 0 开始标号。drop=True 表示放弃原来的索引,使用新的索引。
最终,该函数返回下采样后的 Series 对象,其中包含了每隔 factor 个元素保留一个元素的标签或类别。

4.统一维度:

    lengths = df.applymap(lambda x: len(x)).values
    vert_diffs = np.abs(lengths - np.expand_dims(lengths[0, :], 0))

最后,使用 np.abs(lengths - np.expand_dims(lengths[0, :], 0)) 计算每个时间序列在不同维度上的长度与第一个时间序列在不同维度上的长度的绝对差异。np.expand_dims(lengths[0, :], 0) 用于将第一个时间序列的长度扩展为 (1, num_dimensions) 的形状,以便于与每个时间序列的长度进行差异计算。计算完成后,vert_diffs 是一个 (num_samples, num_dimensions) 形状的 Numpy 数组,其中每个元素都表示对应时间序列在该维度上与第一个时间序列在该维度上长度的绝对差异。

  1. 合并重组:
    # First create a (seq_len, feat_dim) dataframe for each sample, indexed by a single integer ("ID" of the sample)
    # Then concatenate into a (num_samples * seq_len, feat_dim) dataframe, with multiple rows corresponding to the
    # sample index (i.e. the same scheme as all datasets in this project)

    df = pd.concat((pd.DataFrame({col: df.loc[row, col] for col in df.columns}).reset_index(drop=True).set_index(
        pd.Series(lengths[row, 0] * [row])) for row in range(df.shape[0])), axis=0)

这段代码首先使用列表推导式 (pd.DataFrame({col: df.loc[row, col] for col in df.columns}).reset_index(drop=True).set_index(pd.Series(lengths[row, 0] * [row])) for row in range(df.shape[0])),对数据集中的每个时间序列进行处理,生成一个 Pandas DataFrame 对象。具体地,这个列表推导式首先将一个样本的所有时间序列数据组成一个 Pandas DataFrame 对象,其中每行表示该时间序列在该样本中的一个时间点的特征向量,每列表示特征的维度。生成的 DataFrame 对象的形状为 (seq_len, feat_dim),其中 seq_len 表示该时间序列的长度(即时间点数),feat_dim 表示特征的维度。
接着,通过 reset_index(drop=True) 对该 DataFrame 对象的索引进行重置,即使用从 0 开始的连续整数作为新的索引,并将其保存到新的 DataFrame 对象中。
然后,通过 set_index(pd.Series(lengths[row, 0] * [row])) 对新的 DataFrame 对象的索引进行设置,将新的索引设置为由当前样本的 ID 和该时间序列在该样本中的时间点组成的元组。具体地,pd.Series(lengths[row, 0] * [row]) 生成一个形状为 (seq_len,) 的 Pandas Series 对象,其中每个元素都等于当前样本的 ID。这个 Series 对象被用作新的索引,因此新的 DataFrame 对象的每个行都被标记为一个元组,包含了当前样本的 ID 和该时间序列在该样本中的时间点。最终,这个 DataFrame 对象的形状为 (seq_len, feat_dim + 1),其中第一列是样本 ID,后面的列是该时间序列在该样本中的每个时间点的特征向量。
最后,通过 pd.concat() 将所有样本的时间序列数据拼接在一起,生成一个形状为 (num_samples * seq_len, feat_dim + 1) 的 Pandas DataFrame 对象,其中每行表示一个时间序列在一个样本中的一个时间点的特征向量。这个 DataFrame 对象中的第一列是样本 ID,后面的列是时间序列在该样本中的每个时间点的特征向量。

做的最后一步是将所有样本的时间序列数据拼接在一起,生成一个 Pandas DataFrame 对象,其中每行表示一个时间序列在一个样本中的一个时间点的特征向量。这个 DataFrame 对象的形状为 (num_samples * seq_len, feat_dim + 1),其中 num_samples 是数据集中的样本数,seq_len 是所有样本中最长的时间序列的长度,feat_dim 是每个时间序列的特征数或维度。

这个操作的目的是将原始数据集转换为一个标准的格式,以便于进行后续的处理和分析。在转换后的数据集中,每行表示一个时间序列在一个样本中的一个时间点的特征向量,方便应用机器学习算法进行分类、聚类或其他任务。同时,由于数据集已经被整理为一个二维的表格形式,可以更方便地应用 Pandas 和其他 Python 库进行数据预处理和可视化,以及进一步的数据分析和建模。

  1. batch:
def collate_fn(data, max_len=None):
    """Build mini-batch tensors from a list of (X, mask) tuples. Mask input. Create
    Args:
        data: len(batch_size) list of tuples (X, y).
            - X: torch tensor of shape (seq_length, feat_dim); variable seq_length.
            - y: torch tensor of shape (num_labels,) : class indices or numerical targets
                (for classification or regression, respectively). num_labels > 1 for multi-task models
        max_len: global fixed sequence length. Used for architectures requiring fixed length input,
            where the batch length cannot vary dynamically. Longer sequences are clipped, shorter are padded with 0s
    Returns:
        X: (batch_size, padded_length, feat_dim) torch tensor of masked features (input)
        targets: (batch_size, padded_length, feat_dim) torch tensor of unmasked features (output)
        target_masks: (batch_size, padded_length, feat_dim) boolean torch tensor
            0 indicates masked values to be predicted, 1 indicates unaffected/"active" feature values
        padding_masks: (batch_size, padded_length) boolean tensor, 1 means keep vector at this position, 0 means padding
    """

    batch_size = len(data)
    features, labels = zip(*data)

    # Stack and pad features and masks (convert 2D to 3D tensors, i.e. add batch dimension)
    lengths = [X.shape[0] for X in features]  # original sequence length for each time series
    if max_len is None:
        max_len = max(lengths)
    X = torch.zeros(batch_size, max_len, features[0].shape[-1])  # (batch_size, padded_length, feat_dim)
    for i in range(batch_size):
        end = min(lengths[i], max_len)
        X[i, :end, :] = features[i][:end, :]

    targets = torch.stack(labels, dim=0)  # (batch_size, num_labels)

    padding_masks = padding_mask(torch.tensor(lengths, dtype=torch.int16),
                                 max_len=max_len)  # (batch_size, padded_length) boolean tensor, "1" means keep

    return X, targets, padding_masks

暂时还没搞懂:

这段代码实现了一个 PyTorch 的 collate_fn 函数,用于将一个 mini-batch 的数据进行整合和处理,以便于输入到神经网络中进行训练。该函数的输入是一个数据列表,其中每个元素都是一个 (X, y) 元组,表示一个样本的时间序列数据和标签。X 是一个 PyTorch 张量,表示时间序列数据,形状为 (seq_length, feat_dim),其中 seq_length 是时间序列的长度,feat_dim 是每个时间点的特征数或维度;y 是一个 PyTorch 张量,表示标签或目标,形状为 (num_labels,),其中 num_labels 是标签或目标的数量,通常为 1。
该函数的输出包括 4 个张量:X、targets、target_masks 和 padding_masks。其中,X 是一个形状为 (batch_size, padded_length, feat_dim) 的 PyTorch 张量,表示时间序列数据的 mini-batch。padded_length 是所有样本中最长的时间序列的长度,即对于所有时间序列数据,短的序列用零填充到与最长序列等长。targets 是一个形状为 (batch_size, num_labels) 的 PyTorch 张量,表示标签或目标的 mini-batch。target_masks 是一个形状为 (batch_size, padded_length, feat_dim) 的 PyTorch 张量,表示标签或目标的掩码,其中 0 表示填充值,1 表示实际值。padding_masks 是一个形状为 (batch_size, padded_length) 的 PyTorch 张量,表示时间序列数据的掩码,其中 0 表示填充值,1 表示实际值。
具体实现时,函数首先从输入的数据列表中分离出所有的时间序列数据和标签,分别存储在 features 和 labels 变量中。然后,计算每个时间序列数据的原始长度,并将其存储在 lengths 列表中。如果没有指定 max_len 参数,则将 max_len 设置为所有时间序列数据的最大长度。接着,使用 PyTorch 的 torch.zeros() 创建一个全零张量 X,形状为 (batch_size, max_len, feat_dim)。对于每个样本,将其时间序列数据的前 max_len 个时间点复制到 X 对应位置,并将不足 max_len 个时间点的序列用零填充。最后,使用 PyTorch 的 torch.stack() 将所有标签组成一个 mini-batch 张量 targets。同时,使用 padding_mask() 函数生成一个时间序列数据的掩码张量 padding_masks,其中的 0 表示填充值,1 表示实际值。

应用InceptionTime

在应用在InceptionTime上时,发现不对:
InceptionTime使用UCR时,直接:

def readucr(filename, delimiter=','):
    data = np.loadtxt(filename, delimiter=delimiter)
    Y = data[:, 0]
    X = data[:, 1:]
    print(X)
    print(Y)
    return X, Y

出来的结果是:

[[1.9305 1.9125 1.891 … 1.9099 1.9233 1.9301]
[1.9178 1.9029 1.8877 … 1.8925 1.9101 1.9162]
[1.887 1.8693 1.8393 … 1.8794 1.887 1.8896]

[1.8765 1.8655 1.8442 … 1.8572 1.869 1.8754]
[1.8908 1.8816 1.8569 … 1.8389 1.8487 1.8721]
[1.8465 1.8114 1.7723 … 1.8941 1.8858 1.8642]]

在读取UEA时,需要使用:

#########读取UEA ts#########
def interpolate_missing(y):
    """
    Replaces NaN values in pd.Series `y` using linear interpolation
    """
    if y.isna().any():
        y = y.interpolate(method='linear', limit_direction='both')
    return y

def subsample(y, limit=256, factor=2):
    """
    If a given Series is longer than `limit`, returns subsampled sequence by the specified integer factor
    """
    if len(y) > limit:
        return y[::factor].reset_index(drop=True)
    return y


def readuea(filename, delimiter=','):
    df, labels = load_data.load_from_tsfile_to_dataframe(filename, return_separate_X_and_y=True,
                                                             replace_missing_vals_with='NaN')
    
    labels = pd.Series(labels, dtype="category")
    labels_df = labels.cat.codes
    print('type',type(labels_df))
    

    lengths = df.applymap(
        lambda x: len(x)).values  # (num_samples, num_dimensions) array containing the length of each series

    horiz_diffs = np.abs(lengths - np.expand_dims(lengths[:, 0], -1))

    if np.sum(horiz_diffs) > 0:  # if any row (sample) has varying length across dimensions
        df = df.applymap(subsample)

    # lengths = df.applymap(lambda x: len(x)).values
    # vert_diffs = np.abs(lengths - np.expand_dims(lengths[0, :], 0))

    # # First create a (seq_len, feat_dim) dataframe for each sample, indexed by a single integer ("ID" of the sample)
    # # Then concatenate into a (num_samples * seq_len, feat_dim) dataframe, with multiple rows corresponding to the
    # # sample index (i.e. the same scheme as all datasets in this project)

    # df = pd.concat((pd.DataFrame({col: df.loc[row, col] for col in df.columns}).reset_index(drop=True).set_index(
    #     pd.Series(lengths[row, 0] * [row])) for row in range(df.shape[0])), axis=0)

    # Replace NaN values
    grp = df.groupby(by=df.index)
    df = grp.transform(interpolate_missing)

    # df转numpy
    df = df.to_numpy()
    labels_df = labels_df.to_numpy()
    print(df)
    print(labels_df)

    return df, labels_df

结果是:

ength: 1152, dtype: float64 0 54.31
1 56.03
2 56.66
3 54.84
4 52.12

1147 64.03
1148 64.09
1149 64.16
1150 65.03
1151 66.16
Length: 1152, dtype: float64
0 45.78
1 42.78
2 41.94
3 42.81
4 43.47

1147 68.50
1148 68.75
1149 68.06
1150 66.53
1151 64.88
Length: 1152, dtype: float64 … 0 42.22
1 42.12
2 42.34
3 41.28
4 39.81

1147 63.94
1148 62.66
1149 61.16
1150 59.88
1151 58.56
Length: 1152, dtype: float64
0 43.25
1 43.06
2 43.84
3 45.34
4 46.12

1147 54.00
1148 52.94
1149 52.06
1150 51.88
1151 52.34
Length: 1152, dtype: float64 0 43.56
1 43.75
2 42.50
3 40.78
4 40.41

1147 57.81
1148 58.81
1149 60.19
1150 61.34
1151 62.00
Length: 1152, dtype: float64]

[0 22.50
1 21.84
2 20.53
3 17.06
4 14.56

目前我是打算把pandas的结果转换成numpy。

成功方法:

参考这个代码的数据处理方法:https://github.com/donalee/DTW-Pool/blob/c9a6c1162fa8a5a264c7dbec19c7ebf12a86b522/timeseries.py#L16

def readuea(filename, delimiter=','):
    data, labels = load_data.load_from_tsfile_to_dataframe(filename, return_separate_X_and_y=True,
                                                             replace_missing_vals_with='NaN')

    # Replace NaN values
    grp = data.groupby(by=data.index)
    data = grp.transform(interpolate_missing)

    # df转numpy
    data = np.array([np.array([data.values[iidx, vidx].to_numpy(dtype=np.float) \
                                for vidx in range(data.values.shape[1])]) \
                                for iidx in range(data.values.shape[0])]) 
    
    label2idx = {label: idx for idx, label in enumerate(np.unique(labels))}
    labels = np.array([label2idx[label] for label in labels])

    print(data)
    print(labels)

    return data, labels

也是将pd彻底转成numpy的方法,以后可能会经常使用(这个只是针对uea)

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
TimesNet是一个基于深度学习的图像分类模型,可以用来对图像进行分类。为了对自己的数据集进行分类,你需要先准备好数据集,并按照一定的方式进行划分,例如将数据集分成训练集、验证集和测试集。然后,你可以使用TimesNet模型来对数据集进行训练和评估,以得到一个可以对新图像进行分类的模型。 具体来说,以下是一些可能的步骤: 1. 准备数据集:首先,你需要准备好你的数据集。这可以包括一些标注好的图像,每个图像都有一个标签,表示它所属的类别。你可以使用一些图像处理工具来调整图像的大小、裁剪或旋转,以便它们可以适合模型的输入要求。 2. 划分数据集:一般来说,你需要将数据集划分成训练集、验证集和测试集。训练集用于模型的训练,验证集用于模型的评估和调优,而测试集则用于最终的模型测试。 3. 定义模型:接下来,你需要定义一个基于TimesNet的模型。这可以包括一些卷积层、池化层和全连接层,以及一些激活函数和正则化技术,以帮助模型更好地学习数据集中的特征。 4. 训练模型:使用训练集对模型进行训练,以便它可以学习如何对图像进行分类。你可以使用一些训练技巧,例如随机梯度下降、学习率调度和批量归一化,以帮助模型更好地收敛。 5. 评估模型:使用验证集对模型进行评估,以便你可以了解模型的性能和泛化能力。你可以计算一些指标,例如准确率、精确率、召回率和F1分数,以帮助你了解模型的表现。 6. 调优模型:根据评估结果,你可以对模型进行调优,例如调整模型结构、超参数或正则化技术,以提高模型性能。 7. 测试模型:最后,你可以将测试集输入到模型中,以便评估模型在新数据上的表现。你可以计算一些指标,例如准确率或混淆矩阵,以帮助你了解模型的表现和错误分类情况。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值