• 机器学习笔记 - 使用机器学习进行鸟类物种分类


    一、问题概述

            科学家们已经确定,一种已知的鸟类应该分为 3 个不同且独立的物种。这些物种是该国特定地区的特有物种,必须尽可能精确地跟踪和估计它们的种群。

            因此,一个非营利性的保护协会承担了这项任务。他们需要能够根据现场官员在野外观察到的特征记录他们遇到的物种。

            使用某些遗传特征和位置数据,您能预测已观察到的鸟类种类吗?

            这是一个初学者级别的练习比赛,你的目标是根据属性或位置预测鸟类的种类。

    Bird Species Classification Challenge | bitgrit bitgrit hosts data science competitions for all levels. Join in and compete in a range of competitions.https://bitgrit.net/competition/16

    二、数据集

            数据已方便地拆分为训练和测试数据集。在每次训练和测试中,您都会获得位置 1 到 3 的鸟类数据。

            数据集下载地址

    1. 链接:https://pan.baidu.com/s/1aalzQNr0IQLQc3X4JTu9nQ
    2. 提取码:xvy0

             下面来看看前五行training_set.csv

    bill_depthbill_lengthwing_lengthlocationmasssexID
    14.348.2210loc_246000284
    14.448.4203loc_246250101
    18.4NA200loc_334000400
    14.9821138247.50487805NANA4800098
    18.9821138238.25930705217.1869919loc_352000103

             training_set和training_target可以根据‘id’列关联。

            列的含义如下

    1. species : 动物种类 (A, B, C)
    2. bill_length : 喙长 (mm)
    3. bill_depth : 喙深 (mm)
    4. wing_length : 翼长 (mm)
    5. mass : 体重 (g)
    6. location : 岛型 (Location 1, 2, 3 )
    7. 性别 :动物性别(0:男性;1:女性;NA:未知)

    三、编写代码

    1、导入库

    1. import pandas as pd
    2. # plotting
    3. import matplotlib
    4. import matplotlib.pyplot as plt
    5. import seaborn as sns
    6. matplotlib.rcParams['figure.dpi'] = 100
    7. sns.set(rc={'figure.figsize':(11.7,8.27)})
    8. sns.set(style="whitegrid")
    9. %matplotlib inline
    10. # ml
    11. from sklearn.metrics import ConfusionMatrixDisplay, classification_report
    12. from sklearn.model_selection import train_test_split
    13. from sklearn.impute import SimpleImputer
    14. from sklearn.preprocessing import LabelEncoder
    15. from sklearn.tree import DecisionTreeClassifier
    16. from sklearn import tree

    2、处理缺失值的方法

    1. def missing_vals(df):
    2. """prints out columns with perc of missing values"""
    3. missing = [
    4. (df.columns[idx], perc)
    5. for idx, perc in enumerate(df.isna().mean() * 100)
    6. if perc > 0
    7. ]
    8. if len(missing) == 0:
    9. return "no missing values"
    10. # sort desc by perc
    11. missing.sort(key=lambda x: x[1], reverse=True)
    12. print(f"There are a total of {len(missing)} variables with missing values\n")
    13. for tup in missing:
    14. print(str.ljust(f"{tup[0]:<20} => {round(tup[1], 3)}%", 1))

    3、加载数据

            首先,我们使用 read_csv 函数加载训练和测试数据。

            我们还将 training_set.csv(包含特征)与 training_target.csv(包含目标变量)合并并形成训练数据。

    1. train = pd.read_csv("dataset/training_set/training_set.csv")
    2. labels = pd.read_csv("dataset/training_set/training_target.csv")
    3. # join target variable to training set
    4. train = train.merge(labels, on="ID")
    5. test = pd.read_csv("dataset/test_set/test_set.csv")

    1. target_cols = "species"
    2. num_cols = ["bill_depth", "bill_length", "wing_length", "mass"]
    3. cat_cols = ["location", "sex"]
    4. all_cols = num_cols + cat_cols + [target_cols]
    5. train = train[all_cols]

    4、探索性数据分析Exploratory Data Analysis (EDA)

             这是我们研究数据趋势和模式的地方,包括数字和分类。

    train.info()

            使用 info 函数,我们可以看到行数和数据类型。 

    1. <class 'pandas.core.frame.DataFrame'>
    2. Int64Index: 435 entries, 0 to 434
    3. Data columns (total 7 columns):
    4. # Column Non-Null Count Dtype
    5. --- ------ -------------- -----
    6. 0 bill_depth 434 non-null float64
    7. 1 bill_length 295 non-null float64
    8. 2 wing_length 298 non-null float64
    9. 3 mass 433 non-null float64
    10. 4 location 405 non-null object
    11. 5 sex 379 non-null float64
    12. 6 species 435 non-null object
    13. dtypes: float64(5), object(2)
    14. memory usage: 27.2+ KB

            Numerical

            让我们绘制数值变量的直方图。 

    train[num_cols].hist(figsize=(20, 14));

            bill_depth 在 15 和 19 左右达到峰值
            纸币长度在 39 和 47 左右达到峰值
            翼长在 190 和 216 左右达到峰值
            质量向右倾斜

            Categorical

    1. to_plot = cat_cols + [target_cols]
    2. fig, axes = plt.subplots(1, 3, figsize=(20, 7), dpi=100)
    3. for i, col_name in enumerate(train[to_plot].columns):
    4. sns.countplot(x = col_name, data = train, palette="Set1", ax=axes[i % 3])
    5. axes[i % 3].set_title(f"{col_name}", fontsize=13)
    6. plt.subplots_adjust(hspace=0.45)

             我们看到位置和物种似乎与它们各自的位置和物种相匹配(loc2 和物种 C、loc3 和物种 A)。 我们还看到雌性 (1) 鸟类比雄性鸟类略多。

    train.species.value_counts()
    1. C 182
    2. A 160
    3. B 93
    4. Name: species, dtype: int64

            仔细观察,我们发现目标变量是不平衡的,其中 B 类比 C 低近 100 个类,比 A 低大约 70 个。

            不平衡类是一个问题,因为它使模型偏向于更重视具有更多样本的类,即。 C 比 B 更经常被预测。

    5、缺失数据

            缺失值的百分比

    missing_vals(train)
    1. There are a total of 6 variables with missing values
    2. bill_length => 32.184%
    3. wing_length => 31.494%
    4. sex => 12.874%
    5. location => 6.897%
    6. mass => 0.46%
    7. bill_depth => 0.23%

            通过我们的辅助函数,我们发现 bill_length 和wing_length 有超过 30% 的缺失值

            热图Heatplot

    1. plt.figure(figsize=(10, 6))
    2. sns.heatmap(train.isnull(), yticklabels=False, cmap='viridis', cbar=False);

             我们还可以绘制热图以可视化缺失值并查看是否有任何模式。

            估算分类列

            让我们先看看我们的分类变量中有多少缺失变量

    train.sex.value_counts(dropna=False)
    1. 1.0 195
    2. 0.0 184
    3. NaN 56
    4. Name: sex, dtype: int64
    train.location.value_counts(dropna=False)
    1. loc_2 181
    2. loc_3 141
    3. loc_1 83
    4. NaN 30
    5. Name: location, dtype: int64

            让我们使用简单的 imputer 来处理它们,用最频繁的值替换它们。

    1. cat_imp = SimpleImputer(strategy="most_frequent")
    2. train[cat_cols] = cat_imp.fit_transform(train[cat_cols])

            再次确认,已经没有缺失值了。如您所见,通过“最频繁”策略,缺失值被估算为 1.0,这是最频繁的。 

    train.sex.value_counts(dropna=False)
    1. 1.0 251
    2. 0.0 184
    3. Name: sex, dtype: int64

            估算数值列

            让我们使用中值来估算我们的数值

    1. num_imp = SimpleImputer(strategy="median")
    2. train[num_cols] = num_imp.fit_transform(train[num_cols])
    missing_vals(train)
    'no missing values'

    6、特征工程

    train.species.value_counts()
    1. C 182
    2. A 160
    3. B 93
    4. Name: species, dtype: int64

            编码分类变量

            使用标签编码器,我们可以将分类变量(和目标变量)编码为数值。 我们这样做是因为大多数 ML 模型不适用于字符串值。

    1. le = LabelEncoder()
    2. le.fit(train['species'])
    3. le_name_map = dict(zip(le.classes_, le.transform(le.classes_)))
    4. le_name_map
    {'A': 0, 'B': 1, 'C': 2}

            我们可以先把编码器拟合到变量上,然后查看映射是什么样子的,这样以后我们就可以反转映射了

    train['species'] = le.fit_transform(train['species'])

            对于其他具有字符串变量(非数字)的列,我们也进行相同的编码。

    1. for col in cat_cols:
    2. if train[col].dtype == "object":
    3. train[col] = le.fit_transform(train[col])
    train.head()

    1. # Convert cat_features to pd.Categorical dtype
    2. for col in cat_cols:
    3. train[col] = pd.Categorical(train[col])

             我们还将分类特征转换为 pd.Categorical dtype

    train.dtypes
    1. bill_depth float64
    2. bill_length float64
    3. wing_length float64
    4. mass float64
    5. location category
    6. sex category
    7. species int64
    8. dtype: object

    7、创建新特征

    1. train['b_depth_length_ratio'] = train['bill_depth'] / train['bill_length']
    2. train['b_length_depth_ratio'] = train['bill_length'] / train['bill_depth']
    3. train['w_length_mass_ratio'] = train['wing_length'] / train['mass']

            在这里,我们创建一些具有除法的特征以形成变量的比率

    train.head()

    8、模型

            训练测试拆分

            现在是构建模型的时候了,我们首先将其拆分为 X(特征)和 y(目标变量),然后将其拆分为训练集和评估集。

            训练是我们训练模型的地方,评估是我们在将模型拟合到测试集之前对其进行测试的地方。

    1. X, y = train.drop(["species"], axis=1), train[["species"]].values.flatten()
    2. X_train, X_eval, y_train, y_eval = train_test_split(
    3. X, y, test_size=0.25, random_state=0)

            简单的决策树分类器

            在这里,我们使用 max_depth = 2 的简单超参数拟合基线模型

    dtree_model = DecisionTreeClassifier(max_depth = 2).fit(X_train, y_train)

            拟合数据后,我们可以使用它来进行预测

    dtree_pred = dtree_model.predict(X_eval)

    9、模型性能

    print(classification_report(dtree_pred, y_eval))
    1. precision recall f1-score support
    2. 0 1.00 0.70 0.82 57
    3. 1 0.71 0.92 0.80 13
    4. 2 0.75 1.00 0.86 39
    5. accuracy 0.83 109
    6. macro avg 0.82 0.87 0.83 109
    7. weighted avg 0.88 0.83 0.83 109

            分类报告向我们展示了分类器的有用指标。

            例如,我们模型的 f1-score 为 0.83

    10、混淆矩阵

            我们还可以构建一个混淆矩阵来可视化我们的分类器在什么方面做得好/坏。

    1. # save the target variable classes
    2. class_names = le_name_map.keys()
    3. titles_options = [
    4. ("Confusion matrix, without normalization", None),
    5. ("Normalized confusion matrix", "true"),
    6. ]
    7. for title, normalize in titles_options:
    8. fig, ax = plt.subplots(figsize=(8, 8))
    9. disp = ConfusionMatrixDisplay.from_estimator(
    10. dtree_model,
    11. X_eval,
    12. y_eval,
    13. display_labels=class_names,
    14. cmap=plt.cm.Blues,
    15. normalize=normalize,
    16. ax = ax
    17. )
    18. disp.ax_.set_title(title)
    19. disp.ax_.grid(False)
    20. print(title)
    21. print(disp.confusion_matrix)
    1. Confusion matrix, without normalization
    2. [[40 0 0]
    3. [ 5 12 0]
    4. [12 1 39]]
    5. Normalized confusion matrix
    6. [[1. 0. 0. ]
    7. [0.29411765 0.70588235 0. ]
    8. [0.23076923 0.01923077 0.75 ]]

             混淆矩阵向我们展示了它预测了更多的 A 类和 C 类,这并不奇怪,因为我们有更多的样本。

            它还表明该模型在应该是 B/C 时预测了更多的 A 类。

    11、特征重要性

    1. feature_imp = pd.DataFrame(sorted(zip(dtree_model.feature_importances_,X.columns)), columns=['Value','Feature'])
    2. plt.figure(figsize=(20, 15))
    3. sns.barplot(x="Value", y="Feature", data=feature_imp.sort_values(by="Value", ascending=False))
    4. plt.title('LightGBM Features')
    5. plt.tight_layout()
    6. # plt.savefig('lightgbm_fimp.png')

            从特征重要性来看,似乎质量预测物种的能力最好,其次是喙长。 其他变量在分类器中的重要性似乎为零

    1. fig = plt.figure(figsize=(25,20))
    2. _ = tree.plot_tree(dtree_model,
    3. feature_names=X.columns,
    4. class_names=list(class_names),
    5. filled=True)

            我们看到了在我们的决策树分类器的可视化中如何使用特征重要性。

            在根节点中,如果质量低于 4600 左右,则检查 bill_length,否则检查 bill_depth,然后在叶处预测类别。

     四、预测测试数据

            现在是时候在我们将模型拟合到测试数据之前,对训练数据进行相同的特征预处理和工程了。

    1. le = LabelEncoder()
    2. cat_imp = SimpleImputer(strategy="most_frequent")
    3. num_imp = SimpleImputer(strategy="median")
    4. test[cat_cols] = cat_imp.fit_transform(test[cat_cols])
    5. test[num_cols] = num_imp.fit_transform(test[num_cols])
    6. for col in cat_cols:
    7. if test[col].dtype == "object":
    8. test[col] = le.fit_transform(test[col])
    9. # Convert cat_features to pd.Categorical dtype
    10. for col in cat_cols:
    11. test[col] = pd.Categorical(test[col])
    12. # save ID column
    13. test_id = test["ID"]
    14. all_cols.remove('species')
    15. test = test[all_cols]
    16. test['b_depth_length_ratio'] = test['bill_depth'] / test['bill_length']
    17. test['b_length_depth_ratio'] = test['bill_length'] / test['bill_depth']
    18. test['w_length_mass_ratio'] = test['wing_length'] / test['mass']
    1. test_preds = dtree_model.predict(test)
    2. submission_df = pd.concat([test_id, pd.DataFrame(test_preds, columns=['species'])], axis=1)
    3. submission_df.head()
    IDspecies
    022
    150
    270
    380
    490

            请注意,物种值是数字,我们必须将其转换回字符串值。 使用之前带有 fit 的标签编码器,我们可以这样做。

    le_name_map
    {'A': 0, 'B': 1, 'C': 2}
    1. inv_map = {v: k for k, v in le_name_map.items()}
    2. inv_map
    {0: 'A', 1: 'B', 2: 'C'}
    1. submission_df['species'] = submission_df['species'].map(inv_map)
    2. submission_df.head()
    IDspecies
    02C
    15A
    27A
    38A
    49A
    submission_df.to_csv('solution.csv', index=False)

            最后,我们将数据框写入 csv 文件。

  • 相关阅读:
    将本地项目添加到github中的其他办法
    Redis进阶
    数据库系统原理与应用教程(038)—— MySQL 的索引(四):使用 EXPLAIN 命令分析索引
    Java面试题总结(一)
    单个数据源与多数据源使用mybatisplus分页插件total一直为0的解决办法
    MySQL binlog模式及主备的基本原理
    隐私计算行业应用情况和标准化现状
    【Hack The Box】linux练习-- Irked
    大一html5期末大作业 :基于html实现非遗文化网页设计题材【传统文化木雕】7个页面
    【网络原理】- 传输层 TCP 十大机制 / UDP 协议 && 自定义应用层协议
  • 原文地址:https://blog.csdn.net/bashendixie5/article/details/125635006