机器学习与图像处理中上采样与下采样

发布于:2024-11-27 ⋅ 阅读:(11) ⋅ 点赞:(0)

一、机器学习中的上采样

        目的:在机器学习中,上采样用于处理不平衡数据集,即某些类别的样本数量远多于其他类别。上采样的目标是通过增加少数类样本的数量来平衡类别分布,从而提高模型对少数类的识别能力。

1.随机过采样(Random Oversampling)

         在机器学习中,数据不平衡是一个常见的问题,尤其是在分类任务中,某些类别的样本数量远多于其他类别。这种不平衡可能会导致模型偏向于多数类,从而降低对少数类的识别能力。为了解决这个问题,一种简单的方法是通过对少数类样本进行过采样(oversampling),即随机选择少数类样本并复制它们,直到少数类和多数类样本的数量相等。

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from imblearn.over_sampling import RandomOverSampler

# 生成一个不平衡的数据集
X, y = make_classification(n_classes=2, class_sep=2,
                           weights=[0.1, 0.9], n_informative=3, n_redundant=1, flip_y=0,
                           n_features=20, n_clusters_per_class=1, n_samples=1000, random_state=10)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)

# 使用RandomOverSampler对少数类进行过采样
ros = RandomOverSampler(random_state=0)
X_resampled, y_resampled = ros.fit_resample(X_train, y_train)

# 训练随机森林分类器
clf = RandomForestClassifier(n_estimators=100, random_state=0)
clf.fit(X_resampled, y_resampled)

# 评估模型
score = clf.score(X_test, y_test)
print(f"模型准确率: {score:.2f}")

        这种方法的优点是实现简单,不需要进行复杂的数据生成步骤,如SMOTE(Synthetic Minority Over-sampling Technique)等。然而,这种方法也有其缺点,即可能导致过拟合,因为模型可能会对这些重复的样本过度学习,从而影响模型的泛化能力。

2.SMOTE(Synthetic Minority Over-sampling Technique)

        通过在少数类样本之间插值生成新的合成样本。对于每个少数类样本,SMOTE会在其最近的k个少数类样本的连线上随机选择点,生成新的样本。可以减少过拟合的风险,因为它生成的是新的、而不是重复的样本。但在极端不平衡的数据集中可能不太有效,因为合成样本可能不够多样化。

import pandas as pd
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from imblearn.over_sampling import SMOTE
from collections import Counter

# 生成一个不平衡的数据集
X, y = make_classification(n_samples=1000, n_features=20, n_informative=2, n_redundant=10,
                           n_clusters_per_class=1, weights=[0.9, 0.1], flip_y=0, random_state=42)
print(f"原始数据集类别分布: {Counter(y)}")

# 将数据划分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# 使用SMOTE算法对训练集进行过采样
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

# 打印过采样后的数据集类别分布
print(f"过采样后数据集类别分布: {Counter(y_resampled)}")

        在这段代码中,我们首先使用make_classification函数生成了一个不平衡的数据集,其中90%的样本属于多数类,10%的样本属于少数类。然后,我们将数据集划分为训练集和测试集。接着,我们使用SMOTE类从imblearn.over_sampling模块对训练集进行过采样,以平衡类别分布。最后,我们打印出过采样前后的数据集类别分布,以验证SMOTE的效果。

3.ADASYN(Adaptive Synthetic Sampling Approach for Imbalanced Learning)

        根据样本的分类难度自适应地生成合成样本。ADASYN会分析每个少数类样本的k最近邻,并根据这些邻居的分类难度来决定生成多少新的合成样本。可以生成更多样化的合成样本,适应不同的分类难度。但因为它需要对每个样本的邻居进行分析,其计算成本较高。

from collections import Counter
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from imblearn.over_sampling import ADASYN

# 生成一个不平衡的数据集
X, y = make_classification(n_classes=2, class_sep=2,
                           weights=[0.1, 0.9], n_informative=3, n_redundant=1, flip_y=0,
                           n_features=20, n_clusters_per_class=1, n_samples=1000, random_state=10)

