【自学笔记】处理类别数据、独热编码和降维(主成分分析)

类别数据

  与数值特征不同,类别数据往往更难被计算机理解,主要分为序数标称
  序数具有顺序,比如衣服尺码中有XL>L>M等
  标称不含任何顺序,特征之间相互独立。

处理序数特征

  为了让算法正确解读序数特征,我们需要用整数来表示。我们可以定义映射关系,训练后再反向映射,比如:

import pandas as pd

df = pd.DataFrame([['green', 'M', 10.1, 'class2'],
                   ['red', 'L', 13.5, 'class1'],
                   ['blue', 'XL', 15.3, 'class2']])
df.columns = ['color', 'size', 'price', 'classlabel']

size_mapping = {'XL': 3,
                'L': 2,
                'M': 1}
df['size'] = df['size'].map(size_mapping)

在这里插入图片描述
在这里插入图片描述

  查看映射关系:

inv_size_mapping = {v: k for k, v in size_mapping.items()}
df['size'].map(inv_size_mapping)

在这里插入图片描述

处理标称数据

  与上述不同的是,标称数据没有顺序,所以可以更方便地自动获取映射:

# 获取这列数据,用unique去除重复,再为每种数据映射到数字
class_mapping = {label: idx for idx, label in enumerate(np.unique(df['classlabel']))}
df['classlabel'] = df['classlabel'].map(class_mapping)

# 反向映射
inv_class_mapping = {v: k for k, v in class_mapping.items()}
df['classlabel'] = df['classlabel'].map(inv_class_mapping)

在这里插入图片描述

  在ski-learn中也可以直接调用LabelEncoder类来实现:

from sklearn.preprocessing import LabelEncoder

class_le = LabelEncoder()
y = class_le.fit_transform(df['classlabel'].values)
# y = array([1, 0, 1])

# 反向映射
class_le.inverse_transform(y)
# >> array(['class2', 'class1', 'class2'], dtype=object)

独热编码

  虽然我们将标称数据映射成了整数,并声称其没有顺序,但机器学习算法会认为1大于0,2大于1,从而影响算法的偏好。

  解决这个问题的常用方案是独热编码。我们将每一个特征映射成同样大小的向量。比如:

array = [1, 0, 1, 2]

  将会变成:

array2 = [ [0, 1, 0],
      [1, 0, 0],
      [0, 1, 0],
      [0, 0, 1]]

注意

   使用独热编码要小心多重共线性,即所有的特征呈现出了一个相关性(如果n种取值中前n-1个都预测为0,那第n个一定是1)。如果发生了特征高度相关,会导致矩阵求逆是很难的,且会降低数据稳定性。

在这里插入图片描述
   所以,我们可以简单粗暴地删除一个特征列,不用担心丢失信息。

代码实现

   最方便的是pandas:

pd.get_dummies(df[['price', 'color', 'size']], drop_first=True)

   或者用sklearn中的OneHotEncoder:

from sklearn.preprocessing import OneHotEncoder

X = df[['color', 'size', 'price']].values
color_ohe = OneHotEncoder()

# 此处只转换单列
color_ohe.fit_transform(X[:, 0].reshape(-1, 1)).toarray()

  如果想转化多列,ColumnTransformer支持同时对不同列使用不同的转化策略。

# 导入ColumnTransformer类,它允许对数据集的不同列应用不同的转换器
from sklearn.compose import ColumnTransformer

# 从数据帧df中选择'color', 'size', 'price'三列,并将它们转换为NumPy数组赋值给X
X = df[['color', 'size', 'price']].values

# 定义ColumnTransformer实例c_transf
c_transf = ColumnTransformer([

    # 定义第一个转换器:对第0列('color'列)进行独热编码
    ('onehot', OneHotEncoder(), [0]),

    # 定义第二个转换器:对于第1列和第2列('size'和'price'列)不进行任何转换,直接保留原数据
    # 'nothing'是转换器的名称,'passthrough'是一个特殊的转换器,表示这些列的数据将被直接传递,不做任何修改
    ('nothing', 'passthrough', [1, 2])
])

# 使用X数据拟合c_transf,并对其进行转换
# fit_transform()方法首先会根据X数据拟合转换器,然后应用这些转换器对X数据进行转换
c_transf.fit_transform(X).astype(float)

降维

  数据的维度直接决定了训练模型的难度和时间成本,维度过高也有过拟合的风险。主要有两种降维技术:特征选择特征提取

