深度学习(7):RNN实战之人名的国籍预测

server/2024/10/9 10:00:17/

文章目录

  • 数据集
  • NameDataSet
  • One-Hot 编码
    • 是什么
    • 思路
    • 实现代码
    • 带来的问题
  • Embedding
  • 模型过程分析
    • 1.超参数设置
    • 2.数据集处理
    • 3. 数据 to Tensor
    • 4.训练
  • 完整代码
    • NameDateSet
    • simpleRNN

数据集

格式为(country_name).txt的文本文件包含该国家的常见姓名,
所以数据x是文本里面的人名,标签y是文本文件的文件名
在这里插入图片描述

NameDataSet

继承DataSet的读取数据集工具类,本文数据集格式为文本文件的文件名为标签,文件的内容content为输入,所有采用os.listdir的方式读入全部数据集

import osfrom sklearn.model_selection import train_test_split
from torch.utils.data import Datasetclass NameDataSet(Dataset):def __init__(self, train=True, test_size=0.2, random_state=42):self.data = []self.labels = []self.inputs = []folder_path = "data/names/"for filename in os.listdir(folder_path):label = filename.replace(".txt", "")self.labels.append(label)file_path = os.path.join(folder_path, filename)with open(file_path, 'r', encoding='utf-8') as file:content = []for line in file:line = line.strip()content.append(line)self.data.append((line, label))self.inputs.append(content)# 使用train_test_split来划分数据集train_data, test_data = train_test_split(self.data, test_size=test_size, random_state=random_state)# 根据train参数选择使用训练集或测试集self.data = train_data if train else test_dataself.country = self.get_countries_dict()def __getitem__(self, item):x, y = self.data[item]return x, self.country[y]def __len__(self):return len(self.data)def getAll(self):for t in self.data:print(t)def get_countries_dict(self):# 根据国家名对应序号country_dict = dict()for idx, country_name in enumerate(self.labels):country_dict[country_name] = idxreturn country_dictdef idx2country(self, index):# 根据索引返回国家名字return self.labels[index]def get_countries_num(self):# 返回国家名个数(分类的总个数)return len(self.labels)

One-Hot 编码

是什么

大多机器学习能够处理的都是数值类型的数据,所以对非数值类型的数据(如单词,类别标签),我们需要将其转换成数值类型的表示

思路

以单词的One-Hot编码为例

假设当前输入数据中单词的总种类数为n,那么每个单词都会被转变成为长度为n的二进制向量,在这个向量里面只有1个二进制1,其它都为0。

原理:每个类别值都被转换为一个只有一个元素是1,其余元素都是0的二进制向量。这个向量的长度等于类别的数量。

实现代码

因为我们需要获取单词的种类数,所以需要进行统计去重

带来的问题

很显然对于每个编码数据的信息量都特别稀疏,所以表示效率过低,数据量大的时候会产生数据集加载慢,需要通过Embedding(嵌入)提高信息密度,将One-Hot编码的高维稀疏向量映射到一个更低维的密集向量中。

Embedding

Embedding(嵌入) 是一种用于将离散的、稀疏的数据(如单词或字符的索引)映射到连续的、稠密的低维向量空间中的技术。

  • 目的:通过将高维的稀疏向量(例如词汇表中的单词表示为 one-hot 编码)转换为低维的稠密向量,可以更好地捕获数据中的语义信息或特征关系。
  • 优势:Embedding 能够将相似的输入(如具有相似含义的单词)映射到相似的向量空间中,这有助于模型在理解数据之间的关系时表现得更好。

模型过程分析

1.超参数设置

HIDDEN_SIZE = 100
BATCH_SIZE = 256
N_LAYER = 2  # RNN的层数
N_EPOCHS = 100
N_CHARS = 400  # ASCII码的个数
USE_GPU = False

2.数据集处理

train_set = NameDataSet(True)
train_loader = DataLoader(train_set, shuffle=True, batch_size=BATCH_SIZE, num_workers=2)
test_set = NameDataSet(False)
test_loder = DataLoader(test_set, shuffle=False, batch_size=BATCH_SIZE, num_workers=2)
N_COUNTRY = train_set.get_countries_num()

3. 数据 to Tensor

