机器学习相关操作分享(四)

# 数据准备阶段
## 导入基本的包
import time
import matplotlib.pyplot as plt
# plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
# plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
# plt.rcParams['font.family'] = ['sans-serif']
import seaborn as sns

import numpy as np
import pandas as pd 
from sklearn.metrics import mean_squared_error,mean_absolute_error

import xgboost as xgb
import lightgbm as lgb
from catboost import CatBoostRegressor

from sklearn.model_selection import KFold,StratifiedKFold
from sklearn.preprocessing import LabelEncoder

pd.set_option('display.max_columns',100)#显示最大列数

import warnings
warnings.filterwarnings("ignore")
## 导入数据
data_path = './data/'
train_data = pd.read_csv(data_path + 'train_dataset.csv')
train_data['type'] = 1
test_data = pd.read_csv(data_path + 'test_dataset.csv')
test_data['type'] = 1
sample_sub = pd.read_csv(data_path + 'submit_example.csv')

print('train:', train_data.shape)
print('test:', test_data.shape)
train_data.head()
# 数据分析
# 基本信息
train_data.info()
# 查看训练集中信用分的统计信息及分布
print(train_data['信用分'].describe())
train_data['信用分'].hist(bins=70)
查阅资料得知,信用评分从统计学上讲,有一个突出的特点:分数特别低的人和分数特别高的人都比较少,大多数人评分中等,大体呈现左偏分布。

本题给的数据也基本符合这个情况,预感两极人群的预测会成为这个题目后期的关键点。
## 单变量分析
features = [f for f in train_data.columns if f not in ['用户编码','信用分']]
for f in features:
    print(f + "的特征分布如下:")
    print(train_data[f].describe())
    if train_data[f].nunique()<20:
        print(train_data[f].value_counts())
    plt.hist(train_data[f], bins=70)
    plt.show()
**根据单变量分析如下**
* 用户年龄:发现290名年龄为0的用户,以及22位100岁以上的用户。推测年龄位0的用户是主办方用0填充了缺失值。
* 用户话费敏感度:在字段说明中,敏感度包含1-5共五级用户。实际用户出现一部分等级为0的用户。推测这些原本也应是缺失值。
* 在一些消费类的账单数据和某些APP的使用次数,数据分布呈现如下长尾形态。

## 多变量分析
主要分析特征与标签的关系
features = [f for f in train_data.columns if f not in ['用户编码','信用分']]
for f in features:
    if train_data[f].nunique()>=20:
        sns.jointplot(x=f,y='信用分',data = train_data)
**根据多变量分析如下**
* 部分特征与信用分存在相关性,用户账单当月总费用(元)、用户当月账户余额(元)等
## 特征之间是否冗余
sns.heatmap(train_data[features].corr(), cmap='Reds')
plt.show()
可以看出,绝大多数特征间线性相关性并不高,最高的为 '用户近6个月平均消费值(元)'和'用户账单当月总费用(元)',Pearson相关系数达到0.903464,也暂时都保留。
## 查看某个字段不同取值或不同范围,信用分的分布
train_data[train_data['是否大学生客户'] == 1]['信用分'].hist(bins=55)
train_data[train_data['是否大学生客户'] == 0]['信用分'].hist(bins=55)
train_data[train_data['用户话费敏感度'] == 0]['信用分'].hist(bins=55)
train_data[train_data['用户话费敏感度'] == 1]['信用分'].hist(bins=55)
train_data[train_data['用户话费敏感度'] == 2]['信用分'].hist(bins=55)
train_data[train_data['用户话费敏感度'] == 4]['信用分'].hist(bins=55)
train_data.groupby(['用户话费敏感度'])['信用分'].mean()
# 特征工程
## 数据预处理
* 数据合并
* 异常值处理
* 特征分类
data = pd.concat([train_data, test_data], ignore_index=True, sort=True)

data.loc[data['用户年龄'] == 0, '用户年龄'] = None
data.loc[data['用户年龄'] > 100, '用户年龄'] = None
data.loc[data['用户话费敏感度'] == 0, '用户话费敏感度'] = None
data.loc[data['用户近6个月平均消费值(元)'] == 0, '用户近6个月平均消费值(元)'] = None