# 查看原始数据集的类别分布
print('Original dataset shape %s' % Counter(y))

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# 使用ADASYN进行过采样
ada = ADASYN(random_state=42)
X_res, y_res = ada.fit_resample(X_train, y_train)

# 查看过采样后的数据集类别分布
print('Resampled dataset shape %s' % Counter(y_res))

        在这段代码中,我们首先使用make_classification函数生成了一个不平衡的数据集,其中90%的样本属于多数类,10%的样本属于少数类。然后,我们将数据集划分为训练集和测试集,并保持数据分布的一致性。接着,我们使用ADASYN类从imblearn.over_sampling模块对训练集进行过采样,以平衡少数类和多数类样本的数量。最后,我们打印出过采样前后的数据集类别分布,以验证ADASYN的效果。

二、图像处理中的上采样

        图像处理中的上采样,也称为放大图像或图像插值,主要目的是放大原图像,以便可以在更高分辨率的显示设备上显示。上采样有几种常见的方法:双线性插值、转置卷积(Transposed Convolution),以及反池化(Unpooling)。

1.双线性插值(Bilinear Interpolation):

       双线性插值(Bilinear Interpolation)是一种在二维图像中对像素值进行插值的方法,常用于图像缩放、旋转等图像处理操作。它通过对四个最近邻点的加权平均来估计目标点的值,这四个点构成一个矩形区域,目标点位于这个矩形的对角线上。

import numpy as np
import cv2

# 假设我们有一个简单的3x3图像
image = np.array([[10, 20, 30],
                  [40, 50, 60],
                  [70, 80, 90]], dtype=np.float32)

# 定义插值函数
def bilinear_interpolate(image, x, y):
    x_low, y_low = int(np.floor(x)), int(np.floor(y))
    x_high, y_high = min(x_low + 1, image.shape[0] - 1), min(y_low + 1, image.shape[1] - 1)
    
    Ia = image[x_low, y_low]
    Ib = image[x_high, y_low]
    Ic = image[x_low, y_high]
    Id = image[x_high, y_high]
    
    wa = (x_high - x) * (y_high - y)
    wb = (x - x_low) * (y_high - y)
    wc = (x_high - x) * (y - y_low)
    wd = (x - x_low) * (y - y_low)
    
    return wa * Ia + wb * Ib + wc * Ic + wd * Id

# 插值点坐标
x, y = 1.5, 1.5

# 进行插值
interpolated_value = bilinear_interpolate(image, x, y)
print(f"Interpolated value at ({x}, {y}): {interpolated_value}")

        在这段代码中,我们首先定义了一个简单的3x3图像,并定义了一个双线性插值函数bilinear_interpolate。然后,我们选择了一个插值点坐标,并使用该函数计算了该点的插值结果。这个示例展示了如何在一个简单的二维数组上应用双线性插值。在实际应用中,图像通常是更大的二维数组,且插值操作会在图像的每个像素上进行。

2.转置卷积(Transposed Convolution)

        转置卷积(Transpose Convolution),也称为反卷积(Deconvolution),是一种在深度学习中用于上采样的技术,它可以增加特征图的尺寸。与标准卷积不同,转置卷积通过“广播”输入元素来产生大于输入的输出,这使得它在需要恢复图像尺寸的任务中非常有用,比如在生成对抗网络(GANs)中从随机噪声生成图像,或者在语义分割中将低分辨率的特征图上采样到原始图像尺寸。转置卷积与标准卷积的主要区别在于,转置卷积的目的是增加数据的空间维度,而标准卷积则是减少数据的空间维度。在转置卷积中,填充是应用于输出的,而不是输入,步幅也是指定给输出的。

import torch
import torch.nn as nn

# 定义输入数据,比如一个单通道的28x28图像
input_data = torch.randn(1, 1, 28, 28)

# 定义转置卷积层,假设我们想要上采样到56x56的尺寸
# kernel_size是卷积核的大小,stride是步长
transpose_conv_layer = nn.ConvTranspose2d(in_channels=1, out_channels=1, kernel_size=3, stride=2)

# 应用转置卷积
output_data = transpose_conv_layer(input_data)

# 输出上采样后的数据尺寸
print(output_data.size())

