欢迎光临
我们一直在努力

人工智能发展史 — NLP 与 RNN/LSTM/Transformer/GPT 序列模型发展历程

目录

文章目录

  • 目录
  • NLP 的发展阶段
  • NLP 的序列数据与序列模型
  • NLP 的关键任务
    • 中文分词
    • 子词切分
    • 词性标注
    • 文本分类
    • 实体识别
    • 关系抽取
    • 文本摘要
    • 机器翻译
    • 自动问答
  • NLP 文本表示的发展历程
    • VSM 文本向量化表示
    • Word2Vec 文本词嵌入化表示
  • 1990 年:RNN(循环神经网络)
    • 模型结构
      • N-N 结构
      • 1-N 结构
      • N-1 结构
    • 数学表达
    • 训练算法:时间反向传播(BPTT)
    • RNN 的挑战:长期依赖问题
  • 1997 年:LSTM(长短期记忆网络)
    • 模型结构与数学表达
    • 解决梯度消失问题
    • 使用 PyTorch 构建 LSTM
    • RNN v.s LSTM
    • 完整案例
  • 2014 年:GRU(门控循环单元)
    • 模型结构和数据表达
    • LSTM v.s. GRU
  • 2014 年,RNN Encoder–Decoder
    • 常见结构类型
    • Encoder
    • Decoder
  • 2014 年,RNN-seq2seq
  • 2017 年:Transformer 架构
    • Transformer v.s. RNN/LSTM
    • 模型结构
    • 多头自注意力机制
  • 2018 年:OpenAI GPT-1 理解文字
  • 2019 年:OpenAI GPT-2 多任务学习者
  • 2020 年:OpenAI GPT-3 能够举一反三
  • 2022 年:ChartGPT 聊天机器人产品
  • 2023 年:OpenAI GPT-4 多模态
  • 参考文档

NLP 的发展阶段

NLP(Natural Language Processing,自然语言处理)是 AI 领域的一个重要分支,旨在使计算机能够理解和处理人类语言,实现人机之间的自然交流。

语言是人类智力的外化体现。1950 年,艾伦·图灵提出了图灵测试,他说:“如果一台机器可以通过使用打字机成为对话的一部分,并且能够完全模仿人类,没有明显的差异,那么机器可以被认为是能够思考的。” 图灵测试是判断机器是否能够展现出与人类不可区分的智能行为的测试。

在计算机领域,NLP 的主要处理对象之一就是 “自然语言文本” 数据,使得计算机能够执行各种复杂的文本处理任务,如:中文分词、子词切分、词性标注、文本分类、实体识别、关系抽取、文本摘要、机器翻译、自动问答等。这些任务不仅要求计算机能够识别和处理语言的表层结构,更重要的是可以理解语言背后的深层含义,包括语义、语境、情感和文化等方面的复杂因素。NLP 技术的进步为我们从海量文本中提取有用信息、理解语言的深层含义提供了强有力的工具。

