昇思25天学习打卡营第10天|NLP-RNN实现情感分类

打卡

目录

打卡

任务说明

流程

数据准备与加载

加载预训练词向量(分词)

数据集预处理

模型构建

Embedding

RNN(循环神经网络) + LSTM

全连接层

损失函数与优化器

训练逻辑

评估指标和逻辑

模型训练与保存

模型加载与测试

自定义输入测试

代码


任务说明

使用MindSpore实现一个基于RNN网络的情感分类模型

流程

数据准备与加载

1、从 https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/aclImdb_v1.tar.gz 下载数据集。注意,可用tqdm库对下载百分比进行可视化、用IO的方式可安全地下载临时文件,而后保存至指定的路径并返回。如下,是下载的数据集展示。

2、将IMDB数据集加载至内存并构造为迭代对象后,使用 mindspore.dataset 提供的Generatordataset 接口加载数据集迭代对象,并进行下一步的数据处理,例子如下,其中 IMDBData 类是 IMDB 数据集加载器,imdb_train 是构建的一个 Generatordataset 对象。

import mindspore.dataset as ds

def load_imdb(imdb_path):
    imdb_train = ds.GeneratorDataset(
            IMDBData(imdb_path, "train"), 
            column_names=["text", "label"], 
            shuffle=True, 
            num_samples=10000)
    imdb_test = ds.GeneratorDataset(
            IMDBData(imdb_path, "test"), 
            column_names=["text", "label"], 
            shuffle=False)
    return imdb_train, imdb_test

imdb_train, imdb_test = load_imdb(imdb_path) 

加载预训练词向量(分词)

Glove( Global Vectors for Word Representation ) 词向量作为Embedding,是一种无监督学习算法。从 'https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/glove.6B.zip' 下载数据集。如下图所示。


预训练词向量是对输入单词的数值化表示,通过nn.Embedding层,采用查表的方式,输入单词对应词表中的index,获得对应的表达向量。 

由于数据集中可能存在词表没有覆盖的单词,因此需要加入<unk>标记符;同时由于输入长度的不一致,在打包为一个batch时需要将短的文本进行填充,因此需要加入<pad>标记符。 完成后的词表长度为原词表长度+2。mindspore.dataset.text.Vocab 用于创建用于训练NLP模型的Vocab,Vocab是数据集中可能出现的所有Token的集合,保存了各Token与其ID之间的映射关系,其中的函数 from_list(word_listspecial_tokens=Nonespecial_first=True) 从给定Token列表创建Vocab, special_tokens 表示追加到Vocab中的Token列表;tokens_to_ids(tokens) 查找指定Token对应的ID。

示例代码如下,根据输出,对应的词表大小 400002 ,向量长度为100。

import zipfile
import numpy as np

def load_glove(glove_path):
    glove_100d_path = os.path.join(cache_dir, 'glove.6B.100d.txt')
    if not os.path.exists(glove_100d_path):
        glove_zip = zipfile.ZipFile(glove_path)
        glove_zip.extractall(cache_dir)

    embeddings = []
    tokens = []
    with open(glove_100d_path, encoding='utf-8') as gf:
        for glove in gf:
            word, embedding = glove.split(maxsplit=1)
            tokens.append(word)
            embeddings.append(np.fromstring(embedding, dtype=np.float32, sep=' '))
    # 添加 <unk>, <pad> 两个特殊占位符对应的embedding
    embeddings.append(np.random.rand(100))
    embeddings.append(np.zeros((100,), np.float32))

    vocab = ds.text.Vocab.from_list(tokens, special_tokens=["<unk>", "<pad>"], special_first=False)
    embeddings = np.array(embeddings).astype(np.float32)
    return vocab, embeddings

glove_path = download('glove.6B.zip', 'https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/glove.6B.zip')
vocab, embeddings = load_glove(glove_path)  # 

print(len(vocab.vocab()))  # 400002
print(np.shape(embeddings))  ## (400002, 100) 比原始文件多两行

idx = vocab.tokens_to_ids('the')
embedding = embeddings[idx]
print(f"idx={idx}, embedding={embedding}")

代码运行结果例子。

数据集预处理

  • 通过Vocab将所有的 Token 处理为index id。
  • 将文本序列统一长度,不足的使用<pad>补齐,超出的进行截断。

  1. 首先针对token 到 index id 的查表操作,使用 mindspore.dataset.text.Lookup(vocab, unknown_token=None, data_type=mstype.int32) 接口,将前文构造的词表加载,并指定 unknown_token 
  2. 其次为文本序列统一长度操作,使用 dataset.transforms.PadEnd(pad_shape, pad_value=None) 接口,此接口定义最大长度和补齐值(pad_value),这里取最大长度为500,填充值对应词表中 <pad> 的 index id。
  3. 由于后续模型训练的需要,同时要将label数据转为float32格式。
  4. 接着,手动将IMDB数据集分割为训练和验证两部分,比例取0.7, 0.3。
  5. 最后,通过 batch(batch_size, drop_remainder=False, num_parallel_workers=None, **kwargs) 接口指定数据集的 batch 大小,,并设置是否丢弃无法被batch size整除的剩余数据。