序列特征选择算法

  经典的序列特征选择算法是序列后向选择(SBS),使用贪心的思想,每次选择一个性能损失最小的特征并删除,直到新的特征子空间包含了需要的特征数量。

  (1)假设 d d d为特征空间 X d X_{d} Xd的维数
  (2)找到影响最小的特征 x − ∈ X d x^{-}\in{X_{d}} xXd, x − = a r g m a x J ( X d − x ) x^{-}=argmaxJ(X_{d}-x) x=argmaxJ(Xdx) ------max指找到最大值,arg指找到对应使J最大的 x x x
  (3)从特征集中去除特征 x − x^{-} x X d − 1 = X d − x − X_{d-1}=X_{d}-x^{-} Xd1=Xdx d : = d − 1 d:=d-1 d:=d1
  (4)若已经减到期望的特征数,则停止,否则跳转步骤2。

实现

  ski-learn并没有现成的SBS模块,但《python机器学习》帮我们写了一个类,以后直接拿来用就好了

# 导入必要的库和模块
from sklearn.base import clone  # 用于深度克隆模型,避免原始模型被修改
from itertools import combinations  # 用于生成组合
import numpy as np  # 用于数学和数组操作
from sklearn.metrics import accuracy_score  # 用于评估模型预测的准确性
from sklearn.model_selection import train_test_split  # 用于数据集的划分

# 定义SBS类
class SBS():
    def __init__(self, estimator, k_features, scoring=accuracy_score, test_size=0.25, random_state=1):
        # 初始化方法
        self.scoring = scoring  # 评分函数,用于评估模型性能,默认为accuracy_score
        self.estimator = clone(estimator)  # 克隆所需的模型对象,用于避免原始模型被修改
        self.k_features = k_features  # 目标特征数量
        self.test_size = test_size  # 测试集大小
        self.random_state = random_state  # 随机状态,用于确保随机过程的可重复性

    def fit(self, X, y):
        # 模型拟合方法
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=self.test_size, random_state=self.random_state)

        # 将数据集划分为训练集和测试集

        dim = X_train.shape[1]  # 初始特征数量
        self.indices_ = tuple(range(dim))  # 初始特征索引
        self.subsets_ = [self.indices_]  # 存储所有特征子集
        score = self._calc_score(X_train, y_train, X_test, y_test, self.indices_)
        # 计算当前特征集的评分
        self.scores_ = [score]  # 存储每次迭代的评分

        # 主循环,通过逐步移除特征来减少特征数量
        while dim > self.k_features:

            scores = []

            subsets = []

            # 遍历当前特征集的所有可能子集(每个子集少一个特征)

            for p in combinations(self.indices_, r=dim - 1):
                score = self._calc_score(X_train, y_train, X_test, y_test, p)
                # 计算子集的评分
                scores.append(score)
                subsets.append(p)
                # 将评分和子集保存

            best = np.argmax(scores)  # 找到评分最高的子集

            self.indices_ = subsets[best]

            # 更新最佳子集

            self.subsets_.append(self.indices_)

            # 保存当前最佳子集

            dim -= 1  # 减少特征数量

            # 保存当前迭代的评分

            self.scores_.append(scores[best])
        self.k_score_ = self.scores_[-1]  # 最终特征集的评分

        return self

    def transform(self, X):
        # 特征转换方法
        return X[:, self.indices_]  # 返回保留的特征列

    def _calc_score(self, X_train, y_train, X_test, y_test, indices):
        # 计算评分的辅助方法
        self.estimator.fit(X_train[:, indices], y_train)  # 在训练集上拟合模型
        y_pred = self.estimator.predict(X_test[:, indices])  # 预测测试集标签
        score = self.scoring(y_test, y_pred)  # 评估预测结果
        return score
import matplotlib.pyplot as plt  # 导入matplotlib.pyplot模块,用于绘制图表

from sklearn.neighbors import KNeighborsClassifier  # 导入K近邻分类器

# 创建一个K近邻分类器实例,设定k值为5
knn = KNeighborsClassifier(n_neighbors=5)

# 从上文的代码段中,我们已经创建了SBS实例并用训练数据训练了该实例

# 这里的sbs是已经使用X_train_std和y_train数据训练过的SBS实例

# 这个SBS实例用于选择特征,其中k_features设为1,这意味着SBS将递归地减少特征直到只剩1个特征

sbs = SBS(knn, k_features=1)

# 用训练集数据调用SBS实例的fit方法,对特征进行选择
sbs.fit(X_train_std, y_train)

# 创建一个列表,其中的元素是SBS在每轮迭代中选择的特征子集的长度
k_feat = [len(k) for k in sbs.subsets_]

