8 机器学习 逻辑回归案例 制作评分卡 聚类算法 KMeans算法

机器学习

1 逻辑回归案例 制作评分卡

1.1 项目介绍

目标:制作金融申请评分卡

特征介绍

特征名称描述
SeriousDlqin2yrs好坏客户出现90天及更长时间的逾期行为,用于定义好坏客户。
RevolvingUtilizationOfUnsecuredLines可用额度比值贷款或信用卡可用额度与总额度的比例。
age年龄借款人借款时年龄。
NumberOfTime30-59DaysPastDueNotWorse逾期30-59天笔数过去两年内出现30-59天逾期但没有发展得更坏的次数。
DebtRatio负债率每月用于偿还债务、赡养费、生活费等费用与月收入的比例。
MonthlyIncome月收入月收入
NumberOfOpenCreditLinesAndLoans信贷数量开放式贷款和信贷数量。
NumberOfTimes90DaysLate逾期90天笔数过去两年内出现90天逾期或者更坏情况的次数。
NumberRealEstateLoansOrLines固定资产贷款量抵押贷款和房地产贷款数量,包括房屋净值信贷额度。
NumberOfTime60-89DaysPastDueNotWorse逾期60-89天笔数过去两年内出现60-89天逾期但没有发展得更坏的次数。
NumberOfDependents家属数量家庭中不包括自身的家属人数,例如子女、配偶等。
1.2 数据导入
import numpy as np
import pandas as pd
from pandas import DataFrame, Series
import matplotlib.pyplot as plt
%matplotlib inline

# 定义字典,用于修改列索引的名称。
column_dict = {
    'SeriousDlqin2yrs': '好坏客户',
    'RevolvingUtilizationOfUnsecuredLines': '可用额度比值',
    'age': '年龄',
    'NumberOfTime30-59DaysPastDueNotWorse': '逾期30-59天笔数',
    'DebtRatio': '负债率',
    'MonthlyIncome': '月收入',
    'NumberOfOpenCreditLinesAndLoans': '信贷数量',
    'NumberOfTimes90DaysLate': '逾期90天笔数',
    'NumberRealEstateLoansOrLines': '固定资产贷款量',
    'NumberOfTime60-89DaysPastDueNotWorse': '逾期60-89天笔数',
    'NumberOfDependents': '家属数量'
}

# 读取并查看数据
# index_col=0表示使用数据的首列作为行索引。
data = pd.read_csv('./rankingcard.csv', index_col=0)
data.head()
'''
 	SeriousDlqin2yrs 	RevolvingUtilizationOfUnsecuredLines 	age 	NumberOfTime30-59DaysPastDueNotWorse 	DebtRatio 	MonthlyIncome 	NumberOfOpenCreditLinesAndLoans 	NumberOfTimes90DaysLate 	NumberRealEstateLoansOrLines 	NumberOfTime60-89DaysPastDueNotWorse 	NumberOfDependents
1 				   1 								0.766127 	 45 									   2 	 0.802982		   9120.0 								 13 						  0 							   6 									   0 				   2.0
2 				   0 								0.957151 	 40 									   0 	 0.121876		   2600.0 								  4 						  0 							   0 									   0 				   1.0
3 				   0 								0.658180 	 38 									   1 	 0.085113		   3042.0 								  2 						  1 							   0 									   0 				   0.0
4 				   0 								0.233810 	 30 									   0 	 0.036050		   3300.0 								  5 						  0 							   0 									   0 				   0.0
5 				   0 								0.907239 	 49 									   1 	 0.024926		  63588.0 								  7 						  0 							   1 									   0 				   0.0
'''

# 修改列索引名称。
data.rename(columns=column_dict, inplace=True)
data.head()
'''
 	好坏客户 	可用额度比值		年龄 	逾期30-59天笔数 		 负债率 	  月收入 	信贷数量 	逾期90天笔数 	固定资产贷款量 	逾期60-89天笔数 	家属数量
1 		  1 	   0.766127		 45 				 2 	  0.802982 	 9120.0 	 	 13 			  0 	            6 				 0 		2.0
2 		  0 	   0.957151		 40 				 0 	  0.121876 	 2600.0 	  	  4 			  0 	            0 				 0 		1.0
3 		  0 	   0.658180		 38 				 1 	  0.085113 	 3042.0 	  	  2 			  1 	            0 				 0 		0.0
4 		  0 	   0.233810		 30 				 0 	  0.036050 	 3300.0 	  	  5 			  0 	            0 				 0 		0.0
5 		  0 	   0.907239		 49 				 1 	  0.024926  63588.0 	  	  7 			  0 	            1 				 0 		0.0
'''
1.3 数据清洗
1.3.1 处理重复的行数据
# 查看重复数据数量
data.duplicated().sum()  # 609

