day16 numpy和shap深入理解

NumPy数组的创建

NumPy数组是Python中用于存储和操作大型多维数组和矩阵的主要工具。NumPy数组的创建非常灵活,可以接受各种"序列型"对象作为输入参数来创建数组。这意味着你可以将Python的列表(List)、元组(Tuple),甚至其他的NumPy数组等数据结构直接传递给np.array()来创建新的NumPy数组。

简单创建

复制代码
# 创建数组
# 最普通的一维数组,有初始化数据
a = np.array([1, 2, 3, 4, 5])
print(a, end="\n\n") # ------------> [1 2 3 4 5]



# 创建一个【多维】数组
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a, end="\n\n") # ------------> 二维数组:[[1 2 3] [4 5 6]]
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(a, end="\n\n") # ------------> 三位数组:[[1 2 3] [4 5 6] [7 8 9]]

随机创建

在深度学习中,我们经常需要对数据进行随机化处理,以确保模型的泛化能力。NumPy提供了多种随机数生成方法,如np.random.randint()np.random.rand()np.random.randn()等,可以生成不同范围和分布的随机数。

复制代码
c = np.random.rand(2, 2)  # 创建一个2*2的随机数组,区间为[0,1)

NumPy数组的索引

NumPy数组的索引方式非常灵活,支持一维、二维和三维数组的索引。

一维数组索引

一维数组的索引与Python列表类似,可以通过整数索引或切片来访问元素。

复制代码
arr1d = np.arange(10)  # 数组: [0 1 2 3 4 5 6 7 8 9]
arr1d[0]  # 取出数组的第一个元素
arr1d[-1] # 取出数组的最后一个元素

二维数组索引

二维数组可以看作是"数组的数组"或一个矩阵。其结构由两个主要维度决定:行数和列数。

复制代码
arr2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
arr2d[1, :]  # 取出第 1 行的所有元素
arr2d[:, 2]  # 取出第 2 列的所有元素

# 一维数组的切片序列是一样的
a = np.array([1, 2, 3, 4, 5])
 
print(a[1:4])    # [2, 3, 4]
print(a[4::-1])    # [5, 4, 3, 2, 1]
print(a[::2])    # [1, 3, 5]
 
 
 
# 如果是二维以上的,就需要把起始位跟末尾位带上,而且切片形式和不切片形式都有不同的含义
# 基本是就是 [ 行的切片 , 列的切片 ]
#【行】如果不是切片形式就代表固定是【某一行取序列】
#【列】如果没有切片形式就代表固定某一行或所有行都只取【这一列这个数】
a = np.array([[1, 2, 3], [4, 5, 6]])
 
print(a[0])       # 如果是直接取一行,就直接1个数字代表第几行就行 ------------> [1, 2, 3]
print(a[0, :])    # 这样也是直接取一行,只不过是取第[0]行,从第[0]列到第[尾]列,[1, 2, 3]
 
print(a[0, 1])    # 2,取第[0]行,第[1]列的数
print(a[:, 1])    # [2, 5],每一行的第[1]列的数
print(a[:, :])    # [[1, 2, 3], [4, 5, 6]],从第[1]行开始的[从头到尾],到第[2]行开始的[从头到尾]
 
print(a[1, 0:2])    # [4, 5],从第[1]行开始,分割[第0列~第2列]
 
print(a[1, ::-1])    # [6, 5, 4],从第[1]行开始,分割[第尾列~第0列],步长为-1倒着1个1个取

三维数组索引

三维数组的索引更为复杂,但原理与二维数组类似,只是在索引时增加了一个维度。

复制代码
arr3d = np.arange(3 * 4 * 5).reshape((3, 4, 5))
arr3d[1, :, :]  # 选择特定的层

numpy数组计算

1)最基础的一维向量的加减乘除

简单加减乘除

复制代码
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(a + b)    # [1+4, 2+5, 3+6] = [5, 7, 9]
print(a - b)    # [1-4, 2-5, 3-6] = [-3, -3, -3]
print(a * b)    # [1*4, 2*5, 3*6] = [4, 10, 18]
print(a / b)    # [1/4, 2/5, 3/6] = [0.25, 0.4, 0.5]

注意:如果是跟一个数字进行计算,就会把每一个元素都跟这个数计算

复制代码
print(a + 1)    # [1+1, 2+1, 3+1] = [2, 3, 4]
print(a - 1)    # [1-1, 2-1, 3-1] = [0, 1, 2]
print(a * 2)    # [1*2, 2*2, 3*2] = [2, 4, 6]
print(a / 2)    # [1/2, 2/2, 3/2] = [0.5, 1.0, 1.5]

