【深度学习】05-Rnn循环神经网络-01- 自然语言处理概述/词嵌入层/循环网络/文本生成案例精讲

循环神经网络(RNN)主要用于自然语言处理的。

循环神经网络(RNN)、卷积神经网络(CNN)和全连接神经网络(FCN)是三种常见的神经网络类型,各自擅长处理不同类型的数据。下面是它们的简要对比和解释:

1. 循环神经网络(RNN - Recurrent Neural Network)

  • 特点:RNN擅长处理序列数据。与普通神经网络不同,RNN具有“记忆”功能,即它们可以保留之前时刻的信息,并用于当前时刻的输出。其核心特点是循环结构,每个时间步的输出不仅依赖于当前输入,还依赖于前一个时间步的输出。

  • 应用场景:时间序列数据(如股票价格预测)、自然语言处理(如文本生成、机器翻译)、语音识别等。

  • 局限性:标准的RNN在处理长序列时会遇到“梯度消失”问题,导致网络难以学习长时间的依赖关系。为了解决这个问题,改进的RNN结构如LSTM(长短期记忆网络)和GRU(门控循环单元)被引入。

2. 卷积神经网络(CNN - Convolutional Neural Network)

  • 特点:CNN擅长处理图像数据。其核心是卷积层,卷积操作可以提取图像中的局部特征。CNN通常包含多个卷积层、池化层和最后的全连接层。卷积层通过共享权重,能够大大减少网络的参数数量,同时保留重要的空间信息。

  • 应用场景:图像识别、目标检测、图像生成、视频处理等。CNN也是计算机视觉任务中的核心模型。

  • 优势:通过卷积操作,CNN能够有效捕捉局部特征,并能处理较大的图片,具备较好的空间不变性。

3. 全连接神经网络(FCN - Fully Connected Network)

  • 特点:FCN是最基础的神经网络,每一层的神经元与前一层的所有神经元相连。它们通常用于处理结构化数据(如表格数据),但对于高维数据(如图像、文本)来说,它们的参数规模过于庞大,计算效率低,且容易过拟合。

  • 应用场景:主要用于结构化数据的分类和回归任务,如金融数据、医学数据等。它通常用于作为深度神经网络中最后几层的输出层。

  • 局限性:参数数量随着网络层数和每层神经元数量的增加而快速增长,因此不适用于处理高维数据。

三者的对比:

  • 数据类型

    • RNN:适合处理序列数据。

    • CNN:适合处理图像或空间结构化数据。

    • FCN:适合处理结构化数据,但在复杂的任务中通常与CNN、RNN结合使用。

  • 应用场景

    • RNN:时序预测、语言模型、语音识别等。

    • CNN:图像识别、目标检测、视频处理等。

    • FCN:分类、回归等结构化数据任务。

通过这种对比,你可以根据数据类型和任务的不同,选择最合适的神经网络结构。

自然语言处理概述

自然语言处理(Natural Language Processing, NLP)是计算机科学和人工智能的一个重要分支,旨在通过计算机算法分析、理解、生成和处理人类自然语言(如汉语、英语等)。自然语言具有丰富的结构、含义和上下文,难以像传统的结构化数据一样简单地进行数值化处理。

在自然语言处理的任务中,循环神经网络(RNN - Recurrent Neural Network)是一个重要的深度学习模型,尤其适用于处理序列数据,如文本和语音。为了更好地理解RNN在NLP中的作用,下面是一个详细的概述。

一、自然语言处理中的挑战

与传统数据不同,自然语言具有以下几个关键挑战:

  1. 顺序依赖性:语言中的单词或字符通常是有顺序的,单词的顺序对句子的意思有重要影响。例如,"我喜欢你"和"你喜欢我"虽然使用了相同的单词,但顺序不同,意义也不同。

  2. 上下文依赖性:同一个单词在不同的上下文中可能有不同的含义。例如,英语中的单词“bank”可以指“银行”或者“河岸”,这取决于上下文。

  3. 数据稀疏性:词汇表通常非常庞大,且自然语言中的同义词、变体等现象使得数据非常稀疏,难以直接进行数值化处理。

  4. 长程依赖性:有些句子中,单词之间可能存在较远的依赖关系,传统的浅层模型很难捕捉这种长距离依赖。

二、循环神经网络在自然语言处理中的优势

RNN是一类专门设计来处理序列数据的神经网络,能够很好地应对自然语言处理中的顺序依赖和上下文依赖问题。

1. RNN的基本结构

RNN的基本特点是它能够处理一系列输入并保留序列中的上下文信息。在RNN中,每个时间步的输入不仅仅是当前的输入单元(如当前单词或字符),还会结合上一个时间步的信息,从而在处理每个单词时,能够考虑到前面出现过的内容。

在RNN中,输出 ( h_t ) 是当前时间步的隐藏状态,由前一个时间步的隐藏状态 ( h{t-1} )和当前输入( x_t ) 共同决定的:[ h_t = f(h{t-1}, x_t) ] 这种递归结构使RNN能够有效地捕捉序列中前后依赖关系。

2. 长短期记忆网络(LSTM)和门控循环单元(GRU)

由于传统的RNN在处理长序列时容易出现“梯度消失”或“梯度爆炸”问题,导致模型难以学习长距离的依赖,改进版的RNN结构,如LSTM和GRU,被广泛应用于自然语言处理。

  • LSTM(Long Short-Term Memory):通过引入特殊的“门”机制,LSTM能够选择性地保留或丢弃过去的信息,从而解决了标准RNN在处理长程依赖时的梯度消失问题。LSTM中的三个门——输入门、遗忘门和输出门——使得它可以灵活地控制信息的流动和记忆。

  • GRU(Gated Recurrent Unit):GRU是LSTM的简化版本,具有类似的性能,但结构更加简单,计算开销较小。GRU结合了两个门:更新门和重置门,用于决定哪些信息需要保留或丢弃。