在这个例子中,我们创建了一个转置卷积层,其中in_channelsout_channels分别是输入和输出的通道数,kernel_size是卷积核的大小,stride是步长,它决定了输出尺寸相对于输入尺寸的增加比例。通过应用这个转置卷积层,我们可以将输入数据的空间维度从28x28上采样到56x56。这个操作在语义分割、图像生成等任务中非常常见。

3.反池化(Unpooling)

        反池化(Unpooling)是深度学习中用于上采样的一种技术,通常与池化(Pooling)操作相对应。池化操作在卷积神经网络中用于降低特征图的空间维度,而反池化则是将特征图的空间维度恢复到池化之前的尺寸。反池化通常用于卷积神经网络的解码器部分,特别是在需要将特征图上采样到输入图像尺寸的任务中,例如在语义分割或图像分割任务中。

        反池化的一种常见实现是最大池化的反操作,即在最大池化时记录下最大值的位置,然后在反池化时将这些最大值放回到原来的位置,而其他位置则填充为0。这样做可以保留最重要的特征信息,同时恢复特征图的空间尺寸。

import torch
import torch.nn as nn
import torch.nn.functional as F

# 假设我们有一个4x4的特征图,并且我们知道池化操作的步长为2
input_feature = torch.tensor([[1, 2, 3, 4],
                             [5, 6, 7, 8],
                             [9, 10, 11, 12],
                             [13, 14, 15, 16]], dtype=torch.float32).unsqueeze(0).unsqueeze(0)

# 执行2x2的最大池化,并记录最大值的索引
output, indices = F.max_pool2d(input_feature, kernel_size=2, stride=2, return_indices=True)

# 反池化操作,将最大值放回原来的位置
unpool_output = F.max_unpool2d(output, indices, kernel_size=2, stride=2)

print("Input Feature:\n", input_feature)
print("Output of Max Pooling:\n", output)
print("Output of Unpooling:\n", unpool_output)

        在这段代码中,我们首先创建了一个4x4的特征图,并使用max_pool2d函数执行最大池化操作,同时获取最大值的索引。然后,我们使用max_unpool2d函数执行反池化操作,将最大值放回到原来的位置。max_unpool2d函数需要池化操作的输出和索引,以及池化核的大小和步长作为参数。最终,unpool_output将是一个与input_feature尺寸相同的特征图,其中包含了原始特征图中的最大值,而其他位置则填充为0。

三、机器学习中的下采样

        下采样(Downsampling)在机器学习中通常指的是减少数据集中多数类样本的数量,以减少模型对多数类的偏好,从而提高对少数类的识别能力。这种方法特别适用于处理类别不平衡的数据集,即某些类别的样本数量远多于其他类别。

1. 随机欠采样(Random Undersampling)

        通过随机选择并删除多数类样本,直到多数类和少数类样本数量相等或达到某个预定的比例。实施简单,直接减少多数类样本的数量,可以快速平衡数据集中的类别分布。但可能会导致信息丢失,特别是当多数类样本数量远多于少数类时,删除大量样本可能会导致丢失有用的。

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from imblearn.under_sampling import RandomUnderSampler

# 生成一个不平衡的数据集
X, y = make_classification(n_classes=2, class_sep=2,
                           weights=[0.1, 0.9], n_informative=3, n_redundant=1, flip_y=0,
                           n_features=20, n_clusters_per_class=1, n_samples=1000, random_state=10)

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# 使用RandomUnderSampler进行欠采样
rus = RandomUnderSampler(random_state=42)
X_resampled, y_resampled = rus.fit_resample(X_train, y_train)

# 查看欠采样后的数据集类别分布
print(f"Original dataset shape {X_train.shape[0]} samples - {len(set(y_train))} classes")
print(f"Resampled dataset shape {X_resampled.shape[0]} samples - {len(set(y_resampled))} classes")

        在这段代码中,我们首先使用make_classification函数生成了一个不平衡的数据集,其中10%的样本属于少数类,90%的样本属于多数类。然后,我们将数据集划分为训练集和测试集。接着,我们使用RandomUnderSampler类从imblearn.under_sampling模块对训练集进行欠采样,以平衡类别分布。最后,我们打印出欠采样前后的数据集类别分布,以验证随机欠采样的效果。

