Python数据分析案例61——信贷风控评分卡模型(A卡)(scorecardpy 全面解析)

案例背景

虽然在效果上,传统的逻辑回归模型通常不如现代的机器学习模型,但在风控领域,解释性至关重要。逻辑回归的解释性是这些“黑箱”模型所无法比拟的,因此,研究传统的评分卡模型依然是有意义的。 传统的评分卡模型与机器学习模型的主要差异在于特征工程的处理。机器学习模型通常不需要对数据进行大幅度的调整,而传统评分卡模型则需要对数据进行分箱、编码、计算WOE(Weight of Evidence)等处理。

并且由于评分卡模型是线性模型,还需要进行一定的变量筛选。因此,评分卡模型的特征工程相对更加复杂,但解释性更强。 至于哪种模型效果更好,取决于具体的数据鄂使用场景(老板非要看解释性那就得评分卡了,客户非要效果那就得LGBM)。在这个案例中,我们使用一个之前测试过的信贷评分数据集来构建评分卡模型,并与主流机器学习模型进行对比,以评估其效果。这个案例也展示了构建全面的A卡(授信行为)和信贷风控评分模型的整体流程,同时解析了ScoreCardPy包的用法,方便未来快速应用于相关项目。

scorecardpy 的这些用法都会演示,全面解析 数据集划分 (split_df)

变量筛选(iv, var_filter)

变量分箱(woebin, woebin_plot, woebin_adj, woebin_ply)

分数转换(scorecard, scorecard_ply)

效果评估(perf_eva, perf_psi)


数据介绍

大部分的教学,一提到ScoreCardPy包应用,马上就要掏出了他内置的德国信用卡数据集。这个其实对新手小白不是很友好的,他们也不知道这个数据集,咋样读取的,怎么做清洗。我下面会从数据读取开始一步步的进行.

本次数据是这样的:

 

最后一列一变量就代表这个人是否会违约,也就是是不是要给他贷款的和响应变量y。所以这是一个a卡模型。

当然需要本次演示的所有代码文件和数据的同学还是可以参考:信贷评分卡


代码实现

首先导入数据科学常用的包

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt 
import seaborn as sns
import scorecardpy as sc
import shap
 
plt.rcParams ['font.sans-serif'] ='SimHei'               #显示中文
plt.rcParams ['axes.unicode_minus']=False               #显示负号

然后读取数据

读取数据

train=pd.read_csv('train.csv').set_index('Id')
test=pd.read_csv('test.csv').set_index('Id')
print(train.shape,test.shape)
train.head()

可以看到训练集7500条,测试集2500条。变量维度为十六个x,一个y。

查看数据基本信息

train.info()

我们可以看到数据的缺失率参差不齐,并且有一些object的对象这些不能放入模型直接训练,得进行一个转化。

变量的含义的简介:

查看一下标签 分布

train['Credit Default'].value_counts()

可以看到大概的比例是好样本为五,坏样本为二,比例还算较为均衡,没有那么极端。

非数值型变量查看其描述性统计。

train.select_dtypes(exclude=['number']).describe()

可以看到这几个类别变量的类别都不是很多,我们可以直接进行因子化,label encoder。

我们自定义一个函数,不仅训练集要进行同样的转化,测试集也要同样进行类别变量的转化。

def ensure_category_consistency(train, test, columns):
    for col in columns:
        # 找到所有可能的类别
        categories = pd.Categorical(train[col]).categories  #.append(test[col])
        # 定义一致的类别顺序
        train[col] = pd.Categorical(train[col], categories=categories)
        test[col] = pd.Categorical(test[col], categories=categories)
        # 转换为category数据类型
        train[col] = train[col].astype('category')
        test[col] = test[col].astype('category')
    return train, test

### 类别变量转化
columns_to_convert = train.select_dtypes(exclude=['number']).columns.to_list()
train, test = ensure_category_consistency(train, test, columns_to_convert)

查看数据信息

train.info()

现在可以看到所有的数据基本上都是数值型和类别型变量,可以直接进行模型计算。


变量筛选

做机器学习其实大体上是不用进行变量筛选的,变量越多越好,但是做传统的评分卡模型,由于它是线性模型,所以它不能用那么多变量,它得进行一定的筛选,得把一些解释性不好的变量,效果不好的变量给它进行去掉。

其主要用法解析:

默认的参数配置为:iv_limit=0.02, missing_limit=0.95, identical_limit=0.95,即当某个变量的 IV 值小于0.02,或缺失率大于95%,或同值率(除空值外)大于95%,则剔除掉该变量。

