基于【逻辑回归】的评分卡模型金融借贷风控项目实战

背景知识:        

在银行借贷过程中,评分卡是一种以分数形式来衡量一个客户的信用风险大小的手段。今天我们来复现一个评分A卡的模型。完整的模型开发所需流程包括:获取数据,数据清洗和特征工程,模型开发,模型检验和评估,模型上线,模型检测和报告。

我们先来导入相关的模块:

'''获取数据——数据清洗——特征工程——模型训练和开发——模型检验和评估——模型上线和监控'''
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import StandardScaler,MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

先获取数据并查看数据的形状:

# 1.获取数据
data = pd.read_csv(r"E:\AI课程笔记\机器学习_2\05逻辑回归与评分卡\rankingcard.csv")
data.drop("Unnamed: 0", axis=1, inplace=True)
data.shape #(150000, 11)

     接着做数据清洗,包括重复值,缺失值和异常值。先去除重复值并重置索引:

# 2.1 去除重复值
data.drop_duplicates(inplace=True)
data.shape
data.index = range(data.shape[0]) # 重置索引

查看有多少缺失值:

data.isnull().sum() # 查看缺失值

发现monthly Income和numberofdependents有缺失值。在这里,NumberOfDependents用所在列的平均值来填充

(注意:在具体业务中,算法工程师需要和业务人员具体了解每项业务指标的含义来筛选最合适的填充方式)

data["NumberOfDependents"].fillna(int(data["NumberOfDependents"].mean()), inplace=True) # 用平均值填补缺失值

MonthlyIncome我们用随机森林回归(随机森林回归的原理是:基于用特征ABC去预测Z的思想,所以也可以用ABZ去预测C)来填充:

def fill_missing_rf(x, y, to_fill):"""使用随机森林填补一个特征的缺失值的函数参数:x:要填补的特征矩阵y:完整的,没有缺失值的标签to_fill:字符串,要填补的那一列的名称"""# 构建我们的新特征矩阵和新标签df = x.copy() # 复制特征矩阵fill = df.loc[:, to_fill] # 提取我们的标签df = pd.concat([df.loc[:, df.columns != to_fill], pd.DataFrame(y)], axis=1) # 构建新的特征矩阵# 找出我们的训练集和测试集Ytrain = fill[fill.notnull()]Ytest = fill[fill.isnull()]Xtrain = df.iloc[Ytrain.index, :]Xtest = df.iloc[Ytest.index, :]# 用随机森林回归来填补缺失值from sklearn.ensemble import RandomForestRegressor as rfrrfr = rfr(n_estimators=100).fit(Xtrain, Ytrain)Ypredict = rfr.predict(Xtest)return Ypredict
X = data.iloc[:, 1:]
Y = data["SeriousDlqin2yrs"]
y_pred = fill_missing_rf(X, Y, "MonthlyIncome")
data.loc[data.loc[:, "MonthlyIncome"].isnull(), "MonthlyIncome"] = y_pred
data.isnull().sum() # 查看缺失值

将缺失值填充完毕后,查看数据信息:

发现数据已经没有缺失值了。最后我们来处理异常值。显示数据永远都会有异常值,我们需要去根据业务性质去捕捉。在这里,我们发现有一条年龄为0的数据,这显然是异常值,因此我们将它删除,并返回删除后的原数据(还有更多的异常值需要银行业务方面的知识,和算法无关,这里就不赘述了):

data = data[data["age"] != 0]

到这里,重复值,缺失值和异常值我们都处理完毕了。再考虑是否需要做标准化,答案是不需要。因为对业务人员来说,他们无法理解标准化后的数据是什么意思。

接下来我们查看一下好客户和坏客户分别有多少:

Y.value_counts()

我们发现标签值0有13w+的数据,1只有不到1w的数据,这说明数据有严重的样本不均衡问题。在这里我们可以使用上采样法去平衡样本:

