【自然语言处理】seq2seq模型——机器翻译

seq2seq模型——机器翻译

1 任务目标

1.1 案例简介

seq2seq是神经机器翻译的主流框架,如今的商用机器翻译系统大多都基于其构建,在本案例中,我们将使用由NIST提供的中英文本数据训练一个简单的中英翻译系统,在实践中学习seq2seq的具体细节,以及了解机器翻译的基本技术。

1.2 seq2seq模型

从根本上讲,机器翻译需要将输入序列(源语言中的单词)映射到输出序列(目标语言中的单词)。正如我们在课堂上讨论的那样,递归神经网络(RNN)可有效处理此类顺序数据。机器翻译中的一个重要难题是输入和输出序列之间没有一对一的对应关系。即,序列通常具有不同的长度,并且单词对应可以是不平凡的(例如,彼此直接翻译的单词可能不会以相同的顺序出现)。

为了解决这个问题,我们将使用一种更灵活的架构,称为seq2seq模型。该模型由编码器和解码器两部分组成,它们都是RNN。编码器将源语言中的单词序列作为输入,并输出RNN层的最终隐藏状态。解码器与之类似,除了它还具有一个附加的全连接层(带有softmax激活),用于定义翻译中下一个单词的概率分布。以此方式,解码器本质上用作目标语言的神经语言模型。关键区别在于,解码器将编码器的输出用作其初始隐藏状态,而不是零向量。

1.3 数据和代码

本案例使用了一个小规模的中英平行语料数据,并提供了一个简单的seq2seq模型实现,包括数据的预处理、模型的训练、以及简单的评测。

1.4 评分要求

分数由两部分组成,各占50%。第一部分得分为对于简单seq2seq模型的改进,并撰写实验报告,改进方式多样,下一小节会给出一些可能的改进方向。第二分部得分为测试数据的评测结果,我们将给出一个中文测试数据集(test.txt),其中每一行为一句中文文本,需要同学提交模型做出的对应翻译结果,助教将对于大家的提交结果统一机器评测,并给出分数。

1.5 改进方向

  1. 初级改进:

    • 将RNN模型替换成GRU或者LSTM

    • 使用双向的encoder获得更好的源语言表示

    • 对于现有超参数进行调优,这里建议划分出一个开发集,在开发集上进行grid search,并且在报告中汇报开发集结果

    • 引入更多的训练语料(如果尝试复杂模型,更多的训练数据将非常关键)

  2. 进阶改进:

    • 使用注意力机制(注意力机制是一个很重要的NMT技术,建议大家优先进行这方面的尝试,具体有许多种变体,可以参考这个综述)

    • 在Encoder部分,使用了字级别的中文输入,可以考虑加入分词的结果,并且将Encoder的词向量替换为预训练过的词向量,获得更好的性能

  3. 复杂改进:

    • 使用beam search的技术来帮助更好的解码,对于beam-width进行调优

    • 将RNN替换为Transformer模型,以及最新的改进变体

1.6 参考

[1] ml/NLPTrainingCamp/seq2seq at 8dfefc2f41e3cee96efb9e81a3cba97ec3cb0f29 · hotbaby/ml (github.com)

[2] GAN-QA/references/notebook/seq2seq-translation-batched.ipynb at 85d3efdf10fc0dbbbf0b0b2cde86a6b37d1fa9b0 · weilinie/GAN-QA (github.com)

2 seq2seq 基本实现

2.1 seq2seq 基本原理

Seq2Seq(Sequence-to-Sequence,序列到序列)是一种处理序列数据的机器学习模型,尤其在自然语言处理(NLP)领域中非常流行。

Seq2Seq模型的架构通常由两个基本组件组成:编码器(Encoder)和解码器(Decoder)。

image-20240427180703325

  • 编码器(Encoder):负责学习源语言的表示。编码器的任务是读取输入序列(例如,一句话或一段文本),并将其转换成一个固定大小的内部表示,通常称为“上下文向量”(Context Vector)。这个向量旨在捕捉输入序列的主要信息,以便解码器可以生成输出序列。
  • 解码器(Decoder):解码器可以看作一个条件语言模型,它基于编码器学到的表示产生目标语言。解码器的任务是使用编码器生成的上下文向量来生成输出序列。解码器通常是一个循环神经网络(RNN),它可以逐步生成输出序列的每个元素。

2.2 代码实现

