【天池智慧海洋建设】Topline源码——特征工程学习(总结)

【天池智慧海洋建设】Topline源码——特征工程学习


DataWhale智慧海洋学习完整链接地址
https://github.com/datawhalechina/team-learning-data-mining/blob/master/wisdomOcean

前言

topline代码开源学习,仅关注特征工程部分,具体为输入,输出,作用、原理及部分个人理解。

此部分为智慧海洋建设竞赛的特征工程模块,通过特征工程,可以最大限度地从原始数据中提取特征以供算法和模型使用。通俗而言,就是通过X,创造新的X’以获得更好的训练、预测效果。

“数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已”——机器学习界;

类似的,吴恩达曾说过:“特征工程不仅操作困难、耗时,而且需要专业领域知识。应用机器学习基本上就是特征工程。”

赛题:智慧海洋建设

特征工程的目的:

  • 特征工程是一个包含内容很多的主题,也被认为是成功应用机器学习的一个很重要的环节。如何充分利用数据进行预测建模就是特征工程要解决的问题! “实际上,所有机器学习算法的成功取决于如何呈现数据。” “特征工程是一个看起来不值得在任何论文或者书籍中被探讨的一个主题。但是他却对机器学习的成功与否起着至关重要的作用。机器学习算法很多都是由于建立一个学习器能够理解的工程化特征而获得成功的。”——ScottLocklin,in “Neglected machine learning ideas”

  • 数据中的特征对预测的模型和获得的结果有着直接的影响。可以这样认为,特征选择和准备越好,获得的结果也就越好。这是正确的,但也存在误导。预测的结果其实取决于许多相关的属性:比如说能获得的数据、准备好的特征以及模型的选择。

  • 上分!😃 毫不夸张的说在基本的数据挖掘类比赛中,特征工程就是你和topline的距离。

项目地址:https://github.com/datawhalechina/team-learning-data-mining/tree/master/wisdomOcean

比赛地址:https://tianchi.aliyun.com/competition/entrance/231768/introduction?spm=5176.12281957.1004.8.4ac63eafE1rwsY

学习来源

  • 1 团队名称:Pursuing the Past Youth
    链接:
    https://github.com/juzstu/TianChi_HaiYang

  • 2 团队名称:liu123的航空母舰队
    链接:
    https://github.com/MichaelYin1994/tianchi-trajectory-data-mining

  • 3 团队名称:天才海神号
    链接:
    https://github.com/fengdu78/tianchi_haiyang?spm=5176.12282029.0.0.5b97301792pLch

  • 4 团队名称:大白
    链接:
    https://github.com/Ai-Light/2020-zhihuihaiyang

  • 5 团队名称:抗毒救灾
    链接:
    https://github.com/wudejian789/2020DCIC_A_Rank7_B_Rank12

  • 6 团队名称:蜗牛坐车里团队
    链接:
    https://tianchi.aliyun.com/notebook-ai/detail?postId=114808

  • 7 团队名称:用欧气驱散疫情
    链接:
    https://github.com/tudoulei/2020-Digital-China-Innovation-Competition

部分解释

  • 【天池智慧海洋建设】Topline源码——特征工程学习(大白):
    https://blog.csdn.net/qq_44574333/article/details/115188086

  • 【天池智慧海洋建设】Topline源码——特征工程学习(Pursuing the Past Youth):
    https://blog.csdn.net/qq_44574333/article/details/112547081

  • 【天池智慧海洋建设】Topline源码——特征工程学习(天才海神号):
    https://blog.csdn.net/qq_44574333/article/details/115185634

  • 【天池智慧海洋建设】Topline源码——特征工程学习(liu123的航空母舰队):
    https://blog.csdn.net/qq_44574333/article/details/115091764

学习目标

  1. 学习特征工程的基本概念

  2. 学习topline代码的特征工程构造方法,实现构建有意义的特征工程

内容介绍

  1. 特征工程概述

  2. 赛题特征工程

    • 业务特征,根据先验知识进行专业性的特征构建
  3. 分箱特征

    • v、x、y的分箱特征
    • x、y分箱后并构造区域
  4. DataFramte特征

    • count计数值
    • shift偏移量
    • 统计特征
  5. Embedding特征

    • Word2vec构造词向量
    • NMF提取文本的主题分布
  6. 总结