2)向量的点乘

点乘

复制代码
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(np.dot(a, b))    # [1*4 + 2*5 + 3*6] = 32

3)矩阵的乘法

矩阵乘法运算

复制代码
A = np.array([[1, 2],
              [3, 4]])
 
B = np.array([[5, 6],
              [7, 8]])
#【第一种方法】:A @ B
print(A @ B)
"""
[[1*5 + 2*7, 1*6 + 2*8],        [[19, 22],
 [3*5 + 4*7, 3*6 + 4*8]] ------------>   [43, 50]]
"""

【第二种方法】:np.matmul()

print(np.matmul(A, B))

这里有一点要注意,因为同行同列的N维向量进行点乘时,也是得到一个同行同列的N维向量,所以用【np.dot(A,B)】结果是等于上面两种矩阵相乘的结果的,但是这不代表【点乘】=【矩阵相乘】,因为矩阵相乘严格要求【形状相同(同行同列)】的两个矩阵相乘。

那么假设两个一维向量,用np.dot() 会执行点乘,而 A @ B 和 np.matmul() 会因为形状不兼容而抛出错误。

np.dot() 可以用于执行两个数组的点积或矩阵乘法,具体取决于输入数组的形状。

复制代码
print(np.dot(A, B))
"""
[[1*5 + 2*7, 1*6 + 2*8],        [[19, 22], 
 [3*5 + 4*7, 3*6 + 4*8]] ------------>   [43, 50]]
"""

4)求平方

求平方

复制代码
a = np.array([1, 2, 3])
print(a ** 2)    # [1*1, 2*2, 3*3] = [1, 4, 9]
print(np.square(a))    # [1*1, 2*2, 3*3] = [1, 4, 9]

5)求指数、对数的运算

指数、对数运算

复制代码
a = np.array([1, 2, 3])
print(np.pow(a, 3))    # [1^3, 2^3, 3^3] = [1, 8, 27]
print(np.log(a))    # [log(1), log(2), log(3)] = [0, 0.6931471805599453, 1.0986122886681098]

6)求sin、cos值

求sin、cos

复制代码
a = np.array([1, 2, 3])
print(np.sin(a))    # [sin(1), sin(2), sin(3)]
print(np.cos(a))    # [cos(1), cos(2), cos(3)]

7)统计一个数组的最大、最小、平均、中位数、总和、方差、标准差.....

复制代码
a = np.array([1, 2, 3])
 
 
# 返回数组最小元素
print(np.min(a))    # 1
 
# 返回数组最大元素
print(np.max(a))    # 3
 
# 返回数组平均值
print(np.mean(a))    # 2.0
 
# 返回数组中位数
print(np.median(a))    # 2.0
 
# 返回数组最小数的位置
print(np.argmin(a))    # 0
 
# 返回数组最大数的位置
print(np.argmax(a))    # 2
 
# 返回数组总和
print(np.sum(a))    # 6
 
# 返回数组标准差
print(np.std(a))    # 1.0
 
# 返回数组方差
print(np.var(a))    # 1.0

这里对于二维数组,如果只想统计二维数组里的【每一行】或【每一列】,只需要在( )再加第二个参数:【axis=1】(统计行)或【axis=0】(统计列)

复制代码
# 找到二维数组np.arange(9).reshape(3,3)每一行中的最大值?
a = np.arange(9).reshape(3,3)
# [[0 1 2]
#  [3 4 5]
#  [6 7 8]]
 
print(np.max(a, axis=1)) # ------------> 【2 5 8】
# 某一列就把axis换成0
print(np.max(a, axis=0)) # ------------> 【6 7 8】

SHAP的深入理解

SHAP(SHapley Additive exPlanations)值是一种解释机器学习模型预测结果的方法。它基于合作博弈论中的Shapley值,用于解释每个特征对模型预测的贡献。

SHAP值是一个多维数组,其中每个维度代表不同的信息。例如,对于一个二分类问题,SHAP值的第三个维度可能表示两个类别的贡献值。

复制代码
shap_values.shape  # (样本数, 特征数, 类别数)

# 先运行之前预处理好的代码

import pandas as pd    #用于数据处理和分析,可处理表格数据。
import numpy as np     #用于数值计算,提供了高效的数组操作。
import matplotlib.pyplot as plt    #用于绘制各种类型的图表
import seaborn as sns   #基于matplotlib的高级绘图库,能绘制更美观的统计图形。
import warnings
warnings.filterwarnings("ignore")
 
 # 设置中文字体(解决中文显示问题)