代码例子

import mindspore as ms

# 根据词表,将分词标记(token)映射到其索引值(id)。
lookup_op = ds.text.Lookup(
             vocab,  # 词表对象,用于存储分词和索引的映射。
             unknown_token='<unk>'  # 备用词汇,用于要查找的单词不在词汇表时进行替换。 如果单词不在词汇表中,则查找结果将替换为 unknown_token 的值。 如果单词不在词汇表中,且未指定 unknown_token ,将抛出运行时错误。默认值: None ,不指定该参数。
            )

# 对输入Tensor进行填充,要求 pad_shape 与输入Tensor的维度保持一致。
pad_op = ds.transforms.PadEnd(
             [500],  ## 指定填充的shape。设置为较小的维数时该维度的元素将被截断。
             pad_value=vocab.tokens_to_ids('<pad>') ## 用于填充的值。默认 None ,表示不指定填充值。 当指定为默认值,输入Tensor为数值型时默认填充 0 ,输入Tensor为字符型时填充空字符串。
            )  

type_cast_op = ds.transforms.TypeCast(ms.float32)


imdb_train = imdb_train.map(operations=[lookup_op, pad_op], input_columns=['text'])
imdb_train = imdb_train.map(operations=[type_cast_op], input_columns=['label'])

imdb_test = imdb_test.map(operations=[lookup_op, pad_op], input_columns=['text'])
imdb_test = imdb_test.map(operations=[type_cast_op], input_columns=['label'])


imdb_train, imdb_valid = imdb_train.split([0.7, 0.3])
imdb_train = imdb_train.batch(64, drop_remainder=True)
imdb_valid = imdb_valid.batch(64, drop_remainder=True)

模型构建

  • 结构:nn.Embedding -> nn.RNN -> nn.Dense
  • 其中,nn.Embedding层加载Glove词向量,RNN 层做特征提取,nn.Dense 层将特征转化为与分类数量相同的size,用于后续进行模型优化训练。
  • 这里使用能够一定程度规避RNN梯度消失问题的变种LSTM(Long short-term memory)做特征提取层。

Embedding

mindspore.nn.Embedding(vocab_size, embedding_size, use_one_hot=False, embedding_table='normal', dtype=mstype.float32, padding_idx=None)

用于存储词向量并使用索引进行检索,根据输入Tensor中的id,从 embedding_table 中查询对应的 embedding 向量。当输入为id组成的序列时,输出为对应embedding向量构成的矩阵。当 use_one_hot 等于True时,x的类型必须是mindpore.int32。

  • vocab_size (int) - 词典的大小。如上文,对应的词表大小 400002 。

  • embedding_size (int) - 每个嵌入向量的大小。如上文,向量长度为100。

  • use_one_hot (bool) - 指定是否使用one-hot形式。默认值: False 。

  • embedding_table (Union[Tensor, str, Initializer, numbers.Number]) - embedding_table的初始化方法。当指定为字符串,字符串取值请参见类 mindspore.common.initializer 。默认值: "normal" 。

  • dtype (mindspore.dtype) - x的数据类型。默认值: mstype.float32 。

  • padding_idx (int, None) - 将 padding_idx 对应索引所输出的嵌入向量用零填充。默认值: None 。该功能已停用。

RNN(循环神经网络) + LSTM

循环神经网络(Recurrent Neural Network, RNN)是一类以序列(sequence)数据为输入,在序列的演进方向进行递归(recursion)且所有节点(循环单元)按链式连接的神经网络。

RNN的结构拆解:

 RNN单个Cell的结构简单,因此也造成了梯度消失(Gradient Vanishing)问题,具体表现为RNN网络在序列较长时,在序列尾部已经基本丢失了序列首部的信息。为了克服这一问题,LSTM(Long short-term memory)被提出,通过门控机制(Gating Mechanism)来控制信息流在每个循环步中的留存和丢弃。选择LSTM变种而不是经典的RNN做特征提取,来规避梯度消失问题,可以获得更好的模型效果。

mindspore.nn.LSTM(*args, **kwargs) 