I 特征工程概述

特征工程大体可分为3部分,特征构建、特征提取和特征选择。

  • 特征构建

“从数学的角度讲,特征工程就是将原始数据空间变换到新的特征空间,或者说是换一种数据的表达方式,在新的特征空间中,模型能够更好地学习数据中的规律。因此,特征抽取就是对原始数据进行变换的过程。大多数模型和算法都要求输入是维度相同的实向量,因此特征工程首先需要将原始数据转化为实向量。”
其主要包含内容有:

+ 探索性数据分析
+ 数值特征
+ 类别特征
+ 时间特征
+ 文本特征
  • 特征提取和特征选择

特征提取和特征选择概念上来说很像,其实特征提取指的是通过特征转换得到一组具有明显物理或统计意义的特征。而特征选择就是在特征集里直接挑出具有明显物理或统计意义的特征。

与特征提取是从原始数据中构造新的特征不同,特征选择是从这些特征集合中选出一个子集。特征选择对于机器学习应用来说非常重要。特征选择也称为属性选择或变量选择,是指为了构建模型而选择相关特征子集的过程。特征选择的目的有如下三个。

+ 简化模型,使模型更易于研究人员和用户理解。可解释性不仅让我们对模型效果的稳定性有更多的把握,而且也能为业务运营等工作提供指引和决策支持。

+  改善性能。特征选择的另一个作用是节省存储和计算开销。

+  改善通用性、降低过拟合风险。特征的增多会大大增加模型的搜索空间,大多数模型所需要的训练样本数目随着特征数量的增加而显著增加,特征的增加虽然能更好地拟合训练数据,但也可能增加方差。

I 数据部分

在这里插入图片描述

原始数据描述:

渔船ID:渔船的唯一识别,结果文件以此ID为标示
x: 渔船在平面坐标系的x轴坐标
y: 渔船在平面坐标系的y轴坐标
速度:渔船当前时刻航速,单位节
方向:渔船当前时刻航首向,单位度
time:数据上报时刻,单位月日 时:分
type:渔船label,三种作业类型(围网、刺网、拖网)

II 特征工程总结

2.1 赛题特征工程

  • 构造各点的(x、y)坐标与特定点(6165599,5202660)的距离
    在这里插入图片描述
  • 对时间,小时进行白天、黑天进行划分,5-20为白天1,其余为黑天0
    在这里插入图片描述
  • 根据月份划分季度
    在这里插入图片描述
  • 动态速度,速度变化,角度变化,xy相似性等特征
    在这里插入图片描述

2.2 分箱特征

  • v、x、y的分箱特征
    在这里插入图片描述
  • x、y进行分箱并构造区域
def traj_to_bin(traj=None, x_min=12031967.16239096, x_max=14226964.881853,
                y_min=1623579.449434373, y_max=4689471.1780792,
                row_bins=4380, col_bins=3136):

    # Establish bins on x direction and y direction
    x_bins = np.linspace(x_min, x_max, endpoint=True, num=col_bins + 1)
    y_bins = np.linspace(y_min, y_max, endpoint=True, num=row_bins + 1)

    # Determine each x coordinate belong to which bin
    traj.sort_values(by='x', inplace=True)
    x_res = np.zeros((len(traj), ))
    j = 0
    for i in range(1, col_bins + 1):
        low, high = x_bins[i-1], x_bins[i]
        while( j < len(traj)):
            # low - 0.001 for numeric stable.
            if (traj["x"].iloc[j] <= high) & (traj["x"].iloc[j] > low - 0.001):
                x_res[j] = i
                j += 1
            else:
                break
    traj["x_grid"] = x_res
    traj["x_grid"] = traj["x_grid"].astype(int)
    traj["x_grid"] = traj["x_grid"].apply(str)

    # Determine each y coordinate belong to which bin
    traj.sort_values(by='y', inplace=True)
    y_res = np.zeros((len(traj), ))
    j = 0
    for i in range(1, row_bins + 1):
        low, high = y_bins[i-1], y_bins[i]
        while( j < len(traj)):
            # low - 0.001 for numeric stable.
            if (traj["y"].iloc[j] <= high) & (traj["y"].iloc[j] > low - 0.001):
                y_res[j] = i
                j += 1
            else:
                break
    traj["y_grid"] = y_res
    traj["y_grid"] = traj["y_grid"].astype(int)
    traj["y_grid"] = traj["y_grid"].apply(str)

    # Determine which bin each coordinate belongs to.
    traj["no_bin"] = [i + "_" + j for i, j in zip(
        traj["x_grid"].values.tolist(), traj["y_grid"].values.tolist())]
    traj.sort_values(by='time', inplace=True)
    return traj