data.rename(columns={'用户编码': 'id', '信用分': 'score'}, inplace=True)

origin_bool_feature = ['当月是否体育场馆消费', '当月是否景点游览', '当月是否看电影', '当月是否到过福州山姆会员店', '当月是否逛过福州仓山万达',
                       '缴费用户当前是否欠费缴费', '是否经常逛商场的人', '是否大学生客户', '是否4G不健康客户', '是否黑名单客户',
                       '用户最近一次缴费距今时长(月)', '用户实名制是否通过核实']

origin_num_feature = ['用户话费敏感度', '用户年龄', '近三个月月均商场出现次数', '当月火车类应用使用次数', '当月飞机类应用使用次数',
                      '当月物流快递类应用使用次数', '用户当月账户余额(元)', '用户网龄(月)', '缴费用户最近一次缴费金额(元)',
                      '当月通话交往圈人数', '当月旅游资讯类应用使用次数', '当月金融理财类应用使用总次数', '当月网购类应用使用次数',
                      '当月视频播放类应用使用次数', '用户账单当月总费用(元)', '用户近6个月平均消费值(元)']

count_feature_list = []
## 基本统计特征
def feature_count(data, features=[]):
    
    # 样本数等于类别数
    if len(set(features)) != len(features):
        print('equal feature !!!!')
        return data
    
    new_feature = 'count'
    
    # 构建特征名
    for i in features:
        new_feature += '_' + i.replace('add_', '')
    try:
        del data[new_feature]
    except:
        pass
    
    # 构造特征
    temp = data.groupby(features).size().reset_index().rename(columns={0: new_feature})
    data = data.merge(temp, 'left', on=features)
    if new_feature not in count_feature_list:
        count_feature_list.append(new_feature)
    return data


fee_feature = ['用户近6个月平均消费值(元)', '用户账单当月总费用(元)', '缴费用户最近一次缴费金额(元)']

for i in fee_feature:
    data = feature_count(data, [i])
data.groupby('用户账单当月总费用(元)').size().reset_index().sort_values(0, ascending=False)[:20]
与实际业务存在很大的联系,如套餐费用,对数值特征进行编码,通过count来反映套餐类别信息
diff_feature = ['fee_del_mean', 'fee_remain_now']
data['five_all'] = data['用户近6个月平均消费值(元)'] * data['用户网龄(月)'].apply(lambda x: min(x, 6)) - data['用户账单当月总费用(元)']
data['fee_del_mean'] = data['用户账单当月总费用(元)'] - data['用户近6个月平均消费值(元)']
# 缴费对于消费的比例
data['fee_remain_now'] = data['缴费用户最近一次缴费金额(元)'] / data['用户账单当月总费用(元)']
# 各类行为总次数
data['次数'] = data[['当月网购类应用使用次数', '当月物流快递类应用使用次数', '当月金融理财类应用使用总次数',
                   '当月视频播放类应用使用次数', '当月飞机类应用使用次数', '当月火车类应用使用次数', '当月旅游资讯类应用使用次数']].sum(axis=1)
for col in ['当月金融理财类应用使用总次数', '当月旅游资讯类应用使用次数']:  # 这两个比较积极向上一点
    data[col + '_百分比'] = data[col] / data['次数']
data['regist_month'] = data['用户网龄(月)'] % 12
num_feature = ['次数', '当月金融理财类应用使用总次数_百分比',
               '当月旅游资讯类应用使用次数_百分比',
               'five_all',
               'regist_month'] + diff_feature + origin_bool_feature + origin_num_feature + count_feature_list
cate_feature = []
for i in num_feature:
    data[i] = data[i].astype(float)
