pytorch深度学习笔记23

目录

摘要

1.自然语言处理

2.词嵌入层

3.循环网络层


摘要

本篇文章继续学习尚硅谷深度学习教程,学习内容是RNN相关概念,及相关API使用方法

1.自然语言处理

我们平日使用的语言,如汉语或英语,称为自然语言。自然语言处理(Natural Language Processing,NLP)的目标就是让计算机理解人类语言,进而完成对我们有帮助的事情。说到计算机可以理解的语言,我们可能会想到编程语言或者标记语言等。这些语言的语法定义可以唯一性地解释代码含义,计算机也能根据确定的规则分析代码。编程语言是一种机械的、缺乏活力的语言。换句话说,它是一种"硬语言"。而汉语或英语等自然语言是"软语言",其含义和形式会灵活变化,并且会不断出现新的词语或新的含义。要让计算机去理解自然语言,使用常规方法是无法办到的。

基于同义词词典的方法

具有相同(同义词)或类似(近义词)含义的单词,可以归到同一个类别中;而根据单词"整体-部分"或者"上位-下位"关系,可以构建出层级的树状图。

这样,就可以构成一个庞大的"单词网络",用它就可以教会计算机单词之间的关系,从而计算出单词的"相似度"。

主要缺点:

  • 需要人工逐个定义单词之间的相关性,非常费时费力;
  • 新词不断出现,语言不断变化,词典维护成本极高;
  • 在表现力上也有限制。

基于计数的方法

大量的文本数据,构成了 语料库(corpus)。

我们的目的,就是从语料库中,自动且高效地提取出语言的"本质"。最简单的做法,就是统计"词频"。

  1. 分词:对词进行统计,首先需要对文本内容进行切分,找出一个个基本单元;
  2. 词关联ID:给单词标上一个 ID,构建单词和ID的关联字典(称为"词表");
  3. 词向量化:用一个固定长度的向量来表示单词,也称为词的"分布式表示"。

对每一个词,可以统计它周围出现了什么单词、出现了多少次(称为"上下文");把这些词频统计出来,就构成了一个向量;这个向量就可以表示当前的词了,称为"词向量"(word vector)。这样,所有词对应的向量,汇总起来就是一个矩阵,被称为 共现矩阵(co-occurrence matrix)。

主要缺点:对所有词进行向量化表示的计算复杂度极高。

基于推理的方法

除了基于计数的方法,还可以使用推理的方法把词用向量表示出来。

我们希望在已知上下文的前提下,"推测"当前位置的词是什么。

利用神经网络,接收上下文信息作为输入,通过模型计算,输出各个单词可能得出现概率;从而就可以根据上下文,预测该出现的单词了。

2.词嵌入层

自然语言是由文字构成的,而语言的含义是由单词构成的。即单词是含义的最小单位。因此为了让计算机理解自然语言,首先要让它理解单词含义。

词向量是用于表示单词意义的向量,也可以看作词的特征向量。将词映射到向量的技术称为 词嵌入(Word Embedding)。

还有一种使用向量表示单词意义的方式是独热向量,独热向量很容易构建,但它们通常不是一个好的选择。一个主要原因是独热向量不能准确表达不同词之间的相似度。比如使用余弦相似度
来表示两个词之间的相似程度,由于任意两个不同词的独热向量之间的余弦相似度为0,所以独热向量不能编码词之间的相似性。另一个原因是随着词汇量的增大,独热向量表示的向量大小也会增大,在词汇量较大的情况下会消耗大量的存储资源与计算资源。

将词转换为词向量时:

  • 首先需要对文本进行分词,再根据需要进行清洗和标准化。
  • 构建词表(Vocabulary),每个词对应一个索引。
  • 使用词嵌入矩阵将词索引转换为词向量。

API使用

可使用torch.nn.Embedding来初始化词嵌入矩阵:

python 复制代码
torch.nn.Embedding(num_embeddings, embedding_dim)

# num_embeddings:词的数量

# embedding_dim:词向量的维度

例:

先安装jieba库用于分词:pip install jieba。

python 复制代码
mport torch
import torch.nn as nn
import jieba

# 设置随机种子
torch.manual_seed(42)
text = "自然语言是由文字构成的,而语言的含义是由单词构成的。即单词是含义的最小单位。因此为了让计算机理解自然语言,首先要让它理解单词含义。"
# 自定义停用词和标点符号
stopwords = {"的", "是", "而", "由", ",", "。", "、"}
# 分词,过滤停用词和标点,去重,构建词表
words = [word for word in jieba.lcut(text) if word not in stopwords]
vocab = list(set(words))  # 词表
# 构建词到索引的映射
word2idx = dict()
for idx, word in enumerate(vocab):
    word2idx[word] = idx