3. 应用场景

RNN及其变种(如LSTM和GRU)在自然语言处理中的应用非常广泛,特别是在以下任务中表现出色:

  • 机器翻译:通过将一个语言的序列映射到另一个语言的序列,RNN可以捕捉到两种语言之间的语义依赖。例如,谷歌翻译等机器翻译系统大量使用RNN或LSTM。

  • 文本生成:RNN能够生成与给定上下文相关的文本内容。例如,基于输入的部分内容,生成小说、对话等。

  • 情感分析:RNN可以根据输入文本的上下文捕捉情感信息,判断文本的情感倾向(如正面、负面等)。

  • 语言模型:通过预测文本中的下一个单词,RNN可以学习语言的统计规律,从而生成自然的语言序列。语言模型是诸多NLP任务的基础。

  • 语音识别:RNN也被应用于语音到文本的转换,通过分析语音中的时序特征,将其转化为文本。

三、RNN在自然语言处理中的不足与改进

尽管RNN在NLP中表现出色,但它也有一些局限性:

  1. 长程依赖问题:标准RNN在处理非常长的序列时,仍然难以捕捉远距离的依赖关系。虽然LSTM和GRU有所改善,但它们仍有一定的局限性。

  2. 并行性差:由于RNN的序列性特点,前向传播和反向传播必须逐步进行,难以并行计算。这使得RNN的训练效率较低,特别是在处理长序列时。

为了解决这些问题,近年来一些新的模型,如Transformer,逐渐取代RNN,成为主流的NLP模型。Transformer通过自注意力机制,能够并行处理整个序列,并有效捕捉长距离依赖。

四、RNN在NLP中的实际案例

  1. 文本分类

    • 任务:根据给定文本,自动分类为不同类别(如新闻分类、情感分类)。

    • 流程:首先将文本转化为向量序列,然后通过RNN模型处理该序列并输出分类结果。RNN能够根据文本中的语义关系进行分类。

  2. 机器翻译

    • 任务:将一个语言的句子翻译为另一个语言。

    • 流程:使用一个编码器-解码器架构,其中编码器RNN将源语言的句子编码成一个隐藏状态,解码器RNN根据隐藏状态生成目标语言的句子。

总结

循环神经网络(RNN)在自然语言处理中的应用十分广泛,尤其擅长处理序列依赖问题,如语言中的词序和上下文关系。尽管RNN具有局限性,诸如LSTM、GRU等改进版本在很多NLP任务中表现出色,并且通过自注意力机制的Transformer进一步推动了NLP领域的发展。

在自然语言处理(NLP)的预处理中,语料库语料样本分词词表是经常提到的概念。了解这些概念有助于我们更好地理解分词以及NLP任务中的文本处理流程。下面是对这些术语的详细解释:

1. 语料库(Corpus)

语料库是指大量经过收集和整理的文本数据集合,用于语言学研究或自然语言处理的训练和测试。它通常包含了多种形式的文本,例如书籍、新闻文章、对话记录、社交媒体内容等。

  • 用途:语料库是训练自然语言处理模型的基础。通过大规模的语料库,模型可以学习语言的结构、词汇的用法、常见的词汇组合等。

  • 特征:语料库可以是单一语言的(如中文语料库),也可以是多语言的(如英汉双语语料库)。此外,语料库可以标注更多信息,如词性、命名实体等。

    例子

    • Wikipedia语料库:收集自维基百科的文本数据,是一个常见的开源语料库。

    • COCA语料库(Corpus of Contemporary American English):一个专门收集现代美式英语的语料库。

2. 语料(Text/Document)

语料是指语料库中的具体文本数据。简单来说,语料就是语言处理任务中需要分析、训练或者测试的原始文本内容。

  • 用途:语料是构成语料库的基本单元,每个语料通常是具体的文本数据,如一篇文章、一段对话等。它是模型分析和学习的输入数据。

  • 特征:语料可以是结构化的(如已标注词性、句法结构)或非结构化的(纯文本),视任务需求而定。

    例子

    • "我喜欢学习自然语言处理。" 这是一个语料样本,通常被用作模型的输入进行处理。

3. 样本(Sample)

样本是从语料库中提取出来的一小部分数据,通常用作训练或测试模型的具体数据实例。样本可以是句子、段落,甚至是一个完整的文本。

  • 用途:在模型的训练和测试过程中,样本是用于输入到模型中的单位。样本的数量和质量对模型的效果有很大影响。

  • 特征:样本可以是一个单词、一句话或一个段落,具体取决于任务的定义。例如,在情感分析任务中,样本可能是单个句子,而在机器翻译任务中,样本则可能是整段文本。

    例子

    • "我喜欢苹果。" 这是一个样本,用于训练一个情感分类模型时,它会被标注为"正面"情感。

4. 分词(Tokenization)

分词是将连续的文本分割成若干个独立的词汇(或“词语”)的过程。对于像中文这样的语言,由于没有明确的单词边界(不像英文有空格),分词是必要的预处理步骤。在分词过程中,文本被切分为“词”或“标记”(token)。

  • 用途:分词后的结果被用作模型的输入,模型会基于这些词汇进行进一步的分析和处理。

  • 特征:不同语言的分词方式不同。英文的分词相对简单,通常可以通过空格来分隔单词,而中文则需要通过分词算法来确定词语边界。

    例子

    • 对于句子“我爱自然语言处理”,分词结果可能是:[“我”,“爱”,“自然语言处理”]。

