赞
踩
本文旨在介绍多层感知机(MLP)以及卷积神经网络(CNN)在姓氏分类问题中的应用
目录
一、The Multilayer Perceptron(多层感知器)
本实验主要探讨神经网络模型,尤其是多层感知器(MLP)和卷积神经网络(CNN)的应用。实验通过分析简单感知器和MLP的性能差异,以及详细介绍卷积神经网络的机制,展示了这些模型在处理复杂模式识别任务中的能力(以姓氏分类为例)
多层感知机(MLP,Multi-Layer Perceptron)是一种前馈神经网络。前馈意味着数据从输入层流向输出层,中间没有反馈循环。MLP由多个层次组成,包括一个输入层、一个或多个隐藏层和一个输出层。每一层由若干个神经元(或称节点)组成。
1. 输入层:接收输入数据,每个神经元对应一个输入特征。例如,如果我们有一个包含4个特征的数据集,那么输入层会有4个神经元。
2. 隐藏层:位于输入层和输出层之间,可以有一个或多个隐藏层,每个隐藏层包含若干个神经元。隐藏层通过权重矩阵和激活函数对输入数据进行非线性变换。
3. 输出层:生成最终输出,例如分类结果。输出层的神经元数量通常取决于任务的需求。例如,二分类问题会有一个输出神经元,多分类问题会有多个输出神经元。
多层感知机的工作原理可以通过以下几个步骤理解:
1. 输入数据:数据从输入层进入,每个特征对应一个神经元。
2. 权重和偏置:每个连接(即每个神经元之间的连接)都有一个权重,初始时这些权重是随机分配的。每个神经元还有一个偏置值。
3. 激活函数:输入数据经过权重和偏置的线性变换后,经过激活函数处理,激活函数引入了非线性,使得神经网络能够学习复杂的模式。
4. 前向传播:数据从输入层经过隐藏层传递到输出层,层与层之间通过激活函数处理后的值传递。
5. 损失函数:输出层的输出与真实值进行比较,通过损失函数计算误差。
6. 反向传播:根据误差,通过反向传播算法调整权重和偏置,以减少误差。这个过程通过梯度下降等优化算法实现。
假设我们有一个简单的分类问题,我们想通过MLP来区分猫和狗。输入数据可能包括诸如耳朵长度、尾巴长度、体重和身高等特征。输入层将接收这些特征值,然后传递给隐藏层,隐藏层通过权重和激活函数处理这些特征,最终输出层给出分类结果:猫或狗。
1. 初期发展:最早的感知机模型由弗兰克·罗森布拉特(Frank Rosenblatt)在1957年提出。然而,简单的感知机只能处理线性可分的数据,这限制了其应用范围。
2. 多层感知机:在1986年,Hinton等人提出了反向传播算法,这一突破使得多层感知机(MLP)能够有效地训练,从而能够处理更复杂的模式识别任务(XOR问题)。
3. 深度学习时代:随着计算能力的提升和大规模数据集的出现,MLP发展成为深度神经网络(DNN),包含更多的隐藏层和神经元,能够处理更复杂的任务,如图像识别、语音识别和自然语言处理。
4. 现代应用:MLP和其变种(如卷积神经网络CNN、递归神经网络RNN等)在各个领域取得了广泛应用,包括但不限于自动驾驶、医疗诊断、金融预测和推荐系统。
以下是使用PyTorch实现多层感知机的一个简单示例(在实际应用中,定义完模型后,你还需要指定损失函数和优化器。):
- import torch
- import torch.nn as nn
- import torch.optim as optim
-
- # 定义多层感知机模型
- class MLP(nn.Module):
- def __init__(self, input_size, hidden_size, output_size):
- super(MLP, self).__init__()
- self.fc1 = nn.Linear(input_size, hidden_size) # 输入层到隐藏层的线性变换
- self.relu = nn.ReLU() # ReLU激活函数
- self.fc2 = nn.Linear(hidden_size, output_size) # 隐藏层到输出层的线性变换
-
- def forward(self, x):
- out = self.fc1(x) # 输入经过第一个全连接层
- out = self.relu(out) # 经过ReLU激活函数
- out = self.fc2(out) # 经过第二个全连接层
- return out
-
- # 参数定义
- input_size = 784 # 输入层的神经元数量(例如28x28的图像展开成一维向量)
- hidden_size = 500 # 隐藏层的神经元数量
- output_size = 10 # 输出层的神经元数量(例如10个类别)
-
- # 实例化模型
- model = MLP(input_size, hidden_size, output_size)
-
- # 打印模型结构
- print(model)
使用多层感知机(MLP)进行姓氏分类是一种典型的多类别分类问题。姓氏分类的任务是根据输入的姓氏字符串预测该姓氏所属的类别(如国家或地区)。
首先,我们将姓氏字符串转换为数字形式。这是因为计算机处理数字比处理字符串更方便。我们使用one-hot编码,将每个字符转换为一个向量。例如:
这个矩阵输入到MLP的输入层,作为模型的输入数据。
在MLP中,隐藏层通过一系列数学运算(如加权求和和激活函数)对输入数据进行处理。隐藏层的作用是自动学习和提取数据中的特征。
举个例子,假设我们在分类美国和中国的姓氏:
隐藏层中的神经元会通过学习这些模式,识别出哪些字符序列更有可能出现在某个类别的姓氏中。
隐藏层提取的特征被传递到输出层,输出层使用这些特征来进行分类决策。输出层的神经元会给出每个类别的分数,表示输入姓氏属于每个类别的概率。
举个例子:
MLP需要通过大量的训练数据来学习这些特征和模式。训练过程如下:
可以将MLP想象成一个聪明的学生,通过不断练习大量的例题(训练数据),逐渐掌握了如何区分不同类别的姓氏。隐藏层就像学生的思维过程,通过不断总结规律,提取出有用的信息(特征)。输出层则像学生的最终答案,通过对学到的知识进行综合判断,给出最有可能的结果(类别)。
总的来说,MLP通过将姓氏转换为数字特征,利用隐藏层自动提取特征,并在输出层进行分类决策,从而实现姓氏分类。经过大量数据的训练,MLP能够逐渐掌握区分不同类别姓氏的能力。
以下是一个使用PyTorch实现姓氏分类的简单示例
- import pandas as pd
- import torch
- from sklearn.model_selection import train_test_split
- from sklearn.preprocessing import LabelEncoder
-
- # 读取数据
- data = pd.read_csv('surnames.csv') # 假设文件名为surnames.csv
- surnames = data['surname'].values
- categories = data['category'].values
-
- # 编码类别标签
- label_encoder = LabelEncoder()
- categories = label_encoder.fit_transform(categories)
-
- # 分割数据集
- surnames_train, surnames_test, categories_train, categories_test = train_test_split(surnames, categories, test_size=0.2, random_state=42)
- import string
- from collections import Counter
-
- # 字符集(假设只包含小写字母)
- all_letters = string.ascii_lowercase
- n_letters = len(all_letters)
-
- # 字符转one-hot编码
- def letter_to_index(letter):
- return all_letters.find(letter)
-
- def surname_to_tensor(surname):
- tensor = torch.zeros(len(surname), 1, n_letters)
- for i, letter in enumerate(surname):
- tensor[i][0][letter_to_index(letter)] = 1
- return tensor
-
- # 示例:将一个姓氏转换为tensor
- print(surname_to_tensor('smith'))
SurnameClassifier
继承自 nn.Module
。
__init__
方法中,定义了两层全连接层 fc1
和 fc2
。
forward
方法定义了前向传播过程,包括ReLU激活函数。
input_size
是输入特征的数量,即 n_letters
。
hidden_size
是隐藏层神经元的数量。
output_size
是输出类别的数量。
- import torch.nn as nn
- import torch.nn.functional as F
-
- class SurnameClassifier(nn.Module):
- def __init__(self, input_size, hidden_size, output_size):
- super(SurnameClassifier, self).__init__()
- self.fc1 = nn.Linear(input_size, hidden_size)
- self.fc2 = nn.Linear(hidden_size, output_size)
-
- def forward(self, x):
- x = self.fc1(x)
- x = F.relu(x)
- x = self.fc2(x)
- return x
-
- # 参数
- input_size = n_letters
- hidden_size = 128
- output_size = len(label_encoder.classes_)
-
- # 实例化模型
- model = SurnameClassifier(input_size, hidden_size, output_size)
criterion
是交叉熵损失函数,用于计算预测类别和实际类别之间的差异。
optimizer
是Adam优化器,用于更新模型参数。
训练循环迭代 num_epochs
次,每次遍历训练集。
每个姓氏被转换为tensor格式,前向传播计算预测结果,计算损失,反向传播计算梯度,优化器更新模型参数。
每个epoch结束后打印损失值。
- import torch.optim as optim
-
- # 损失函数和优化器
- criterion = nn.CrossEntropyLoss()
- optimizer = optim.Adam(model.parameters(), lr=0.001)
-
- # 训练循环
- num_epochs = 10
-
- for epoch in range(num_epochs):
- for surname, category in zip(surnames_train, categories_train):
- # 准备输入数据
- surname_tensor = surname_to_tensor(surname).view(-1, n_letters)
- category_tensor = torch.tensor([category])
-
- # 前向传播
- output = model(surname_tensor)
- loss = criterion(output, category_tensor)
-
- # 反向传播和优化
- optimizer.zero_grad()
- loss.backward()
- optimizer.step()
-
- print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
torch.no_grad()
关闭梯度计算,减少内存使用,提高评估速度。
遍历测试集,每个姓氏转换为tensor格式,前向传播计算预测结果。
torch.max
返回预测概率最大的类别。
比较预测类别和实际类别,计算正确预测的数量。
计算并打印准确率。
- # 评估模型
- correct = 0
- total = len(surnames_test)
-
- with torch.no_grad():
- for surname, category in zip(surnames_test, categories_test):
- surname_tensor = surname_to_tensor(surname).view(-1, n_letters)
- category_tensor = torch.tensor([category])
-
- output = model(surname_tensor)
- _, predicted = torch.max(output, 1)
-
- if predicted.item() == category:
- correct += 1
-
- accuracy = correct / total
- print(f'Test Accuracy: {accuracy * 100:.2f}%')
在自然语言处理(NLP)和其他机器学习任务中,有时候不仅需要看模型的最优预测结果,还需要查看模型的前k个最佳预测结果。这有助于在实际应用中使用另一种模型或方法对这些候选结果进行进一步处理或重新排序。
在实际应用中,我们可以使用 torch.topk
获取前k个候选结果,然后使用其他方法(如语言模型或排序模型)对这些候选结果进行重新排序或进一步处理。例如,在姓氏分类任务中,我们可以获取前3个最可能的类别,然后根据其他信息(如上下文或特定规则)对这3个类别进行重新排序。
- import torch
-
- # 模拟模型的输出,假设有5个类别的概率分布
- output = torch.tensor([0.1, 0.3, 0.2, 0.25, 0.15])
-
- # 获取前3个最大的值及其索引
- topk_values, topk_indices = torch.topk(output, k=3)
-
- print("Top 3 values:", topk_values)
- print("Top 3 indices:", topk_indices)
在了解了MLP之后、我们知道MLP是由一系列线性层和非线性函数构建的神经网络。但MLP不是利用顺序模式的最佳工具。
例如,在姓氏数据集中,姓氏可以有(不同长度的)段,这些段可以显示出相当多关于其起源国家的信息(如“O’Neill”中的“O”、“Antonopoulos”中的“opoulos”、“Nagasawa”中的“sawa”或“Zhu”中的“Zh”)。这些段的长度可以是可变的,挑战是在不显式编码的情况下捕获它们。
在本节中,我们将介绍卷积神经网络(CNN),这是一种非常适合检测空间子结构(并因此创建有意义的空间子结构)的神经网络。CNNs通过使用少量的权重来扫描输入数据张量来实现这一点。通过这种扫描,它们产生表示子结构检测(或不检测)的输出张量。
在本节的其余部分中,我们首先描述CNN的工作方式,以及在设计CNN时应该考虑的问题。我们深入研究CNN超参数,目的是提供直观的行为和这些超参数对输出的影响。最后,我们通过几个简单的例子逐步说明CNNs的机制。
图像是一种特殊的数据,它具有二维的结构,像素之间有空间上的关系。传统的全连接神经网络(MLP)难以有效处理这种结构化数据,因为它们无法利用图像中像素的局部关系。
卷积层是CNN的核心组件。卷积层通过卷积核(或称滤波器)在输入图像上滑动,计算局部区域的加权和,提取图像的局部特征。每个卷积核可以看作是一个小的“窗口”,它在图像上移动,捕捉局部的模式(例如边缘、角落)。
通过多个卷积核,卷积层可以提取不同的特征图,每个特征图反映图像的不同特征。
池化层用于降低特征图的维度,减少计算量,同时保留重要的特征。最常用的是最大池化(max pooling),它取局部区域中的最大值,代表该区域的特征。
CNN中常用的激活函数是ReLU(Rectified Linear Unit),它将负值设为0,保留正值。这增加了模型的非线性能力,使其能够学习更复杂的模式。
在卷积层和池化层提取特征后,CNN通常通过一到多层全连接层(fully connected layer)来进行最终的分类。这类似于传统的神经网络,将提取到的特征映射到类别空间。
构建一个简单的CNN模型,包括卷积层、池化层和全连接层
- class SimpleCNN(nn.Module):
- def __init__(self):
- super(SimpleCNN, self).__init__()
- self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
- self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
- self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
- self.fc1 = nn.Linear(64 * 7 * 7, 128)
- self.fc2 = nn.Linear(128, 10)
-
- def forward(self, x):
- x = self.pool(F.relu(self.conv1(x)))
- x = self.pool(F.relu(self.conv2(x)))
- x = x.view(-1, 64 * 7 * 7)
- x = F.relu(self.fc1(x))
- x = self.fc2(x)
- return x
-
- # 实例化模型
- model = SimpleCNN()
姓氏分类任务的目标是根据给定的姓氏(字符串),预测它属于哪个类别(例如,哪个国家或哪个语言群体)。虽然姓氏是文本数据,但我们可以通过一些方法将其转换为适合CNN处理的形式。
首先,我们需要将姓氏转换为数值表示。常用的方法是将姓氏中的每个字符转换为一个one-hot向量。例如,假设我们只考虑小写字母'a'到'z':
例如:
- s -> [0, 0, 0, ..., 1, 0, 0]
- m -> [0, 0, 1, ..., 0, 0, 0]
- i -> [0, 0, 0, ..., 0, 1, 0]
- t -> [0, 0, 0, ..., 0, 0, 1]
- h -> [0, 1, 0, ..., 0, 0, 0]
这样,"smith" 就被转换为一个5x26的矩阵。
卷积层使用多个卷积核(小矩阵)在输入数据上滑动,提取局部特征。例如,一个3x3的卷积核在姓氏的one-hot编码矩阵上滑动时,会关注到局部的字符组合模式:
这些卷积核通过不断滑动和计算,生成多个特征图,这些特征图可以捕捉到姓氏中的局部模式。
每个卷积层之后通常会使用激活函数(如ReLU),它将负值设为0,保留正值。这样做可以增加模型的非线性能力,使其能够学习更复杂的特征。
池化层(如最大池化)会对特征图进行降维处理。它通常取局部区域的最大值,减少特征图的尺寸,同时保留重要的特征:
在经过多层卷积和池化层后,我们得到的是提取到的高层次特征。这些特征输入到全连接层,全连接层将这些特征映射到输出类别(例如,国家或语言群体):
假设我们有一个姓氏 "li",它被转换为一个矩阵后:
可以将CNN想象成一个“特征探测器”和“分类器”的组合:
通过这种方式,CNN能够有效地处理姓氏分类任务,即使面对大量复杂的姓氏,也能准确地进行分类。
首先,需要对姓氏数据进行预处理。这通常包括将姓氏文本转换为适合输入神经网络的格式。例如,可以将每个字符转换为对应的数字编码,或者使用one-hot编码表示字符。
- import pandas as pd
-
- # 加载数据
- data = pd.read_csv('/data/surnames/surnames.csv')
-
- # 数据清洗
- data.dropna(inplace=True)
-
- # 数据转换:例如将每个字符转换为数字编码
- data['label'] = data['label'].astype('category').cat.codes
-
- print(data.head())
卷积神经网络通过卷积层(Convolutional Layer)和池化层(Pooling Layer)提取数据特征,最后通过全连接层(Fully Connected Layer)进行分类。以下是一个用于姓氏分类的CNN模型定义:
- import torch
- import torch.nn as nn
- import torch.optim as optim
- import torch.nn.functional as F
-
- # 定义CNN模型
- class CNN(nn.Module):
- def __init__(self, num_classes):
- super(CNN, self).__init__()
- self.conv1 = nn.Conv1d(in_channels=1, out_channels=64, kernel_size=3, stride=1, padding=1)
- self.pool = nn.MaxPool1d(kernel_size=2, stride=2, padding=0)
- self.conv2 = nn.Conv1d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1)
- self.fc1 = nn.Linear(128 * 50, 256) # 假设输入长度为100,经过两次池化后长度为100/4=25
- self.fc2 = nn.Linear(256, num_classes)
-
- def forward(self, x):
- x = self.pool(F.relu(self.conv1(x)))
- x = self.pool(F.relu(self.conv2(x)))
- x = x.view(-1, 128 * 25)
- x = F.relu(self.fc1(x))
- x = self.fc2(x)
- return x
-
- # 假设有100个类别
- num_classes = 100
- model = CNN(num_classes=num_classes)
-
- criterion = nn.CrossEntropyLoss()
- optimizer = optim.Adam(model.parameters(), lr=0.001)
在模型定义完成后,进行模型训练。训练过程包括前向传播(Forward Propagation)、计算损失(Loss Calculation)、反向传播(Backward Propagation)和参数更新(Parameter Update)。
- # 训练模型
- for epoch in range(10):
- optimizer.zero_grad()
-
- # 假设数据已经预处理为适合输入CNN的格式
- inputs = torch.FloatTensor(preprocessed_data['input']) # 输入数据,形状为 (batch_size, 1, input_length)
- labels = torch.LongTensor(preprocessed_data['label']) # 标签数据,形状为 (batch_size)
-
- outputs = model(inputs)
- loss = criterion(outputs, labels)
- loss.backward()
- optimizer.step()
-
- if (epoch+1) % 1 == 0:
- print(f'Epoch [{epoch+1}/10], Loss: {loss.item():.4f}')
多层感知机(MLP)在姓氏分类任务中的应用主要通过全连接层将输入数据转换为特征向量,然后通过一系列的隐藏层进行特征提取和分类决策。每个隐藏层的神经元与上一层的所有神经元相连接,通过非线性激活函数处理后传递给下一层。
特点
优势
劣势
应用情况
卷积神经网络(CNN)在姓氏分类任务中的应用主要通过卷积层和池化层进行特征提取,然后通过全连接层进行分类决策。卷积层通过局部连接和共享权重的方式提取局部特征,池化层用于下采样和减少数据维度。
特点
优势
劣势
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。