bin_size = 800
col_bins = int((14226964.881853 - 12031967.16239096) / bin_size)
row_bins = int((4689471.1780792 - 1623579.449434373) / bin_size)

基础特征大多是由pandas DataFrame的一些方法得到的,在此没有基础的小伙伴推荐先系统学习一下DataWhale的精品良心pandas学习:
https://datawhalechina.github.io/joyful-pandas/

统计类特征构造,各个topline必备。其构造方法有许多,首先一定要掌握基本的

2.3 DataFrame特征

分组统计方法

对于df.groupby的基本用法:
  df.groupby('主键')[’所选要处理的列名‘].agg({'新定义的列名':'所用方法'})
其中,各个名词含义为
‘主键’:以哪一列为分组的条件进行分组统计,比如想要将ID列中相同id的进行同组处理,这样就相当于将该df按照ID列划分,每一组进行后面的处理
‘要处理的列名’:想要进行统计的特征列(如年龄列),如在以ID列分组后,同一id下代表同一个它(他她)的年龄进行统计处理
’新定义的列名’:在针对特征列进行处理后,会得到新的一列,可给其进行命名
’所用方法‘:可自定义函数,也可以使用Dataframe内置统计函数
在这里插入图片描述
这里引用DataWhale的joyful pandas的示例进行更清晰的解释

  • count计数
    在这里插入图片描述
  • shift偏移量特征
    在这里插入图片描述
  • 统计特征
补充:

分组统计特征agg的使用非常重要,在此进行代码示例,详细请参考:
http://joyfulpandas.datawhale.club/Content/ch4.html

- 请注意{}[]的使用

分组标准格式:

df.groupby(分组依据)[数据来源].使用操作

先分组,得到

gb = df.groupby(['School', 'Grade'])

- 【a】使用多个函数

gb.agg(['具体方法(如内置函数)'])

如gb.agg(['sum'])


- 【b】对特定的列使用特定的聚合函数

gb.agg({'指定列':'具体方法'})

如gb.agg({'Height':['mean','max'], 'Weight':'count'})

- 【c】使用自定义函数

gb.agg(函数名或匿名函数)

如gb.agg(lambda x: x.mean()-x.min())

- 【d】聚合结果重命名

gb.agg([
    ('重命名的名字',具体方法(如内置函数、自定义函数))
])

如gb.agg([('range', lambda x: x.max()-x.min()), ('my_sum', 'sum')])

另外需要注意,使用对一个或者多个列使用单个聚合的时候,重命名需要加方括号,否则就不知道是新的名字还是手误输错的内置函数字符串:

- 下述代码主要使用了

一种是df.groupby('id').agg{'列名':'方法'},另一种是df.groupby('id')['列名'].agg(字典)
pre_cols = df.columns

def start(x):
    try:
        return x[0]
    except:
        return None

def end(x):
    try:
        return x[-1]
    except:
        return None


def mode(x):
    try:
        return pd.Series(x).value_counts().index[0]
    except:
        return None

for f in ['dist_move_prev_bin', 'v_bin']:
    # 上一时刻类别 速度类别映射处理
    df[f + '_sen'] = df['id'].map(df.groupby('id')[f].agg(lambda x: ','.join(x.astype(str))))
    
    # 一系列基本统计量特征 每列执行相应的操作