# 删除重复数据
data.drop_duplicates(inplace=True)

# 恢复行索引
data.index = range(data.shape[0])

data.duplicated().sum()  # 0
data.shape  # (149391, 11)
1.3.2 处理缺失数据

检查存在缺失数据的列。

data.isnull().any(axis=0)
'''
好坏客户          False
可用额度比值        False
年龄            False
逾期30-59天笔数    False
负债率           False
月收入            True
信贷数量          False
逾期90天笔数       False
固定资产贷款量       False
逾期60-89天笔数    False
家属数量           True
'''

data.isnull().sum()
'''
好坏客户              0
可用额度比值            0
年龄                0
逾期30-59天笔数        0
负债率               0
月收入           29221
信贷数量              0
逾期90天笔数           0
固定资产贷款量           0
逾期60-89天笔数        0
家属数量           3828
'''

“家属数量”列缺失数据比较少,直接删除,
“月收入”列存在的缺失数据比较多,使用均值填充。

data = data.loc[data['家属数量'].notnull()]
data.fillna({'月收入': data['月收入'].mean()}, inplace=True)

# 恢复行索引
data.index = range(data.shape[0])
data.isnull().sum()
'''
好坏客户          0
可用额度比值        0
年龄            0
逾期30-59天笔数    0
负债率           0
月收入           0
信贷数量          0
逾期90天笔数       0
固定资产贷款量       0
逾期60-89天笔数    0
家属数量          0
'''
1.3.3 处理异常数据

需要排除银行数据中不符合常识的数据,例如收入不能为负数。

data.describe()
data.describe([0.1, 0.25, 0.5, 0.75, 0.9, 0.99])

年龄必须大于0。

(data['年龄'] == 0).sum()  # 1
data = data.loc[data['年龄'] != 0]
特征名称描述最大值
逾期30-59天笔数过去两年内出现30-59天逾期但没有发展得更坏的次数。365 * 2 / 30 ~ 24
逾期60-89天笔数过去两年内出现60-89天逾期但没有发展得更坏的次数。365 * 2 / 60 ~ 12
逾期90天笔数过去两年内出现90天逾期或者更坏情况的次数。365 * 2 / 90 ~ 8
data = data.loc[data['逾期30-59天笔数'] < 24]
data = data.loc[data['逾期60-89天笔数'] < 12]
data = data.loc[data['逾期90天笔数'] < 8]
# 恢复行索引
data.index = range(data.shape[0])
# data.shape  # (145290, 11)
1.4 特征工程
1.4.1 查看标签(好坏客户)的分布情况
data['好坏客户'].value_counts() / data['好坏客户'].value_counts().sum()
'''
0    0.933485
1    0.066515
'''

数据标签(好坏客户)的分布不均匀。

1.4.2 特征选择
1.4.2.1 单变量分析

单变量分析是分析每个自变量与因变量之间的关系,一般用于处理样本特征数量不多且能够理解每个特征的意义的情况。
此处以年龄(自变量)和好坏客户(因变量)为例进行分析。

对年龄进行分箱操作,分成5组。

age_cut = pd.cut(data['年龄'], bins=5)

查看不同年龄段的用户数量。

# 方式1
age_cut.value_counts()
'''
(38.2, 55.4]      57988
(55.4, 72.6]      46130
(20.914, 38.2]    27996
(72.6, 89.8]      12623
(89.8, 107.0]       553
'''

# 方式2 对Series进行分组处理。
data['好坏客户'].groupby(by=age_cut).count()
'''
(38.2, 55.4]      57988
(55.4, 72.6]      46130
(20.914, 38.2]    27996
(72.6, 89.8]      12623
(89.8, 107.0]       553
'''

获取各个年龄段分组的用户数量。

