Mice Protein Expression¶

See More

小鼠蛋白质表达数据集说明,文章中的预测目标主要是根据蛋白质表达水平将小鼠分类到八个不同的类别。具体来说,预测目标可以总结如下:

预测目标¶

分类任务:利用77种蛋白质的表达水平,预测每只小鼠所属的类别。这八个类别基于以下三个特征组合:

  • 基因型(Genotype):

    • 控制组(Control, c)
    • 三体综合征组(Trisomic, t)
  • 行为测试条件(Behavior):

    • 情景-电击刺激(Context-Shock, CS):小鼠被刺激以进行联想学习。
    • 电击-情景刺激(Shock-Context, SC):小鼠未被刺激以进行联想学习。
  • 治疗类型(Treatment):

    • 注射盐水(Saline, s)
    • 注射美金刚(Memantine, m)

因此,八个类别分别为:

  • c-CS-s
  • c-CS-m
  • c-SC-s
  • c-SC-m
  • t-CS-s
  • t-CS-m
  • t-SC-s
  • t-SC-m

具体预测任务¶

  • 输入特征:77种蛋白质的表达水平(数值型特征)。
  • 输出目标:小鼠所属的具体类别(八分类问题)。

研究目标¶

识别区分不同类别的小鼠的关键蛋白质:通过机器学习模型,找出哪些蛋白质的表达水平对区分不同类别的小鼠(基因型、行为测试条件、治疗类型)最具判别力。 理解蛋白质表达与实验条件之间的关系:分析蛋白质表达如何受基因型、行为测试和治疗条件的影响,从而为下游的生物学研究提供依据。

可能的应用¶

生物标记物发现:识别与三体综合征相关的蛋白质,可能有助于疾病的早期诊断或治疗。 药物效果评估:通过比较注射美金刚和盐水的小鼠蛋白质表达,评估药物对三体综合征小鼠学习能力的影响。

备注:蛋白质表达数据的创建者和所有者 Katheleen J. Gardiner 目前就职于美国科罗拉多州奥罗拉市科罗拉多大学医学院 Linda Crnic 唐氏综合症研究所、儿科系、生物化学和分子遗传学系、人类医学遗传学和基因组学以及神经科学项目。

资料来源:加州大学欧文分校机器学习库

第一步:读取数据,查看信息¶

In [1]:
import pandas
import warnings

from docutils.nodes import figure

warnings.filterwarnings('ignore')

data : pandas.DataFrame = pandas.read_csv('Data_Cortex_Nuclear.csv')
data
Out[1]:
MouseID DYRK1A_N ITSN1_N BDNF_N NR1_N NR2A_N pAKT_N pBRAF_N pCAMKII_N pCREB_N ... pCFOS_N SYP_N H3AcK18_N EGR1_N H3MeK4_N CaNA_N Genotype Treatment Behavior class
0 309_1 0.503644 0.747193 0.430175 2.816329 5.990152 0.218830 0.177565 2.373744 0.232224 ... 0.108336 0.427099 0.114783 0.131790 0.128186 1.675652 Control Memantine C/S c-CS-m
1 309_2 0.514617 0.689064 0.411770 2.789514 5.685038 0.211636 0.172817 2.292150 0.226972 ... 0.104315 0.441581 0.111974 0.135103 0.131119 1.743610 Control Memantine C/S c-CS-m
2 309_3 0.509183 0.730247 0.418309 2.687201 5.622059 0.209011 0.175722 2.283337 0.230247 ... 0.106219 0.435777 0.111883 0.133362 0.127431 1.926427 Control Memantine C/S c-CS-m
3 309_4 0.442107 0.617076 0.358626 2.466947 4.979503 0.222886 0.176463 2.152301 0.207004 ... 0.111262 0.391691 0.130405 0.147444 0.146901 1.700563 Control Memantine C/S c-CS-m
4 309_5 0.434940 0.617430 0.358802 2.365785 4.718679 0.213106 0.173627 2.134014 0.192158 ... 0.110694 0.434154 0.118481 0.140314 0.148380 1.839730 Control Memantine C/S c-CS-m
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1075 J3295_11 0.254860 0.463591 0.254860 2.092082 2.600035 0.211736 0.171262 2.483740 0.207317 ... 0.183324 0.374088 0.318782 0.204660 0.328327 1.364823 Ts65Dn Saline S/C t-SC-s
1076 J3295_12 0.272198 0.474163 0.251638 2.161390 2.801492 0.251274 0.182496 2.512737 0.216339 ... 0.175674 0.375259 0.325639 0.200415 0.293435 1.364478 Ts65Dn Saline S/C t-SC-s
1077 J3295_13 0.228700 0.395179 0.234118 1.733184 2.220852 0.220665 0.161435 1.989723 0.185164 ... 0.158296 0.422121 0.321306 0.229193 0.355213 1.430825 Ts65Dn Saline S/C t-SC-s
1078 J3295_14 0.221242 0.412894 0.243974 1.876347 2.384088 0.208897 0.173623 2.086028 0.192044 ... 0.196296 0.397676 0.335936 0.251317 0.365353 1.404031 Ts65Dn Saline S/C t-SC-s
1079 J3295_15 0.302626 0.461059 0.256564 2.092790 2.594348 0.251001 0.191811 2.361816 0.223632 ... 0.187556 0.420347 0.335062 0.252995 0.365278 1.370999 Ts65Dn Saline S/C t-SC-s