此外,该方法还内置了除上述以外的其他参数:

def var_filter(dt, y, x=None, iv_limit=0.02, missing_limit=0.95,  
               identical_limit=0.95, var_rm=None, var_kp=None, 
               return_rm_reason=False, positive='bad|1')
其中各参数含义如下:

varrm可设置强制保留的变量,默认为空;
varkp可设置强制剔除的变量,默认为空;
return_rm_reason可设置是否返回剔除原因,默认为不返回(False);
positive可设置坏样本对应的值,默认为“bad|1”。

下面进行变量筛选,我们使用iv最小为0.02的这个条件。(不懂iv是啥的可以去搜一下)

dt_s = sc.var_filter(train, y="Credit Default",iv_limit=0.02, missing_limit=0.95, identical_limit=0.95,return_rm_reason=True)

我们这里打开了返回原因的这个参数,因为所以它后面会返回一个字典,我们来查看一下这个字典是什么。

dt_s['rm']  ## return_rm_reason=True 返回一个字典

可以看到它移除了5个变量,并且它这个方法返回的rm的这个键对应的值是一个data frame,他清楚地告诉你哪些变量是v小于0.02才导致他们被移除的。

data_train=dt_s['dt']
data_train.info()

现在只剩下十二个变量了,x只有十一个了

我们对测试集也进行同样的变量过滤

### 测试集也进行同样的变量过滤,但是没y
test=test[data_train.columns[:-1]]

这里附赠一个,我自己写的如何能全面的看iv的一个方法吧。

### 变量筛选咋定义看IV的方法
def calculate_pred_proba_bin(true_labels,predictions, bins=10):
    # 创建分箱区间
    bin_edges = np.linspace(0, 1, bins + 1)
    # 分箱
    bin_labels = [f"{bin_edges[i]:.2f}-{bin_edges[i+1]:.2f}" for i in range(len(bin_edges)-1)]
    bin_indices = np.digitize(predictions, bin_edges, right=False) - 1
    
    # 创建数据框
    df = pd.DataFrame({ 'bin': [bin_labels[i] for i in bin_indices], 'label': true_labels })
    
    # 统计各个分箱的总数、类别为0和1的样本数
    result = df.groupby('bin')['label'].agg(total='count',
        count_0=lambda x: (x == 0).sum(),
        count_1=lambda x: (x == 1).sum()).reset_index()
    
    # 计算坏样本率和坏样本在所有坏样本中的比例
    total_bad_samples = result['count_1'].sum()
    result['bad_rate'] = result['count_1'] / result['total']
    result['bad_percent'] = result['count_1'] / total_bad_samples
    #result=result.sort_values('bin',ascending=False)
    result['lift']=result['bad_rate']/(total_bad_samples/result['total'].sum())
    result['cumulative_bad_percent'] = result['bad_percent'][::-1].cumsum()[::-1]
    return result.style.bar(color='skyblue').format(subset=['bad_rate','bad_percent','lift','cumulative_bad_percent'], precision=4)
 
def scorecardpy_display_bin(bins_info):
    df_list = []
    for col, bin_data in bins_info.items():
        df = pd.DataFrame(bin_data)
        df_list.append(df)
 
    result_df = pd.concat(df_list, ignore_index=True)
    # 增加 lift 列
    total_bad = result_df['bad'].sum()   ;   total_count = result_df['count'].sum()
    overall_bad_rate = total_bad / total_count
    result_df['lift'] = result_df['badprob'] / overall_bad_rate
    result_df=result_df.sort_values(['total_iv','variable'],ascending=False).set_index(['variable','total_iv','bin'])[['count_distr',
                                'count','good','bad','badprob','lift','bin_iv','woe']]
 
    return  result_df.style.format(subset=['count','good','bad'], precision=0).format(subset=['count_distr', 'bad','lift',
           'badprob','woe','bin_iv'], precision=4).bar(subset=['badprob','bin_iv','lift'], color=['#d65f5f', '#5fba7d'])
train_miss=train.copy()
train_miss['Years in current job']=train_miss['Years in current job'].astype('str').fillna('missing').astype('category')
bins_adj = sc.woebin(train_miss, y="Credit Default")
scorecardpy_display_bin(bins_adj)

这个是经过排序的,可以清楚看到哪些变量,他们对应的IV是多少,他们的分箱情况是怎么样的,并且每一箱的换样本浓度还有提升度以及他们对iv的贡献都在上面,可能一目了然。