total_user_age = data['好坏客户'].groupby(by=age_cut).count()

获取各个分组中坏用户的数量。
0表示好用户,1表示坏用户,因此可以直接求和来获取坏用户数量。

bad_user_age = data['好坏客户'].groupby(by=age_cut).sum()

连接操作

age_cut_group = pd.concat((total_user_age, bad_user_age), axis=1)
age_cut_group.columns = ['总客户数量', '坏客户数量']
'''
 			总客户数量 	坏客户数量
年龄
(20.914, 38.2] 	27996 		 2933
(38.2, 55.4] 	57988 		 4593
(55.4, 72.6] 	46130 		 1841
(72.6, 89.8] 	12623 		  285
(89.8, 107.0] 	  553 		   12
'''

填加表示好客户数量的列和坏客户占比的列。

age_cut_group['好客户数量'] = age_cut_group['总客户数量'] - age_cut_group['坏客户数量']
age_cut_group['坏客户数量占比'] = age_cut_group['坏客户数量'] / age_cut_group['总客户数量']
'''
			总客户数量 	坏客户数量 	好客户数量 	坏客户数量占比
年龄 				
(20.914, 38.2] 	27996 	     2933 	    25063 	     0.104765
(38.2, 55.4] 	57988 	     4593 	    53395 	     0.079206
(55.4, 72.6] 	46130 		 1841 	    44289 	     0.039909
(72.6, 89.8] 	12623 		  285 	    12338 		 0.022578
(89.8, 107.0] 	  553 		   12 	      541 		 0.021700
'''
from pylab import mpl

mpl.rcParams['font.sans-serif'] = ['FangSong']
mpl.rcParams['axes.unicode_minus'] = False

ax1 = age_cut_group[['好客户数量', '坏客户数量']].plot.bar(figsize=(10,5))
ax1.set_xticklabels(age_cut_group.index, rotation=15)
ax1.set_ylabel('客户数量')

在这里插入图片描述

ax2 = age_cut_group['坏客户数量占比'].plot(figsize=(10, 5))
ax2.set_xticklabels([0, 20, 29, 38, 47, 55, 64, 72, 81, 89, 98, 107])
ax2.set_ylabel("坏客户率")
ax2.set_title("坏客户率随年龄的变化趋势图")

在这里插入图片描述

1.4.2.2 IV&WOE编码

封装计算WOE的函数get_woe_data,参数cut表示每个分组的数据。
这里将坏用户设置为正例样本,好用户设置为反例样本。

def get_woe_data(cut):
    gb = data['好坏客户'].value_counts()  # 整个样本中正反例(好坏客户)样本数量
    gi = pd.crosstab(cut, data['好坏客户'])  # 每组中正反例(好坏客户)样本数量
    gbi = (gi[1] / gi[0]) / (gb[1] / gb[0])
    woe = np.log(gbi)
    return woe

封装计算IV的函数get_iv_data,参数cut表示每个分组的数据。

def get_iv_data(cut):
    gb = data['好坏客户'].value_counts()  # 整个样本中好坏客户样本数量
    gi = pd.crosstab(cut, data['好坏客户'])  # 每组中正反例样本数量
    gbi = (gi[1] / gi[0]) / (gb[1] / gb[0])
    woe = np.log(gbi)
    # iv = (py - pn) * woe 中的py表示分组中正例样本数量与整个样本集中正例样本数量的比值。
    iv = ((gi[1] / gb[1]) - (gi[0] / gb[0])) * woe

对比使用cut和qcut分箱

  1. 使用cut
cut1_temp = pd.cut(data["可用额度比值"], 4)

pd.crosstab(cut1_temp, data['好坏客户'])
'''
		  好坏客户 	0 		1
可用额度比值 		
(-50.708, 12677.0] 	135616 	9664
(12677.0, 25354.0] 	8 		0
(25354.0, 38031.0] 	1 		0
(38031.0, 50708.0] 	1 		0
'''

get_woe_data(cut1_temp)
'''
(-50.708, 12677.0]    0.000074
(12677.0, 25354.0]        -inf
(25354.0, 38031.0]        -inf
(38031.0, 50708.0]        -inf
'''
  1. 使用qcut
cut1 = pd.qcut(data["可用额度比值"], 4)

