多层感知器(MLP)和卷积神经网络(CNN)

今天我们来聊聊神经网络中的两种重要的前馈网络模型:多层感知器(MLP)和卷积神经网络(CNN)。虽然它们都基于相同的原理,但它们在设计和应用上有着不同的侧重点,分别适用于不同的任务。

多层感知器(MLP

多层感知器是神经网络的基础,它由多个感知器层(称为神经元)组成,每个神经元都是一个线性变换后接一个非线性激活函数。MLP可以学习复杂的非线性关系,是许多复杂任务的基础模型。

MLP的特点

* **结构简单**:MLP由线性层和激活函数层交替堆叠而成。

* **易于实现**:可以使用深度学习框架(如PyTorch)轻松实现MLP。

* **通用性强**:MLP可以应用于各种分类和回归任务。

我们来看一个简单的MLP实现:

import torch.nn as nn

import torch.nn.functional as F

class MultilayerPerceptron(nn.Module):

    def __init__(self, input_dim, hidden_dim, output_dim):

        super(MultilayerPerceptron, self).__init__()

        self.fc1 = nn.Linear(input_dim, hidden_dim)

        self.fc2 = nn.Linear(hidden_dim, output_dim)

    def forward(self, x_in, apply_softmax=False):

        intermediate = F.relu(self.fc1(x_in))

        output = self.fc2(intermediate)

        if apply_softmax:

            output = F.softmax(output, dim=1)

        return output

这个MLP有两个线性层,中间使用ReLU激活函数。最后一层可以选择输出softmax概率。

MLP的局限性

* **过拟合风险**:MLP容易过拟合,需要正则化方法来防止。

* **训练复杂度**:MLP的训练过程可能比CNN复杂。

卷积神经网络(CNN)

卷积神经网络是一种特殊的神经网络,它通过卷积操作提取输入数据的局部特征。CNN在图像识别、语音识别和自然语言处理等领域取得了显著的成果。

**CNN的特点**:

* **局部感知**:CNN学习局部特征,能够识别图像中的物体部分。

* **参数共享**:CNN中的卷积核在整个网络中共享,减少了参数数量。

* **平移不变性**:CNN可以识别平移后的物体。

我们来看一个简单的CNN实现

import torch.nn as nn

import torch.nn.functional as F

class SurnameClassifier(nn.Module):

    def __init__(self, initial_num_channels, num_classes, num_channels):

        super(SurnameClassifier, self).__init__()

        self.convnet = nn.Sequential(

            nn.Conv1d(in_channels=initial_num_channels, out_channels=num_channels, kernel_size=3),

            nn.ELU(),

            nn.Conv1d(in_channels=num_channels, out_channels=num_channels, kernel_size=3, stride=2),

            nn.ELU(),

            # ... more conv layers

        )

        self.fc = nn.Linear(num_channels, num_classes)

    def forward(self, x_surname, apply_softmax=False):

        features = self.convnet(x_surname).squeeze(dim=2)

        prediction_vector = self.fc(features)

        if apply_softmax:

            prediction_vector = F.softmax(prediction_vector, dim=1)

        return prediction_vector

这个CNN模型使用了一系列的1D卷积层,提取了字符级的特征,最后接一个全连接层得到分类结果。

**CNN的应用**:

* **图像识别**:识别图像中的物体、场景和人脸。

* **目标检测**:检测图像中的物体位置和类别。

* **语音识别**:识别语音中的单词和短语。

* **自然语言处理**:识别文本中的实体和关系。

MLP和CNN各有优缺点,以下是它们之间的主要区别:

MLP vs. CNN

| 特点                                | MLP |                                             |CNN |

| 结构 |                      多层感知器层                         卷积层、池化层、全连接层 |

| 特征提取 |                 非局部特征                                       局部特征 |

| 参数数量 |                      较多 |                                                 较少 |

| 训练复杂度 |                  较高 |                                                 较低 |

| 应用 |     通用分类和回归任务 | 图像识别、语音识别、自然语言处理 |

### 总结

MLP和CNN是神经网络中两种重要的前馈网络模型,它们在结构和应用上有着不同的侧重点。MLP适用于通用分类和回归任务,而CNN适用于图像识别、语音识别和自然语言处理等领域。选择合适的模型取决于具体的应用场景和任务需求。

理解每种神经网络层对输入数据张量的大小和形状的影响,对于深入理解模型的行为至关重要。让我们通过一些具体的例子来详细说明这一点:

  1. 全连接层(Linear Layer)
    如示例4-14所示,全连接层对输入张量的每一行进行线性变换,将其映射到新的向量空间。输入张量形状为(batch_size, input_dim),全连接层会将每个输入向量映射到一个新的 out_features 维向量。输出张量形状为(batch_size, out_features)。

这是示例代码

import torch

from torch.nn import Conv1d

batch_size = 2

one_hot_size = 10

sequence_width = 7

data = torch.randn(batch_size, one_hot_size, sequence_width)

conv1 = Conv1d(in_channels=one_hot_size, out_channels=16,

               kernel_size=3)

intermediate1 = conv1(data)

print(data.size())

print(intermediate1.size())

  1. 卷积层(Conv2d)
    2D卷积层在每个输入通道上应用一组可学习的滤波器,生成一组输出通道特征映射。输入张量形状为(batch_size, in_channels, H, W),输出张量形状为(batch_size, out_channels, H', W'),其中H'和W'取决于卷积核大小、步幅和填充方式。

conv2 = nn.Conv1d(in_channels=16, out_channels=32, kernel_size=3)

conv3 = nn.Conv1d(in_channels=32, out_channels=64, kernel_size=3)

intermediate2 = conv2(intermediate1)

intermediate3 = conv3(intermediate2)

print(intermediate2.size())

print(intermediate3.size())

  1. 池化层(MaxPool2d)
    2D最大池化层对输入张量的每个通道执行下采样操作,降低特征映射的分辨率。输入输出张量形状相同(batch_size, channels, H', W'),但H'和W'会根据池化窗口大小和步幅而减小。

理解这些形状变换非常重要,因为它们反映了神经网络在不同层次上对输入数据的编码方式。全连接层引入了全连接的变换,卷积层则捕获了局部空间模式,池化层降低了分辨率,使特征更加鲁棒。监控张量形状变化可以帮助调试模型,验证层操作是否符合预期。这种对底层计算的深入理解,是构建和理解复杂神经网络模型的关键。

在每次卷积中,通道维数的大小都会增加,因为通道维数是每个数据点的特征向量。张量实际上是一个特征向量的最后一步是去掉讨厌的尺寸=1维。

另外还有两种方法可以将张量简化为每个数据点的一个特征向量:将剩余的值压平为特征向量,并在额外维度上求平均值。这两种方法如示例4-16所示。使用第一种方法,只需使用PyTorch的view()方法将所有向量平展成单个向量。第二种方法使用一些数学运算来总结向量中的信息。最常见的操作是算术平均值,但沿feature map维数求和和使用最大值也是常见的。每种方法都有其优点和缺点。扁平化保留了所有的信息,但会导致比预期(或计算上可行)更大的特征向量。平均变得与额外维度的大小无关,但可能会丢失信息。

示例代码:

# Method 2 of reducing to feature vectors

print(intermediate1.view(batch_size, -1).size())

# Method 3 of reducing to feature vectors

print(torch.mean(intermediate1, dim=2).size())

# print(torch.max(intermediate1, dim=2).size())

# print(torch.sum(intermediate1, dim=2).size())

接下来,我们将探讨如何使用卷积神经网络(CNN)对姓氏数据集进行分类。CNN已成为处理序列数据(如文本)的有效方法之一。让我们从数据集开始。

为了使用CNN对姓氏进行分类,我们需要将每个姓氏表示为矩阵。每个字符将映射到一个one-hot向量,并将这些向量组合成一个矩阵。矩阵的每一列对应于姓氏中的一个字符位置。

class SurnameVectorizer(object):

    def vectorize(self, surname):

        """

        Args:

            surname (str): 姓氏

        Returns:

            one_hot_matrix (np.ndarray): 由one-hot向量组成的矩阵

        """

        one_hot_matrix_size = (len(self.character_vocab), self.max_surname_length)

        one_hot_matrix = np.zeros(one_hot_matrix_size, dtype=np.float32)

       

        for position_index, character in enumerate(surname):

            character_index = self.character_vocab.lookup_token(character)

            one_hot_matrix[character_index][position_index] = 1

           

        return one_hot_matrix

现在让我们看看用于分类的CNN模型。它由一系列Conv1D层和一个全连接层组成

class SurnameClassifier(nn.Module):

    def __init__(self, initial_num_channels, num_classes, num_channels):

        super().__init__()

        self.convnet = nn.Sequential(

            nn.Conv1d(in_channels=initial_num_channels, out_channels=num_channels, kernel_size=3),

            nn.ELU(),

            nn.Conv1d(in_channels=num_channels, out_channels=num_channels, kernel_size=3, stride=2),

            nn.ELU(),

            nn.Conv1d(in_channels=num_channels, out_channels=num_channels, kernel_size=3, stride=2),

            nn.ELU(),

            nn.Conv1d(in_channels=num_channels, out_channels=num_channels, kernel_size=3),

            nn.ELU()

        )

        self.fc = nn.Linear(num_channels, num_classes)

        

    def forward(self, x_surname, apply_softmax=False):

        features = self.convnet(x_surname).squeeze(dim=2)

        prediction_vector = self.fc(features)

        if apply_softmax:

            prediction_vector = F.softmax(prediction_vector, dim=1)

        return prediction_vector

forward函数中,Conv1D层被应用于输入矩阵。这些层能够学习输入序列中的局部模式。经过一系列卷积后,我们得到一个特征向量,该向量被输入到全连接层以生成预测向量。

为了训练模型,我们遵循标准的训练程序:

  1. 实例化数据集
  2. 实例化模型
  3. 实例化损失函数和优化器
  4. 遍历训练数据并更新模型参数
  5. 遍历验证数据并评估性能

# 初始化数据集

surname_dataset = SurnameDataset.load_dataset_and_make_vectorizer(args.surname_csv)

train_dataset = surname_dataset.train_data

vectorizer = surname_dataset.vectorizer

# 初始化模型

model=SurnameClassifier(initial_num_channels=vectorizer.alphabet_size, num_classes=vectorizer.num_nationalities, num_channels=args.num_channels)

model = model.to(device)

# 初始化损失函数和优化器 

loss_func = nn.CrossEntropyLoss()

optimizer = optim.Adam(model.parameters(), lr=args.learning_rate)

# 训练模型

for epoch in range(args.num_epochs):

    ...

    model.train()

    for batch in train_data:

        ...

        optimizer.zero_grad()

        y_pred = model(x_surname)

        loss = loss_func(y_pred, y_nationality)

        loss.backward()

        optimizer.step()

   

    # 在验证集上评估模型

    model.eval()

val_losses = []

最后,我们可以使用训练好的模型对新的姓氏进行预测。

    def predict_nationality(surname, classifier, vectorizer):

    vectorized_surname = vectorizer.vectorize(surname)

    vectorized_surname = torch.tensor(vectorized_surname).unsqueeze(0)

    result = classifier(vectorized_surname, apply_softmax=True)

   

    probability_values, indices = result.max(dim=1)

    index = indices.item()

   

    predicted_nationality = vectorizer.nationality_vocab.lookup_index(index)

    probability_value = probability_values.item()

   

    return {'nationality': predicted_nationality, 'probability': probability_value}

   

# 示例用法

surname = "Kernitzki"

prediction = predict_nationality(surname, model, vectorizer)

print(f"Surname: {surname}, Predicted Nationality: {prediction['nationality']}, Probability: {prediction['probability']:.3f}")

本篇博客讨论了卷积神经网络(CNN)在自然语言处理任务中的应用,以姓氏分类为例。主要内容包括:

  1. 介绍了如何使用PyTorch实现CNN模型进行姓氏分类。提供了模型定义、数据预处理、向量化器和训练程序的代码示例。
  2. 解释了如何将姓氏数据集预处理为one-hot矩阵形式,以满足CNN模型的输入要求。
  3. 详细说明了CNN模型的结构,包括嵌入层、一维卷积层、池化层和全连接层,并解释了每一层的作用。
  4. 介绍了在CNN中常用的技术,如池化操作、BatchNormNetwork-in-Network连接。
  5. 提供了使用训练好的模型对新姓氏进行分类和检索Top K预测的函数示例。
  6. 通过与多层感知器模型的性能对比,说明了CNN在这一任务上的优势。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值