g = df.groupby('id').agg({
    'id': ['count'], 'x_bin1': [mode], 'y_bin1': [mode], 'x_bin2': [mode], 'y_bin2': [mode], 'x_y_bin1': [mode],
    'x': ['mean', 'max', 'min', 'std', np.ptp, start, end],
    'y': ['mean', 'max', 'min', 'std', np.ptp, start, end],
    'v': ['mean', 'max', 'min', 'std', np.ptp], 'dir': ['mean'],
    'x_bin1_count': ['mean'], 'y_bin1_count': ['mean', 'max', 'min'],
    'x_bin2_count': ['mean', 'max', 'min'], 'y_bin2_count': ['mean', 'max', 'min'],
    'x_bin1_y_bin1_count': ['mean', 'max', 'min'],
    'dist_move_prev': ['mean', 'max', 'std', 'min', 'sum'],
    'x_y_min': ['mean', 'min'], 'y_x_min': ['mean', 'min'],
    'x_y_max': ['mean', 'min'], 'y_x_max': ['mean', 'min'],
}).reset_index()
g.columns = ['_'.join(col).strip() for col in g.columns] #提取列名
g.rename(columns={'id_': 'id'}, inplace=True) #重命名id_
cols = [f for f in g.keys() if f != 'id'] #特征列名提取
df = df.merge(g,on='id',how='left')

new_cols = [i for i in df.columns if i not in pre_cols]
df[new_cols].head()

Embedding特征

- Question!

为什么在数据挖掘类比赛中,我们需要word2vec或NMF(方法有很多,但这两种常用)来构造 “词嵌入特征”?

答: 上分!

确实,上分是现象,但背后却是对整体数据的考虑,上述的统计特征、业务特征等也都是考虑了数据的整体性,但是却难免忽略了数据间的关系。举个例子,对于所有人的年龄特征,如果仅做一些统计特征如平均值、最值,业务特征如标准体重=体重/年龄等,这些都是人为理解的。那将这些特征想象成一个个词,并将所有数据(或同一组数据)的这些词组合当成一篇文章来考虑,是不是就可以得到一些额外的规律,即特征。

- 简介

所谓word embedding就是把一个词用编码的方式表示以便于feed到网络中。Word Embedding有的时候也被称作为分布式语义模型或向量空间模型等,所以从名字和其转换的方式我们就可以明白, Word Embedding技术可以将相同类型的词归到一起,例如苹果,芒果香蕉等,在投影之后的向量空间距离就会更近,而书本,房子这些则会与苹果这些词的距离相对较远。

- 使用场景

目前为止,Word Embedding可以用到特征生成,文件聚类,文本分类和自然语言处理等任务,例如:

计算相似的词:Word Embedding可以被用来寻找与某个词相近的词。

构建一群相关的词:对不同的词进行聚类,将相关的词聚集到一起;

用于文本分类的特征:在文本分类问题中,因为词没法直接用于机器学习模型的训练,所以我们将词先投影到向量空间,这样之后便可以基于这些向量进行机器学习模型的训练;

用于文件的聚类

上面列举的是文本相关任务,当然目前词嵌入模型已经被扩展到方方面面。典型的,例如:

在微博上面,每个人都用一个词来表示,对每个人构建Embedding,然后计算人之间的相关性,得到关系最为相近的人;

在推荐问题里面,依据每个用户的购买的商品记录,对每个商品进行Embedding,就可以计算商品之间的相关性,并进行推荐;

在此次天池的航海问题中,对相同经纬度上不同的船进行Embedding,就可以得到每个船只的向量,就可以得到经常在某些区域工作的船只;

可以说,词嵌入为寻找物体之间相关性带来了巨大的帮助。现在基本每个数据竞赛都会见到Embedding技术。让我们来看看用的最多的Word2Vec模型。

-  Word2Vec在做什么?

Word2vec在向量空间中对词进行表示, 或者说词以向量的形式表示,在词向量空间中:相似含义的单词一起出现,而不同的单词则位于很远的地方。这也被称为语义关系。

神经网络不理解文本,而只理解数字。词嵌入提供了一种将文本转换为数字向量的方法。

Word2vec就是在重建词的语言上下文。那什么是语言上下文?在一般的生活情景中,当我们通过说话或写作来交流,其他人会试图找出句子的目的。例如,“印度的温度是多少”,这里的上下文是用户想知道“印度的温度”即上下文。

简而言之,句子的主要目标是语境。围绕口头或书面语言的单词或句子(披露)有助于确定上下文的意义。Word2vec通过上下文学习单词的矢量表示。

