metaX

V1

2022/08/12阅读:9主题:默认主题

个人信用风险评估项目

个人信用风险评估项目

本文由博雅数智暑期项目整理而来:

2007-2008年的全球金融危机凸显了透明度严密性在银行业务中的重要性。由于信贷供应受到了限制,所以银行正日益紧缩其贷款体系,转向机器学习来更准确地识别高风险贷款。通过构建自动化的信用评分模型,以在线方式进行即时的信贷审批能够为银行节约很多人工成本。

image-20220811181928324
image-20220811181928324

读取数据并了解基本信息

通过Pandas库来进行数据的读取,以及查看数据的基本信息,来对数据作基础的了解。

本项目使用的数据集包含1000条个人数据,每条数据包含20个贷款相关信息字段以及1个违约类别字段,记录该笔贷款是否违约。(下图为部分数据集)

  1. 数据集描述
image-20220811183302821
image-20220811183302821
  1. 读取数据集pd.read_csv()
  2. 查看数据基本情况dataframe.info()
  3. 查看数据基本统计信息dataframe.describe()

探索性分析

通过Python中的绘图库如Matplotlib、Seaborn等,利用一系列可视化的手段,通过绘图的方式展示数据字段的取值分布以及数据字段间相关关系。利用Sklearn中的SelectKBest方法,计算连续型和离散型特征与标签之间的相关性,对数据进行更深层次的分析。

  • 绘制违约与未违约数量柱状图,查看违约人数与未违约人数的分布情况。

    sns.countplot(x ='default', data =credit, palette= 'Set3' )

  • 绘制贷款持续时间违约率条形图,查看贷款持续时间与违约之间的关系。

  • #groupby操作计算各个分组的违约率

    #对'months_loan_duration'进行离散化
    months = (0,6,12,18,24,30,36,42,48,54,72)
    months_cut = pd.cut(x = credit['months_loan_duration'],bins = months)

    months_rate = credit.groupby(months_cut)['default'].apply(lambda x:x.sum()/len(x))

    sns.barplot(x = months_rate.values, y = months_rate.index,palette = 'Set2')
  • 绘制违约与未违约贷款金额分布分组条形图,查看贷款金额与违约之间的关系。

    interval = (0,200040006000,8000,10000,12000,14000,16000,20000)
    #对amount进行分组
    amount_cut = pd.cut(credit['amount'], bins =interval)
    #绘制柱状图
    fig = plt.figure(figsize=(7,5))
    sns.countplot(y = amount_cut,hue='default',data = credit, palette = 'Set2')
  • 绘制信用记录违约率条形图,查看信用记录与违约之间的关系。

    #分组,并计算违约率
    credit_history_rate = credit.groupby('credit_history')['default'].apply(lambda x:x.sum()/len(x))
    #绘制条形图
    fig = plt.figure(figsize=(7,5))
    sns.barplot(x = credit_history_rate.values,y = credit_history_rate.index, palette = 'Set3' )
    plt.xlabel('违约率',fontsize=10)
    plt.ylabel('信用记录',fontsize=10)
    plt.title('信用记录违约率条形图',fontsize=13)
    plt.box(False)
  • 数据可视化小提琴图分析

    sns.violinplot(x = credit['default'],y =credit['age'])
    plt.xticks([0,1],['未违约','违约'])
    plt.box(False)
    
  • 绘制连续型变量与标签之间的相关性柱状图,查看连续型字段与是否违约之间的相关性大小。

    • 建模目标为离散型,常用衡量连续型和离散型特征相关性的方法是(ANOVA)
    • 原理:我们可以把样本所属的不同的目标特征视作不同的总体,假设是个二分类问题,我们要考虑特征 ,如果 这个特征对预测类别有很大的帮助,它必然存在这样一种特性:即当样本 属于正类时, 会取某些特定的值(视作集合 ,当样本 属于 负类时, 会取另一些特定的值(视作集合 )。集合 呈现出的差异越大,特 征 对类别的预测能力就越强。落实到方差分析问题上,即我们需要验假设 ,我们当然希望拒绝 ,所以希望构造出来的 值越大越好。即值越大,拒绝 的把握也越大,越有理由相信 ,越有把握认为集合 呈现出巨大差异,也就说 这个特征对预测类别的帮助也越大! 所以我们可以根据样本的某个特征 值来判断特征;对预测类别的帮助,值越大,预测能力也就越强,相关性就越大,从而基于此可以进行特征选择。
  • Sklearn中的SelectKBest()方法可用于特征选择,根据给定的方法,选择出前k个与目标最相关的特征,其主要参数如下:

    • score_func:用于计算相关性的函数,函数接受两个数组X和y,并返回一对数组(scorespvalue)或带scores的单个数组,scores表示得分,得分越高相关性越强,pvalue表示检验的P值,值越小表示相关性越强,默认值为f_classif,表示计算方差分析的F值,其它取值如下: 1.f_regression:计算相关系数的F值,适用于连续型与连续型的相关性计算 2.chi2:计算卡方统计量,适用于离散型与离散型的相关性计算 3.mutual_info_classif:离散型与离散型的互信息计算 4.mutual_info_regression:连续型与连续型的互信息计算

    • k:需要选择的最佳特征个数,默认值为10。

    • SelectKBest()方法调用fit()方法,传入X和y两组特征,计算它们之间的相关性,调用SelectKBest()方法的scores_属性得到相关性得分,调用pvalues_属性得到检验的P值

# 局部代码
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif

feature_int = credit.dtypes[credit.dtypes=='int64'].index[:-1]
#定义SelectKBest对象
selector = SelectKBest(score_func = f_classif,k =len(feature_int))
#使用fit进行训练
selector.fit(credit[feature_int],credit['default'])
#将相关性大小绘制条形图
fig = plt.figure(figsize=(7,5))
sns.barplot(x = selector.scores_, y = feature_int, palette='Set3')
  • 绘制离散型变量与标签之间的相关性柱状图,查看离散型字段与是否违约之间的相关性大小。

    • 卡方检验:统计样本的实际观测值与理论推断值之间的偏离程度。实际观测值与理论推断值之间的偏离程度就决定卡方值的大小,如果卡方值越大,二者偏差程度越大;反之,二者偏差越小。若两个值完全相等时,卡方值就为0。

      卡方检验的公式为:

      ,其中 是真实值, 是理论值

      卡方检验是以卡方分布为基础的一种常用假设检验方法,假设 :观察频数与期望频数没有差别。卡方检验的基本思想是:首先假设 成立,基于此前提计算出 值,它表示观察值与理论值之间的偏离程度。根据 分布及自由度可以确定在 假设成立的情况下获得当前统计量及更极端情况的概率 。如果 值很小,说明观察值与理论值偏离程度太大,应当拒绝无效假设,表示比较对象之间有显著差异;否则不能拒绝假设 ,不能认为样本所代表的实际情况和理论假设有差别。

from sklearn.preprocessing import LabelEncoder
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2

#得到离散型特征列名
feature_obj = credit.dtypes[credit.dtypes=='object'].index
#数字编码
for col in feature_obj:
    credit.loc[:,col] = LabelEncoder().fit_transform(credit[col])
#定义SelectKBest对象
selector= SelectKBest(chi2, k =len(feature_obj))
#使用fit进行训练
selector.fit(credit[feature_obj],credit['default'])
#将相关性大小绘制条形图
fig = plt.figure(figsize=(7,5))
sns.barplot(x = selector.scores_, y = feature_obj,palette ='Set2')

插曲:

  • 在Python的世界中,object是父子关系的顶端,所有的数据类型的父类都是它;type是类型实例关系的顶端,所有对象都是它的实例的。它们两个的关系可以这样描述:object是一个type,object is an instance of type。即Object是type的一个实例。

  • Python3 中,bool 是 int 的子类,True 和 False 可以和数字相加,True==1、 False==0 会返回 True

Baseline模型构建

  1. 训练集测试集划分

    train_test_split(X, y, test_size=None, random_state=None,stratify=y)

  • X,y:分别为预测所需的所有特征以及需要预测的特征(即违约情况default)。
  • test_size:测试集比例,例如test_size=0.25表示划分25%的数据作为测试集。
  • random_state:随机种子,因为划分过程是随机的,为了每次划分都得到相同的训练集和测试集,需要设置一个随机种子数,使结果重现。
  • stratify:使用分层采样,保证训练集和测试集中违约和未违约客户的比例相同,并且与总体数据中的比例保持一致。
from sklearn.model_selection import train_test_split

y = credit['default'].values
X  =  credit.drop('default',axis=1)
#训练集测试集划分
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size =.25,random_state=10,stratify=y)