feature = num_feature + cate_feature
# 训练模型
def get_predict_w(model, data, label='label', feature=[], cate_feature=[], random_state=2018, n_splits=5,
                  model_type='lgb'):
    # 随机数种子
    model.random_state = random_state
    # 交叉验证
    kfold = KFold(n_splits=n_splits, shuffle=True, random_state=random_state)
    
    # 初始化预测结果
    predict_label = 'predict_' + label
    data[predict_label] = 0
    
    # 测试集index提取
    test_index = (data[label].isnull()) | (data[label] == -1)
    # 训练数据提取
    train_data = data[~test_index].reset_index(drop=True)
    # 测试数据提取
    test_data = data[test_index]

    for train_idx, val_idx in kfold.split(train_data):

        train_x = train_data.loc[train_idx][feature]
        train_y = train_data.loc[train_idx][label]

        test_x = train_data.loc[val_idx][feature]
        test_y = train_data.loc[val_idx][label]
        
        if model_type == 'lgb':
            model.fit(train_x, train_y, eval_set=[(train_x, train_y),(test_x, test_y)], early_stopping_rounds=100,
                      eval_metric='mae',
                      categorical_feature=cate_feature,
                      # sample_weight=train_data.loc[train_idx]['sample_weight'],
                      verbose=100)
        elif model_type == 'ctb':
            model.fit(train_x, train_y, eval_set=[(train_x, train_y),(test_x, test_y)], early_stopping_rounds=100,
                      cat_features=cate_feature,
                      # sample_weight=train_data.loc[train_idx]['sample_weight'],
                      verbose=100)
        elif model_type == 'xgb':
            model.fit(train_x, train_y, eval_set=[(train_x, train_y),(test_x, test_y)], early_stopping_rounds=100, 
                      # sample_weight=train_data.loc[train_idx]['sample_weight'],
                      verbose=100)
        
        train_data.loc[val_idx, predict_label] = model.predict(test_x)
        
        # 获取测试集结果
        if len(test_data) != 0:
            test_data[predict_label] = test_data[predict_label] + model.predict(test_data[feature])
    
    # 测试集结果加权平均
    test_data[predict_label] = test_data[predict_label] / n_splits
    
    print(mean_squared_error(train_data[label], train_data[predict_label]) * 5, train_data[predict_label].mean(),
          test_data[predict_label].mean())

    return pd.concat([train_data, test_data], sort=True, ignore_index=True), predict_label
## 单模型
### LightGBM
lgb_model = lgb.LGBMRegressor(
    num_leaves=32, reg_alpha=0., reg_lambda=0.01, objective='mse', metric='mse',
    max_depth=-1, learning_rate=0.1, min_child_samples=50,
    n_estimators=500, subsample=0.7, colsample_bytree=0.45, subsample_freq=5,
)
data.tail()
data, predict_label = get_predict_w(lgb_model, data, label='score',
                                    feature=feature,
                                    random_state=2019, n_splits=5)
data['lgb_mse'] = data[predict_label]
### CatBoost
ctb_params = {
    'n_estimators': 10000,
    'learning_rate': 0.05,
    'random_seed': 4590,
    'reg_lambda': 0.08,
    'subsample': 0.7,
    'bootstrap_type': 'Bernoulli',
    'boosting_type': 'Plain',
    'one_hot_max_size': 10,
    'rsm': 0.5,
    'leaf_estimation_iterations': 5,
    'use_best_model': True,
    'max_depth': 6,
    'verbose': -1,
    'thread_count': 4
}
ctb_model = CatBoostRegressor(**ctb_params)

data, predict_label = get_predict_w(ctb_model, data, label='score',
                                    feature=feature,
                                    random_state=2019, n_splits=5, model_type='ctb')
data['ctb_mse'] = data[predict_label]
### XGBoost
xgb_model = xgb.XGBRegressor(
                        max_depth=6 , learning_rate=0.05, n_estimators=10000, 
                        objective='reg:linear', tree_method = 'hist', subsample=0.8, 
                        colsample_bytree=0.6, min_child_samples=5
                        )
# objective='reg:linear' 线性回归
# 替换inf
data[feature] = data[feature].replace(np.inf, np.nan)
data, predict_label = get_predict_w(xgb_model, data, label='score',
                                    feature=feature,
                                    random_state=2019, n_splits=5, model_type='xgb')

data['xgb_mse'] = data[predict_label]
## 损失函数选择
**MSE均方误差**

因为MSE对error e进行了平方,可以看到,如果e大于1,这个值就会>> |e|。 用了MSE为代价函数的模型因为要最小化这个异常值带来的误差,就会尽量贴近异常值,也就是对outliers(异常值)赋予更大的权重。这样就会影响总体的模型效果。
**MAE平均绝对误差**

相比MSE来说,MAE在数据里有不利于预测结果异常值的情况下撸棒性更好。