长短期记忆(LSTM)网络,根据输入序列和给定的初始状态计算输出序列和最终状态。在LSTM模型中,有两条管道连接两个连续的Cell,一条是Cell状态管道,另一条是隐藏状态管道。将两个连续的时间节点表示为 t−1 和 t。指定在 t 时刻输入 $x_t$ , t-1 时刻的隐藏状态 $h_{t-1}$  和Cell状态 $c_{t-1}$

t 时刻的Cell状态 $c_{t}$ 和隐藏状态 $h_{t}$ 使用门控机制计算得到。

输入门  $i_t$ 计算出候选值。遗忘门  $f_t$决定是否让  $h_{t-1}$ 学到的信息通过或部分通过。

输出门  $o_t$ 决定哪些信息输出。

候选Cell状态 $ \tilde{c_{t}} $ 是用当前输入计算的。

最后,使用遗忘门、输入门、输出门计算得到当前时刻的Cell状态 $c_t$和隐藏状态 $h_t$

如下公式,𝜎 是sigmoid激活函数, ∗ 是乘积。 𝑊, 𝑏 是公式中输出和输入之间的可学习权重。例如, $W_{ix}$$b_{ix}$ 是用于从输入 𝑥 转换为 𝑖 的权重和偏置。

$i_t = \sigma (W_{ix}x_{t} + b_{ix} + W_{ih}h_{(t-1)} + b_{ih})$

$f_t = \sigma (W_{fx}x_{t} + b_{fx} + W_{fh}h_{(t-1)} + b_{fh})$

$ \tilde{c_{t}} = tanh(W_{cx}x_t + b_{cx} + W_{ch}h_{(t-1)} + b_{ch})$

$o_t = \sigma (W_{ox} x_{t} + b_{ox} + W_{oh} h_{(t-1)} + b_{oh}) $

$ c_{t} = f_t * c_{(t-1)} + i_t * \tilde{c_t}$

h_t = o_t * tanh(c_t)

MindSpore中的LSTM隐藏了整个循环神经网络在序列时间步(Time step)上的循环(同pyTorch),送入输入序列、初始状态,即可获得每个时间步的隐藏状态(hidden state)拼接而成的矩阵,以及最后一个时间步对应的隐状态。我们使用最后的一个时间步的隐藏状态作为输入句子的编码特征,送入下一层。LSTM 公式为:$h_{0:n}, (h_n, c_n) = LSTM(x_{0:n}, (h_0, c_0))$

全连接层

全连接层,即 nn.Dense (in_channels, out_channels, weight_init=None, bias_init=None, has_bias=True, activation=None, dtype=mstype.float32)将特征维度变换为二分类所需的维度1,经过Dense层后的输出即为模型预测结果。

其中公式为 outputs = activation(X * kernel + bias) ,activation 是激活函数,kernel 是权重矩阵,bias 是偏置向量。

模型构建的示例代码如下:

import math
import mindspore as ms
import mindspore.nn as nn
import mindspore.ops as ops
from mindspore.common.initializer import Uniform, HeUniform

class RNN(nn.Cell):
    def __init__(self, embeddings, 
                 hidden_dim, 
                 output_dim, 
                 n_layers,
                 bidirectional, 
                 pad_idx):
        super().__init__()
        vocab_size, embedding_dim = embeddings.shape
        self.embedding = nn.Embedding(
                vocab_size, 
                embedding_dim, 
                embedding_table=ms.Tensor(embeddings), 
                padding_idx=pad_idx)
        self.rnn = nn.LSTM(embedding_dim,
                           hidden_dim,
                           num_layers=n_layers,
                           bidirectional=bidirectional,
                           batch_first=True)
        weight_init = HeUniform(math.sqrt(5))
        bias_init = Uniform(1 / math.sqrt(hidden_dim * 2))
        self.fc = nn.Dense(hidden_dim * 2, 
                           output_dim, 
                           weight_init=weight_init, 
                           bias_init=bias_init)

    def construct(self, inputs):
        embedded = self.embedding(inputs)
        _, (hidden, _) = self.rnn(embedded)
        hidden = ops.concat((hidden[-2, :, :], hidden[-1, :, :]), axis=1)
        output = self.fc(hidden)
        return output

损失函数与优化器

针对本节情感分类问题的特性,即预测Positive或Negative的二分类问题,选择nn.BCEWithLogitsLoss(reduction='mean', weight=None, pos_weight=None) (二分类交叉熵损失函数)。

训练逻辑

一般训练逻辑分为一下步骤:

  1. 读取一个Batch的数据;
  2. 送入网络,进行正向计算和反向传播,更新权重;
  3. 返回loss。

grad_fn =  mindspore.value_and_grad(forward_fn, None, optimizer.parameters) 生成求导函数,用于计算给定函数的正向计算结果和梯度。

评估指标和逻辑

模型评估:使用模型的预测结果和测试集的正确标签进行对比,求出预测的准确率。