2. 基于聚类的欠采样(Clustering-Based Undersampling)

        首先对多数类样本进行聚类,然后从每个聚类中选择一个或几个代表性样本,删除其他样本。这种方法旨在保留多数类样本的多样性,同时减少样本数量。可以减少信息丢失,因为它保留了多数类样本的代表性。但实现相对复杂,需要进行聚类分析。

import numpy as np
from sklearn.datasets import make_classification
from sklearn.cluster import KMeans

# 生成一个不平衡的数据集
X, y = make_classification(n_samples=1000, n_features=2, n_informative=2, n_redundant=0, n_clusters_per_class=1, weights=[0.1, 0.9], random_state=42)

# 将多数类样本和少数类样本分开
X_majority = X[y == 0]
X_minority = X[y == 1]

# 确定要保留的多数类样本数量,使其与少数类样本数量相等
n_minority = len(X_minority)
n_clusters = n_minority

# 对多数类样本进行K-Means聚类
kmeans = KMeans(n_clusters=n_clusters, random_state=42)
kmeans.fit(X_majority)

# 从每个簇中随机选择一个样本
cluster_indices = kmeans.labels_
X_resampled = np.zeros((n_clusters, X_majority.shape[1]))
for i in range(n_clusters):
    cluster_samples = X_majority[cluster_indices == i]
    X_resampled[i] = cluster_samples[np.random.choice(len(cluster_samples))]

# 将重新采样的多数类样本与少数类样本合并
X_balanced = np.vstack((X_resampled, X_minority))
y_balanced = np.hstack((np.zeros(n_clusters), np.ones(n_minority)))

# 打印结果以验证
print("原始多数类样本数量:", len(X_majority))
print("重新采样后的多数类样本数量:", len(X_resampled))
print("重新采样后的总样本数量:", len(X_balanced))

        在这个示例中,我们首先生成了一个不平衡的数据集,然后分离出多数类和少数类样本。我们使用K-Means算法对多数类样本进行聚类,并从每个簇中随机选择一个样本,使得重新采样后的多数类样本数量与少数类样本数量相等。最后,我们将重新采样的多数类样本与少数类样本合并,得到一个平衡的数据集。这个过程可以有效地减少多数类样本的数量,同时尽量保留原始数据集中的信息。

3. 条件欠采样(Conditional Undersampling)

        是一种更精细的下采样方法,它不是简单地随机删除多数类样本,而是根据某些条件(如样本的特征值、类别分布等)来选择删除哪些样本。这种方法旨在在减少样本数量的同时,尽可能保留多数类样本的多样性。可以减少信息丢失,提高模型的泛化能力。但需要精心设计删除样本的条件,以避免引入偏差。

from sklearn.datasets import make_classification
from imblearn.under_sampling import CondensedNearestNeighbour

# 生成一个不平衡的数据集
X, y = make_classification(n_samples=1000, n_features=2, n_informative=2, n_redundant=0, n_clusters_per_class=1,
                           weights=[0.1, 0.9], random_state=42)

# 使用CondensedNearestNeighbour进行条件欠采样
cnn = CondensedNearestNeighbour(random_state=42)
X_resampled, y_resampled = cnn.fit_resample(X, y)

# 打印欠采样后的数据集类别分布
print(f"Resampled dataset shape {X_resampled.shape[0]} samples - {len(set(y_resampled))} classes")

        在这个示例中,我们首先使用make_classification函数生成了一个不平衡的数据集。然后,我们使用CondensedNearestNeighbour类对多数类样本进行条件欠采样。CondensedNearestNeighbour是一种条件欠采样方法,它通过迭代地选择和删除样本来平衡数据集,同时尽量保留对模型训练有价值的样本。最后,我们打印出欠采样后的数据集类别分布,以验证条件欠采样的效果。

四、图像处理中的下采样

        对于一幅图像尺寸为M×N,对其进行s倍下采样,可以得到(M/s)×(N/s)尺寸的分辨率图像。在矩阵形式的图像中,就是把原始图像s×s窗口内的图像变成一个像素,这个像素点的值就是窗口内所有像素的均值或最大值。