数据集划分

虽然sklearn库有它自带的划分训练集的方式,但是我们的sc包它也有。划分训练集和验证集的方法,我们就用他的好了。

train, val = sc.split_df(data_train, 'Credit Default', ratio=0.7, seed=186).values()
print(train.shape, val.shape )

可以看到我们的训练集大概有5250条数据,12个变量,其中11个是x,一个是y,我们的验证集是2250条。也是12个变量。


变量分箱

可通过woebin()函数对全部变量进行自动分箱,并基于woe_bin的结果,使用woebin_plot对各变量分箱的count distribution和bad probability进行可视化,可观察是否存在单调性:

woebin()函数包括如下参数:

def woebin(dt, y, x=None, 
           var_skip=None, breaks_list=None, special_values=None, 
           stop_limit=0.1, count_distr_limit=0.05, bin_num_limit=8, 
           # min_perc_fine_bin=0.02, min_perc_coarse_bin=0.05, max_num_bin=8, 
           positive="bad|1", no_cores=None, print_step=0, method="tree",
           ignore_const_cols=True, ignore_datetime_cols=True, 
           check_cate_num=True, replace_blank=True, 
           save_breaks_list=None, **kwargs)
woebin()可针对数值型和类别型变量生成最优分箱结果,方法可选择决策树分箱、卡方分箱或自定义分箱。其他各参数的含义如下:

var_skip: 设置需要跳过分箱操作的变量;
breaks_list: 切分点列表,默认为空。如果非空,则按设置的切分点进行分箱处理;
special_values: 设置需要单独分箱的值,默认为空;
count_distr_limit: 设置分箱占比的最小值,一般可接受范围为0.01-0.2,默认值为0.05;
method: 设置分箱方法,可设置"tree"(决策树)或"chimerge"(卡方),默认值为"tree";

stop_limit: 当IV值的增长率小于所设置的stop_limit,或卡方值小于qchisq(1-stoplimit, 1)时,停止分箱。一般可接受范围为0-0.5,默认值为0.1;
bin_num_limit: 该参数为整数,代表最大分箱数。
positive: 指定样本中正样本对应的标签,默认为"bad|1";
no_cores: 设置用于并行计算的 CPU 数目;
print_step: 该参数为非负数,默认值为1。若print_step>0,每次迭代会输出变量名。若iteration=0或no_cores>1,不会输出任何信息;

ignore_const_cols: 是否忽略常数列,默认值为True,即忽略常数列;
ignore_datetime_cols: 是否忽略日期列,默认值为True,即忽略日期列;
check_cate_num: 检查类别变量中枚举值数目是否大于50,默认值为True,即自动进行检查。若枚举值过多,会影响分箱过程的速度;
replace_blank: 设置是否将空值填为None,默认为True。
若对自动分箱结果不满意,还可手动自定义分箱:

breaks_adj = {'age.in.years': [26, 35, 40],
    'other.debtors.or.guarantors': ["none", "co-applicant%,%guarantor"] }
bins_adj = sc.woebin(dt_s, y="creditability", breaks_list=breaks_adj)

其实使用很简单,就下面一行代码,然后再画图看一看就行。

bins = sc.woebin(data_train, y="Credit Default" )
sc.woebin_plot(bins)

所有变量都有一张图,但是我这里篇幅限制就不放那么多了,只放这一个变量。

从这个图中我们可以清楚的看到它的分箱的情况,每一箱里面的好坏的数量,比例以及坏样本的浓度是怎么样变化的。

## 可以看到 Credit Score 这个字段有点问题,按理来说分数越高黑样本浓度越低 ,750分以上黑样本这么高明显有问题

我们画拉出明细来看一下

bins['Credit Score']#.plot.hist()

这个明细其实就是上面的图,但是他并没有展示750以上的这个数据的分布长什么样,我们直接筛选来看一下。

##查看到750以上的,部分是比750大一点,很多是5000以上的分数,明显有问题,这个切割点需要重新划分,
data_train[data_train["Credit Score"]>800]["Credit Score"].plot.hist()

800以上都是直接跳到了6000这种最高分了,下面重新划分这个变量的分箱阈值

breaks_adj = {'Credit Score': [678,696,728,740,800] }
bins_adj = sc.woebin(data_train, y="Credit Default", breaks_list=breaks_adj)
sc.woebin_plot(bins_adj)