由于IMDB的情感分类为二分类问题,对预测值直接进行四舍五入即可获得分类标签(0或1),然后判断是否与正确标签相等即可。下面为二分类准确率计算函数实现:

def binary_accuracy(preds, y):
    """
    计算每个batch的准确率
    """

    # 对预测值进行四舍五入
    rounded_preds = np.around(ops.sigmoid(preds).asnumpy())
    correct = (rounded_preds == y).astype(np.float32)
    acc = correct.sum() / len(correct)
    return acc

模型评估逻辑设计步骤:

  1. 读取一个Batch的数据;
  2. 送入网络,进行正向计算,获得预测结果;
  3. 计算准确率。
def evaluate(model, test_dataset, criterion, epoch=0):
    total = test_dataset.get_dataset_size()
    epoch_loss = 0
    epoch_acc = 0
    step_total = 0
    ## 在进行evaluate前,通过model.set_train(False)将模型置为评估状态,此时Dropout不生效。
    model.set_train(False)

    with tqdm(total=total) as t:
        # 使用tqdm进行loss和过程的可视化。
        t.set_description('Epoch %i' % epoch)
        for i in test_dataset.create_tuple_iterator():
            ## 进行evaluate时,使用的模型是不包含损失函数和优化器的网络主体
            predictions = model(i[0])
            loss = criterion(predictions, i[1])
            epoch_loss += loss.asnumpy()

            acc = binary_accuracy(predictions, i[1])
            epoch_acc += acc

            step_total += 1
            t.set_postfix(loss=epoch_loss/step_total, acc=epoch_acc/step_total)
            t.update(1)

    return epoch_loss / total

模型训练与保存

模型训练,设置5轮。同时维护一个用于保存最优模型的变量best_valid_loss,根据每一轮评估的loss值,取loss值最小的轮次,将模型进行保存。为节省用例运行时长,此处num_epochs设置为3 。

num_epochs = 3
best_valid_loss = float('inf')
ckpt_file_name = os.path.join(cache_dir, 'sentiment-analysis.ckpt')

for epoch in range(num_epochs):
    train_one_epoch(model, imdb_train, epoch)
    valid_loss = evaluate(model, imdb_valid, loss_fn, epoch)

    if valid_loss < best_valid_loss:
        best_valid_loss = valid_loss
        ms.save_checkpoint(model, ckpt_file_name)

模型加载与测试

加载已保存的最优模型(即checkpoint),供后续测试使用。

直接使用MindSpore提供的Checkpoint加载和网络权重加载接口:1.将保存的模型Checkpoint加载到内存中,2.将Checkpoint加载至模型。
param_dict = ms.load_checkpoint(ckpt_file_name)
ms.load_param_into_net(model, param_dict)


## 对测试集打batch,然后使用evaluate方法进行评估,得到模型在测试集上的效果。
imdb_test = imdb_test.batch(64)
evaluate(model, imdb_test, loss_fn)

如下测试集效果,一般,有空了可以调一调训练参数。比如LSTM层数、学习率等。

自定义输入测试

输入一句评价,获得评价的情感分类.

score_map = {
    1: "Positive",
    0: "Negative"
}

def predict_sentiment(model, vocab, sentence):
    model.set_train(False)
    tokenized = sentence.lower().split()
    indexed = vocab.tokens_to_ids(tokenized)
    tensor = ms.Tensor(indexed, ms.int32)
    tensor = tensor.expand_dims(0)
    prediction = model(tensor)
    return score_map[int(np.round(ops.sigmoid(prediction).asnumpy()))]


predict_sentiment(model, vocab, "This film is terrible")
predict_sentiment(model, vocab, "This film is great")
predict_sentiment(model, vocab, "This movie is not good, but i like it")

代码

import os
import shutil
import requests
import tempfile
from tqdm import tqdm
from typing import IO
from pathlib import Path
import re
import six
import string
import tarfile
import mindspore.dataset as ds
import zipfile
import numpy as np
import mindspore as ms


# 指定保存路径为 `home_path/.mindspore_examples`
cache_dir = Path.home() / '.mindspore_examples'

def http_get(url: str, temp_file: IO):
    """使用requests库下载数据,并使用tqdm库进行流程可视化"""
    req = requests.get(url, stream=True)
    content_length = req.headers.get('Content-Length')
    total = int(content_length) if content_length is not None else None
    progress = tqdm(unit='B', total=total)
    for chunk in req.iter_content(chunk_size=1024):
        if chunk:
            progress.update(len(chunk))
            temp_file.write(chunk)
    progress.close()

