TensorFlow深度学习实战——基于自编码器构建句子向量

    • 0. 前言
    • 1. 句子向量
    • 2. 基于自编码器构建句子向量
      • 2.1 数据处理
      • 2.2 模型构建与训练
    • 3. 模型测试
    • 相关链接

0. 前言

在本节中,我们将构建和训练一个基于长短期记忆 (Long Short Term Memory, LSTM) 的自编码器,用于生成 Reuters-21578 语料库中文档的句子向量。我们已经学习了如何使用词嵌入表示一个词,从而创建表示该词在其上下文中含义的向量。本节中,我们将学习如何为句子构建句子向量,句子是单词的序列,因此句子向量表示一个句子的含义。

1. 句子向量

构建句子向量 (Sentence Vector) 的最简单方法是将句子中所有单词的向量加总起来,然后除以单词数量。但这种方法将句子视为词袋,未考虑单词的顺序,在这种情况下,“The dog bit the man” 和 “The man bit the dog” 具有相同的句子向量。长短期记忆 (Long Short Term Memory, LSTM) 设计用于处理序列输入,并考虑单词的顺序,从而能够得到更好、更自然的句子表示。

2. 基于自编码器构建句子向量

2.1 数据处理

(1) 首先,导入所需的库:

from sklearn.model_selection import train_test_split
from tensorflow.keras.callbacks import ModelCheckpoint
from tensorflow.keras.layers import Input
from tensorflow.keras.layers import RepeatVectorfrom tensorflow.keras.layers import LSTM
from tensorflow.keras.layers import Bidirectional
from tensorflow.keras.models import Model
from tensorflow.keras.preprocessing import sequence
from scipy.stats import describe
import collections
import matplotlib.pyplot as plt
import nltk
import numpy as np
import os
from time import gmtime, strftime
from tensorflow.keras.callbacks import TensorBoard
import re
# Needed to run only once
nltk.download('punkt')
nltk.download('reuters')
from nltk.corpus import reuters

(2) 下载完成后使用以下命令解压 Reuters 语料库:

$ unzip ~/nltk_data/corpora/reuters.zip -d ~/nltk_data/corpora

(3) 接下来,由于需要使用 GloVe 嵌入,因此首先下载 glove.6B.zip 并解压缩:

$ unzip glove.6B.zip

(4) 把每个文本块(文档)转换为一个句子列表,列表中每个元素表示一个句子。同时,每个句子中的单词在添加时会被规范化。规范化包括移除所有数字并将其替换为数字 9,然后将单词转换为小写。同时,计算单词频率,得到单词频率表 word_freqs

def is_number(n):temp = re.sub("[.,-/]", "",n)return temp.isdigit()# parsing sentences and building vocabulary
word_freqs = collections.Counter()
documents = reuters.fileids()
#ftext = open("text.tsv", "r")
sents = []
sent_lens = []
num_read = 0
for i in range(len(documents)):# periodic heartbeat reportif num_read % 100 == 0:print("building features from {:d} docs".format(num_read))# skip docs without specified topictitle_body = reuters.raw(documents[i]).lower()if len(title_body) == 0:continuenum_read += 1# convert to list of word indexestitle_body = re.sub("\n", "", title_body)for sent in nltk.sent_tokenize(title_body):for word in nltk.word_tokenize(sent):if is_number(word):word = "9"word = word.lower()word_freqs[word] += 1sents.append(sent)sent_lens.append(len(sent))

(5) 获取关于语料库的信息,用于确定合适的 LSTM 网络常量:

print("Total number of sentences are: {:d} ".format(len(sents)))
print ("Sentence distribution min {:d}, max {:d} , mean {:3f}, median {:3f}".format(np.min(sent_lens), np.max(sent_lens), np.mean(sent_lens), np.median(sent_lens)))
print("Vocab size (full) {:d}".format(len(word_freqs)))

输出的语料库信息如下:

Total number of sentences are: 50470 
Sentence distribution min 1, max 3688 , mean 167.072657, median 155.000000
Vocab size (full) 33743

(6) 根据以上信息,为 LSTM 模型设置常量。将 VOCAB_SIZE 设为 5000,即词汇表包含最常见的 5,000 个单词,这覆盖了语料库中 93% 以上的单词。其余单词视为超出词汇范围 (out of vocabulary, OOV) 并用词元 UNK 替代。在预测时,模型未见过的单词也会替换为词元 UNK。序列长度 SEQUENCE_LEN 设为训练集中句子的中位数长度的一半。长度小于 SEQUENCE_LEN 的句子用 PAD 字符进行填充,而比 SEQUENCE_LEN 长的句子将被截断:

VOCAB_SIZE = 5000
EMBED_SIZE = 50
LATENT_SIZE = 512
SEQUENCE_LEN = 50

(7) 由于 LSTM 的输入需要数值型数据,需要建立一个在单词和单词 ID 之间转换的查找表。由于将词汇表大小限制为 5,000,并且还需要添加两个特殊词元 PADUNK,因此查找表中包含了最常出现的 4,998 个单词以及 PADUNK 词元:

# word2id = collections.defaultdict(lambda: 1)
word2id = {}
word2id["PAD"] = 0
word2id["UNK"] = 1
for v, (k, _) in enumerate(word_freqs.most_common(VOCAB_SIZE - 2)):word2id[k] = v + 2
id2word = {v: k for k, v in word2id.items()}

(8) 网络输入为单词序列,每个单词由一个向量表示。可以使用独热编码 (one-hot encoding) 来表示每个单词,但这会使输入数据非常庞大。因此,我们使用 50 维的 GloVe 嵌入来编码每个单词。嵌入生成一个形状为 (VOCAB_SIZE, EMBED_SIZE) 的矩阵,其中每一行表示词汇表中一个单词的 GloVe 嵌入,PADUNK (分别是 01 )分别用零和随机均值填充:

def lookup_word2id(word):try:return word2id[word]except KeyError:return word2id["UNK"]def load_glove_vectors(glove_file, word2id, embed_size):embedding = np.zeros((len(word2id), embed_size))fglove = open(glove_file, "rb")for line in fglove:cols = line.strip().split()word = cols[0].decode('utf-8')if embed_size == 0:embed_size = len(cols) - 1if word in word2id:vec = np.array([float(v) for v in cols[1:]])embedding[lookup_word2id(word)] = vecembedding[word2id["PAD"]] = np.zeros((embed_size))embedding[word2id["UNK"]] = np.random.uniform(-1, 1, embed_size)return embedding

(9) 接下来,生成嵌入:

sent_wids = [[lookup_word2id(w) for w in s.split()] for s in sents]
sent_wids = sequence.pad_sequences(sent_wids, SEQUENCE_LEN)# load glove vectors into weight matrix
embeddings = load_glove_vectors("glove.6B/glove.6B.{:d}d.txt".format(EMBED_SIZE), word2id, EMBED_SIZE)
print(embeddings.shape)

自编码器模型接受 GloVe 单词向量序列,并学习生成一个与输入序列相似的序列。编码器 LSTM 将序列压缩成一个固定大小的上下文向量,解码器 LSTM 使用该上下文向量来重建原始序列:

模型架构

(10) 由于输入数据量较大,我们将使用生成器来生成每一批次输入,生成器产生形状为 (BATCH_SIZE, SEQUENCE_LEN, EMBED_SIZE) 的张量批次,其中,BATCH_SIZE64,由于使用的是 50 维的 GloVe 向量,因此 EMBED_SIZE50。在每个训练 epoch 开始时打乱句子,每个批次包含 64 个句子,每个句子表示为一个 GloVe 单词向量的向量。如果词汇表中的单词没有对应的 GloVe 嵌入,则用零向量表示。构建两个生成器实例,一个用于训练数据,另一个用于测试数据,分别包含原始数据集的 70%30%