这里就使用到了break list,就是可以自己手工定义分享的节点阈值的。参数我觉得还是挺好用的,可以调整这个分箱,然后重新再看我们的这个变量的情况。

由于分箱多了一箱,所以可能有点密集重叠,但是我们可以明显的看到。信用分数这一项,随着分数变高,它的换样本浓度是明显的下降的只有在800分以上。6000分以上的这种才全是黑样本,这应该是数据的异常情况。

可以看到 iv已经升到了1.6119级,信用卡评分在800分以上的基本全部是黑样本。这应该是数据的一些错误之类的,因为信用卡评分也不可能到五六千分以上。

重新划分后,800分以上的,黑样本100浓度,IV超高。分箱完成后,使用woebin_ply()函数对变量进行woe变换,之后需要把所得到的woe值作为模型的输入。


WOE转化

分箱分好之后,我们就要进行woe的转化编码,在这个包里面使用很简单。

train_woe = sc.woebin_ply(train, bins_adj)
val_woe = sc.woebin_ply(val, bins_adj)
test_woe = sc.woebin_ply(test, bins_adj)

数据现在就准备好了我们可以进行训练了。


模型训练

我们取出x跟y训练集和验证集,还有测试集。

y_train = train_woe.loc[:,'Credit Default']
X_train = train_woe.loc[:,train_woe.columns != 'Credit Default']
y_val = val_woe.loc[:,'Credit Default']
X_val = val_woe.loc[:,train_woe.columns != 'Credit Default']

X_test = test_woe.loc[:,train_woe.columns[1:] != 'Credit Default']
print(X_train.shape,y_train.shape,X_val.shape,y_val.shape, X_test.shape)

使用逻辑回归进行学习器

# 逻辑回归 ------
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(penalty='l1', C=0.9, solver='saga', n_jobs=-1)
lr.fit(X_train, y_train)
lr.coef_,lr.intercept_

可以看到回归的系数项和截距项。

然后我们可以直接进行预测概率。

# 预测
train_pred = lr.predict_proba(X_train)[:,1]
val_pred = lr.predict_proba(X_val)[:,1]

test_pred = lr.predict_proba(X_test)[:,1]


效果评估

可使用perf_eva()函数对模型效果进行计算及可视化,基于预测的概率值和label值,提供KS(kolmogorov-smirnow), ROC, lift以及precision-recall四种评估指标:

该函数还包括以下参数:

def perf_eva(label, pred, title=None, groupnum=None, plot_type=["ks", "roc"], 
             show_plot=True, positive="bad|1", seed=186)
参数plot_type可设置为:"ks", "lift", "roc", "pr",默认为["ks", "roc"]。
 

他的评估看ks跟auc特别简单,一行代码就可以了。

train_perf = sc.perf_eva(y_train, train_pred, title = "train")
val_perf = sc.perf_eva(y_val, val_pred, title = "val")

训练集KS=0.38,AUC=0.77,验证集KS=0.3716,AUC=0.765,没有过拟合,很好的效果。

到这里可能就有小聪明要问了,为什么不用分类问题常用的评价指标,准确率,精准度,召回率f1值呢。

呃,因为在信贷模型里面大部分的时候都是样本不平衡的,这4个指标在样本不平衡的情况下效果非常差,信贷模型主要还是看ks跟auc。


分数转换

然后基于’woebin'的结果和sklearn.linear_model的LogisticRegression,创建scorecard()函数,用于构建评分卡,只需一行代码:

scorecard()包括以下参数:

def scorecard(bins, model, xcolumns, points0=600, odds0=1/19, 
              pdo=50, basepoints_eq0=False, digits=0)
各参数含义如下:

bins:由`woebin`得到的分箱信息;
model:LogisticRegression模型对象;
points0:基准分数,默认值为600;
odds0: 基准 Odds(好坏比),与真实违约概率对应,可换算得到违约概率,Odds = p/(1-p)。默认值为 1/19;
pdo: Points toDouble theOdds,即Odds变成2倍时,所增加的信用分。默认值为50;
basepoints_eq0:设置是否要把basepoints均分给每个变量的得分,默认为False,即不进行均分。但大多数评分卡倾向于所有分数均为正数,所以可手动改为True。

使用很简单

card = sc.scorecard(bins_adj, lr, X_train.columns)

然后基于scorecard的结果,用scorecard_ply()函数计算train和test数据集的信用分数:

train_score = sc.scorecard_ply(train, card, print_step=0)
val_score = sc.scorecard_ply(val, card, print_step=0)
test_score = sc.scorecard_ply(test, card, print_step=0)

最后用perf_psi()得到该评分卡在测试数据集上的表现。

但是这个方法好像有时候版本问题容易报错,所以我们就自己自定义了一个怎么算psi的方法。

train.shape,val.shape,train_score.shape,val_score.shape , test_score.shape

def calculate_psi(expected, actual, bins=10, epsilon=1e-10):
    # 分箱
    breakpoints = np.linspace(0, 1, bins + 1) * (max(expected.max(), actual.max()) - min(expected.min(), actual.min())) + min(expected.min(), actual.min())
    
    expected_percents = np.histogram(expected, bins=breakpoints)[0] / len(expected)
    actual_percents = np.histogram(actual, bins=breakpoints)[0] / len(actual)
    
    # 增加一个小的正数,避免零值
    expected_percents = np.where(expected_percents == 0, epsilon, expected_percents)
    actual_percents = np.where(actual_percents == 0, epsilon, actual_percents)
    
    # 计算PSI
    psi_value = np.sum((expected_percents - actual_percents) * np.log(expected_percents / actual_percents))
    
    return psi_value
print(f'训练集和验证集的PSI{calculate_psi(train_score.to_numpy().reshape(-1,), val_score.to_numpy().reshape(-1,))}') 
print(f'训练集测试集的PSI{calculate_psi(train_score.to_numpy().reshape(-1,), test_score.to_numpy().reshape(-1,)) }')

可以看到训练集和验证集,还有训练集和测试集的预测的分数PSI变化都不大,因此预测出来的分布都较为稳定,模型性能应该都挺良好的。

plt.figure(figsize=(7, 3),dpi=128)
sns.kdeplot(train_score, bw_adjust=1.5,label='Train Score', fill=True, palette="Set1", color='gold', alpha=0.3)#
sns.kdeplot(val_score,bw_adjust=1.5,label='Validation Score',fill=True,  palette="Set2", alpha=0.3)#
sns.kdeplot(test_score, label='Test Score', fill=True,palette="Set3", alpha=0.3) #

plt.title('Kernel Density Plot of Scores')
plt.xlabel('Score')
plt.ylabel('Density')
plt.legend()
plt.show()

高度重叠,效果很好,测试集也没偏移

test_score是信用卡评分,是概率转化后的分数,test_pred是概率,现在重新预测为变成枚举值后 可以储存,然后提交

test_pred = lr.predict(X_test)#[:,1]
df_pred=pd.DataFrame(test_pred,index=[test.index],columns=['Credit Default'])
df_pred.head()

df_pred.to_csv('评分卡模型预测结果.csv')

进行储存,我们测试集上的这些申请人,就知道要不要给他贷款了。


和机器学习模型对比

上面的评分卡模型和机器学习模型到底谁效果好呢?我们测试一下

(之前有全面用这个数据集做过一篇关于机器学习及其可解释性可视化分析的一个案例链接在这儿)

这里的机器学习就是简单的算一个评价指标ks,做一下对比。

重新读取数据

train=pd.read_csv('train.csv').set_index('Id')
test=pd.read_csv('test.csv').set_index('Id')
columns_to_convert = train.select_dtypes(exclude=['number']).columns.to_list()
train, test = ensure_category_consistency(train, test, columns_to_convert)

划分训练集和验证集

X=train.iloc[:,:-1]   ;  y=train.iloc[:,-1]
from sklearn.model_selection import train_test_split
X_train,X_val,y_train,y_val=train_test_split(X,y,stratify=y,test_size=0.3,random_state=186)

定义分类问题常用的四个评价指标

from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
from sklearn.metrics import cohen_kappa_score
 
def evaluation(y_test, y_predict):
    accuracy=classification_report(y_test, y_predict,output_dict=True)['accuracy']
    s=classification_report(y_test, y_predict,output_dict=True)['weighted avg']
    precision=s['precision']
    recall=s['recall']
    f1_score=s['f1-score']
    #kappa=cohen_kappa_score(y_test, y_predict)
    return accuracy,precision,recall,f1_score #, kappa

构建Lgbml模型

from lightgbm import LGBMClassifier
model=LGBMClassifier(objective='binary',random_state=1,verbose=-1,max_depth=6,n_estimators=100,eta=0.05)
model.fit(X_train, y_train)
y_pred=model.predict(X_val)
evaluation(y_val,y_pred)