print(X_train.shape)
print(X_test.shape)
  1. 标准化和数值编码

    • 标准化:要构建逻辑回归模型,并且默认会添加L2正则化项,所以系数的大小会影响优化效果,所以在本节中,我们需要首先对连续型数据进行标准化。

    • Z-score标准化是常用的标准化方式,标准化的数据均值为0,方差为1,其公式为:

    • 在Sklearn的preprocessing模块中,StandardScaler()类可以对连续型数据进行Z-socre标准化,可调用的主要方法有:

      • fit: 用于计算训练数据的均值和方差,后面用均值和方差来转换数据。
      • transform: 按照保存的均值和方差转换数据
      • fit_transform: 不仅计算训练数据的均值和方差,还会基于计算出来的均值和方差来转换数据。
    • One-Hot编码:在建模之前,对于离散型特征,首先需要将字符映射为数字,但数字编码会给离散型特征引入原本不存在的次序关系,会使得距离计算变得不合理。为了解决这个问题,我们采用One-Hot编码来消除这种次序关系。One-Hot编码将包含K个取值的离散型字段转换成K个取值为0或1的二元特征。由于下一节会建立逻辑回归模型,为保证系数的大小也可以反映不同特征对目标的影响程度,需要对离散特征进行One-Hot编码,消除数字编码带来的次序关系。

      Pandas中的get_dummies()函数可以进行One-Hot编码,主要参数如下:

      • data:输入的数据,Series或DataFrame对象。
      • prefix:One-Hot编码后,新特征列名的前缀,默认为None。
      • prefix_sep:新特征列名的中间的分隔符,默认为'_'。
      • columns:指定需要实现One-Hot编码的列名。
      • drop_first:去除第一个二元特征,K个取值转换成K-1个二元特征,相当于进行哑编码。