5. 词表(Vocabulary)

词表是指分词之后所有独立词汇的集合。它包含了在语料库中出现过的所有词语,每个词语都会有一个唯一的编号,方便后续的处理和计算。词表可以看作是模型的“词汇量”。

  • 用途:词表是模型进行词汇映射的基础。通过将文本中的词汇与词表中的编号对应,模型能够将文本转化为数值形式。词表也是语言模型训练过程中控制词汇范围的重要工具。

  • 特征:词表的大小通常根据语料库中的词汇数量决定。较大的词表可以覆盖更多的词汇,但会导致计算资源的增加,而较小的词表则可能会忽略一些低频或罕见词汇。

    例子

    • 词表可能包含:[“我”:1,"爱”:2,"自然语言处理”:3,"喜欢”:4,"学习”:5]。

这些概念的关系

  1. 语料库是一个大规模的文本集合,里面包含了许多语料

  2. 语料可以进一步被处理成若干个样本,供模型进行训练或测试。

  3. 分词是将语料样本切分为一个个独立的词汇

  4. 所有经过分词处理后的词语会形成一个词表,用来统一词汇的编号。

总结

  • 语料库是一个语言数据的整体集合,语料是其中的单个文本数据,样本是从语料中提取出来的一部分。

  • 分词是将语料或样本切分为词的过程,而词表是分词结果中所有词汇的集合。

分词是自然语言处理(NLP)中的关键预处理步骤之一,特别是在处理中文、日文等没有明确单词边界的语言中。在分词的过程中,文本被切分为一个个独立的词汇或短语,方便后续的分析和处理。

一、分词的作用

在大多数NLP任务中,计算机需要将语言转化为数值表示才能进行处理。原始的句子是一串连续的字符,对于计算机而言,它并不能直接理解这些字符的含义。分词的作用就是将这段连续的文本按词语的边界切分开,形成一个个独立的单词或短语,作为后续处理的基础。

二、分词的重要性

  1. 词汇层次的信息:分词可以提取句子中的词汇信息,保留了词语的基本含义。尤其是像中文这样的语言,词与词之间没有空格,需要分词才能构建基本的词汇单位。

  2. 提高模型效果:在文本分类、情感分析等任务中,分词后的词语序列能够更好地反映文本的语义信息,有助于提高模型的准确性和性能。

  3. 简化处理:通过分词,可以减少文本的长度,降低处理难度。与处理字符级别的文本相比,词级别的文本处理更简单且更高效。

三、分词的类型

在进行分词时,根据任务需求和语言的特点,分词可以分为以下几种类型:

1. 基于规则的分词

这种方法使用预定义的词典和规则进行匹配,通常根据最大匹配原则进行分词:

  • 正向最大匹配:从左到右逐步扫描文本,尽可能匹配最长的词。例如,“研究生命科学”可能被切分为[“研究”, “生命科学”]。

  • 逆向最大匹配:从右到左进行匹配,同样优先选择最长的词。

优点:实现简单,适合词汇量不大且有明确边界的文本。

缺点:对未登录词(词典中没有的词)表现不好,且容易出现歧义问题。例如,“长春市长春药店”中的“长春市”和“长春”会引发分词歧义。

2. 基于统计的分词

这种方法基于文本中的词频统计,通过大规模语料训练模型,判断两个字符之间的关联度,并据此进行分词。例如,使用常见的隐马尔可夫模型(HMM)最大熵模型等。

  • 核心思想:根据每个词在训练语料中的出现频率以及上下文的关联度,判断是否将字符组合成词。比如“生活质量”这几个字会比“生”和“活质量”在语料库中组合出现的次数更多,因此被分成“生活”和“质量”两个词。

优点:能够处理未登录词,灵活性高。

缺点:需要大量语料来进行训练,可能对罕见词或新词效果不好。

3. 基于深度学习的分词

近年来,基于神经网络的分词方法越来越流行,尤其是结合了RNNLSTMBERT等模型的端到端分词。通过深度学习模型,计算机能够自动学习文本的语义和句法结构。

  • 例子:BERT等预训练语言模型可以基于上下文信息进行分词,并能够处理复杂的语境。

优点:能够结合上下文信息,解决多义词和歧义问题,处理效果更好。

缺点:训练时间较长,计算资源消耗较大。

四、分词中的常见问题

  1. 歧义问题:由于语言的复杂性,分词时经常会遇到歧义。例如,句子“研究生命科学”可以分为“研究/生命/科学”或“研究/生命科学”,不同的分词方式对应着不同的语义。

  2. 未登录词问题:词典中没有的词(如新词、专有名词等),在基于规则的分词方法中难以处理。这也是为什么需要引入基于统计或深度学习的分词方法的原因。

  3. 多义词问题:同一个词在不同语境下可能有不同的含义,分词时可能无法准确判断。例如,“苹果”既可以是水果的名字,也可以是公司名。

五、中文分词的常用工具

  1. 结巴分词(jieba)

    • 这是一个非常流行的中文分词工具,基于前缀词典、HMM等技术,提供了多种分词模式,如全模式、精确模式和搜索引擎模式。

  2. THULAC

    • 清华大学开发的一个分词工具,支持词性标注,适合中文处理任务。

  3. HanLP

    • 支持多种自然语言处理任务的开源工具,性能较高,并且集成了分词、词性标注、命名实体识别等功能。

六、分词的实际案例

假设我们有一句话:“我爱自然语言处理”,通过不同的分词方式,我们可以得到不同的结果:

  • 结巴分词(精确模式):["我", "爱", "自然语言处理"]

  • 结巴分词(全模式):["我", "爱", "自然", "自然语言", "语言", "语言处理", "处理"]

  • 基于深度学习的分词:["我", "爱", "自然语言", "处理"]