plt.rcParams['font.sans-serif'] = ['SimHei']  # Windows系统常用黑体字体
plt.rcParams['axes.unicode_minus'] = False    # 正常显示负号
data = pd.read_csv('data.csv')    #读取数据


# 先筛选字符串变量 
discrete_features = data.select_dtypes(include=['object']).columns.tolist()
# Home Ownership 标签编码
home_ownership_mapping = {
    'Own Home': 1,
    'Rent': 2,
    'Have Mortgage': 3,
    'Home Mortgage': 4
}
data['Home Ownership'] = data['Home Ownership'].map(home_ownership_mapping)

# Years in current job 标签编码
years_in_job_mapping = {
    '< 1 year': 1,
    '1 year': 2,
    '2 years': 3,
    '3 years': 4,
    '4 years': 5,
    '5 years': 6,
    '6 years': 7,
    '7 years': 8,
    '8 years': 9,
    '9 years': 10,
    '10+ years': 11
}
data['Years in current job'] = data['Years in current job'].map(years_in_job_mapping)

# Purpose 独热编码,记得需要将bool类型转换为数值
data = pd.get_dummies(data, columns=['Purpose'])
data2 = pd.read_csv("data.csv") # 重新读取数据,用来做列名对比
list_final = [] # 新建一个空列表,用于存放独热编码后新增的特征名
for i in data.columns:
    if i not in data2.columns:
       list_final.append(i) # 这里打印出来的就是独热编码后的特征名
for i in list_final:
    data[i] = data[i].astype(int) # 这里的i就是独热编码后的特征名



# Term 0 - 1 映射
term_mapping = {
    'Short Term': 0,
    'Long Term': 1
}
data['Term'] = data['Term'].map(term_mapping)
data.rename(columns={'Term': 'Long Term'}, inplace=True) # 重命名列
continuous_features = data.select_dtypes(include=['int64', 'float64']).columns.tolist()  #把筛选出来的列名转换成列表
 
 # 连续特征用中位数补全
for feature in continuous_features:     
    mode_value = data[feature].mode()[0]            #获取该列的众数。
    data[feature].fillna(mode_value, inplace=True)          #用众数填充该列的缺失值,inplace=True表示直接在原数据上修改。

# 最开始也说了 很多调参函数自带交叉验证,甚至是必选的参数,你如果想要不交叉反而实现起来会麻烦很多
# 所以这里我们还是只划分一次数据集
from sklearn.model_selection import train_test_split
X = data.drop(['Credit Default'], axis=1)  # 特征,axis=1表示按列删除
y = data['Credit Default'] # 标签
# 按照8:2划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)  # 80%训练集,20%测试集

from sklearn.ensemble import RandomForestClassifier #随机森林分类器

from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score # 用于评估分类器性能的指标
from sklearn.metrics import classification_report, confusion_matrix #用于生成分类报告和混淆矩阵
import warnings #用于忽略警告信息
warnings.filterwarnings("ignore") # 忽略所有警告信息
# --- 1. 默认参数的随机森林 ---
# 评估基准模型,这里确实不需要验证集
print("--- 1. 默认参数随机森林 (训练集 -> 测试集) ---")
import time # 这里介绍一个新的库,time库,主要用于时间相关的操作,因为调参需要很长时间,记录下会帮助后人知道大概的时长
start_time = time.time() # 记录开始时间
rf_model = RandomForestClassifier(random_state=42)
rf_model.fit(X_train, y_train) # 在训练集上训练
rf_pred = rf_model.predict(X_test) # 在测试集上预测
end_time = time.time() # 记录结束时间