1.最大池化(Max Pooling)

        在给定的池化窗口内选择最大值作为输出特征。这种方法可以保留图像中最重要的特征。可以有效地保留图像中的重要特征,但可能会丢失一些细节信息。

import torch
import torch.nn as nn

# 创建一个1x3x3的输入张量,模拟一个单通道的3x3特征图
input_tensor = torch.tensor([[[
    [1, 3, 2],
    [4, 6, 5],
    [7, 8, 9]
]]], dtype=torch.float32)

# 定义最大池化层,这里使用2x2的窗口和步长为2
max_pool = nn.MaxPool2d(kernel_size=2, stride=2)

# 应用最大池化
output_tensor = max_pool(input_tensor)

print("Input Tensor:\n", input_tensor)
print("Output Tensor after Max Pooling:\n", output_tensor)

        在这个例子中,我们首先创建了一个1x3x3的输入张量,模拟一个单通道的3x3特征图。然后,我们定义了一个MaxPool2d层,其中kernel_size=2表示池化窗口的大小为2x2,stride=2表示窗口移动的步长为2。通过应用这个最大池化层,我们得到了输出张量,其尺寸为1x1x1,因为输入的特征图在每个方向上都被减少了一半。

2.平均池化(Average Pooling)

        计算给定池化窗口内所有值的平均数作为输出特征。这种方法可以减少特征的维度并保留有效信息。可以减少特征的维度,同时保留图像的总体信息,但可能会模糊图像的细节。

import torch
import torch.nn as nn

# 创建一个1x3x3的输入张量,模拟一个单通道的3x3特征图
input_tensor = torch.tensor([[[
    [1, 3, 2],
    [4, 6, 5],
    [7, 8, 9]
]]], dtype=torch.float32)

# 定义平均池化层,这里使用2x2的窗口和步长为2
avg_pool = nn.AvgPool2d(kernel_size=2, stride=2)

# 应用平均池化
output_tensor = avg_pool(input_tensor)

print("Input Tensor:\n", input_tensor)
print("Output Tensor after Average Pooling:\n", output_tensor)

        在这个例子中,我们首先创建了一个1x3x3的输入张量,模拟一个单通道的3x3特征图。然后,我们定义了一个AvgPool2d层,其中kernel_size=2表示池化窗口的大小为2x2,stride=2表示窗口移动的步长为2。通过应用这个平均池化层,我们得到了输出张量,其尺寸为1x1x1,因为输入的特征图在每个方向上都被减少了一半。

3.全局平均池化(Global Average Pooling)

        对整个特征图计算平均值,常用于分类任务的最后一层,以减少全连接层的参数数量。可以减少模型的参数数量,提高计算效率,但可能会丢失空间信息,因为它不区分特征图的不同区域。

import torch
import torch.nn as nn

# 定义一个简单的卷积神经网络模型
class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        # 添加全局平均池化层
        self.gap = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Linear(128, 10)  # 假设有10个类别

    def forward(self, x):
        x = torch.relu(self.conv1(x))
        x = torch.relu(self.conv2(x))
        x = self.gap(x)  # 使用全局平均池化
        x = x.view(x.size(0), -1)  # 将特征图展平
        x = self.fc(x)
        return x

# 实例化模型并进行前向传播
model = SimpleCNN()
input_tensor = torch.randn(1, 3, 32, 32)  # 假设输入为1张3通道的32x32图像
output = model(input_tensor)
print(output.size())  # 输出大小为[1, 10],表示10个类别的预测概率

        在这个例子中,我们首先定义了一个简单的卷积神经网络模型SimpleCNN,其中包含了两个卷积层、一个全局平均池化层和一个全连接层。在forward函数中,我们首先将输入图像经过两个卷积层进行特征提取,然后使用AdaptiveAvgPool2d类实现全局平均池化,将特征图的大小调整为1x1,最后使用全连接层对特征进行分类。需要注意的是,在使用全局平均池化时,我们需要将特征图展平后再输入到全连接层中。在上面的代码中,我们使用view函数将特征图展平为一个长度为128的向量,然后输入到全连接层中进行分类。