# 样本不均衡,用上采样算法生成新的样本
import imblearn # imblearn是专门用来处理样本不均衡问题的库
from imblearn.over_sampling import SMOTE # SMOTE是上采样算法
sm = SMOTE(random_state=42) # 实例化
X = data.iloc[:, 1:] 
Y = data["SeriousDlqin2yrs"]
X, Y = sm.fit_resample(X, Y) # 返回上采样过后的特征矩阵和标签
X = pd.DataFrame(X) # 将X转换为DataFrame格式
Y = pd.DataFrame(Y) # 将Y转换为DataFrame格式
data2 = pd.concat([Y, X], axis=1) # 将X和Y合并
data2.columns = data.columns # 将data2的列名改为data的列名
data2.head(5)
data2.shape
Y.value_counts()

这个时候我们发现样本就均衡了。

到这里,我们就完成了数据预处理的全部工作。接下来我们将数据切片成特征矩阵和标签矩阵,在其基础上划分为训练集和测试集后,将特征训练集和标签训练集合并,特征测试集和标签测试集合并,并将他们保存至本地:

# 数据集划分
X = data2.iloc[:, 1:]
Y = data2.iloc[:, 0]
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X, Y, test_size=0.3, random_state=420)# 训练集和测试集分别存储至本地
train = pd.concat([Ytrain, Xtrain], axis=1)
train.index = range(train.shape[0])
train.columns = data.columnstest = pd.concat([Ytest, Xtest], axis=1)
test.index = range(test.shape[0])
test.columns = data.columnstrain.to_csv(r"E:\AI课程笔记\机器学习_2\05逻辑回归与评分卡\train.csv")
test.to_csv(r"E:\AI课程笔记\机器学习_2\05逻辑回归与评分卡\test.csv")

接下来我们对各个特征进行分档,我们使用分箱来离散化连续变量,好让拥有不同属性的人,根据不同的特征被分成不同的类别,打上不同的分数,类似于聚类。分箱最好在4-5个为佳。

分箱有几个重要的原因:

  • 简化模型:将连续数据分成箱子后,可以将其视为离散数据,更容易建立和理解模型。
  • 处理异常值:分箱可以帮助识别和处理异常值,将其归入适当的箱子中,减少异常值对模型的影响。
  • 解决非线性关系:某些情况下,变量与目标之间的关系可能是非线性的,分箱可以捕捉到这种非线性关系。

在这里还要介绍两个概念,IV和WOE。

        每个箱子的WOE越大,代表这个箱子的优质客户越多;IV值衡量的是某一个变量的信息量,可用来表示一个变量的预测能力,用来做特征选择。箱子越多IV会越小,因为信息损失会很多;IV越小说明特征几乎不带有有效信息,对模型没有贡献,可以被删除,但IV越大,有效信息非常多,对模型的贡献率超高并且可疑。所以我们需要找到V的大小和箱子个数的平衡点。

        在分箱的过程中,箱子的数量是一个重要的参数。箱子的数量越多,每个箱子的区间就越小,模型对数据的拟合程度就越高,但是也会导致信息损失更多。因为当箱子的数量增加时,每个箱子中的样本数量就会减少,从而导致每个箱子中的样本分布更加不均匀,可能会出现某些箱子中只有少数样本,或者某些箱子中只有一种样本。这些情况都会导致模型的泛化能力下降,从而影响模型的预测效果。所以我们需要画出IV值的学习曲线。

分箱的步骤是:①先把连续性变量分成分类型变量②确保每一组都包含两种类型的样本③对相邻的组进行卡方检验,如果P值很大则进行合并,直到少于N箱。④让一个特征分成(2,3,4,20)箱,观察每个特征的IV值如何变化,找出最适合的分箱个数。⑤计算每个分箱的WOE值,观察分箱效果。

接下来以[age]特征为例,来对数据进行分箱,在这里我们用pandas库的qcut函数来分箱(假设先分成20箱,q = 20),并生成一个“qcut新列”:

# qcut等频分箱
train1 = train.copy()
train1["qcut"], updown = pd.qcut(train1["age"], retbins=True, q=20) # 等频分箱
train1["qcut"].value_counts() # 查看每个分箱中的样本量
updown # 查看每个分箱的上限和下限

新生成的列如下图所示:

可以清晰的看到每个样本所在的分箱情况,我们再来看看每个箱子里面包含的样本数:

接下来我们再来看看每个箱子中0和1的个数:

# 查看每个分箱中0和1的数量
coount_y0 = train1[train1["SeriousDlqin2yrs"] == 0].groupby(by="qcut").count()["SeriousDlqin2yrs"] # 每个箱子中0的个数
coount_y1 = train1[train1["SeriousDlqin2yrs"] == 1].groupby(by="qcut").count()["SeriousDlqin2yrs"] # 每个箱子中1的个数

上图所示的是每个箱子中0的个数。为了将数据信息统一展示,我们运行如下代码将数据合并:-

num_bins = [*zip(updown, updown[1:], coount_y0, coount_y1)] # 将每个分箱的上限、下限、0的个数、1的个数放在一起

为了让数据可读性更强,我们重新生成表头:

    columns = ["min", "max", "count_0", "count_1"]df = pd.DataFrame(num_bins, columns=columns)

每个箱子的上限和下限以及0的数量,1的数量都清晰可见了。接下来我们构造两个函数,分别计算WOE和IV值:

# 计算WOE和iv值
def get_woe(num_bins):# 通过num_bins数据计算woecolumns = ["min", "max", "count_0", "count_1"]df = pd.DataFrame(num_bins, columns=columns) # 将num_bins转换为DataFramedf["total"] = df.count_0 + df.count_1 # 每个箱子的总数df["percentage"] = df.total / df.total.sum() # 每个箱子的占比df["bad_rate"] = df.count_1 / df.total # 每个箱子中1的占比df["good%"] = df.count_0 / df.count_0.sum() # 每个箱子中0的占比df["bad%"] = df.count_1 / df.count_1.sum() # 每个箱子中1的占比df["woe"] = np.log(df["good%"] / df["bad%"]) # 计算每个箱子的woe值return df
# 计算IV值
def get_iv(df): # 通过df计算IV值rate = df["good%"] - df["bad%"] # 计算每个箱子中好人和坏人的占比差iv = np.sum(rate * df.woe) # 计算IV值return iv

接下来我们通过卡方检验,判断箱子之间的相似性:

# 卡方检验 用来检验两个变量之间是否独立
num_bins_ = num_bins.copy()
import scipy.stats
IV = []
axisx = []
while len(num_bins_) > 2:pvs = []# 获取num_bins_两两之间的卡方检验的置信度(或卡方值)for i in range(len(num_bins_) - 1):x1 = num_bins_[i][2:]x2 = num_bins_[i + 1][2:]# 0返回卡方值,1返回p值pv = scipy.stats.chi2_contingency([x1, x2])[1] # p值pvs.append(pv)# 通过p值进行处理,合并p值最大的两组i = pvs.index(max(pvs))num_bins_[i:i + 2] = [(num_bins_[i][0],num_bins_[i + 1][1],num_bins_[i][2] + num_bins_[i + 1][2],num_bins_[i][3] + num_bins_[i + 1][3])] # 将卡方值最大的两组合并bins_df = get_woe(num_bins_)axisx.append(len(num_bins_))IV.append(get_iv(bins_df))
plt.figure()
plt.plot(axisx, IV)
plt.xticks(axisx)
plt.xlabel("number of box")
plt.ylabel("IV")
plt.show()

由图可知,我们要找到转折点,也就是当箱体等于6时,可以得到最优的IV。因为当箱体从6开始,IV值的增长速率由快转慢。

接下来我们把分箱过程包装成1个函数:

# 将合并箱体的过程包装成函数,实现分箱
def get_bin(num_bins,n):while len(num_bins) > n:pvs = []# 获取num_bins_两两之间的卡方检验的置信度(或卡方值)for i in range(len(num_bins) - 1):x1 = num_bins[i][2:]x2 = num_bins[i + 1][2:]# 0返回卡方值,1返回p值pv = scipy.stats.chi2_contingency([x1, x2])[1] # p值pvs.append(pv)# 通过p值进行处理,合并p值最大的两组i = pvs.index(max(pvs))num_bins[i:i + 2] = [(num_bins[i][0],num_bins[i + 1][1],num_bins[i][2] + num_bins[i + 1][2],num_bins[i][3] + num_bins[i + 1][3])] # 将卡方值最大的两组合并return num_binsafterbins = get_bin(num_bins, 6)
afterbins