def make_tensors(names, countries):# eg. name2list(name):chad -> ([ascall], 4)name_len_list = [name2list(name) for name in names]# 拿到每个名字拆分的ascall列表,即[ascall]name_seq = [sl[0] for sl in name_len_list]# 拿到每个名字的长度,即时间步seq_lengths = torch.LongTensor([sl[1] for sl in name_len_list])# 消除floatTensor带来的精度影响countries = countries.long()# 创建全0的tensor,用于待会将names得到的ascall列表填充进去# 维度是len(name_seq) * seq_lengths.max(),# 已经进行了padding,将所有的name都对齐了seq_tensor = torch.zeros(len(name_seq), seq_lengths.max().item()).long()# 进行填充for idx, (seq, seq_len) in enumerate(zip(name_seq, seq_lengths)):seq_tensor[idx, :seq_len] = torch.LongTensor(seq)'''[[0, 0, 0, 0, 0],   # 第1个名字,"John"[0, 0, 0, 0, 0],   # 第2个名字,"Alice"[0, 0, 0, 0, 0]]   # 第3个名字,"Bob"[[74, 111, 104, 110, 0],   # John -> [74, 111, 104, 110],长度 4,剩余部分补零[65, 108, 105, 99, 101],  # Alice -> [65, 108, 105, 99, 101],长度 5[66, 111, 98, 0, 0]]      # Bob -> [66, 111, 98],长度 3,剩余部分补零'''# 在行的方向进行降序,即对每行进行降序# perm_idx:排序后的索引,用来记录原始数据的顺序变化。seq_lengths, perm_idx = seq_lengths.sort(dim=0, descending=True)seq_tensor = seq_tensor[perm_idx]countries = countries[perm_idx]return create_tensor(seq_tensor), create_tensor(seq_lengths), create_tensor(countries)

4.训练

def train(epoch, start):total_loss = 0for i, (names, countries) in enumerate(train_loader, 1):# 将数据进行处理inputs, seq_lengths, target = make_tensors(names, countries)# 前向传播,获取输出output = model(inputs, seq_lengths)# 计算损失loss = criterion(output, target)# 后向传播optimizer.zero_grad()loss.backward()optimizer.step()# 统计损失total_loss += loss.item()if i % 10 == 0:print(f'[{timesince(start)}] Epoch {epoch} ', end='')print(f'[{i * len(inputs)}/{len(train_set)}] ', end='')print(f'loss={total_loss / (i * len(inputs))}')  # 打印每个样本的平均损失return total_loss

完整代码

NameDateSet

import osfrom sklearn.model_selection import train_test_split
from torch.utils.data import Datasetclass NameDataSet(Dataset):def __init__(self, train=True, test_size=0.2, random_state=42):self.data = []self.labels = []self.inputs = []folder_path = "data/names/"for filename in os.listdir(folder_path):label = filename.replace(".txt", "")self.labels.append(label)file_path = os.path.join(folder_path, filename)with open(file_path, 'r', encoding='utf-8') as file:content = []for line in file:line = line.strip()content.append(line)self.data.append((line, label))self.inputs.append(content)# 使用train_test_split来划分数据集train_data, test_data = train_test_split(self.data, test_size=test_size, random_state=random_state)# 根据train参数选择使用训练集或测试集self.data = train_data if train else test_dataself.country = self.get_countries_dict()def __getitem__(self, item):x, y = self.data[item]return x, self.country[y]def __len__(self):return len(self.data)def getAll(self):for t in self.data:print(t)def get_countries_dict(self):# 根据国家名对应序号country_dict = dict()for idx, country_name in enumerate(self.labels):country_dict[country_name] = idxreturn country_dictdef idx2country(self, index):# 根据索引返回国家名字return self.labels[index]def get_countries_num(self):# 返回国家名个数(分类的总个数)return len(self.labels)

simpleRNN