pd.crosstab(cut1, data['好坏客户'])
'''
		好坏客户 	0 	    1
可用额度比值 		
(-0.001, 0.0311] 	35645 	678
 (0.0311, 0.158] 	35575 	747
  (0.158, 0.558] 	34493 	1829
(0.558, 50708.0] 	29913 	6410
'''

get_woe_data(cut1)
'''
(-0.001, 0.0311]   -1.320723
(0.0311, 0.158]    -1.221840
(0.158, 0.558]     -0.295494
(0.558, 50708.0]    1.101060
'''

因此,对于特征可用额度比值,使用qcut函数进行分箱操作。

计算IV

get_iv_data(cut1)
'''
可用额度比值
(-0.001, 0.0311]    0.254452
(0.0311, 0.158]     0.226047
(0.158, 0.558]      0.019226
(0.558, 50708.0]    0.487474
'''

获取最优的分箱数
分箱数量的不同会导致特征IV值各不相同,因此可以使用学习曲线寻找特征最优时的分箱数。
这里以年龄特征为例,取分箱个数范围为5-15进行测试。

ivs = []
bins = []
for i in range(5, 15):
    cut = pd.cut(data['年龄'], bins=i)
    iv_value = get_iv_data(cut).sum()
    ivs.append(iv_value)
    bins.append(i)
    
plt.plot(bins,ivs)

在这里插入图片描述

特征选择

分箱操作

cut1 = pd.qcut(data["可用额度比值"], 4)  # 使用qcut分箱

cut2 = pd.cut(data["年龄"], 8)

bins3 = [-1, 0, 1, 3, 5, 13]
cut3 = pd.cut(data["逾期30-59天笔数"], bins3)

cut4 = pd.qcut(data["负债率"], 3)

cut5 = pd.qcut(data["月收入"], 4)

cut6 = pd.cut(data["信贷数量"], 4)

bins7 = [-1, 0, 1, 3, 5, 20]
cut7 = pd.cut(data["逾期90天笔数"], bins7)

bins8 = [-1, 0, 1, 2, 3, 33]
cut8 = pd.cut(data["固定资产贷款量"], bins8)

bins9 = [-1, 0, 1, 3, 12]
cut9 = pd.cut(data["逾期60-89天笔数"], bins9)

bins10 = [-1, 0, 1, 2, 3, 5, 21]
cut10 = pd.cut(data["家属数量"], bins10)

计算出每个特征对应的IV值。

cut1_iv = get_iv_data(cut1).sum()
cut2_iv = get_iv_data(cut2).sum()
cut3_iv = get_iv_data(cut3).sum()
cut4_iv = get_iv_data(cut4).sum()
cut5_iv = get_iv_data(cut5).sum()
cut6_iv = get_iv_data(cut6).sum()
cut7_iv = get_iv_data(cut7).sum()
cut8_iv = get_iv_data(cut8).sum()
cut9_iv = get_iv_data(cut9).sum()
cut10_iv = get_iv_data(cut10).sum()
from pylab import mpl

mpl.rcParams['font.sans-serif'] = ['FangSong']
mpl.rcParams['axes.unicode_minus'] = False

IV = pd.DataFrame([cut1_iv, cut2_iv, cut3_iv, cut4_iv, cut5_iv, cut6_iv, cut7_iv, cut8_iv, cut9_iv, cut10_iv],
                  index=['可用额度比值', '年龄', '逾期30-59天笔数', '负债率', '月收入', '信贷数量', '逾期90天笔数', '固定资产贷款量', '逾期60-89天笔数',
                         '家属数量'], columns=['IV'])
iv = IV.plot.bar(color='b', alpha=0.3, rot=30, figsize=(10, 5), fontsize=(10))
iv.set_title('特征变量与IV值分布图', fontsize=(15))
iv.set_xlabel('特征变量', fontsize=(15))
iv.set_ylabel('IV', fontsize=(15))

在这里插入图片描述

使用IV值替换原始特征数据。

def map_op(cut):
    # 获取每一列特征分箱后的映射关系表
    dic = get_iv_data(cut).to_dict()
    # 进行映射操作,将原始数据替换成特征的IV值
    return cut.values.map(dic)

cuts = [cut1, cut2, cut3, cut4, cut5, cut6, cut7, cut8, cut9, cut10]
cuts_iv_list = []
for cut in cuts:
    cuts_iv_list.append(map_op(cut))