import pandas as pd
from sklearn.preprocessing import StandardScaler
# 标准化
scaler = StandardScaler()
#训练并转换数据
X_train.loc[:,feature_int] = scaler.fit_transform(X_train[feature_int])
#转换数据
X_test.loc[:,feature_int] = scaler.fit_transform(X_test[feature_int])

#One-Hot编码
all_onehot = pd.get_dummies(pd.concat([X_train[feature_obj],X_test[feature_obj]]).astype('object'),drop_first=False)
#合并标准化和One-Hot编码后的数据
X_train = pd.concat([all_onehot.iloc[:X_train.shape[0],:],X_train[feature_int]],axis=1)
X_test = pd.concat([all_onehot.iloc[X_train.shape[0]:,:],X_test[feature_int]],axis=1)

  1. 逻辑回归模型训练
    • 使用Scikit-learn中的LogisticRegression类可以构建逻辑回归模型,LogisticRegression算法位于sklearn.linear_model包,首先将其导入,然后再将划分好的训练集X_triany_train带入模型中,调用fit方法进行模型训练。
    • LogisticRegression模型参数如下:
      • penalty:正则化项,也称为惩罚项,可选参数为l1和l2,默认为l2。如果在调参时主要目的是解决过拟合,一般会选择l2正则化。但是当预测结果不好时,可选用l1正则化。
      • C:正则化系数\lambdaλ的倒数,float类型,默认为1.0,必须是正浮点数类型,数值越小则反应正则化越强。
      • fit_intercept:是否拟合截距,默认为True,布尔类型。
      • class_weight:类别权重,可以是字典或者字符串,默认值为None也就是不考虑权重。如果选择balanced,会根据训练样本类别占比分配类别权重,某种类型的样本量越多,则权重越低,样本量越少,则权重越高。
      • random_state:随机种子,默认为None,仅在优化方法为sag或liblinear时有效。
      • solver:逻辑回归的优化方法。 liblinear:使用开源的liblinear库实现,使用坐标轴下降法来迭代优化损失函数。 lbfgs:拟牛顿法的一种,利用损失函数二阶导数也即Hessian矩阵来迭代优化损失函数。 newton-cg:利用损失函数二阶导数也即Hessian矩阵来迭代优化损失函数。 sag:随机平均梯度下降,与普通梯度下降法的区别是每次迭代仅仅用一部分的样本来计算梯度。
      • max_iter:算法收敛的最大迭代次数,默认为100。仅在正则优化算法为newton-cg、sag和lbfgs时有效。
      • multi_class:面对多分类问题的分类方式,默认为'auto。'