1080 rows × 82 columns

In [2]:
# 特征
# 其中ID是无关数据、DYRK1A_N到CaNA_N是影响特征(有的可能有的对预测影响不大)、'class'是目标,这是一个多分类问题
data.columns, data.columns.__len__()
Out[2]:
(Index(['MouseID', 'DYRK1A_N', 'ITSN1_N', 'BDNF_N', 'NR1_N', 'NR2A_N', 'pAKT_N',
        'pBRAF_N', 'pCAMKII_N', 'pCREB_N', 'pELK_N', 'pERK_N', 'pJNK_N',
        'PKCA_N', 'pMEK_N', 'pNR1_N', 'pNR2A_N', 'pNR2B_N', 'pPKCAB_N',
        'pRSK_N', 'AKT_N', 'BRAF_N', 'CAMKII_N', 'CREB_N', 'ELK_N', 'ERK_N',
        'GSK3B_N', 'JNK_N', 'MEK_N', 'TRKA_N', 'RSK_N', 'APP_N', 'Bcatenin_N',
        'SOD1_N', 'MTOR_N', 'P38_N', 'pMTOR_N', 'DSCR1_N', 'AMPKA_N', 'NR2B_N',
        'pNUMB_N', 'RAPTOR_N', 'TIAM1_N', 'pP70S6_N', 'NUMB_N', 'P70S6_N',
        'pGSK3B_N', 'pPKCG_N', 'CDK5_N', 'S6_N', 'ADARB1_N', 'AcetylH3K9_N',
        'RRP1_N', 'BAX_N', 'ARC_N', 'ERBB4_N', 'nNOS_N', 'Tau_N', 'GFAP_N',
        'GluR3_N', 'GluR4_N', 'IL1B_N', 'P3525_N', 'pCASP9_N', 'PSD95_N',
        'SNCA_N', 'Ubiquitin_N', 'pGSK3B_Tyr216_N', 'SHH_N', 'BAD_N', 'BCL2_N',
        'pS6_N', 'pCFOS_N', 'SYP_N', 'H3AcK18_N', 'EGR1_N', 'H3MeK4_N',
        'CaNA_N', 'Genotype', 'Treatment', 'Behavior', 'class'],
       dtype='object'),
 82)
In [3]:
# 查看存在的数据量
# 查看每列的非缺失值数量
data.notnull().sum()
Out[3]:
MouseID      1080
DYRK1A_N     1077
ITSN1_N      1077
BDNF_N       1077
NR1_N        1077
             ... 
CaNA_N       1080
Genotype     1080
Treatment    1080
Behavior     1080
class        1080
Length: 82, dtype: int64
In [4]:
# 缺失数据的特征
loss_some_datas_columns : pandas.Index = data.columns[data.isnull().any()]
loss_some_datas_columns, loss_some_datas_columns.__len__()
Out[4]:
(Index(['DYRK1A_N', 'ITSN1_N', 'BDNF_N', 'NR1_N', 'NR2A_N', 'pAKT_N', 'pBRAF_N',
        'pCAMKII_N', 'pCREB_N', 'pELK_N', 'pERK_N', 'pJNK_N', 'PKCA_N',
        'pMEK_N', 'pNR1_N', 'pNR2A_N', 'pNR2B_N', 'pPKCAB_N', 'pRSK_N', 'AKT_N',
        'BRAF_N', 'CAMKII_N', 'CREB_N', 'ELK_N', 'ERK_N', 'GSK3B_N', 'JNK_N',
        'MEK_N', 'TRKA_N', 'RSK_N', 'APP_N', 'Bcatenin_N', 'SOD1_N', 'MTOR_N',
        'P38_N', 'pMTOR_N', 'DSCR1_N', 'AMPKA_N', 'NR2B_N', 'pNUMB_N',
        'RAPTOR_N', 'TIAM1_N', 'pP70S6_N', 'BAD_N', 'BCL2_N', 'pCFOS_N',
        'H3AcK18_N', 'EGR1_N', 'H3MeK4_N'],
       dtype='object'),
 49)