分词后的结果可以用作文本分类、情感分析、机器翻译等任务的输入数据。

总结

分词是自然语言处理中的一个关键步骤,尤其对于中文等没有空格分隔的语言来说尤为重要。根据任务需求和语言特点,分词方法可以分为基于规则、统计和深度学习的方式。在处理自然语言的过程中,分词质量对后续任务的效果有着重要影响。

预处理分词和词嵌入层:本质是把文本变成向量,然后输入到循环神经网络中

以下是对预处理过程词嵌入层的详细解释和验证:

1. 预处理过程:文本转数字

在自然语言处理(NLP)的预处理中,将文本(字符串)转换为数字的过程通常包括以下几步:

a. 分词

分词是将文本切分成词的过程,比如中文需要通过分词工具(如结巴分词)将句子拆解成词。

b. 词汇表映射

分词后的词会被映射到一个唯一的数字ID,通常使用一个词汇表(Vocabulary)进行映射。例如,一个词汇表可能包含以下映射:

  • "我" -> 1

  • "爱" -> 2

  • "自然语言处理" -> 3

最终,原始句子 "我爱自然语言处理" 会被转换为 [1, 2, 3] 的索引序列。

总结:预处理过程是将字符串转化为离散的数字索引,表示单词的位置和唯一性,但不包含语义信息。这个过程不涉及向量或嵌入,只是简单的编号。

2. 词嵌入层:词转向量

词嵌入层是一种将离散的词汇索引(数字)转换为向量表示的方法。通过词嵌入,模型可以将每个单词转换成一个包含语义信息的向量。

a. 词嵌入的作用

词嵌入层通过学习一个词汇表中所有词的语义信息,将每个单词转换为一个固定长度的向量。例如:

  • "我" -> [0.2, 0.4, 0.6, 0.8]

  • "爱" -> [0.1, 0.3, 0.7, 0.9]

  • "自然语言处理" -> [0.5, 0.2, 0.4, 0.6]

这些向量表示捕捉了单词之间的语义相似性,比如“猫”和“狗”可能会有相似的向量表示,因为它们的语义相近。

b. 训练方式

词嵌入层可以通过以下两种方式进行训练:

  • 随机初始化后通过模型训练学习:在深度学习模型中,词嵌入层会随着整个模型的训练一同更新和优化。

  • 使用预训练嵌入:如Word2Vec、GloVe等预训练模型生成的词向量,可以直接使用。

c. 词嵌入层的特点
  • 连续向量表示:词嵌入层会将每个单词表示为一个连续的、固定长度的向量,向量中的每个值表示该单词在不同维度上的特征。

  • 语义相似性:词嵌入向量通过学习能够捕捉到单词之间的语义相似性,表现出更强的泛化能力。

3. 区别验证

为了确保理解正确,可以进一步明确预处理和词嵌入的区别:

  • 预处理过程:将文本中的单词通过分词和词汇表映射为离散的数字索引,不包含语义信息。

  • 词嵌入层:将数字索引映射为向量表示,捕捉单词的语义信息。这是神经网络模型的一部分,包含了学习单词之间关系的能力。

确保正确性:分词和词汇表映射属于文本预处理阶段,用于将文本转为可以被模型处理的数字序列。而词嵌入属于模型的一部分,通过向量化表示将单词的语义关系编码到模型中。

词嵌入层讲解

接下来,我们就实现下刚才的需求:

1. 安装 jieba 分词模块

在命令行或终端中运行以下命令来安装 jieba

pip install jieba

2. 验证安装

安装完成后,可以在 Python 中导入 jieba 来验证是否成功:

import jieba
​
# 测试分词
text = "巴黎奥运会的进度条已经过半,不少外国运动员在完成自己的比赛后踏上归途"
words = jieba.lcut(text)
print("分词结果:", words)

如果安装成功,程序将正确输出分词结果。

['巴黎', '奥运会', '的', '进度条', '已经', '过半', ',', '不少', '外国', '运动员', '在', '完成', '自己', '的', '比赛', '后', '踏上', '归途']

这些分词将用于构建词嵌入层。现在你可以将这些分词映射到唯一的索引,并通过词嵌入层来将这些索引转换为向量表示。

代码:

import torch
import torch.nn as nn
import jieba

if __name__ == '__main__':
    # 1.文本数据
    test = "巴黎奥运会的进度条已经过半,不少外国运动员在完成自己的比赛后踏上归途"
    # 2.分词
    words = jieba.lcut(test)
    print(words)
    
    # 3.构建词向量矩阵,构建的时候,矩阵的里面的值是随机的初始化的
    embed = nn.Embedding(num_embeddings=len(words), embedding_dim=4)
    print(embed(torch.tensor(1)))

输出 

疑惑的点是,这个embed 初始化的时候,只用了,样本个数和向量的维度,然后使用的时候,只是用了一个id,那么他是怎么和这个words 拉上关系的呢,我没有看到申明

疑惑很合理。关键点是关于词汇表的映射问题,以及 nn.Embedding 是如何工作的。让我们逐步分析。

1. 关于 nn.Embedding 的工作原理

nn.Embedding 是 PyTorch 提供的一个用于词嵌入的模块,作用是将一个词汇索引(整数)映射到一个固定大小的向量。在构建 Embedding 层时:

embed = nn.Embedding(num_embeddings=len(words), embedding_dim=4)

这段代码创建了一个嵌入矩阵,其中:

  • num_embeddings=len(words) 定义了词汇表的大小,也就是有多少个不同的词(在例子中就是分词后的 words 列表)。

  • embedding_dim=4 定义了每个词的向量维度是 4(语料库很大,这个值就需要调特别大)。