def download(file_name: str, url: str):
    """下载数据并存为指定名称"""
    if not os.path.exists(cache_dir):
        os.makedirs(cache_dir)
    cache_path = os.path.join(cache_dir, file_name)
    cache_exist = os.path.exists(cache_path)
    if not cache_exist:
        with tempfile.NamedTemporaryFile() as temp_file:
            http_get(url, temp_file)
            temp_file.flush()
            temp_file.seek(0)
            with open(cache_path, 'wb') as cache_file:
                shutil.copyfileobj(temp_file, cache_file)
    return cache_path



class IMDBData():
    """IMDB数据集加载器

    加载IMDB数据集并处理为一个Python迭代对象。

    """
    
    # label_map是一个类属性,它是一个字典,将影评的情感标签映射为数值("pos"代表正面评价,映射为1;"neg"代表负面评价,映射为0)。
    label_map = {
        "pos": 1,
        "neg": 0
    }
    def __init__(self, path, mode="train"):
        # 构造函数接受两个参数:path(数据集的路径)和mode(模式,默认为"train",可能还有"test"等)。
        # 初始化实例变量mode和path,以及两个空列表docs和labels用于存储文档内容和对应的标签。然后调用_load方法加载正面和负面评价的数据。
        self.mode = mode
        self.path = path
        self.docs, self.labels = [], []

        self._load("pos")
        self._load("neg")

    def _load(self, label):
        pattern = re.compile(r"aclImdb/{}/{}/.*\.txt$".format(self.mode, label))
        # 将数据加载至内存
        with tarfile.open(self.path) as tarf:
            tf = tarf.next()
            while tf is not None:
                if bool(pattern.match(tf.name)):
                    # 对文本进行分词、去除标点和特殊字符、小写处理
                    self.docs.append(str(tarf.extractfile(tf).read().rstrip(six.b("\n\r"))
                                         .translate(None, six.b(string.punctuation)).lower()).split())
                    self.labels.append([self.label_map[label]])
                tf = tarf.next()

    def __getitem__(self, idx):
        return self.docs[idx], self.labels[idx]

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


def load_imdb(imdb_path):
    imdb_train = ds.GeneratorDataset(IMDBData(imdb_path, "train"), column_names=["text", "label"], shuffle=True, num_samples=10000)
    imdb_test = ds.GeneratorDataset(IMDBData(imdb_path, "test"), column_names=["text", "label"], shuffle=False)
    return imdb_train, imdb_test


def load_glove(glove_path):
    glove_100d_path = os.path.join(cache_dir, 'glove.6B.100d.txt')
    if not os.path.exists(glove_100d_path):
        glove_zip = zipfile.ZipFile(glove_path)
        glove_zip.extractall(cache_dir)

    embeddings = []
    tokens = []
    with open(glove_100d_path, encoding='utf-8') as gf:
        for glove in gf:
            word, embedding = glove.split(maxsplit=1)
            tokens.append(word)
            embeddings.append(np.fromstring(embedding, dtype=np.float32, sep=' '))
    # 添加 <unk>, <pad> 两个特殊占位符对应的embedding
    embeddings.append(np.random.rand(100))
    embeddings.append(np.zeros((100,), np.float32))

    vocab = ds.text.Vocab.from_list(tokens, special_tokens=["<unk>", "<pad>"], special_first=False)
    embeddings = np.array(embeddings).astype(np.float32)
    return vocab, embeddings



imdb_path = download('aclImdb_v1.tar.gz', 'https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/aclImdb_v1.tar.gz')
glove_path = download('glove.6B.zip', 'https://mindspore-website.obs.myhuaweicloud.com/notebook/datasets/glove.6B.zip')

imdb_train = IMDBData(imdb_path, 'train')
print(f"train dataset len: {len(imdb_train)}") ### pos + neg = 25000 === train/test
imdb_train, imdb_test = load_imdb(imdb_path)   ## imdb_train 是构建的一个 mindspore.dataset.Generatordataset 对象。

vocab, embeddings = load_glove(glove_path)
print(len(vocab.vocab()))   ## 400002, <mindspore.dataset.text.utils.Vocab object at 0xfffe9a9e38b0>
print(np.shape(embeddings))  ## (400002, 100)  


## 例子
idx = vocab.tokens_to_ids('the')
embedding = embeddings[idx]
print(f"idx={idx}, embedding={embedding}, len embedding={len(embedding)}")
idx = vocab.tokens_to_ids('it')
embedding = embeddings[idx]
print(f"idx={idx}, embedding={embedding}, len embedding={len(embedding)}")

# 数据预处理
lookup_op = ds.text.Lookup(vocab, unknown_token='<unk>')
pad_op = ds.transforms.PadEnd([500], pad_value=vocab.tokens_to_ids('<pad>'))
type_cast_op = ds.transforms.TypeCast(ms.float32)

