TensorFlow深度学习实战(13)——神经嵌入详解

0. 前言

神经嵌入 (Neural Embedding) 是一种通过神经网络模型将离散的符号(如词语、字符、图像等)映射到低维连续向量空间中的技术。它属于更广泛的嵌入 (Embedding) 技术范畴,在深度学习中起着关键作用。神经嵌入通过在神经网络训练过程中学习到的向量表示,捕捉了输入数据的潜在特征和语义信息。

1. 神经嵌入简介

Word2Vec 和 GloVe 提出以来,词嵌入技术已经取得了多方向的发展。其中一种方向是将词嵌入应用于非词汇环境,也称为神经嵌入 (Neural Embedding) 。我们知道,词嵌入利用了分布假设,即出现在相似上下文中的词通常具有相似的含义,其中上下文通常是围绕目标词的一个固定大小(单词数量)的窗口。
神经嵌入的核心思想与之相似,即出现在相似上下文中的实体通常彼此密切相关。构建这些上下文的方式通常依赖于具体情况。接下来,我们将介绍两种基础且通用的技术,能够应用于多种用例。

1.1 Item2Vec

Item2Vec 嵌入模型最早由 BarkanKoenigstein 提出,用于协同过滤 (collaborative filtering),即根据具有类似购买历史的用户向目标用户推荐商品。使用商品作为“词”,用户随时间购买的商品集合(即商品序列)作为“句子”,从中推导出“词上下文”。
例如,考虑在超市向购物者推荐商品的问题。假设超市销售 5,000 种商品,因此每种商品可以表示为大小为 5,000 的稀疏独热编码向量,每个用户由其购物车表示,即一系列独热编码向量。应用类似于 Word2Vec 中的上下文窗口,可以训练一个 skip-gram 模型来预测可能的商品对。学习到的嵌入模型将商品映射到一个稠密的低维空间,其中相似的商品彼此靠近,可以用于进行相似商品的推荐。

1.2 node2vec

node2vec 嵌入模型由 GroverLeskovec 提出,作为一种可扩展的方式学习图中节点特征。通过在图上执行大量固定长度的随机游走来学习图结构的嵌入。节点是视为“单词”,随机游走是从中派生“词上下文”的“句子”。

2. 数据集与模型分析

为了说明创建自定义神经嵌入的简便性,我们将利用词语之间的共现关系,生成类似于 node2vec 的模型或者更准确地说是一个基于图的嵌入,即 DeepWalk,用于分析 1987-2015 年间 NeurIPS 会议上的论文。
数据集是一个 11,463×5,812 的词频矩阵,其中行代表词语,列代表会议论文。我们将利用此数据构建论文的图,其中两篇论文之间的边表示它们之间共同出现的词语。
node2vecDeepWalk 都假设图是无向且无权重的。构建的图是无向的,因为两篇论文之间的关系是双向的。但是,边可以根据两篇文档之间词语共现的数量进行加权。在本节中,我们将共现数量大于 0 的情况视为有效的无权重边。

3. 实现神经嵌入

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

import gensim
import logging
import numpy as np
import os
import shutil
import tensorflow as tf

from scipy.sparse import csr_matrix
# from scipy.stats import spearmanr
from sklearn.metrics.pairwise import cosine_similarity

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

(2)UCI 下载数据,并将其转换为稀疏的词-文档矩阵 TD,然后通过将词-文档矩阵的转置与自身相乘来构建文档-文档矩阵 E。构建的图通过文档-文档矩阵表示为邻接矩阵或边矩阵。由于每个元素代表两个文档之间的相似性,我们通过将矩阵E中的非零元素设置为 1 来对矩阵 E 进行二值化处理:

DATA_DIR = "./data"
UCI_DATA_URL = "https://archive.ics.uci.edu/ml/machine-learning-databases/00371/NIPS_1987-2015.csv"

def download_and_read(url):
    local_file = url.split('/')[-1]
    p = tf.keras.utils.get_file(local_file, url, cache_dir=".")
    row_ids, col_ids, data = [], [], []
    rid = 0
    f = open(p, "r")
    for line in f:
        line = line.strip()
        if line.startswith("\"\","):
            # header
            continue
        if rid % 100 == 0:
            print("{:d} rows read".format(rid))
        # compute non-zero elements for current row
        counts = np.array([int(x) for x in line.split(',')[1:]])
        nz_col_ids = np.nonzero(counts)[0]
        nz_data = counts[nz_col_ids]
        nz_row_ids = np.repeat(rid, len(nz_col_ids))
        rid += 1
        # add data to big lists
        row_ids.extend(nz_row_ids.tolist())
        col_ids.extend(nz_col_ids.tolist())
        data.extend(nz_data.tolist())
    print("{:d} rows read, COMPLETE".format(rid))
    f.close()
    TD = csr_matrix((
        np.array(data), (
            np.array(row_ids), np.array(col_ids)
            )
        ),
        shape=(rid, counts.shape[0]))
return TD

# read data and convert to Term-Document matrix
TD = download_and_read(UCI_DATA_URL)
# compute undirected, unweighted edge matrix
E = TD.T * TD
# binarize
E[E > 0] = 1
print(E.shape)

(3) 获取了稀疏二值化的邻接矩阵E后,可以从每个顶点生成随机游走。从每个节点开始,构造 32 条最大长度为 40 个节点的随机游走,这些随机游走具有 0.15 的随机重启概率,这意味着对于任何节点,特定的随机游走有 15% 的概率在该节点结束。构建随机游走,并将其写入由 RANDOM_WALKS_FILE 指定的文件。为了说明输入的情况,查看该文件前 10 行,显示从节点 3274 开始的随机游走:

随机游走

需要注意的是,这是一个非常缓慢的过程。

(4) RANDOM_WALKS_FILE 中的样本看起来像语言中的句子,其中词汇表是图中的所有节点 ID。我们已经知道,词嵌入利用语言的结构生成词的分布式表示。DeepWalknode2vec 等图嵌入方案也使用随机游走生成的“句子”执行相同的操作。这些嵌入可以捕捉图中节点之间的相似性:

NUM_WALKS_PER_VERTEX = 32
MAX_PATH_LENGTH = 40
RESTART_PROB = 0.15

RANDOM_WALKS_FILE = os.path.join(DATA_DIR, "random-walks.txt")

def construct_random_walks(E, n, alpha, l, ofile):
    """ NOTE: takes a long time to do, consider using some parallelization
        for larger problems.
    """
    if os.path.exists(ofile):
        print("random walks generated already, skipping")
        return
    f = open(ofile, "w")
    for i in range(E.shape[0]):  # for each vertex
        if i % 100 == 0:
            print("{:d} random walks generated from {:d} starting vertices"
                .format(n * i, i))
        if i <= 3273:
            continue
        for j in range(n):       # construct n random walks
            curr = i
            walk = [curr]
            target_nodes = np.nonzero(E[curr])[1]
            for k in range(l):   # each of max length l, restart prob alpha
                # should we restart?
                if np.random.random() < alpha and len(walk) > 5:
                    break
                # choose one outgoing edge and append to walk
                try:
                    curr = np.random.choice(target_nodes)
                    walk.append(curr)
                    target_nodes = np.nonzero(E[curr])[1]
                except ValueError:
                    continue
            f.write("{:s}\n".format(" ".join([str(x) for x in walk])))

    print("{:d} random walks generated from {:d} starting vertices, COMPLETE".format(n * i, i))
    f.close()

# construct random walks (caution: long process!)
construct_random_walks(E, NUM_WALKS_PER_VERTEX, RESTART_PROB, MAX_PATH_LENGTH, RANDOM_WALKS_FILE)

(5) 创建词嵌入模型。Gensim 包提供了一个简单的 API,允许我们创建和训练 Word2Vec 模型,训练好的模型将序列化到 W2V_MODEL_FILE 指定的文件中。Documents 类允许我们流式处理大型输入文件以训练 Word2Vec 模型,避免出现内存问题。使用 skip-gram 模式训练 Word2Vec 模型,窗口大小为 10,这意味着训练模型来预测给定中心顶点的最多五个相邻顶点。每个顶点的嵌入结果是一个大小为 128 的稠密向量:

W2V_MODEL_FILE = os.path.join(DATA_DIR, "w2v-neurips-papers.model")

class Documents(object):
    def __init__(self, input_file):
        self.input_file = input_file

    def __iter__(self):
        with open(self.input_file, "r") as f:
            for i, line in enumerate(f):
                if i % 1000 == 0:
                    if i % 1000 == 0:
                        logging.info("{:d} random walks extracted".format(i))
                yield line.strip().split()


def train_word2vec_model(random_walks_file, model_file):
    if os.path.exists(model_file):
        print("Model file {:s} already present, skipping training"
            .format(model_file))
        return
    docs = Documents(random_walks_file)
    model = gensim.models.Word2Vec(
        docs,
        vector_size=128,    # size of embedding vector
        window=10,   # window size
        sg=1,        # skip-gram model
        min_count=2,
        workers=4
    )
    model.train(
        docs, 
        total_examples=model.corpus_count,
        epochs=50)
model.save(model_file)

# train model
train_word2vec_model(RANDOM_WALKS_FILE, W2V_MODEL_FILE)

(6) 得到的 DeepWalk 模型实际上就是一个 Word2Vec 模型,因此在单词的上下文中可以用 Word2Vec 完成的任务,在顶点的上下文中也可以进行。使用该模型计算文档之间的相似性:

def evaluate_model(td_matrix, model_file, source_id):
    model = gensim.models.Word2Vec.load(model_file).wv
    most_similar = model.most_similar(str(source_id))
    scores = [x[1] for x in most_similar]
    target_ids = [int(x[0]) for x in most_similar]
    # compare top 10 scores with cosine similarity between source and each target
    X = np.repeat(td_matrix[source_id].todense(), 10, axis=0)
    Y = td_matrix[target_ids].todense()
    cosims = [cosine_similarity(np.asarray(X[i]), np.asarray(Y[i]))[0, 0] for i in range(10)]
    for i in range(10):
        print("{:d} {:s} {:.3f} {:.3f}".format(
            source_id, str(target_ids[i]), cosims[i], scores[i]))
# evaluate
source_id = np.random.choice(E.shape[0])
evaluate_model(TD, W2V_MODEL_FILE, source_id)

输出结果如下所示。第一列和第二列是源和目标顶点的 ID。第三列是对应于源和目标文档的词向量之间的余弦相似度,第四列是 Word2Vec 模型报告的相似度分数。可以看到,在 10 个文档对中,余弦相似度只得到 4 个相似度,但 Word2Vec 模型能够在嵌入空间中检测到潜在的相似性。这类似于在独热编码和稠密嵌入之间的行为:

输出结果

小结

神经嵌入是将离散的符号映射到连续向量空间的强大技术,广泛应用于自然语言处理、推荐系统、计算机视觉和图数据分析等领域。通过神经网络训练,这些嵌入能够有效地捕捉数据中的潜在关系和语义信息,为各种任务提供了有效的特征表示。随着深度学习的发展,神经嵌入已经成为现代人工智能系统中不可或缺的一部分。

系列链接

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)——词嵌入技术详解

评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盼小辉丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值