# 绘制特征数量与模型准确率的关系图
plt.plot(k_feat, sbs.scores_, marker='o')  # 以特征数量为x轴,准确率为y轴,绘制散点图
plt.ylim([0.7, 1.02])  # 设置y轴的范围为0.7到1.02
plt.ylabel('Accuracy')  # 设置y轴的标签为'Accuracy'
plt.xlabel('Number of features')  # 设置x轴的标签为'Number of features'

plt.grid()  # 显示网格线
plt.tight_layout()  # 自动调整子图参数,使之填充整个图像区域

# 保存图表为PNG文件,dpi参数指定图像分辨率,此行注释掉了,不会执行
# plt.savefig('images/04_08.png', dpi=300)

# 显示图表

plt.show()

  准确率随维度变化:
在这里插入图片描述

使用随机森林评估特征的重要性

  在随机森林或者决策树模型中,在构建每棵树的过程中,每个特征在节点上被用来分割数据集时,都会导致节点的“杂质度”(通常是基尼不纯度或熵)的减少。这个减少量可以被视为该特征对模型预测能力的贡献度。也称为决策树的平均杂质度衰减(Mean Decrease Impurity, MDI)。这是评价一个特征重要性的指标。

  scikit-learn中提供的随机森林模型已经为我们收集好了特征的重要程度:

from sklearn.ensemble import RandomForestClassifier

# 定义一个列表,其中包含数据集中除目标变量以外的所有特征的标签
feat_labels = df_wine.columns[1:]

# 创建一个随机森林分类器实例,设置树的数量为500,随机种子为1以保证结果可复现
forest = RandomForestClassifier(n_estimators=500,
                                random_state=1)

# 训练
forest.fit(X_train, y_train)

# 计算随机森林中每个特征的重要性,返回一个数组,数组中的值表示每个特征的重要性得分
importances = forest.feature_importances_

# 对特征的重要性得分进行排序,生成排序后的特征索引数组,[::-1]表示按照从大到小的顺序排序
indices = np.argsort(importances)[::-1]

# 遍历训练数据集中所有特征
for f in range(X_train.shape[1]):
    # 打印每个特征的排名、名称和重要性得分
    print("%2d) %-*s %f" % (f + 1, 30, 
                            feat_labels[indices[f]], 
                            importances[indices[f]]))
# 可视化
plt.title('Feature Importance')

# 绘制特征重要性的柱状图,x轴为特征的索引,y轴为特征的重要性得分
plt.bar(range(X_train.shape[1]), 
        importances[indices],
        align='center')  # 柱状图居中对齐

# 设置x轴的刻度标签,使用特征的名称,刻度标签旋转90度以适应图表
plt.xticks(range(X_train.shape[1]), 
           feat_labels[indices], rotation=90)

# 设置x轴的范围,确保所有特征都被包含在图表中
plt.xlim([-1, X_train.shape[1]])

# 调整图表布局,使其紧凑且适合显示
plt.tight_layout()

# plt.savefig('images/04_09.png', dpi=300)

plt.show()

在这里插入图片描述
  然后我们可以对特征进行挑选以降维:

from sklearn.feature_selection import SelectFromModel  # 导入SelectFromModel类,用于基于模型的特征选择

# 创建SelectFromModel实例,模型为随机森林分类器,设置阈值为0.1,表示选择所有特征重要性大于等于0.1的特征
# prefit参数为True表示提供的模型estimator已经被预训练过,SelectFromModel不需要再进行训练
sfm = SelectFromModel(forest, threshold=0.1, prefit=True)

# 使用SelectFromModel实例对训练数据集X_train进行转换,返回一个只包含选定特征的新数据集
X_selected = sfm.transform(X_train)

# 打印满足阈值条件的特征数量,即特征重要性大于等于0.1的特征数量
print('Number of features that meet this threshold criterion:', 
      X_selected.shape[1])

# 遍历选择后的特征,打印其排名、名称和重要性得分。这里实际上是对所有特征进行遍历,
# 但由于X_selected只包含选择的特征,因此实际上只打印出满足条件的那些特征
for f in range(X_selected.shape[1]):
    print("%2d) %-*s %f" % (f + 1, 30, 
                            feat_labels[indices[f]], 
                            importances[indices[f]]))

在这里插入图片描述