- 参考文献

[NLP] 秒懂词向量Word2vec的本质:https://zhuanlan.zhihu.com/p/26306795
def traj_cbow_embedding(traj_data_corpus=None, embedding_size=70,
                        iters=40, min_count=3, window_size=25,
                        seed=9012, num_runs=5, word_feat="no_bin"):
    """CBOW embedding for trajectory data."""
    boat_id = traj_data_corpus['id'].unique()
    sentences, embedding_df_list, embedding_model_list = [], [], []
    for i in boat_id:
        traj = traj_data_corpus[traj_data_corpus['id']==i]
        sentences.append(traj[word_feat].values.tolist())

    print("\n@Start CBOW word embedding at {}".format(datetime.now()))
    print("-------------------------------------------")
    for i in tqdm(range(num_runs)):
        model = Word2Vec(sentences, size=embedding_size,
                                  min_count=min_count,
                                  workers=mp.cpu_count(),
                                  window=window_size,
                                  seed=seed, iter=iters, sg=0)

        # Sentance vector
        embedding_vec = []
        for ind, seq in enumerate(sentences):
            seq_vec, word_count = 0, 0
            for word in seq:
                if word not in model:
                    continue
                else:
                    seq_vec += model[word]
                    word_count += 1
            if word_count == 0:
                embedding_vec.append(embedding_size * [0])
            else:
                embedding_vec.append(seq_vec / word_count)
        embedding_vec = np.array(embedding_vec)
        embedding_cbow_df = pd.DataFrame(embedding_vec, 
            columns=["embedding_cbow_{}_{}".format(word_feat, i) for i in range(embedding_size)])
        embedding_cbow_df["id"] = boat_id
        embedding_df_list.append(embedding_cbow_df)
        embedding_model_list.append(model)
    print("-------------------------------------------")
    print("@End CBOW word embedding at {}".format(datetime.now()))
    return embedding_df_list, embedding_model_list
embedding_size=70
iters=70
min_count=3
window_size=25
num_runs=1

df_list, model_list = traj_cbow_embedding(df,
                                          embedding_size=embedding_size,
                                          iters=iters, min_count=min_count,
                                          window_size=window_size,
                                          seed=9012,
                                          num_runs=num_runs,
                                          word_feat="no_bin")

train_embedding_df_list = [d.reset_index(drop=True) for d in df_list]
fea = train_embedding_df_list[0]
fea = pd.DataFrame(fea)
pre_cols = df.columns
df = df.merge(fea,on='id',how='left')


new_cols = [i for i in df.columns if i not in pre_cols]
df[new_cols].head()

在这里插入图片描述

  • NMF提取文本的主题分布
