• RNN项目实战——文本输入与预测


            在本篇博客文章中,我们将使用pytorch搭建RNN模型来生成文本。

    文本输入

            神经网络不像人类那样善于处理文本数据。因为绝大多数的NLP任务,文本数据都会先通过嵌入码(Embedding code),独热编码(One-hot encoding)等方式转为数字编码。在本篇文章中,我将使用one-hot编码标识我们的字符。因此,我将简要介绍一下它是什么。

            与大多数机器学习或深度学习项目一样,数据预处理通常会占用项目大部分时间。 在稍后的示例中,我们将把文本数据预处理为简单的表示形式——字符级别的One-hot encoding。

            这种编码形式基本上是给文本中的每个字符一个唯一的向量。 例如,如果我们的文本只包含单词“GOOD”,那么只有 3 个唯一字符,G,O,D,三个,因此我们的词汇量只有 3。我们将为每个唯一字符分配一个唯一向量,其中除了索引中的一项之外,所有项都为零。 这就是我们向模型表示每个字符的方式。

    对于只有三个单词的one-hot,那么维度即为3;按序编码G,O,D,那么
    G为1,展开one-hot就是[1,0,0],
    O为2, 就是[0,1,0],
    D为3,就是[0,0,1]

    输入转换为一个one-hot 表示,输出为相应的类别得分

    动手实例

            在这个实现中,我们将使用 PyTorch 框架,这是一个易于使用并被顶级研究人员广泛使用的深度学习平台。 我们将构建一个模型,该模型将根据传入的一个单词或几个字符来完成一个句子。

    我们的模型将如何处理输入数据和产生输出

        

            该模型将输入一个单词,并预测句子中的下一个字符是什么。 这个过程会不断重复,直到我们生成所需长度的句子。

            为了保持简短和简单,我们不会使用任何大型或外部数据集。 相反,我们将只定义几个句子来看看模型如何从这些句子中学习。 此实现将采取的过程如下:

    实施流程(Flow of the implementation)

    代码流程

            我们将首先导入主要的 PyTorch 包以及构建模型时将使用的 nn 包。 此外,我们将只使用 NumPy 来预处理我们的数据,因为 Torch 与 NumPy 配合得非常好。 

    1. import torch
    2. from torch import nn
    3. import numpy as np

            首先,我们将定义我们希望模型在输入第一个单词或前几个字符时输出的句子。

            然后我们将从句子中的所有字符创建一个字典,并将它们映射到一个整数。 这将允许我们将输入字符转换为它们各自的整数(char2int),反之亦然(int2char)。

    1. text = ['hey how are you','good i am fine','have a nice day']
    2. # Join all the sentences together and extract the unique characters from the combined sentences
    3. chars = set(''.join(text))
    4. # Creating a dictionary that maps integers to the characters
    5. int2char = dict(enumerate(chars))
    6. # Creating another dictionary that maps characters to integers
    7. char2int = {char: ind for ind, char in int2char.items()}

            char2int 字典看起来像这样:它包含我们句子中出现的所有字母/符号,并将它们中的每一个映射到一个唯一的整数。结果如下所示(结果不唯一):

    {'f': 0, 'a': 1, 'h': 2, 'i': 3, 'u': 4, 'e': 5, 'm': 6, 'w': 7, 'y': 8, 'd': 9, 'c': 10, ' ': 11, 'r': 12, 'o': 13, 'n': 14, 'g': 15, 'v': 16}

            接下来,我们将填充(padding)输入句子以确保所有句子都是标准长度。 虽然 RNN 通常能够接收可变大小的输入,但我们通常希望分批输入训练数据以加快训练过程。 为了使用批次来训练我们的数据,我们需要确保输入数据中的每个序列大小相等。

            因此,在大多数情况下,可以通过用 0 值填充太短的序列和修剪太长的序列来完成填充。 在我们的例子中,我们将找到最长序列的长度,并用空格填充其余句子以匹配该长度。

    1. # Finding the length of the longest string in our data
    2. maxlen = len(max(text, key=len))
    3. # Padding
    4. # A simple loop that loops through the list of sentences and adds a ' ' whitespace until the length of
    5. # the sentence matches the length of the longest sentence
    6. for i in range(len(text)):
    7. while len(text[i])
    8. text[i] += ' '

            由于我们要在每个时间步预测序列中的下一个字符,我们必须将每个句子分为:

    • 输入数据
      最后一个字符需排除因为它不需要作为模型的输入
    • 目标/真实标签
      它为每一个时刻后的值,因为这才是下一个时刻的值。
    1. # Creating lists that will hold our input and target sequences
    2. input_seq = []
    3. target_seq = []
    4. for i in range(len(text)):
    5. # Remove last character for input sequence
    6. input_seq.append(text[i][:-1])
    7. # Remove first character for target sequence
    8. target_seq.append(text[i][1:])
    9. print("Input Sequence: {}\nTarget Sequence: {}".format(input_seq[i], target_seq[i]))

    输入和输出样例如下:

    • 输入:hey how are yo
    • 对应的标签: ey how are you

             现在我们可以通过使用上面创建的字典映射输入和目标序列到整数序列。 这将允许我们随后对输入序列进行一次one-hot encoding。

    1. for i in range(len(text)):
    2. input_seq[i] = [char2int[character] for character in input_seq[i]]
    3. target_seq[i] = [char2int[character] for character in target_seq[i]]

    定义如下三个变量

    1. dict_size: 字典的长度,即唯一字符的个数。它将决定one-hot vector的长度
    2. seq_len:输入到模型中的sequence长度。这里是最长的句子的长度-1,因为不需要最后一个字符
    3. batch_size: mini batch的大小,用于批量训练
    1. dict_size = len(char2int)
    2. seq_len = maxlen - 1
    3. batch_size = len(text)

     one-hot编码

    1. def one_hot_encode(sequence, dict_size, seq_len, batch_size):
    2. # Creating a multi-dimensional array of zeros with the desired output shape
    3. features = np.zeros((batch_size, seq_len, dict_size), dtype=np.float32)
    4. # Replacing the 0 at the relevant character index with a 1 to represent that character
    5. for i in range(batch_size):
    6. for u in range(seq_len):
    7. features[i, u, sequence[i][u]] = 1
    8. return features

    同时定义一个helper function,用于初始化one-hot向量 

    1. # Input shape --> (Batch Size, Sequence Length, One-Hot Encoding Size)
    2. input_seq = one_hot_encode(input_seq, dict_size, seq_len, batch_size)

     到此我们完成了所有的数据预处理,可以将数据从NumPy数组转为PyTorch张量啦。

    1. input_seq = torch.from_numpy(input_seq)
    2. target_seq = torch.Tensor(target_seq)

            接下来就是搭建模型的步骤,你可以在这一步使用全连接层,卷积层,RNN层,LSTM层等等。但是我在这里使用最最基础的nn.rnn来示例一个RNN是如何使用的。

            在开始构建模型之前,让我们使用 PyTorch 中的内置功能来检查我们正在运行的设备(CPU 或 GPU)。 此实现不需要 GPU,因为训练非常简单。 但是,随着处理具有数百万个可训练参数的大型数据集和模型,使用 GPU 对加速训练非常重要。

    1. # torch.cuda.is_available() checks and returns a Boolean True if a GPU is available, else it'll return False
    2. is_cuda = torch.cuda.is_available()
    3. # If we have a GPU available, we'll set our device to GPU. We'll use this device variable later in our code.
    4. if is_cuda:
    5. device = torch.device("cuda")
    6. print("GPU is available")
    7. else:
    8. device = torch.device("cpu")
    9. print("GPU not available, CPU used")

             要开始构建我们自己的神经网络模型,我们可以为所有神经网络模块定义一个继承 PyTorch 的基类(nn.module)的类。 这样做之后,我们可以开始在构造函数下定义一些变量以及模型的层。 对于这个模型,我们将只使用一层 RNN,然后是一个全连接层。 全连接层将负责将 RNN 输出转换为我们想要的输出形状。

            我们还必须将 forward() 下的前向传递函数定义为类方法。 前向函数是按顺序执行的,因此我们必须先将输入和零初始化隐藏状态通过 RNN 层,然后再将 RNN 输出传递到全连接层。 请注意,我们使用的是在构造函数中定义的层。

            我们必须定义的最后一个方法是我们之前调用的用于初始化hidden state的方法 - init_hidden()。 这基本上会在我们的隐藏状态的形状中创建一个零张量。

    1. class Model(nn.Module):
    2. def __init__(self, input_size, output_size, hidden_dim, n_layers):
    3. super(Model, self).__init__()
    4. # Defining some parameters
    5. self.hidden_dim = hidden_dim
    6. self.n_layers = n_layers
    7. #Defining the layers
    8. # RNN Layer
    9. self.rnn = nn.RNN(input_size, hidden_dim, n_layers, batch_first=True)
    10. # Fully connected layer
    11. self.fc = nn.Linear(hidden_dim, output_size)
    12. def forward(self, x):
    13. batch_size = x.size(0)
    14. # Initializing hidden state for first input using method defined below
    15. hidden = self.init_hidden(batch_size)
    16. # Passing in the input and hidden state into the model and obtaining outputs
    17. out, hidden = self.rnn(x, hidden)
    18. # Reshaping the outputs such that it can be fit into the fully connected layer
    19. out = out.contiguous().view(-1, self.hidden_dim)
    20. out = self.fc(out)
    21. return out, hidden
    22. def init_hidden(self, batch_size):
    23. # This method generates the first hidden state of zeros which we'll use in the forward pass
    24. # We'll send the tensor holding the hidden state to the device we specified earlier as well
    25. hidden = torch.zeros(self.n_layers, batch_size, self.hidden_dim)
    26. return hidde

             在定义了上面的模型之后,我们必须用相关参数实例化模型并定义我们的超参数。 我们在下面定义的超参数是:

    • n_epochs: 模型训练所有数据集的次数
    • lr: learning rate学习率

            与其他神经网络类似,我们也必须定义优化器和损失函数。 我们将使用 CrossEntropyLoss,因为最终输出基本上是一个分类任务和常见的 Adam 优化器。

    1. # Instantiate the model with hyperparameters
    2. model = Model(input_size=dict_size, output_size=dict_size, hidden_dim=12, n_layers=1)
    3. # We'll also set the model to the device that we defined earlier (default is CPU)
    4. model.to(device)
    5. # Define hyperparameters
    6. n_epochs = 100
    7. lr=0.01
    8. # Define Loss, Optimizer
    9. criterion = nn.CrossEntropyLoss()
    10. optimizer = torch.optim.Adam(model.parameters(), lr=lr)

            现在我们可以开始训练了! 由于我们只有几句话,所以这个训练过程非常快。 然而,随着我们的进步,更大的数据集和更深的模型意味着输入数据要大得多,并且我们必须计算的模型中的参数数量要多得多。

    1. # Training Run
    2. for epoch in range(1, n_epochs + 1):
    3. optimizer.zero_grad() # Clears existing gradients from previous epoch
    4. input_seq.to(device)
    5. output, hidden = model(input_seq)
    6. loss = criterion(output, target_seq.view(-1).long())
    7. loss.backward() # Does backpropagation and calculates gradients
    8. optimizer.step() # Updates the weights accordingly
    9. if epoch%10 == 0:
    10. print('Epoch: {}/{}.............'.format(epoch, n_epochs), end=' ')
    11. print("Loss: {:.4f}".format(loss.item()))
    1. [Out]: Epoch: 10/100............. Loss: 2.4176
    2. Epoch: 20/100............. Loss: 2.1816
    3. Epoch: 30/100............. Loss: 1.7952
    4. Epoch: 40/100............. Loss: 1.3524
    5. Epoch: 50/100............. Loss: 0.9671
    6. Epoch: 60/100............. Loss: 0.6644
    7. Epoch: 70/100............. Loss: 0.4499
    8. Epoch: 80/100............. Loss: 0.3089
    9. Epoch: 90/100............. Loss: 0.2222
    10. Epoch: 100/100............. Loss: 0.1690

            现在让我们测试我们的模型,看看我们会得到什么样的输出。 作为第一步,我们将定义一些辅助函数来将我们的模型输出转换回文本。 

    1. # This function takes in the model and character as arguments and returns the next character prediction and hidden state
    2. def predict(model, character):
    3. # One-hot encoding our input to fit into the model
    4. character = np.array([[char2int[c] for c in character]])
    5. character = one_hot_encode(character, dict_size, character.shape[1], 1)
    6. character = torch.from_numpy(character)
    7. character.to(device)
    8. out, hidden = model(character)
    9. prob = nn.functional.softmax(out[-1], dim=0).data
    10. # Taking the class with the highest probability score from the output
    11. char_ind = torch.max(prob, dim=0)[1].item()
    12. return int2char[char_ind], hidden
    1. # This function takes the desired output length and input characters as arguments, returning the produced sentence
    2. def sample(model, out_len, start='hey'):
    3. model.eval() # eval mode
    4. start = start.lower()
    5. # First off, run through the starting characters
    6. chars = [ch for ch in start]
    7. size = out_len - len(chars)
    8. # Now pass in the previous characters and get a new one
    9. for ii in range(size):
    10. char, h = predict(model, chars)
    11. chars.append(char)
    12. return ''.join(chars)

    让我们测试一下good

    sample(model, 15, 'good')

            正如我们所看到的,如果我们用“good”这个词输入到模型,该模型能够提出“good i am fine”这个句子。

    完整代码CPU版本

    1. # RNN_model.py
    2. import torch
    3. from torch import nn
    4. class Model(nn.Module):
    5. """
    6. input_size (int):输入数据的特征大小,即每个时间步的输入向量的维度。
    7. hidden_size (int):隐藏层的特征大小,即每个时间步的隐藏状态向量的维度。
    8. num_layers (int,可选):RNN的层数,默认值为1。当层数大于1时,RNN会变为多层RNN。
    9. nonlinearity (str,可选):指定激活函数,默认值为'tanh'。可选值有'tanh'和'relu'。
    10. bias (bool,可选):如果设置为True,则在RNN中添加偏置项。默认值为True。
    11. batch_first (bool,可选):如果设置为True,则输入数据的形状为(batch_size, seq_len, input_size)。否则,默认输入数据的形状为(seq_len, batch_size, input_size)。默认值为False。
    12. dropout (float,可选):如果非零,则在除最后一层之外的每个RNN层之间添加dropout层,其丢弃概率为dropout。默认值为0。
    13. bidirectional (bool,可选):如果设置为True,则使用双向RNN。默认值为False。
    14. """
    15. def __init__(self, input_size, output_size, hidden_dim, n_layers):
    16. super(Model, self).__init__()
    17. # Defining some parameters
    18. self.hidden_dim = hidden_dim # 隐藏状态 ht 的维度
    19. self.n_layers = n_layers # 网络的层数
    20. # Defining the layers
    21. # RNN Layer
    22. self.rnn = nn.RNN(input_size, hidden_dim, n_layers, batch_first=True)
    23. # Fully connected layer
    24. self.fc = nn.Linear(hidden_dim, output_size)
    25. def forward(self, x):
    26. batch_size = x.size(0)
    27. # Initializing hidden state for first input using method defined below
    28. hidden = self.init_hidden(batch_size)
    29. # Passing in the input and hidden state into the model and obtaining outputs
    30. out, hidden = self.rnn(x, hidden)
    31. # Reshaping the outputs such that it can be fit into the fully connected layer
    32. out = out.contiguous().view(-1, self.hidden_dim)
    33. out = self.fc(out)
    34. return out, hidden
    35. def init_hidden(self, batch_size):
    36. # This method generates the first hidden state of zeros which we'll use in the forward pass
    37. # We'll send the tensor holding the hidden state to the device we specified earlier as well
    38. hidden = torch.zeros(self.n_layers, batch_size, self.hidden_dim)
    39. return hidden
    1. # train.py
    2. import torch
    3. from torch import nn
    4. import numpy as np
    5. # 首先,我们将定义我们希望模型在输入第一个单词或前几个字符时输出的句子。
    6. # 然后我们将从句子中的所有字符创建一个字典,并将它们映射到一个整数。
    7. # 这将允许我们将输入字符转换为它们各自的整数(char2int),反之亦然(int2char)。
    8. text = ['hey how are you', 'good i am fine', 'have a nice day']
    9. # Join all the sentences together and extract the unique characters from the combined sentences
    10. # 将所有句子连接在一起,并从组合的句子中提取独特的字符。
    11. chars = set(''.join(text))
    12. # print(chars)# 输出 : {'y', 'o', ' ', 'd', 'f', 'n', 'm', 'i', 'w', 'r', 'u', 'v', 'h', 'c', 'g', 'e', 'a'} (注意:输出不定,但都包含了所有的字符)
    13. # Creating a dictionary that maps integers to the characters
    14. int2char = dict(enumerate(chars))
    15. # print(int2char)
    16. # Creating another dictionary that maps characters to integers
    17. char2int = {char: ind for ind, char in int2char.items()}
    18. # char2int 字典看起来像这样:它包含我们句子中出现的所有字母/符号,并将它们中的每一个映射到一个唯一的整数。
    19. # print(char2int)
    20. # ------------------------------------------------------------------------------------
    21. # 接下来,我们将填充(padding)输入句子以确保所有句子都是标准长度。
    22. # 虽然 RNN 通常能够接收可变大小的输入,但我们通常希望分批输入训练数据以加快训练过程。
    23. # 为了使用批次(batch)来训练我们的数据,我们需要确保输入数据中的每个序列大小相等。
    24. # 因此,在大多数情况下,可以通过用 0 值填充太短的序列和修剪太长的序列来完成填充。
    25. # 在我们的例子中,我们将找到最长序列的长度,并用空格填充其余句子以匹配该长度。
    26. # Finding the length of the longest string in our data
    27. maxlen = len(max(text, key=len))
    28. # Padding
    29. # A simple loop that loops through the list of sentences and adds a ' ' whitespace until the length of
    30. # the sentence matches the length of the longest sentence
    31. for i in range(len(text)):
    32. while len(text[i])<maxlen:
    33. text[i] += ' '
    34. # 由于我们要在每个时间步预测序列中的下一个字符,我们必须将每个句子分为:
    35. # 输入数据
    36. # 最后一个字符需排除因为它不需要作为模型的输入
    37. # 目标/真实标签
    38. # 它为每一个时刻后的值,因为这才是下一个时刻的值。
    39. # Creating lists that will hold our input and target sequences
    40. input_seq = []
    41. target_seq = []
    42. for i in range(len(text)):
    43. # Remove last character for input sequence
    44. input_seq.append(text[i][:-1])
    45. # Remove first character for target sequence
    46. target_seq.append(text[i][1:])
    47. print("Input Sequence: {}\nTarget Sequence: {}".format(input_seq[i], target_seq[i]))
    48. # 现在我们可以通过使用上面创建的字典映射输入和目标序列到整数序列。
    49. # 这将允许我们随后对输入序列进行一次one-hot encoding。
    50. for i in range(len(text)):
    51. input_seq[i] = [char2int[character] for character in input_seq[i]]
    52. target_seq[i] = [char2int[character] for character in target_seq[i]]
    53. # 定义如下三个变量
    54. # dict_size: 字典的长度,即唯一字符的个数。它将决定one-hot vector的长度
    55. # seq_len:输入到模型中的sequence长度。这里是最长的句子的长度-1,因为不需要最后一个字符
    56. # batch_size: mini batch的大小,用于批量训练
    57. dict_size = len(char2int)
    58. seq_len = maxlen - 1
    59. batch_size = len(text)
    60. def one_hot_encode(sequence, dict_size, seq_len, batch_size):
    61. # Creating a multi-dimensional array of zeros with the desired output shape
    62. features = np.zeros((batch_size, seq_len, dict_size), dtype=np.float32)
    63. # Replacing the 0 at the relevant character index with a 1 to represent that character
    64. for i in range(batch_size):
    65. for u in range(seq_len):
    66. features[i, u, sequence[i][u]] = 1
    67. return features
    68. # 同时定义一个helper function,用于初始化one-hot向量
    69. # Input shape --> (Batch Size, Sequence Length, One-Hot Encoding Size)
    70. input_seq = one_hot_encode(input_seq, dict_size, seq_len, batch_size)
    71. # 到此我们完成了所有的数据预处理,可以将数据从NumPy数组转为PyTorch张量啦
    72. input_seq = torch.from_numpy(input_seq)
    73. target_seq = torch.Tensor(target_seq)
    74. # 接下来就是搭建模型的步骤,你可以在这一步使用全连接层,卷积层,RNN层,LSTM层等等。
    75. # 但是我在这里使用最最基础的nn.rnn来示例一个RNN是如何使用的。
    76. from RNN_model import Model
    77. """
    78. # 在开始构建模型之前,让我们使用 PyTorch 中的内置功能来检查我们正在运行的设备(CPU 或 GPU)。
    79. # 此实现不需要 GPU,因为训练非常简单。
    80. # 但是,随着处理具有数百万个可训练参数的大型数据集和模型,使用 GPU 对加速训练非常重要。
    81. # torch.cuda.is_available() checks and returns a Boolean True if a GPU is available, else it'll return False
    82. # is_cuda = torch.cuda.is_available()
    83. # If we have a GPU available, we'll set our device to GPU. We'll use this device variable later in our code.
    84. # if is_cuda:
    85. # device = torch.device("cuda")
    86. # print("GPU is available")
    87. # else:
    88. # device = torch.device("cpu")
    89. # print("GPU not available, CPU used")
    90. """
    91. # 要开始构建我们自己的神经网络模型,我们可以为所有神经网络模块定义一个继承 PyTorch 的基类(nn.module)的类。
    92. # 这样做之后,我们可以开始在构造函数下定义一些变量以及模型的层。 对于这个模型,我们将只使用一层 RNN,然后是一个全连接层。 全连接层将负责将 RNN 输出转换为我们想要的输出形状。
    93. # 我们还必须将 forward() 下的前向传递函数定义为类方法。 前向函数是按顺序执行的,因此我们必须先将输入和零初始化隐藏状态通过 RNN 层,然后再将 RNN 输出传递到全连接层。 请注意,我们使用的是在构造函数中定义的层。
    94. # 我们必须定义的最后一个方法是我们之前调用的用于初始化hidden state的方法 - init_hidden()。 这基本上会在我们的隐藏状态的形状中创建一个零张量。
    95. # 在定义了上面的模型之后,我们必须用相关参数实例化模型并定义我们的超参数。 我们在下面定义的超参数是:
    96. # n_epochs: 模型训练所有数据集的次数
    97. # lr: learning rate学习率
    98. # 与其他神经网络类似,我们也必须定义优化器和损失函数。 我们将使用 CrossEntropyLoss,因为最终输出基本上是一个分类任务和常见的 Adam 优化器。
    99. # Instantiate the model with hyperparameters
    100. model = Model(input_size=dict_size, output_size=dict_size, hidden_dim=12, n_layers=1)
    101. # We'll also set the model to the device that we defined earlier (default is CPU)
    102. # model.to(device)
    103. # Define hyperparameters
    104. n_epochs = 100 # 训练轮数
    105. lr = 0.01 # 学习率
    106. # Define Loss, Optimizer
    107. loss_fn = nn.CrossEntropyLoss() # 交叉熵损失函数
    108. optimizer = torch.optim.Adam(model.parameters(), lr=lr) # 采用Adam作为优化器
    109. # 现在我们可以开始训练了!
    110. # 由于我们只有几句话,所以这个训练过程非常快。
    111. # 然而,随着我们的进步,更大的数据集和更深的模型意味着输入数据要大得多,并且我们必须计算的模型中的参数数量要多得多。
    112. # Training Run
    113. for epoch in range(1, n_epochs + 1):
    114. optimizer.zero_grad() # Clears existing gradients from previous epoch
    115. # input_seq.to(device) # 使用GPU
    116. output, hidden = model(input_seq)
    117. loss = loss_fn(output, target_seq.view(-1).long())
    118. loss.backward() # Does backpropagation and calculates gradients
    119. optimizer.step() # Updates the weights accordingly
    120. if epoch % 10 == 0:
    121. print('Epoch: {}/{}.............'.format(epoch, n_epochs), end=' ')
    122. print("Loss: {:.4f}".format(loss.item()))
    1. # test.py
    2. # 现在让我们测试我们的模型,看看我们会得到什么样的输出。 作为第一步,我们将定义一些辅助函数来将我们的模型输出转换回文本。
    3. # This function takes in the model and character as arguments and returns the next character prediction and hidden state
    4. import numpy as np
    5. import torch
    6. from torch import device
    7. import torch.nn as nn
    8. from train import char2int, one_hot_encode, dict_size, int2char, model
    9. def predict(model, character):
    10. # One-hot encoding our input to fit into the model
    11. character = np.array([[char2int[c] for c in character]])
    12. character = one_hot_encode(character, dict_size, character.shape[1], 1)
    13. character = torch.from_numpy(character)
    14. # character.to(device)
    15. out, hidden = model(character)
    16. prob = nn.functional.softmax(out[-1], dim=0).data
    17. # Taking the class with the highest probability score from the output
    18. char_ind = torch.max(prob, dim=0)[1].item()
    19. return int2char[char_ind], hidden
    20. # This function takes the desired output length and input characters as arguments, returning the produced sentence
    21. def sample(model, out_len, start='hey'):
    22. model.eval() # eval mode
    23. start = start.lower()
    24. # First off, run through the starting characters
    25. chars = [ch for ch in start]
    26. size = out_len - len(chars)
    27. # Now pass in the previous characters and get a new one
    28. for ii in range(size):
    29. char, h = predict(model, chars)
    30. chars.append(char)
    31. return ''.join(chars)

    验证

    我们在测试文件中执行以下代码,并得到结果。

    1. print(sample(model, 15, 'good')) # good i am fine
    2. print(sample(model, 15, 'h')) # have a nice day
    3. print(sample(model, 15, 'you')) # youd i am fine

            我们发现,为模型输入‘good’、‘h’后,预测的结果正确;而为模型输入‘you’后,其预测结果为youd i am fine,模型不知道该如何预测,预测结果不理想。 

    该模型的局限

            虽然这个模型绝对是一个过度简化的语言模型,但让我们回顾一下它的局限性以及为了训练更好的语言模型需要解决的问题。

    局限一、过拟合 over-fitting

            我们只为模型提供了 3 个训练句子,因此它基本上“记住”了这些句子的字符序列,从而返回了我们训练它的确切句子。 但是,如果在更大的数据集上训练一个类似的模型,并添加一些随机性,该模型将挑选出一般的句子结构和语言规则,并且能够生成自己独特的句子。

            尽管如此,使用单个样本或批次运行模型可以作为对工作流程的健全性检查,确保您的数据类型全部正确,模型学习良好等。

    局限二、处理未见过的字符

            该模型目前只能处理它之前在训练数据集中看到的字符。 通常,如果训练数据集足够大,所有字母和符号等应该至少出现一次,从而出现在我们的词汇表中。 然而,有一种方法来处理从未见过的字符总是好的,例如将所有未知数分配给它自己的索引。

    局限三、文本标识的方式

            在这个实现中,我们使用 one-hot 编码来表示我们的字符。 虽然由于它的简单性,它可能适合此任务,但大多数时候它不应该用作实际或更复杂问题的解决方案。 这是因为:

    • 对于大型数据集,计算成本太高
    • one-hot向量中没有嵌入上下文/语义信息

            以及许多其他使此解决方案不太可行的缺点。

            相反,大多数现代 NLP 解决方案依赖于词嵌入(word2vec、GloVe)或最近在 BERT、ELMo 和 ULMFit 中的独特上下文词表示。 这些方法允许模型根据出现在它之前的文本来学习单词的含义,并且在 BERT 等的情况下,也可以从出现在它之后的文本中学习。

  • 相关阅读:
    校园论坛(Java)—— 登录注册和用户信息模块
    【ROS】机械人开发三--ROS工作空间与功能包
    Spring Boot + Vue3前后端分离实战wiki知识库系统<八>--分类管理功能开发二
    elform-item动态prop
    MyBatis-Puls一篇就够
    Java手写背包问题算法
    Python 逗号的巧用
    编译和链接
    k8s网络插件之Calico
    Redis 数据结构与对象
  • 原文地址:https://blog.csdn.net/m0_48241022/article/details/132794866