​ 项目中已给出可以运行的完整代码,但是测试下来发现某些中文字符无法预测,经过修改后,以下是代码实现的详细介绍。

  1. 读取数据

    我们将读取目录下的 cn-eng.txt 文件,其中每一行是一个平行句对,例子如下

    我們試試看!	Let's try something.
    
    • 对于单词进行编号

      定义了一个 Lang 类,用于处理文本分词,这里除了引入了三个特殊的Token:

      • SOS, "Start of sentence”,标识句子开始
      • EOS, “End of sentence”,表示句子结束
      • UNK, “Unknown Token”,标识未登录词
      SOS_token = 0
      EOS_token = 1
      UNK_token = 2class Lang:def __init__(self, name):self.name = name            # 语言名称self.word2index = {}        # 词到索引的映射self.word2count = {}        # 词到出现次数的映射self.index2word = {0: "SOS", 1: "EOS", 2: "UNK"}    # 索引到词的映射self.n_words = 3            # 单词的数量# 索引句子中的单词def index_words(self, sentence):if self.name == 'cn':    for word in sentence:self.index_word(word)else:for word in sentence.split(' '):self.index_word(word)# 索引单个单词def index_word(self, word):if word not in self.word2index:self.word2index[word] = self.n_wordsself.word2count[word] = 1self.index2word[self.n_words] = wordself.n_words += 1else:self.word2count[word] += 1
      
    • 文本预处理

      丢弃除了中文、字母和常用标点之外的符号。

      # Turn a Unicode string to plain ASCII, thanks to http://stackoverflow.com/a/518232/2809427
      def unicode_to_ascii(s):return ''.join(c for c in unicodedata.normalize('NFD', s)if unicodedata.category(c) != 'Mn')# Lowercase, trim, and remove non-letter characters
      def normalize_string(s):s = unicode_to_ascii(s.lower().strip())s = re.sub(r"([.!?])", r" \1", s)s = re.sub(r"[^a-zA-Z\u4e00-\u9fa5.!?,。?]+", r" ", s)return s
      

      读取平行语料,并进行清理。

      def read_langs(lang1, lang2, reverse=False):print("Reading lines...")# Read the file and split into lineslines = open('%s-%s.txt' % (lang1, lang2)).read().strip().split('\n')# Split every line into pairs and normalizepairs = [[normalize_string(s) for s in l.split('\t')] for l in lines]# Reverse pairs, make Lang instancesif reverse:pairs = [list(reversed(p)) for p in pairs]input_lang = Lang(lang2)output_lang = Lang(lang1)else:input_lang = Lang(lang1)output_lang = Lang(lang2)return input_lang, output_lang, pairs
      
    • 过滤句子

      样例为了加快训练,只保留了不长于10个单词的句对,真正实验中将更多数据考虑进来可能获得更好的效果。

      MAX_LENGTH = 10def filter_pair(p):return len(p[1].split(' ')) < MAX_LENGTHdef filter_pairs(pairs):return [pair for pair in pairs if filter_pair(pair)]
      

      处理数据的全过程:

      • 读取数据,每一行分别处理,将其转换成句对

      • 对于文本进行处理,过滤无用符号

      • 根据已有文本对于单词进行编号,构建符号到编号的映射

      def prepare_data(lang1_name, lang2_name, reverse=False):input_lang, output_lang, pairs = read_langs(lang1_name, lang2_name, reverse)print("Read %s sentence pairs" % len(pairs))pairs = filter_pairs(pairs)print("Trimmed to %s sentence pairs" % len(pairs))print("Indexing words...")for pair in pairs:input_lang.index_words(pair[0])output_lang.index_words(pair[1])return input_lang, output_lang, pairsinput_lang, output_lang, pairs = prepare_data('cn', 'eng', False)# Print an example pair
      print(random.choice(pairs))
      
  2. 将文本数据转换为张量

    为了训练,我们需要将句子变成神经网络可以理解的东西(数字)。每个句子将被分解成单词,然后变成张量,其中每个单词都被索引替换(来自之前的Lang索引)。在创建这些张量时,我们还将附加EOS令牌以表示该句子已结束。

    image-20240504155945943

    此处添加 UNK_token 以免出现未标识的中文字符。

    # Return a list of indexes, one for each word in the sentence
    def indexes_from_sentence(lang, sentence):"""根据词表,将句子转化成索引列表。:reutrn list,e.g. [1, 2, 3, 4]"""if lang.name == 'cn':return [lang.word2index[word] if word in lang.word2index else UNK_token for word in sentence ]else:return [lang.word2index[word] if word in lang.word2index else UNK_token for word in sentence.split(' ')]def variable_from_sentence(lang, sentence):"""将句子转换成Tensor.:return Tensor, shape(n, 1)"""indexes = indexes_from_sentence(lang, sentence)indexes.append(EOS_token)var = torch.LongTensor(indexes).view(-1, 1)if USE_CUDA: var = var.cuda()return vardef variables_from_pair(pair):"""将平行语料对转化成Tensors.:return (input_tensor, output_tensor)"""input_variable = variable_from_sentence(input_lang, pair[0])target_variable = variable_from_sentence(output_lang, pair[1])return (input_variable, target_variable)
    
  3. 组件模型

    介绍训练需要的组件。

    • 编码器

      采用 RNN 架构的编码器,将输入序列(如一句话或一段文本)转换成一个固定大小的内部表示,即上下文向量。

      class EncoderRNN(nn.Module):def __init__(self, input_size, hidden_size, n_layers=1):super(EncoderRNN, self).__init__()self.input_size = input_sizeself.hidden_size = hidden_sizeself.n_layers = n_layersself.embedding = nn.Embedding(input_size, hidden_size)self.rnn = nn.RNN(hidden_size, hidden_size, n_layers)def forward(self, word_inputs, hidden):# Note: we run this all at once (over the whole input sequence)seq_len = len(word_inputs)embedded = self.embedding(word_inputs).view(seq_len, 1, -1)output, hidden = self.rnn(embedded, hidden)return output, hiddendef init_hidden(self):hidden = torch.zeros(self.n_layers, 1, self.hidden_size)if USE_CUDA: hidden = hidden.cuda()return hidden
      
    • 解码器

      采用 RNN 架构的解码器,使用编码器的上下文向量和先前生成的单词来生成目标序列,一步一个单词。

      class DecoderRNN(nn.Module):def __init__(self, hidden_size, output_size, n_layers=1, dropout_p=0.1):super(DecoderRNN, self).__init__()# Keep parameters for referenceself.hidden_size = hidden_sizeself.output_size = output_sizeself.n_layers = n_layersself.dropout_p = dropout_p# Define layersself.embedding = nn.Embedding(output_size, hidden_size)self.rnn = nn.RNN(hidden_size, hidden_size, n_layers, dropout=dropout_p)self.out = nn.Linear(hidden_size, output_size)def forward(self, word_input, last_hidden):# Note: we run this one step at a time        word_embedded = self.embedding(word_input).view(1, 1, -1) # S=1 x B x Nrnn_output, hidden = self.rnn(word_embedded, last_hidden)rnn_output = rnn_output.squeeze(0)output = F.log_softmax(self.out(rnn_output))return output, hidden
      

2.3 模型训练

