学习word2vec+lstm(从github上下载的代码)
如有侵权立即删除。
https://github.com/lukysummer/Movie-Review-Sentiment-Analysis-LSTM-Pytorch
如有侵权立即删除。
数据集
一行一个样本,共25000行
标签:25000个
1. 读取训练文本
with open("data/reviews.txt") as f:
reviews = f.read()
with open("data/labels.txt") as f:
labels = f.read()
# 3行的小数据集
with open("./data/small_reviews.txt") as f:
reviews = f.read()
print(reviews)
print(type(reviews)) #<class 'str'>
with open("./data/small_labels.txt") as f:
labels = f.read()
print(labels)
print(type(labels)) # <class 'str'>
先用三行的小文本测试的
2.文本预处理
from string import punctuation
def preprocess(text):
text = text.lower()
text = "".join([ch for ch in text if ch not in punctuation]) # 3行
all_reviews = text.split("\n")
# text = " ".join(text)
text = " ".join(all_reviews) #改动 把所有的内容连接 添加到了text中 1行
# print(text) # bromwell high is a cartoon comedy it ran at the same tim
all_words = text.split()
# print(all_words) # ['bromwell', 'high', 'is', 'a', 'cartoon', 'comedy', 'it', 'ran', 'at', 'the', 'same', 'time', 'as', 'some', 'other', 'programs', 'about', 'school', 'life', 'such',
return all_reviews, all_words
all_reviews, all_words = preprocess(reviews)
前面是读取了数据集的内容reviews 3行
然后将数据全部转为小写 3行
然后去除所有的标点符号 3行
将内容按行划分为列表。一维列表,每个元素是一个篇评论。
将内容连接成一行
将内容按照空格划分为 单词列表
返回 评论列表(小写且不包含标点)和单词列表
3.创建词典并对评论进行编码
from collections import Counter # 用于计算可迭代对象中元素的频率。
word_counts = Counter(all_words) # 这是一个频率字典 # 使用 Counter 计算 all_words 中每个单词的频率,将结果存储在 word_counts 中,它是一个字典,其中键是单词,值是对应的频率。
word_list = sorted(word_counts, key=word_counts.get, reverse=True) #这是单词列表 # 将 word_counts 中的单词按照它们的频率从高到低排序,并将排序后的结果存储在 word_list 中。key=word_counts.get 意味着按照每个单词在 word_counts 中的频率进行排序,reverse=True 表示降序排列。
print(word_list) # ['the', 'a', 'to', 'it', 'of', 's', 'on', 'is', 'who', 'or', 'br', 'as', 'that
vocab_to_int = {word:idx+1 for idx, word in enumerate(word_list)} # 整一个字典 键是词,值是标识
int_to_vocab = {idx:word for word, idx in vocab_to_int.items()} # 整一个字典 反过来 键是标识,值是词
# encoded_reviews = [[vocab_to_int[word] for word in review] for review in all_reviews]
### fixed by Ritian-Li ###
encoded_reviews = [[vocab_to_int.get(word) for word in review.split()] for review in all_reviews]
print(encoded_reviews)
这是两个字典:vocab_to_int、int_to_vocab
encoded_reviews 将评论转化为二维列表,每个元素代表一个评论,其中每个评论是将单词转化为对应索引。
# 将评论中得每个单词转化为对应的索引,得到一个二维列表,每个列表因为评论长度不同,列表长度也不同
4.编码标签
all_labels = labels.split("\n")
encoded_labels = [1 if label == "positive" else 0 for label in all_labels] # 将标签转化为0/1 ,积极得就是1.消极的就是0
print(encoded_labels)
print(len(encoded_labels))
# 判断 评论的列表长度是否等于标签列表长度 如果不相同,将引发断言错误,显示错误消息, "# of encoded reivews & encoded labels must be the same!"
assert len(encoded_reviews) == len(encoded_labels), "# of encoded reivews & encoded labels must be the same!"
5.去掉长度为0的评论
import numpy as np
import torch
# 获得评论长度大于0的标签列表
encoded_labels = np.array( [label for idx, label in enumerate(encoded_labels) if len(encoded_reviews[idx]) > 0] )
# 获得评论长度大于0的评论列表
encoded_reviews = [review for review in encoded_reviews if len(review) > 0]
6.使所有评论的篇幅相同
# 使篇幅长度相同
def pad_text(encoded_reviews, seq_length):
reviews = []
for review in encoded_reviews:
if len(review) >= seq_length:
reviews.append(review[:seq_length]) # 直接去除长于200的那部分
else:
reviews.append([0]*(seq_length-len(review)) + review) # 如果 不到200,则在前面填充0,使长度达到200
# print(reviews)
return np.array(reviews)
padded_reviews = pad_text(encoded_reviews, seq_length=200) # 填充后的评论,现在评论的长度都相同,都是200
print(padded_reviews.shape) # (3, 200)
reviews的样子 是一个列表
padded_reviews 是numpy类型的数组
7.划分数据并获取(评论、标签)数据加载器
train_ratio = 0.8
valid_ratio = (1 - train_ratio)/2
total = padded_reviews.shape[0] # 总共有多少条评论,也就是总共有多少样本
train_cutoff = int(total * train_ratio) # 训练个数
valid_cutoff = int(total * (1 - valid_ratio)) # 验证个数
train_x, train_y = padded_reviews[:train_cutoff], encoded_labels[:train_cutoff] # 训练 都是numpy类型的数组
valid_x, valid_y = padded_reviews[:train_cutoff : valid_cutoff], encoded_labels[train_cutoff : valid_cutoff] # 验证
test_x, test_y = padded_reviews[valid_cutoff:], encoded_labels[valid_cutoff:] # 测试
from torch.utils.data import TensorDataset, DataLoader
# 转化为tensor类型的数据集
train_data = TensorDataset(train_x, train_y)
valid_data = TensorDataset(valid_x, valid_y)
test_data = TensorDataset(test_x, test_y)
# 转化为dataloader
batch_size = 20 # 从50转为20
train_loader = DataLoader(train_data, batch_size = batch_size, shuffle = True)
valid_loader = DataLoader(valid_data, batch_size = batch_size, shuffle = True)
test_loader = DataLoader(test_data, batch_size = batch_size, shuffle = True)
8.定义LSTM模型
from torch import nn
class SentimentLSTM(nn.Module):
def __init__(self, n_vocab, n_embed, n_hidden, n_output, n_layers, drop_p = 0.5):
super().__init__()
# params: "n_" means dimension
self.n_vocab = n_vocab # number of unique words in vocabulary
self.n_layers = n_layers # number of LSTM layers
self.n_hidden = n_hidden # number of hidden nodes in LSTM
self.embedding = nn.Embedding(n_vocab, n_embed)
self.lstm = nn.LSTM(n_embed, n_hidden, n_layers, batch_first = True, dropout = drop_p)
self.dropout = nn.Dropout(drop_p)
self.fc = nn.Linear(n_hidden, n_output)
self.sigmoid = nn.Sigmoid()
def forward (self, input_words):
# INPUT : (batch_size, seq_length)
embedded_words = self.embedding(input_words) # (batch_size, seq_length, n_embed)
lstm_out, h = self.lstm(embedded_words) # (batch_size, seq_length, n_hidden)
lstm_out = self.dropout(lstm_out)
lstm_out = lstm_out.contiguous().view(-1, self.n_hidden) # (batch_size*seq_length, n_hidden)
fc_out = self.fc(lstm_out) # (batch_size*seq_length, n_output)
sigmoid_out = self.sigmoid(fc_out) # (batch_size*seq_length, n_output)
sigmoid_out = sigmoid_out.view(batch_size, -1) # (batch_size, seq_length*n_output)
# extract the output of ONLY the LAST output of the LAST element of the sequence
sigmoid_last = sigmoid_out[:, -1] # (batch_size, 1)
return sigmoid_last, h
def init_hidden (self, batch_size): # initialize hidden weights (h,c) to 0
device = "cuda" if torch.cuda.is_available() else "cpu"
weights = next(self.parameters()).data
h = (weights.new(self.n_layers, batch_size, self.n_hidden).zero_().to(device),
weights.new(self.n_layers, batch_size, self.n_hidden).zero_().to(device))
return h
初始化lstm,参数的意思
self.lstm = nn.LSTM(n_embed, n_hidden, n_layers, batch_first = True, dropout = drop_p)
-
n_embed
: 输入特征的维度,通常是词嵌入的维度。在这个情感分析模型中,n_embed
表示每个单词的嵌入维度。 -
n_hidden
: LSTM 层中的隐藏节点数,表示模型学习的表示空间的维度。更多的隐藏节点可能允许模型学到更复杂的特征,但也会增加计算成本。 -
n_layers
: LSTM 层的数量。多层 LSTM 允许模型学习更复杂的时序模式。 -
batch_first=True
: 这个参数指定输入数据的形状。如果设置为True
,输入数据的形状应该是(batch_size, seq_length, n_embed)
,其中batch_size
表示每个批次的样本数,seq_length
表示序列的长度(在这个例子里,是一个评论的长度是200),n_embed
表示每个单词的嵌入维度。 -
dropout=drop_p
: 这是一个可选的参数,用于指定在 LSTM 层中是否使用 dropout。dropout
是一种正则化技术,有助于防止过拟合。它指定在网络中随机丢弃输入单元的比例。
self.lstm = nn.LSTM(n_embed, n_hidden, n_layers, batch_first = True, dropout = drop_p) self.dropout = nn.Dropout(drop_p)
为什么上面lstm有一个dropout,下面还要加一个
总体而言,这一行代码创建了一个具有指定参数的 LSTM 层,用于处理输入序列。在情感分析中,输入序列通常是单词序列,而 LSTM 层有助于模型理解文本中的时序信息。
在上述代码中,nn.LSTM
的 dropout
参数是关于输入的 dropout,而下面的 nn.Dropout
则是关于输出的 dropout。
具体来说:
-
self.lstm = nn.LSTM(n_embed, n_hidden, n_layers, batch_first=True, dropout=drop_p)
: 这里的dropout
参数是用于控制输入层到LSTM层的 dropout。这表示在将输入序列传递到LSTM层之前,每个输入元素都有drop_p
的概率被随机清零,这有助于防止过拟合。 -
self.dropout = nn.Dropout(drop_p)
: 这里的nn.Dropout
是一个独立的 dropout 层,通常用于模型的其他部分,比如在全连接层之后。这个 dropout 用于控制从 LSTM 层到全连接层的输出的 dropout。它有助于防止模型在训练时对于特定的输入过于依赖,同样也是为了防止过拟合。
这两者一起使用有助于提高模型的泛化能力,降低对于训练数据的过拟合风险。dropout 在训练时起到正则化的作用,而在模型评估或推断时,这些 dropout 层通常会被关闭。
9.实例化带有超参数的模型
n_vocab = len(vocab_to_int)
n_embed = 400
n_hidden = 512
n_output = 1 # 1 ("positive") or 0 ("negative")
n_layers = 2
net = SentimentLSTM(n_vocab, n_embed, n_hidden, n_output, n_layers)
10.定义损失和优化器
from torch import optim
criterion = nn.BCELoss()
optimizer = optim.Adam(net.parameters(), lr = 0.001)
11. 训练这个网络
12.在测试集上测试训练后的模型
13.在随机单次审查中测试训练后的模型
一些知识点:
1.nn.Embedding(num_embeddings=vocab_size, embedding_dim=embed_dim)
参数是词表大小和嵌入维度
这个函数的作用是,对你的词表产生一个嵌入表,这个嵌入只保证唯一性,不保证相关性(所以和word2vec还是有区别的)。
在使用时,直接调用:
embedding = self.embedding(input)
这里的 input 应为一个下标列表,输出即为对应的嵌入。
更多推荐
所有评论(0)