class nmf_list(object):
    def __init__(self,data,by_name,to_list,nmf_n,top_n):
        self.data = data
        self.by_name = by_name
        self.to_list = to_list
        self.nmf_n = nmf_n
        self.top_n = top_n

    def run(self,tf_n):
        df_all = self.data.groupby(self.by_name)[self.to_list].apply(lambda x :'|'.join(x)).reset_index()
        self.data =df_all.copy()

        print('bulid word_fre')
        # 词频的构建
        def word_fre(x):
            word_dict = []
            x = x.split('|')
            docs = []
            for doc in x:
                doc = doc.split()
                docs.append(doc)
                word_dict.extend(doc)
            word_dict = Counter(word_dict)
            new_word_dict = {}
            for key,value in word_dict.items():
                new_word_dict[key] = [value,0]
            del word_dict  
            del x
            for doc in docs:
                doc = Counter(doc)
                for word in doc.keys():
                    new_word_dict[word][1] += 1
            return new_word_dict 
        self.data['word_fre'] = self.data[self.to_list].apply(word_fre)

        print('bulid top_' + str(self.top_n))
        # 设定100个高频词
        def top_100(word_dict):
            return sorted(word_dict.items(),key = lambda x:(x[1][1],x[1][0]),reverse = True)[:self.top_n]
        self.data['top_'+str(self.top_n)] = self.data['word_fre'].apply(top_100)
        def top_100_word(word_list):
            words = []
            for i in word_list:
                i = list(i)
                words.append(i[0])
            return words 
        self.data['top_'+str(self.top_n)+'_word'] = self.data['top_' + str(self.top_n)].apply(top_100_word)
        # print('top_'+str(self.top_n)+'_word的shape')
        print(self.data.shape)

        word_list = []
        for i in self.data['top_'+str(self.top_n)+'_word'].values:
            word_list.extend(i)
        word_list = Counter(word_list)
        word_list = sorted(word_list.items(),key = lambda x:x[1],reverse = True)
        user_fre = []
        for i in word_list:
            i = list(i)
            user_fre.append(i[1]/self.data[self.by_name].nunique())
        stop_words = []
        for i,j in zip(word_list,user_fre):
            if j>0.5:
                i = list(i)
                stop_words.append(i[0])

        print('start title_feature')
        # 讲融合后的taglist当作一句话进行文本处理
        self.data['title_feature'] = self.data[self.to_list].apply(lambda x: x.split('|'))
        self.data['title_feature'] = self.data['title_feature'].apply(lambda line: [w for w in line if w not in stop_words])
        self.data['title_feature'] = self.data['title_feature'].apply(lambda x: ' '.join(x))

        print('start NMF')
        # 使用tfidf对元素进行处理
        tfidf_vectorizer = TfidfVectorizer(ngram_range=(tf_n,tf_n))
        tfidf = tfidf_vectorizer.fit_transform(self.data['title_feature'].values)
        #使用nmf算法,提取文本的主题分布
        text_nmf = NMF(n_components=self.nmf_n).fit_transform(tfidf)


        # 整理并输出文件
        name = [str(tf_n) + self.to_list + '_' +str(x) for x in range(1,self.nmf_n+1)]
        tag_list = pd.DataFrame(text_nmf)
        print(tag_list.shape)
        tag_list.columns = name
        tag_list[self.by_name] = self.data[self.by_name]
        column_name = [self.by_name] + name
        tag_list = tag_list[column_name]
        return tag_list
data = df.copy()
data.rename(columns={'v':'speed','id':'ship'},inplace=True)
for j in range(1,4):
    print('********* {} *******'.format(j))
    for i in ['speed','x','y']:
        data[i + '_str'] = data[i].astype(str)
        nmf = nmf_list(data,'ship',i + '_str',8,2)
        nmf_a = nmf.run(j)
        nmf_a.rename(columns={'ship':'id'},inplace=True)
        data_label = data_label.merge(nmf_a,on = 'id',how = 'left')

在这里插入图片描述

III 总结与思考

  • 赛题特征工程:该如何构建有效果的赛题特征工程

      参考:通过数据EDA、查阅对应赛题的参考文献,寻找并构建有实际意义的业务特征
    

补充:所谓业务特征,就是在对应的专业领域通过公式、结构构造一些有意义的组合,例如在“高能粒子挑战赛里”可以通过近代物理公式构造物理学中的各种粒子属性,如角动量、势能、能量等。或本赛题的渔网捕捞区域。这些都是要通过参考文献、理论去探索的,上分稳定但仅适合初期理解赛题。

  • 分箱特征:几乎所有topline代码中均有分箱特征的构造,为何分箱特征如此重要且有效。在什么情况下使用分箱特征的效果好?(为什么本赛题需要分箱特征)

      参考:分箱的原理
    
  • DataFrame特征:针对pandas DataFrame的内置方法的使用,可以构造出大量的统计特征。建议:自行整理一份针对表格数据的统计特征构造函数

      参考:DataWhale的joyful pandas
    
  • Embedding特征:上分秘籍,将序列转换成NLP文本中的一句话或一篇文章进行特征向量化为何效果如此之好。如何针对给定数据,调整参数构造较好的词向量?

      参考:Word2vec的学习
    

补充:这也是四月DataWhale组队学习中 智慧海洋的 Task3特征工程 任务,我也是本任务学习内容的整理者,也欢迎大家参与组队学习或是加入DataWhale!

  • 在此推荐一份批作业时发现的学习收获很大的学员作业
    《绝对不咕咕》队的烟逝:
    https://blog.csdn.net/zero112535/article/details/115907339
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿芒Aris

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

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

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

打赏作者

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

抵扣说明:

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

余额充值