import math
import timeimport numpy as np
from matplotlib import pyplot as plt
from torch import optimfrom NameDataset import NameDataSet
from torch.utils.data import DataLoader
import torch
from torch.nn.utils.rnn import pack_padded_sequence# 1.设置超参数
HIDDEN_SIZE = 100
BATCH_SIZE = 256
N_LAYER = 2  # RNN的层数
N_EPOCHS = 100
N_CHARS = 400  # ASCII码的个数
USE_GPU = False# 2.数据集相关
train_set = NameDataSet(True)
train_loader = DataLoader(train_set, shuffle=True, batch_size=BATCH_SIZE, num_workers=2)
test_set = NameDataSet(False)
test_loder = DataLoader(test_set, shuffle=False, batch_size=BATCH_SIZE, num_workers=2)
N_COUNTRY = train_set.get_countries_num()# 工具类函数
# 把名字转换成ASCII码
# 返回ASCII码值列表和名字的长度
def name2list(name):arr = [ord(c) for c in name]return arr, len(arr)# 是否把数据放到GPU上
def create_tensor(tensor):if USE_GPU:device = torch.device('cuda:0')tensor = tensor.to(device)return tensordef timesince(since):now = time.time()s = now - sincem = math.floor(s / 60)  # math.floor()向下取整s -= m * 60return '%dmin %ds' % (m, s)  # 多少分钟多少秒class GRUClassifier(torch.nn.Module):def __init__(self, input_size, hidden_size, output_size, n_layers=1, bidirectional=True):super(GRUClassifier, self).__init__()self.hidden_size = hidden_sizeself.n_layers = n_layersself.n_directions = 2 if bidirectional else 1# 词嵌入层,将词语映射到hidden维度# 特征处理,将稀疏高维向量变成连续的稠密表示作为隐藏层的输入self.embedding = torch.nn.Embedding(input_size, hidden_size)# GRU层(输入为特征数,这里是embedding_size,其大小等于hidden_size))self.gru = torch.nn.GRU(hidden_size, hidden_size, num_layers=n_layers,bidirectional=bidirectional)# 线性层,输出结果self.fc = torch.nn.Linear(hidden_size * self.n_directions, output_size)def _init_hidden(self, bath_size):# 初始化权重,(n_layers * num_directions 双向, batch_size, hidden_size)hidden = torch.zeros(self.n_layers * self.n_directions, bath_size, self.hidden_size)return create_tensor(hidden)def forward(self, input, seq_lengths):# 转置 B X S -> S X Binput = input.t()  # 此时的维度为seq_len, batch_sizebatch_size = input.size(1)hidden = self._init_hidden(batch_size)# 嵌入层处理 input:(seq_len,batch_size) -> embedding:(seq_len,batch_size,embedding_size)embedding = self.embedding(input)# 进行打包(不考虑0元素,提高运行速度)需要将嵌入数据按长度排好gru_input = pack_padded_sequence(embedding, seq_lengths)# output:(*, hidden_size * num_directions),*表示输入的形状(seq_len,batch_size)# hidden:(num_layers * num_directions, batch, hidden_size)output, hidden = self.gru(gru_input, hidden)if self.n_directions == 2:hidden_cat = torch.cat([hidden[-1], hidden[-2]],dim=1)  # hidden[-1]的形状是(1,256,100),hidden[-2]的形状是(1,256,100),拼接后的形状是(1,256,200)else:hidden_cat = hidden[-1]  # (1,256,100)fc_output = self.fc(hidden_cat)return fc_outputdef make_tensors(names, countries):# eg. name2list(name):chad -> ([ascall], 4)name_len_list = [name2list(name) for name in names]# 拿到每个名字拆分的ascall列表,即[ascall]name_seq = [sl[0] for sl in name_len_list]# 拿到每个名字的长度,即时间步seq_lengths = torch.LongTensor([sl[1] for sl in name_len_list])# 消除floatTensor带来的精度影响countries = countries.long()# 创建全0的tensor,用于待会将names得到的ascall列表填充进去# 维度是len(name_seq) * seq_lengths.max(),# 已经进行了padding,将所有的name都对齐了seq_tensor = torch.zeros(len(name_seq), seq_lengths.max().item()).long()# 进行填充for idx, (seq, seq_len) in enumerate(zip(name_seq, seq_lengths)):seq_tensor[idx, :seq_len] = torch.LongTensor(seq)'''[[0, 0, 0, 0, 0],   # 第1个名字,"John"[0, 0, 0, 0, 0],   # 第2个名字,"Alice"[0, 0, 0, 0, 0]]   # 第3个名字,"Bob"[[74, 111, 104, 110, 0],   # John -> [74, 111, 104, 110],长度 4,剩余部分补零[65, 108, 105, 99, 101],  # Alice -> [65, 108, 105, 99, 101],长度 5[66, 111, 98, 0, 0]]      # Bob -> [66, 111, 98],长度 3,剩余部分补零'''# 在行的方向进行降序,即对每行进行降序# perm_idx:排序后的索引,用来记录原始数据的顺序变化。seq_lengths, perm_idx = seq_lengths.sort(dim=0, descending=True)seq_tensor = seq_tensor[perm_idx]countries = countries[perm_idx]return create_tensor(seq_tensor), create_tensor(seq_lengths), create_tensor(countries)# 训练循环
def train(epoch, start):total_loss = 0for i, (names, countries) in enumerate(train_loader, 1):# 将数据进行处理inputs, seq_lengths, target = make_tensors(names, countries)# 前向传播,获取输出output = model(inputs, seq_lengths)# 计算损失loss = criterion(output, target)# 后向传播optimizer.zero_grad()loss.backward()optimizer.step()# 统计损失total_loss += loss.item()if i % 10 == 0:print(f'[{timesince(start)}] Epoch {epoch} ', end='')print(f'[{i * len(inputs)}/{len(train_set)}] ', end='')print(f'loss={total_loss / (i * len(inputs))}')  # 打印每个样本的平均损失return total_loss# 测试循环
def t():correct = 0total = len(test_set)print('evaluating trained model ...')# print("len = " + total.__str__())with torch.no_grad():for i, (names, countries) in enumerate(test_loder, 1):inputs, seq_lengths, target = make_tensors(names, countries)output = model(inputs, seq_lengths)pred = output.max(dim=1, keepdim=True)[1]# 返回每一行中最大值的那个元素的索引,且keepdim=True,表示保持输出的二维特性correct += pred.eq(target.view_as(pred)).sum().item()  # 计算正确的个数percent = '%.2f' % (100 * correct / total)print(f'Test set: Accuracy {correct}/{total} {percent}%')return correct / total  # 返回的是准确率,0.几几的格式,用来画图if __name__ == '__main__':model = GRUClassifier(N_CHARS, HIDDEN_SIZE, N_COUNTRY, N_LAYER)criterion = torch.nn.CrossEntropyLoss()optimizer = optim.Adam(model.parameters(), lr=0.001)device = 'cuda:0' if USE_GPU else 'cpu'model.to(device)start = time.time()print('Training for %d epochs...' % N_EPOCHS)acc_list = []# 在每个epoch中,训练完一次就测试一次for epoch in range(1, N_EPOCHS + 1):# Train cycletrain(epoch, start)acc = t()acc_list.append(acc)# 绘制在测试集上的准确率epoch = np.arange(1, len(acc_list) + 1)acc_list = np.array(acc_list)plt.plot(epoch, acc_list)plt.xlabel('Epoch')plt.ylabel('Accuracy')plt.grid()plt.show()

