TensorFlow谷歌神经机器翻译 seq2seq教程 | 从零开始打造属于你的翻译系统

赞赏 2017-07-13

机器翻译——自动在两种语言之间进行翻译的任务——是机器学习中最活跃的研究领域之一。

在多种机器翻译方法中,序列到序列(“seq2seq”)模型最近取得了巨大的成功,并已经成为大多数商业翻译系统的事实上的标准,例如谷歌翻译。这是由于 seq2seq 模型能够利用深度神经网络捕捉句子意义。

但是,虽然 seq2seq 模型(例如 OpenNMT 或 tf-seq2seq)有大量的资料,但是缺少可以同时教知识和构建高质量翻译系统的技能的教程。


谷歌今天公布了一个用 TensorFlow 构建神经机器翻译(NMT)系统的教程,全面解释 seq2seq 模型,并演示如何从零开始构建 NMT 翻译模型。这个教程从 NMT 的背景知识讲起,并提供构建一个 NMT 系统的代码细节。接着,教程讲解注意力机制(attention mechanism),这是让 NMT 能够处理长句子的关键。最后,教程提供如何复制谷歌的 NMT 系统(GNMT)中的关键功能,在多个 GPU 上进行训练的详细信息。


这一教程还包括详细的基准测试结果,使用者可以自行复制。谷歌的模型提供了强大的开源基准,性能与 GNMT 的结果相当,在流行的 WMT'14 英语 - 德语翻译任务上实现了 BLEU 得分 24.4 的性能。


教程还包括其他基准测试结果(英语 - 越南语,德语 - 英语)。


此外,这个教程还提供了完全动态的 seq2seq API(与 TensorFlow 1.2 一起发布),旨在使构建 seq2seq 模型更加简洁:

  • 使用tf.contrib.data中新的输入管道轻松读取和预处理动态大小的输入序列。

  • 使用padded batching和sequence length bucketing来提高训练和推理速度。

  • 使用流行的架构和训练schedule训练seq2seq模型,包括几种类型的attention和scheduled sampling。

  • 使用in-graph beam search在seq2seq模型中执行推理。

  • 为多GPU设置优化seq2seq模型。


希望这一教程有助于研究界创造更多新的NMT模型并进行实验。

完整教程的GitHub地址:https://github.com/tensorflow/nmt

本文提供主要内容的翻译介绍。


神经机器翻译(seq2seq)教程

作者:Thang Luong, Eugene Brevdo, Rui Zhao

目录


  • 导言

  • 基础

       神经机器翻译背景知识

          安装教程

          训练——如何构建你的第一个NMT系统

                 嵌入

                 编码器

                 解码器

                 损失

                 梯度计算和优化

          实践——让我们开始训练一个NMT模型

          推理——如何生成翻译

  • 中级教程

          注意力机制的背景知识

          Attention Wrapper API

          实践——构建一个以注意力为基础的NMT模型

  • 提示与技巧

          构建训练,评估和推理图

          数据输入管道

          更好的NMT模型的其他细节

                 双向RNN

                 束搜索(Beam Search)

                 超参数

                 多GPU训练

  • 基准

          IWSLT英语 - 越南语

          WMT德语 - 英语

          WMT英语 - 德语(完全比较)

  • 其他资源

  • 致谢

  • 参考文献


导言


序列到序列(seq2seq)模型(Sutskever et al.,2014,Cho et al.,2014)在机器翻译、语音识别、文本概况等各种任务中取得了巨大的成功。

本教程提供了对 seq2seq 模型的全面解释,并演示了如何从头开始构建一个具有竞争力的 seq2seq 模型。

我们专注于神经机器翻译(NMT)任务,这是第一个大获成功的 seq2seq 模型的测试平台。

教程中包含的代码是轻便,高质量,生产就绪,并结合了最新的研究观点的。

我们通过以下方式实现这一目标:

  1. 使用最新的解码器/注意力包装 API,TensorFlow 1.2 数据迭代器
  2. 结合我们在构建循环模型和 seq2seq 模型方面的专长
  3. 提供构建最好的 NMT 模型以及复制谷歌的 NMT(GNMT)系统的提示和技巧。