In [5]:
# 缺失数据的特征的数据帧
loss_some_datas = data[loss_some_datas_columns]
loss_some_datas
Out[5]:
DYRK1A_N ITSN1_N BDNF_N NR1_N NR2A_N pAKT_N pBRAF_N pCAMKII_N pCREB_N pELK_N ... pNUMB_N RAPTOR_N TIAM1_N pP70S6_N BAD_N BCL2_N pCFOS_N H3AcK18_N EGR1_N H3MeK4_N
0 0.503644 0.747193 0.430175 2.816329 5.990152 0.218830 0.177565 2.373744 0.232224 1.750936 ... 0.394721 0.339571 0.482864 0.294170 0.122652 NaN 0.108336 0.114783 0.131790 0.128186
1 0.514617 0.689064 0.411770 2.789514 5.685038 0.211636 0.172817 2.292150 0.226972 1.596377 ... 0.368255 0.321959 0.454519 0.276431 0.116682 NaN 0.104315 0.111974 0.135103 0.131119
2 0.509183 0.730247 0.418309 2.687201 5.622059 0.209011 0.175722 2.283337 0.230247 1.561316 ... 0.363880 0.313086 0.447197 0.256648 0.118508 NaN 0.106219 0.111883 0.133362 0.127431
3 0.442107 0.617076 0.358626 2.466947 4.979503 0.222886 0.176463 2.152301 0.207004 1.595086 ... 0.366771 0.328492 0.442650 0.398534 0.132781 NaN 0.111262 0.130405 0.147444 0.146901
4 0.434940 0.617430 0.358802 2.365785 4.718679 0.213106 0.173627 2.134014 0.192158 1.504230 ... 0.351551 0.312206 0.419095 0.393447 0.129954 NaN 0.110694 0.118481 0.140314 0.148380
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1075 0.254860 0.463591 0.254860 2.092082 2.600035 0.211736 0.171262 2.483740 0.207317 1.057971 ... 0.260516 0.239130 0.340756 0.527041 0.190483 NaN 0.183324 0.318782 0.204660 0.328327
1076 0.272198 0.474163 0.251638 2.161390 2.801492 0.251274 0.182496 2.512737 0.216339 1.081150 ... 0.275837 0.235080 0.346252 0.518377 0.190463 NaN 0.175674 0.325639 0.200415 0.293435
1077 0.228700 0.395179 0.234118 1.733184 2.220852 0.220665 0.161435 1.989723 0.185164 0.884342 ... 0.235239 0.231876 0.292788 0.460202 0.216682 NaN 0.158296 0.321306 0.229193 0.355213
1078 0.221242 0.412894 0.243974 1.876347 2.384088 0.208897 0.173623 2.086028 0.192044 0.922595 ... 0.258475 0.215951 0.301587 0.490496 0.222263 NaN 0.196296 0.335936 0.251317 0.365353
1079 0.302626 0.461059 0.256564 2.092790 2.594348 0.251001 0.191811 2.361816 0.223632 1.064085 ... 0.281486 0.273698 0.368269 0.546729 0.227606 NaN 0.187556 0.335062 0.252995 0.365278

1080 rows × 49 columns

In [6]:
import math
import matplotlib.pyplot as plt
from matplotlib.axes import Axes

# 特征总数
num_features = len(loss_some_datas_columns)

def draw_distribution():
    # 计算子图网格布局:每行 4 个图
    num_cols = 4
    num_rows = math.ceil(num_features / num_cols)

    # 创建子图
    fig, axes = plt.subplots(num_rows, num_cols, figsize=(16, 4 * num_rows))
    axes = axes.flatten()  # 将 axes 转为 1D 数组,方便索引

    # 绘制每个特征的分布
    for i, col in enumerate(loss_some_datas_columns):
        ax : Axes = axes[i]
        # ax.scatter(range(len(loss_some_datas[col])), loss_some_datas[col], alpha=0.6, s=10)
        ax.hist(loss_some_datas[col])
        ax.set_title(f"Feature: {col}")
        ax.set_xlabel("Index")
        ax.set_ylabel("Value")

    # 如果子图多于特征数量,隐藏多余的空子图
    for j in range(i + 1, len(axes)):
        axes[j].axis('off')

    # 调整布局并显示
    plt.tight_layout()
    plt.show()
draw_distribution()
No description has been provided for this image

数据处理¶

对于数据中部分是空缺值,考虑到基因值较为连续,又有上面的各种缺失特征下的值分布,发现缺失值所在特征的值分布不是均匀分布,而是很明显的某区间居多,其他区间较少

采取策略:按分布比例填充(随机采样)¶

根据非缺失值的分布,用随机采样的方法填充缺失值,从而保持整体分布的相似性。

In [7]:
import numpy

# 锁定随机值,保证数据一致性
numpy.random.seed(42)

def fill_missing_by_distribution(df : pandas.DataFrame, column : str) -> pandas.DataFrame:
    # 获取非缺失值
    non_missing_values = df[column].dropna()
    # 按原始分布随机采样填补
    df[column] = df[column].apply(
        lambda x: numpy.random.choice(non_missing_values) if numpy.isnan(x) else x
    )
    return df