BATCH_SIZE = 64
NUM_EPOCHS = 20
def sentence_generator(X, embeddings, batch_size):while True:# loop once per epochnum_recs = X.shape[0]indices = np.random.permutation(np.arange(num_recs))num_batches = num_recs // batch_sizefor bid in range(num_batches):sids = indices[bid * batch_size: (bid + 1) * batch_size]Xbatch = embeddings[X[sids, :]]yield Xbatch, Xbatch# split sentences into training and test
train_size = 0.7
Xtrain, Xtest = train_test_split(sent_wids, train_size=train_size)
print("number of sentences: ", len(sent_wids))
print(Xtrain.shape, Xtest.shape)# define training and test generators
train_gen = sentence_generator(Xtrain, embeddings, BATCH_SIZE)
test_gen = sentence_generator(Xtest, embeddings, BATCH_SIZE)

2.2 模型构建与训练

定义自编码器,自编码器由编码器 LSTM 和解码器 LSTM 组成。编码器 LSTM 读取形状为 (BATCH_SIZE, SEQUENCE_LEN, EMBED_SIZE) 的张量,表示一批次句子。每个句子表示为一个固定长度为 SEQUENCE_LEN 的填充序列,每个单词用一个 50 维的 GloVe 向量表示。编码器 LSTM 的输出维度使用超参数 LATENT_SIZE 定义,代表从训练好的自编码器的编码器部分获得的句子向量的大小,维度为 LATENT_SIZE 的向量空间表示了编码句子含义的潜空间。LSTM 的输出是大小为 LATENT_SIZE 的向量,因此对于一个批次,输出张量的形状是 (BATCH_SIZE, LATENT_SIZE)。接下来,将该张量输入到 RepeatVector 层,该层会在整个序列中复制这个向量,即该层输出张量形状为 (BATCH_SIZE, SEQUENCE_LEN, LATENT_SIZE)。这个张量输入到解码器 LSTM 中,其输出维度为 EMBED_SIZE,因此输出张量的形状为 (BATCH_SIZE, SEQUENCE_LEN, EMBED_SIZE),也就是说,与输入张量的形状相同:

# define autoencoder network
inputs = Input(shape=(SEQUENCE_LEN, EMBED_SIZE), name="input")
encoded = Bidirectional(LSTM(LATENT_SIZE), merge_mode="sum",name="encoder_lstm")(inputs)
decoded = RepeatVector(SEQUENCE_LEN, name="repeater")(encoded)
decoded = Bidirectional(LSTM(EMBED_SIZE, return_sequences=True),merge_mode="sum",name="decoder_lstm")(decoded)autoencoder = Model(inputs, decoded)

使用 Adam 优化器和 MSE 损失函数编译模型。选择 MSE 的原因是我们希望重建一个具有相似含义的句子,即在 LATENT_SIZE 维度的嵌入空间中接近原始句子的句子。将损失函数定义为均方误差,并选择 Adam 优化器:

autoencoder.compile(optimizer="adam", loss="mse")

训练自编码器 20epoch

# train
num_train_steps = len(Xtrain) // BATCH_SIZE
num_test_steps = len(Xtest) // BATCH_SIZE
history = autoencoder.fit(train_gen,steps_per_epoch=num_train_steps,epochs=NUM_EPOCHS,validation_data=test_gen,validation_steps=num_test_steps) plt.plot(history.history["loss"], label = "training loss")
plt.plot(history.history["val_loss"], label = "validation loss")
plt.xlabel("epochs")
plt.ylabel("Loss")
plt.legend()
plt.show()

下图显示了训练和验证数据的损失变化情况,可以看到随着模型的学习,损失逐渐减少:

训练过程

由于输入是嵌入矩阵,因此输出也是词嵌入矩阵。由于嵌入空间是连续的,而词汇表是离散的,并不是每个输出嵌入都会对应一个单词。我们能做的就是找到一个最接近输出嵌入的单词,以重建原始文本,所以我们将以不同的方式评估自编码器。
由于自编码器的目标是产生良好的潜表示,我们将使用原始输入和自编码器的输出生成的潜向量进行比较。首先,提取编码器组件提取:

# collect autoencoder predictions for test set
test_inputs, test_labels = next(test_gen)
preds = autoencoder.predict(test_inputs)# extract encoder part from autoencoder
encoder = Model(autoencoder.input,autoencoder.get_layer("encoder_lstm").output)

3. 模型测试

在测试集上运行自编码器,以返回预测的嵌入。接着,将输入嵌入和预测嵌入都通过编码器,生成各自的句子向量,并使用余弦相似度比较这两个向量。余弦相似度接近 1 表示两个向量高度相似,而余弦相似度接近 0 则表示两个向量相似度较低。
在包含 500 个测试句子的随机子集上测试,并计算源嵌入和自编码器生成的目标嵌入之间的句子向量的余弦相似度值:

def compute_cosine_similarity(x, y):return np.dot(x, y) / (np.linalg.norm(x, 2) * np.linalg.norm(y, 2))# compute difference between vector produced by original and autoencoded
k = 500
cosims = np.zeros((k))
i = 0
for bid in range(num_test_steps):xtest, ytest = next(test_gen)ytest_ = autoencoder.predict(xtest)Xvec = encoder.predict(xtest)Yvec = encoder.predict(ytest_)for rid in range(Xvec.shape[0]):if i >= k:breakcosims[i] = compute_cosine_similarity(Xvec[rid], Yvec[rid])if i <= 10:print(cosims[i])i += 1if i >= k:breakplt.hist(cosims, bins=10, density=True)
plt.xlabel("cosine similarity")
plt.ylabel("frequency")
plt.show()

10 个余弦相似度值如下所示。可以看到,这些向量之间相似较高:

0.9826117753982544
0.983581006526947
0.9853078126907349
0.9853724241256714
0.9793808460235596
0.9805294871330261
0.9780978560447693
0.9855653643608093
0.9836362600326538
0.9835963845252991
0.9832736253738403

下图显示了前 500 个句子的句子向量的余弦相似度值分布的直方图。

余弦相似度

这证实了自编码器输入和输出生成的句子向量非常相似,表明生成的句子向量是对句子的良好表示。

相关链接

TensorFlow深度学习实战(1)——神经网络与模型训练过程详解
TensorFlow深度学习实战(2)——使用TensorFlow构建神经网络
TensorFlow深度学习实战(3)——深度学习中常用激活函数详解
TensorFlow深度学习实战(4)——正则化技术详解
TensorFlow深度学习实战(5)——神经网络性能优化技术详解
TensorFlow深度学习实战(6)——回归分析详解
TensorFlow深度学习实战(7)——分类任务详解
TensorFlow深度学习实战(8)——卷积神经网络
TensorFlow深度学习实战(9)——构建VGG模型实现图像分类
TensorFlow深度学习实战(10)——迁移学习详解
TensorFlow深度学习实战(11)——风格迁移详解
TensorFlow深度学习实战(12)——词嵌入技术详解
TensorFlow深度学习实战(13)——神经嵌入详解
TensorFlow深度学习实战(14)——循环神经网络详解
TensorFlow深度学习实战(15)——编码器-解码器架构
TensorFlow深度学习实战(16)——注意力机制详解
TensorFlow深度学习实战(17)——主成分分析详解
TensorFlow深度学习实战(18)——K-means 聚类详解
TensorFlow深度学习实战(19)——受限玻尔兹曼机
TensorFlow深度学习实战(20)——自组织映射详解
TensorFlow深度学习实战(21)——Transformer架构详解与实现
TensorFlow深度学习实战(22)——从零开始实现Transformer机器翻译
TensorFlow深度学习实战(23)——自编码器详解与实现
TensorFlow深度学习实战(24)——卷积自编码器详解与实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/88001.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/88001.shtml
英文地址,请注明出处:http://en.pswp.cn/web/88001.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言使用Protobuf进行网络通信

