PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

PyTorch深度学习实践概论笔记12-循环神经网络基础篇中简单介绍了RNN,接下来13讲,我们介绍一个关于神经网络的应用:实现一个循环神经网络的分类器。

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

1 RNN Classifier – Name Classification

用RNN做一个分类器。

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

先看看这个问题。现在有一个数据集,数据集里有人名和对应的国家,我们需要训练一个模型,输入一个新的名字,模型能预测出这个名字是基于哪种语言的(18种不同的语言,18分类)。

2 Revision

回顾上一讲。

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

在自然语言处理中,通常的方式:①先把词或字变成一个one-hot向量,one-hot向量维度高,而且过于稀疏,所以一般来说先通过嵌入层(Embed)把one-hot向量转化成低维的稠密向量,②然后经过RNN,隐层的输出不一定和最终要求的目标一致,所以要用一个 线性层把输出映射成和我们的要求一致。

3 Our Model

我们的需求是输出名字所属的语言分类,我们对O1-O5这些输出是没有要求的,即不需要对所有的隐层输出做线性变换,为了解决这个问题,我们可以把网络简化,如下图所示。

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

输入向量经过嵌入层之后,输入到RNN,输出最终的隐层状态,最终的隐层状态经过一个线性层,我们分成18个类别,就可以实现名字分类的任务了。

这一讲使用的模型如下:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

看一下数据,只有两列:Name和Country。注意输入的每一个名字都是一个序列(x1,x2,…xN),而且序列的长短不一样。

4 Implementation

首先看一下主要的循环是怎么写的。

4.1 Implementation – Main Cycle

代码如下:

if __name__ == '__main__':
    #N_CHARS:字符数量(输入的是英文字母,每一个字符都要转变成one-hot向量,这是自己设置的字母表的大小)
    #HIDDEN_SIZE:隐层数量(GRU输出的隐层的维度)
    #N_COUNTRY:一共有多少个分类
    #N_LAYER:设置用几层的GRU
    #实例化分类模型

    classifier = RNNClassifier(N_CHARS, HIDDEN_SIZE, N_COUNTRY, N_LAYER)

    #判断是否使用GPU训练模型
    if USE_GPU:

        device = torch.device("cuda:0")

        classifier.to(device)

    #构造损失函数和优化器
    criterion = torch.nn.CrossEntropyLoss()

    optimizer = torch.optim.Adam(classifier.parameters(), lr=0.001)

    start = time.time()  #计算一下时间
    print("Training for %d epochs..." % N_EPOCHS)
    acc_list = []
    #每一次epoch做一次训练和测试
    for epoch in range(1, N_EPOCHS + 1):
        # Train cycle
        trainModel()
        acc = testModel()
        #测试结果添加到acc_list列表,可以绘图等
        acc_list.append(acc)

计算运行时间的函数time_since()的代码如下:

def time_since(since):
    s = time.time() - since
    m = math.floor(s / 60)
    s -= m * 60
    return '%dm %ds' % (m, s)

记录测试的准确率,代码如下:

import matplotlib.pyplot as plt
import numpy as np

epoch = np.arange(1, len(acc_list) + 1, 1)

acc_list = np.array(acc_list)

plt.plot(epoch, acc_list)

plt.xlabel('Epoch')

plt.ylabel('Accuracy')

plt.grid()

plt.show()

回忆训练的基本过程(四步):

准备数据–定义模型–构造损失函数和优化器–训练过程

4.2 Implementation – Preparing Data

首先看一下数据上的准备。

4.2.1 Name的处理

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

①拿到的是字符串,先转变成序列,转成字符列表,列表里面的每一个数就是名字里面的每一个字符。

②接下来做词典,可以用 ASCII表,ASCII表是128个字符,我们把 字典长度设置成128,求每一个字符对应的ASCII值,拼成我们想要的序列。上图中的最右表中每一个数并不是一个数字,而是一个one-hot向量。例如77,就是一个128维的向量,第77个数的值为1,其他的值都是0。对于Embedding(嵌入层)来说,只要告诉嵌入层第几个维度是1就行了,所以只需要把ASCII值放在这就行了。