# 对缺失特征进行分布式填充
# loss : str, feature's name.
for loss in loss_some_datas_columns:
    data = fill_missing_by_distribution(data, loss)
# 最后核对一下是不是和给定数据量一样
data.notnull().sum()
Out[7]:
MouseID      1080
DYRK1A_N     1080
ITSN1_N      1080
BDNF_N       1080
NR1_N        1080
             ... 
CaNA_N       1080
Genotype     1080
Treatment    1080
Behavior     1080
class        1080
Length: 82, dtype: int64
In [8]:
# 另外顺手保存一下
data.to_csv("Complete_Data_Cortex_Nuclear.csv", index=False)

现在数字型数据已经处理完了,该处理字符型数据了¶

输入特征Genotype、Treatment、Behavior映射,同时输出class也需要

In [23]:
# 在一开始进行空值统计的时候,我们就发现,字符型特征未出现缺失,也不可能缺失,因为是预测目标
# 由于数据量不多,通过浏览数据以及文章中给的资料,发现,数据就那几种
# 为了验证我们的直觉,等会我来用柱状图统计,然后再决定然后处理这些数据
# 提取输出
output_column_datas : pandas.Series = data['class']
output_column_datas, output_column_datas
Out[23]:
(0       c-CS-m
 1       c-CS-m
 2       c-CS-m
 3       c-CS-m
 4       c-CS-m
          ...  
 1075    t-SC-s
 1076    t-SC-s
 1077    t-SC-s
 1078    t-SC-s
 1079    t-SC-s
 Name: class, Length: 1080, dtype: object,
 0       c-CS-m
 1       c-CS-m
 2       c-CS-m
 3       c-CS-m
 4       c-CS-m
          ...  
 1075    t-SC-s
 1076    t-SC-s
 1077    t-SC-s
 1078    t-SC-s
 1079    t-SC-s
 Name: class, Length: 1080, dtype: object)
In [24]:
strs_column_datas_copes : pandas.DataFrame = data[['Genotype', 'Treatment', 'Behavior']]
strs_column_datas_copes
Out[24]:
Genotype Treatment Behavior
0 Control Memantine C/S
1 Control Memantine C/S
2 Control Memantine C/S
3 Control Memantine C/S
4 Control Memantine C/S
... ... ... ...
1075 Ts65Dn Saline S/C
1076 Ts65Dn Saline S/C
1077 Ts65Dn Saline S/C
1078 Ts65Dn Saline S/C
1079 Ts65Dn Saline S/C

1080 rows × 3 columns

In [25]:
# 查看每种特征有哪些值分布
# 索性:数据分配较为均匀
# 1. 避免类别不平衡问题
# 在机器学习中,类别不平衡问题通常是指某些类别的样本数量远远多于其他类别的样本,这会导致模型对少数类的预测效果很差,容易出现偏向多数类的情况。当目标分布均匀时,模型的训练能够平衡各个类别的学习,减少了类别不平衡带来的负面影响。
#
# 2. 提高模型的泛化能力
# 在训练过程中,均匀分布的目标意味着每个类别都有相对充足的样本供模型学习。这有助于模型更好地识别不同类别的特征,提升其在各个类别上的泛化能力,即使面对未见过的样本时,模型也能较好地进行分类。
#
# 3. 训练过程更稳定
# 如果数据集中的目标分布过于偏斜,模型在训练过程中可能会过度拟合到某些类别,从而影响整体表现。均匀分布的目标可以帮助训练过程更加平稳,因为模型不会过度依赖某一类别,能够更加公平地对待所有类别的样本。
#
# 4. 评估指标更可靠
# 如果数据集中的目标类别不均衡,常用的评估指标(如准确率)可能会误导我们对模型性能的评估。均匀分布使得常见的评估指标如准确率、F1分数等能够较好地反映模型在各个类别上的性能。
#
# 5. 数据采样和模型训练更加高效
# 均匀分布的目标让数据采样(比如交叉验证、随机采样等)和训练过程更加高效。因为每个类别都有足够的数据,模型不需要通过人工调整数据量(如过采样、欠采样)来弥补类别不平衡带来的问题。
plt.rcParams['font.sans-serif'] = ['SimHei']
def draw_strs_distribution():
    fig, axes = plt.subplots(1, 4, figsize=(12, 4))
    axes = axes.flatten()

    for index, colName in enumerate([*strs_column_datas_copes.columns, output_column_datas.name]):
        se: pandas.Series = data[colName] if index <= 2 else output_column_datas
        dictMap = {}
        for value in se:
            dictMap[value] = dictMap.get(value, 0) + 1
        ax : Axes = axes[index]
        ax.bar(dictMap.keys(), dictMap.values())
        ax.set_title(colName)
        ax.set_xlabel('Values')
        ax.set_ylabel('Count')
        if index == 3:
            ax.tick_params(axis='x', rotation=90)


    # 调整子图之间的间距
    plt.subplots_adjust(wspace=0.5, hspace=0.4)
    plt.suptitle('分布')
    plt.show()