可以看到这4个指标的数据的情况,但是信贷模型一般不关注他们,所以就不是很重要了。

画出pr曲线和roc图。

from sklearn.metrics import roc_curve, auc, precision_recall_curve
y_pred_proba = model.predict_proba(X_val)[:, 1]
# 计算ROC曲线和AUC值
fpr, tpr, _ = roc_curve(y_val, y_pred_proba)
roc_auc = auc(fpr, tpr)
# 计算PR曲线
precision, recall, _ = precision_recall_curve(y_val, y_pred_proba)

# 创建1*2的子图
plt.figure(figsize=(10, 4),dpi=128)

# 绘制ROC曲线
plt.subplot(1, 2, 1)
plt.plot(fpr, tpr, color='tomato', lw=2, label='AUC = %0.2f' % roc_auc)
plt.plot([0, 1], [0, 1], color='k', lw=1, linestyle='--')
plt.xlim([0.0, 1.0]) ;  plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')  ;  plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC) Curve')
plt.legend(loc="lower right")

# 绘制PR曲线
plt.subplot(1, 2, 2)
plt.plot(recall, precision, color='skyblue', lw=2)
plt.xlim([0.0, 1.0])  ;  plt.ylim([0.0, 1.05])
plt.xlabel('Recall')  ;  plt.ylabel('Precision')
plt.title('Precision-Recall (PR) Curve')

# 显示图像
plt.tight_layout()
plt.show()

画出ks的图

import scikitplot as skplt
skplt.metrics.plot_ks_statistic(y_val,model.predict_proba(X_val))
plt.show()

Lgbm模型验证集上的KS=0.369,AUC=0.77 ,比上面的评分卡模型验证集KS=0.3716,AUC=0.765差不多

Lgbm模型处理简单,但是没太多解释性,评分卡逻辑回归要做很多woe处理,解释性强,效果都差不多,各有优缺点吧

预测结果储存

test_pred=model.predict(test)
df_pred=pd.DataFrame(test_pred,index=[test.index],columns=['Credit Default'])
df_pred.head()
df_pred.to_csv('LGBM模型预测结果.csv')

那么到底谁的结果好呢?我们把测试集上用两种不同模型得到的结果分别提交到kaggle上去进行一个打分。

 

测试集上传到kaggle上计算F1值的对比,高下立见!

看来还是现代的机器学习效果更好啊!


下面是PS,补充学习内容

评分卡转化的影响

我们模型输出的是概率要转化为评分卡的这个分数,要经过一定的几率转化,这个转化有一些参数,我们可以研究一下它对分数的分布的影响。

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 生成1000个正态分布的随机概率,假设均值在0.5,标准差较小以确保大部分集中在0到1之间
np.random.seed(42)  # 固定随机种子以保证结果可重复
random_probs = np.clip(np.random.normal(loc=0.5, scale=0.1, size=1000), 0, 1)

import numpy as np

def cal_score(pred, pdo=0.2, base_score=0.5, base_odds=1.0):
    factor = pdo / np.log(2)  # 调整这部分
    offset = base_score - factor * np.log(base_odds)
    odds = (1 - pred) / pred
    score = offset + factor * np.log(odds)
    # 限制分数在0到1之间
    #score = np.clip(score, score_min, score_max)
    return score

# 应用分数转换
scores = cal_score(random_probs)

调整 pdo 参数:pdo(每翻一倍的分数差)决定了分数随赔率变化的敏感度。通过降低 pdo 值,你可以增加分数对概率变化的敏感度,从而增加区分度。

调整 base_odds 参数:base_odds 是基准的赔率。调整这个值可以影响分数的整体偏移。例如,如果你将基准赔率稍微增大或减小,可能会得到偏离中心的分数。

调整 base_score 参数:base_score 是赔率为基准赔率时的分数。通过改变这个值,你可以整体上平移分数分布。

scores2 = cal_score(random_probs,pdo=0.2, base_score=0.5, base_odds=1,)
scores3 = cal_score(random_probs,pdo=0.5, base_score=0.5, base_odds=1,)
scores4 = cal_score(random_probs,pdo=0.2, base_score=0.05, base_odds=1,)
scores5 = cal_score(random_probs,pdo=0.2, base_score=0.5, base_odds=2,)

可视化对比