​ 本次实验采用在本地训练,具体训练过程及结果如下。

  1. 一次训练迭代

    为了训练,我们首先通过编码器逐字运行输入语句,并跟踪每个输出和最新的隐藏状态。接下来,为解码器提供解码器的最后一个隐藏状态作为其第一隐藏状态,并向其提供<SOS>作为其第一输入。从那里开始,我们迭代地预测来自解码器的下一个单词。

    • Teacher Forcing

      "Teacher Forcing"指的是每次都基于完全准确的上文进行解码,这样训练模型收敛很快,但是会造成实际场景和训练场景有较大差别,因为实际场景上文也都是模型预测的,可能不准确,具体细节可参考论文。

      观察Teacher Forcing的网络的输出,我们可以看到该网络语法连贯,但是偏离正确的翻译。可以将其为学会了如何听老师的指示,而未学习如何独自冒险。

    • Scheduled Sampling

      解决强迫教师问题的方法称为“计划抽样”(Scheduled Sampling),它在训练时仅在使用目标值和预测值之间进行切换。我们将在训练时随机选择,有时我们将使用真实目标作为输入(忽略解码器的输出),有时我们将使用解码器的输出。

    teacher_forcing_ratio = 0.5
    clip = 5.0def train(input_variable, target_variable, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion, max_length=MAX_LENGTH):# Zero gradients of both optimizersencoder_optimizer.zero_grad()decoder_optimizer.zero_grad()loss = 0 # Added onto for each word# Get size of input and target sentencesinput_length = input_variable.size()[0]target_length = target_variable.size()[0]# Run words through encoderencoder_hidden = encoder.init_hidden()encoder_outputs, encoder_hidden = encoder(input_variable, encoder_hidden)# Prepare input and output variablesdecoder_input = torch.LongTensor([[SOS_token]])decoder_hidden = encoder_hidden # Use last hidden state from encoder to start decoderif USE_CUDA:decoder_input = decoder_input.cuda()# Choose whether to use teacher forcinguse_teacher_forcing = random.random() < teacher_forcing_ratioif use_teacher_forcing:# Teacher forcing: Use the ground-truth target as the next inputfor di in range(target_length):decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)loss += criterion(decoder_output, target_variable[di])decoder_input = target_variable[di] # Next target is next inputelse:# Without teacher forcing: use network's own prediction as the next inputfor di in range(target_length):decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)loss += criterion(decoder_output, target_variable[di])# Get most likely word index (highest value) from outputtopv, topi = decoder_output.data.topk(1)ni = topi[0][0]decoder_input = torch.LongTensor([[ni]]) # Chosen word is next inputif USE_CUDA: decoder_input = decoder_input.cuda()# Stop at end of sentence (not necessary when using known targets)if ni == EOS_token: break# Backpropagationloss.backward()torch.nn.utils.clip_grad_norm(encoder.parameters(), clip)torch.nn.utils.clip_grad_norm(decoder.parameters(), clip)encoder_optimizer.step()decoder_optimizer.step()return loss.item() / target_length
    

    下面是用于辅助输出训练情况的函数。

    def as_minutes(s):m = math.floor(s / 60)s -= m * 60return '%dm %ds' % (m, s)def time_since(since, percent):now = time.time()s = now - sincees = s / (percent)rs = es - sreturn '%s (- %s)' % (as_minutes(s), as_minutes(rs))
    
  2. 进行训练

    • 模型初始化

      定义隐藏层大小和层数,初始化模型、优化器,定义损失函数。

      hidden_size = 500
      n_layers = 2
      dropout_p = 0.05# Initialize models
      encoder = EncoderRNN(input_lang.n_words, hidden_size, n_layers)
      decoder = DecoderRNN(hidden_size, output_lang.n_words, n_layers, dropout_p=dropout_p)# Move models to GPU
      if USE_CUDA:encoder.cuda()decoder.cuda()# Initialize optimizers and criterion
      learning_rate = 0.0001
      encoder_optimizer = optim.Adam(encoder.parameters(), lr=learning_rate)
      decoder_optimizer = optim.Adam(decoder.parameters(), lr=learning_rate)
      criterion = nn.NLLLoss()
      

      以下设置变量用于绘制图标和跟踪进度:

      # Configuring training
      n_epochs = 50000
      plot_every = 200
      print_every = 1000# Keep track of time elapsed and running averages
      start = time.time()
      plot_losses = []
      print_loss_total = 0 # Reset every print_every
      plot_loss_total = 0 # Reset every plot_every
      
    • 实际训练

      要进行实际训练,我们会多次调用训练函数,并在进行过程中打印中间信息。

      # Begin!
      for epoch in range(1, n_epochs + 1):# Get training data for this cycletraining_pair = variables_from_pair(random.choice(pairs))input_variable = training_pair[0]target_variable = training_pair[1]# Run the train functionloss = train(input_variable, target_variable, encoder, decoder, encoder_optimizer, decoder_optimizer, criterion)# Keep track of lossprint_loss_total += lossplot_loss_total += lossif epoch == 0: continueif epoch % print_every == 0:print_loss_avg = print_loss_total / print_everyprint_loss_total = 0print_summary = '%s (%d %d%%) %.4f' % (time_since(start, epoch / n_epochs), epoch, epoch / n_epochs * 100, print_loss_avg)print(print_summary)if epoch % plot_every == 0:plot_loss_avg = plot_loss_total / plot_everyplot_losses.append(plot_loss_avg)plot_loss_total = 0
      
  3. 训练结果

    运行训练代码,进行如下打印。

    0m 13s (- 11m 23s) (1000 2%) 5.0433
    0m 27s (- 11m 7s) (2000 4%) 4.6816
    0m 41s (- 10m 46s) (3000 6%) 4.4992
    ...
    11m 17s (- 0m 28s) (48000 96%) 4.0000
    11m 31s (- 0m 14s) (49000 98%) 3.9631
    11m 45s (- 0m 0s) (50000 100%) 3.9057
    

    绘制训练 loss 曲线,结果如下。

    image-20240504164257602

    可以看出,训练最终得到的loss值在3.9左右波动,结果并不是很好。

2.4 模型测试

  1. 测试代码

    该代码用于评估一个给定的输入句子,并生成对应输出的翻译序列。 max_length 指定了输出序列的最大长度,解码器循环 max_length 次,或直到生成结束符号 <EOS>

    def evaluate(sentence, max_length=MAX_LENGTH):input_variable = variable_from_sentence(input_lang, sentence)input_length = input_variable.size()[0]# Run through encoderencoder_hidden = encoder.init_hidden()encoder_outputs, encoder_hidden = encoder(input_variable, encoder_hidden)# Create starting vectors for decoderdecoder_input = torch.LongTensor([[SOS_token]]) # SOSif USE_CUDA:decoder_input = decoder_input.cuda()decoder_hidden = encoder_hiddendecoded_words = []decoder_attentions = torch.zeros(max_length, max_length)# Run through decoderfor di in range(max_length):decoder_output, decoder_hidden = decoder(decoder_input, decoder_hidden)# Choose top word from outputtopv, topi = decoder_output.data.topk(1)ni = topi[0][0]if ni == EOS_token:decoded_words.append('<EOS>')breakelse:decoded_words.append(output_lang.index2word[ni.item()])# Next input is chosen worddecoder_input = torch.LongTensor([[ni]])if USE_CUDA: decoder_input = decoder_input.cuda()return decoded_words
    
  2. 预测测试集数据

    测试集数据 test.txt 是 9688 行的中文文本,编写以下代码,使用 evaluate 翻译 test.txt 为英文。

    def predict(filepath):"""预测"""with open('test.txt') as f:sentences =  [line.strip() for line in f.readlines()]output_sentences = []for sentence in sentences:output_sentence = ' '.join(evaluate(sentence))output_sentences.append(output_sentence.strip('<EOS>'))with open(filepath, 'w') as f:f.write('\n'.join(output_sentences))
    

    翻译结果前10行结果如下。

    OriginalTranslation
    為什麼我一直學不好英語?where is the ? ?
    她讓我坐在她的身邊。she is a to . . .
    這瓶酸奶不含乳糖。this is is a the . . .
    這瓶酸奶不含乳糖。this is is a the . . .
    我不能幫你了。i m t you . . .
    我不能幫你了。i m t you . . .
    湯姆不是一個好司機。tom is mary to . . .
    我會普通話、西南官話、吳語和西班牙語。my father is a the . . .
    這個問題沒有那麼簡單。this is is a the . . .
    他不會說英語也不會說法語。he is a to . . . .

    可以看出,翻译效果很差。只会用一些基本词汇拼接,无法组成一个由完整意思的句子。

3 seq2seq 改进

​ 根据 1.5 改进方向 来对 seq2seq 进行改进。

