PyTorch 深度学习实践-循环神经网络(高级篇)
视频指路
参考博客笔记
参考笔记二
文章目录
- 上课笔记
- 总代码
- 练习
上课笔记
个人能力有限,重看几遍吧,第一遍基本看不懂
名字的每个字母都是一个特征x1,x2,x3…,一个名字是一个序列
rnn用GRU
用ASCII表作为词典,长度为128,每一个值对应一个独热向量,比如77对应128维向量中第77个位置为1其他位置为0,但是对于embed层只要告诉它哪个是1就行,这些序列长短不一,需要padding到统一长度
把国家的名字变成索引标签,做成下图所示的词典
数据集构建
class NameDataset(Dataset):def __init__(self, is_train=True):# 文件读取filename = './dataset/names_train.csv.gz' if is_train else './dataset/names_test.csv.gz'with gzip.open(filename, 'rt') as f: # rt表示以只读模式打开文件,并将文件内容解析为文本形式reader = csv.reader(f)rows =list(reader) # 每个元素由一个名字和国家组成# 提取属性self.names = [row[0] for row in rows]self.len = len(self.names)self.countries = [row[1] for row in rows]# 编码处理self.country_list = list(sorted(set(self.countries))) # 列表,按字母表顺序排序,去重后有18个国家名self.country_dict = self.get_countries_dict() # 字典,国家名对应序号标签self.country_num = len(self.country_list)def __getitem__(self, item):# 索引获取return self.names[item], self.country_dict[self.countries[item]] # 根据国家去字典查找索引def __len__(self):# 获取个数return self.lendef get_countries_dict(self):# 根据国家名对应序号country_dict = dict()for idx, country_name in enumerate(self.country_list):country_dict[country_name] = idxreturn country_dictdef idx2country(self, index):# 根据索引返回国家名字return self.country_list[index]def get_countries_num(self):# 返回国家名个数(分类的总个数)return self.country_num
双向RNN,从左往右走一遍,把得到的值和逆向计算得到的hN拼到一起,比如最后一个 [ h 0 b , h n f ] [h^b_0, h^f_n] [h0b,hnf]
self.n_directions = 2 if bidirectional else 1self.gru = torch.nn.GRU(hidden_size, hidden_size, num_layers=n_layers, bidirectional=bidirectional)#bidirectional双向神经网络
h i d d e n = [ h N f , h N b ] hidden = [h{^f_N},h{^b_N}] hidden=[hNf,hNb]
# 进行打包(不考虑0元素,提高运行速度)首先需要将嵌入数据按长度排好
gru_input = pack_padded_sequence(embedding, seq_lengths)
pack_padded_sequence
:这是 PyTorch 提供的一个函数,用于将填充后的序列打包。其主要目的是跳过填充值,并且在 RNN 中只处理实际的序列数据。它会将填充后的嵌入和实际序列长度作为输入,并返回一个打包后的序列,便于 RNN 处理。可以只把非零序列提取出来放到一块,也就是把为0的填充量都丢掉,这样将来fru就可以处理长短不一的输入序列
首先要根据序列长度进行排序,然后再经过嵌入层
如下图所示:这样用gru的时候效率就会更高,因为可以方便去掉好多padding的数据
双向RNN要拼接起来
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)
总代码
import csv
import time
import matplotlib.pyplot as plt
import numpy as np
import math
import gzip # 用于读取压缩文件
import torch
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
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 = 128 # ASCII码的个数
USE_GPU = False# 工具类函数
# 把名字转换成ASCII码, b 返回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) # 多少分钟多少秒# 2.构建数据集
class NameDataset(Dataset):def __init__(self, is_train=True):# 文件读取filename = './dataset/names_train.csv.gz' if is_train else './dataset/names_test.csv.gz'with gzip.open(filename, 'rt') as f: # rt表示以只读模式打开文件,并将文件内容解析为文本形式reader = csv.reader(f)rows =list(reader) # 每个元素由一个名字和国家组成# 提取属性self.names = [row[0] for row in rows]self.len = len(self.names)self.countries = [row[1] for row in rows]# 编码处理self.country_list = list(sorted(set(self.countries))) # 列表,按字母表顺序排序,去重后有18个国家名self.country_dict = self.get_countries_dict() # 字典,国家名对应序号标签self.country_num = len(self.country_list)def __getitem__(self, item):# 索引获取return self.names[item], self.country_dict[self.countries[item]] # 根据国家去字典查找索引def __len__(self):# 获取个数return self.lendef get_countries_dict(self):# 根据国家名对应序号country_dict = dict()for idx, country_name in enumerate(self.country_list):country_dict[country_name] = idxreturn country_dictdef idx2country(self, index):# 根据索引返回国家名字return self.country_list[index]def get_countries_num(self):# 返回国家名个数(分类的总个数)return self.country_num# 3.实例化数据集
train_set = NameDataset(is_train=True)
train_loader = DataLoader(train_set, shuffle=True, batch_size=BATCH_SIZE, num_workers=2)
test_set = NameDataset(is_train=False)
test_loder = DataLoader(test_set, shuffle=False, batch_size=BATCH_SIZE, num_workers=2)
N_COUNTRY = train_set.get_countries_num() # 18个国家名,即18个类别# 4.模型构建
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)#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_output# 3.数据处理(姓名->数字)
def make_tensors(names, countries):# 获取嵌入长度从大到小排序的seq_tensor(嵌入向量)、seq_lengths(对应长度)、countries(对应顺序的国家序号)-> 便于pack_padded_sequence处理name_len_list = [name2list(name) for name in names] # 每个名字对应的1列表name_seq = [sl[0] for sl in name_len_list] # 姓名列表seq_lengths = torch.LongTensor([sl[1] for sl in name_len_list]) # 名字对应的字符个数countries = countries.long() # PyTorch 中,张量的默认数据类型是浮点型 (float),这里转换成整型,可以避免浮点数比较时的精度误差,从而提高模型的训练效果# 创建全零张量,再依次进行填充# 创建了一个 len(name_seq) * seq_length.max()维的张量seq_tensor = torch.zeros(len(name_seq), seq_lengths.max()).long()for idx, (seq, seq_len) in enumerate(zip(name_seq, seq_lengths)):seq_tensor[idx, :seq_len] = torch.LongTensor(seq)# 为了使用pack_padded_sequence,需要按照长度排序# perm_idx是排序后的数据在原数据中的索引,seq_tensor是排序后的数据,seq_lengths是排序后的数据的长度,countries是排序后的国家seq_lengths, perm_idx = seq_lengths.sort(dim=0, descending=True) # 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 test():correct = 0total = len(test_set)print('evaluating trained model ...')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 = test()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()
在准确率最高点save模型
保存整个模型:
torch.save(model,'save.pt')
只保存训练好的权重:
torch.save(model.state_dict(), 'save.pt')
练习
数据集地址,判断句子是哪类(0-negative,1-somewhat negative,2-neutral,3-somewhat positive,4-positive)情感分析
训练集(train.tsv)
-
文件名:
train.tsv
-
包含字段:
-
PhraseId: 每条评论的唯一标识符。
-
SentenceId: 句子的唯一标识符。一个句子可以包含多个短语(Phrase)。
-
Phrase: 具体的短语文本,表示电影评论的一部分。
-
Sentiment:
情感标签,表示这条短语的情感分类。情感分类共有五个等级:
- 0:消极情感(negative)
- 1:有点消极情感(somewhat negative)
- 2:中性情感(neutral)
- 3:有点积极情感(somewhat positive)
- 4:积极情感(positive)
-
测试集(test.tsv)
- 文件名:
test.tsv
- 包含字段:
- PhraseId: 每条评论的唯一标识符。
- SentenceId: 句子的唯一标识符。
- Phrase: 具体的短语文本,表示电影评论的一部分。
数据解释
- PhraseId: 是每个评论的唯一标识符。它用于在数据集中唯一标识每条评论短语。
- SentenceId: 表示一个完整句子的唯一标识符。一个句子可能会被分成多个短语进行情感分析。
- Phrase: 是具体的评论内容,包含了实际的文本数据。
- Sentiment: 是情感标签,用于训练集,表示每条短语推理后的分类
import pandas as pd
import torch
from torch.utils.data import Dataset, DataLoader
import torch.nn as nn
import torch.optim as optim
from sklearn.preprocessing import LabelEncoder
from torch.nn.utils.rnn import pad_sequence
from torch.nn.utils.rnn import pack_padded_sequence
import zipfile# 超参数设置
BATCH_SIZE = 64
HIDDEN_SIZE = 100
N_LAYERS = 2
N_EPOCHS = 10
LEARNING_RATE = 0.001# 数据集路径
TRAIN_ZIP_PATH = './dataset/train.tsv.zip'
TEST_ZIP_PATH = './dataset/test.tsv.zip'# 解压缩文件
def unzip_file(zip_path, extract_to='.'):with zipfile.ZipFile(zip_path, 'r') as zip_ref:zip_ref.extractall(extract_to)unzip_file(TRAIN_ZIP_PATH)
unzip_file(TEST_ZIP_PATH)# 数据集路径
TRAIN_PATH = './train.tsv'
TEST_PATH = './test.tsv'# 自定义数据集类
class SentimentDataset(Dataset):def __init__(self, phrases, sentiments=None):self.phrases = phrasesself.sentiments = sentimentsdef __len__(self):return len(self.phrases)def __getitem__(self, idx):phrase = self.phrases[idx]if self.sentiments is not None:sentiment = self.sentiments[idx]return phrase, sentimentreturn phrase# 加载数据
def load_data():train_df = pd.read_csv(TRAIN_PATH, sep='\t')test_df = pd.read_csv(TEST_PATH, sep='\t')return train_df, test_dftrain_df, test_df = load_data()# 数据预处理
def preprocess_data(train_df, test_df):le = LabelEncoder()train_df['Sentiment'] = le.fit_transform(train_df['Sentiment'])# le.fit_transform(train_df['Sentiment']) 被调用之后,le 对象的状态会被更新:train_phrases = train_df['Phrase'].tolist()train_sentiments = train_df['Sentiment'].tolist()test_phrases = test_df['Phrase'].tolist()return train_phrases, train_sentiments, test_phrases, letrain_phrases, train_sentiments, test_phrases, le = preprocess_data(train_df, test_df)# 构建词汇表
"""
使用 enumerate 为集合中的每个单词分配一个唯一的索引。start=1 表示索引从 1 开始,而不是 0。
构建一个字典 word2idx,其中 word 是单词,idx 是该单词的索引。
添加一个特殊的 <PAD> 标记,通常用于填充文本以使其具有相同的长度。在词到索引的映射中,<PAD> 标记被分配了索引 0。
"""
def build_vocab(phrases):vocab = set()for phrase in phrases:for word in phrase.split():vocab.add(word)word2idx = {word: idx for idx, word in enumerate(vocab, start=1)}word2idx['<PAD>'] = 0return word2idxword2idx = build_vocab(train_phrases + test_phrases)# 将短语转换为索引
def phrase_to_indices(phrase, word2idx):return [word2idx[word] for word in phrase.split() if word in word2idx]train_indices = [phrase_to_indices(phrase, word2idx) for phrase in train_phrases]
test_indices = [phrase_to_indices(phrase, word2idx) for phrase in test_phrases]# 移除长度为0的样本
train_indices = [x for x in train_indices if len(x) > 0]
train_sentiments = [y for x, y in zip(train_indices, train_sentiments) if len(x) > 0]
test_indices = [x for x in test_indices if len(x) > 0]# 数据加载器
def collate_fn(batch):phrases, sentiments = zip(*batch)lengths = torch.tensor([len(x) for x in phrases])phrases = [torch.tensor(x) for x in phrases]phrases_padded = pad_sequence(phrases, batch_first=True, padding_value=0)sentiments = torch.tensor(sentiments)return phrases_padded, sentiments, lengthstrain_dataset = SentimentDataset(train_indices, train_sentiments)
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, collate_fn=collate_fn)test_dataset = SentimentDataset(test_indices)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, collate_fn=lambda x: pad_sequence([torch.tensor(phrase) for phrase in x], batch_first=True, padding_value=0))# 模型定义
class SentimentRNN(nn.Module):def __init__(self, vocab_size, embed_size, hidden_size, output_size, n_layers):super(SentimentRNN, self).__init__()self.embedding = nn.Embedding(vocab_size, embed_size, padding_idx=0)self.lstm = nn.LSTM(embed_size, hidden_size, n_layers, batch_first=True, bidirectional=True)self.fc = nn.Linear(hidden_size * 2, output_size)def forward(self, x, lengths):x = self.embedding(x)x = pack_padded_sequence(x, lengths.cpu(), batch_first=True, enforce_sorted=False)# 表示序列长度不需要严格递减,避免了手动排序 lengths 的需求_, (hidden, _) = self.lstm(x)hidden = torch.cat((hidden[-2], hidden[-1]), dim=1)out = self.fc(hidden)return outvocab_size = len(word2idx)
embed_size = 128
output_size = len(le.classes_)model = SentimentRNN(vocab_size, embed_size, HIDDEN_SIZE, output_size, N_LAYERS)criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)# 训练和测试循环
def train(model, train_loader, criterion, optimizer, n_epochs):model.train()for epoch in range(n_epochs):total_loss = 0for phrases, sentiments, lengths in train_loader:optimizer.zero_grad()output = model(phrases, lengths)loss = criterion(output, sentiments)loss.backward()optimizer.step()total_loss += loss.item()print(f'Epoch: {epoch+1}, Loss: {total_loss/len(train_loader)}')def generate_test_results(model, test_loader, test_ids):model.eval()results = []with torch.no_grad():for phrases in test_loader:lengths = torch.tensor([len(x) for x in phrases])output = model(phrases, lengths)preds = torch.argmax(output, dim=1)results.extend(preds.cpu().numpy())return resultstrain(model, train_loader, criterion, optimizer, N_EPOCHS)test_ids = test_df['PhraseId'].tolist()
preds = generate_test_results(model, test_loader, test_ids)# 保存结果
output_df = pd.DataFrame({'PhraseId': test_ids, 'Sentiment': preds})
output_df.to_csv('sentiment_predictions.csv', index=False)
引入随机性:重要性采样,对分类的样本按照它的分布来进行随机采样
gpt优化后代码:停词表自己在网上找个博客复制粘贴成stopwords.txt文件就行
主要改进点:
-
增加了嵌入层维度和隐藏层大小,LSTM 层数。
-
使用 GloVe 预训练词向量。
-
使用了 dropout 防止过拟合。
-
使用 AdamW 优化器。
-
移除停用词。
-
增加了验证集并使用学习率调度器用于模型性能评估。
-
保存和加载最优模型。
le = LabelEncoder()
train_df[‘Sentiment’] = le.fit_transform(train_df[‘Sentiment’])# le.fit_transform(train_df[‘Sentiment’]) 被调用之后,le 对象的状态会被更新:
- **`le.classes_`**:`le` 会保存 `train_df['Sentiment']` 中出现的所有唯一标签的排序列表。这是 `LabelEncoder` 内部用来将标签转换为数值的依据。
- **`le.class_to_index`**:`le` 内部保存了从标签到数值的映射关系。这些映射关系在 `fit` 操作中被建立,并在 `transform` 操作中应用。**示例**假设你有以下数据:```python
import pandas as pd
from sklearn.preprocessing import LabelEncoderdata = {'Sentiment': [2, 1, 0, 2, 1, 0]
}
train_df = pd.DataFrame(data)# 创建 LabelEncoder 实例
le = LabelEncoder()# 拟合和转换
encoded_labels = le.fit_transform(train_df['Sentiment'])print("Encoded Labels:", encoded_labels)
print("Classes:", le.classes_)
"""
输出
Encoded Labels: [2 1 0 2 1 0]
Classes: [0 1 2]
"""
le.fit_transform(train_df['Sentiment'])
通过拟合 train_df['Sentiment']
数据并转换它来更新 LabelEncoder
对象 le
的内部状态。这意味着 le
将保存关于标签的映射关系,并能够在未来对新的数据进行相同的转换。
BATCH_SIZE
定义: 每次模型训练时使用的样本数量。
选择考虑:
- 计算资源: 较大的
BATCH_SIZE
会增加内存需求,尤其是在 GPU 上训练时。选择BATCH_SIZE
时,需要考虑到你的硬件资源。 - 训练稳定性: 较大的批量可以稳定训练过程,但过大的批量可能导致训练收敛缓慢或性能下降。
- 训练速度: 较大的
BATCH_SIZE
通常会加快训练速度,但可能导致过拟合或内存不足。合理选择能在训练速度和内存限制之间取得平衡。
64 是一个常见的 BATCH_SIZE
,平衡了训练速度和内存消耗。你可以根据实际情况调整,比如尝试 32
或 128
,看看对训练效果的影响。
HIDDEN_SIZE
定义: LSTM 或其他 RNN 中隐藏状态的维度。
选择考虑:
- 模型复杂性: 较大的
HIDDEN_SIZE
可以增加模型的表达能力,使其能够捕捉更多的特征,但也会增加计算成本和过拟合的风险。 - 计算资源: 较大的
HIDDEN_SIZE
会增加内存和计算负担。
256 是一个常见的选择,适合许多任务。你可以尝试更小(如 128
)或更大(如 512
)的值来调节模型的表现。
EMBED_SIZE
定义: 词嵌入的维度,决定了每个词在嵌入空间中的表示大小。
选择考虑:
- 词汇表大小: 较大的
EMBED_SIZE
能够更好地表示词汇表中的词,但也需要更多的内存。 - 任务需求: 更高的
EMBED_SIZE
可以捕捉更复杂的语义信息,但也可能导致过拟合。
300 是 GloVe 和 FastText 常用的嵌入维度之一,通常能提供较好的语义表示。可以尝试其他值,如 100
或 200
,看看是否对任务有改进。
glove = GloVe(name='6B', dim=EMBED_SIZE)
这行代码创建了一个 GloVe 词向量实例。name='6B'
表示使用 GloVe 6B(6 billion tokens)模型,dim=EMBED_SIZE
指定了词向量的维度,通常是 50, 100, 200, 300 等。
在 PyTorch 中,collate_fn
是一个用于处理数据加载器(DataLoader
)中批次数据的函数。batch
是 DataLoader
在每次迭代中传递给 collate_fn
函数的参数。它由 DataLoader
从数据集中提取并组织数据后传递给 collate_fn
进行进一步处理。
def train()函数中为什么要用两次 model.train()
?
- 第一次
model.train()
:- 这是在函数开始时调用的,目的是将模型设置为训练模式。某些层,如 dropout 和 batch normalization,会在训练模式和评估模式下有不同的行为。通过调用
model.train()
,确保模型的这些层在训练时按预期工作。
- 这是在函数开始时调用的,目的是将模型设置为训练模式。某些层,如 dropout 和 batch normalization,会在训练模式和评估模式下有不同的行为。通过调用
- 第二次
model.train()
:- 这行代码在每个 epoch 的开始时调用,确保即使在某些情况下,模型的状态被切换到训练模式(例如,模型之前可能被切换到评估模式)。这样可以避免因模型状态不一致而导致的问题。
通常,调用 model.train()
一次就足够了,但在某些复杂的训练和验证循环中,为了确保模型在每个 epoch 的开始时都处于正确的模式,可能会看到在每个 epoch 开始时都调用 model.train()
。这可以避免因为模型状态没有被正确设置而导致的问题。
import pandas as pd
import torch
from torch.utils.data import Dataset, DataLoader
import torch.nn as nn
import torch.optim as optim
from sklearn.preprocessing import LabelEncoder
from torch.nn.utils.rnn import pad_sequence
from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence
from sklearn.model_selection import train_test_split
import zipfile
import os
from torchtext.vocab import GloVe# 超参数设置
BATCH_SIZE = 64
HIDDEN_SIZE = 256
N_LAYERS = 3
N_EPOCHS = 20
LEARNING_RATE = 0.01
EMBED_SIZE = 300
DROPOUT = 0.5# 数据集路径
TRAIN_ZIP_PATH = './dataset/train.tsv.zip'
TEST_ZIP_PATH = './dataset/test.tsv.zip'# 解压缩文件
def unzip_file(zip_path, extract_to='.'):with zipfile.ZipFile(zip_path, 'r') as zip_ref:zip_ref.extractall(extract_to)unzip_file(TRAIN_ZIP_PATH)
unzip_file(TEST_ZIP_PATH)# 数据集路径
TRAIN_PATH = './train.tsv'
TEST_PATH = './test.tsv'# 自定义数据集类
class SentimentDataset(Dataset):def __init__(self, phrases, sentiments=None):self.phrases = phrasesself.sentiments = sentimentsdef __len__(self):return len(self.phrases)def __getitem__(self, idx):phrase = self.phrases[idx]if self.sentiments is not None:sentiment = self.sentiments[idx]return phrase, sentimentreturn phrase# 加载数据
def load_data():train_df = pd.read_csv(TRAIN_PATH, sep='\t')test_df = pd.read_csv(TEST_PATH, sep='\t')return train_df, test_dftrain_df, test_df = load_data()# 数据预处理
def preprocess_data(train_df, test_df):le = LabelEncoder()train_df['Sentiment'] = le.fit_transform(train_df['Sentiment'])train_phrases = train_df['Phrase'].tolist()train_sentiments = train_df['Sentiment'].tolist()test_phrases = test_df['Phrase'].tolist()test_ids = test_df['PhraseId'].tolist()return train_phrases, train_sentiments, test_phrases, test_ids, letrain_phrases, train_sentiments, test_phrases, test_ids, le = preprocess_data(train_df, test_df)# 移除停用词
def remove_stopwords(phrases):stopwords = set(open('./dataset/stopwords.txt').read().split())return [' '.join([word for word in phrase.split() if word not in stopwords]) for phrase in phrases]train_phrases = remove_stopwords(train_phrases)
test_phrases = remove_stopwords(test_phrases)# 构建词汇表
def build_vocab(phrases):vocab = set()for phrase in phrases:for word in phrase.split():vocab.add(word)word2idx = {word: idx for idx, word in enumerate(vocab, start=1)}word2idx['<PAD>'] = 0return word2idxword2idx = build_vocab(train_phrases + test_phrases)# 加载预训练的词向量
glove = GloVe(name='6B', dim=EMBED_SIZE)# 创建嵌入矩阵
def create_embedding_matrix(word2idx, glove):vocab_size = len(word2idx)embedding_matrix = torch.zeros((vocab_size, EMBED_SIZE))for word, idx in word2idx.items():if word in glove.stoi:# glove.stoi: 是一个字典,用于将词汇表中的词映射到索引位置。不能直接用gloveembedding_matrix[idx] = glove[word]else:embedding_matrix[idx] = torch.randn(EMBED_SIZE)return embedding_matrixembedding_matrix = create_embedding_matrix(word2idx, glove)# 将短语转换为索引
def phrase_to_indices(phrase, word2idx):return [word2idx[word] for word in phrase.split() if word in word2idx]train_indices = [phrase_to_indices(phrase, word2idx) for phrase in train_phrases]
test_indices = [phrase_to_indices(phrase, word2idx) for phrase in test_phrases]# 移除长度为0的样本
train_indices, train_sentiments = zip(*[(x, y) for x, y in zip(train_indices, train_sentiments) if len(x) > 0])
# 注意:这里不移除 test_indices 中的空样本,因为我们需要保持 test_ids 的完整性
test_indices_with_default = [phrase if len(phrase) > 0 else [0] for phrase in test_indices]# 划分训练集和验证集
train_indices, val_indices, train_sentiments, val_sentiments = train_test_split(train_indices, train_sentiments, test_size=0.2, random_state=42)# 数据加载器
def collate_fn(batch):phrases, sentiments = zip(*batch)lengths = torch.tensor([len(x) for x in phrases])phrases = [torch.tensor(x) for x in phrases]phrases_padded = pad_sequence(phrases, batch_first=True, padding_value=0)sentiments = torch.tensor(sentiments)return phrases_padded, sentiments, lengthstrain_dataset = SentimentDataset(train_indices, train_sentiments)
val_dataset = SentimentDataset(val_indices, val_sentiments)
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, collate_fn=collate_fn)
val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, collate_fn=collate_fn)test_dataset = SentimentDataset(test_indices_with_default)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False,collate_fn=lambda x: pad_sequence([torch.tensor(phrase) for phrase in x], batch_first=True,padding_value=0))# 模型定义
class SentimentRNN(nn.Module):def __init__(self, vocab_size, embed_size, hidden_size, output_size, n_layers, dropout, embedding_matrix):super(SentimentRNN, self).__init__()self.embedding = nn.Embedding.from_pretrained(embedding_matrix, freeze=False)self.lstm = nn.LSTM(embed_size, hidden_size, n_layers, batch_first=True, bidirectional=True, dropout=dropout)self.fc = nn.Linear(hidden_size * 2, output_size)self.dropout = nn.Dropout(dropout)def forward(self, x, lengths):x = self.embedding(x)x = pack_padded_sequence(x, lengths.cpu(), batch_first=True, enforce_sorted=False)packed_output, (hidden, _) = self.lstm(x)hidden = torch.cat((hidden[-2], hidden[-1]), dim=1)hidden = self.dropout(hidden)out = self.fc(hidden)return outvocab_size = len(word2idx)
output_size = len(le.classes_)model = SentimentRNN(vocab_size, EMBED_SIZE, HIDDEN_SIZE, output_size, N_LAYERS, DROPOUT, embedding_matrix)criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=LEARNING_RATE)# 学习率调度器
"""
在 PyTorch 中,optim.lr_scheduler.ReduceLROnPlateau 是一种学习率调度器,用于在模型的训练过程中自动调整学习率,以帮助提高模型的性能和收敛速度。它会监控一个指定的指标(通常是验证集的损失),并在指标不再改善时减少学习率。
"""
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=3)# 训练和测试循环
def train(model, train_loader, val_loader, criterion, optimizer, scheduler, n_epochs):model.train()best_val_accuracy = 0for epoch in range(n_epochs):total_loss = 0model.train()for phrases, sentiments, lengths in train_loader:optimizer.zero_grad()output = model(phrases, lengths)loss = criterion(output, sentiments)loss.backward()optimizer.step()total_loss += loss.item()print(f'Epoch: {epoch + 1}, Loss: {total_loss / len(train_loader)}')val_accuracy = evaluate(model, val_loader)scheduler.step(total_loss / len(train_loader))if val_accuracy > best_val_accuracy:best_val_accuracy = val_accuracytorch.save(model.state_dict(), 'best_model.pt')print(f'Epoch: {epoch + 1}, Val Accuracy: {val_accuracy}')def evaluate(model, val_loader):model.eval()correct, total = 0, 0with torch.no_grad():for phrases, sentiments, lengths in val_loader:output = model(phrases, lengths)preds = torch.argmax(output, dim=1)correct += (preds == sentiments).sum().item()total += sentiments.size(0)return correct / total"""
.cpu() 方法将 preds 张量从 GPU 内存移动到 CPU 内存。这是因为 NumPy 操作需要在 CPU 上进行,因此必须先将张量移到 CPU 上。
extend() 方法用于将一个可迭代对象(在这里是 NumPy 数组)中的所有元素添加到 results 列表中,而不是将整个对象添加为一个单独的元素。
results.extend(preds.cpu().numpy()) 的作用是:
将 preds 张量从 GPU 转移到 CPU。
将 preds 张量转换为 NumPy 数组。
将 NumPy 数组的所有元素添加到 results 列表中。
"""
def generate_test_results(model, test_loader):model.eval()results = []with torch.no_grad():for phrases in test_loader:lengths = torch.tensor([len(x) for x in phrases])output = model(phrases, lengths)preds = torch.argmax(output, dim=1)results.extend(preds.cpu().numpy())return resultstrain(model, train_loader, val_loader, criterion, optimizer, scheduler, N_EPOCHS)# 加载最优模型
model.load_state_dict(torch.load('best_model.pt'))preds = generate_test_results(model, test_loader)# 确保输出
print(123)
# 确保 test_ids 和 preds 长度一致
assert len(test_ids) == len(preds), f"Lengths do not match: {len(test_ids)} vs {len(preds)}"print(3456)
# 保存结果
output_df = pd.DataFrame({'PhraseId': test_ids, 'Sentiment': preds})
output_df.to_csv('sentiment_predictions2.csv', index=False)
相关文章:
PyTorch 深度学习实践-循环神经网络(高级篇)
视频指路 参考博客笔记 参考笔记二 文章目录 上课笔记总代码练习 上课笔记 个人能力有限,重看几遍吧,第一遍基本看不懂 名字的每个字母都是一个特征x1,x2,x3…,一个名字是一个序列 rnn用GRU 用ASCII表作为词典,长度为128&#x…...
这才是老板喜欢的电子信息类简历
点击可直接使用...
MySQL学习之事务,锁机制
事务 什么是事务? 事务就是逻辑上的一组操作,要么全做,要么全不做 事务经典例子:转账,转账需要两个操作,从一个人账户上减钱,在另一个账户上加钱,比如说小红给小明转账100元&…...
开源知识付费小程序源码 内容付费系统php源码 含完整图文部署教程
在当今数字化时代,知识付费作为一种新型的经济模式,正逐渐受到越来越多内容创作者、专家及商家的青睐。开源知识付费小程序源码和内容付费系统PHP源码作为实现这一模式的重要工具,为构建高效、安全、可扩展的知识付费平台提供了强大的技术支持…...
时序数据库如何选型?详细指标总结!
工业物联网场景,如何判断什么才是好的时序数据库? 工业物联网将机器设备、控制系统与信息系统、业务过程连接起来,利用海量数据进行分析决策,是智能制造的基础设施,并影响整个工业价值链。工业物联网机器设备感知形成了…...
【前端】JavaScript入门及实战51-55
文章目录 51 函数52 函数的参数53 返回值54 练习55 return 51 函数 <!DOCTYPE html> <html> <head> <title></title> <meta charset "utf-8"> <script type"text/javascript">/* 函数:1. 函数也是…...
【引领未来智造新纪元:量化机器人的革命性应用】
在日新月异的科技浪潮中,量化机器人正以其超凡的智慧与精准的操作,悄然改变着各行各业的生产面貌,成为推动产业升级、提升竞争力的关键力量。今天,让我们一同探索量化机器人在不同领域的广泛应用价值,见证它如何以科技…...
山东航空小程序查询
山东航空小程序 1) 请求地址 https://scxcx.sda.cn/mohe/proxy?url/trp/ticket/search 2) 调用方式:HTTP post 3) 接口描述: 接口描述详情 4) 请求参数: {"dep": "TAO","arr": "HRB","flightDate&qu…...
MySQL添加索引时会锁表吗?
目录 简介Online DDL概念Online DDL用法总结 简介 在MySQL5.5以及之前的版本,通常更改数据表结构操作(DDL)会阻塞对表数据的增删改操作(DML)。 MySQL5.6提供Online DDL之后可支持DDL与DML操作同时执行,降低…...
算法日记day 16(二叉树的广度优先遍历|反转、对称二叉树)
一、二叉树的层序遍历 题目: 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 示例 1: 输入:root [3,9,20,null,null,15,7] 输出:[[3]…...
PolarisMesh源码系列--Polaris-Go注册发现流程
导语 北极星是腾讯开源的一款服务治理平台,用来解决分布式和微服务架构中的服务管理、流量管理、配置管理、故障容错和可观测性问题。在分布式和微服务架构的治理领域,目前国内比较流行的还包括 Spring Cloud,Apache Dubbo 等。在 Kubernete…...
vue3 vxe-grid修改currentPage,查询数据的时候,从第一页开始查询
1、当我们设置好VxeGrid.Options进行数据查询的时候,下面是可能的设置: const gridOptions reactive<BasicTableProps>({id: UserTable,showHeaderOverflow: false,showOverflow: true,keepSource: true,columns: userColumns,size: small,pagerConfig: {cur…...
电商数据集成之电商商品信息采集系统架构设计||电商API接口
一、引言 本架构设计文档旨在阐述基于 Selenium 的电商商品信息采集系统的整体架构,包括系统视图、逻辑视图、物理视图、开发视图和进程视图,并提供一个简单的采集电商商品信息的 demo。该系统通过模拟浏览器行为,实现对电商商品信息的自…...
Spring Cloud Stream 实现统一消息通信平台
1. 概述 Spring Cloud Stream:是Spring提供的消息通信框架,旨在构建跨不同消息中间件的统一通信平台。目的:通过消息通信机制降低分布式系统中服务间的耦合度,实现异步服务交互。 2. 消息通信与RPC RPC:远程过程调用…...
uniapp安卓plus原生选择系统文件
uniapp安卓plus原生选择系统文件 效果: 组件代码: <template xlang"wxml" minapp"mpvue"><view></view> </template> <script>export default {name: file-manager,props: {},data() {return {is…...
Go语言 Import导入
本文主要介绍Go语言import导入使用时注意事项和功能实现示例。 目录 Import 创建功能文件夹 加法 减法 主函数 优化导入的包名 .引入方法 总结 Import 创建功能文件夹 做一个计算器来演示,首先创建test文件夹。 加法 在test文件夹中创建add文件夹ÿ…...
一款异次元小清新风格的响应式wordpress个人博客主题
一款异次元小清新风格的响应式个人博客主题。这是一款专注于用户阅读体验的响应式 WordPress 主题,整体布局简洁大方,针对资源加载进行了优化。 Kratos主题基于Bootstrap和Font Awesome的WordPress一个干净,简单且响应迅速的博客主题&#x…...
【cocos creator】ts中export的模块管理
在 TypeScript(TS)中,export 和 import 的概念与 Java 中的 public 类、接口以及 import 语句有一些相似之处。可以用以下方式来类比理解: Export 在 TypeScript 中,export 用于将模块中的变量、函数、类等暴露给外部…...
QT JSON使用实例
下面是一个使用Qt框架的示例代码,展示如何获取仪器的状态,将其打包成JSON格式,保存到当前目录下的JSON文件中,然后通过FTP发送该文件。 1. 准备工作 确保你已经安装了Qt,并创建一个新的Qt Console项目或Qt Widgets项目…...
浅聊 Three.js 屏幕空间反射SSR-SSRShader
浅聊 Three.js 屏幕空间反射SSR(2)-SSRShader 前置基础 渲染管线中的相机和屏幕示意图 -Z (相机朝向的方向)||| -------------- <- 屏幕/投影平面| | || | || | (f) | <- 焦距| | ||…...
Windows图形界面(GUI)-DLG-C/C++ - 月历控件(MonthCalendar)
公开视频 -> 链接点击跳转公开课程博客首页 -> e链接点击跳转博客主页 目录 月历控件(MonthCalendar) 使用场景 控件操作 月历控件(MonthCalendar) 使用场景 日程安排:用户可以通过月历控件选择特定的日期来安排会议或活动。事件管理&#x…...
【Langchain大语言模型开发教程】基于文档问答
🔗 LangChain for LLM Application Development - DeepLearning.AI Embedding: https://huggingface.co/BAAI/bge-large-en-v1.5/tree/main 学习目标 1、Embedding and Vector Store 2、RetrievalQA 引包、加载环境变量 import osfrom dotenv import…...
大厂面试-基本功
大厂面试第4季 服务可用性多少个9是什么意思遍历集合add或remove操作bughashcode冲突案例BigdecimalList去重复IDEA Debugger测试框架ThreaLocal父子线程数据同步 InheritableThreadLocal完美解决线程数据同步方案 TransmittableThreadLocal 服务可用性多少个9是什么意思 遍历集…...
RV1103使用rtsp和opencv推流视频到网页端
参考: Luckfox-Pico/Luckfox-Pico-RV1103/Luckfox-Pico-pinout/CSI-Camera Luckfox-Pico/RKMPI-example Luckfox-Pico/RKMPI-example 下载源码 其中源码位置:https://github.com/luckfox-eng29/luckfox_pico_rtsp_opencv 使用git clone由于项目比较大&am…...
与Bug较量:Codigger之软件项目体检Software Project HealthCheck来帮忙
在软件工程师的世界里,与 Java 小程序中的 Bug 作战是一场永不停歇的战役。每一个隐藏在代码深处的 Bug 都像是一个狡猾的敌人,时刻准备着给我们的项目带来麻烦。 最近,我就陷入了这样一场与 Java 小程序 Bug 的激烈较量中。这个小程序原本应…...
Git --- Branch Diverged
Git --- Branch Diverged Branch Diverged是如何形成的如何解决RebaseMerge Branch Diverged是如何形成的 尝试提交并将更改推送到 master 分支时,是否看到这条烦人的消息 原因是: 直到更改 B 之前,我的分支和“origin/master”完全相同。从…...
go标准库---net/http服务端
1、http简单使用 go的http标准库非常强大,调用了两个函数就能够实现一个简单的http服务: func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) func ListenAndServe(addr string, handler Handler) error handleFunc注册一个路…...
Linux文件和目录常用命令
1.操作命令 查看目录内容 ls 切换目录 cd 创建和删除操作 touch rm mkdir 拷贝和移动文件 cp mv 查看文件内容 cat more grep 其他 echo 重定向 > 和 >> 管道 | 1.1 终端实用技巧 1>自动补全 在敲出 文件/目录/命令 的前几个字母之后,按下…...
【C++刷题】优选算法——链表
链表常用技巧和操作总结 常用技巧 画图 引入虚拟头节点 不要吝啬空间,大胆定义变量 快慢双指针常用操作 创建一个新节点 尾插 头插 两数相加 ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {int carry 0;ListNode* newHead new ListNode, *cur newHea…...
Flex和Bison
Flex和Bison是Linux和Unix环境下两个非常强大的工具,分别用于生成词法分析器和语法分析器。它们在编译器设计、文本处理等领域有着广泛的应用。下面我将详细介绍Flex和Bison的基本概念、功能、用法以及它们之间的关系。 一、Flex 1. 基本概念 Flex(其…...
建网站要钱吗/投放广告找什么平台
Nacos支持基于Namespace和Group的配置分组管理,以便用户更灵活的根据自己的需要按照环境或者应用、模块等分组管理微服务的大量配置,在配置管理中主要提供了配置历史版本、回滚、订阅者查询等核心管理能力。 配置列表 点击Nacos控制台的配置管理->配…...
可以做雷达图的网站/世界500强企业
经过简单测试 1、不能像xshell一样选择服务器的密钥登陆,只能通过将本机的公钥传到服务器上 2、不能使用本地socket5做代理登录 3、不能使用系统代理登录 一、使用密钥连接到跳板机 1、内网穿透 2、将本机公钥上传到服务器上 1)建立密钥对 无论是win…...
成品网站灬源码1688/黄页网络的推广软件
n&(n-1)统计二进制比特中1的个数 unsigned int bitOneCount(unsigned int n) {unsigned int count 0;while(0 < n){n & (n--);count;}return count; } 蓝色的是n的初始值,黑色的是当前最新的n-1,也就是上一行的值减1,红色的是n…...
wordpress 转移本地/怎么在百度发布自己的文章
微信小程序教程系列 微信小程序学习请点击原链接 这个是微信小程序从开发到上线的教程原链接 微信小程序项目实战系列 项目实战请点击原链接 微信小程序下拉刷新加载更多 请点击进入原链接 页面之间传递参数的跳转 https://cnsyear.com/posts/fd62d149.html原链接 页面…...
web程序设计asp.net实用网站开发/怎么做电商卖东西
建议一:创业初期要聚焦重点,把一个产品做到极致 1989年,从深圳大学读完研究生,史玉柱开启了自己的创业之旅。 史玉柱把创业项目选在自己所熟悉的软件开发上。他的四人开发团队用了8个月的时间,对开发的软件进行了完善。…...
地方门户网站的前途/广告公司名字
文章目录前言一、ANR是什么?二、ANR时间规定1.Service Timeout2.BroadcastQueue Timeout3.ContentProvider Timeout4.InputDispatching Timeout三、ANR场景分析InputEvent TimeoutBroadcastReceiver TimeoutService TimeoutContentProvider 类型四.导致ANR的原因应用…...