所以 nn.Embedding 实际上创建了一个 len(words) * 4 大小的矩阵。每一行代表一个词对应的向量,初始向量是随机生成的,并且会在模型训练时逐渐调整。

2. 关于输入 torch.tensor(1)

当调用:

print(embed(torch.tensor(1)))

传入的是整数 1,这里的 1 是一个索引。这个索引告诉 Embedding 层从嵌入矩阵中返回第 1 行对应的嵌入向量。

例如,在这个调用中,索引 1 可能对应于分词结果中的第二个词("奥运会")。因此,

embed(torch.tensor(1))

返回的是与 "奥运会" 相关联的随机初始化向量

[ -0.0131, -0.9808, 0.9618, -0.4715 ]

注意:这里的词和向量的关系是由索引直接对应的,但代码中并没有显式创建词与索引的映射。

3. 为什么看不到显式声明的映射?

在代码中,实际上 words 列表中的每个词都隐式地被索引了:

  • 列表中的第一个词 "巴黎" 的索引是 0。

  • 第二个词 "奥运会" 的索引是 1。

  • 第三个词 "的" 的索引是 2。

  • 以此类推。

当传递 torch.tensor(1)Embedding 层时,它只是简单地根据这个整数索引从嵌入矩阵中选择相应的向量。由于 PyTorch 的 nn.Embedding 只处理索引到向量的映射,因此它只需要提供一个索引,它会自动从嵌入矩阵中返回相应的向量。

但是:在实际的 NLP 应用中,通常会有一个明确的词汇表,将词映射到索引。需要手动将词汇表的每个词与其对应的索引关联起来,然后使用这些索引去查找词嵌入。

4. 补充说明:如何创建显式的词汇映射

在实际的 NLP 项目中,我们通常会显式地创建一个词汇表并将其映射到索引,比如:

# 创建一个词汇表,将词映射到索引
vocab = {word: i for i, word in enumerate(words)}
​
# 打印词汇表
print(vocab)  # {"巴黎": 0, "奥运会": 1, "的": 2, ...}
​
# 查询"奥运会"的嵌入向量
word_idx = vocab["奥运会"]
print(embed(torch.tensor(word_idx)))

这样,可以明确地看到每个词与其索引的关系,随后就可以使用这些索引来查询词嵌入向量。

5. 总结

  • nn.Embedding 内部维护了一个嵌入矩阵,每一行对应一个词汇的向量表示。这个映射是基于词汇的索引进行的。

  • 在代码中,虽然没有显式创建词汇和索引的映射,但实际上 words 列表中的词通过其顺序隐式地与整数索引对应。

  • torch.tensor(1) 作为索引,告诉 nn.Embedding 去返回嵌入矩阵中对应行的向量。

  • 为了更清晰地控制词汇和索引的对应关系,建议显式地构建一个词汇表(如字典 vocab),并通过索引来访问词嵌入向量。

代码

import torch
import torch.nn as nn
import jieba

if __name__ == '__main__':
    # 1. 文本数据,准备要处理的文本字符串
    sentence = "巴黎奥运会的进度条已经过半,不少外国运动员在完成自己的比赛后踏上归途"
    
    # 2. 分词,将句子拆分成词语列表
    word_list = jieba.lcut(sentence)
    
    # 3. 构建词汇表 (去重,确保每个词只出现一次)
    unique_words = list(set(word_list))
    vocab_size = len(unique_words)  # 获取词汇表大小
    print("词汇表:", unique_words)
    print("词汇表大小:", vocab_size)
    
    
    # 4. 构建词嵌入层,参数为词汇表大小和词向量的维度
    embedding_dim = 4  # 定义词向量的维度
    embedding_layer = nn.Embedding(num_embeddings=vocab_size, embedding_dim=embedding_dim)
    
    # 5. 输出每个词的词向量
    for word_index, word in enumerate(unique_words):
        word_index = torch.tensor([word_index])  # 获取词的索引,并转换为张量
        word_embedding = embedding_layer(word_index)  # 获取对应的词向量
        print(f"词语: {word},索引: {word_index},词向量: {word_embedding}")

输出 