from sklearn.linear_model import LogisticRegression
#新建模型对象
model_lr_base = LogisticRegression(class_weight = 'balanced',random_state=10)
#模型训练
model_lr_base.fit(X_train,y_train)
  1. 逻辑回归模型评估
  • 训练模型后,可以使用模型在测试集X_test上作出预测。在Sklearn的分类算法中,通用的预测方法有两种:predictpredict_proba, 前者直接预测分类标签,后者则输出概率估计(取值在0-1之间)。对于二分类问题,predict_proba方法返回形式为一个二维的数组对象,若两类用0和1表示,第一列表示样本属于0类的概率,第二列表示样本属于1类的概率,它们相加和为1。

  • 类别不平衡的二分类问题,有一些特定的模型评价指标。首先是混淆矩阵(Confusion Matrix),若记0类为负类,1类为正类(一般数量较少的类别被称为正类),则对于真实的负类样本,我们可能预测其为正类或负类,同样对于真实为正类的样本,我们也可能预测其为负类或正类,这样会产生四种可能性,构成下列2×2的矩阵,称为混淆矩阵。

    • 从混淆矩阵可以得到两组评价指标

    • 第一组为精准率(Precision)与召回率(Recall):

      • Precision ,全部预测为正类的样本中被预测正确的比例
      • Recall , 实际为正类的样本中被预测正确的比例
    • 通过设置不同的分类阈值,可以得到不同的Precision和Recall值,一组 (Recall, Precision) 可 以看作直角坐标系上的点,将这些点连接起来可以得到PR曲线(Precision Recall Curve), 。PR曲线纵轴为 Precision,横轴为Recall,衡量不同分类阈值下 Precision 随 Recall 的变化情况。PR曲线的两个指标都聚焦于正例,类别不平衡问题通常较为关心正例。 我们希望 Precision 和 Recall 都较高,所以曲线越贴近右上角越好。PR曲线的线下面积被称 为Average Precision (AP),表示每个阈值处Precision的加权平均值,将前一阈值的召回率 增加作为权重,公式为:

    • 第二组为真阳率 (True Positive Rate,TPR) 与假阳率 (False Positive Rate,FPR) :

      • 真阳率: ,实际为正类的样本中被预测正确的比例,等价于 Recall
      • 假阳率: , 实际为负类的样本中被预测错误的比例
    • 通过设置不通的阈值,可以得到不同的TPR和FPR,一组 (FPR,TPR) 也可以看作直角坐标系上的点,将这些点连接起来可以得到ROC曲线 (Receiver Operating Characteristic Curve), 衡量不同分类阈值下 TPR 随 FPR 的变化情况。我们希望 TPR 越高,同时 FPR 越低,所以曲线越靠近右上角,模型性能越好。ROC曲线中,TPR关注所有正样本,FPR 关注所有负样本,所以比较适合评估模型的整体性能。ROC曲线的线下面积被称为AUC值 (Area Under Curve),取值为 ,数值越大模型性能越好,对所有可能的分类阈值的效果进行综合衡量,反映模型对样本的排序能力。

    • 在Sklearn的metric模块中,classification_report函数可输出分类报告,内容包括Precision、Recall和F1-score(Precision和Recall的调和平均值),其主要参数为:

      • y_true:真实标签
      • y_pred:预测标签

      函数average_precision_score可用于计算AP,其主要参数为:

      • y_true:真实标签
      • y_score:正类预测概率
      • pos_label:指定正类标签

      函数roc_auc_score可用于计算AUC值,其主要参数为:

      • y_true:真实标签
      • y_score:正类预测概率
    from sklearn.metrics import classification_report,roc_auc_score,average_precision_score

    #预测标签
    model_lr_base_prelabel = model_lr_base.predict(X_test)
    #预测概率,并取正类概率
    model_lr_base_prob = model_lr_base.predict_proba(X_test)[:,1]
    #输出分类报告
    print(classification_report(y_true=y_test,y_pred=model_lr_base_prelabel))
    #输出AP值
    print('LogisticRegression '+' AP:',average_precision_score(y_test,model_lr_base_prob))
    print('LogisticRegression '+' AUC:',roc_auc_score(y_test,model_lr_base_prob))
  1. XGBoost模型训练

    XGBoost是一种集成学习算法,属于三类常用的集成方法(Bagging,Boosting,Stacking)中的Boosting算法的一种。它是一个加法模型,基模型一般选择树模型,但也可以选择逻辑回归模型。

    XGBoost属于梯度提升树(GBDT)模型的改进算法,GBDT的基本想法是让新的基模型去拟合前面模型的偏差,从而不断将加法模型的偏差降低。相比于经典的GBDT,XGBoost做了一些改进,从而在效果和性能上有明显的提升。

    • GBDT将目标函数泰勒展开到一阶,而XGBoost将目标函数泰勒展开到了二阶,保留了更多有关目标函数的信息,对提升效果有帮助;

    • XGBoost加入了叶子节点权重的L2正则化项,因而有利于模型获得更低的方差;

    • XGBoost增加了自动处理缺失值特征的策略。通过把带缺失值样本分别划分到左子树或者右子树,比较两种方案下目标函数的优劣,从而自动对有缺失值的样本进行划分,无需对缺失特征进行填充预处理;

    • XGBoost借鉴了随机森林的做法,支持列抽样,不仅能降低过拟合,还能减少计算量;

    • XGBoost还支持候选分位点切割,特征并行等;

    • XGBClassifier模型主要参数如下:

      常规参数:

      • booster:gbtree 树模型做为基分类器(默认),gbliner 线性模型做为基分类器
  • scale_pos_weight:正样本的权重,在二分类任务中,当正负样本比例失衡时,设置正样本的权重,模型效果更好。例如,当正负样本比例为1:10时,scale_pos_weight=10。

    模型参数:

    • n_estimatores:基模型的个数
  • early_stopping_rounds:在测试集上,当连续n次迭代,评价分数没有提高后,提前终止训练,防止过拟合。

    • max_depth:树的深度,默认值为6,典型值为3-10,值越大,越容易过拟合;值越小,越容易欠拟合。
    • min_child_weight:最小叶节点样本权重,默认值为1,值越大,越容易欠拟合;值越小,越容易过拟合。
    • subsample:训练每棵树时,使用的数据占全部训练集的比例,默认值为1,典型值为0.5-1,防止过拟合。
    • colsample_bytree:训练每棵树时,使用的特征占全部特征的比例,默认值为1,典型值为0.5-1,防止过拟合。

    学习任务参数:

    • learning_rate:学习率,控制每次迭代更新权重时的步长,默认为0.3,值越小,训练越慢,典型值为0.01-0.2。
  • objective 目标函数: 回归任务:reg:linear (默认)或logistic 。 二分类: binary:logistic(概率)或logitraw(类别)。 多分类:multi:softmax num_class=n(返回类别)softprob num_class=n(返回概率)。rank:pairwise 。

    • eval_metric: 回归任务(默认rmse),rmse均方根误差,mae平均绝对误差。 分类任务(默认error),auc-roc曲线下面积,error错误率(二分类),merror错误率(多分类),logloss负对数似然函数(二分类),mlogloss负对数似然函数(多分类)。
    • gamma:惩罚项系数,指定节点分裂所需的最小损失函数下降值。
    • alpha:L1正则化系数,默认为1。
    • lambda:L2正则化系数,默认为1。
  • 模型训练好后便可使用测试集X_test进行预测。

    使用sklearn.metrics中的classification_report查看模型结果的分类报告,average_precision_score计算模型的AP值,roc_auc_score计算模型的AUC值。

    函数average_precision_score计算AP,其主要参数为:

    • y_true:真实标签
  • y_score:正类预测概率

    • pos_label:指定正类标签

    函数roc_auc_score计算AUC值,其主要参数为:

    • y_true:真实标签
  • y_score:正类预测概率