主成分分析实现降维

  对于数学零基础的,强推这篇:
  用通俗易懂的方式讲解:主成分分析(PCA)算法及案例

  主成分分析(PCA),属于一种无监督线性变换技术。如下图,令 x 1 , x 2 x_{1}, x_{2} x1,x2为原始特征轴,而 P C 1 , P C 2 PC1, PC2 PC1,PC2为主成分方向,可以理解为方差最大的方向
在这里插入图片描述

降维目标:

  (1)找到方差最大的方向,通过保留这个方向的信息,有助于最大化保留数据中的特征。
  (2)选择主成分,将原始数据投影到新坐标系中,使各个维度正交,有助于减少特征的冗余,实现降维

步骤1: 标准化

  与之前一样,不赘述:

import pandas as pd

df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/'
                      'machine-learning-databases/wine/wine.data',
                      header=None)
                      
from sklearn.model_selection import train_test_split

X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values

X_train, X_test, y_train, y_test = \
    train_test_split(X, y, test_size=0.3, 
                     stratify=y,
                     random_state=0)

步骤2: 构造协方差矩阵

 协方差定义:

  协方差(Covariance)是统计学和概率论中一个重要的概念,用于度量两个随机变量的线性相关程度。它描述了两个变量如何一起变化,即一个变量的值相对于其平均值变化时,另一个变量的值相对于其平均值变化的趋势和程度。
  对于两个随机变量 X X X Y Y Y,它们的协方差 C o v ( X , Y ) Cov(X, Y) Cov(X,Y)定义为:

C o v ( X , Y ) = E [ ( X − μ x ) ( Y − μ y ) ] Cov(X,Y)=E[(X-\mu_{x})(Y-\mu_{y})] Cov(X,Y)=E[(Xμx)(Yμy)]
       = 1 n ∑ i = 1 n ( x i − x ˉ ) ( y i − y ˉ ) =\frac{1}{n}\sum_{i=1}^{n}(x_{i}-\bar{x})(y_{i}-\bar{y}) =n1i=1n(xixˉ)(yiyˉ)

  假设数据集维度为 d d d,则我们构造一个 d × d d\times d d×d对称协方差矩阵 Σ \Sigma Σ,来存储不同特征之间的协方差,例如,特征 x j x_{j} xj x k x_{k} xk的协方差可以计算成:

σ j k = 1 n ∑ i = 1 n ( x j ( i ) − μ j ) ( y k ( i ) − μ k ) \sigma _{jk}=\frac{1}{n}\sum_{i=1}^{n}(x_{j}^{(i)}-\mu _{j})(y_{k}^{(i)}-\mu _{k}) σjk=n1i=1n(xj(i)μj)(yk(i)μk)

  然后我们找到它的特征向量 v ⃗ \vec{v} v 和特征值(标量) λ \lambda λ,满足:

Σ v ⃗ = λ v ⃗ \Sigma \vec{v}=\lambda \vec{v} Σv =λv

  这里的特征向量代表了协方差矩阵的主成分(最大方差的方向),而特征值定义了它们的大小。注意,一个 d × d d \times d d×d的矩阵最多有 d d d个特征向量。
  我们使用 N u m p y Numpy Numpy的 linalg.eig 函数获取特征向量和特征值:

import numpy as np

# 假设X_train_std是一个二维NumPy数组,包含了经过预处理(例如,标准化)的训练数据集。
# 计算X_train_std的协方差矩阵。使用.T来转置数据集,这是因为np.cov()方法期望每一列代表一个变量,每一行代表一个观测值。当调用np.cov()时,我们通常转置数据,以确保每一行代表一个变量(特征),这在计算协方差矩阵时是必需的。
cov_mat = np.cov(X_train_std.T)

# 使用NumPy的线性代数子模块linalg的eig()函数来计算cov_mat的特征值和特征向量。
# eig()函数返回两个对象:eigen_vals是一个一维数组,包含了cov_mat的特征值;eigen_vecs是一个二维数组,其中的每一列是与特征值相对应的特征向量。
eigen_vals, eigen_vecs = np.linalg.eig(cov_mat)

print('\nEigenvalues \n%s' % eigen_vals)  # 输出所有的特征值

在这里插入图片描述

步骤3: 特征变换

  将协方差矩阵分解为特征对(一个特征值和一个特征向量)后,我们进行以下步骤完成特征变换,将原数据集变换到新的主成分轴:

  (1)选出前 k k k大的特征值对应的特征向量,那么 k k k就是新特征子空间的维数。
  (2)用这 k k k个特征向量构建投影矩阵 W W W
  (3)用投影矩阵 W W W变换 d d d维的原数据集 X X X,获得新的 k k k维特征子空间。

  我们先获取投影矩阵 W W W