可以这么想?哪个常数能够最小化我们的MSE? 答案是中值。因为在有异常值的时候,中值的代表性要好于均值。所以MAE的撸棒性要高于MSE。
**可以通过不同损失函数的尝试,构建结果的差异性,进行最终的融合**
**LightGBM使用MAE平均绝对误差**
lgb_model = lgb.LGBMRegressor(
    num_leaves=32, reg_alpha=0., reg_lambda=0.01, objective='mae',
    max_depth=-1, learning_rate=0.1, min_child_samples=50,
    n_estimators=500, subsample=0.7, colsample_bytree=0.45, subsample_freq=5,
)

data, predict_label = get_predict_w(lgb_model, data, label='score',
                                    feature=feature,
                                    random_state=2019, n_splits=5)

data['lgb_mae'] = data[predict_label]
print(data['lgb_mse'].describe())
data['lgb_mse'].hist(bins=70)
print(data['lgb_mae'].describe())
data['lgb_mae'].hist(bins=70)
## 加权融合
* 构建有差异的结果
* 模型差异
* 样本差异
* 特征差异
* 损失函数差异
* 训练目标差异,对于树模型而言,更容易学习到稳定的结果,如果目标的值方差很大,可以选择进行log变换
all_score = ['lgb_mse', 'ctb_mse', 'xgb_mse']
data['t_label'] = data['lgb_mse'] * 0.5 + data['ctb_mse'] * 0.3 + data['xgb_mse'] * 0.2
## Stacking
**Stacking是一种表示学习(representation learning)**

stacking集成学习框架的对于基分类器的两个要求: 差异化要大、准确性要高

**Stacking的输出层选择简单的模型,如逻辑回归等**

为了降低过拟合的问题,第二层分类器应该是较为简单的分类器,广义线性如逻辑回归是一个不错的选择。在特征提取的过程中,我们已经使用了复杂的非线性变换,因此在输出层不需要复杂的分类器。
### 第二层仅为学习到的特征
def stack_model(oof_1, oof_2, oof_3, predictions_1, predictions_2, predictions_3, y, eval_type='regression'):
   
    train_stack = np.vstack([oof_1, oof_2, oof_3]).transpose()
    test_stack = np.vstack([predictions_1, predictions_2, predictions_3]).transpose()
    
    from sklearn.model_selection import RepeatedKFold
    folds = RepeatedKFold(n_splits=5, n_repeats=1, random_state=2018)
    oof = np.zeros(train_stack.shape[0])
    predictions = np.zeros(test_stack.shape[0])

    for fold_, (trn_idx, val_idx) in enumerate(folds.split(train_stack, y)):
        print("fold n°{}".format(fold_+1))
        trn_data, trn_y = train_stack[trn_idx], y[trn_idx]
        val_data, val_y = train_stack[val_idx], y[val_idx]
        print("-" * 10 + "Stacking " + str(fold_) + "-" * 10)
        clf = BayesianRidge()
        clf.fit(trn_data, trn_y)

        oof[val_idx] = clf.predict(val_data)
        predictions += clf.predict(test_stack) / (n_splits * n_repeats)
    if eval_type == 'regression':
        print('mean: ',np.sqrt(mean_squared_error(y, oof)))
    if eval_type == 'binary':
        print('mean: ',log_loss(y, oof))
    
    return oof, predictions
# oof_stack , predictions_stack  = stack_model(oof_lgb[0] , oof_xgb[0] , oof_cat[0] , predictions_lgb[0] , predictions_xgb[0] , predictions_cat[0] , target)
### 学习到的特征+原始特征

**Stacking是否需要多层?第一层的分类器是否越多越好?**

stacking的表示学习不是来自于多层堆叠的效果,而是来自于不同学习器对于不同特征的学习能力,并有效的结合起来。一般来看,2层对于stacking足够了。多层的stacking会面临更加复杂的过拟合问题,且收益有限。

第一层分类器的数量对于特征学习应该有所帮助,经验角度看越多的基分类器越好。即使有所重复和高依赖性,我们依然可以通过特征选择来处理,问题不大。
**stacking与深度学习不同之处**

* stacking需要宽度,深度学习不需要

* 深度学习需要深度,而stacking不需要

**但stacking和深度学习都共同需要面临**