3.1 使用LSTM替换RNN

  1. LSTM介绍

    LSTM(Long Short-Term Memory)是一种特殊的RNN,由Sepp Hochreiter和Jürgen Schmidhuber在1997年提出。LSTM设计了一种复杂的门控机制来解决传统RNN的梯度消失问题,这使得LSTM能够学习到长距离的依赖关系:

    • 遗忘门(Forget Gate):这个门决定在当前状态下应该丢弃多少之前的信息。它基于当前输入和先前的隐藏状态来计算一个遗忘门的激活值。
    • 输入门:这个门与遗忘门相对,它决定在当前状态下应该记住多少新的信息。输入门会更新单元状态,这是模型中的一个内部状态,可以看作是长期记忆。
    • 单元状态(Cell State):LSTM有一个额外的状态,称为单元状态,它通过遗忘门和输入门的控制来更新。
    • 隐藏状态:LSTM的隐藏状态是基于当前的单元状态和先前的隐藏状态计算得到的,它用作模型的输出。

    LSTM的这种结构使其能够在序列数据中捕捉长期依赖关系,这在许多自然语言处理任务中非常有用,如机器翻译、文本摘要和情感分析。

  2. 代码改进

    将编码器、解码器的RNN替换为LSTM架构,并采用 jieba 进行中文分词。

    • 编码器

      class EncoderLSTM(nn.Module):def __init__(self, input_size, hidden_size, n_layers=1):super(EncoderLSTM, self).__init__()self.input_size = input_sizeself.hidden_size = hidden_sizeself.n_layers = n_layersself.embedding = nn.Embedding(input_size, hidden_size)self.lstm = nn.LSTM(hidden_size, hidden_size, n_layers)def forward(self, word_inputs, hidden):# 注意:我们一次性处理整个输入序列seq_len = len(word_inputs)embedded = self.embedding(word_inputs).view(seq_len, 1, -1)output, hidden = self.lstm(embedded, hidden)return output, hiddendef init_hidden(self):# 初始化隐藏状态和细胞状态hidden = (torch.zeros(self.n_layers, 1, self.hidden_size),torch.zeros(self.n_layers, 1, self.hidden_size))if USE_CUDA:hidden = (hidden[0].cuda(), hidden[1].cuda())return hidden
      
    • 解码器

      class DecoderLSTM(nn.Module):def __init__(self, hidden_size, output_size, n_layers=1, dropout_p=0.1):super(DecoderLSTM, self).__init__()self.hidden_size = hidden_sizeself.output_size = output_sizeself.n_layers = n_layersself.dropout_p = dropout_pself.embedding = nn.Embedding(output_size, hidden_size)self.rnn = nn.LSTM(hidden_size, hidden_size, n_layers, dropout=dropout_p)self.out = nn.Linear(hidden_size, output_size)def forward(self, word_input, last_hidden):word_embedded = self.embedding(word_input).view(1, 1, -1)rnn_output, hidden = self.rnn(word_embedded, last_hidden)rnn_output = rnn_output.squeeze(0)output = F.log_softmax(self.out(rnn_output), dim=1)return output, hidden
      
    • 使用 jieba 进行中文分词

      相比于将中文逐个字进行分词,选择使用 jieba 对中文按词汇短语分词,更容易让模型学习中文。

      def index_words(self, sentence):if self.name == 'cn':words = list(jieba.cut(sentence)) if SEGMENTATION else sentence    for word in words:self.index_word(word)else:words = sentence.split(' ')for word in words:self.index_word(word)
      
  3. 模型训练

    模型仍然训练50000轮次,结果如下。

    Epoch 1000/50000, 0m 21s (- 17m 35s), 5.2286
    Epoch 2000/50000, 0m 43s (- 17m 21s), 4.8161
    Epoch 3000/50000, 1m 5s (- 16m 59s), 4.6575
    ...
    Epoch 48000/50000, 18m 0s (- 0m 45s), 3.4405
    Epoch 49000/50000, 18m 23s (- 0m 22s), 3.4779
    Epoch 50000/50000, 18m 45s (- 0m 0s), 3.4605
    

    Loss曲线如下图所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    可以看到,loss最终下降到3.4左右,相比于基础版本的3.8左右的loss值有明显下降。

  4. 模型测试

    • 文本测试

      使用 evaluate() 预测 test.txt 文件,翻译中文文本,结果前10行结果如下。

      OriginalTranslation
      為什麼我一直學不好英語?why can t we me to ? ?
      她讓我坐在她的身邊。she asked her her her . .
      這瓶酸奶不含乳糖。no one are not to . .
      這瓶酸奶不含乳糖。no one are not to . .
      我不能幫你了。i can t you you . .
      我不能幫你了。i can t you you .
      湯姆不是一個好司機。tom is not a good good .
      我會普通話、西南官話、吳語和西班牙語。i bought a a a of my . .
      這個問題沒有那麼簡單。this is not not this this .
      他不會說英語也不會說法語。he can speak speak speak . .

      可以看出,翻译效果仍然并不是很好。

    • BLEU测试

      随机选取100条数据,生成 cn-eng-test.txt 作为验证集。

      def sample_test_dataset(size=100):with open('cn-eng-test.txt', 'w+') as f:f.write('\n'.join(['\t'.join(pair) for pair in random.sample(pairs, k=size)]))sample_test_dataset()
      

      通过 torchtext.data.metrics 库的 bleu_score 来计算BLEU值。

      import collections
      from torchtext.data.metrics import bleu_score# 读取测试数据集
      with open('cn-eng-test.txt') as f:lines = f.read().strip().split('\n')test_pairs = [[normalize_string(s) for s in l.split('\t')] for l in lines]test_pairs_dict = collections.defaultdict(lambda : [])for pair in test_pairs:test_pairs_dict[pair[0]].append(pair[1].split(' '))def evaluate_bleu_score():candicates = []references = []for i, pair in enumerate(test_pairs_dict.items(), start=1):candicate = evaluate(pair[0])if candicate[-1] == '<EOS>':candicate.pop(-1)candicates.append(candicate)references.append(pair[1])score = bleu_score(candicates, references)return scoreprint('test dataset bleu score: %s' % evaluate_bleu_score())
      

      最终得到的 BLEU 值如下:

      test dataset bleu score: 0.045502024475269916
      