词汇表: ['不少', '过半', '的', '外国', '自己', '在', '已经', '进度条', '运动员', '归途', ',', '比赛', '巴黎', '后', '奥运会', '踏上', '完成']
词汇表大小: 17
词语: 不少,索引: tensor([0]),词向量: tensor([[2.1497, 1.0149, 0.9918, 0.5382]], grad_fn=<EmbeddingBackward0>)
词语: 过半,索引: tensor([1]),词向量: tensor([[ 0.8716, -1.8488,  0.2891,  1.1344]], grad_fn=<EmbeddingBackward0>)
词语: 的,索引: tensor([2]),词向量: tensor([[-1.7215,  2.2367, -1.3738, -0.3897]], grad_fn=<EmbeddingBackward0>)
词语: 外国,索引: tensor([3]),词向量: tensor([[-0.9484, -0.0633,  1.4533,  0.2565]], grad_fn=<EmbeddingBackward0>)
词语: 自己,索引: tensor([4]),词向量: tensor([[-0.1045,  1.2132, -0.6344,  2.3763]], grad_fn=<EmbeddingBackward0>)
词语: 在,索引: tensor([5]),词向量: tensor([[-0.4545, -0.6336,  0.6195, -0.6505]], grad_fn=<EmbeddingBackward0>)
词语: 已经,索引: tensor([6]),词向量: tensor([[-0.5900,  1.6933, -0.7605,  0.1284]], grad_fn=<EmbeddingBackward0>)
词语: 进度条,索引: tensor([7]),词向量: tensor([[ 2.2881, -0.3610,  1.8706, -0.5137]], grad_fn=<EmbeddingBackward0>)
词语: 运动员,索引: tensor([8]),词向量: tensor([[-0.3448,  0.1217, -0.0943,  0.3493]], grad_fn=<EmbeddingBackward0>)
词语: 归途,索引: tensor([9]),词向量: tensor([[ 0.0720,  0.2340,  1.6238, -0.6006]], grad_fn=<EmbeddingBackward0>)
词语: ,,索引: tensor([10]),词向量: tensor([[-0.5438,  0.6621, -2.4010, -0.3911]], grad_fn=<EmbeddingBackward0>)
词语: 比赛,索引: tensor([11]),词向量: tensor([[ 0.6472, -0.0451,  1.4426,  0.4981]], grad_fn=<EmbeddingBackward0>)
词语: 巴黎,索引: tensor([12]),词向量: tensor([[-0.9074, -0.0805,  0.9370, -1.2739]], grad_fn=<EmbeddingBackward0>)
词语: 后,索引: tensor([13]),词向量: tensor([[-1.0162,  0.8043, -0.6054,  0.7315]], grad_fn=<EmbeddingBackward0>)
词语: 奥运会,索引: tensor([14]),词向量: tensor([[-0.9563, -0.2509, -0.2551,  0.6060]], grad_fn=<EmbeddingBackward0>)
词语: 踏上,索引: tensor([15]),词向量: tensor([[ 0.9793,  1.2424, -0.4906, -0.5413]], grad_fn=<EmbeddingBackward0>)
词语: 完成,索引: tensor([16]),词向量: tensor([[ 0.5868,  0.6690,  0.3030, -0.6114]], grad_fn=<EmbeddingBackward0>)

这样一个句子就被分词,然后和初始化的词向量关联起来了,那么每个词向量就有了其表达的意义了,那么在神经网络中,由于反向传播的存在,这些词向量都相当于参数会被一步一步的更新,最终如果训练的好的话,那么同义词的话,距离会更近,反义词的距离会远一些,如果语料库更大,那么我们要区别出词语的向量就需要更多的维度,也就是embedding_dim 向量的元素 更多一些。最终训练完成,那么这些向量就有了词语的意义。

注意⚠️: 我们这里只是使用一句话来讲解这个分词和词嵌入层的作用,实际中,输入会是一个.txt的语料库,里面会有很多句子。

循环网络层 

RNN 网络原理讲解

循环神经网络(Recurrent Neural Network,RNN)的原理。

1. 为什么要使用循环神经网络(RNN)?

在自然语言处理(NLP)、时间序列预测等任务中,我们的数据通常是具有时间序列上下文依赖的。也就是说,某个时刻的数据不仅仅依赖于当前的输入,还依赖于之前的输入。例如:

• 在一个句子中,后面的单词往往和前面的单词有很强的关联。比如句子“我爱你”,如果我们颠倒顺序说“爱你我”或者“你我爱”,整个意思就完全变了。

• 在股价预测中,明天的股价往往取决于过去几天的走势,而不仅仅取决于今天的股价。

因此,我们需要一种神经网络模型,能够“记住”过去的数据,并基于这些历史数据来做当前时刻的预测。

RNN能够捕捉这种序列关系,并逐步处理序列中的每个单元,保留过去的信息,并在未来的计算中利用这些信息。这是循环神经网络(RNN)正是为了解决这个问题而设计的,解决自然语言、时间序列等任务时的关键优势。

2. 什么是循环神经网络?

在传统的前馈神经网络(Feedforward Neural Network, FNN)中,每个输入数据点是相互独立的,模型一次性处理输入,然后输出结果。它无法处理序列数据,因为它“看不到”前后输入之间的关系。

RNN 的核心不同点在于它具备一个“记忆”功能,能够在每一个时间步保留之前时间步的信息。它的网络结构中有一个隐藏状态(hidden state),这个隐藏状态会在每个时间步更新,并传递到下一个时间步,这样每一步的输出不仅仅依赖于当前的输入,还依赖于之前的隐藏状态,形成了循环结构。

2. RNN的基本原理

RNN 的核心特性是它的循环结构,可以在不同时间步之间传递隐藏状态信息。在每个时间步,RNN会接收当前的输入数据,并结合前一个时间步的隐藏状态,传递到当前神经元,计算新的隐藏状态和输出。举个例子:

  • 让我们用PPT中的例子“我爱你”来解释这个概念:

        1.    输入“我”:RNN接收输入“我”,结合初始隐藏状态(通常为零向量),计算出一个新的隐藏状态  h_1
        2.    输入“爱”:接下来RNN接收输入“爱”,这时候它不只依赖“爱”这个词的输入,还会结合之前的隐藏状态  h_1 ,输出新的隐藏状态  h_2  和当前时刻的输出。
        3.    输入“你”:同样,RNN接收输入“你”,结合前一个隐藏状态  h_2​​​​​​​  计算新的隐藏状态  h_3  和最终输出。

    这种“记住”过去输入的能力,使得RNN能够很好地处理上下文依赖的任务。

3. 为什么RNN可以实现“循环”?

在PPT中,我们看到了一个非常形象的RNN结构图,每个蓝色的神经元代表一个时间步的计算。虽然看起来像是不同的神经元,但实际上,RNN的神经元是共享的,也就是说,不同时间步上,实际上是同一个神经元在反复使用

具体来说,每一步的计算结果(隐藏状态)都会被传递到下一时间步,这就是所谓的“循环”:

• 当前时间步的输出不仅依赖于当前的输入,还依赖于前一个时间步的隐藏状态。

• 这种循环结构使得RNN能够在处理序列时保持对之前输入的记忆,直到序列结束。

3. RNN的循环过程

如PPT中所示,RNN的结构是一个循环网络。每个神经元接收当前时间步的输入和前一个时间步的隐藏状态,计算出新的隐藏状态和输出。

  • 初始隐藏状态 ( h_0 ) 一般是零向量。

  • 每个时间步的输出和更新是基于当前的输入向量 ( x_t) 和前一个时间步的隐藏状态 (h_{t-1} ) 计算出来的。

尽管在图中展示的是多个神经元,但实际上所有的时间步共享同一个神经元。这个共享机制让RNN能够有效处理长序列数据。

4. RNN 的内部计算

​​​​​​​

通过这个公式,RNN能够不断更新隐藏状态,并保持对序列数据的记忆。

5. RNN的应用

RNN在自然语言处理中的应用非常广泛,典型任务包括:

  • 文本生成:给定句子的前几个词,预测下一个词。

  • 机器翻译:从源语言序列翻译成目标语言序列。

  • 情感分析:根据句子或段落的内容判断其情感倾向。

6. 具体例子:句子“我爱你”

通过具体的例子“我爱你”,我们可以详细解释RNN的计算过程。

这种逐步计算的方式使得RNN能够记住前面的输入,并结合当前的输入,生成当前的输出。

7. 优点与局限

优点

  • 处理序列数据的能力:RNN非常适合处理序列化的数据,如自然语言、时间序列、视频等。它能够利用前后信息做出更加合理的预测或分类。

    共享参数:每一个时间步使用相同的神经元和参数,这使得模型可以很好地泛化,处理任意长度的序列。

局限

  • 梯度消失问题:当序列过长时,RNN很难将早期的信息保留到后面的时间步,这会导致梯度消失问题,从而使模型难以学习到长程依赖关系。这也是为什么后来出现了长短期记忆网络(LSTM)和 门控循环单元(GRU)来解决这个问题。

总结

RNN的核心优势在于它能够处理带有时间序列或上下文依赖的数据,通过循环结构,它能够记住过去的信息,并结合当前输入来做出预测。这种特性在自然语言处理、语音识别、时间序列预测等领域中非常重要。

我们今天讲解了RNN的基本工作原理和它为什么能够捕捉到序列中的依赖关系。

接下来我们来讲解一个三层循环神经网络(RNN)的计算过程(事实上我们一般是使用一层的,很少说用三层来做RNN),通过实例推演每个时间步的计算,并解释为什么它能够处理序列数据。为了简单,我们假设句子为“我爱你”,并且每个词用向量表示。

1. 设定输入

  • 输入句子是“我爱你”,每个词语通过词嵌入层转换成向量:

    • “我” -> 向量 (x_1)

    • “爱” -> 向量 ( x_2 )

    • “你” -> 向量 ( x_3 )

2. RNN层设计

在RNN中,每个时间步会有输入  x_t (当前时刻的输入向量),以及前一个时间步的隐藏状态  h_{t-1}。隐藏状态可以看作是记忆,它记录了之前时间步的信息。

初始的隐藏状态  h_0  一般设置为全零向量  h_0 = [0, 0, 0] (具体维度是几个0,自己设定,是超参数)。

我们假设使用三层的RNN,每层都有隐藏状态,以下是结构:

  • 第一层RNN会接收输入数据 ( x_t ),并计算出隐藏状态 ( h_1 )。

  • 第二层RNN使用第一层的隐藏状态作为输入,计算出新的隐藏状态 ( h_2)。

  • 第三层RNN再接收第二层的隐藏状态,最终输出 (h_3 )。

每一层的计算遵循公式:

3. 计算过程推演

时间步1:处理“我”

输入: 当前时间步的输入是“我”的向量  x_1 ,同时有初始隐藏状态  h_0(初始值通常为全零向量,后续的隐藏状态则是计算出来的。)

加权和:RNN会计算“我”的输入  x_1  和初始隐藏状态  h_0  的加权和:

具体来看,计算步骤如下:
    •     W_{ih} x_1:将输入 x_1  乘以权重矩阵  W_{ih} ,表示输入的加权求和。
    •     W_{hh} h_0 :将前一个时间步的隐藏状态  h_0  乘以权重矩阵  W_{hh}
    •    将以上两部分相加,再加上偏置  b_{ih}  和  b_{hh} ,形成一个新的隐藏状态 h_1
    •    使用激活函数  \tanh 来处理这个加权和,使得输出值限制在 -1 到 1 之间。
得到的隐藏状态  h_1  就包含了输入“我”的信息,记住了第一步的内容。​​​​​​​

时间步2:处理“爱”

输入: 当前时间步的输入是“爱”的向量  x_2 ,同时有前一步的隐藏状态 h_{1,3}

加权和:​​​​​​​RNN会计算“我”的输入  x_1  和初始隐藏状态 h_{1,3} 的加权和:

h_2 = \tanh(W_{ih} x_2 + b_{ih} + W_{hh} h_{1,3} + b_{hh})

这一步的计算步骤与第一个‘我’时间步类似,但是这次隐藏状态  h_{1,3}   不是全零,而是来自于前一个时间步第三层的输出。RNN会将“爱”的信息和“我”的信息结合,生成一个新的隐藏状态  h_2
解释:通过这种方式,RNN在第二个时间步不仅考虑了“爱”的输入,还记住了之前输入“我”的信息。因此, h_2  包含了“我”和“爱”两个词的上下文信息。

​​​​​​​

时间步3:处理“你”

