【DL】第6章 问题匹配

  大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流

个人主页-Sonhhxg_柒的博客_CSDN博客 

欢迎各位→点赞 + 收藏⭐️ + 留言​

系列专栏 - 机器学习【ML】 自然语言处理【NLP】  深度学习【DL】

 foreword

✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。

如果你对这个系列感兴趣的话,可以关注订阅哟

我们现在已经看到了一些关于如何构建和使用词嵌入来将术语相互比较的示例。很自然地问我们如何将这个想法扩展到更大的文本块。我们可以创建整个句子或段落的语义嵌入吗?在本章中,我们将尝试这样做:我们将使用来自 Stack Exchange 的数据为整个问题构建嵌入;然后我们可以使用这些嵌入来查找类似的文档或问题。

我们将从 Internet 档案库中下载和解析我们的训练数据开始。然后我们将简要探讨 Pandas 如何帮助分析数据。在对我们的数据进行特征化和为手头的任务构建模型时,我们让 Keras 完成繁重的工作。然后,我们研究如何从 Pandas 中输入这个模型,DataFrame以及我们如何运行它来得出结论。

本章的代码可以在以下笔记本中找到:

06.1 问题匹配

6.1 从 Stack Exchange 获取数据

问题

您需要访问大量问题才能开始您的培训。

解决方案

使用 Internet 存档检索问题转储。

Stack Exchange 数据转储可在Internet Archive上免费获得,其中托管了许多有趣的数据集(以及努力提供整个 Web 的存档)。Stack Exchange 上的每个区域(例如,旅行、科幻等)的数据都使用一个 ZIP 文件进行布局。让我们下载旅行部分的文件:

xml_7z = utils.get_file(
    fname='travel.stackexchange.com.7z',
    origin=('https://ia800107.us.archive.org/27/'
            'items/stackexchange/travel.stackexchange.com.7z'),
)

虽然输入在技术上是一个 XML 文件,但其结构非常简单,我们只需读取单独的行并拆分字段即可。当然,这有点脆弱。我们将限制自己从数据集中处理 100 万条记录;这可以防止我们的内存使用量激增,并且应该有足够的数据供我们使用。我们将处理后的数据保存为 JSON 文件,这样下次我们就不必再次进行处理了:

def extract_stackexchange(filename, limit=1000000):
    json_file = filename + 'limit=%s.json' % limit

    rows = []
    for i, line in enumerate(os.popen('7z x -so "%s" Posts.xml'
                             % filename)):
        line = str(line)
        if not line.startswith('   limit:
            break

    with open(json_file, 'w') as fout:
        json.dump(rows, fout)

    return rows


rows = download_stackexchange()

讨论

Stack Exchange 数据集是一个很好的问题/答案对来源,它带有一个很好的重用许可证。只要你给出归属,你几乎可以以任何你想要的方式使用它。将压缩的 XML 转换为更易于使用的 JSON 格式是一个很好的预处理步骤。

6.2 使用 Pandas 探索数据

问题

您如何快速探索大型数据集,以确保它包含您期望的内容?

解决方案

使用 Python 的 Pandas。

Pandas 是一个强大的 Python 数据处理框架。在某些方面,它可以与电子表格相媲美;数据存储在行和列中,我们可以快速过滤、转换和汇总记录。让我们首先将我们的 Python 字典行转换为DataFrame. Pandas 试图“猜测”某些列的类型。我们将强制将我们关心的列转换为正确的格式:

df = pd.DataFrame.from_records(rows)
df = df.set_index('Id', drop=False)
df['Title'] = df['Title'].fillna('').astype('str')
df['Tags'] = df['Tags'].fillna('').astype('str')
df['Body'] = df['Body'].fillna('').astype('str')
df['Id'] = df['Id'].astype('int')
df['PostTypeId'] = df['PostTypeId'].astype('int')

现在df.head我们可以看到我们的数据库中发生了什么。

我们还可以使用 Pandas 快速查看我们数据中的热门问题:

list(df[df['ViewCount'] > 2500000]['Title'])
['How to horizontally center a <div> in another <div>?',
 'What is the best comment in source code you have ever encountered?',
 'How do I generate random integers within a specific range in Java?',
 'How to redirect to another webpage in JavaScript/jQuery?',
 'How can I get query string values in JavaScript?',
 'How to check whether a checkbox is checked in jQuery?',
 'How do I undo the last commit(s) in Git?',
 'Iterate through a HashMap',
 'Get selected value in dropdown list using JavaScript?',
 'How do I declare and initialize an array in Java?']

如您所料,最受欢迎的问题是关于常用语言的一般问题。

讨论

Pandas 是用于多种类型数据分析的绝佳工具,无论您只是想随意查看数据还是想进行深入分析。尝试将 Pandas 用于许多任务可能很诱人,但不幸的是,Pandas 界面根本不是常规的,对于复杂的操作,性能可能比使用真实数据库要差得多。在 Pandas 中查找比使用 Python 字典要昂贵得多,所以要小心!

6.3 使用 Keras 对文本进行特征化

问题

如何从文本中快速创建特征向量?

解决方案

使用TokenizerKeras 的课程。

在将文本输入模型之前,我们需要将其转换为特征向量。一种常见的方法是为文本中前N个单词中的每一个分配一个整数,然后用它的整数替换每个单词。Keras 让这变得非常简单:

from keras.preprocessing.text import Tokenizer
VOCAB_SIZE = 50000

tokenizer = Tokenizer(num_words=VOCAB_SIZE)
tokenizer.fit_on_texts(df['Body'] + ' ' + df['Title'])

现在让我们标记整个数据集的标题和正文:

df['title_tokens'] = tokenizer.texts_to_sequences(df['Title'])
df['body_tokens'] = tokenizer.texts_to_sequences(df['Body'])

讨论

使用分词器将文本转换为一系列数字是神经网络可以使用文本的经典方法之一。在上一章中,我们基于每个字符转换了文本。基于字符的模型将单个字符作为输入(无需分词器)。权衡是训练模型需要多长时间:因为你在强迫模型学习如何标记和词干,你需要更多的训练数据和更多的时间。

按单词处理文本的缺点之一是文本中可以出现的不同单词的数量没有实际上限,尤其是在我们必须处理错别字和错误的情况下。在这个秘籍中,我们只关注按计数出现在前 50,000 个中的单词,这是解决这个问题的一种方法。

6.4 建立问答模型

问题

你如何计算问题的嵌入?

解决方案

训练模型以预测 Stack Exchange 数据集中的问题和答案是否匹配。

每当我们构建模型时,我们应该问的第一个问题是:“我们的目标是什么?” 也就是说,模型将尝试分类什么?

理想情况下,我们会有一个“与这个问题类似的问题”的列表,我们可以用它来训练我们的模型。不幸的是,获取这样的数据集会非常昂贵!相反,我们将依赖一个替代目标:让我们看看我们是否可以训练我们的模型,给定一个问题,区分匹配的答案和随机问题的答案。这将迫使模型学习对标题和正文的良好表示。

我们通过定义输入来开始我们的模型。在这种情况下,我们有两个输入,标题(问题)和正文(答案):

title = layers.Input(shape=(None,), dtype='int32', name='title')
body = layers.Input(shape=(None,), dtype='int32', name='body')

两者都有不同的长度,所以我们必须填充它们。每个字段的数据将是一个整数列表,标题或正文中的每个单词对应一个整数。

现在我们要定义一组共享的层,两个输入都将通过这些层。我们首先要为输入构建一个嵌入,然后屏蔽掉无效值,并将所有单词的值加在一起:

embedding = layers.Embedding(
        mask_zero=True,
        input_dim=vocab_size,
        output_dim=embedding_size
    )

mask = layers.Masking(mask_value=0)
def _combine_sum(v):
    return K.sum(v, axis=2)

sum_layer = layers.Lambda(_combine_sum)

在这里,我们指定了 a vocab_size(我们的词汇表中有多少单词)和 an embedding_size(我们每个单词的嵌入应该有多宽;例如,GoogleNews 向量是 300 维)。

现在让我们将这些层应用于我们的单词输入:

title_sum = sum_layer(mask(embedding(title)))
body_sum = sum_layer(mask(embedding(body)))

现在我们的标题和正文有了一个向量,我们可以用余弦距离将它们相互比较,就像我们在4.2节中所做的那样。在 Keras 中,这通过dot层表示:

sim = layers.dot([title_sum, word_sum], normalize=True, axes=1)

最后,我们可以定义我们的模型。它将标题和正文输入并输出两者之间的相似性:

sim_model = models.Model(inputs=[title,body], outputs=[sim])
sim_model.compile(loss='mse', optimizer='rmsprop')

讨论

我们在这里建立的模型学习匹配问题和答案,但实际上我们给它的唯一自由是改变单词的嵌入,使得标题和正文的嵌入总和匹配。这应该让我们嵌入问题,以便相似的问题将具有相似的嵌入,因为相似的问题将具有相似的答案。

我们的训练模型使用两个参数编译,告诉 Keras 如何改进模型:

损失函数

这告诉系统给定答案有多“错误”。例如,如果我们告诉网络应该输出 1.0 title_abody_a但网络预测为 0.8,那么错误有多严重?当我们有多个输出时,这将成为一个更复杂的问题,但我们稍后会介绍。对于这个模型,我们将使用均方误差。对于前面的示例,这意味着我们将通过 (1.0–0.8) ** 2 或 0.04 惩罚模型。这种损失将通过模型传播回来,并在模型每次看到示例时改进嵌入。

优化器

有很多方法可以使用损失来改进我们的模型。这些被称为优化策略优化器。幸运的是,Keras 内置了许多可靠的优化器,所以我们不必担心这个:我们可以选择一个合适的。在这种情况下,我们使用了rmsprop优化器,它往往在各种问题上都表现得非常好。

6.5 用 Pandas 训练模型

问题

如何根据 Pandas 中包含的数据训练模型?

解决方案

构建一个利用 Pandas 的过滤器和样本功能的数据生成器。

与上一个秘籍一样,我们将训练我们的模型来区分问题标题和正确答案(正文)与另一个随机问题的答案。我们可以把它写成一个迭代我们的数据集的生成器。它将为正确的问题标题和正文输出 1,为随机标题和正文输出 0:

def data_generator(batch_size, negative_samples=1):
    questions = df[df['PostTypeId'] == 1]
    all_q_ids = list(questions.index)

    batch_x_a = []
    batch_x_b = []
    batch_y = []

    def _add(x_a, x_b, y):
        batch_x_a.append(x_a[:MAX_DOC_LEN])
        batch_x_b.append(x_b[:MAX_DOC_LEN])
        batch_y.append(y)

    while True:
        questions = questions.sample(frac=1.0)

        for i, q in questions.iterrows():
            _add(q['title_tokens'], q['body_tokens'], 1)

            negative_q = random.sample(all_q_ids, negative_samples)
            for nq_id in negative_q:
                _add(q['title_tokens'],
                     df.at[nq_id, 'body_tokens'], 0)

            if len(batch_y) >= batch_size:
                yield ({
                    'title': pad_sequences(batch_x_a, maxlen=None),
                    'body': pad_sequences(batch_x_b, maxlen=None),
                }, np.asarray(batch_y))

                batch_x_a = []
                batch_x_b = []
                batch_y = []

这里唯一的复杂之处是数据的批处理。这不是绝对必要的,但对性能非常重要。所有深度学习模型都经过优化,可以一次处理大量数据。使用的最佳批量大小取决于您正在处理的问题。使用更大的批次意味着您的模型每次更新都会看到更多数据,因此可以更准确地更新其权重,但另一方面它不能经常更新。更大的批量大小也需要更多的内存。最好从小处着手并不断将批量大小增加一倍,直到结果不再改善。

现在让我们训练模型:

sim_model.fit_generator(
    data_generator(batch_size=128),
    epochs=10,
    steps_per_epoch=1000
)

我们将训练它 10,000 步,分为 10 个 epoch,每个 1,000 步。每个步骤将处理 128 个文档,因此我们的网络最终将看到 128 万个训练示例。如果您有 GPU,您会惊讶于它的运行速度有多快!

6.6 检查相似性

问题

您想使用 Keras 通过使用另一个网络的权重来预测值。

解决方案

构建第二个模型,该模型使用与原始网络不同的输入和输出层,但共享其他一些层。

我们sim_model已经接受过培训,并且作为其中的一部分学会了如何从头衔变为title_sum,这正是我们所追求的。这样做的模型是:

embedding_model = models.Model(inputs=[title], outputs=[title_sum])

我们现在可以使用“嵌入”模型来计算数据集中每个问题的表示。让我们将其包装在一个类中以便于重用:

questions = df[df['PostTypeId'] == 1]['Title'].reset_index(drop=True)
question_tokens = pad_sequences(tokenizer.texts_to_sequences(questions))

class EmbeddingWrapper(object):
    def __init__(self, model):
        self._questions = questions
        self._idx_to_question = {i:s for (i, s) in enumerate(questions)}
        self._weights = model.predict({'title': question_tokens},
                                      verbose=1, batch_size=1024)
        self._model = model
        self._norm = np.sqrt(np.sum(self._weights * self._weights
                                    + 1e-5, axis=1))

    def nearest(self, question, n=10):
        tokens = tokenizer.texts_to_sequences([sentence])
        q_embedding = self._model.predict(np.asarray(tokens))[0]
        q_norm= np.sqrt(np.dot(q_embedding, q_embedding))
        dist = np.dot(self._weights, q_embedding) / (q_norm * self._norm)

        top_idx = np.argsort(dist)[-n:]
        return pd.DataFrame.from_records([
            {'question': self._r[i], ‘similarity': float(dist[i])}
            for i in top_idx
        ])

现在我们可以使用它了:

lookup = EmbeddingWrapper(model=sum_embedding_trained)
lookup.nearest('Python Postgres object relational model')

这会产生以下结果:

相似 问题

0.892392

使用 django 和 sqlalchemy 但后端…

0.893417

自动生成/更新表的 Python ORM ...

0.893883

SqlA 中的动态表创建和 ORM 映射...

0.896096

SQLAlchemy with count, group_by 和 order_by u…

0.897706

SQLAlchemy:使用 ORM 扫描大表?

0.902693

使用 SQLAlchemy 高效更新数据库...

0.911446

有哪些好的 Python ORM 解决方案?

0.922449

蟒蛇

0.924316

用于从 ar... 构建类的 Python 库

0.930865

python ORM允许创建表和bul…

在很短的训练时间内,我们的网络设法找出“SQL”、“查询”和“插入”都与 Postgres 相关!

你可能感兴趣的