* 黑箱与解释问题
* 严重的过拟合问题
## 样本权重
**样本权重参数: sample_weight**
* 第一类:样本不平衡问题

样本不平衡,导致样本不是总体样本的无偏估计,从而可能导致我们的模型预测能力下降。遇到这种情况,我们可以通过调节样本权重来尝试解决这个问题

* 第二类:误差较大的样本

给予误差交大的样本,很难学习的样本更大的权重
### 选择误差大的样本

### 调整样本权重
data['temp_label'] = data['lgb_mse']
data.loc[data.id.isin(ab_id), 'temp_label'] = None
data['sample_weight'] = data['temp_label'] + 200
data['sample_weight'] = data['sample_weight'] / data['sample_weight'].mean()

##top up amount, 充值金额是整数,和小数,应该对应不同的充值途径?
def produce_offline_feature(train_data):
    train_data['不同充值途径']=0
    train_data['不同充值途径'][(train_data['缴费用户最近一次缴费金额(元)']%10==0)&train_data['缴费用户最近一次缴费金额(元)']!=0]=1
    return train_data

train_data=produce_offline_feature(train_data)
test_data=produce_offline_feature(test_data)
##看importance,当月话费 和最近半年平均话费都很高,算一下当月/半年 -->稳定性
def produce_fee_rate(train_data):
    train_data['当前费用稳定性']=train_data['用户账单当月总费用(元)']/(train_data['用户近6个月平均消费值(元)']+1)
    
    ##当月话费/当月账户余额
    train_data['用户余额比例']=train_data['用户账单当月总费用(元)']/(train_data['用户当月账户余额(元)']+1)
    return train_data

train_data=produce_offline_feature(train_data)
test_data=produce_offline_feature(test_data)
#获取特征
def get_features(data):
    data.loc[data['用户年龄']==0,'用户年龄'] = None
    data.loc[data['用户话费敏感度'] == 0, '用户话费敏感度'] = None
    data.loc[data['用户账单当月总费用(元)'] == 0, '用户账单当月总费用(元)'] = None
    data.loc[data['用户近6个月平均消费值(元)'] == 0, '用户近6个月平均消费值(元)'] = None
    data['缴费金额是否能覆盖当月账单']    = data['缴费用户最近一次缴费金额(元)'] - data['用户账单当月总费用(元)']
    data['最近一次缴费是否超过平均消费额'] = data['缴费用户最近一次缴费金额(元)'] - data['用户近6个月平均消费值(元)']
    data['当月账单是否超过平均消费额'] = data['用户账单当月总费用(元)'] - data['用户近6个月平均消费值(元)']
    
    #映射年龄
    def map_age(x):
        if x<=18:
            return 1
        elif x<=30:
            return 2
        elif x<=35:
            return 3
        elif x<=45:
            return 4
        else:
            return 5
    data['是否大学生_黑名单']=data['是否大学生客户']+data['是否黑名单客户']
    data['是否去过高档商场']=data['当月是否到过福州山姆会员店']+data['当月是否逛过福州仓山万达']
    data['是否去过高档商场']=data['是否去过高档商场'].map(lambda x:1 if x>=1 else 0)
    data['是否_商场_电影']=data['是否去过高档商场']*data['当月是否看电影']
    data['是否_商场_体育馆']=data['是否去过高档商场']*data['当月是否体育场馆消费']
    data['是否_商场_旅游']=data['是否去过高档商场']*data['当月是否景点游览']
    data['是否_电影_体育馆']=data['当月是否看电影']*data['当月是否体育场馆消费']
    data['是否_电影_旅游']=data['当月是否看电影']*data['当月是否景点游览']
    data['是否_旅游_体育馆']=data['当月是否景点游览']*data['当月是否体育场馆消费']
    
    data['是否_商场_旅游_体育馆']=data['是否去过高档商场']*data['当月是否景点游览']*data['当月是否体育场馆消费']
    data['是否_商场_电影_体育馆']=data['是否去过高档商场']*data['当月是否看电影']*data['当月是否体育场馆消费']
    data['是否_商场_电影_旅游']=data['是否去过高档商场']*data['当月是否看电影']*data['当月是否景点游览']
    data['是否_体育馆_电影_旅游']=data['当月是否体育场馆消费']*data['当月是否看电影']*data['当月是否景点游览']
    
    data['是否_商场_体育馆_电影_旅游']=data['是否去过高档商场']*data['当月是否体育场馆消费']*\
                                   data['当月是否看电影']*data['当月是否景点游览']
    
    discretize_features=['当月物流快递类应用使用次数','当月飞机类应用使用次数',\
                         '当月火车类应用使用次数','当月旅游资讯类应用使用次数']
    data['交通类应用使用次数比']=(data['当月飞机类应用使用次数'] + 1) / (data['当月火车类应用使用次数'] + 1)
    
    data['6个月平均占比总费用']=data['用户近6个月平均消费值(元)']/data['用户账单当月总费用(元)']+1
    
    def map_discretize(x):
        if x==0:
            return 0
        elif x<=5:
            return 1
        elif x<=15:
            return 2
        elif x<=50:
            return 3
        elif x<=100:
            return 4
        else:
            return 5
        
    for col in discretize_features[:]:
        data[col]=data[col].map(lambda x:map_discretize(x))
    
    return data