# 初始化嵌入层
embed = nn.Embedding(num_embeddings=len(word2idx), embedding_dim=5)
# 打印词向量
for idx, word in enumerate(vocab):
    word_vec = embed(torch.tensor(idx))  # 通过索引获取词向量
    print(f"{idx:>2}:{word:8}\t{word_vec.detach().numpy()}")

3.循环网络层

文本是连续的,具有序列特性。如果其序列被重排可能就会失去原有的意义。比如"狗咬人"这段文本具有序列关系,如果文字的序列颠倒可能就会表达不同的意思。

目前我们接触的神经网络都是前馈型神经网络。前馈(feedforward)是指网络的传播方向是单向的。具体地说,将输入信号传给下一层,下一层接收到信号后传给下下一层,然后再传给下下下一层...像这样,信号仅在一个方向上传播。虽然前馈网络结构简单、易于理解,并且可以应用于许多任务中。不过,这种网络存在一个大问题,就是不能很好地处理时间序列数据。更确切地说,单纯的前馈网络无法充分学习时序数据的性质。于是,循环神经网络(Recurrent Neural Network,RNN)应运而生。

RNN层具有环路,通过环路数据可以在层内循环。向时序数据输入层中,相应的会输出

RNN层的循环的展开

由图可知,各个时刻的RNN层接收传给该层的输入和前一个时刻RNN层的输出,据此计算当前时刻RNN层的输出

RNN层有2个权重,分别是与输入运算的权重,和与前一时刻RNN层的输出

(也叫隐藏状态,隐状态)运算的权重。执行完乘积和求和运算之后使用tanh函数转换,其结果就是时刻t的输出

API使用

可使用torch.nn.RNN来初始化RNN层:

python 复制代码
rnn = torch.nn.RNN(input_size, hidden_size, num_layers)
# input_size:输入数据的特征数量
# hidden_size:隐藏状态的特征数量
# num_layers:隐藏层的层数,如果设置多个层,前一个隐藏层的输出作为下一个隐藏层的输入

调用时需要传入2个参数:

python 复制代码
output, hn = rnn(input, hx)
# input:输入数据[seq_len序列长度, batch_size批量大小, input_size]
# hx:初始隐状态[num_layers, batch_size, hidden_size]
# output:输出数据[seq_len, batch_size, hidden_size]
# hn:隐状态[num_layers, batch_size, hidden_size]

例:

python 复制代码
import torch

rnn = torch.nn.RNN(input_size=8, hidden_size=16, num_layers=2)
input = torch.rand(1, 3, 8)
hx = torch.randn(2, 3, 16)
output, hn = rnn(input=input, hx=hx)
print(output.shape)  # torch.Size([1, 3, 16])
print(hn.shape)  # torch.Size([2, 3, 16])

4.代码案例:古诗生成

数据预处理

数据在.txt文件中,每行为一首诗。

首先将每个字作为一个词构建词表。并将原文转换为索引序列。

python 复制代码
import re
import torch
from torch import nn, optim
from torch.utils.data import Dataset, DataLoader

# 数据预处理
def process_poems(file_path):
    poems = []  # 保存处理后的诗
    char_set = set()  # 保存所有不重复的字
    with open(file_path, "r", encoding="utf-8") as f:
        for line in f:
            # 逐行处理
            line = re.sub(r"[,。、?!:]", "", line).strip()  # 去掉标点符号与两侧空白
            # 按字分割并去重
            char_set.update(list(line))
            # 按字保存诗
            poems.append(list(line))

    # 构建词表
    vocab = list(char_set) + ["<UNK>"]
    # 创建词到索引的映射
    word2idx = {word: idx for idx, word in enumerate(vocab)}

    # 将诗转换为索引序列
    sequences = []
    for poem in poems:
        seq = [word2idx.get(word) for word in poem]
        sequences.append(seq)
    return sequences, word2idx, vocab

sequences, word2idx, vocab = process_poems("data/poems.txt")

自定义Dataset

构建训练模型的数据集,将一个序列作为输入,另一个序列作为目标。

python 复制代码
# 自定义Dataset
class PoetryDataset(Dataset):
    def __init__(self, sequences, seq_len):
        self.seq_len = seq_len
        self.data = []
        for seq in sequences:
            for i in range(0, len(seq) - self.seq_len):
                self.data.append((seq[i : i + self.seq_len], seq[i + 1 : i + 1 + self.seq_len]))

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        x = torch.LongTensor(self.data[idx][0])
        y = torch.LongTensor(self.data[idx][1])
        return x, y

dataset = PoetryDataset(sequences, 24)

搭建模型

词嵌入层→RNN→全连接层