imdb_train = imdb_train.map(operations=[lookup_op, pad_op], input_columns=['text'])
imdb_train = imdb_train.map(operations=[type_cast_op], input_columns=['label'])
imdb_test = imdb_test.map(operations=[lookup_op, pad_op], input_columns=['text'])
imdb_test = imdb_test.map(operations=[type_cast_op], input_columns=['label'])

imdb_train, imdb_valid = imdb_train.split([0.7, 0.3])
print(f"len imdb_train = ", len(imdb_train))
imdb_train = imdb_train.batch(64, drop_remainder=True)
imdb_valid = imdb_valid.batch(64, drop_remainder=True)
print(f"len imdb_train = ", len(imdb_train) * 64)


###############################################################################3
## model construct 
import math
import mindspore as ms
import mindspore.nn as nn
import mindspore.ops as ops
from mindspore.common.initializer import Uniform, HeUniform

class RNN(nn.Cell):
    def __init__(self, embeddings, hidden_dim, output_dim, n_layers,
                 bidirectional, pad_idx):
        super().__init__()
        vocab_size, embedding_dim = embeddings.shape
        self.embedding = nn.Embedding(vocab_size, embedding_dim, embedding_table=ms.Tensor(embeddings), padding_idx=pad_idx)
        self.rnn = nn.LSTM(embedding_dim, ## 100 
                           hidden_dim,  ## 
                           num_layers=n_layers,
                           bidirectional=bidirectional,
                           batch_first=True)
        weight_init = HeUniform(math.sqrt(5))
        bias_init = Uniform(1 / math.sqrt(hidden_dim * 2))
        self.fc = nn.Dense(hidden_dim * 2, output_dim, weight_init=weight_init, bias_init=bias_init)

    def construct(self, inputs):
        embedded = self.embedding(inputs)
        _, (hidden, _) = self.rnn(embedded)
        hidden = ops.concat((hidden[-2, :, :], hidden[-1, :, :]), axis=1)
        output = self.fc(hidden)
        return output
    

def forward_fn(data, label):
    logits = model(data)
    loss = loss_fn(logits, label)
    return loss


def train_step(data, label):
    loss, grads = grad_fn(data, label)
    optimizer(grads)
    return loss

def train_one_epoch(model, train_dataset, epoch=0):
    model.set_train()
    total = train_dataset.get_dataset_size()
    loss_total = 0
    step_total = 0
    with tqdm(total=total) as t:
        t.set_description('Epoch %i' % epoch)
        for i in train_dataset.create_tuple_iterator():
            loss = train_step(*i)
            loss_total += loss.asnumpy()
            step_total += 1
            t.set_postfix(loss=loss_total/step_total)
            t.update(1)
            
            
def binary_accuracy(preds, y):
    """
    二分类准确率计算函数
    计算每个batch的准确率
    """

    # 对预测值进行四舍五入
    rounded_preds = np.around(ops.sigmoid(preds).asnumpy())
    correct = (rounded_preds == y).astype(np.float32)
    acc = correct.sum() / len(correct)
    return acc
     
    
def evaluate(model, test_dataset, criterion, epoch=0):
    total = test_dataset.get_dataset_size()
    epoch_loss = 0
    epoch_acc = 0
    step_total = 0
    ## 在进行evaluate前,需要通过model.set_train(False)将模型置为评估状态,此时Dropout不生效。
    model.set_train(False)

    with tqdm(total=total) as t:
        # 使用tqdm进行loss和过程的可视化。
        t.set_description('Epoch %i' % epoch)
        for i in test_dataset.create_tuple_iterator():
            ## 进行evaluate时,使用的模型是不包含损失函数和优化器的网络主体
            predictions = model(i[0])
            loss = criterion(predictions, i[1])
            epoch_loss += loss.asnumpy()

            acc = binary_accuracy(predictions, i[1])
            epoch_acc += acc

            step_total += 1
            t.set_postfix(loss=epoch_loss/step_total, acc=epoch_acc/step_total)
            t.update(1)

    return epoch_loss / total
    
    
hidden_size = 256  ## 输入size
output_size = 1  ## 输出size
num_layers = 2  ## 层级
bidirectional = True
lr = 0.001  ## 学习率
pad_idx = vocab.tokens_to_ids('<pad>')  ## tokens_to_ids(tokens) 查找指定Token对应的ID。

model = RNN(embeddings, hidden_size, output_size, num_layers, bidirectional, pad_idx)
loss_fn = nn.BCEWithLogitsLoss(reduction='mean')
optimizer = nn.Adam(model.trainable_params(), learning_rate=lr)
print("model = ", model)
print("loss_fn = ", loss_fn)
print("optimizer = ", optimizer)