可以看到,原先20箱的数据,现在变成了6箱。查看一下每组的WOE值:

bins_df = get_woe(afterbins)
bins_df

可以看到,WOE的组间差距很大,并且WOE单调递增(如果WOE有超过两个转折点,说明分箱过程有问题)。接下来我们将上述的全部分箱过程,打包成一个函数:

# 接下来我们将选取最佳分箱个数的过程包装成函数,对所有特征进行分箱
def graphforbestbin(DF, X, Y, n=5, q=20, graph=True):"""自动最优分箱函数,基于卡方检验的分箱参数:DF: 需要输入的数据X: 需要分箱的列名Y: 分箱数据对应的标签 Y 列名n: 保留分箱个数q: 初始分箱的个数graph: 是否要画出IV图像区间为前开后闭 (]"""DF = DF[[X, Y]].copy()DF["qcut"], bins = pd.qcut(DF[X], retbins=True, q=q, duplicates="drop")coount_y0 = DF.loc[DF[Y] == 0].groupby(by="qcut").count()[Y] # 每个箱子中0的个数coount_y1 = DF.loc[DF[Y] == 1].groupby(by="qcut").count()[Y] # 每个箱子中1的个数num_bins = [*zip(bins, bins[1:], coount_y0, coount_y1)] # 将每个分箱的上限、下限、0的个数、1的个数放在一起for i in range(q):if 0 in num_bins[0][2:]:num_bins[0:2] = [(num_bins[0][0],num_bins[1][1],num_bins[0][2] + num_bins[1][2],num_bins[0][3] + num_bins[1][3])]continuefor i in range(len(num_bins)):if 0 in num_bins[i][2:]:num_bins[i - 1:i + 1] = [(num_bins[i - 1][0],num_bins[i][1],num_bins[i - 1][2] + num_bins[i][2],num_bins[i - 1][3] + num_bins[i][3])]breakelse:breakdef get_woe(num_bins):# 通过num_bins数据计算woecolumns = ["min", "max", "count_0", "count_1"]df = pd.DataFrame(num_bins, columns=columns) # 将num_bins转换为DataFramedf["total"] = df.count_0 + df.count_1 # 每个箱子的总数df["percentage"] = df.total / df.total.sum() # 每个箱子的占比df["bad_rate"] = df.count_1 / df.total # 每个箱子中1的占比df["good%"] = df.count_0 / df.count_0.sum() # 每个箱子中0的占比df["bad%"] = df.count_1 / df.count_1.sum() # 每个箱子中1的占比df["woe"] = np.log(df["good%"] / df["bad%"]) # 计算每个箱子的woe值return dfdef get_iv(df): # 通过df计算IV值rate = df["good%"] - df["bad%"] # 计算每个箱子中好人和坏人的占比差iv = np.sum(rate * df.woe) # 计算IV值return ivIV = []axisx = []while len(num_bins) > n:pvs = []# 获取num_bins_两两之间的卡方检验的置信度(或卡方值)for i in range(len(num_bins) - 1):x1 = num_bins[i][2:]x2 = num_bins[i + 1][2:]# 0返回卡方值,1返回p值pv = scipy.stats.chi2_contingency([x1, x2])[1]pvs.append(pv)# 通过p值进行处理,合并p值最大的两组i = pvs.index(max(pvs))num_bins[i:i + 2] = [(num_bins[i][0],num_bins[i + 1][1],num_bins[i][2] + num_bins[i + 1][2],num_bins[i][3] + num_bins[i + 1][3])]bins_df = pd.DataFrame(get_woe(num_bins))axisx.append(len(num_bins))IV.append(get_iv(bins_df))if graph:plt.figure()plt.plot(axisx, IV)plt.xticks(axisx)plt.xlabel("number of box")plt.ylabel("IV")plt.show()return bins_df
for i in train.columns[1:-1]:print(i)graphforbestbin(train, i, "SeriousDlqin2yrs", n=2, q=20, graph=True)

运行一下看看结果:

可以发现有的可以自动分箱,有的无法自动分箱。无法自动分箱的原因是该特征本身就是分类特征,不是连续特征,因此系统无法绘制出分箱图像。对于无法自动分箱的特征,我们用负无穷和正无穷替换原有的最小值和最大值,这是为了可以覆盖所有情况

# 可以自动分箱的变量
auto_col_bins = {"RevolvingUtilizationOfUnsecuredLines": 6,"age": 5,"DebtRatio": 4,"MonthlyIncome": 3,"NumberOfOpenCreditLinesAndLoans": 5}# 不能自动分箱的变量
hand_bins = {"NumberOfTime30-59DaysPastDueNotWorse": [0, 1, 2, 13],"NumberOfTimes90DaysLate": [0, 1, 2, 17],"NumberRealEstateLoansOrLines": [0, 1, 2, 4, 54],"NumberOfTime60-89DaysPastDueNotWorse": [0, 1, 2, 8],"NumberOfDependents": [0, 1, 2, 3]}
# 保证区间覆盖使用np.inf替换最大值,使用-np.inf替换最小值
hand_bins = {k: [-np.inf, *v[:-1], np.inf] for k, v in hand_bins.items()}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机基础知识41

前端 # 前端是所有跟用户直接打交道 比如:PC页面、手机页面、汽车显示屏,肉眼可以看见的 # 后端:一堆代码,用户不能够直接看到,不直接与用户打交道 常见的后端:Python、Java、Go等 # 学了前端就可以做全栈…

C语言每日一练(二)