from xgboost import XGBClassifier
from sklearn.metrics import classification_report,roc_auc_score,average_precision_score


#新建模型对象
model_xgb_base = XGBClassifier(n_estimators=50,learning_rate=0.1,gamma=0.65,random_state=10,n_jobs=1,eval_metric=['logloss','auc','error'])
#模型训练
model_xgb_base.fit(X_train,y_train)
#预测标签
model_xgb_base_prelabel = model_xgb_base.predict(X_test)
model_xgb_base_prob = model_xgb_base.predict_proba(X_test)[:,1]
#输出分类报告
print(classification_report(y_test,model_xgb_base_prelabel))
#输出AP值
print('XGBoost '+' AP:',average_precision_score(y_test,model_xgb_base_prob))
#输出AUC值
print('XGBoost '+' AUC:',roc_auc_score(y_test,model_xgb_base_prob))

特征工程后提升模型精度

首先对数据集进行WOE编码。WOE叫做证据权重(Weight of Evidence),是一种有监督的编码方式,将预测类别的集中度的属性作为编码的数值。数据集进行WOE编码后,表示的其实是自变量取某个值的时候对违约比例的一种影响。在金融风控模型构建领域,WOE编码十分常用,对特征进行WOE编码后,表示特征取某个值的时对违约比例的一种影响。