train_data=get_features(train_data)
test_data=get_features(test_data)
def base_process(data):
    transform_value_feature=['用户年龄','用户网龄(月)','当月通话交往圈人数','近三个月月均商场出现次数','当月网购类应用使用次数',\
                             '当月物流快递类应用使用次数','当月金融理财类应用使用总次数','当月视频播放类应用使用次数',\
                             '当月飞机类应用使用次数','当月火车类应用使用次数','当月旅游资讯类应用使用次数']
    user_fea=['缴费用户最近一次缴费金额(元)','用户近6个月平均消费值(元)','用户账单当月总费用(元)','用户当月账户余额(元)']
    log_features=['当月网购类应用使用次数','当月金融理财类应用使用总次数','当月物流快递类应用使用次数','当月视频播放类应用使用次数']
    
    #处理离散点
    for col in transform_value_feature+user_fea+log_features:
        #取出最高99.9%值
        ulimit=np.percentile(train_data[col].values,99.9)
        #取出最低0.1%值
        llimit=np.percentile(train_data[col].values,0.1)
        train_data.loc[train_data[col]>ulimit,col]=ulimit
        train_data.loc[train_data[col]<llimit,col]=llimit
        
    for col in user_fea+log_features:
        data[col]=data[col].map(lambda x:np.log1p(x))
    
    return data

# train_data=base_process(train_data)
# test_data=base_process(test_data)
lgb_model = lgb.LGBMRegressor(
    num_leaves=32, reg_alpha=0., reg_lambda=0.01, objective='mse', metric='mae',
    max_depth=-1, learning_rate=0.01, min_child_samples=50,
    n_estimators=15000, subsample=0.7, colsample_bytree=0.45, subsample_freq=5,
)
train_label = train_data['信用分']
train = train_data.drop(['用户编码','信用分'], axis=1)
test = test_data.drop(['用户编码'], axis=1)
folds = KFold(n_splits=5, shuffle=False, random_state=2019)
oof_lgb = np.zeros(train.shape[0])
predictions_lgb = np.zeros(test.shape[0])
for fold_, (trn_idx, val_idx) in enumerate(folds.split(train, train_label)):
    print("fold n°{}".format(fold_+1))
    trn_x, trn_y = train.loc[trn_idx].values, train_label.loc[trn_idx].values
    val_x, val_y = train.loc[val_idx].values, train_label.loc[val_idx].values
    
    lgb_model.fit(trn_x, trn_y, 
              eval_set=[(trn_x, trn_y),(val_x, val_y)], 
              verbose=500, early_stopping_rounds=300)
    
    oof_lgb[val_idx] = lgb_model.predict(train.loc[val_idx].values)
    predictions_lgb += lgb_model.predict(test) / folds.n_splits

print("CV mse score: {:<8.5f}".format(mean_squared_error(train_label , oof_lgb)))
print("CV mae score: {:<8.5f}".format(mean_absolute_error(train_label, oof_lgb)))
sample_sub = sample_sub[['id']]
sample_sub['score'] = predictions_lgb
sample_sub['score'] = sample_sub['score'].apply(lambda x: int(np.round(x)))
sample_sub.to_csv('output/sub.csv', index=False)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值