我们认为,最重要的是提供可以让人轻松复制的基准。因此,我们提供了完整的实验结果,并在以下公开数据集对模型进行了预训练:

  • 小规模:IWSLT Evaluation Campaign 提供的 TED 演讲(133K句子对)的英语 - 越南语平行语料库。

  • 大规模:WMT Evaluation Campaign 提供的德语 - 英语平行语料库(4.5M句子对)。

我们首先提供构建 NMT 的 seq2seq 模型的一些基本知识,说明如何构建和训练一个 NMT 模型。第二部分将详细介绍构建一个有竞争力的 NMT 模式的注意力机制。最后,我们将提供一些提示和技巧,以构建最佳性能的 NMT 模型(包括训练速度和翻译质量),例如 TensorFlow 的最佳实践(batching, bucketing),bidirectional RNN 和 beam search。


基础


神经机器翻译的背景知识

回到过去,传统的基于短语的翻译系统是通过将源语言的句子分解成多个部分,然后逐个短语地进行翻译。

这导致机器翻译的结果与人类翻译的结果很不同。人类是通读整个源句子,理解它的含义,然后进行翻译。神经机器翻译(NMT)模拟了这样的过程!


图1:编码器-解码器架构,NMT的一个通用方法的示例。编码器将源句子转换成一个“meaning”向量,这个向量通过解码器传递,产生翻译结果。


具体来说,NMT 系统首先使用编码器读取源语句来构建“meaning”向量,即表示句子意义的一个数字序列; 然后,解码器处理句子向量以输出翻译结果,如图1所示。这一架构同城被称为编码器-解码器架构(encoder-decoder architecture)。以这种方式,NMT 解决了传统的基于短语的方法中翻译局部性的问题:它可以捕获语言的远距离依赖性,例如性一致, 句法结构,等等,并产生更流畅的翻译,如谷歌的神经机器翻译系统所演示的。


NMT 模型的具体结构有所不同。序列数据的一般选择是大多数NMT模型使用的循环神经网络(RNN)。通常,RNN用于编码器和解码器。但是,RNN模型在以下方面不同:(a)方向性——单向或双向; (b)深度——单层或多层; 和(c)类型——通常是普通RNN,长短期记忆(LSTM)或循环门单位(Gated Recurrent Unit, GRU)。有兴趣的读者可以在这篇博客文章了解有关RNN和LSTM的更多信息:http://colah.github.io/posts/2015-08-Understanding-LSTMs/


在本教程中,我们将一个单向的深度多层RNN作为示例,并将LSTM作为一个循环单元。

图2 是这样一个模型的例子。在这个示例中,我们构建一个模型来将源句子“I am a student”翻译成一个目标句子“Je suisétudiant”。在高层水平上,NMT模型由两个循环神经网络组成:编码器RNN简单地处理输入的源词汇,不进行任何预测; 另一方面,解码器RNN在预测下一个单词的同时处理目标句子。