WOE公式:

表示每组中标签为good的数量即没有违约的数量, 为good的总数量;bad相同。

其次划分训练集和测试集,对数据进行标准化。然后构建构建逻辑回归模型和XGBoost模型进行训练,对模型进行评估,查看模型的分类报告,绘制PR曲线和ROC曲线,查看AP值以及AUC的值。查看对数据进行特征工程之后,模型效果的提升。

  1. WOE编码
  • 从category_encoders库中导入WOEEncoder,可以进行WOE编码。

    WOEEncoder常用参数:

    • cols:对指定列进行编码,如果未指定则将对所有列进行编码。
    • drop_invariant:布尔型,是否删除方差为0的列。
    • regularization:正则化,浮点型,正则化的主要目的是防止被零除。

    可调用的主要方法:

    • fit: 用于计算训练数据的WOE值,后面用其来转换数据。
    • transform: 按照保存的WOE值转换数据。
    • fit_transform: 不仅计算训练数据的WOE值,还会基于计算出来的WOE值来转换数据。
from category_encoders import WOEEncoder

#WOE编码
credit = WOEEncoder(cols=feature_obj).fit_transform(credit,credit['default'])

print(credit.head())
  1. 数据标准化
  2. 逻辑回归模型训练
  3. 逻辑回归模型评估
  4. XGBoost模型训练
  5. XGBoost模型评估

补充:

GBDT模型

GBDT(Gradient Boosting Decision Tree), 是属于集成学习中的Boosting算法, 指把多个弱学习器相加, 产生一个新的强学习器.

  • GBDT的原理是通过不断添加弱学习器, 到达减小训练过程产生的损失, 从而实现将数据分类或者回归.
  • GBDT的迭代中, 假设我们前一轮迭代得到的强学习器是 , 那么本轮迭代的目标是找到 一个CART回归树模型的弱学习器 , 使得本轮的损失 减小, 就可以得到一个新的更强的学习器。

如何拟合损失?

  • GBDT的精髓:损失函数的负梯度来拟合本轮损失的近似值
  • 例如, 回归问题的损失函数是均方误差时:
  • 则损失函数的负梯度为:

即为残差。下一轮可以直接用残差值作为样本, 训练下一个弱回归器进行拟合

  • 对于二分类问题, 使用交叉熵作为损失函数, 拟合真实概率 (1或0)与预测概率之间的差距。

XGBoost是GBDT的工程实现, 并做了改进:

  • GBDT将目标函数泰勒展开到一阶,而XGBoost将目标函数泰勒展开到了二阶,保留了更多有关目标函数的信息,对提升效果有帮助,加入了正则化项,包括单棵树复杂度控制和叶 子节点权重的L2正则化项,因而有利于模型获得更低的方差。

  • image-20220812114619830

代价敏感学习—一参数class_weight

  • 样本类别不平衡且误分类代价较高,平衡类别权重,提升少数类分类效果
  • 设置为“balanced”, 自动计算类别权重: 第 类权重 例: 样本总数为 类有20个,0类有80个,则调整后1类与0类的权重比为
  • 可自行设置类别权重,以字典形式传入,形如:

分类:

人工智能

标签:

机器学习

作者介绍

metaX
V1