单链表经典算法专题 一、 单链表相关经典算法OJ题1:移除链表元素 解法一:在原链表中删除Node.nextnext的节点 typedef struct ListNode ListNode; struct ListNode* removeElements( ListNode* head, int val) {ListNode* pcur head;ListNode* pre h…

世界前沿技术发展报告2023《世界航空技术发展报告》(五)直升机技术

(五)直升机技术 1.常规直升机技术1.1 北约六国联合启动下一代旋翼飞行器能力项目1.2 美国和法国重视发展有人/无人直升机编组能力1.3 美国“黑鹰”直升机完成不载人全自主飞行 2.新概念直升机技术2.1 美国“劫掠者”X型直升机参与陆军“未来攻击侦察机”…

Go学习第十五章——Gin参数绑定bind与验证器

Go web框架——Gin(参数绑定bind与验证器) 1 bind参数绑定1.1 JSON参数1.2 Query参数1.3 Uri绑定动态参数1.4 ShouldBind自动绑定 2 验证器2.1 常用验证器2.2 gin内置验证器2.3 自定义验证的错误信息2.4 自定义验证器 1 bind参数绑定 在Gin框架中&#…

Vue 路由指南:畅游单页应用的地图(Vue Router 和 <router-view>)

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

【时间复杂度和空间复杂度】

前言: 首先介绍一下算法(Algorithm) 算法是对特定问题求解步骤的一种描述。一个“好”的算法应该达到以下目标:正确性、可读性、健壮性、高效率与低存储量需求 算法的效率的度量 是通过 时间复杂度 和 空间复杂度 来描述的 一、时间复杂度 时间复杂度…

【数据结构】Map和Set

Map和Set 1. 搜索树 1.1 概念 二叉搜索树是左子树比根节点小,右子树比根节点大的二叉树。(如果左右子树不为空的话是这样,但是左右子树也可以为空) 1.2 操作——查找 查找的思想与二分查找类似。 如果根节点的值和所要查找的…

wangEditor富文本编辑器的使用

文章目录 🟢 wangeditor 富文本⭐️安装 wangeditor⭐️demo 模板⭐️效果图 ✒️总结 🟢 wangeditor 富文本 一款开源 Web 富文本编辑器,开箱即用,配置简单 wangedito 官网 简洁易用、功能强大、文档教程丰富支持 JS、Vue、Rea…

【Note详细图解】中缀表达式如何转为后缀表达式?数据结构

中缀表达式 中缀表达式(中缀记法)是一个通用的算术或逻辑公式表示方法,操作符是以中缀形式处于操作数的中间(例:3 4),中缀表达式是人们常用的算术表示方法。 前缀或后缀记法不同的是&#xf…

【JVM】类加载器

【JVM】类加载器 文章目录 【JVM】类加载器0. 类加载器概述1. 类加载器的分类1.1 启动类加载器1.2 Java中的默认类加载器1.2.1 扩展类加载器1.2.2 应用程序类加载器 2. 双亲委派机制2.1 类的双亲委派机制是什么?2.2 打破双亲委派机制2.2.1 自定义类加载器2.2.2 线程…

并行和并发有什么区别?

并行和并发 并行和并发最早其实描述的是 Java 并发编程里面的概念。他们强调的是 CPU 处理任务的能力。简单来说: 并发,就是同一个时刻,CPU 能够处理的任务数量,并且对于应用程序来说,不会出现卡顿现象。并行&#x…

【Linux】冯诺依曼体系结构以及初始操作系统

文章目录 冯诺依曼体系结构操作系统概念设计OS的目的定位如何理解管理 总结系统调用和库函数概念 冯诺依曼体系结构 我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。 截至目前,我们所认识…

HDFS 基本 shell 操作

HDFS 基本 shell 操作 1.1 创建目录1.2 上传指令1.3 创建空文件1.4 向分布式文件系统中的文件里追加内容1.5 查看指令1.6 下载指令1.7 合并下载1.8 移动hdfs中的文件1.9 复制hdfs中的文件到hdfs的另一个目录1.10 删除命令1.11 查看磁盘利用率和文件大小1.12 修改权限1.13 修改文…

专门解决数学问题的大模型

01 项目介绍 LLEMMA:一个专门解决数学问题的开源大语言模型,能力超过所有已知的开源模型 LLEMMA由多个大学和Eleuther AI公司共同研发,模型能够理解和生成数学表达式、解决数学问题,并与其他计算工具(如Python解释器…

修改el-date-picker宽度

<div style"width: 100%"><el-date-pickerstyle"width:100%"v-model"value"type"datetimerange"start-placeholder"开始日期"end-placeholder"结束日期":default-time"[12:00:00]"value-forma…

pytorch 入门 (五)案例三:乳腺癌识别-VGG16实现

本文为&#x1f517;小白入门Pytorch内部限免文章 &#x1f368; 本文为&#x1f517;小白入门Pytorch中的学习记录博客&#x1f366; 参考文章&#xff1a;【小白入门Pytorch】乳腺癌识别&#x1f356; 原作者&#xff1a;K同学啊 在本案例中&#xff0c;我将带大家探索一下深…

Response Header中不暴露Server(IIS)版本、ASP.NET及相关版本等信息

ASP MVC开发的Web默认情况下会在请求的回应中暴露Server、X-AspNet-Version、X-AspNetMvc-Version、X-Powered-By等相关服务端信息&#xff0c;公开这些敏感信息会存在一定的安全风险。 X-SourceFiles标头用于被IIS / IIS Express中某些调试模块理解&#xff0c;它包含到磁盘上…

【Vue】初步认识<script setup>语法糖和组合式 API

▒ 目录 ▒ &#x1f6eb; 导读需求开发环境 1️⃣ &#x1f6eb; 导读 需求 最近写代码的时候&#xff0c;发现<script setup>这样的代码&#xff0c;没见过&#xff0c;好奇&#xff0c;想知道。 所以就有了这篇文章。 很多文章都说setup是vue3的特权。但是&#xff…

Vue图片路径问题(动态引入)

vue项目中我们经常会遇到动态路径的图片无法显示的问题&#xff0c;以下是静态路径和动态路径的常见使用方法。 1.静态路径 在日常的开发中&#xff0c;图片的静态路径通过相对路径和绝对路径的方式引入。 相对路径&#xff1a;以.开头的&#xff0c;例如./、../之类的。就是…

MySQL主从架构

1 主从架构解决了什么问题 随着业务的持续增长&#xff0c;单体数据库满足不了业务的需求&#xff0c;可能会出现负载过重&#xff0c;操作数据库速度变慢的情况。为了解决这个问题&#xff0c;数据库一般采用一主一从、一主多从的架构。 为了操作提高效率&#xff0c;减轻压…