3.2 使用GRU替换RNN

  1. GRU介绍

    **GRU(Gated Recurrent Unit)**是一种使用门控机制来控制信息流的RNN,它由Kyunghyun Cho等人在2014年提出。GRU的核心思想是通过引入两个门(update gate和reset gate)来控制信息的遗忘和记忆:

    • 更新门(Update Gate):这个门决定在当前状态下应该保留多少之前的信息。它基于当前输入和先前的隐藏状态来计算一个更新门的激活值。
    • 重置门(Reset Gate):这个门决定在当前状态下应该忽略多少之前的信息。它允许模型在处理当前输入时“重置”或“忘记”之前的一些信息。
    • 隐藏状态:GRU的隐藏状态同时用作输出状态,这意味着它的输出是隐藏状态的直接输出。

    GRU的结构简化了LSTM,因为它没有单独的单元状态,只有隐藏状态。这使得GRU在某些情况下比LSTM更快、更简单,但也可能导致它无法捕捉比LSTM更长的依赖关系。

  2. 代码改进

    • 将编码器、解码器的RNN替换为GRU架构。

      class EncoderGRU(nn.Module):"""GRU 编码器"""def __init__(self, input_size, hidden_size, n_layers=1, bidirectional=False):super(EncoderGRU, self).__init__()self.input_size = input_sizeself.hidden_size = hidden_sizeself.n_layers = n_layersself.bidirectional = bidirectionalself.embedding = nn.Embedding(input_size, hidden_size)# 用GRU替换RNN# self.rnn = nn.RNN(hidden_size, hidden_size, n_layers)self.rnn = nn.GRU(hidden_size, hidden_size, n_layers, bidirectional=bidirectional)def forward(self, word_inputs, hidden):# Note: we run this all at once (over the whole input sequence)seq_len = len(word_inputs)embedded = self.embedding(word_inputs).view(seq_len, 1, -1)output, hidden = self.rnn(embedded, hidden)return output, hiddendef init_hidden(self):num_directions = 2 if self.bidirectional else 1hidden = torch.zeros(self.n_layers*num_directions, 1, self.hidden_size)if USE_CUDA: hidden = hidden.cuda()return hidden
      
      class DecoderGRU(nn.Module):"""GRU 解码器"""def __init__(self, hidden_size, output_size, n_layers=1, dropout_p=0.1, bidirectional=False):super(DecoderGRU, self).__init__()# Keep parameters for referenceself.hidden_size = hidden_sizeself.output_size = output_sizeself.n_layers = n_layersself.dropout_p = dropout_pself.bidirectional = bidirectional# Define layersself.embedding = nn.Embedding(output_size, hidden_size)# 使用GRU替换RNN# self.rnn = nn.RNN(hidden_size, hidden_size, n_layers, dropout=dropout_p)self.rnn = nn.GRU(hidden_size, hidden_size, n_layers, dropout=dropout_p, bidirectional=bidirectional)self.out = nn.Linear(hidden_size, output_size)def forward(self, word_input, last_hidden):# Note: we run this one step at a time        word_embedded = self.embedding(word_input).view(1, 1, -1) # S=1 x B x Nrnn_output, hidden = self.rnn(word_embedded, last_hidden)rnn_output = rnn_output.squeeze(0)output = F.log_softmax(self.out(rnn_output))return output, hidden
      
    • 使用 jieba 进行中文分词

      相比于将中文逐个字进行分词,选择使用 jieba 对中文按词汇短语分词,更容易让模型学习中文。

      def index_words(self, sentence):if self.name == 'cn':words = list(jieba.cut(sentence)) if SEGMENTATION else sentence    for word in words:self.index_word(word)else:words = sentence.split(' ')for word in words:self.index_word(word)
      
  3. 模型训练

    模型仍然训练50000轮次,结果如下。

    Epoch 1000/50000, 0m 21s (- 17m 25s), 5.0521
    Epoch 2000/50000, 0m 54s (- 21m 56s), 4.6759
    Epoch 3000/50000, 1m 25s (- 22m 21s), 4.5540
    ...
    Epoch 48000/50000, 17m 53s (- 0m 44s), 3.2733
    Epoch 49000/50000, 18m 15s (- 0m 22s), 3.2901
    Epoch 50000/50000, 18m 37s (- 0m 0s), 3.1813
    

    Loss曲线如下图所示。

    image-20240504202556339

    可以看到,loss最终下降到3.0左右,相比于基础版本的3.8左右的loss值有明显下降,也比训练相同轮次 LSTM改进后 的 loss 值更低。

  4. 模型测试

    • 文本测试

      使用 evaluate() 预测 test.txt 文件,翻译中文文本,结果前10行结果如下。

      OriginalTranslation
      為什麼我一直學不好英語?why is he speak english ? ? ?
      她讓我坐在她的身邊。she made me her her . . .
      這瓶酸奶不含乳糖。this can t be in this . .
      這瓶酸奶不含乳糖。this can t be in this . .
      我不能幫你了。i can t you you . .
      我不能幫你了。i m t you you . .
      湯姆不是一個好司機。tom is not a good . .
      我會普通話、西南官話、吳語和西班牙語。i ll take the the the . .
      這個問題沒有那麼簡單。this is not not this . . .
      他不會說英語也不會說法語。he can speak speak speak speak english .

      可以看出,虽然也没有翻译出很完整,但是相比于基础代码有所提升,能够贴近正确翻译。

    • BLEU 测试

      通过 torchtext.data.metrics 库的 bleu_score 来计算BLEU值,最终得到的 BLEU 值如下:

      test dataset bleu score: 0.06819677298621382
      

      相比于 LSTM 改进后的结果,BLEU值更高。

3.3 使用双向的 encoder 获得更好的源语言表示