data_arr = np.array(cuts_iv_list)
new_df = pd.DataFrame(data=data_arr).T
new_df.columns = ['可用额度比值', '年龄', '逾期30-59天笔数', '负债率', '月收入', '信贷数量', '逾期90天笔数', '固定资产贷款量', '逾期60-89天笔数', '家属数量']
new_df['好坏客户'] = data['好坏客户']

空值存在的原因:
分箱所设置的箱子范围中可能不包含所有的特征数据,做映射时所有分组范围外的数据不能匹配到任何分组的IV值,因此为空值。

new_df.isnull().sum()
'''
可用额度比值        0
年龄            0
逾期30-59天笔数    0
负债率           0
月收入           0
信贷数量          0
逾期90天笔数       0
固定资产贷款量       1
逾期60-89天笔数    0
家属数量          0
好坏客户          0
'''

new_df.dropna(axis=0, inplace=True)
1.5 构建逻辑回归模型

属于二分类问题

from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score, f1_score
from sklearn.linear_model import LogisticRegression

feature = new_df.iloc[:, :-1]  # 取前十列数据,为特征。
target = new_df.iloc[:, -1]  # 取最后一列数据,为标签。
x_train, x_test, y_train, y_test = train_test_split(feature, target, test_size=0.1, random_state=0)

l = LogisticRegression().fit(x_train, y_train)

y_pred = l.predict(x_test)
f1_score(y_test, y_pred, average='micro')  # 0.9364718838185697

# 二分类问题需要使用ROC评估模型。
y_score = l.predict_proba(x_test)[:, 1]  # 正例样本(坏用户)的分类概率
roc_auc_score(y_test, y_score)  # 0.7756002482215016

# 评分:每个用户分到反例样本(好用户)的概率 * 100
l.predict_proba(x_test)[:, 0] * 100

2 无监督学习与聚类算法

2.1 无监督学习

有监督学习的模型算法使用的样本数据既需要特征数据,也需要标签数据。
有监督学习的模型算法只需要使用特征数据。

2.2 聚类算法
2.2.1 介绍

聚类算法主要工作是将数据划分成有意义或有用的组(或簇),这种划分可以基于我们的业务需求或建模需求来完成,也可以单纯地帮助我们探索数据的自然结构和分布。

2.2.2 聚类和分类区别

在这里插入图片描述
在这里插入图片描述

3 KMeans算法

3.1 介绍
3.1.1 簇与质心

簇:KMeans算法将一组N个样本的特征矩阵X划分为K个无交集的簇,直观上来看是簇是一个又一个聚集在一起的数据,在一个簇中的数据就认为是同一类,簇就是聚类的结果表现。
质心:簇中所有数据的均值u通常被称为这个簇的“质心”(centroids)。
在一个二维平面中,一簇数据点的质心的横坐标就是这一簇数据点的横坐标的均值,质心的纵坐标就是这一簇数据点的纵坐标的均值。同理可推广至高维空间。
质心的个数与聚类后的类别数是一致的。

3.1.2 KMeans算法原理

在KMeans算法中,簇的个数K是一个超参数,需要我们人为输入来确定。
KMeans的核心任务是根据我们设定好的K,找出K个最优的质心,并将离这些质心最近的数据分别分配到这些质心代表的簇中去。具体过程可以总结如下:
在这里插入图片描述
在每次迭代中被分配到这个质心上的样本都是一致的,即每次新生成的簇都是一致的,所有的样本点都不会再从一个簇转移到另一个簇,质心就不会变化了。
这个过程在可以由下图来显示,我们规定,将数据分为4簇(K=4),其中白色X代表质心的位置:
在这里插入图片描述

3.1.3 分类结果

聚类算法聚出的类有什么含义呢?这些类有什么样的性质?
我们认为,被分在同一个簇中的数据是有相似性的,而不同簇中的数据是不同的,当聚类完毕后,我们就要分别去研究每个簇中的样本都有什么样的性质,从而根据业务需求制定不同的商业或者科技策略。
聚类算法追求**“簇内差异 小,簇外差异 大”**,而这个“差异“,由样本点到其所在簇的质心的距离来衡量。