print(f"训练与预测耗时: {end_time - start_time:.4f} 秒")
print("\n默认随机森林 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred))
print("默认随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred))

SHAP 基于 Shapley 值,因此我们首先需要知道什么是 Shapley 值。假设我们有 3 名球员,即 L、M、N,要参加一场机器篮球比赛。如果 L 单独上场,他可以获得 10 分。而 M 和 N 的分数分别是 20 分和 25 分。如果 L 和 M 一起打球,他们知道如何合作,最终能得到 40 分。然而,当 L 和 N 组队时,他们只能得到 30 分。详情如下表所示,其中 v(S) 是 S 中成员通过合作可以获得的总贡献。

如何根据上述信息找出球队中每个球员的贡献?

这三名球员中哪一名最优秀?例如,一名球员 L 的贡献可以通过 L 为最终得分带来的差异来计算。换句话说,我们考虑的是有 L 参加比赛时与没有 L 参加比赛时的差异。再细分一下,当 L 加入游戏时,他可以单独玩,也可以与其他成员一起玩。因此,直观地说,L 的最终贡献应该是所有情况的平均值,到目前为止的总结夏普利值的主要思想是获取某一特征在所有不同排序中的边际贡献,然后求取平均值

  1. 效率与准确性的权衡:在用SHAP库算特征贡献的时候,我发现这玩意儿计算量真不小。特别是特征一多,那计算时间就蹭蹭往上涨。我就得琢磨,怎么在不牺牲太多准确性的前提下,让这玩意儿跑快点。

  2. 动态特征重要性:我发现特征对模型的影响并不是静态的。比如,在不同的数据子集上,同一个特征的重要性可能会有很大差异。这让我意识到,可能需要定期重新评估特征的重要性,以适应数据的变化。

遇到的问题

  1. 计算资源:有时候,数据量大了,计算SHAP值特别费资源。我就得想办法优化代码,或者用更强大的计算资源,这在资源有限的情况下是个问题。

  2. 解释的复杂性:有时候,即使SHAP值计算出来了,解释为啥某个特征重要也挺难的。特别是对于那些业务逻辑复杂的项目,你得深入理解业务,才能把特征影响解释清楚。

  3. 编码:错误或不合适的编程代码导致在数据庞大的时候出现错误,例如在之前项目实战中的独热编码

    复制代码
    #分离连续变量与离散变量
    discrete_features=['invoice_no','customer_id','gender','category','payment_method','invoice_date']

    我发现data = pd.get_dummies(data, columns=discrete_lists) 的运行速度非常慢,后面查看数据才发现['invoice_no','customer_id','invoice_date']有问题,其中:1.invoice_no和'customer_id'有99457个唯一值、'invoice_date'有797个唯一值,也就是说我在试图为了这三列特征生成几万个新列,导致了运行内存崩溃,程序未响应。直方图在前也是侥幸画出,后面需要更改这个独热编码,筛选关键列或者合并类别

修改后:

复制代码
# discrete features
for col in discrete_features:
    if col in ['invoice_no', 'customer_id', 'invoice_date']:
        print(f"跳过 {col} 的直方图,因为它不适合绘制直方图。")
        continue
    plt.figure(figsize=(10, 4))
    sns.histplot(x=data[col],shrink=0.8)
    plt.xticks(rotation=45, ha='right')
    plt.show()

# 处理日期列
data['invoice_date'] = pd.to_datetime(data['invoice_date'])
data['year'] = data['invoice_date'].dt.year
data['month'] = data['invoice_date'].dt.month
data['day'] = data['invoice_date'].dt.day
data['weekday'] = data['invoice_date'].dt.weekday
data['quarter'] = data['invoice_date'].dt.quarter

# 删除原始日期列


#对没有顺序的离散变量进行独热编码
discrete_lists = [] # 新建一个空列表,用于存放离散变量名
for discrete_features in data.columns:
    if data[discrete_features].dtype == 'object':
        discrete_lists.append(discrete_features)

# 排除高基数的列
discrete_lists = [col for col in discrete_lists if col not in ['invoice_no', 'customer_id', 'invoice_date']]
# 离散变量独热编码
data = pd.get_dummies(data, columns=discrete_lists) 
data.columns

参考:深入理解SHAP

@浙大疏锦行

相关推荐
sword devil9002 分钟前
基于python生成taskc语言文件--时间片轮询
开发语言·python
dudly6 分钟前
用Python打造自己的专属命令行工具
开发语言·python·batch命令
开开心心就好8 分钟前
提升办公效率的PDF转图片实用工具
运维·服务器·网络·python·智能手机·pdf·ocr
留思难22 分钟前
Python生活手册-正则表达式:从快递单到咖啡订单的文本魔法
python·正则表达式
球求了39 分钟前
Linux 入门:操作系统&&进程详解
linux·运维·服务器·开发语言·学习
李匠202439 分钟前
C++负载均衡远程调用学习之负载均衡算法与实现
运维·c++·学习·负载均衡
CodeCipher41 分钟前
Java后端程序员学习前端之CSS
前端·css·学习
李匠20241 小时前
C++负载均衡远程调用学习之Agent代理模块基础构建
c++·学习
像风一样自由20201 小时前
PyQt5 到 PySide6 技术栈转换详解
开发语言·python·qt
留思难1 小时前
Python生活手册-NumPy数组创建:从快递分拣到智能家居的数据容器
python·numpy