​ 本小节分别实现了双向的 LSTM 和双向的 GRU 来提升模型能力。

  1. 双向LSTM

    • 代码设计

      **编码器:**将 nn.LSTMbidirectional 参数置为 True ,将 init_hiddenn_layers 翻倍。

      class BioEncoderLSTM(nn.Module):def __init__(self, input_size, hidden_size, n_layers=1):super(BioEncoderLSTM, self).__init__()self.input_size = input_sizeself.hidden_size = hidden_sizeself.n_layers = n_layersself.embedding = nn.Embedding(input_size, hidden_size)# 设置双向LSTMself.lstm = nn.LSTM(hidden_size, hidden_size, n_layers, bidirectional=True)def forward(self, word_inputs, hidden):seq_len = len(word_inputs)embedded = self.embedding(word_inputs).view(seq_len, 1, -1)output, hidden = self.lstm(embedded, hidden)return output, hiddendef init_hidden(self):# 对于双向LSTM,num_directions 应该是2num_directions = 2hidden = (torch.zeros(self.n_layers * num_directions, 1, self.hidden_size),torch.zeros(self.n_layers * num_directions, 1, self.hidden_size))if USE_CUDA:hidden = (hidden[0].cuda(), hidden[1].cuda())return hidden
      

      **解码器:**与单向的解码器相同。

      class DecoderLSTM(nn.Module):def __init__(self, hidden_size, output_size, n_layers=1, dropout_p=0.1):super(DecoderLSTM, self).__init__()self.hidden_size = hidden_sizeself.output_size = output_sizeself.n_layers = n_layersself.dropout_p = dropout_pself.embedding = nn.Embedding(output_size, self.hidden_size)self.rnn = nn.LSTM(self.hidden_size, self.hidden_size, n_layers, dropout=dropout_p)self.out = nn.Linear(self.hidden_size, output_size)def forward(self, word_input, last_hidden):word_embedded = self.embedding(word_input).view(1, 1, -1)rnn_output, hidden = self.rnn(word_embedded, last_hidden)rnn_output = rnn_output.squeeze(0)output = F.log_softmax(self.out(rnn_output), dim=1)return output, hidden
      
    • 模型训练

      因为是双向,解码器 n_layers 变成2倍。

      # Initialize models
      encoder = BioEncoderLSTM(input_lang.n_words, hidden_size, n_layers)
      decoder = DecoderLSTM(hidden_size, output_lang.n_words, n_layers*2, dropout_p=dropout_p)
      

      训练过程如下:

      Epoch 1000/50000, 0m 30s (- 25m 3s), 5.4492
      Epoch 2000/50000, 1m 1s (- 24m 37s), 4.9193
      Epoch 3000/50000, 1m 33s (- 24m 20s), 4.7066
      ...
      Epoch 48000/50000, 36m 30s (- 1m 31s), 3.5253
      Epoch 49000/50000, 37m 1s (- 0m 45s), 3.4787
      Epoch 50000/50000, 37m 32s (- 0m 0s), 3.4415
      

      训练得到的loss曲线如下:

      image-20240505213519292

      可以看到 loss 值降低到 3.4 左右,相比单向的编码器区别不大。

    • 模型测试

      通过 predict 来测试 test.txt 文件,前十条数据翻译如下。

      OriginalTranslation
      為什麼我一直學不好英語?why do i think that is ? ?
      她讓我坐在她的身邊。she asked her her her her .
      這瓶酸奶不含乳糖。the is is t t . . .
      這瓶酸奶不含乳糖。the is is t t . . . .
      我不能幫你了。i can t you you you .
      我不能幫你了。i can t you you you .
      湯姆不是一個好司機。tom is t a good good .
      我會普通話、西南官話、吳語和西班牙語。i can i i to to to . . .
      這個問題沒有那麼簡單。the boy is is to . . .
      他不會說英語也不會說法語。he can speak english english english .

      可以看到,翻译的结果也并没有很准确。

      BLEU值测试如下:

      test dataset bleu score: 0.05062519386410713
      

      BLEU值相比于单向的编码器有所增加。

  2. 双向GRU

    • 代码设计

      **编码器:**将 nn.LSTMbidirectional 参数置为 True ,将 init_hiddenn_layers 翻倍。

      class BioEncoderGRU(nn.Module):"""GRU 编码器"""def __init__(self, input_size, hidden_size, n_layers=1, bidirectional=False):super(BioEncoderGRU, self).__init__()self.input_size = input_sizeself.hidden_size = hidden_sizeself.n_layers = n_layersself.bidirectional = bidirectionalself.embedding = nn.Embedding(input_size, hidden_size)# 用GRU替换RNN# self.rnn = nn.RNN(hidden_size, hidden_size, n_layers)self.rnn = nn.GRU(hidden_size, hidden_size, n_layers, bidirectional=bidirectional)def forward(self, word_inputs, hidden):# Note: we run this all at once (over the whole input sequence)seq_len = len(word_inputs)embedded = self.embedding(word_inputs).view(seq_len, 1, -1)output, hidden = self.rnn(embedded, hidden)return output, hiddendef init_hidden(self):num_directions = 2 if self.bidirectional else 1hidden = torch.zeros(self.n_layers*num_directions, 1, self.hidden_size)if USE_CUDA: hidden = hidden.cuda()return hidden
      

      **解码器:**与单向的解码器相同。

      class DecoderGRU(nn.Module):"""GRU 解码器"""def __init__(self, hidden_size, output_size, n_layers=1, dropout_p=0.1):super(DecoderGRU, self).__init__()# Keep parameters for referenceself.hidden_size = hidden_sizeself.output_size = output_sizeself.n_layers = n_layersself.dropout_p = dropout_p# Define layersself.embedding = nn.Embedding(output_size, hidden_size)# 使用GRU替换RNN# self.rnn = nn.RNN(hidden_size, hidden_size, n_layers, dropout=dropout_p)self.rnn = nn.GRU(hidden_size, hidden_size, n_layers, dropout=dropout_p)self.out = nn.Linear(hidden_size, output_size)def forward(self, word_input, last_hidden):# Note: we run this one step at a time        word_embedded = self.embedding(word_input).view(1, 1, -1) # S=1 x B x Nrnn_output, hidden = self.rnn(word_embedded, last_hidden)rnn_output = rnn_output.squeeze(0)output = F.log_softmax(self.out(rnn_output))return output, hidden
      
    • 模型训练

      训练过程如下:

      Epoch 1000/50000, 0m 39s (- 31m 55s), 4.8897
      Epoch 2000/50000, 1m 18s (- 31m 31s), 4.5166
      Epoch 3000/50000, 1m 58s (- 30m 59s), 4.3445
      ...
      Epoch 48000/50000, 32m 16s (- 1m 20s), 2.4504
      Epoch 49000/50000, 32m 57s (- 0m 40s), 2.4343
      Epoch 50000/50000, 33m 37s (- 0m 0s), 2.4332
      

      训练得到的loss曲线如下:

      image-20240505221351532

      可以看到,loss值下降到2.2左右,相比单向编码器有着明显下降。

    • 模型测试

      通过 predict 来测试 test.txt 文件,前十条数据翻译如下。

      OriginalTranslation
      為什麼我一直學不好英語?why don t i have a english ?
      她讓我坐在她的身邊。she made me angry at the .
      這瓶酸奶不含乳糖。this isn t a little . .
      這瓶酸奶不含乳糖。this isn t a little . .
      我不能幫你了。i can t help you .
      我不能幫你了。i can t help you .
      湯姆不是一個好司機。tom isn t a good . .
      我會普通話、西南官話、吳語和西班牙語。i can speak spanish spanish in english .
      這個問題沒有那麼簡單。this problem is not so .
      他不會說英語也不會說法語。he can t speak speak speak french .

      可以看到,翻译的准确性有很大的提升。

      BLEU值测试如下:

      test dataset bleu score: 0.2297259837529158
      

      BLEU值与单向编码器,双向的LSTM相比,也有着显著的提升。