# 画核密度图
plt.figure(figsize=(7, 4),dpi=128)
sns.kdeplot(random_probs, fill=True, bw_adjust=1.5,label='real')
sns.kdeplot(scores2, fill=True, bw_adjust=1.5,label='pdo=0.2, base_score=0.5, base_odds=1')
sns.kdeplot(scores3, fill=True, bw_adjust=1.5,label='pdo=0.5, base_score=0.5, base_odds=1')
sns.kdeplot(scores4, fill=True, bw_adjust=1.5,label='pdo=0.2, base_score=0.05, base_odds=1')
sns.kdeplot(scores5, fill=True, bw_adjust=1.5,label='pdo=0.2, base_score=0.5, base_odds=2')
plt.title('KDE of Transformed Probabilities')
plt.xlabel('Probability')
plt.ylabel('Density')
plt.legend(fontsize=6)
plt.show()


创作不易,看官觉得写得还不错的话点个关注和赞吧,本人会持续更新python数据分析领域的代码文章~(需要定制类似的代码可私信)

以往的文章可以在这里查看:数据分析案例合集


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/907514.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Canvas 教程(一)

目录 一、初体验 二、通过js的方式创建canvas 三、为什么推荐属性的方式设置canvas的宽高? 四、常见画笔API 4.1 画直线 🔶 步骤 🫢 小练习 4.2 线条的样式 4.2.1 线条的宽度设置 🔶 API 4.2.2 线条的颜色设置 &#…

[CISCN 2019华北]PWN1-好久不见7

Partial RELRO 表示部分 RELRO 保护已启用。在这种情况下,只有某些部分(如 GOT 中的只读部分)是只读的。 NX enabled 表示这个二进制文件启用了 NX 保护,数据段是不可执行的。这可以防止某些类型的代码注入攻击。 这里是ida识别…

Leetcode 64. 最小路径和 动态规划+空间优化

原题链接&#xff1a;Leetcode 64. 最小路径和 二维数据 class Solution { public:int minPathSum(vector<vector<int>>& grid) {int m grid.size();int n grid[0].size();int dp[m][n];dp[0][0] grid[0][0];for (int j 1; j < n; j)dp[0][j] dp[0][…

教你一个免费把PDF产品宣传册转化为翻页电子产品宣传册的方法

在这个数字化时代&#xff0c;电子宣传册已经成为企业推广产品的重要手段。相比于传统的纸质宣传册&#xff0c;电子宣传册具有更高的互动性和传播效率。那么&#xff0c;如何将现有的PDF产品宣传册转化为具有翻页效果的电子宣传册呢&#xff1f;本文将为您详细介绍一种免费的方…

大数阶乘求末尾0的个数

题目&#xff1a;求123…n所得数的末尾有多少个0&#xff1f;n由键盘输入&#xff0c;且1000<n<10000。 答&#xff1a;思路1&#xff1a;考虑末尾0的来源。阶乘结果末尾的零是由于因数10的存在&#xff0c;10分解质因数&#xff1a;25。在阶乘的计算中&#xff0c;偶数&…

2024最新的开源博客系统:vue3.x+SpringBoot 3.x 前后端分离

本文转载自&#xff1a;https://fangcaicoding.cn/article/54 大家好&#xff01;我是方才&#xff0c;目前是8人后端研发团队的负责人&#xff0c;拥有6年后端经验&3年团队管理经验&#xff0c;截止目前面试过近200位候选人&#xff0c;主导过单表上10亿、累计上100亿数据…

鸿蒙生态下开发挑战-鸿蒙低代码开发工具展望及优势

鸿蒙生态下开发挑战 在鸿蒙生态下开发时&#xff0c;开发者可能会遇到多方面的挑战&#xff0c;这些挑战主要涉及开发工具、技术难度、生态竞争以及市场定位等方面。以下是对这些挑战的详细分析&#xff1a; 一、开发工具不完善 尽管鸿蒙系统的开发工具DevEco Studio在逐步完…

JavaScript的迭代器和生成器

1. 迭代器Iterator 1. 基本概念 JavaScript 表示集合的对象大致有Object&#xff0c;Array&#xff0c;Map&#xff0c;Set四种&#xff0c;并且这四种类型的数据之间可以相互以成员嵌套&#xff08;如Array的成员可以是Object&#xff0c;而Map又可以嵌入Object的成员中&am…

深度学习常用开源数据集介绍【持续更新】

DIV2K 介绍&#xff1a;DIV2K是一个专为 图像超分辨率&#xff08;SR&#xff09; 任务设计的高质量数据集&#xff0c;广泛应用于计算机视觉领域的研究和开发。它包含800张高分辨率&#xff08;HR&#xff09;训练图像和100张高分辨率验证图像&#xff0c;每张图像都具有极高…