笔者前面博文Go语言网络游戏服务器模块化编程介绍了Go语言在开发网络游戏时如何进行模块化编程&#xff0c;在其中使用了Protobuf进行网络通信。在Protobuf官方实现中并没有生成C语言的实现&#xff0c;不过有一个开源的protobuf-c可以使用。 先来看看protobuf-c生成的代码&am…

vue3 随手笔记12--组件通信方式9/5--useAttrs

一 什么是useAttrsuseAttrs 是 Vue 3 Composition API 中提供的一个函数&#xff0c;它属于 Vue 的组合式 API 工具集的一部分。通过 useAttrs&#xff0c;你可以访问传递给组件但未被声明为 props 的所有属性。这对于处理非 prop 特性&#xff08;attributes&#xff09;特别有…

HumanRisk-自动化安全意识与合规教育平台方案

权威数据显示&#xff0c;74%以上的数据泄露与网络安全事件归根结底与人为因素有关&#xff0c;60%以上的网络安全事件是由内部人员失误造成的。这一现状揭示了一个核心命题&#xff1a;网络安全威胁正从技术漏洞转向“人为因素风险”。Gartner的调查发现&#xff0c;即便接受了…

2025年食品科学与健康大数据国际会议(SHBD 2025)

2025年食品科学与健康大数据国际会议 2025 International Conference on Food Science and Health Big Data&#xff08;一&#xff09;大会信息 会议简称&#xff1a;ICFSHBD 2025 大会地点&#xff1a;中国上…

CompareFace人脸识别算法环境部署

一、docker 安装 步骤1&#xff1a;启用系统功能 右键开始菜单 → 应用和功能 → 点击 程序和功能 → 勾选 Hyper-V 和 Windows子系统Linux 步骤2&#xff1a;获取安装包 访问Docker官网安装包下载页 &#xff0c;下载「Docker Desktop Installer.rar」压缩包 步骤3&#…

STM32固件升级设计——内部FLASH模拟U盘升级固件

目录 一、功能描述 1、BootLoader部分&#xff1a; 2、APP部分&#xff1a; 二、BootLoader程序制作 1、分区定义 2、 主函数 3、配置USB 4、配置fatfs文件系统 5、程序跳转 三、APP程序制作 四、工程配置&#xff08;默认KEIL5&#xff09; 五、运行测试 结束语…

操作系统引导过程

操作系统引导是指计算机利用 CPU 运行特定程序&#xff0c;通过程序识别硬盘&#xff0c;识别硬盘分区&#xff0c;识别硬盘分区上的操作系统&#xff0c;最后通过程序启动操作系统。 引导流程&#xff08;8步核心环节&#xff09; 1. 激活CPU 加电后CPU自动读取 ROM中的Boot…

Safetensors与大模型文件格式全面解析

Safetensors是一种专为存储大型张量数据设计的文件格式&#xff0c;由Hugging Face团队开发&#xff0c;旨在提供安全高效的模型参数存储解决方案。下面将详细介绍Safetensors格式及其特点&#xff0c;并全面梳理当前主流的大模型文件格式。 一、Safetensors格式详解 1. 基本概…

分布式理论:CAP、Base理论

目录 1、CAP理论 1.1、介绍 1.2、CAP的三种选择 1.3、CAP的注意事项 2、BASE理论 2.1、定义介绍 2.2、最终一致性的介绍 2.3、BASE的实现方式 2.4、与ACID的对比 3、CAP与BASE的联系 4、如何选择CAP 前言 在分布式系统中&#xff0c;CAP理论和BASE理论是指导系统设计…

【最新】飞算 JavaAl安装、注册,使用全流程,让ai自己给你写代码,解放双手

目录 飞算 JavaAl 产品介绍 安装飞算 JavaAl 第一步&#xff1a;点击 File->Setting 第二步&#xff1a;点击 Plugins 第三步&#xff1a;搜索 CalEx-JavaAI 第四步&#xff1a;点击 Install 进行安装 第五步&#xff1a;点击 Install &#xff0c;查看安装好的飞算…