3.4 使用注意力机制提升模型能力

  1. 注意力机制

    本小节实现了一个简单的注意力机制,允许解码器根据解码器的隐藏状态动态地聚焦于编码器输出的不同部分,并以此生成一个聚合的、加权的编码器表示,这个表示随后可以用于解码器的下一步输入。

    image-20240506202647396

  2. 代码设计

    注意力机制类:采用简单的注意力机制。

    class Attention(nn.Module):def __init__(self, hidden_size):super(Attention, self).__init__()self.hidden_size = hidden_sizedef forward(self, decoder_hidden, encoder_outputs):attn_weights = F.softmax(torch.matmul(torch.squeeze(encoder_outputs), torch.squeeze(decoder_hidden).view(-1, 1)))attn_weights = attn_weights.expand(encoder_outputs.shape[0], -1)attn_output = torch.sum(attn_weights * torch.squeeze(encoder_outputs), dim=0)return attn_output.view(1, 1, -1)
    

    编码器:采用单向GRU。

    class EncoderGRU(nn.Module):"""GRU 编码器"""def __init__(self, input_size, hidden_size, n_layers=1, bidirectional=False):super(EncoderGRU, self).__init__()self.input_size = input_sizeself.hidden_size = hidden_sizeself.n_layers = n_layersself.bidirectional = bidirectionalself.embedding = nn.Embedding(input_size, hidden_size)# 用GRU替换RNNself.rnn = nn.GRU(hidden_size, hidden_size, n_layers)def forward(self, word_inputs, hidden):# Note: we run this all at once (over the whole input sequence)seq_len = len(word_inputs)embedded = self.embedding(word_inputs).view(seq_len, 1, -1)output, hidden = self.rnn(embedded, hidden)return output, hiddendef init_hidden(self):num_directions = 2 if self.bidirectional else 1hidden = torch.zeros(self.n_layers*num_directions, 1, self.hidden_size)if USE_CUDA: hidden = hidden.cuda()return hidden
    

    解码器:采用注意力机制的解码器。

    class DecoderGRU(nn.Module):"""注意力机制解码器"""def __init__(self, hidden_size, output_size, n_layers=1, dropout_p=0.1):super(DecoderGRU, self).__init__()# Keep parameters for referenceself.hidden_size = hidden_sizeself.output_size = output_sizeself.n_layers = n_layersself.dropout_p = dropout_pself.attention = Attention(hidden_size)# Define layersself.embedding = nn.Embedding(output_size, hidden_size)# 使用GRU替换RNNself.rnn = nn.GRU(hidden_size, hidden_size, n_layers, dropout=dropout_p)self.out = nn.Linear(hidden_size*2, output_size)def forward(self, word_input, last_hidden, encoder_outputs):# Note: we run this one step at a time        word_embedded = self.embedding(word_input).view(1, 1, -1) # S=1 x B x Nrnn_output, hidden = self.rnn(word_embedded, last_hidden)rnn_output = rnn_output.squeeze(0)# attention weighted encoder outputattn_weighted_encoder_output = self.attention(hidden, encoder_outputs)attn_weighted_encoder_output = attn_weighted_encoder_output.squeeze(0)concat_output = torch.cat([rnn_output, attn_weighted_encoder_output], dim=1)output = F.log_softmax(self.out(concat_output))return output, hidden
    
  3. 模型训练

    本次实验采用150000 epoch 的训练轮次,训练过程如下:

    Epoch 1000/150000, 0m 22s (- 56m 33s), 4.8073
    Epoch 2000/150000, 0m 46s (- 57m 43s), 4.3964
    Epoch 3000/150000, 1m 10s (- 57m 15s), 4.2832
    ...
    Epoch 148000/150000, 77m 34s (- 1m 2s), 1.7956
    Epoch 149000/150000, 77m 58s (- 0m 31s), 1.8039
    Epoch 150000/150000, 78m 23s (- 0m 0s), 1.7942
    

    loss曲线如下:

    image-20240506204155351

    经过长时间的训练,loss最终降低到了1.6左右。

  4. 模型验证

    通过 predict 来测试 test.txt 文件,前十条数据翻译如下。

    OriginalTranslation
    為什麼我一直學不好英語?why should i always been english ?
    她讓我坐在她的身邊。she let me by her sit .
    這瓶酸奶不含乳糖。the does not take the . .
    這瓶酸奶不含乳糖。the does not take the . .
    我不能幫你了。i couldn t help you .
    我不能幫你了。i couldn t help you .
    湯姆不是一個好司機。tom isn t a good . .
    我會普通話、西南官話、吳語和西班牙語。i like this meeting and may . .
    這個問題沒有那麼簡單。this question is not . .
    他不會說英語也不會說法語。he can speak english can speak english english .

    可以看到翻译效果有所提升。

    BLEU值测试如下:

    test dataset bleu score: 0.3370101494002167
    

    BLEU值经过长时间训练,也有所提高。

4 总结

​ 可以看到,通过多种改进方式,有效提升了模型能力,最终采用了训练 150000 epoch 轮次的双向GRU编码器,训练结果最好。

  1. 模型训练

    训练过程如下:

    Epoch 1000/150000, 0m 38s (- 96m 7s), 4.9300
    Epoch 2000/150000, 1m 18s (- 97m 7s), 4.5659
    Epoch 3000/150000, 1m 58s (- 96m 43s), 4.3506
    ...
    Epoch 148000/150000, 100m 45s (- 1m 21s), 1.4097
    Epoch 149000/150000, 101m 26s (- 0m 40s), 1.4008
    Epoch 150000/150000, 102m 7s (- 0m 0s), 1.4062
    

    loss曲线如下:

    image-20240506211003618

  2. 模型验证

    通过 predict 来测试 test.txt 文件,前十条数据翻译如下。

    OriginalTranslation
    為什麼我一直學不好英語?why don t i have any english english ?
    她讓我坐在她的身邊。she asked her on her side .
    這瓶酸奶不含乳糖。this bottle has not gone to .
    這瓶酸奶不含乳糖。this bottle has not gone to this
    我不能幫你了。i m not help you .
    我不能幫你了。i m not help you .
    湯姆不是一個好司機。tom isn t a good driver .
    我會普通話、西南官話、吳語和西班牙語。i m getting spanish and my brother .
    這個問題沒有那麼簡單。this question is not that . .
    他不會說英語也不會說法語。he can t speak english and french .

    可以看出,这组的翻译结果最好。

    BLEU值测试如下:

    test dataset bleu score: 0.5388663729613064
    

    BLEU值也是整体训练结果最高的。

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

深度解读《深度探索C++对象模型》之C++的临时对象(二)

目录 临时对象的生命期 特殊的情况 接下来我将持续更新“深度解读《深度探索C对象模型》”系列&#xff0c;敬请期待&#xff0c;欢迎左下角点击关注&#xff01;也可以关注公众号&#xff1a;iShare爱分享&#xff0c;或文章末尾扫描二维码&#xff0c;自动获得推文和全部的…

VTK数据的读写--Vtk学习记录1--《VTK图形图像开发进阶》

读和写操作是VTK可视化管线两端相关的类--Reader和Writer类 Reader:将外部数据读入可视化管线&#xff0c;主要步骤如下 s1:实例化Reader对象 s2:指定所要读取的文件名 s3:调用Update()促使管线执行 对应的Writer: s1:实例化Writer对象 s2输入要写的数据以及指定写入的文…

如何实现网页上3D模型的展示、浏览和互动?

实现网页上3D模型的展示、浏览和互动&#xff0c;可以通过以下步骤进行&#xff1a; 1、创建3D内容&#xff1a;使用3ds max、Maya、blender、C4D等3D软件制作好3D模型。 2、设计3D应用&#xff1a;把制作好的模型导出为fbx、obj、dae、gltf、glb等格式文件&#xff0c;上传到…

^_^填坑备忘^_^C#自动化编程实现STK+Exata对卫星互联网星座进行网络仿真

C#实际选择 STK11版本 or STK12版本的问题备注。 【C#自动化客户端调用STK时&#xff0c;实际选择 STK11版本 or STK12版本 的调试运行备注】 以下代码“更新并重新打包备份为”〔testSTKQualNetInterface备份08.1_★避坑★【种子卫星&#xff1a;天线直接安装在卫星上&#…