更多信息请参阅Luong(2016)的教程(https://github.com/lmthang/thesis),本教程正是基于这个教程的扩充。


图2:神经机器翻译——将源句子“I am a student”翻译成目标句子“Je suisétudiant”,这是一个深度循环架构的例子。这里,“<s>”表示解码处理的开始,“</ s>”提示解码器停止。


安装教程


要安装本教程,你需要在系统上安装TensorFlow。本教程要求最新版本的TensorFlow(version 1.2.1)。要安装TensorFlow,请按照官方的安装说明进行操作(https://www.tensorflow.org/install)。


安装好TensorFlow之后,您可以通过运行下面的代码下载本教程的源代码:

git clone https://github.com/tensorflow/nmt/


训练——如何构建你的第一个NMT系统


我们先用一些具体的代码片段看看构建一个NMT模型的核心,详细解释一下图2。我们后面会提供数据准备和完整代码。这部分涉及model.py文件。


在底层,编码器RNN和解码器RNN作为输入接收以下内容:首先是源句子(source sentence),然后是一个边界标记“<s>”,提示从编码模式到解码模式的切换,最后是目标句子(target sentence)。对于训练过程,我们将为系统提供以下张量,它们是time-major的格式,包含单词索引:


encoder_inputs [max_encoder_time, batch_size]: 源输入单词

decoder_inputs [max_decoder_time, batch_size]: 目标输入单词

decoder_outputs [max_decoder_time, batch_size]: 目标输出单词,即 decoder_inputs左移动一个时间步长,同时在右边附一个句末标记。


为了提高效率,我们一次训练多个句子(batch_size)。测试过程略有不同,我们会在后面讨论。


嵌入


给定词类属性,模型必须先查找源和目标嵌入以检索相应的词汇表示。为了使嵌入层工作,首先要为每种语言选择一个词汇表。通常,选择词汇大小V,并且只有最常用的V词汇被视为唯一的。其他所有词汇都转换成一个“unknown”字符(token),并且都得到相同的嵌入。通常在训练期间学习嵌入的权重,每种语言一套。


同样,我们可以构建 embedding_decoder 和 decode_emb_inp。请注意,可以选择使用预训练的单词表示(例如 word2vec 或 Glove vector)来初始化嵌入权重。一般来说,给定大量训练数据,我们可以从头开始学习这些嵌入。


编码器


一旦被检索到,那么嵌入词汇就作为输入被喂入主网络中,该主网络由两个多层RNN组成——用于源语言的编码器和用于目标语言的解码器。这两个RNN原则上可以共享相同的权重; 但是,在实践中,我们经常使用两种不同的RNN参数(这些模型在拟合大型训练数据集时做得更好)。编码器RNN使用零向量作为起始状态,构建如下:



请注意,句子具有不同的长度以避免计算上的浪费,我们通过source_seqence_length 告诉 dynamic_rnn 确切的源句子长度。由于我们的输入是 time major 的,因此设置 time_major = True。 在这里,我们只构建一个单层LSTM,encoder_cell。在后面的部分将介绍如何构建多层 LSTM,添加 dropout,以及使用 attention。


解码器


解码器也需要访问源信息,一个简单的方法就是用编码器的最后一个隐藏状态(encode_state)来初始化解码器。 在图2中,我们将源代码“student”的隐藏状态传递到解码器端。




这里,代码的核心部分是 BasicDecoder ,接收 decode_cell(类似于encoder_cell)的 decoder,一个 helper,以及作为输出的前一个 encoder_state。通过分开 decoders 和 helpers,我们可以重复利用不同的代码库,例如,可以用 reedyEmbeddingHelper 替代 TrainingHelper 进行 greedy decoding。更多信息请查看 helper.py。


最后,我们还没提到 projection_layer,它是一个密集矩阵(dense matrix),用于将顶部的隐藏状态转换为维度V的对数向量(logit vectors)。这个过程在图2的顶部说明了。


损失


有了上面的 logits,现在可以计算训练损失:

这里,target_weights 是与 decode_outputs 大小相同的0-1矩阵,它将目标序列长度之外的位置填充为值为0。


重要注意事项:我们用 batch_size 来分割损失,所以我们的超参数对 batch_size是“不变的”。有的人将损失以 batch_size * num_time_steps 进行分割,这可以减少短句子的翻译错误。更巧妙的是,我们的超参数(应用于前面的方法)不能用于后面的方法。例如,如果两种方法都使用学习律为1.0的SGD,那么后一种方法有效利用更小的学习率,即1 / num_time_steps。


梯度计算和优化


我们现在已经定义NMT模型的前向传播。计算反向传播只需要几行代码:



训练RNN的重要步骤之一是梯度剪切(gradient clipping)。这里,我们按照global norm警醒剪切。最大值max_gradient_norm通常设置为5或1。最后一步是选择优化器。Adam优化器是常见的选择。也需要选择学习率(learning rate)。learning_rate的值通常在0.0001到0.001之间; 也可以设置为随着训练的进行,学习率降低。



在我们自己的实验中,我们使用标准SGD(tf.train.GradientDescentOptimizer)以及可降低的学习率设置,从而产生更好的性能。具体见benchmark部分。


实践——训练一个NMT模型


让我们开始训练第一个NMT模型,将越南语翻译成英语!代码的入口点是 nmt.py


我们将使用一个小型的TED 演讲(133K训练样本)的平行语料库来进行这个实践。我们在这里使用的所有数据可以在下面网址找到:https://nlp.stanford.edu/projects/nmt/。我们将使用tst2012作为dev数据集,tst2013作为测试数据集。


运行以下命令下载训练NMT模型的数据:nmt/scripts/download_iwslt15.sh /tmp/nmt_data


运行以下命令开始训练:



上面的命令训练一个具有128-dim的隐藏单元和12个epoch的嵌入的2层LSTM seq2seq模型。我们使用的dropout值为0.2(保持或然率为0.8)。如果不出现error,随着训练的困惑度值(perplexity value)降低,应该可以看到类似下面的logs:



详细信息请参阅train.py。


我们可以在训练期间启动Tensorboard来查看模型的概要:

tensorboard --port 22222 --logdir /tmp/nmt_model/


以上是从英语翻译成越南语的训练,通过下面的代码可以简单地变成从越南语翻译成英语:

--src=en --tgt=vi


推理——如何生成翻译


在训练NMT模型时(以及已经训练完时),你可以得到之前模型没见过的源句子的翻译。这个过程称为推理(inference)。训练和推理(测试)之间有明确的区别:在推理时,我们只能访问源句子,即encoder_inputs。执行解码有很多种方法。解码方法包括greedy解码,采样解码和束搜索(beam-search)解码。这里,我们将讨论贪心解码策略。


它的想法是很简单的,如图3:


  • 我们仍然以与训练期间相同的方式对源句子进行编码,以获得encoder_state,并使用该encoder_state来初始化解码器。

  • 一旦解码器接收到开始符号“<s”(参见代码中的tgt_sos_id),就开始进行解码(转换)处理。

  • 对于解码器侧的每个时间步长,我们将RNN的输出视为一组logits。我们选择最有可能的单词,即与最大logit值相关联的id作为输出的单词(这就是“greedy”行为)。例如在图3中,在第一个解码步骤中,单词“moi”具有最高的翻译概率。然后,我们将这个词作为输入提供给下一个时间步长。

  • 这个过程继续进行,直到生成句尾标记“</ s>”作为输出符号(在我们的代码中是tgt_eos_id)。


图3:Greedy解码——训练好的NMT模型使用greedy搜索生成源句子“Je suisétudiant”的翻译。


令推理与训练不同的是步骤3。推理使用模型预测的单词,而不是总是正确的目标单词作为输入。以下是实现greedy解码的代码。它与解码器的训练代码非常相似。



在这里,我们使用GreedyEmbeddingHelper而不是TrainingHelper。由于我们预先不知道目标序列长度,所以使用maximum_iterations来限制翻译长度。 一个启发是解码最多两倍的源句子长度。



训练好一个模型后,现在可以创建一个推理文件并翻译一些句子:



注意,上述命令也可以在模型正在训练时运行,只要存在一个训练的检查点。 详细请参阅inference.py。


进阶版:注意力机制

说完了最基本的 seq2seq 模型后,下面是进阶版!


注意力机制:背景


为了建立最先进的神经机器翻译系统,我们将需要更多的“特殊材料”:注意力机制,这是 Bahdanau 等人于 2015 年首次引入,然后由 Luong 等人在同年完善的。注意力机制的关键在于通过在翻译过程中,对相关来源内容进行“注意”,建立目标与来源之间的直接连接。注意力机制的一个很好的副产品,是源和目标句子之间的对齐矩阵(如图 4 所示)。


图4:注意力机制可视化:源和目标句子之间的比对的例子。图像来自论文 Bahdanau et al.,2015。


在简单的 seq2seq 模型中,开始解码时,我们将最后的源状态从编码器传递到解码器。这对比较短和中等长度的句子效果很好;然而,对于长句子,单个固定大小的隐藏状态就成了信息瓶颈。注意力机制并不是丢掉在源 RNN 中计算的所有隐藏状态,而是让解码器将它们视为源信息的动态存储器。通过这样做,注意力机制改善了较长句子的翻译质量。如今,注意力机制成为神经机器翻译的首选,而且也成功应用于许多其他任务(包括图说生成,语音识别和文本摘要)。


我们现在介绍注意力机制的一个实例,这个实例是 Luong 等人在 2015 年论文中提出的,已被用于 OpenNMT 开放源码工具包等多个最先进的系统,TF seq2seq API 教程中也使用了这个例子。


图5:注意力机制:Luong 等人 2015 年所述的基于注意力的 NMT 系统的例子。这里详细介绍了注意力计算的第一步。为了清楚起见,没有将图 2 中的嵌入和投射层绘制出来。


如图 5 所示,注意力计算在每个解码器时间步长都有发生,包括以下阶段:


  1. 比较当前目标隐藏状态与所有源状态,获得注意力权重“attention weight”(可以如图 4 所示);

  2. 基于注意力权重,计算上下文矢量(context vector),作为源状态的加权平均值;

  3. 将上下文矢量与当前目标隐藏状态相结合,产生最终的注意力向量“attention vector”;

  4. 注意力向量作为输入,被传递到下一个时间步。


注意力机制中最关键的是什么?


根据 score 函数和 loss 函数的不同,存在很多不同的注意力变体。但在实践中,我们发现只有特定的一些选择很重要。首先是注意力的基本形式,也即目标和源之间的直接关系。 其次是将注意力向下馈送到下一个时间步长,这是告知网络过去的注意力做了什么决定(Luong 等人,2015)。最后,score 函数的选择往往会导致性能表现不同。


AttentionWrapper API


在部署 AttentionWrapper 时,我们借鉴了 Weston 等人 2015 年在 memory network 方面的一些术语。与可读写的 memory 不同,本教程中介绍的注意力机制是只读存储器。具体来说,源的一组隐藏状态被作为“记忆”(memory)。在每个时间步长中,使用当前目标隐藏状态作为“query”来决定要读取 memory 的哪个部分。通常,query 需要与对应于各个内存插槽的 key 进行比较。在我们的介绍中,恰好将源隐藏状态作为“key”。你可以受到记忆网络术语的启发,得出其他形式的注意力!


由于有了 attention wrapper,用 attention 扩展普通 seq2seq 代码就十分简单了。这部分参考文件 attention_model.py


首先,我们需要定义注意机制,例如(Luong等人,2015):



在以前的 Encoder 部分中,encoder_outputs 是顶层所有源隐藏状态的集合,其形状为 [max_time,batch_size,num_units](因为我们将 dynamic_rnn 与 time_major 设置为 True)。对于注意力机制,我们需要确保传递的“记忆”是批处理的,所以需要转置 attention_states。 将 source_sequence_length 传递给注意力机制,以确保注意力权重正确归一化(仅在 non-padding 位置上发生)。


定义了注意力机制后,使用 AttentionWrapper 解码单元格:



代码的其余部分与 Decoder 那节是一样的!


实践:构建基于注意力的 NMT 模型


为了实现注意力,我们需要使用 luong,scaled_luong,bahdanau 或 normed_bahdanau 中的一个,作为训练期间的注意力 flag 的值。这个 flag 指定了我们将要使用的注意力机制。 我们还需要为注意力模型创建一个新的目录,这样才不会重复使用以前训练过的基本 NMT 模型。


运行以下指令开始训练:



在训练完成后,使用同样的推理指令 model_dir 做推理:




玩转 NMT:窍门和技巧

构建训练图、评估图和推理图


在 TensorFlow 中构建机器学习模型时,最好建立 3 个独立的图:


  • 首先是训练图,其中:

  1. 批次、bucket 和可能的子样本从一组文件/外部输入输入;

  2. 包括前向和后向 op;

  3. 构建优化器,并添加训练 op。


  • 其次是评估图,其中:

  1. 批次和 bucket 从一组文件/外部输入数据;

  2. 包括 1 个训练前向 op 和不用于训练的其他评估 op


  • 最后是推理图,其中:

  1. 可能不批量输入数据;

  2. 不会对输入数据进行子采样;

  3. 从占位符读取输入数据

  4. 包括模型前向 op 的一个子集,也可能含有用于存储 session.run 调用之间状态的其他特殊输入/输出。


构建单独的图有几个好处:


  • 推理图通常与其他两个不同,因此需要分开构建;

  • 这样评估图也更简单,因为没有了额外的反向 op;

  • 可以为每个图分别实现数据馈送;

  • 各种重用都更加简单。例如,在评估图中,不需要用 reuse = True 重新打开可变范围,因为训练模型已经创建了这些变量。不需要到处使用 reuse=;

  • 在分布式训练中,训练、评估和推断分开用不同的机器做很正常。反正都需要各自建图。因此,分开建图也有助于你构建分布式训练系统。


主要的问题是,在只有单机的情况下,如何在 3 个图中共享变量 Variables。这可以通过为每个图使用单独的 session 来解决。训练 session 定期保存检查点,评估和推理 session 定期从检查点恢复参数。


下面的例子显示了两种方法的主要区别。


1. 统一建图:一个图里 3 个模型



2. 分别建图:3 个 session 共享变量 



注意,后一种方法很容易就能转换为分布式版本。


另一个区别在于,我们使用了有状态的迭代器对象,而不是使用 feed_dicts 来在每个 session.run 调用中提供数据。这些迭代器使输入管道在单机和分布式设置中都容易得多。


其他技巧:双向 RNN


编码器的双向性通常会带来更好的性能(但由于使用了更多层,速度会有一些降低)。在这里,我们给出一个简单的例子,说明如何用单个双向层构建编码器:



其他技巧:Beam Search


虽然贪婪解码得出的翻译质量不错,但是 beam search 解码器可以进一步提高性能。Beam search 在翻译时总是将一小部分顶级候选词留在身边,从而在搜索空间更好地探索所有可能的翻译。 Beam 的大小称为“宽度”width;大小为 10 的宽度基本就够了。以下是 Beam search 的示例:



其他技巧:超参数


有些超参数能带来性能的进一步提升。以下是根据我们的经验列出的一些超参数:


  • 优化函数:虽然在“不太熟悉”的架构里,Adam 能带来不错的结果,但如果你能训练 SGD,SGD 通常会更好;

  • 注意力:Bahadnau 风格的注意力需要解码器双向性才好用;Luong 风格的注意力在不同设置下都挺好。在这份教程中,我们推荐两个变体: scaled_luong & normed bahdanau


其他技巧:多 GPU 训练


训练一个 NMT 模型需要好几天。将不同的 RNN 层放在不用的 GPU 上能提升训练速度。以下为一个例子:



你可能会发现,随着 GPU 数量的增长,基于注意力的 NMT 模型训练速度提升非常有限。这是因为标准注意力架构在每个时间步长使用顶层(最后一层)的输出做为 query 注意力。这意味着每一次解码都需要等前面的步骤完全结束了才行。因此,无法在多台 GPU 上并行解码 RNN。


谷歌提出的 GNMT 注意力架构使用底层(第一层)输出作为 query 注意力。因此,前一步刚刚结束就能实行注意力计算。我们实现了 GNMTAttentionMultiCell 中的架构,这是 tf.contrib.rnn.MultiRNNCell 的一个子类。 以下是使用 GNMTAttentionMultiCell 创建解码器单元的示例:



最后的基准部分请参考原文。


原文:https://github.com/tensorflow/nmt


点击展开全文
阅读 2260 赞赏 1 有用 9 没用 0 收藏 3 分享

   


来源:Google blog、GitHub Google 原文链接:https://github.com/tensorflow/nmt
新智元编译:刘小芹 闻菲 原文链接:http://mp.weixin.qq.com/s/-D-5p_7DfvR3D7cZjlUmUA

0 条留言

列位于大天的头像

列位于大天

六合八荒唯我独尊

相关文章

机器学习原来如此有趣!全世界最简单的机器学习入门指南

25岁Java工程师如何转型学习人工智能?(学习资料及学习路径推荐)

人工智能之神经网络入门

如何搭建大规模机器学习平台?以阿里和蚂蚁的多个实际场景为例

[干货]什么是机器学习?基础机器学习算法

手把手教你如何在 Mac OSX 系统安装 TensorFlow

网络被劫持了 总去请求http://10.19.99.18:89/cookie/flash.js 卡顿2s 如何解决

通俗有趣的说说人工智能

「翻译」史上最全人工智能产品清单(1/3)

成为未来几年最炙手可热的机器学习人才,基本功、秘密武器和弹药补给

他的文章

人工智能在识别奢侈品真假上的应用 -- 再也不用担心买到假LV包包了

继吴恩达后,亚马逊AI主任科学家李沐上线 动手学深度学习 | 中文免费

「翻译」史上最全人工智能产品清单(1/3)

吴恩达给你的人工智能第一课 | 视频 免费 中英文

近300位数据挖掘专家云集阿里,最精彩的发言都在这儿

刷爆朋友圈的穿上军装背后的AI技术及简单实现

如何搭建大规模机器学习平台?以阿里和蚂蚁的多个实际场景为例

如何解决租房烦恼?阿里工程师写了一套神奇的代码

阿里首次披露!拍立淘技术框架及核心算法,日均UV超千万

针对初学者,数学基础差,深度学习如何入门?

手机扫一扫
分享文章