无人设备遥控器之姿态控制算法篇

无人设备遥控器的姿态控制算法通过传感器数据融合、控制算法优化和执行机构调节实现动态平衡&#xff0c;核心算法包括PID控制、自适应控制、模型预测控制&#xff08;MPC&#xff09;&#xff0c;以及数据融合中的互补滤波和卡尔曼滤波&#xff0c;同时涉及四元数算法和深度强…

【加解密与C】Base系列(三)Base85

Base85 编码简介 Base85&#xff08;也称为 Ascii85&#xff09;是一种二进制到文本的编码方案&#xff0c;用于将二进制数据转换为可打印的ASCII字符。它的效率高于Base64&#xff0c;但生成的字符串可能包含特殊字符&#xff08;如引号或反斜杠&#xff09;&#xff0c;需在…

Docker企业级应用:从入门到生产环境最佳实践

一、Docker核心概念与架构 1.1 Docker技术栈 #mermaid-svg-CUEiyGo05ZYG524v {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-CUEiyGo05ZYG524v .error-icon{fill:#552222;}#mermaid-svg-CUEiyGo05ZYG524v .error-te…

8、保存应用数据

目录用户首选项的使用用户首选项主要API用户首选项开发流程用户首选项开发实践关系型数据库的使用关系型数据库工作流程关系型数据库开发实践用户首选项的使用 用户首选项主要API 用户首选项开发流程 成功的获取了一个名为myStore的Preferences实例 保存了一个键值对&#x…

(C++)list列表相关基础用法(C++教程)(STL库基础教程)

源代码&#xff1a;#include <iostream> #include <list>using namespace std;int main(){list<int> numbers{10,20,30};numbers.push_front(5);numbers.push_back(40);auto it numbers.begin();advance(it,2);numbers.insert(it,15);cout<<"该列…

Spring CGLIB私有方法访问成员变量为null问题

场景 代码 RestController public class TestJob {Autowiredprivate XxService xxService;XxlJob("testCGLIB")private void doTest(){System.out.println("方法调用");System.out.println("成员变量注入:"(xxService!null));this.doInnerTest()…

Paimon本地表查询引擎LocalTableQuery详解

LocalTableQueryLocalTableQuery 是 Paimon 中实现本地化、带缓存的表查询的核心引擎。它的主要应用场景是 Flink 中的 Lookup Join。当 Flink 作业需要根据一个流中的 Key 去关联一个 Paimon 维表时&#xff0c;LocalTableQuery 可以在 Flink 的 TaskManager 节点上&#xff0…

使用协程简化异步资源获取操作

异步编程的两种场景 在异步编程中&#xff0c;回调函数通常服务于两种不同场景&#xff1a; 一次性资源获取&#xff1a;等待异步操作完成并返回结果。持续事件通知。监听并响应多个状态变更。 Kotlin为这两种场景提供了解决方案&#xff1a;使用挂起函数简化一次性资源获取…

ABP VNext + Cosmos DB Change Feed:搭建实时数据变更流服务

ABP VNext Cosmos DB Change Feed&#xff1a;搭建实时数据变更流服务 &#x1f680; &#x1f4da; 目录ABP VNext Cosmos DB Change Feed&#xff1a;搭建实时数据变更流服务 &#x1f680;TL;DR ✨&#x1f680;1. 环境与依赖 &#x1f3d7;️2. 服务注册与依赖注入 &…

STM32-定时器

定时器&#xff1a;有4个独立通道&#xff1a;输入捕获&#xff1b;输出比较PWM生成&#xff1b;单脉冲模式输出&#xff1b;可通外部信号控制定时器&#xff08;TIMx-ETR&#xff09;&#xff1b;支持针对定时的增量&#xff08;正交&#xff09;编码器、霍尔传感器电路通用定…