NLP 从诞生至今经历了符号主义阶段、统计学习阶段、深度学习阶段、PLM(Pretrain Language Model,预训练模型)阶段到而今的 LLM(Large Language Model,大语言模型)阶段的多次变革。每次技术变革都极大地推动了 NLP 技术的发展,使其在机器翻译、情感分析、实体识别和文本摘要等任务上取得了显著成就。

  • 符号主义阶段:符号主义学者认为人类理解力范围内的一切事物皆可以用符号来标记,人类理解力本身可以被描述成这些符号之间的运算。结合以上两者,得到一套带运算的符号系统(物理符号系统),它就等于人类的智能,并且,这套系统可以被完整地写进机器,从而机器便可拥有与人类一致的智能。在这个视角下,智能表现为对符号的操作(比如基于规则的推理)。符号逻辑思想之下最具代表性的实例就是专家系统。(Siri 最初就是一个专家系统。) 在这里插入图片描述

  • 联结主义和统计学习阶段:联结主义学者又称为仿生学派,他们认为智力可以从大量简单的神经单元之间的联结之中涌现出来,正如生物人脑的实际情况那样。为此他们创造出一个数学模型,就是人工神经网络。但是,要如何调整这个模型,才能让它具备人类的智能而不是成为其他生物呢?这就是统计学习发挥作用的地方了。只有仿生的模型还不够,需要加以概率统计的 “操控”,宛如给模型这匹野马上了一套马具。 在这里插入图片描述

  • DL(Deep Learning,深度学习)阶段:后来随着计算机科学、统计学、机器学习、深度学习、NLP 等多领域的发展和交叉融合,在 NLP 领域中诞生了 RNN(Recurrent Neural Network,循环神经网络)、LSTM(Long Short-Term Memory,长短时记忆网络)、Attention(注意力机制)等序列模型,取得了令人瞩目的成果。 在这里插入图片描述

  • PLM(Pretrain Language Model,预训练语言模型)阶段:PLM 的典型代表是 GPT-1/2、BERT 等,采用 Transformer 结构,通过在海量无监督文本上进行自监督预训练,结合在少量有监督文本上进行微调,实现了强大的自然语言理解能力。但是,PLM 仍然依赖于一定量有监督数据进行下游任务微调,且在自然语言生成任务上性能还不尽如人意,NLP 系统的性能距离人们所期待的通用人工智能还有不小的差距。 在这里插入图片描述

  • LLM(Large Language Model,大语言模型)阶段: LLM 则在 PLM 的基础之上,通过大量扩大模型参数、预训练数据规模,并引入 SFT 指令微调、RLHF 人类反馈强化学习等手段实现的突破性成果。相较于 PLM,LLM 具备了涌现能力、强大的上下文学习能力、指令理解能力和文本生成能力。在 LLM 阶段,NLP 研究者可以一定程度抛弃大量的监督数据标注工作,只需要为 SFT 提供少量监督示例。强大的指令理解能力与文本生成能力使 LLM 能够直接、高效、准确地响应用户指令,从而真正向通用人工智能的目标逼近。

  • 在这里插入图片描述

    NLP 的序列数据与序列模型

    在 NLP 领域,文本翻译、语音识别等场景都离不开对序列数据的处理。

    序列是数据点或事件的有序列表。与独立的图像或表格数据不同,序列数据中的元素具有内在的顺序和时间依赖性。 典型的例子包括:自然语言文本、语音、视频、股票价格、天气读数或传感器数据等。

    传统的 FFN(Feedforward Neural Networks,前馈神经网络)或 CNN(卷积神经网络)在处理这类数据时会遇到根本性的困难,因为它们假设所有输入都是相互独立的,所以无法捕捉到数据点之间的时序关系。比如在处理一个句子时,FNN 会孤立地看待每个单词,从而丢失了至关重要的上下文信息。为了解决这个问题,我们需要一种能够 “记忆过去” 信息的模型,而这正是 RNN 的设计初衷。

    在这里插入图片描述

    注:FNN 是最基础的神经网络结构之一,其核心特点是:信息只能沿着输入到输出的方向单向传播,不存在循环或反馈连接。也就是说,数据从输入层进入,经过中间的隐藏层逐层处理后,最终由输出层输出结果,层与层之间的连接是 “前馈” 的,没有反向的信息流动。

    在这里插入图片描述

    而 Sequence Model(序列模型)就是一种用于处理序列数据的 AI 模型,具有 “历史记忆”,能够用于解决输入数据为序列形式的各种问题。在 NLP 领域的应用场景包括:

  • 文本处理:单词的顺序决定了句子的含义。如:机器翻译、情感分析、聊天机器人。
  • 语音识别:音频转文本。
  • 音乐生成:声波是随时间变化的连续信号。
  • 视频生成:视频由一系列有序的图像帧组成。
  • 时间序列分析:分析按时间顺序排列的数据点序列,如:股票价格、传感器读数、天气预报等,其当前值与历史值密切相关。
  • 值得注意的是,Sequence Model 的 “历史记忆” 和 Hopfield Network 的 “联系记忆” 有本质的区别,“联想记忆” 并不适用于处理序列数据,而 “历史记忆” 的目标是发现序列数据中的模式和依赖关系,从而进行预测、分类,甚至生成新的序列。当下经典的序列模型结构包括:RNN、LSTM、GRU、Transformer 和 GPT。

    在这里插入图片描述

    时至今日,虽然像 Transformer 这样的基于 Attention(注意力机制)的新架构在许多 NLP 任务中(尤其是在处理超长序列时)表现出了更强的性能和更好的并行性,但 RNN、LSTM 和 GRU 仍然是序列建模领域不可或缺的基础工具。 它们在计算效率、对实时流数据的处理以及在某些特定任务上的表现依然具有优势。

    NLP 的关键任务

    中文分词

    由于中文句子的词与词之间没有像英文那样的空格,所以无法直接通过空格来确定词的边界。因此,中文分词任务(Chinese Word Segmentation,CWS)是中文文本处理的基础,将连续的中文文本切分成有意义的词汇序列。

    英文输入:The cat sits on the mat.
    英文切割输出:[The | cat | sits | on | the | mat]

    中文输入:今天天气真好,适合出去游玩.
    中文切割输出:["今天", "天气", "真", "好", ",", "适合", "出去", "游玩", "。"]

    正确的分词结果对于后续的词性标注、实体识别等任务都很重要。如果分词不准确,将直接影响到整个文本处理的效果。

    输入:雍和宫的荷花开的很好。

    正确切割:雍和宫 || 荷花 |||||
    错误切割 1:雍 || 宫的 | 荷花 | 开的 | 很好 | 。 (地名被拆散)
    错误切割 2:雍和 || 的荷 | 花开 | 的很 | 好。 (词汇边界混乱)

    子词切分

    对于特别长尾的生僻词或未见过的新词,模型很可能不认识,此时需要应用子词切分(Subword Segmentation)技术来进行 “猜测”。子词切分将词汇进一步分解为更小的单位,即子词,模型通过已知的子词单位来理解或生成生僻词或新词,比如英文中的词根词缀法。子词切分对于那些拼写复杂、合成词多的语言(如德语)或者在 PLM(如 BERT、GPT)中尤为重要。

    输入:unhappiness

    不使用子词切分:整个单词作为一个单位,输出:“unhappiness”
    使用子词切分(假设 BPE 算法):单词被分割为:“un”、“happi”、“ness”

    子词切分的方法有很多,如:BPE(Byte Pair Encoding)、WordPiece、Unigram、SentencePiece 等。如上,基本思想是将单词分解成更小的、频繁出现的片段,这些片段可以是单个字符、字符组合或者词根和词缀。即使模型从未见过 unhappiness 这个完整的单词,它也可以通过这些已知的子词来理解其大致意思为 “不幸福的状态”。

    词性标注

    词性标注(Part-of-Speech Tagging)的目标是为文本中的每个单词分配一个词性标签,如:名词、动词、形容词等。词性标注对于理解句子结构、进行句法分析、语义角色标注等高级 NLP 任务至关重要。

    词性标注通常依赖于机器学习模型,如 RNN 和 LSTM 等。词性标注基于预先定义的词性标签集,如英文中的常见标签有名词(Noun,N)、动词(Verb,V)、形容词(Adjective,Adj)等。这些模型通过学习大量的标注数据来预测新句子中每个单词的词性。

    通过词性标注,计算机可以更好地理解文本的含义,进而进行信息提取、情感分析、机器翻译等更复杂的处理。

    英文句子:She is playing the guitar in the park.

    词性标注的结果如下:
    She (代词,Pronoun,PRP)
    is (动词,Verb,VBZ)
    playing (动词的现在分词,Verb,VBG)
    the (限定词,Determiner,DT)
    guitar (名词,Noun,NN)
    in (介词,Preposition,IN)
    the (限定词,Determiner,DT)
    park (名词,Noun,NN)
    . (标点,Punctuation,.)

    文本分类

    文本分类(Text Classification)可以将给定的文本自动分配到一个或多个预定义的类别中。应用场景广泛,例如:情感分析、垃圾邮件检测、新闻分类、主题识别等。

    将新闻文章分类为 “体育”、“政治” 或 “科技” 三个类别之一。

    文本:“NBA季后赛将于下周开始,湖人和勇士将在首轮对决。”
    类别:“体育”

    文本:“美国总统宣布将提高关税,引发国际贸易争端。”
    类别:“政治”

    文本:“苹果公司发布了新款 Macbook,配备了最新的m3芯片。”
    类别:“科技”

    文本分类的关键在于理解文本的含义和上下文,并基于此将文本映射到特定的类别。随着深度学习技术的发展,使用神经网络进行文本分类已经成为一种趋势,它们能够捕捉到文本数据中的复杂模式和语义信息,从而在许多任务中取得了显著的性能提升。

    实体识别

    文本中的实体包括:人物、物品、事件、地方等等。实体识别(Named Entity Recognition)旨在自动识别文本中具有特定意义的实体,然后将它们分类为预定义的类别,如:人名、地点、组织、日期、时间等。

    输入:李雷和韩梅梅是北京市海淀区的居民,他们计划在2024年4月7日去上海旅行。
    输出:[("李雷", "人名"), ("韩梅梅", "人名"), ("北京市海淀区", "地名"), ("2024年4月7日", "日期"), ("上海", "地名")]

    通过实体识别任务,我们不仅能识别出文本中的实体,还能了解它们的类别,为深入理解文本内容和上下文提供了重要信息。

    关系抽取

    关系抽取(Relation Extraction)可以在实体识别的基础之上继续识别实体之间的语义关系,比如:因果关系、拥有关系、亲属关系、地理位置关系等。

    输入:比尔·盖茨是微软公司的创始人。
    输出:[("比尔·盖茨", "创始人", "微软公司")]

    关系抽取对于理解文本内容、构建知识图谱、提升机器理解语言的能力等方面具有重要意义。通过关系抽取,我们可以从文本中提取出有用的信息,帮助计算机更好地理解文本内容,为后续的知识图谱构建、问答系统等任务提供支持。

    文本摘要

    文本摘要(Text Summarization)就是根据文本内容生成一段简洁准确的摘要,来概括原文的主要内容。文本摘要可以分为两大类:

  • 抽取式摘要(Extractive Summarization):通过直接从原文中选取关键句子或短语来组成摘要。优点是摘要中的信息完全来自原文,因此准确性较高。然而,由于仅仅是原文中句子的拼接,有时候生成的摘要可能不够流畅。
  • 生成式摘要(Abstractive Summarization):不仅涉及选择文本片段,还需要对这些片段进行重新组织和改写,并生成新的内容。生成式摘要更具挑战性,因为它需要理解文本的深层含义,并能够以新的方式表达相同的信息。生成式摘要通常需要更复杂的模型,如基于 Attention 的 Seq2Seq 模型。
  • 2021年5月22日,国家航天局宣布,我国自主研发的火星探测器“天问一号”成功在火星表面着陆。此次任务的成功,标志着我国在深空探测领域迈出了重要一步。“天问一号”搭载了多种科学仪器,将在火星表面进行为期90个火星日的科学探测工作,旨在研究火星地质结构、气候条件以及寻找生命存在的可能性。

    抽取式摘要:我国自主研发的火星探测器“天问一号”成功在火星表面着陆,标志着我国在深空探测领域迈出了重要一步。
    生成式摘要:“天问一号”探测器成功实现火星着陆,代表我国在宇宙探索中取得重大进展。

    文本摘要任务在信息检索、新闻推送、报告生成等领域有着广泛的应用。通过自动摘要,用户可以快速获取文本的核心信息,节省阅读时间,提高信息处理效率。

    机器翻译

    机器翻译(Machine Translation)不仅涉及到词汇的直接转换,更重要的是要准确传达源语言文本的语义、风格和文化背景等,使得翻译结果在目标语言中自然、准确、流畅。

    源语言:今天天气很好。
    目标语言:The weather is very nice today.

    机器翻译的关键在于处理更长、更复杂的文本时,会面临长序列记忆难题的挑战。现如今基于神经网络的 Seq2Seq 模型、 Transformer 模型等已经能够学习到源语言和目标语言之间的复杂映射关系,从而实现更加准确和流畅的翻译。

    自动问答

    自动问答(Automatic Question Answering,Q&A)的关键在于能够理解提问人的真实意图,并根据给定的数据源自动提供准确的答案。自动问答涵盖了从简单的事实查询到复杂的推理和解释,通常涉及 NLP 的多个关键人物,例如:信息检索、文本理解、知识表示和推理等。

    自动问答大致可分为三类:

  • 检索式问答(Retrieval-based QA):通过搜索引擎等方式从大量文本中检索答案。
  • 知识库问答(Knowledge-based QA):通过结构化的知识库来回答问题。
  • 社区问答(Community-based QA):依赖于用户生成的问答数据,如问答社区、论坛等。
  • 自动问答系统的开发和优化是一个持续的过程,随着技术的进步和算法的改进,这些系统在准确性、理解能力和应用范围上都有显著的提升。通过结合不同类型的数据源和技术方法,自动问答系统正变得越来越智能,越来越能够处理复杂和多样化的问题。

    NLP 文本表示的发展历程

    上述 NLP 关键任务的底层支撑之一就是 NLP 文本表示,指将自然语言文本内容转化为计算机可以处理的数据格式,目的是让文本的分析和处理更高效。它直接决定了 NLP 系统的质量和性能。

    更具体而言,文本表示涉及到将文本中的语言单位(字、词、短语、句子等)以及它们之间的关系和结构信息转换为计算机能够理解和操作的形式,例如:向量、矩阵或其他数据结构。文本表示不仅仅需要保留足够的语义信息以便于后续的 NLP 任务,如文本分类、情感分析、机器翻译等,还需要考虑计算效率和存储效率。

    在这里插入图片描述

    VSM 文本向量化表示

    向量空间模型(Vector Space Model,VSM)是一种经典的 NLP 文本表示方法,其核心思想就是将文本转化为高维向量空间中的向量,继而通过比较向量之间的相似度来评估文本之间相关性。这些文本的类型包括:字、词、短语、句子、段落、整个文档、Query 查询等等。如下图所示,doc1、doc2 和 query 之间的距离表示了它们之间的相关程度。

    在这里插入图片描述

    VSM 的核心流程包括 “构建向量空间、计算维度权重和评估相似度” 这三步。

  • 构建向量空间:高维向量空间中的每一个维度都是一个词语,抽象化的称为特征项(term)。所以向量空间的维度由词汇表来决定,词汇表是一个基于分词技术的词语集合,包含所有可能出现的词语。该场景中 “空间维数 = 词汇表大小”。例如:如果词汇表大小为 16384 ,那么一个词向量都会被表示为一个长度为 16384 的数组,其中只有该词对应的位置为 1,其他位置都为 0。同理,下面是一个句子向量的例子。文档向量也类似。
  • 句子:"雍和宫的荷花很美"
    词汇表大小:16384
    句子向量包含 5 个词:["雍和宫", "的", "荷花", "很", "美"]
    词向量 = [0, 0, ..., 1, 0, ..., 1, 0, ..., 1, 0, ..., 1, 0, ..., 1, 0, ...]
    ↑ ↑ ↑ ↑ ↑

    实际有效维度:仅 5 维(非零维度),16384 维中只有 5 个位置为 1,其余 16379 个位置为 0
    数据稀疏率:(16384-5)/16384 ≈ 99.97%

  • 计算维度权重:权重计算的主体是(词、句子或文章)向量中每个有效词语的权重。常用算法有 TF-IDF、BM25 等,可以简单理解为词语出现次数越多权重就越高,反映了某个词语在句子中的重要程度。所以在第一步有了词位置向量之后,会再使用词权重值替换得到词权重向量。
  • 句子向量包含 5 个词:["雍和宫", "的", "荷花", "很", "美"]
    词位置向量 = [0, 0, ..., 1, 0, ..., 1, 0, ..., 1, 0, ..., 1, 0, ..., 1, 0, ...]
    词权重向量 = [0, 0, ..., 0.1, 0, ..., 0.2, 0, ..., 0.3, 0, ..., 0.4, 0, ..., 0.5, 0, ...]

    在这里插入图片描述

  • 评估相似度:向量之间的相似度的几何数学含义就是 “距离”。常用的相似度算法有余弦相似度、欧氏距离、杰卡德系数等。 在这里插入图片描述 其中,余弦相似度 cosθ 的 θ 为两向量间夹角,值∈[0,1],越接近 1 则越相似。 在这里插入图片描述
  • 可见,VSM 非常适用于文本相似度计算、文本分类、信息检索等 NLP 任务。它将复杂的文本数据转换为易于计算和分析的数学形式,使得文本的相似度计算和模式识别成为可能。

    然而,VSM 也存在很多问题。其中最主要的是数据稀疏性和维数灾难问题,因为特征项数量庞大导致向量维度极高,同时大多数元素的值为零。此外,由于模型基于特征项之间的独立性假设,忽略了文本中的结构信息,比如词序和上下文信息等,这也限制了模型的表现力。特征项的选择和权重计算方法的不足也是 VSM 需要解决的问题。

    Word2Vec 文本词嵌入化表示

    2013 年,Google 提出 Word2Vec 模型开创了文本向量化表示的新时代,为 NLP 任务提供了更加有效的文本表示方法。

    VSM 开创了通过向量来进行文本表示的先河,是后来 Word2Vec、GloVe 等众多 Word Embedding(词嵌入)技术的基础。就 Word2Vec 而言,它相较于 VSM 有 2 个关键改进:

  • Word2Vec 解决了 VSM 无法理解语义的问题:VSM 词向量只有权重信息,而没有语义信息。而 Word2Vec 的语义理解能力基于一个哲学思想:词的上下文环境决定了词的意义!所以在 Word2Vec 训练词向量的时候,是通过中心词的上下文词来计算出中心词向量的,因而这样的词向量也具有了上下文的语义信息。这对 NLP 推理场景非常重要。
  • Word2Vec 解决了 VSM 高维稀疏向量的问题:VSM 词向量是高维稀疏的(维度 = 词汇表大小),存储和计算效率较低。而 Word2Vec 能够生成一个低维的高密度向量。
  • Word2Vec 是一种词嵌入(Word Embedding)技术,用于词汇表中的每个词语映射到一个词向量,这个词向量还能够包含词语的语义信息,且这个语义是上下文决定的。在空间几何表述中,语义相近的词(例如 “苹果” 和 “香蕉”)对应的向量在空间中的距离也更近。因为在大量文本中,它们通常会出现在相似的上下文中。下面是一些具体的例子。

    在这里插入图片描述 在这里插入图片描述

    在具体实现层面,Word2Vec 是一个 AI 模型。Google 通过大规模的语料训练出了 {词: 向量} 的映射规则,再基于这个规则完成 “词→向量” 的映射,最终产出体现为一个词汇表。这就是 Word to Vector 的含义。

    训练阶段:Word2Vec 模型在训练阶段要产出一个词汇表,里面记录了每个词的向量。Word2Vec 利用 “中心词” 在文本中的 “上下文词” 来学习到上下文的语义关系,使得语义相似的词在向量空间中距离较近。具体而言,Word2Vec 提供了 2 种训练方式。

  • CBOW(Continuous Bag of Words,连续词袋模型):用 “上下文词” 预测 “中心词”。适用于小型数据集。
    • 逻辑:给定一个中心词的上下文,比如 “我吃 __ 很开心” 中的 “我、吃、很、开心”,然后模型学习调整向量,让这些上下文词的向量能精准预测中心词 “苹果”。
    • 结果:模型预测收敛后,语义相近的词,比如 “苹果” 和 “香蕉”,因两者上下文分布相似,最终对应的向量在高维空间中距离也近。
  • Skip-Gram(跳字模型):与 CBOW 相反,用 “中心词” 预测 “上下文词”。在大型数据集中表现更好。
    • 逻辑:给定中心词 “苹果”,模型学习调整向量,让这个词的向量能精准预测其上下文 “我、吃、很、开心”。
    • 结果:同样让语义相关的词拥有相似向量,如 “电脑” 和 “计算机”。
  • 上述 2 种方式最终都会输出一个词汇表字典,包含了 {词: 向量} 的映射关系,词向量的每一维数值都是 Word2Vec 模型训练后得到的最优值,表征该词的语义特征。并且,相较于 VSM 的高维稀疏向量(维度 = 词汇表大小,One-Hot 编码),Word2Vec 生成的是低维稠密向量(通常几百维),有助于减少计算复杂度和存储需求。

    同时,Word2Vec 模型也可以很好的泛化到未见过的词,因为它是基于上下文信息学习的,而不是基于传统词典。但由于 CBOW 和 Skip-Gram 模型是基于局部上下文的,无法捕捉到长距离的依赖关系,缺乏整体的词与词之间的关系,因此在一些复杂的语义任务上表现不佳。

    在这里插入图片描述

    如下图所示,是一个 Skip-gram 模型的核心训练流程:

  • 输入中心词:CAT
  • 预测上下文词:PURR(猫发出的咕噜声)
  • 嵌入层(Embedding):基于词汇表,将 CAT 转化为词向量 V_cat。
  • 线性变换层:由权重矩阵 W 和偏置项 b 组成,计算得到一个 中间向量矩阵 W·V_cat + b。这个中间向量矩阵的维度等于整个词汇表的大小,每一维对应词汇表中一个词的 “预测得分”。
  • Softmax 层:使用 Softmax 激活函数,将 “预测得分” 转化为概率分布(所有维度的概率和为 1)。表示以 CAT 为中心词时,词汇表中每个词作为其上下文词的概率。
  • Loss 计算:红色框是 Softmax 输出的词汇表上下文概率分布预测值向量;绿色框是 PURR 的 One-Hot(独热)向量,只有对应 PURR 的位置是 1(必然出现),其余是 0,表示 PURR 是 CAT 的上下文词的真实值。同规格预测值和真实值求得 Loss 之后反向传播更新权重。使用交叉熵损失算法(Cross Entropy)来计算 Loss。
  • 在这里插入图片描述

    转换阶段:训练完成后,把单个词转为向量的过程极其简单。

    • 若该词在训练好的词汇表中:直接从字典中取出对应的向量,比如查 “苹果”,就返回训练好的 300 维数组。
    • 若该词不在词汇表中:无现成向量,所以只能通过 “字符级向量拼接”、“相近词替换” 等方式近似,或者直接返回全 0 向量(无语义意义)。

    在这里插入图片描述

    值得一提的是,Work2Vec 有一个有趣的发现,就是可以使用向量运算来 “推理” 单词。例如:man 和 woman 的相对距离非常类似于 king 和 queen 的相对距离。big 和 biggest 的相对距离非常类似于 small 和 smallest 的相对距离。

    在这里插入图片描述

    1990 年:RNN(循环神经网络)

    1990 年,Jeffrey Elman 发表论文《Finding structure in time(在时间中寻找结构)》提出了 SRN(Simple Recurrent Network,简单循环网络),这是世界上第一个 RNN(Recurrent Neural Networks,循环神经网络)序列模型。它开启了对序列数据和序列模型的研究,开创了模型能够 “记忆历史信息” 的先河。

    在这里插入图片描述

    模型结构

    RNN 的核心是让模型 “记住过去”。为了实现这一能力,RNN 模型结构具有以下 3 个关键特性:

  • 信息持久化(Information Persistence):RNN 能够在隐藏层(Hidden Layer)节点中存储 “历史记忆”,称之为隐藏状态。通过这个状态 RNN 可以在序列处理的过程中传递 “历史记忆”,这是 RNN 能够学习到序列元素之间的长距离依赖关系的前提。
  • 反馈循环(Feedback Loop):通过在隐藏层的每个节点上添加一个反馈循环连接,使得它在处理序列中的每个元素(如一个词、一个时间点的数据)时,它不仅要考虑当前的输入,还要考虑它从之前所有步骤中计算并保留下来的 “历史记忆”。所以这个状态会随着网络处理输入序列中的每个元素而更新,就像是不断被刷新的 “记忆”,但该 “记忆容量” 有限且无法长期保存。
  • 捕捉依赖关系(Captures Dependencies):RNN 的目的是捕捉序列数据中随时间分布的依赖关系和模式。但受限于梯度消失,仅能有效学习短距离依赖,无法处理长序列(如超过 10 个时间步)的长期关联。
  • 注:时间步:在序列模型中,时间步是处理序列数据的基本单位。它代表了模型按顺序处理输入数据的一个 “步骤” 或 “瞬间”。

    如下图所示,RNN 和 FFN 结构的主要区别就是 RNN 引入了一个循环结构,RNN 在每个时间步(t)处理一个序列元素时,不仅会接收当前的输入,还会接收来自上一个时间步(t-1)的隐藏状态(Hidden State)。 这个隐藏状态就是 “历史记忆”,是 RNN 到目前为止所被处理过的序列数据的一个 “历史记忆”。 在这里插入图片描述

    可见,每一个隐藏层节点的 “当前记忆” 都由 “当前输入” 和 “历史记忆” 来共同决定的。如下图所示。 在这里插入图片描述

    更具体的,一个 RNN 单元看起来就像是一个环路的人工神经元。对于每个时间步 t,该单元接收当前输入 x_t 和前一时间步的隐藏状态 h_t-1,然后计算出当前时间步的隐藏状态 h_t 和输出 y_t。再把这个循环的过程可 unfold(展开),就可以看见一个具有时间属性的 “神经元序列”,这样更容易理解序列数据是如何在不同的时间步之间进行 “有序” 传递的。

  • 输入单元(input units):x_t
  • 隐藏单元(hidden units):h_t
  • 输出单元(output units):y_t
  • 在这里插入图片描述

    同时,展开后的 RNN 网络看起来像一个很深的 FFN 网络,其中每一层都代表一个时间步,并且所有时间步共享相同的权重矩阵。 请添加图片描述

    N-N 结构

    N-N 结构,包含 N 个输入 x1, x2, …, xN,和 N 个输出 y1, y2, …, yN。 N-N 结构,输入和输出序列的长度是相等的,通常适合用于以下任务:

  • 词性标注。
  • 训练语言模型,使用之前的词预测下一个词等。
  • 在这里插入图片描述

    1-N 结构

    1-N 结构中,只有一个输入 x,和 N 个输出 y1, y2, …, yN。 1-N 结构适合用于以下任务:

  • 图像生成文字,输入 x 就是一张图片,输出就是一段图片的描述文字。
  • 根据音乐类别,生成对应的音乐。
  • 根据小说类别,生成相应的小说。
  • 可以有两种方式实现 1-N:

  • 只将输入 x 传入第一个 RNN 神经元 在这里插入图片描述
  • 将输入 x 传入所有的 RNN 神经元。 在这里插入图片描述
  • N-1 结构

    N-1 结构中,有 N 个输入 x1, x2, …, xN,和一个输出 y。 N-1 结构适合用于以下任务:

  • 序列分类任务,一段语音、一段文字的类别,句子的情感分析。
  • 在这里插入图片描述

    数学表达

    简单 RNN 的核心计算可以用以下两个公式来描述:

  • 隐藏状态的计算:
  • h_t = f(W_hh * h_t-1 + W_xh * x_t + b_h)

  • 输出的计算:
  • y_t = W_hy * h_t + b_y

    其中:

    • x_t:在时间步 t 的输入向量,如文本中的词向量。
    • h_t, h_t-1:在时间步 h_t 或 t-1 的隐藏状态,即:历史信息,是核心的记忆载体。
    • y_t:在时间步 t 的输出。
    • W_xh, W_hh, W_hy:分别是输入层到隐藏层、隐藏层到自循环隐藏层、隐藏层到输出层的权重矩阵。作为共享参数,在所有时间步中是共享的。
      • W_xh 形状:隐藏层维度 × 输入维度
      • W_hh 形状:隐藏层维度 × 隐藏层维度(关键)
      • W_hy 形状:输出维度 × 隐藏层维度
    • b_h, b_y:分别是隐藏层和输出层的偏置量。
      • b_h 形状:隐藏层维度 × 1
      • b_y 形状:输出维度 × 1
    • f:激活函数
      • 隐藏层用 tanh,值缩至 [-1,1]。
      • 输出层用 Softmax(分类)或线性激活(回归)

    在这里插入图片描述

    训练算法:时间反向传播(BPTT)

    RNN 的训练通过 BPTT(Backpropagation Through Time,时间反向传播)来完成,本质上是标准 BP 反向传播算法在 Unfold 展开后的 RNN 上的具体应用。Loss 损失函数在每个时间步 t 计算,然后将梯度从最后一个时间步开始,沿着时间序列反向传播,并用这些梯度来更新共享的 W_xh, W_hh, W_hy 权重矩阵。

    在这里插入图片描述

    RNN 的挑战:长期依赖问题

    尽管 RNN 的设计在理论上能够捕捉任意长度的序列依赖关系,但在实践中,它们很难学习到 “长期依赖”(Long-Term Dependencies)。即:当序列过长时,早期信息的梯度会逐渐消失,导致模型无法记住早期信息,例如:在句子 “I grew up in China…(20 个词后)…so I speak fluent Chinese” 中,RNN 难以关联单词 China 和 Chinese。

    长期依赖问题主要源于梯度消失(Vanishing Gradients)和梯度爆炸(Exploding Gradients):

    • 梯度消失:1991 年,Sepp Hochreiter 在他的毕业论文中阐述了梯度消失问题,当梯度在稍微深一点的网络中(如超过 10 个时间步)进行反向传播时就会发现训练过程中前面层的参数几乎不更新,即梯度几乎为零,核心的原因是 “累乘衰减”。具体而言,在通过 BPTT 反向传播梯度时,如果激活函数(如 tanh)的导数持续小于 1,那么梯度在每一步传播时都会被乘以一个小于 1 的权重矩阵。经过许多时间步后,梯度会变得非常小,几乎接近于零。这导致网络无法有效地更新与早期时间步相关的权重,从而 “忘记” 了久远的信息。

    • 梯度爆炸:相反,如果权重矩阵的值很大,梯度在反向传播过程中可能会指数级增长,导致数值溢出和训练过程的不稳定。 虽然梯度爆炸可以通过梯度裁剪(Gradient Clipping)等技术相对容易地解决,但梯度消失问题则更为棘手。

    可见,由于梯度消失问题,RNN 的 “历史记忆” 实际上是短暂的,这限制了它在需要理解长篇文本或分析长期时间序列等任务中的应用。

    1997 年:LSTM(长短期记忆网络)

    1997 年,Sepp Hochreiter 和 Jürgen Schmidhuber 发表了论文《Long Short-Term Memory》提出了 LSTM(长短期记忆网络)。

    LSTM 是一种特殊且复杂的 RNN,它通过使用专门的记忆单元和门控机制来序列数据中的长期依赖关系,能够学习到 “何时” 记忆信息、何时遗忘信息以及何时输出信息。解决了 RNN 的梯度消失问题。

    模型结构与数学表达

    LSTM 的核心思想是随时间选择性地记住或遗忘信息,通过记忆单元的线性传递路径(类似信息高速公路)和门控机制,避免梯度在长序列中衰减,相当于为梯度传递安装了中继器。

    LSTM 将 RNN 中的隐藏层单元换成了一种具有特殊记忆功能的循环体结构,在隐藏层状态 h_t 的基础上增加了记忆单元状态(Memory Cell State) C_t。如下图所示,LSTM 在模型结构的关键创新是引入了一个 Memory Cell(记忆细胞),并在内部实现了 “输入 => 门控 => 输出” 的控制机制,以此来解决梯度消失的问题。

    • 输入:包括当前输入、上一时间步的隐藏状态(Hidden State)、上一时间步的记忆单元状态(Cell State)。
    • 门控:每个 Cell 包含了 “记忆、遗忘、输出” 这 3 个关键的门,它们互相协作共同更新当前时间步的记忆单元状态(如图最上方连线)。门的本质是一个由 Sigmoid 激活函数和一个点积乘法操作组成的神经网络层。Sigmoid 函数的输出在 0~1 之间,这个值决定了有多少信息可以通过。0 表示 “完全不允许通过”,而 1 表示 “完全允许通过”,0-1 表示保留部分信息。
    • 输出:包括当前记忆单元状态和当前输出。 在这里插入图片描述
  • 遗忘门(Forget Gate):控制历史信息的保留。利用 sigmoid 激活函数,它获取 h_t-1 和 x_t,并为 C_t-1 中的每个数字输出一个 0~1 之间的值。这个值代表了保留或遗忘的程度。
  • f_t = σ(W_f * [h_t-1, x_t] + b_f)

  • 输入门(Input Gate):控制当前时刻的输入中有多少信息要被添加到 Cell State 中。首先通过 sigmoid 函数确定需要添加的信息。然后通过 tanh 函数创建一个候选的记忆单元状态向量 C̃_t,该向量可以被添加到记忆单元状态中。
  • i_t = σ(W_i * [h_t-1, x_t] + b_i)
    C̃_t = tanh(W_c * [h_t-1, x_t] + b_c)

  • 记忆单元状态更新:通过处理要遗忘和要保留的信息,完成上一时刻的记忆单元状态 C_t-1 到当前时刻 C_t 的更新(矩阵乘积)更新。先将 C_t-1* f_t(要遗忘的旧信息),然后 “+” 加上 i_t * C̃_t(要添加的新信息)。
  • C_t = f_t * C_t-1 + i_t * C̃_t

  • 输出门 (Output Gate):控制输出的过程。首先 sigmoid 层决定记忆单元状态 C_t 的哪些部分将要输出。然后 C_t 通过 tanh 函数(将其值缩放到 -1~1 之间),并将其与 sigmoid 层的输出相乘,从而只会输出了想要输出的部分。这个输出就是新的隐藏状态 h_t。
  • o_t = σ(W_o * [h_t-1, x_t] + b_o)
    h_t = o_t * tanh(C_t)

    在这里插入图片描述 在这里插入图片描述

    解决梯度消失问题

    上述过程可知,LSTM Memory Cell 中的门控机制约束了哪些信息是可以遗忘的、哪些信息是可以记住的、哪些信息是可以输出的。 其独特的 Cell State 更新公式 C_t = f_t * C_t-1 + …,让 C_t 的梯度包含一个 f_t 因子,通过学习,遗忘门可以设置 f_t 为接近 1 的值,从而允许梯度在许多时间步内几乎无衰减地流动,这样就解决了 RNN “累积衰减” 的问题。这种结构使得网络更容易学习和保持长期依赖关系。

    使用 PyTorch 构建 LSTM

    使用 PyTorch 框架构建一个 LSTM 模型,用于进行文本生成。我们将用一段文本来训练模型,然后让它生成新的文本。

    import torch
    import torch.nn as nn
    import numpy as np

    # — 1. 数据准备 —
    # 将整个文本作为一个长字符串,并创建了字符与整数之间的双向映射。
    # 示例文本
    text = """
    Recurrent Neural Networks (RNNs) are a class of neural networks that are helpful in modeling sequence data.
    Derived from feedforward networks, RNNs are similar to human brains in the way they function.
    They are designed to recognize patterns in sequences of data, such as text, handwriting, or time series data.
    """

    # 创建字符到整数的映射
    chars = sorted(list(set(text)))
    char_to_int = {ch: i for i, ch in enumerate(chars)}
    int_to_char = {i: ch for i, ch in enumerate(chars)}
    n_chars = len(text)
    n_vocab = len(chars)
    print("总字符数: ", n_chars)
    print("词汇表大小: ", n_vocab)

    # — 2. 准备训练数据 —
    # 创建了输入-输出对。输入是一个固定长度(`seq_length`)的字符序列,输出是紧随其后的那个字符。这种方式训练模型根据前面的字符序列来预测下一个字符。
    # 将文本转换为整数序列
    seq_length = 100
    dataX = []
    dataY = []
    for i in range(0, n_chars seq_length, 1):
    seq_in = text[i:i + seq_length]
    seq_out = text[i + seq_length]
    dataX.append([char_to_int[char] forchar in seq_in])
    dataY.append(char_to_int[seq_out])
    n_patterns = len(dataX)
    print("总模式数: ", n_patterns)

    # 将输入序列重塑为 [样本数, 时间步长, 特征数]
    X = torch.tensor(dataX, dtype=torch.float32).reshape(n_patterns, seq_length, 1)
    # 对输入进行归一化
    X = X / float(n_vocab)
    # one-hot编码输出变量
    y = torch.tensor(dataY)

    # — 3. 定义 LSTM 模型 —
    # 定义了一个继承自 `nn.Module` 的类。
    # `nn.LSTM` 是 PyTorch 中实现 LSTM 的核心层。`batch_first=True` 参数让输入张量的维度顺序为 `[批量大小, 序列长度, 特征维度]`,这更符合直觉。我们堆叠了两个 LSTM 层 (`num_layers=2`)以增加模型的表达能力。
    # `nn.Linear` 是一个标准的全连接层,用于将 LSTM 层的输出映射到我们的词汇表大小,以便为每个字符生成一个分数。
    # `forward` 方法定义了数据如何在模型中流动。我们初始化隐藏状态和细胞状态为零,然后将输入传递给 LSTM 层,最后将 LSTM 最后一个时间步的输出传递给全连接层。
    class CharLSTM(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
    super(CharLSTM, self).__init__()
    self.hidden_size = hidden_size
    # input_size: 输入特征维度 (这里是1)
    # hidden_size: LSTM隐藏层维度
    # num_layers: LSTM层数
    self.lstm = nn.LSTM(input_size, hidden_size, num_layers=2, batch_first=True)
    # 全连接层,将LSTM的输出映射到词汇表大小
    self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x):
    # 初始化隐藏状态和细胞状态
    h0 = torch.zeros(2, x.size(0), self.hidden_size) # 2for num_layers
    c0 = torch.zeros(2, x.size(0), self.hidden_size)

    # LSTM 前向传播
    out, _ = self.lstm(x, (h0, c0))

    # 我们只关心最后一个时间步的输出
    out = self.fc(out[:, 1, :])
    return out

    model = CharLSTM(input_size=1, hidden_size=256, output_size=n_vocab)
    print(model)

    # — 4. 训练模型 —
    # 使用 `CrossEntropyLoss` 作为损失函数(适用于多分类问题)和 Adam 优化器进行训练。
    n_epochs = 20
    lr = 0.001

    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)

    for epoch in range(n_epochs):
    optimizer.zero_grad()
    outputs = model(X)
    loss = criterion(outputs, y)
    loss.backward()
    optimizer.step()
    print(f'Epoch [{epoch+1}/{n_epochs}], Loss: {loss.item():.4f}')

    # — 5. 生成文本 —
    # 我们从数据中随机选取一个序列作为“种子”,然后循环地让模型预测下一个字符,并将预测出的字符添加到生成文本中,同时更新下一次预测的输入序列。
    # 随机选择一个种子序列
    start = np.random.randint(0, len(dataX)1)
    pattern = dataX[start]
    print("种子序列: ")
    print("\\"", ''.join([int_to_char[value] for value in pattern]), "\\"")

    print("\\n生成的文本: ")
    generated_text = ""
    with torch.no_grad():
    for i in range(500):
    # 准备输入
    x = torch.tensor(pattern, dtype=torch.float32).reshape(1, seq_length, 1)
    x = x / float(n_vocab)

    # 预测
    prediction = model(x)

    # 获取概率最高的字符索引
    index = torch.argmax(prediction).item()
    result = int_to_char[index]
    generated_text += result

    # 更新种子序列
    pattern.append(index)
    pattern = pattern[1:len(pattern)]

    print(generated_text)

    RNN v.s LSTM

    RNN 作为序列建模的 “基石”,以简单的循环结构开创了历史信息复用的思路,但受限于梯度消失无法处理长序列;LSTM 则通过记忆细胞和门控机制的创新,从梯度传递路径上解决了长期依赖问题,成为长序列任务的经典方案。

    RNN 优势:

    • 结构简单,参数少;
    • 适用于短期依赖序列建模;
    • 训练速度快,适合资源受限场景。

    RNN 劣势:

    • 容易陷入梯度消失/爆炸;
    • 长期依赖学习能力弱。

    LSTM 优势:

    • 通过门控机制保留长期信息;
    • 性能更稳定,泛化能力强;
    • 适用于文本、语音、金融等长时间序列建模。

    LSTM 劣势:

    • 参数多,训练成本高;
    • 相比 RNN 更复杂,不易调参。

    请添加图片描述

    完整案例

    • RNNModel:使用一个标准的单层 RNN;
    • LSTMModel:使用单层 LSTM;
    • 都接一个全连接层预测输出;
    • Loss 函数为 MSE,优化器为 Adam。

    import numpy as np
    import pandas as pd
    import torch
    import torch.nn as nn
    import matplotlib.pyplot as plt
    from sklearn.preprocessing import MinMaxScaler
    from torch.utils.data import Dataset, DataLoader
    from sklearn.metrics import mean_squared_error

    # 设置随机种子
    np.random.seed(42)
    torch.manual_seed(42)

    # 创建一个合成的时间序列数据(正弦波+噪声)
    t = np.arange(0, 100, 0.1)
    data = np.sin(t) + np.random.normal(scale=0.5, size=len(t))
    df = pd.DataFrame(data, columns=['value'])

    # 数据归一化
    scaler = MinMaxScaler()
    df['value'] = scaler.fit_transform(df[['value']])

    # 创建数据集类
    class TimeSeriesDataset(Dataset):
    def __init__(self, data, seq_length):
    self.data = data
    self.seq_length = seq_length

    def __len__(self):
    return len(self.data) self.seq_length

    def __getitem__(self, idx):
    x = self.data[idx:idx+self.seq_length]
    y = self.data[idx+self.seq_length]
    return torch.FloatTensor(x), torch.FloatTensor([y])

    # 创建训练和测试数据
    seq_length = 20
    train_size = int(len(df) * 0.8)
    train_data = df['value'].values[:train_size]
    test_data = df['value'].values[train_size seq_length:]

    train_dataset = TimeSeriesDataset(train_data, seq_length)
    test_dataset = TimeSeriesDataset(test_data, seq_length)

    train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
    test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False)

    # 定义 RNN 模型
    class RNNModel(nn.Module):
    def __init__(self, input_size=1, hidden_size=50):
    super(RNNModel, self).__init__()
    self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
    self.fc = nn.Linear(hidden_size, 1)

    def forward(self, x):
    out, _ = self.rnn(x.unsqueeze(1))
    out = self.fc(out[:, 1, :])
    return out

    # 定义 LSTM 模型
    class LSTMModel(nn.Module):
    def __init__(self, input_size=1, hidden_size=50):
    super(LSTMModel, self).__init__()
    self.lstm = nn.LSTM(input_size, hidden_size, batch_first=True)
    self.fc = nn.Linear(hidden_size, 1)

    def forward(self, x):
    out, _ = self.lstm(x.unsqueeze(1))
    out = self.fc(out[:, 1, :])
    return out

    # 训练函数
    def train_model(model, train_loader, num_epochs=10):
    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
    losses = []

    for epoch in range(num_epochs):
    for x_batch, y_batch in train_loader:
    optimizer.zero_grad()
    output = model(x_batch)
    loss = criterion(output, y_batch)
    loss.backward()
    optimizer.step()
    losses.append(loss.item())
    return model, losses

    # 预测函数
    def predict(model, data_loader):
    model.eval()
    predictions = []
    actuals = []
    with torch.no_grad():
    for x_batch, y_batch in data_loader:
    pred = model(x_batch)
    predictions.append(pred.item())
    actuals.append(y_batch.item())
    return np.array(predictions), np.array(actuals)

    # 初始化模型并训练
    rnn_model = RNNModel()
    lstm_model = LSTMModel()

    rnn_model, rnn_losses = train_model(rnn_model, train_loader, num_epochs=20)
    lstm_model, lstm_losses = train_model(lstm_model, train_loader, num_epochs=20)

    # 模型预测
    rnn_preds, rnn_actuals = predict(rnn_model, test_loader)
    lstm_preds, lstm_actuals = predict(lstm_model, test_loader)

    # 反归一化预测值
    rnn_preds_inv = scaler.inverse_transform(rnn_preds.reshape(1, 1)).flatten()
    lstm_preds_inv = scaler.inverse_transform(lstm_preds.reshape(1, 1)).flatten()
    actuals_inv = scaler.inverse_transform(lstm_actuals.reshape(1, 1)).flatten()

    # 绘制图像
    plt.figure(figsize=(16, 12))

    # 图1: 损失函数曲线
    plt.subplot(2, 2, 1)
    plt.plot(rnn_losses, label='RNN Loss', color='crimson')
    plt.plot(lstm_losses, label='LSTM Loss', color='mediumseagreen')
    plt.title('训练损失随Epoch变化图')
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.legend()
    plt.grid(True)

    # 图2: 测试集预测结果比较
    plt.subplot(2, 2, 2)
    plt.plot(actuals_inv, label='Actual', color='black')
    plt.plot(rnn_preds_inv, label='RNN Predicted', color='darkorange')
    plt.plot(lstm_preds_inv, label='LSTM Predicted', color='dodgerblue')
    plt.title('测试集预测结果比较')
    plt.xlabel('Time Step')
    plt.ylabel('Value')
    plt.legend()
    plt.grid(True)

    # 图3: 预测误差分布直方图
    plt.subplot(2, 2, 3)
    rnn_error = rnn_preds_inv actuals_inv
    lstm_error = lstm_preds_inv actuals_inv
    plt.hist(rnn_error, bins=30, alpha=0.7, label='RNN Error', color='red')
    plt.hist(lstm_error, bins=30, alpha=0.7, label='LSTM Error', color='green')
    plt.title('预测误差分布直方图')
    plt.xlabel('误差')
    plt.ylabel('频率')
    plt.legend()
    plt.grid(True)

    # 图4: 实际 vs 预测散点图
    plt.subplot(2, 2, 4)
    plt.scatter(actuals_inv, rnn_preds_inv, label='RNN', alpha=0.5, color='purple')
    plt.scatter(actuals_inv, lstm_preds_inv, label='LSTM', alpha=0.5, color='cyan')
    plt.plot(actuals_inv, actuals_inv, color='black', linestyle='–', label='Ideal')
    plt.title('实际值 vs 预测值')
    plt.xlabel('实际值')
    plt.ylabel('预测值')
    plt.legend()
    plt.grid(True)

    plt.tight_layout()
    plt.show()

    在这里插入图片描述

    • 训练损失曲线:展示模型随训练过程损失的下降趋势。LSTM 收敛更快,且最终损失更低,表明其对序列建模更有效。
    • 测试集预测结果对比:可视化真实值与两种模型的预测值对比。LSTM 更紧密拟合真实趋势,RNN 预测波动性较大。
    • 误差分布图:分析两种模型预测误差的分布范围。LSTM 的误差集中度高,波动小;RNN 误差分布更宽,精度低。
    • 实际 vs 预测散点图:检查预测值与真实值的一致性。LSTM 点更集中在理想线附近,说明相关性更高。

    2014 年:GRU(门控循环单元)

    2014 年,Cho 等人提出了 GRU(门控循环单元),是 LSTM 的一个简化版本。它保持了与 LSTM 相当性能的同时,结构更简单,参数更少,计算效率更高。

    模型结构和数据表达

    在这里插入图片描述

    GRU 在模型结构上的主要创新在于它将 LSTM 的遗忘门和输入门合并为了一个单一的更新门(Update Gate),并且它还合并了记忆单元状态和隐藏状态。

    GRU 只有两个门:

  • 更新门(Update Gate):决定了应该在多大程度上保留前一个时间步的信息,以及在多大程度上接收新生成的信息。 它类似于 LSTM 中遗忘门和输入门的组合。
  • z_t = σ(W_z * [h_t-1, x_t] + b_z)

  • 重置门(Reset Gate):决定了在计算新的候选隐藏状态时,应该忽略多少过去的信息。 如果重置门的输出接近 0,那么模型在计算候选状态时将主要依赖于当前输入 x_t。
  • r_t = σ(W_r * [h_t-1, x_t] + b_r)

  • 候选隐藏状态与最终隐藏状态:首先,使用重置门计算一个候选隐藏状态 h̃_t 重置门 r_t 作用于 h_{t-1},控制了前一状态对候选状态的影响。然后,更新门 z_t 在 h_{t-1} 和 h̃_t 之间进行线性插值,以产生最终的隐藏状态 h_t。
  • h̃_t = tanh(W_h * [r_t * h_t-1, x_t] + b_h)
    h_t = (1 – z_t) * h_t-1 + z_t * h̃_t

    当 z_t 接近 1 时,新的隐藏状态 h_t 主要由候选状态 h̃_t 构成;当 z_t 接近 0 时,新的隐藏状态 h_t 则几乎完全保留了前一个状态 h_{t-1}。这种机制使得 GRU 也能有效地捕捉长期依赖。 在这里插入图片描述

    LSTM v.s. GRU

    LSTM 解决了 RNN 因长期依赖带来的梯度消失和梯度爆炸问题,但是 LSTM 有三个不同的门,参数较多,训练起来比较困难。而 GRU 只含有两个门控结构,结构更为简单,参数更少,收敛速度更快。但因为 GRU 的参数更小所以理论上精度和表达能力会稍差于 LSTM。

    LSTM 和 GRU 的适用场景不同:

    • 长文本或复杂依赖:当数据涉及较长的文本,或任务需要理解复杂的上下文依赖时,LSTM 的强大记忆能力可以发挥优势。例如,处理长篇文章的情感分析、机器翻译等任务。例如:我们分析的是一篇长篇博客文章,其中前半部分描述了一个积极的事件,而后半部分充满了负面情感。要准确捕捉这种情感转变,LSTM 可能是更好的选择,因为它能够通过遗忘门和记忆单元,逐步积累并更新情感信息,从而做出更精确的情感判断。
    • 短文本或实时性要求:如果你处理的是短文本,如推特、简短评论,或需模型快速响应,GRU 通常是更好的选择。它的计算效率更高,且在短期依赖场景下表现优异。例如:在分析推特或短句子的情感时,GRU的效率优势更加明显。GRU能快速处理短文本的情感特征,并及时提供预测结果,这对实时分析推特流的情感趋势尤其重要。

    在某些场景下,可以尝试混合使用 LSTM 和 GRU,取长补短。例如:在情感分析任务中,如果希望获得高效且准确的模型,可以考虑使用 GRU 处理短文本情感,而使用 LSTM 处理长篇文本情感。通过这种组合方式,可以兼顾速度和准确性,提升整体模型表现。

    另外,除了适用场景之外,在选择 LSTM 或 GRU 时,需要考虑以下因素:

  • 任务复杂度:如果任务需要处理长期依赖关系,LSTM 可能更适合。而如果任务相对简单,GRU 的表现也可以达到满意水平。
  • 计算效率:GRU 相较于 LSTM 具有更少的参数和更简洁的结构,因此在计算效率方面更优。
  • 数据规模:对于大规模数据,LSTM 可能更适合,因为它具有更强的表达能力。而对于小规模数据,GRU 的表现也可以达到满意水平。
  • 至今为止,GRU 和 LSTM 在序列处理场景中的价值已经得到了验证,它们两者各有优缺点。理解它们的区别和适用场景,能够帮助你在不同任务中做出更优的选择。

    2014 年,RNN Encoder–Decoder

    RNN 的 N-N 结构可知,RNN 仅适用于输入和输出等长的任务。但实际中很多任务的序列的长度是不固定的,例如机器翻译中,源语言和目标语言的句子长度是不一样的;再例如对话系统中,问题和答案的句子长度也是不一样的。

    2014 年,Bengio 在论文《Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation》中提出了基于 RNN 的 Encoder–Decoder 模型。

    在这里插入图片描述

    RNN Encoder–Decoder 的核心思想是将输出和输出分离为 2 个 RNN,并引入一个定长的隐状状态(Hidden state)来作为输入和输出之间的桥梁,以此来构建 N-1-M 的输入和输出不等长序列。

    在这里插入图片描述

    • Encoder(编码器):用于编码输入序列的信息,将任意长度 N 的序列信息编码到一个定长的向量 c 里。Encoder 把输入句子的所有语义信息压缩成一个固定长度的中间语义向量(也称为上下文向量或隐向量或隐状态),该向量包含了可供计算与学习的、代表句子语言特点和含义的特征信息,是输入的浓缩摘要。具体逻辑为:
      • Encoder 会对输入句子的每个词进行处理,处理每个词之后会产生一个隐藏状态。
      • 从输入的第二个词开始,Encoder 每个时刻的输入是上一个时刻的隐藏状态和输入的新单词。
      • Encoder 输出的最后一个时刻的隐藏状态就是编码了整个句子语义的语义上下文(Context),这是一个固定长度的高维特征向量 c,输入句子每个时间步的信息都包含在了这个上下文中。
    • Decoder(解码器):用于解码输出序列的信息,得到上下文信息向量 c 之后可以将信息解码,并输出为序列。值得注意的是,对于 Decoder 而言,上一轮的 Decoder 输出还会作为下一轮的输入以补充上下文信息。Decoder 会把这个中间语义上下文向量 c 解码成输出句子,即 Decoder 将 Encoder 学习到的特征信息再转化为相应的句子。具体逻辑为:
      • 在每个时刻,Decoder 都是自回归的,即上一个时刻的输出 y_t−1 会作为当前时刻 t 的输入之一,生成当前时刻的字符 y_t。
      • Decoder 最初的输入是中间语义上下文向量 c,解码器依据 c 计算出第一个输出词和新的隐藏状态,即 Decoder 的每个预测都受到先前输出词和隐藏状态的微妙影响。
      • Decoder 接着用新的隐藏状态和第一个输出词作为联合输入来计算第二个输出词,以此类推,直到解码器产生一个 EOS(End Of Service,序列结束)标记或者达到预定序列长度的边界。
    • 隐状状态(Hidden state):定长向量 C,把隐状态看成对输入信息的一种编码的话,用于在 Encoder 和 Decoder 之间传递信息。

    请添加图片描述

    RNN Encoder-Decoder 架构在短句子上有非常好的表现。但是一个关键问题在于,Encoder 需要能够将源句子的所有必要信息压缩到一个固定长度的向量 c 中。这会使 RNN 难以处理长句子,尤其是那些比训练语料中的句子还长的句子。随着输入句子长度的增加,Encoder-Decoder 架构的性能会迅速恶化。

    常见结构类型

    RNN Encoder–Decoder 模型结构有很多种,下面是几种比较常见的 3 种: 在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述 第三种 Decoder 结构和第二种类似,但是在输入的部分多了上一个神经元的输出 y’。即每一个神经元的输入包括:上一个神经元的隐藏层向量 h’,上一个神经元的输出 y’,当前的输入 c(Encoder 编码的上下文向量)。对于第一个神经元的输入 y’0,通常是句子其实标志位的 embedding 向量。 在这里插入图片描述

    第三种 Decoder 的隐藏层及输出计算公式: 在这里插入图片描述

    Encoder

    上述 RNN Encoder–Decoder 模型结构中的 Encoder 都是一样的,区别在于 Decoder。

    Encoder 的 RNN 接受输入 x,最终输出一个编码所有信息的上下文向量 c,中间的神经元没有输出。 在这里插入图片描述

    从公式可以看到,c 可以有 3 种计算方式:

  • c 可以直接使用最后一个 RNN 神经元的隐藏状态 hN 表示;
  • 也可以在最后一个 RNN 神经元的隐藏状态上进行某种变换 hN 而得到,q 函数表示某种变换;
  • 还可以使用所有 RNN 神经元的隐藏状态 h1, h2, …, hN 计算得到。
  • 得到上下文向量 c 之后,需要传递到 Decoder,然后解码出需要的信息。 在这里插入图片描述

    Decoder

    第一种 Decoder 结构比较简单,将上下文向量 c 当成是 RNN 的初始隐藏状态,输入到 RNN 中,后续只接受上一个神经元的隐藏层状态 h’ 而不接收其他的输入 x。 在这里插入图片描述

    第一种 Decoder 结构的隐藏层及输出的计算公式: 在这里插入图片描述

    第二种 Decoder 结构有了自己的初始隐藏层状态 h’0,不再把上下文向量 c 当成是 RNN 的初始隐藏状态,而是当成 RNN 每一个神经元的输入。可以看到在 Decoder 的每一个神经元都拥有相同的输入 c。 在这里插入图片描述

    这种 Decoder 的隐藏层及输出计算公式: 在这里插入图片描述

    2014 年,RNN-seq2seq

    2014 年,Google 在论文《Sequence to Sequence Learning with Neural Networks》也提出了基于 RNN 的 Seq2Seq(Sequence to Sequence,序列到序列)方法,可以实现从一个源序列生成一个目标序列的操作,主要应用在机器翻译领域。由此,深度学习开始在机器翻译领域发挥作用,同时掀起了 NLP(自然语言处理领域)的深度学习浪潮。

    Transformer 出现之前,处理文本序列信息主要依靠的是 RNN 和 LSTM 模型。但 RNN 及 LSTM 虽然具有捕捉时序信息、适合序列生成的优点,却有两个难以弥补的缺陷:

  • 无法并行化:序列依序计算的模式能够很好地模拟时序信息,但限制了计算机并行计算的能力。由于序列需要依次输入、依序计算,GPU 并行计算的能力受到了极大限制,导致 RNN 为基础架构的模型虽然参数量不算特别大,但计算时间成本却很高;
  • 超长距离依赖问题:在 RNN 架构中,距离越远的输入之间的关系就越难被捕捉,同时 RNN 需要将整个序列读入内存依次计算,也限制了序列的长度。第一个词的隐藏状态信息在经过几十个甚至几百个时间步的传递后,会逐渐衰减甚至丢失。虽然 LSTM 中通过门机制对此进行了一定优化,但对于较远距离相关关系的捕捉,RNN 依旧是不如人意的。
  • 如下图所示,BLEU(Bilingual Evaluation Understudy)是一种自动评价机器翻译质量的指标,得分一般在 0~100 之间。得分越高,表示机器翻译结果与参考翻译越接近,质量越好。可见 RNN 随着 Sentence Length 的加大而减分。

    在这里插入图片描述

    2017 年:Transformer 架构

    2014 年 6 月 24 日,Google DeepMind 发表《Recurrent Models of Visual Attention》,使得注意力机制(Attention Mechanism)开始受到广泛关注。该论文采用了 RNN 模型,并集成了注意力机制来处理 CV(图像识别)任务,开创了将注意力机制应用于深度学习模型的先河。

    2014 年 9 月 1 日,Dzmitry Bahdanau、KyungHyun Cho 和 Yoshua Bengio 发表论文《Neural Machine Translation by Jointly Learning to Align and Translate》,将注意力机制引入机器翻译,以提高长序列处理能力。它在机器翻译的历史中标志着一个重要的转折点。

    2017 年,Google Brain 的 Vaswani 等学者参考了在 CV 领域被经常融入到 RNN 中使用的注意力机制(Attention),创新性地搭建了完全由注意力机制构成的 Transformer 架构,并发表了论文《Attention is All You Need》。采用了全新的 Self-Attention(自注意力),计算输入序列中每个位置的关系权重,并行化处理序列数据,取代了 RNN 的时序依赖,成为 GPT 等大语言模型(Large Language Model,LLM)的核心。

    2018 年,GPT 和 BERT 分别使用自回归语言建模和自编码语言建模作为预训练目标。所有后续的大规模预训练模型都是这两个模型的变体,即 Transformer 的变体。

    Transformer v.s. RNN/LSTM

    RNN 的结构如下图所示,大体上是在模仿人理解序列的过程:

  • 把序列从头至尾看一遍,把已经看过的部分留下一个 “历史记忆” 在头脑里;
  • 每看到一个新元素,都尝试与这个 “历史记忆” 综合起来理解。
  • 这些前代方法虽然能在某些场景下取得不错的效果,但是都面临着两个问题:

  • 不擅长处理长距离依赖:面对一个很长的输入序列,RNN 处理到靠后部分时,靠前的信息往往已被丢失(“遗忘”)。
  • 不支持并行计算:RNN 的训练、推理计算必须严格遵守时序(前序完成了,后序才能开始),这个特性导致它无法启用并行计算,所以规模难以提升。
  • Attention 机制使用了一种全新的方式来处理序列信息,绕开了困扰着 RNN、LSTM 方法的这两个问题。

    模型结构

    请添加图片描述

    • Encoder-Decoder:Transformer 由 Encoder 和 Decoder 两部分组成。Encoder 处理输入序列,Decoder 自迭代生成任意长度的输出序列。
    • Embedding(词嵌入):模型的输入首先通过一个词嵌入层,将词汇映射到高维空间中的向量表示。
    • Positional Encoding(位置编码):Transformer 模型本身不具备处理序列顺序的能力,因此需要引入位置编码来捕捉序列中词汇的位置信息。位置编码可以是学习得到的,也可以通过正余弦函数(sin/cos)生成。为了保留序列信息,添加了一个位置编码到词向量中,使得模型能够理解单词之间的相对顺序。
    • Multi-Head Attention(多头自注意力机制):允许模型在处理序列中的每个元素时,同时考虑序列中的其他元素,计算得到一个表示着该元素与整个序列中所有其它元素的关系的中间向量。这个新的中间向量中蕴含着该元素与序列中所有元素之间的关系,即:把上下文的信息融入当前元素之中,借助 “上下文” 来理解这个元素,从而捕捉序列内部的依赖关系。
    • Feed Forward Network(前馈网络):在 Self-Attention 之后,会通过一个 FFN 对每个位置的向量进行独立相同的操作。
    • Layer Normalization(层级归一化):在每个 Layer(Self-Attention + FFN)之后,都会进行层归一化操作,这有助于模型的训练稳定性。
    • Residual Connection(残差连接):每个 Layer 的输出与输入相加,形成残差连接,这有助于解决深层网络中的梯度消失问题。
    • ** Layer 堆叠**:Encoder 和 Decoder 都会由多个相同的 Layer 堆叠而成。使得 MHA 计算得到的中间向量还可以继续做 Attention,这样一层层做下去,每前进一层,实质都是在更大的范围内交融序列中的各个元素、解析其间的关系,这样的结构使得模型能够捕获不同尺度的依赖关系。最终就能够得到表示着整个序列的总体特征的向量了。

    可见,Attention 并不存在 RNN 和 LSTM 存在的 2 个问题:

  • 不管序列有多长,Attention 都能建立序列中所有元素之间的关系。
  • Attention 处理序列中每一个元素的过程是相互独立的,因此可以并行。
  • 在这里插入图片描述

    多头自注意力机制

    MHA(Multi-Head Attention,多头自注意力机制)允许模型在不同的表示子空间中并行地捕捉信息。

    在这里插入图片描述

    自注意力机制的核心思想:对于输入的每个位置,计算该位置与其他所有位置之间的相关性或相似性,并据此调整每个位置的表示。

  • 输入表示:假设输入是一个矩阵 X,形状为 [序列长度, 隐藏维度]。

  • 线性映射变换:X 被映射到三个不同的向量:查询(Query)、键(Key)和值(Value)。这些映射是通过与三个不同的权重矩阵 W_Q, W_K, W_V 相乘来实现的,这些权重矩阵是可学习的参数。 在这里插入图片描述

  • 计算注意力权重:

    • 多头结构:多头自注意力机制包含多个 Head,每个头都独立地执行自注意力操作。每个头都有自己的一套权重矩阵,用于 Q、K、V 的线性映射。
    • 缩放点积注意力:通过计算 Q 和 K 的点积来衡量各个位置之间的相关性,然后通过除以一个缩放因子来防止梯度消失或爆炸。如下,d_k 是 K 向量的维度,平方根号 d_k 分之一是缩放因子,用于防止点积值过大。
    • Softmax 归一化:对缩放后的点积结果应用 Softmax 函数,得到每个头的注意力权重矩阵。
    • 加权求和:使用 Softmax 得到的注意力权重矩阵与 V 矩阵相乘,得到每个头的输出,即加权求和的上下文向量。
    • 掩码操作:如果存在,掩码(Mask)会被应用到点积的结果上,通常是将某些位置的值设置为一个非常大的负数(如负无穷),这样在应用 Softmax 时,这些位置的权重会接近于零,从而实现对某些位置的忽略。 在这里插入图片描述 请添加图片描述
  • 头的组合:所有头的输出会被拼接在一起,然后通过一个线性层进行处理,这个线性层可以学习如何最好地组合来自不同头的信息。 在这里插入图片描述

  • 前向传播:在前向传播过程中,每个自注意力头独立地处理输入,然后将结果沿着最后一个维度进行拼接,形成最终的多头自注意力输出。

  • 线性变换输出:公式如下。 在这里插入图片描述

  • 捕捉到序列中不同位置之间的复杂关系;通过并行处理提高了模型的计算效率;使得模型能够从不同的角度学习数据的表示,增强了模型的表达能力。如下图 Self-Attention 计算输入序列中每个位置的关系权重如下,并行化处理序列数据,避免了诸如 RNN 的时序依赖。 请添加图片描述

    2018 年:OpenAI GPT-1 理解文字

    2018 年 6 月,OpenAI 发表了论文《Improving Language Understanding by Generative Pre-Training(通过生成式预训练模型,来提升对于语言本身的理解)》,提出了第一代模型 GPT-1。其中,GPT 的全称就是 Generative Pre-Training。

    在传统 ML(机器学习)中,学者们更喜欢用标注好的数据集来进行机器学习。比如:我心情真棒(正面情绪),括号中的就是一个标注。然而 OpenAI 认为,这个世界上有如此丰富的语料,但大部分都是没有被标注的数据。虽然不好用,但我们可以通过对其学习,只要学的足够多,我们就可以培养模型对于语言表达的理解能力。

    所以 GPT-1 的目标就是理解文字,仅此而已。

    在这里插入图片描述

    2019 年:OpenAI GPT-2 多任务学习者

    当 GPT-1 已经能够理解文字了,那么这个模型可以用来做什么呢?OpenAI 在在大量试验后,他们发现,人们以往认为 ML 中的 NLP 也许不一定需要划分成很多很多的子任务,例如前文提到的复杂任务可能需要拆分为多个子任务(分词、词性标注、分类等等)和模型来完成。

    OpenAI 认为如果语言本身包含了这些信息,那么各类任务,都应该可以被统一学习。比如:传统思路中,人们认为翻译,你必须要有中英文对应,来进行学习。但是,既然 GPT 已经学习了这么多的语料库,那么翻译,应该是自然而然就能学会的,不需要单独进行。同理,归类、找相似性、情感分析等等任务,都应该可以被大模型统一的进行处理。

    于是,2019 年 11 月 OpenAI 发布第二代模型 GPT-2 和论文《Language Models are Unsupervised Multitask Learners(大语言模型,是无需监督的多任务学习者)》。

    在论文中,OpenAI 提出了 GPT-2 是一名多任务学习者的概念。即它可以自己学会做很多很多事,并且这些学会的知识,应该是具备普适性的,能够处理很多任务。 在这里插入图片描述

    2020 年:OpenAI GPT-3 能够举一反三

    GPT-2 已经学会了很多内容,可以完成很多任务。既然它通过学习大量数据,掌握了人类语言,又理论上具备多任务处理能力,怎么让它更有用呢?

    传统思路中,ML 模型需要进行针对性优化,来提升对于不同任务的表现。但 GPT-2 已经非常巨大,每次针对性优化,显然不现实。所以 OpenAI 想到既然人类可以通过举例学习,语言模型应该也可以,这就是 Post-Training(后训练)微调和强化学习的前身。

    因此,2020 年,OpenAI 发布了第三代模型 GPT-3 和论文《Language Models are Few-Shot Learners(通过几个例子模型就能学会你要他做什么)》。GPT-3 成为了一名能够 “举一反三” 的学习者。

    在这里插入图片描述

    2022 年:ChartGPT 聊天机器人产品

    2022 年 11 月 30 日,OpenAI 发布了 ChartGPT,它是一个聊天机器人产品,能够将 GPT 系列模型通过 ChatBot 的形态把 AI 智能以服务的方式向用户提供。

    ChatGPT 能够基于在预训练阶段所见的模式和统计规律,来生成回答,还能根据聊天的上下文进行互动,真正像人类一样来聊天交流。并且得益于 GPT-2 的多任务能力,GPT-3 的举一反三能力,使得 ChatGPT 不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

    ChartGPT 是一个跨时代的产品,一经推出,迅速在社交媒体上走红,短短 5 天,注册用户数就超过 100 万。2023 年 1 月末,ChatGPT 的月活用户已突破 1 亿,成为史上增长最快的消费者应用。

    2023 年:OpenAI GPT-4 多模态

    在这里插入图片描述

    2023 年 3 月 14 日,OpenAI 发布了第四代模型 GPT-4。

    GPT-3 让 OpenAI 看见了 Scaling Low 的潜力,所以 GPT-4 的参数量来到了惊人的 1.76T 级别。使其成为了一个更通用的模型,适用于更广泛的应用,包括但不限于文本生成、翻译、摘要、问答、编程辅助等,还增加了对于长文本的处理能力和更好的泛化能力。

    最重要的,GPT-4 还增加了一种新的交互方式,就是对于图片的理解。也就是 GPT-4 论文的标题《Large Multimodal Model(多模态大模型)》。GPT-4 能够处理图像和文本的混合输入,除本身带了对于图片 OCR 外,还有对位置和细节的理解能力。

    在这里插入图片描述

    2023 年 5 月,在和 OpenAI 达成合作后,微软宣布 GPT-4 全面接入 Office 系列产品。 2024 年 4 月 9 日,OpenAI发布 GPT-4 Turbo。 2024 年 2 月 15 日,OpenAI 发布 Sora 文生视频模型 Demo。 2024 年 5 月 14 日,OpenAI 推出新的旗舰模型 GPT-4o,o 代表 omni,即 “全能” 之意。其最突出特点是在与人的交互方面更为自然。它可以在最快 232 毫秒的时间内响应音频输入,平均响应时间为 320 毫秒,几乎接近人类在交谈中的响应时间。

    参考文档

    https://cloud.tencent.com/developer/article/1869960 https://mp.weixin.qq.com/s/dO9AbJ4Iu23mfiHRqKR4GQ

    赞(0)
    未经允许不得转载:171主机测评 » 人工智能发展史 — NLP 与 RNN/LSTM/Transformer/GPT 序列模型发展历程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址