Pinia-状态管理

Pinia-状态管理 特点&#xff1a; 1. 轻量和模块化 Pinia 是一个轻量级的状态管理库&#xff0c;支持模块化管理&#xff0c;即可以将应用的状态分成多个 store 以实现更好的组织。使用 Pinia&#xff0c;可以定义多个 store&#xff0c;每个 store 都是一个独立的模块&#x…

向量模型Jina Embedding: 从v1到v3论文笔记

文章目录 Jina Embedding: 从v1到v3Jina Embedding v1数据集准备训练过程 Jina Embedding v2预训练修改版BERT在文本对上微调在Hard Negatives上微调 Jina Embedding v2 双语言预训练修改版BERT在文本对上微调用多任务目标微调 Jina Embedding v3预训练在文本对上微调训练任务相…

修改HarmonyOS鸿蒙图标和名字,打包后安装到真机,应用图标丢失变成透明,修改名字也不生效,还是默认的labeL解决方案教程

HarmonyOS鸿蒙打包hap 安装应用到桌面没有图标&#xff0c;用hdc安装到真机&#xff0c;打包后应用图标丢失变成透明&#xff0c;名字也还是默认的label的bug&#xff0c;以下是解决方案 以下是修改方案&#xff1a; 1、修改应用名字&#xff1a; 2、修改应用图标&#xff1a…

3个模型的交互式多模型IMM,基于EKF的目标跟踪实例(附MATLAB代码)

文章目录 3个模型的IMM源代码运行结果代码介绍总结 3个模型的IMM 代码实现了基于 I M M IMM IMM&#xff08;Interacting Multiple Model&#xff09;算法的目标跟踪。它使用三种不同的运动模型&#xff08;匀速直线运动、左转弯和右转弯&#xff09;来预测目标的位置&#x…

Webservice 客户端 生成代码 cxf方式 jdk方式 wsdl保存到本地后,生成客户端代码

详解视频&#xff0c;如果看不懂图片&#xff0c;请看这个视频 客户端三种方式 jdk cxf 客户单 wsdl保存到本地后&#xff0c;生成客户端代码

轮廓图【HTML+CSS+JavaScript】

给大家分享一个很好看的轮播图&#xff0c;这个也是之前看到别人写的效果感觉很好看&#xff0c;所以后面也自己实现了一下&#xff0c;在这里分享给大家&#xff0c;希望大家也可以有所收获 轮播图效果&#xff1a; 视频效果有点浑浊&#xff0c;大家凑合着看&#xff0c;大家…

Windows上安装Redis

1.下载Redis 下载有2中选择&#xff1a; 官方redis官方下载地址&#xff1a; https://redis.io/download&#xff0c; 选择适合Windows的版本下载。 redis 64位下载地址&#xff1a; https://github.com/ServiceStack/rediswindows/tree/master/downloads&#xff0c; 我们下…

计算机视觉实验一:图像基础处理

1. 图像的直方图均衡 1.1 实验目的与要求 (1)理解直方图均衡的原理与作用; (2)掌握统计图像直方图的方法; (3)掌握图像直方图均衡的方法。 1.2 实验原理及知识点 直方图均衡化是通过灰度变换将一幅图象转换为另一幅均衡直方图&#xff0c;即在每个灰度级上都具有相同的象素…

第8章 利用CSS制作导航菜单作业

1.利用CSS技术&#xff0c;结合链接和列表&#xff0c;设计并实现“山水之间”页面。 浏览效果如下&#xff1a; HTML代码如下&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>山水之间</title><…

dhcp池没有空闲ip导致手机无法获得ip

得到用户反馈&#xff0c;一个高速项目部的wifi无法接入&#xff0c;让排查原因。 反馈有的手机能接入&#xff0c;有的接入不了。查看ac界面发现有个终端获得的ip是169.254.xxx.xxx。 ip地址是169.254.96.17显然是手机打开wlan开关后&#xff0c;鉴权通过后dhcp过程&#xff0…

AJAX和JSON

一.AJAX技术 1.1 AJAX介绍 Ajax 即“Asynchronous Javascript And XML”&#xff08;异步 JavaScript 和 XML&#xff09;&#xff0c;是指一种创建 交互式、快速动态应用的网页开发技术&#xff0c;无需重新加载整个网 页的情况下&#xff0c;能够更新页面局部数据的技术。 通…