draw_strs_distribution()
No description has been provided for this image
In [73]:
# 根据分布,输入的三个字符串数据分布可以由于只有两种/特征,而且根据class分布可知,这是一个多分类问题
# 因此这里选用独热编码,分别对三个输入和目标输出进行编码
# 处理该机器学习任务的时候,我打算把1080个数据分成4:1分别用于训练和测试
from sklearn.preprocessing import LabelEncoder, StandardScaler

# 字符型特征编码
label_encoder = LabelEncoder()
Y : pandas.Series = output_column_datas.copy()
Y = label_encoder.fit_transform(Y)
# 编码后的数据
Y
Out[73]:
array([0, 0, 0, ..., 7, 7, 7])
In [74]:
label_encoder.classes_
Out[74]:
array(['c-CS-m', 'c-CS-s', 'c-SC-m', 'c-SC-s', 't-CS-m', 't-CS-s',
       't-SC-m', 't-SC-s'], dtype=object)
In [75]:
# 特征和目标分离
# 其中X是除去编号的输入,y是目标输出
X = data.drop(['class', 'MouseID', 'Genotype', 'Treatment', 'Behavior'], axis=1)
y = Y

X.columns, X.columns.__len__(), type(X), type(y)
Out[75]:
(Index(['DYRK1A_N', 'ITSN1_N', 'BDNF_N', 'NR1_N', 'NR2A_N', 'pAKT_N', 'pBRAF_N',
        'pCAMKII_N', 'pCREB_N', 'pELK_N', 'pERK_N', 'pJNK_N', 'PKCA_N',
        'pMEK_N', 'pNR1_N', 'pNR2A_N', 'pNR2B_N', 'pPKCAB_N', 'pRSK_N', 'AKT_N',
        'BRAF_N', 'CAMKII_N', 'CREB_N', 'ELK_N', 'ERK_N', 'GSK3B_N', 'JNK_N',
        'MEK_N', 'TRKA_N', 'RSK_N', 'APP_N', 'Bcatenin_N', 'SOD1_N', 'MTOR_N',
        'P38_N', 'pMTOR_N', 'DSCR1_N', 'AMPKA_N', 'NR2B_N', 'pNUMB_N',
        'RAPTOR_N', 'TIAM1_N', 'pP70S6_N', 'NUMB_N', 'P70S6_N', 'pGSK3B_N',
        'pPKCG_N', 'CDK5_N', 'S6_N', 'ADARB1_N', 'AcetylH3K9_N', 'RRP1_N',
        'BAX_N', 'ARC_N', 'ERBB4_N', 'nNOS_N', 'Tau_N', 'GFAP_N', 'GluR3_N',
        'GluR4_N', 'IL1B_N', 'P3525_N', 'pCASP9_N', 'PSD95_N', 'SNCA_N',
        'Ubiquitin_N', 'pGSK3B_Tyr216_N', 'SHH_N', 'BAD_N', 'BCL2_N', 'pS6_N',
        'pCFOS_N', 'SYP_N', 'H3AcK18_N', 'EGR1_N', 'H3MeK4_N', 'CaNA_N'],
       dtype='object'),
 77,
 pandas.core.frame.DataFrame,
 numpy.ndarray)
In [76]:
# 数据分割
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
X_train.shape, X_test.shape
Out[76]:
((864, 77), (216, 77))

已经完成了以下步骤:¶

  • 数据读取:加载了数据。
  • 缺失值处理:使用分布式填充方法处理了缺失值。
  • 字符型特征编码:对字符型特征进行了标签编码。
  • 目标变量编码:对目标变量进行了标签编码。
  • 特征选择:排除了无关列(如 MouseID)。
  • 数据分割:按照 4:1 比例随机分割数据为训练集和测试集。
In [77]:
# 为了使特征量纲一致,通常需要对数值型特征进行标准化或归一化。
# StandardScaler特征标准化
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
X_train_scaled
Out[77]:
array([[-0.1110217 ,  0.05608569,  0.78860997, ..., -0.08918986,
        -0.11913695, -0.51604779],
       [ 0.41648983,  0.02159753, -1.66985957, ...,  1.48710283,
        -0.0436903 , -0.73506131],
       [ 0.22988659, -0.11291573, -2.11108501, ...,  0.14739791,
         0.32603518, -0.31564918],
       ...,
       [ 1.28486685,  1.56140133,  1.52843203, ..., -0.94547969,
         0.52147519,  0.92497192],
       [-0.26019858, -0.1323843 ,  2.05089497, ..., -0.31584713,
         0.21354662, -0.54765434],
       [ 0.1008547 ,  0.4133534 , -0.29144956, ..., -0.69362133,
        -0.18344431,  1.46911859]])