## 生成求导函数,用于计算给定函数的正向计算结果和梯度。
grad_fn = ms.value_and_grad(forward_fn, None, optimizer.parameters)


### 模型训练,设置5轮。同时维护一个用于保存最优模型的变量best_valid_loss,根据每一轮评估的loss值,取loss值最小的轮次,将模型进行保存。
num_epochs = 5
best_valid_loss = float('inf')
ckpt_file_name = os.path.join(cache_dir, 'sentiment-analysis.ckpt')

for epoch in range(num_epochs):
    train_one_epoch(model, imdb_train, epoch)
    valid_loss = evaluate(model, imdb_valid, loss_fn, epoch)

    if valid_loss < best_valid_loss:
        best_valid_loss = valid_loss
        ms.save_checkpoint(model, ckpt_file_name)
        

## 加载已保存的最优模型(即checkpoint),供后续测试使用。
param_dict = ms.load_checkpoint(ckpt_file_name)
ms.load_param_into_net(model, param_dict)

## 对测试集打batch,然后使用evaluate方法进行评估,得到模型在测试集上的效果。
imdb_test = imdb_test.batch(64)
evaluate(model, imdb_test, loss_fn)


################################自定义输入测试
## 输入一句评价,获得评价的情感分类.
score_map = {
    1: "Positive",
    0: "Negative"
}

def predict_sentiment(model, vocab, sentence):
    model.set_train(False)
    tokenized = sentence.lower().split()
    indexed = vocab.tokens_to_ids(tokenized)
    tensor = ms.Tensor(indexed, ms.int32)
    tensor = tensor.expand_dims(0)
    prediction = model(tensor)
    print(f"prediction={prediction}")
    return score_map[int(np.round(ops.sigmoid(prediction).asnumpy()))]


predict_sentiment(model, vocab, "This film is terrible")
predict_sentiment(model, vocab, "This film is great")
predict_sentiment(model, vocab, "This movie is not good, but i like it")

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/799838.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

周报(1)<仅供自己学习>

文章目录 一.pytorch学习1.配置GPU2.数据读取问题1&#xff08;已解决问题2&#xff08;已解决 3.卷积的学习 二.NeRF学习1.介绍部分问题1&#xff08;已解决 2.神经辐射场表示问题2&#xff08;已解决问题3&#xff08;已解决问题4&#xff08;已解决问题5&#xff1a;什么是视…

1-5岁幼儿胼胝体的表面形态测量

摘要 胼胝体(CC)是大脑中的一个大型白质纤维束&#xff0c;它参与各种认知、感觉和运动过程。尽管CC与多种发育和精神疾病有关&#xff0c;但关于这一结构的正常发育(特别是在幼儿阶段)还有很多待解开的谜团。虽然早期文献中报道了性别二态性&#xff0c;但这些研究的观察结果…

Armv8-R内存模型详解

目录 1.内存模型的必要性 2.Armv8-R内存模型分类 2.1 Normal memory 2.2 Device Memory 2.2.1 Gathering 2.2.2 Reordering 2.2.3 Early Write Acknowledgement 3.小结 大家好&#xff0c;今天是悲伤的肌肉。 在调研区域控制器芯片时&#xff0c;发现了S32Z、Stellar …

从Centos7升级到Rocky linux 9后,网卡连接显示‘Wired connection 1‘问题解决方法

问题描述 从Centos7升级到Rocky9后, 发现网卡eth0的IP不正确。通过nmcli查看网卡连接&#xff0c;找不到name为eth0的连接&#xff0c;只显示’Wired connection 1’ 查看/etc/NetworkManager/system-connections/&#xff0c;发现找不到网卡配置文件。 原因分析 centos7使…

git取消合并:--hard 或 --merge

第一步&#xff1a;查了git日志 git reflog如下&#xff0c;运行上述命令后&#xff0c;可以看见所有的提交哈希&#xff08;id&#xff09; 第二步 查看到上述所有的提交记录后&#xff0c;有如下方法去回退 方法1&#xff1a;--hard 确定上一次提交的哈希值 git reset…

RK3568笔记三十八:DS18B20驱动开发测试

若该文为原创文章&#xff0c;转载请注明原文出处。 DS18B20驱动参考的是讯为电子的单总线驱动第十四期 | 单总线_北京迅为的博客-CSDN博客 博客很详细&#xff0c;具体不描述。 只是记录测试下DS18B20读取温度。 一、介绍 流程基本和按键驱动差不多&#xff0c;主要功能是…

内存RAS技术介绍:内存故障预测