http://www.ppmy.cn/server/129212.html

相关文章

Leetcode—152. 乘积最大子数组【中等】

2024每日刷题&#xff08;174&#xff09; Leetcode—152. 乘积最大子数组 C实现代码 class Solution { public:int maxProduct(vector<int>& nums) {int n nums.size();int mx nums[0];int mn nums[0];int ans mx;for(int i 1; i < n; i) {const int prem…

powerbi之常用DAX函数使用介绍——提供数据源练习

前述&#xff1a; 本次使用数据是包含产品表、客户表、区域表、销售订单表的一份销售订单数据&#xff0c;数据源链接如下&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1micl_09hFrgz2aUBERkeZg 提取码&#xff1a;y17e 一、CALCULATE 1.语法结构 语法结构CALCUL…

Python爬虫之正则表达式于xpath的使用教学及案例

正则表达式 常用的匹配模式 \d # 匹配任意一个数字 \D # 匹配任意一个非数字 \w # 匹配任意一个单词字符&#xff08;数字、字母、下划线&#xff09; \W # 匹配任意一个非单词字符 . # 匹配任意一个字符&#xff08;除了换行符&#xff09; [a-z] # 匹配任意一个小写字母 […

HarmonyOS应用六之应用程序进阶二

目录&#xff1a; 一、进度条通知二、闹钟提醒2.1、在module.json5配置文件中开启权限2.2、导入后台代理提醒reminderAgentManager模块&#xff0c;将此模块命名为reminderAgentManager2.3、如果是新增提醒&#xff0c;实现步骤如下&#xff1a; 3、Native C交互4、第三方库的基…

低代码平台的缩写和使用方法是什么?蓝燕云低代码平台开发经验!

随着企业数字化转型的加速&#xff0c;低代码平台越来越受到欢迎。低代码平台可以极大地简化开发过程&#xff0c;使得非专业开发者也能够快速创建应用程序。然而&#xff0c;对于低代码平台&#xff0c;很多人还存在一些疑惑&#xff0c;尤其是在其缩写和具体使用方法方面。本…

单片机(学习)2024.10.8

ok家人们&#xff0c;国庆回来继续开始新的单片机的学习&#xff0c;这次学习的单片机芯片为STM32U575RIT6 计算机基础 IO逻辑 计算机系统中的高低电平逻辑1和0&#xff0c;数据在计算机中的存储、传输、运算都是以二进制形式进行的。数据的传输通过总线真正传递的是电信号&a…

力扣10.8

174. 地下城游戏 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里&#xff0c;他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健康点数为一个正整数。如果他的健康点数…

在ES6中,数组新增扩展及其用法汇总

在ES6中&#xff0c;数组新增了多项扩展&#xff0c;极大提高了操作数组的便捷性。以下是一些常用的扩展及其用法&#xff1a; 1. Array.from() 用于从类数组对象或迭代器创建一个新的数组实例。这个方法可以接受两个参数&#xff1a; source (来源)&#xff1a;这是必须的参…