In [78]:
X_test_scaled
Out[78]:
array([[ 0.23643501,  0.14197418,  0.41725965, ...,  1.45449112,
         0.28690346,  0.35812565],
       [-0.96670063, -1.24779557, -1.7889251 , ...,  0.95207479,
         2.38153978, -0.77194996],
       [-0.55541606, -0.41037164, -0.64986097, ...,  2.61987485,
         1.00141504, -0.5787188 ],
       ...,
       [-0.40843068,  0.12182298,  0.6267048 , ...,  0.17670958,
         0.02532569, -0.79436052],
       [-0.05613064,  0.12562003, -0.14817458, ..., -0.58548566,
        -1.08086111,  1.74682872],
       [-0.54673033, -0.37239424,  0.53390614, ...,  1.19614753,
        -0.13482184, -0.57978215]])

选择模型进行预测¶

这是一个特征算是很多的问题,数据量较少,因此,选对模型至关重要

In [79]:
# 先试试K近邻模型(KNN)
from sklearn.neighbors import KNeighborsClassifier

# 初始化KNN模型
knn = KNeighborsClassifier()
# 训练模型
knn.fit(X_train_scaled, y_train)
# 预测
y_pred_knn = knn.predict(X_test_scaled)
# 查看有多少个相等的
correct_predictions = (y_pred_knn == y_test).sum()
correct_predictions, y_test.__len__(), correct_predictions / y_test.__len__()
Out[79]:
(211, 216, 0.9768518518518519)
In [80]:
from sklearn.metrics import accuracy_score

# 计算准确率,上面已经算过了
accuracy = accuracy_score(y_test, y_pred_knn)
print(f'Accuracy: {accuracy}')
Accuracy: 0.9768518518518519
In [81]:
label_encoder.classes_
Out[81]:
array(['c-CS-m', 'c-CS-s', 'c-SC-m', 'c-SC-s', 't-CS-m', 't-CS-s',
       't-SC-m', 't-SC-s'], dtype=object)
In [82]:
from sklearn.metrics import classification_report

# 确保 target_names 是类别的名称
target_names = list(label_encoder.classes_)
# 打印分类报告
report = classification_report(y_test, y_pred_knn)
print(report)
              precision    recall  f1-score   support

           0       0.91      0.97      0.94        32
           1       1.00      0.92      0.96        26
           2       1.00      0.96      0.98        27
           3       0.97      0.97      0.97        34
           4       1.00      1.00      1.00        19
           5       0.96      1.00      0.98        22
           6       1.00      1.00      1.00        25
           7       1.00      1.00      1.00        31

    accuracy                           0.98       216
   macro avg       0.98      0.98      0.98       216
weighted avg       0.98      0.98      0.98       216

In [84]:
# 试试MinMaxScaler
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
X_train_mm_scaled = scaler.fit_transform(X_train)
X_test_mm_scaled = scaler.transform(X_test)

# 初始化KNN模型
knn = KNeighborsClassifier()
# 训练模型
knn.fit(X_train_mm_scaled, y_train)
# 预测
y_pred_knn = knn.predict(X_test_mm_scaled)
accuracy = accuracy_score(y_test, y_pred_knn)
print(f'Accuracy: {accuracy}')
Accuracy: 0.9814814814814815

Precision(精确率):¶

定义:在所有被模型预测为某个类别的样本中,实际属于该类别的比例。

计算公式:Precision = TP / (TP + FP) TP:真正例(True Positives),预测正确的正样本数。 FP:假正例(False Positives),被误分类为该类别的负样本数。 精确率高意味着误分类为该类别的负样本较少。

Recall(召回率):

定义:在所有实际属于某个类别的样本中,模型成功预测为该类别的比例。

计算公式:Recall = TP / (TP + FN) FN:假负例(False Negatives),实际属于该类别,但被模型误分类为其他类别的样本。 召回率高意味着模型能够正确捕获该类别的大部分样本。

F1-Score:

定义:精确率和召回率的调和平均数,是精确率和召回率之间的平衡。

计算公式:F1-Score = 2 * (Precision * Recall) / (Precision + Recall) F1 分数综合考虑了精确率和召回率,越接近 1 表示模型越好。

Support(支持度):

定义:每个类别在测试集中实际的样本数。也就是说,每个类别在 y_test 中的出现次数。

补充:对于一些误差评估方案(损失函数),如RMSE、MSE、R2,通常分类问题并不需要计算这些

总结KNN¶

不考虑前面特征工程,但看模型,KNN为什么效果这么好?

  • 优点: 简单易懂,模型不需要训练,只需要保存数据。 对于某些高维数据集,KNN 能够通过相似度捕捉到隐藏的模式。
  • 缺点: 计算代价较高,尤其是在数据量很大的时候。 对噪声比较敏感,性能依赖于距离度量的选择。
  • 适用情况:

适合数据维度较高且没有显著噪声的分类问题。

  • 本问题正好满足了数据少、特征多、噪声很少(可以从直方图看出)
In [86]:
# 下面看看目前很火的XGBoost
from xgboost import XGBClassifier

# 初始化XGBoost分类器
xgb = XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42)

# 训练模型
xgb.fit(X_train_scaled, y_train)

# 预测
y_pred_xgb = xgb.predict(X_test_scaled)

# 评估性能
accuracy = accuracy_score(y_test, y_pred_xgb)
print(f"Accuracy: {accuracy:.4f}")
print("Classification Report:")
print(classification_report(y_test, y_pred_xgb))

## 试试MinMax
xgb.fit(X_train_mm_scaled, y_train)
y_pred_xgb = xgb.predict(X_test_mm_scaled)
accuracy = accuracy_score(y_test, y_pred_xgb)
print(f"Accuracy: {accuracy:.4f}")
print("Classification Report:")
print(classification_report(y_test, y_pred_xgb))
Accuracy: 0.9583
Classification Report:
              precision    recall  f1-score   support

           0       0.91      0.97      0.94        32
           1       0.96      0.92      0.94        26
           2       0.87      1.00      0.93        27
           3       1.00      1.00      1.00        34
           4       0.95      0.95      0.95        19
           5       1.00      1.00      1.00        22
           6       1.00      0.84      0.91        25
           7       1.00      0.97      0.98        31

    accuracy                           0.96       216
   macro avg       0.96      0.96      0.96       216
weighted avg       0.96      0.96      0.96       216

In [90]:
# 对KNN打包,
def knn_pipeline(n : int, scaler_stand : bool = True) -> float:
    # 初始化KNN模型
    knn = KNeighborsClassifier(n)
    if not scaler_stand:
        # 训练模型
        knn.fit(X_train_mm_scaled, y_train)
        # 预测
        y_pred_knn = knn.predict(X_test_mm_scaled)
    else:
         # 训练模型
        knn.fit(X_train_scaled, y_train)
        # 预测
        y_pred_knn = knn.predict(X_test_scaled)
    accuracy = accuracy_score(y_test, y_pred_knn)
    return accuracy

x = list(range(2, 21))
y = [knn_pipeline(n) for n in x]
print(x, y)
print("y mean == %f" % numpy.mean(y))
plt.plot(x, y)
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] [0.9768518518518519, 0.9907407407407407, 0.9814814814814815, 0.9768518518518519, 0.9583333333333334, 0.9537037037037037, 0.9490740740740741, 0.9212962962962963, 0.9305555555555556, 0.9259259259259259, 0.9259259259259259, 0.9305555555555556, 0.9166666666666666, 0.8981481481481481, 0.8981481481481481, 0.8796296296296297, 0.8796296296296297, 0.8703703703703703, 0.8657407407407407]
y mean == 0.927875
Out[90]:
[<matplotlib.lines.Line2D at 0x22252f88740>]
No description has been provided for this image
In [89]:
x = list(range(2, 21))
y = [knn_pipeline(n, False) for n in x]
print(x, y)
print("y mean == %f" % numpy.mean(y))
plt.plot(x, y)
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] [0.9814814814814815, 0.9953703703703703, 0.9907407407407407, 0.9814814814814815, 0.9722222222222222, 0.9675925925925926, 0.9675925925925926, 0.9583333333333334, 0.9537037037037037, 0.9305555555555556, 0.9305555555555556, 0.9212962962962963, 0.9166666666666666, 0.9120370370370371, 0.8981481481481481, 0.8935185185185185, 0.8935185185185185, 0.8935185185185185, 0.8796296296296297]
y mean == 0.938840
Out[89]:
[<matplotlib.lines.Line2D at 0x22252891dc0>]
No description has been provided for this image

原因:¶

和特征数据都在几点几之间有关

  1. n_neighbors 小时(2-3)准确度较高:

当 n_neighbors 为 2 和 3 时,准确度相对较高,分别为 0.9861 和 0.9954。 小的 n_neighbors 值意味着模型会更多依赖于最近的邻居。因此,当样本分布较为清晰、类之间有明显的界限时,少量邻居可能有助于更精确地分类。 2. n_neighbors 增大后准确度稍微下降: 当 n_neighbors 增加到 4 和 5 时,准确度保持较高,分别为 0.9907。 在这个区间,增加邻居数量对模型性能没有产生太大负面影响,准确度基本保持稳定。 3. n_neighbors 更大时,准确度开始下降: 从 n_neighbors = 6 开始,准确度开始逐渐下降。例如,n_neighbors = 6 的准确度降到了 0.9769,之后为 0.9676、0.9537 等。 较大的 n_neighbors 值使得模型考虑了更多的邻居,这有可能导致模型对局部噪声的鲁棒性减弱,同时也可能引入过度平滑的现象,导致模型的分类能力下降,尤其是在复杂或分布不均的数据集上。 4. 过大 n_neighbors 导致欠拟合: 当 n_neighbors 增加到 10 和 11 时,准确度进一步下降,分别为 0.9583 和 0.9444。 过大的 n_neighbors 值意味着分类器依赖更多的邻居,这样的模型更倾向于将不同类别的样本混合,从而导致预测结果的精度下降。这种情况表明,模型可能开始欠拟合,即无法准确捕捉到数据中的局部结构。 总结: 小的 n_neighbors 值(例如 2-3)通常适合于数据较为清晰的情况,因为它能够精确地基于局部信息进行分类。 中等的 n_neighbors 值(例如 4-5)通常提供较好的平衡,能够防止过度拟合或欠拟合。 较大的 n_neighbors 值(例如 6 及以上)通常会导致 欠拟合,因为它增加了分类决策的平滑性,可能忽略了数据中的某些重要局部模式。

In [91]:
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score

# 初始化支持向量机分类器
svm_model = SVC(kernel='linear', random_state=42)

# 训练模型
svm_model.fit(X_train_scaled, y_train)

# 在测试集上进行预测
y_pred_svm = svm_model.predict(X_test_scaled)

# 计算并输出准确率
accuracy_svm = accuracy_score(y_test, y_pred_svm)
print(f'SVM Accuracy: {accuracy_svm:.4f}')
SVM Accuracy: 0.9907
In [92]:
from sklearn.tree import DecisionTreeClassifier

# 初始化决策树分类器
dt_model = DecisionTreeClassifier(random_state=42)

# 训练模型
dt_model.fit(X_train_scaled, y_train)

# 在测试集上进行预测
y_pred_dt = dt_model.predict(X_test_scaled)

# 计算并输出准确率
accuracy_dt = accuracy_score(y_test, y_pred_dt)
print(f'Decision Tree Accuracy: {accuracy_dt:.4f}')
Decision Tree Accuracy: 0.8935
In [93]:
from sklearn.ensemble import RandomForestClassifier

# 初始化随机森林分类器
rf_model = RandomForestClassifier(random_state=42)

# 训练模型
rf_model.fit(X_train_scaled, y_train)

# 在测试集上进行预测
y_pred_rf = rf_model.predict(X_test_scaled)

# 计算并输出准确率
accuracy_rf = accuracy_score(y_test, y_pred_rf)
print(f'Random Forest Accuracy: {accuracy_rf:.4f}')
Random Forest Accuracy: 0.9861
In [94]:
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import accuracy_score
# 初始化神经网络分类器
mlp_model = MLPClassifier(hidden_layer_sizes=(100,), activation='relu', solver='adam', max_iter=1000, random_state=42)

# 训练模型
mlp_model.fit(X_train_scaled, y_train)

# 在测试集上进行预测
y_pred_mlp = mlp_model.predict(X_test_scaled)

# 计算并输出准确率
accuracy_mlp = accuracy_score(y_test, y_pred_mlp)
print(f'MLP Neural Network Accuracy: {accuracy_mlp:.4f}')
MLP Neural Network Accuracy: 0.9954
In [95]:
# 初始化并训练多分类逻辑回归模型
from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression(max_iter=1000, multi_class='ovr', solver='lbfgs')  # 'ovr'表示一对多策略
logreg.fit(X_train_scaled, y_train)

# 预测
y_pred_logreg = logreg.predict(X_test_scaled)

# 计算准确率
accuracy = accuracy_score(y_test, y_pred_logreg)
print(f'Accuracy: {accuracy}')
Accuracy: 0.9814814814814815
In [106]:
# 获取每个特征的系数
# 正系数表示特征对该类别的影响是正向的,而负系数表示负向影响。系数的大小表示影响的强度。
# logreg.coef_ 返回的是每个类别(目标)对应的特征权重(系数)
plt.rcParams['axes.unicode_minus'] = False
coefficients = logreg.coef_
feature_names = X.columns
lfeatures = len(feature_names)
targets = list(set(output_column_datas.values))
ltargets = len(targets)
fig, axes = plt.subplots(ltargets, 1, figsize=(12, 12))
for index, ax in enumerate(axes):
    ax : Axes
    ax.plot(numpy.array(range(lfeatures)), coefficients[index])
    ax.scatter(numpy.array(range(lfeatures)), coefficients[index], c='#000fff', s=30)
    ax.set_title(targets[index])
    ax.tick_params(axis='x', rotation=90)
    if index == len(axes) - 1:
        ax.set_xticks(numpy.arange(lfeatures))  # 设置 X 轴位置
        ax.set_xticklabels(feature_names, rotation=90)  # 设置 X 轴标签为特征名称,并旋转90度以避免重叠
No description has been provided for this image