# eigen_vals是一个包含协方差矩阵特征值的一维数组,而eigen_vecs是一个包含协方差矩阵特征向量的二维数组。
# 创建一个列表,其中的每个元素都是一个元组,元组的第一个元素是特征值的绝对值(因为特征值可能为负),第二个元素是与该特征值对应的特征向量。
eigen_pairs = [(np.abs(eigen_vals[i]), eigen_vecs[:, i])
               for i in range(len(eigen_vals))]

# 根据特征值的绝对值大小对eigen_pairs列表进行排序,特征值大的特征向量排在前面。
# 使用sort()方法,key参数指定排序依据,这里是一个lambda函数,返回元组的第一个元素(特征值的绝对值),reverse=True表示降序排序。
eigen_pairs.sort(key=lambda k: k[0], reverse=True)  

# 选择排序后前两个主成分对应的特征向量,构建变换矩阵W。
# 通过np.hstack()函数水平堆叠(即按列堆叠)两个特征向量,形成一个二维数组。[:, np.newaxis]将一维特征向量转换为二维数组,便于水平堆叠。
w = np.hstack((eigen_pairs[0][1][:, np.newaxis],  
               eigen_pairs[1][1][:, np.newaxis]))

print('Matrix W:\n', w)  # 构建好的投影矩阵W,它包含了两个主成分方向。

在这里插入图片描述
  然后,我们通过计算矩阵点积将整个数据集变换成两个主成分:

X ′ = X W X^{'}=XW X=XW

X_train_pca = X_train_std.dot(w)
colors = ['r', 'b', 'g']
markers = ['s', 'x', 'o']

for l, c, m in zip(np.unique(y_train), colors, markers):
    plt.scatter(X_train_pca[y_train == l, 0], 
                X_train_pca[y_train == l, 1], 
                c=c, label=l, marker=m)

plt.xlabel('PC 1')
plt.ylabel('PC 2')
plt.legend(loc='lower left')
plt.tight_layout()
# plt.savefig('images/05_03.png', dpi=300)
plt.show()

在这里插入图片描述
  到这里,主成分分析部分已经完成,接下来就可以用线性分类器等方法继续训练模型

使用scikit-learn实现主成分分析

  scikit-learn中提供了转换器类PCA来实现主成分分析。
  先在plot_decision_regions_script.py文件中实现一个决策区域可视化的脚本:

from matplotlib.colors import ListedColormap

def plot_decision_regions(X, y, classifier, resolution=0.02):

    # setup marker generator and color map
    markers = ('s', 'x', 'o', '^', 'v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])

    # plot the decision surface
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
                           np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape)
    plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap)
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())

    # plot examples by class
    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl, 0], 
                    y=X[y == cl, 1],
                    alpha=0.6, 
                    color=cmap(idx),
                    edgecolor='black',
                    marker=markers[idx], 
                    label=cl)

  然后写主程序进行主成分分析:

from sklearn.linear_model import LogisticRegression  # 导入逻辑回归模型类。
from sklearn.decomposition import PCA  # 导入PCA类,用于数据降维。

# 创建PCA实例,指定保留2个主成分。若n_components设置为None,则保留所有主成分
pca = PCA(n_components=2) 

# 对训练集进行PCA变换,fit_transform()方法先拟合数据,再进行变换,返回降维后的数据。
X_train_pca = pca.fit_transform(X_train_std)

# >>pca.explained_variance_ratio_ # 这里可以输出一下解释方差比,决定要保留多少个维度

# 使用PCA模型对测试集进行变换,注意只使用transform()方法,因为模型已经在训练集上拟合好了。
X_test_pca = pca.transform(X_test_std)

# 创建逻辑回归分类器实例,设置分类策略为one-vs-rest,随机状态为1,求解器为L-BFGS-B。
lr = LogisticRegression(multi_class='ovr', random_state=1, solver='lbfgs')

# 使用训练集的降维数据训练逻辑回归模型。
lr = lr.fit(X_train_pca, y_train)

# 绘制决策边界
plot_decision_regions(X_train_pca, y_train, classifier=lr)  # 即上一份代码
plt.xlabel('PC 1')  # 设置x轴标签为“主成分1”。
plt.ylabel('PC 2')  # 设置y轴标签为“主成分2”。
plt.legend(loc='lower left')  # 添加图例,位置在左下角。
plt.tight_layout()  # 调整图形布局,确保标签不被裁剪。

plt.show()  # 显示图形。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值