对于一个簇来说,所有样本点到质心的距离之和越小,我们就认为这个簇中的样本越相似,簇内差异就越小。而距离的衡量方法有多种,令x表示簇中的一个样本点,u表示该簇中的质心,n表示每个样本点中的特征数目,i表示组成点的每个特征,则该样本点到质心的距离可以由以下距离来度量:
在这里插入图片描述

3.2 损失函数
3.2.1 簇内平方和

如我们采用欧几里得距离,则一个簇中所有样本点到质心的距离的平方和为簇内平方和,使用簇内平方和就可以表示簇内差异的大小。

3.2.2 整体平方和

将一个数据集中的所有簇的簇内平方和相加,就得到了整体平方和(Total Cluster Sum of Square),又叫做total inertia。整体平方和越小,代表着每个簇内样本越相似,聚类的效果就越好。

KMeans追求的是,求解能够让簇内平方和最小化的质心。实际上,在质心不断变化不断迭代的过程中,整体平方和是越来越小的。当整体平方和最小的时候,质心就不再发生变化了。如此,K-Means的求解过程,就变成了一个最优化问题。
因此我们认为:
在KMeans中,我们在一个固定的簇数K下,通过最小化整体平方和来求解最佳质心,并基于质心的存在去进行聚类。并且,整体距离平方和的最小值其实可以使用梯度下降来求解。因此,有许多博客和教材都这样写道:簇内平方和/整体平方和是KMeans的损失函数。
但是也有人认为:
损失函数本质是用来衡量模型的拟合效果的(损失越小,模型的拟合效果越好),只有有着求解参数需求的算法,才会有损失函数。Kmeans不求解什么参数,它的模型本质也没有在拟合数据,而是在对数据进行一种探索。所以如果你去问大多数数据挖掘工程师,甚至是算法工程师,他们可能会告诉你说,K-Means不存在 什么损失函数,整体平方和更像是Kmeans的模型评估指标,而非损失函数。

3.3 代码实现
class sklearn.cluster.KMeans (n_clusters=8, init=’k-means++’, n_init=10, max_iter=300, tol=0.0001, precompute_distances=’auto’, verbose=0, random_state=None, copy_x=True, n_jobs=None, algorithm=’auto’)


n_clusters
n_clusters是KMeans中的k,表示着我们告诉模型我们要分几类。这是KMeans当中唯一一个必填的参数,默认为8 类,但通常我们的聚类结果会是一个小于8的结果。通常,在开始聚类之前,我们并不知道n_clusters究竟是多少, 因此我们要对它进行探索。

random_state
用于初始化质心的生成器。

KMeans的首次探索
当我们拿到一个数据集,如果可能的话,我们希望能够通过绘图先观察一下这个数据集的数据分布,以此来为我们聚类时输入的n_clusters做一个参考。 首先,我们来自己创建一个数据集使用make_blobs。这样的数据集是我们自己创建,所以是有标签的。

from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt

X, y = make_blobs(n_samples=500, n_features=2, centers=4, random_state=10)
X.shape  # (500, 2)

plt.scatter
# 将原始已经有类别的样本数据绘制在散点图中,每一个类别使用不同颜色来表示
color = ['red', 'pink', 'orange', 'gray']
fig, ax1 = plt.subplots(1)
for i in range(4):
    ax1.scatter(X[y == i, 0], X[y == i, 1], c=color[i], s=8)
plt.show()

聚类效果

from sklearn.cluster import KMeans

n_clusters = 4
cluster = KMeans(n_clusters=n_clusters)
cluster.fit(X)
KMeans(n_clusters=4)

基于这个分布,我们来使用Kmeans进行聚类。首先,我们要假设一下,这个数据中有几簇。

# 重要属性Labels_,查看聚好的类别,每个样本所对应的类
y_pred = cluster.labels_
y_pred
# 重要属性cLuster_centers_,查看质心
centroid = cluster.cluster_centers_
centroid
# 重要属性inertia_,查看总距离平方和
inertia = cluster.inertia_
inertia
# 画出聚类结果
color = ['red', 'pink', 'orange', 'gray']
fig, ax1 = plt.subplots(1)
for i in range(n_clusters):
    ax1.scatter(X[y_pred == i, 0], X[y_pred == i, 1], c=color[i], s=8)
plt.show()
  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值