python 复制代码
# 搭建模型
class PoetryRNN(nn.Module):
    def __init__(self, vocab_size, embedding_dim=128, hidden_size=256, num_layers=1):
        super().__init__()
        self.embed = nn.Embedding(num_embeddings=vocab_size, embedding_dim=embedding_dim)
        self.rnn = nn.RNN(input_size=embedding_dim, hidden_size=hidden_size, num_layers=num_layers)
        self.linear = nn.Linear(in_features=hidden_size, out_features=vocab_size)

    def forward(self, input, hx=None):
        embed = self.embed(input)
        output, hidden = self.rnn(embed, hx)
        output = self.linear(output)
        return output, hidden

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = PoetryRNN(len(vocab), 256, 512, 2).to(device)

模型训练

使用交叉熵损失函数,Adam优化方法

python 复制代码
# 模型训练
def train(model, dataset, lr, epoch_num, batch_size, device):
    model.train()  # 设置为训练模式
    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
    loss = nn.CrossEntropyLoss()  # 损失函数
    optimizer = optim.Adam(model.parameters(), lr=lr)  # 优化器
    for epoch in range(epoch_num):
        loss_accumulate = 0  # 累加损失
        for batch_count, (x, y) in enumerate(dataloader):
            # 前向传播
            x, y = x.to(device), y.to(device)
            output, _ = model(x)
            # 反向传播
            loss_value = loss(output.transpose(1, 2), y)
            optimizer.zero_grad()
            loss_value.backward()
            optimizer.step()
            # 累加损失
            loss_accumulate += loss_value.item()
            print(f"\repoch:{epoch:0>2}[{'='*(int((batch_count+1) / len(dataloader) * 50)):<50}]", end="")
        print(f" loss:{loss_accumulate/len(dataloader):.6f}")

train(model=model, dataset=dataset, lr=1e-3, epoch_num=20, batch_size=32, device=device)

测试

输入一个起始词让模型开始生成

python 复制代码
# 生成
def generate_poem(model, word2idx, vocab, start_token, line_num=4, line_length=7):
    model.eval()  # 设置为预测模式
    poem = []  # 记录生成结果
    current_line_length = line_length  # 当前句的剩余长度
    start_token = word2idx.get(start_token, word2idx["<UNK>"])  # 起始token
    # 如果起始token在词典中,添加到结果中
    if start_token != word2idx["<UNK>"]:
        poem.append(vocab[start_token])
        current_line_length -= 1
    input = torch.LongTensor([[start_token]]).to(device)  # 输入
    hidden = None  # 初始化隐状态
    with torch.no_grad():  # 关闭梯度计算
        for _ in range(line_num):  # 生成的行数
            for interpunction in [",", "。\n"]:  # 每行两句
                while current_line_length > 0:  # 每句诗line_length个字
                    output, hidden = model(input, hidden)
                    prob = torch.softmax(output[0, 0], dim=-1)  # 计算概率
                    next_token = torch.multinomial(prob, 1)  # 从概率分布中随机采样
                    poem.append(vocab[next_token.item()])  # 将采样结果添加到结果中
                    input = next_token.unsqueeze(0)
                    current_line_length -= 1
                current_line_length = line_length
                poem.append(interpunction)  # 每句结尾添加标点符号
    return "".join(poem)  # 将列表转换为字符串

print(generate_poem(model, word2idx, vocab, start_token="一", line_num=4, line_length=7))
相关推荐
LaughingZhu2 小时前
Product Hunt 每日热榜 | 2026-03-21
人工智能·经验分享·深度学习·神经网络·产品运营
阿_旭2 小时前
基于YOLO26深度学习的【桃子成熟度检测与分割系统】【python源码+Pyqt5界面+数据集+训练代码】图像分割、人工智能
人工智能·python·深度学习·桃子成熟度检测
剑穗挂着新流苏3122 小时前
109_神经网络的决策层:线性层(Linear Layer)与数据展平详解
人工智能·pytorch·深度学习
逄逄不是胖胖2 小时前
《动手学深度学习》-69BERT预训练实现
人工智能·深度学习
FakeOccupational2 小时前
【电路笔记 STM32】Cortex-M7 内核上的数据缓存结构图 + MPU内存保护单元 + Cache基本操作 + Cache&DMA 时序图
笔记·stm32·缓存
C羊驼3 小时前
C语言学习笔记(十一):数据在内存中的存储
c语言·经验分享·笔记·学习
剑穗挂着新流苏3123 小时前
111_神经网络的指路明灯:损失函数与反向传播深度解析
人工智能·深度学习·神经网络
忧郁的橙子.3 小时前
08-QLora微调&GGUF模型转换、Qwen打包部署 ollama 运行
人工智能·深度学习·机器学习·qlora·打包部署 ollama
承渊政道3 小时前
【优选算法】(实战体验滑动窗口的奇妙之旅)
c语言·c++·笔记·学习·算法·leetcode·visual studio