故障预测是内存可靠性、可用性和服务性&#xff08;RAS&#xff09;领域中的一个重要方面&#xff0c;旨在提前识别潜在的不可纠正错误&#xff08;UE&#xff09;&#xff0c;以防止系统崩溃或数据丢失。 4.1 错误日志记录与预测基础 错误一般通过Linux内核模块Mcelog记录到…

Matlab 判断直线上一点

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 判断一个点是否位于一直线上有很多方法,这里使用一种很有趣的坐标:Plucker线坐标,它的定义如下所示: 这个坐标有个很有趣的性质,我们可以使用Plucker坐标矢量构建一个Plucker矩阵: 则它与位于对应线上的齐次点…

鸿蒙语言基础类库:【@system.configuration (应用配置)】

应用配置 说明&#xff1a; 从API Version 7 开始&#xff0c;该接口不再维护&#xff0c;推荐使用新接口[ohos.i18n]和[ohos.intl]。本模块首批接口从API version 3开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import configurati…

uniapp启动图延时效果,启动图的配置

今天阐述uniapp开发中给启动图做延迟效果&#xff0c;不然启动图太快了&#xff0c;一闪就过去了&#xff1b; 一&#xff1a;修改配置文件&#xff1a;manifest.json "app-plus" : {"splashscreen" : {"alwaysShowBeforeRender" : false,"…

vue学习day09-自定义指令、插槽

29、自定义指令 &#xff08;1&#xff09;概念&#xff1a;自己定义的指令&#xff0c;可以封装一些dom操作&#xff0c;扩展额外的功能。 &#xff08;2&#xff09;分类&#xff1a; 1&#xff09;全局注册 2&#xff09;局部注册 3&#xff09;示例&#xff1a; 让表…

前端Vue组件化实践:自定义银行卡号格式化组件的探索与应用

在前端开发中&#xff0c;随着业务逻辑的复杂化和应用规模的扩大&#xff0c;传统的一体式开发方式逐渐显露出其局限性。任何微小的改动或新功能的增加都可能牵一发而动全身&#xff0c;导致整体逻辑的修改&#xff0c;进而增加了开发成本和维护难度。为了解决这一问题&#xf…

Java软件设计模式-单例设计模式

目录 1.软件设计模式的概念 2.设计模式分类 2.1 创建型模式 2.2 结构型模式 2.3 行为型模式 3.单例设计模式 3.1 单例模式的结构 3.2 单例模式的实现 3.2.1 饿汉式-方式1&#xff08;静态变量方式&#xff09; 3.2.2 懒汉式-方式1&#xff08;线程不安全&#xff09; 3.…

数据结构之初始二叉树(2)

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;数据结构&#xff08;Java版&#xff09; 二叉树的前置知识&#xff08;概念、性质、、遍历&#xff09; 通过上篇文章的学习&#xff0c;我们…

STM32第十八课:SPIFlash

目录 需求一、SPI概要二、SPI配置1.开时钟2.配置IO3.配置&使能SPI 三、FLash操作函数1.SPI发送数据2.FLASH写使能3.FLASH等待操作完成4.FLASH页写操作5.FLASH读操作6.FLASH扇区擦除 四、需求实现 需求 通过SPI控制FLash进行数据的保存和删除。 一、SPI概要 在我们使用UA…

oracle控制文件详解以及新增控制文件

文章目录 oracle控制文件1、 控制文件包含的主要信息如下&#xff1a;2、查看目前系统的控制文件信息&#xff0c;主要是查看相关的字典视图 oracle新增控制文件 oracle控制文件 控制文件是一个很小的二进制文件(10MB左右)&#xff0c;含有数据库结构信息&#xff0c;包括数据…

(leetcode学习)15. 三数之和

给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 示例 1&a…

浅谈全量微调和PEFT高效微调(LoRA)

浅谈全量微调和LoRA微调 全量微调Full Fine-Tuning 全量微调是指在预训练的大型模型基础上调整所有层和参数&#xff0c;‌使其适应特定任务的过程。‌这一过程使用较小的学习率和特定任务的数据进行&#xff0c;‌可以充分利用预训练模型的通用特征 高效微调 高效微调&…

PyQt5图形界面--基础笔记

from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QToolTip, QLabel, QLineEdit from PyQt5.QtGui import QIcon, QFont, QPixmap import sys https://www.bitbug.net/ 将图片转换为ico格式, 用来更改打包的文件图标 -F 只产生exe文件, 其他临时文件不产生 -…

深度学习论文: XFeat: Accelerated Features for Lightweight Image Matching

深度学习论文: XFeat: Accelerated Features for Lightweight Image Matching XFeat: Accelerated Features for Lightweight Image Matching PDF:https://arxiv.org/pdf/2404.19174 PyTorch: https://github.com/shanglianlm0525/PyTorch-Networks 1 概述 本文创新性地推出了…