序列长短不一应该怎么解决

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

③如上图左,每一行是一个序列,我们解决序列长短不一的方法是 padding(因为张量必须保证所有的数据都添满,不然就不是张量),如上图右侧,在做一个batch的时候,我们看这一个batch里面哪一个字符串的长度最长,然后把其他字符串填充成和它一样的长度,这样就能保证可以构成一个张量, 因为每个维度的数量不一样是没办法构成张量的

4.2.2 Country的处理

我们需要把各个分类(国家)转成一个 分类索引(index),分类索引必须是0开始的 整数,不能直接用字符串作为我们的分类标签。

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

整个数据集一共18个国家,做成一个词典。

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

代码如下:

import gzip
import csv

class NameDataset(Dataset):

    def __init__(self, is_train_set=True):
        #从gz当中读取数据
        filename = 'data/names_train.csv.gz' if is_train_set else 'data/names_test.csv.gz'

        with gzip.open(filename, 'rt') as f:
            reader = csv.reader(f) #每一行都是(name,country)的元组
            rows = list(reader)
        #将names和countries保存在list中
        self.names = [row[0] for row in rows]
        self.len = len(self.names)
        self.countries = [row[1] for row in rows]
        #将countries和它的index保存在list和dictionary中
        self.country_list = list(sorted(set(self.countries)))
        self.country_dict = self.getCountryDict()
        self.country_num = len(self.country_list)#国家的个数

    #提供索引访问
    def __getitem__(self, index):
        return self.names[index], self.country_dict[self.countries[index]]

   #返回dataset的长度
    def __len__(self):
        return self.len

    #将list转化成dictionary
    def getCountryDict(self):
        country_dict = dict()
        for idx, country_name in enumerate(self.country_list, 0):
            country_dict[country_name] = idx
        return country_dict

    #给定index返回country,方便展示
    def idx2country(self, index):
        return self.country_list[index]

    #返回country的数目
    def getCountriesNum(self):
        return self.country_num

#Prepare Dataset and DataLoader
Parameters
HIDDEN_SIZE = 100
BATCH_SIZE = 256
N_LAYER = 2
N_EPOCHS = 100
N_CHARS = 128
USE_GPU = False
#训练数据
trainset = NameDataset(is_train_set=True)
trainloader = DataLoader(trainset, batch_size=BATCH_SIZE, shuffle=True)
#测试数据
testset = NameDataset(is_train_set=False)
testloader = DataLoader(testset, batch_size=BATCH_SIZE, shuffle=False)
#N_COUNTRY is the output size of our model
N_COUNTRY = trainset.getCountriesNum()

注意上述代码读取数据集为什么不用Numpy?因为读取数据集有很多种方式,如果是pickle/HDFS/HD5类型的数据,要就要用相应的包。

根据人名找到他的国家对应的index:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

4.3 Implementation – Model Design

4.3.1 Implementation – Model Design

先看看和GRU相关的参数:hidden_size和n_layers。

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

注意Embedding层的输入、输出维度:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

还有GRU的输入、输出维度:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

这里n_directions设置单向还是双向的:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

代码如下:

class RNNClassifier(torch.nn.Module):
    def __init__(self, input_size, hidden_size, output_size, n_layers=1, bidirectional=True):
        super(RNNClassifier, self).__init__()
        #parameters of GRU layer
        self.hidden_size = hidden_size
        self.n_layers = n_layers
        #What is the Bi-Direction RNN/LSTM/GRU?

        self.n_directions = 2 if bidirectional else 1

        #The input of Embedding Layer with shape:𝑠𝑒𝑞𝐿𝑒𝑛, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒
        #The output of Embedding Layer with shape:𝑠𝑒𝑞𝐿𝑒𝑛, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒
        self.embedding = torch.nn.Embedding(input_size, hidden_size)
        #The inputs of GRU Layer with shape:
        #𝑖𝑛𝑝𝑢𝑡: 𝑠𝑒𝑞𝐿𝑒𝑛, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒
        #ℎ𝑖𝑑𝑑𝑒𝑛: 𝑛𝐿𝑎𝑦𝑒𝑟𝑠 ∗ 𝑛𝐷𝑖𝑟𝑒𝑐𝑡𝑖𝑜𝑛𝑠, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒
        #The outputs of GRU Layer with shape:
        #𝑜𝑢𝑡𝑝𝑢𝑡: 𝑠𝑒𝑞𝐿𝑒𝑛, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒 ∗ 𝑛𝐷𝑖𝑟𝑒𝑐𝑡𝑖𝑜𝑛𝑠
        #ℎ𝑖𝑑𝑑𝑒𝑛: 𝑛𝐿𝑎𝑦𝑒𝑟𝑠 ∗ 𝑛𝐷𝑖𝑟𝑒𝑐𝑡𝑖𝑜𝑛𝑠, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒
        self.gru = torch.nn.GRU(hidden_size, hidden_size, n_layers,bidirectional=bidirectional)
        self.fc = torch.nn.Linear(hidden_size * self.n_directions, output_size)

    def _init_hidden(self, batch_size):
        hidden = torch.zeros(self.n_layers * self.n_directions,batch_size, self.hidden_size)
        return create_tensor(hidden)

下面具体看看什么是双向的神经网络吧。

4.3.2 Implementation – Bi-direction RNN/LSTM/GRU

序列的forward方向流程图(注意forward不是正向传播):

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

上图的情况x_(N-1)只包含过去的信息,但是有时候在NLP中也需要考虑未来的信息。

序列的backward方向流程图(注意backward不是反向传播):

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

接着

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

最后

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

这样的神经网络称双向的神经网络,backward最后得到h_N^b,输出是上面的h0、h1…hN。

最后输出的hidden只有两个,公式如下:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

4.3.3 Implementation – Model Design

再接着看,这个地方就需要乘上n_directions:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

然后再看一下forward过程

首先我们做的是矩阵转置:input = input.t()。接着保存batch_size的值,之后用来构造h0。

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

然后嵌入层:embedding = self.embedding(input),维度就变成下图的维度。

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

注意看下图,如果之后的都padding为0之后没有必要参与运算,pytorch提供了下面的功能来加快运算。

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

利用这行代码gru_input = pack_padded_sequence(embedding, seq_lengths),输入输出如下:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

源码:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

如果还是不太清楚,接着往下看:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

直接把左侧非0的列排到右侧,把填充的0去掉,GRU可以处理长短不一的数据序列(数据长度保存),但是不能使用打包函数,想要打包的话,必须按照长度降序排列。降序排列如下图:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

排好之后,重新计算:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

这样做之后工作效率更高了。

整体代码如下:

class RNNClassifier(torch.nn.Module):

    def __init__(self, input_size, hidden_size, output_size, n_layers=1, bidirectional=True):

        super(RNNClassifier, self).__init__()

        self.hidden_size = hidden_size

        self.n_layers = n_layers

        self.n_directions = 2 if bidirectional else 1

        self.embedding = torch.nn.Embedding(input_size, hidden_size)

        #The inputs of GRU Layer with shape:

        #𝑖𝑛𝑝𝑢𝑡: 𝑠𝑒𝑞𝐿𝑒𝑛, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒

        #ℎ𝑖𝑑𝑑𝑒𝑛: 𝑛𝐿𝑎𝑦𝑒𝑟𝑠 ∗ 𝑛𝐷𝑖𝑟𝑒𝑐𝑡𝑖𝑜𝑛𝑠, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒

        #The outputs of GRU Layer with shape:

        #𝑜𝑢𝑡𝑝𝑢𝑡: 𝑠𝑒𝑞𝐿𝑒𝑛, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒 ∗ 𝑛𝐷𝑖𝑟𝑒𝑐𝑡𝑖𝑜𝑛𝑠

        #ℎ𝑖𝑑𝑑𝑒𝑛: 𝑛𝐿𝑎𝑦𝑒𝑟𝑠 ∗ 𝑛𝐷𝑖𝑟𝑒𝑐𝑡𝑖𝑜𝑛𝑠, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒
        self.gru = torch.nn.GRU(hidden_size, hidden_size, n_layers,bidirectional=bidirectional)

        self.fc = torch.nn.Linear(hidden_size * self.n_directions, output_size)

    def _init_hidden(self, batch_size):

        hidden = torch.zeros(self.n_layers * self.n_directions,batch_size, self.hidden_size)

        return create_tensor(hidden)

    def forward(self, input, seq_lengths):

        # input shape : B x S -> S x B

        input = input.t()
        #Save batch-size for make initial hidden

        batch_size = input.size(1)

        #Initial hidden with shape:

        #(𝑛𝐿𝑎𝑦𝑒𝑟 ∗ 𝑛𝐷𝑖𝑟𝑒𝑐𝑡𝑖𝑜𝑛𝑠, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒)
        hidden = self._init_hidden(batch_size)
        #Result of embedding with shape:

        #(𝑠𝑒𝑞𝐿𝑒𝑛, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒)

        embedding = self.embedding(input)

        # pack them up
        #The first parameter with shape:

        #(𝑠𝑒𝑞𝐿𝑒𝑛, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒)

        #The second parameter is a tensor, which is a list of sequence length of each batch element.

        #Result of embedding with shape:(𝑠𝑒𝑞𝐿𝑒𝑛, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒)

        #It returns a PackedSquence object.

        gru_input = pack_padded_sequence(embedding, seq_lengths)

        #The output is a PackedSequence object, actually it is a tuple.

        #the shape of hidden, which we concerned, with shape:

        #(𝑛𝐿𝑎𝑦𝑒𝑟𝑠 ∗ 𝑛𝐷𝑖𝑟𝑒𝑐𝑡𝑖𝑜𝑛, 𝑏𝑎𝑡𝑐ℎ𝑆𝑖𝑧𝑒, ℎ𝑖𝑑𝑑𝑒𝑛𝑆𝑖𝑧𝑒)
        output, hidden = self.gru(gru_input, hidden)

        if self.n_directions == 2:

            hidden_cat = torch.cat([hidden[-1], hidden[-2]], dim=1)

        else:

            hidden_cat = hidden[-1]

        fc_output = self.fc(hidden_cat)

        return fc_output

以上内容是模型相应的设定。

4.4 Implementation – Convert name to tensor

接下来看看name转化成tensor的过程。

转化过程如下:

①name转换成一个一个字符,转化成对应的ASCII值。

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

②填充:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

③转置:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

④降序排列:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

看看make_tensors函数。

把每个字符变成列表。代码如下:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

4.5 Implementation – One Epoch Training

训练:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

代码如下:

def create_tensor(tensor):
    if USE_GPU:
        device = torch.device("cuda:0")
        tensor = tensor.to(device)
    return tensor

def trainModel():
    total_loss = 0
    for i, (names, countries) in enumerate(trainloader, 1):
        inputs, seq_lengths, target = make_tensors(names, countries)
        output = classifier(inputs, seq_lengths)
        loss = criterion(output, target)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
        if i % 10 == 0:
        print(f'[{time_since(start)}] Epoch {epoch} ', end='')
        print(f'[{i * len(inputs)}/{len(trainset)}] ', end='')
        print(f'loss={total_loss / (i * len(inputs))}')
    return total_loss

4.6 Implementation – Testing

测试代码如下:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类
def testModel():
    correct = 0
    total = len(testset)
    print("evaluating trained model ...")
    with torch.no_grad():
        for i, (names, countries) in enumerate(testloader, 1):
            inputs, seq_lengths, target = make_tensors(names, countries)
            output = classifier(inputs, seq_lengths)
            pred = output.max(dim=1, keepdim=True)[1]
            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

输出结果图如下:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

5 Exercise 13-1 Sentiment Analysis on Movie Reviews

对电影影评做情感分析。

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

ref:https://www.kaggle.com/c/sentiment-analysis-on-movie-reviews/data

数据集如下:

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

练习之后会解答。

说明:记录学习笔记,如果错误欢迎指正!写文章不易,转载请联系我。

Original: https://blog.csdn.net/csdn_xmj/article/details/122396373
Author: 双木的木
Title: PyTorch深度学习实践概论笔记13-循环神经网络高级篇-分类

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/665932/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球