check startup检查各种资源文件

check startup 命令功能 check startup命令用来检查各种资源文件&#xff08;paf文件、补丁包、启动软件、配置文件&#xff09;是否正确。 命令格式 check startup [ crc ] [ next ] 参数说明 参数参数说明取值 crc 对资源文件进行CRC校验。 - next 检查下一次启动的各…

scitb5函数2.1版本(交互效应函数P for interaction)发布----用于一键生成交互效应表、森林图

写在前面的话&#xff0c;此函数不适用于NHANES数据&#xff0c;也不能用于COX回归,请注意甄别。 在SCI文章中&#xff0c;交互效应表格&#xff08;通常是表五&#xff09;几乎是高分SCI必有。因为增加了亚组人群分析&#xff0c;增加了文章的可信度&#xff0c;能为文章锦上添…

规培报名身份证上传怎么小于500k?这几个方法试试看

大家都知道在规培报名的时候&#xff0c;是需要上传一些自己的个人信息资料到平台上的&#xff0c;其中身份证照片是比较重要的一项&#xff0c;我们自己拍的身份证照片大小有时候可能不符合网站的规定&#xff0c;需要去做一些图片修改调整&#xff0c;比如图片你压缩&#xf…

OpenAI潜入黑客群聊!盗用ChatGPT被换成“喵喵GPT”,网友:绝对的传奇

当ChatGPT被**黑客“入侵”**时&#xff0c;OpenAI会如何应对&#xff1f; 掐断API&#xff0c;不让他们用&#xff1f;不不不。 这帮极客们采取的做法可谓是剑走偏锋——反手一记《无间道》。 故事是这样的。 OpenAI虽然在发布ChatGPT之前做了大量的安全性检测&#xff0c;…

基于FPGA的数字信号处理(10)--定点数的舍入模式(1)四舍五入round

1、前言 将浮点数定量化为定点数时&#xff0c;有一个避不开的问题&#xff1a;某些小数是无法用有限个数的2进制数来表示的。比如&#xff1a; 0.5(D) 0.1(B) 0.1(D) 0.0001100110011001~~~~(B) 可以看到0.5是可以精准表示的&#xff0c;但是0.1却不行。原因是整数是离散的…

物流EDI:GEFCO EDI 需求分析

GEFCO专注于汽车物流领域近70年&#xff0c;是欧洲整车市场的物流供应商&#xff0c;也是欧洲十大领先的运输和物流集团之一。GEFCO的业务遍及六大洲&#xff0c;业务覆盖150个国家&#xff0c;在全球拥有庞大的员工队伍&#xff0c;在全球汽车行业的挑战中茁壮成长。为汽车制造…

如何通过 4 种方式在 Mac 上恢复未保存的 Excel 文件

您曾在 MacBook 上花费数小时处理 Excel 工作簿&#xff0c;但现在它消失了。或者&#xff0c;当您退出 Excel 文件时&#xff0c;您无意中选择了“不保存”。这是不是说你所有的努力都白费了&#xff1f;本文系统地介绍了如何在 Mac 上恢复丢失的 Excel 文件。通过我们的 4 种…

响应式编程Spring Reactor探索

一&#xff0c;介绍 响应式编程&#xff08;Reactive Programming&#xff09;&#xff0c;简单来说是一种生产者只负责生成并发出数据/事件&#xff0c;消费者来监听并负责定义如何处理数据/事件的变化传递方式的编程思想。 响应式编程借鉴了Reactor设计模式&#xff0c;我们…

孩子用什么样的灯对眼睛没有伤害?分享多款满分护眼台灯

为人父母以后&#xff0c;深感压力山大。如今不仅要抓孩子的学习&#xff0c;还得时刻关注孩子的身心健康&#xff0c;尤其是视力问题。现在不少学生都存在近视的现象&#xff0c;而导致这一现象的主要原因&#xff0c;除了平时的学业压力过大以外&#xff0c;夜晚学习的光线也…

第二证券|为什么指数涨回来了钱没回来?

在a股市场上&#xff0c;常常会呈现指数涨回来了钱没回来的状况&#xff0c;呈现这种状况的原因如下&#xff1a; 1、大盘上涨是权重股所造成的 大盘上涨或许是受一些权重比较大的职业所影响&#xff0c;比方证券职业、钢铁职业、银行职业等等&#xff0c;这些职业的大涨&…

Progesterone(孕酮/黄体酮) ELISA检测试剂盒--3小时内可得到检测结果

孕酮&#xff08;Progesterone&#xff09;又称为黄体酮&#xff0c;是卵巢分泌的具有生物活性的主要孕激素&#xff0c;负责与生殖有关的活动&#xff0c;如乳房腺体发育、参与月经周期以及妊娠的建立和维持。此外&#xff0c;孕酮还参与支持妊娠期间的生理过程&#xff0c;包…

致远M3 log 敏感信息泄露漏洞

文章目录 免责漏洞描述漏洞原理影响版本漏洞复现修复方法 免责 只为学习与交流&#xff0c;若利用做一切违法乱纪的事与本人无关 漏洞描述 致远M3是一个企业移动业务管理平台&#xff0c;全面覆盖各种工作场景&#xff0c;通过智能化的办公和业务场景融合&#xff0c;为企业…

Cloudera的简介及安装部署

简介 Cloudera是一家位于美国的软件公司&#xff0c;成立于2008年&#xff0c;专注于为企业客户提供基于Apache Hadoop的软件、支持、服务以及培训。Cloudera的开源Apache Hadoop发行版&#xff0c;即Cloudera Distribution including Apache Hadoop&#xff08;CDH&am…

Linux中的软连接和硬链接

一、软和硬链接连接 在Linux系统中&#xff0c;软连接&#xff08;符号链接&#xff09;和硬链接是文件系统中两种不同类型的链接&#xff0c;它们用于创建对文件的引用。下面详细解释这两种链接的特点和区别&#xff1a;、 软连接&#xff08;符号链接&#xff09; 定义&…

项目解决方案:多台poe摄像机接到3台NVR上,如何进行统一管理

目录 一、概述 二、建设目标及需求 三、设计依据与设计原则 1、先进性与适用性 2、经济性与实用性 3、可靠性与安全性 4、开放性 5、可扩充性 6、追求最优化的系统设备配置 7、提高监管力度与综合管理水平 四、建设方案设计 &#xff08;一&#xff09;系统方案设计…

旋转矩阵(将坐标轴旋转)

旋转矩阵&#xff08;将坐标轴旋转&#xff09; 在二维空间中&#xff0c;旋转可以用一个单一的角 定义。作为约定&#xff0c;正角表示逆时针旋转。把笛卡尔坐标的列向量关于原点逆时针旋转的矩阵是&#xff1a; 原坐标系下 坐标系逆时针旋转β 补充 sin(-a) -sin(a) co…