输入: 当前时间步的输入是“你”的向量  x_3 ,同时有前一个时间步的隐藏状态 h_{2,3}  。

加权和:RNN继续计算“你”的输入  x_3  和隐藏状态  h_{2,3}  的加权和:

h_3 = \tanh(W_{ih} x_3 + b_{ih} + W_{hh} h_{2,3} + b_{hh})

最终生成的隐藏状态  h_{3,3}  包含了“我”、“爱”、“你”这三个词的信息。此时,RNN已经处理完了整个序列,它对这个句子的上下文有了完整的理解。

4. 输出层(可选)

在每一个时间步,RNN除了更新隐藏状态,还可以产生一个输出。这通常是通过输出层(例如全连接层)来实现。假设我们在每个时间步预测下一个单词:

• 在处理“我”时,预测下一个单词可能是“爱”。

• 在处理“爱”时,预测下一个单词可能是“你”。

• 在处理“你”时,可能会预测句子的结束。

这一步通常会结合交叉熵损失(cross-entropy loss)来优化模型,使得RNN能够更准确地预测下一个词。

5. 设计思路总结

通过这个例子,我们可以看到 RNN 是如何利用循环结构来处理序列数据的。它的关键特点是隐藏状态的循环传播,每个时间步的计算不仅仅依赖于当前输入,还结合了之前时间步的信息。这种机制使得RNN能够捕捉序列中的上下文依赖。

通过三层的RNN,我们每个时间步的输入不仅依赖于当前词语的向量表示,还结合了前面时间步的隐藏状态。这样的设计确保了模型能够记住之前时间步的上下文信息,从而在序列任务中捕捉前后依赖关系。

每一层RNN不断更新隐藏状态,并将其传递给下一层,这种多层堆叠的方式能够提高模型的表达能力,使得模型在处理复杂的序列任务时表现更好。

6.RNN的关键步骤总结:

    1.    输入数据: 每个时间步接收当前的输入向量  x_t
    2.    隐藏状态:每个时间步保留和更新一个隐藏状态  h_t ,它结合了之前时间步的隐藏状态  h_{t-1}  和当前的输入。
    3.    激活函数:通过  \tanh   激活函数对加权和进行非线性变换。
    4.    循环传递:每一步的隐藏状态被传递到下一个时间步,形成“循环”结构。

通常而言,我们使用RNN 循环神经网络的时候都是只有一层循环层的,而不是多层循环。

每一层的 W ,b 是固定的对吗?​​​​​​​

是的,每一层的权重矩阵 W 和偏置项 b  是固定的,也就是说,同一层在处理不同的输入(例如“我”、“爱”、“你”)时,权重和偏置不发生变化。具体来说:

1.输入权重矩阵 W_{ih}  和输入偏置项  b_{ih}

  • ( W_{ih}):输入到隐藏状态的权重矩阵,表示如何将每个时间步的输入(例如“我”、“爱”、“你”对应的词向量)映射到隐藏状态。

  • (b_{ih}):输入到隐藏状态的偏置项。

  • 这两个参数在整个序列中的所有时间步都是共享的,也就是说,不管是“我”、“爱”还是“你”作为输入,它们使用的都是相同的 ( W_{ih}) 和 ( b_{ih} )。​​​​​​​

2.隐藏状态权重矩阵 ( W_{hh}) 和隐藏状态偏置项 ( b_{hh})

  • ( W_{hh}):隐藏状态到隐藏状态的权重矩阵,表示如何将前一个时间步的隐藏状态 ( h_{t-1} ) 映射到当前时间步。

  • ( b_{hh} ):隐藏状态的偏置项。

  1. 同样,( W_{hh} ) 和 ( b_{hh} ) 也在所有时间步中共享,不管是处理“我”、“爱”还是“你”,它们使用的都是相同的权重和偏置。

3. 注意⚠️:输入权重矩阵 W_{ih}  , b_{ih} 和  隐藏状态权重矩阵  W_{hh},  b_{hh} 并不是想等的,神经网络中的每一层的值和每一层的值也是不想等的。各自有各自的权重和偏置值,只是一旦值被固定下来,每一层就确定好了值,那么不管输入的文字是什么,输入层和隐藏状态的 W 和 b 就不会再改变了~

​​​​​​​

为什么权重和偏置共享?

3.权重共享的原因

  • RNN的设计初衷就是处理序列数据,模型希望在每个时间步都能应用相同的转换规则。因此,权重共享意味着RNN在处理每个时间步的输入时,应用相同的计算机制,从而让模型具有很好的泛化能力,能够处理任意长度的序列。

  1. 减少参数的数量

    • 如果每个时间步都有不同的权重矩阵,那么整个网络的参数数量将非常庞大,训练过程会变得更加复杂,容易过拟合。共享权重能够显著减少需要学习的参数,使得模型在合理的计算资源内可以处理更长的序列。

总结:

  • 同一层的权重矩阵 ( W_{ih}, W_{hh} ) 和偏置项 ( b_{ih}, b_{hh} ) 是固定的,在处理整个序列中的每个时间步时都是共享的。

  • 输入“我”、“爱”、“你”时使用相同的权重和偏置,不同时间步只改变输入向量和前一个时间步的隐藏状态。

这个设计确保了RNN能够高效地处理序列数据,并捕捉到前后文之间的关系。

好了我们的循环网络层的原理就讲解完毕了,接下来我们会用一个文本生成的案例,去讲解如何使用pytorch 来应用 RNN 循环神经网络进行文本生成。

文本生成案例讲解:

好了,具体代码可以自己思考下,我会另开一篇文章去写完整的代码过程,预知后续如何,请听下回分解~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值