跳至主要內容

point2|文本如何向量化?

AI悦创原创ChatGPTChatGPT大约 20 分钟...约 6131 字

你好,我是悦创。

接下来,我将带你学习如何将我们的文本向量化。这在 NLP 领域至关重要,这理我不带你训练模型,我会带你使用已经训练好的模型,也会带你使用 OpenAI 现成的 API 来实现。

下面是一些常见下载数据、模型的网站:

1. 环境

在一开始,我们需要配置我们的环境,这个环境至关重要。

我们主要安装 Faiss 和 fasttext 以及 huggingface_hub ,这两个的安装真的有点闹心,所以我在这里有必要来一起解决一下。

1.1 fasttext 安装

Windows

1. Visual Studio 2022

Windows 安装的话,需要先安装 C++ 构建基础,这个问题直接安装 Visual Studio 2022 就可以解决,安装的时候你可以自行改变安装路径。

直接选择 C++ 的桌面开发这个选项即可解决。

2. 使用 Anaconda 进行安装

conda install -c conda-forge fasttext
conda install -c "conda-forge/label/cf201901" fasttext
conda install -c "conda-forge/label/cf202003" fasttext

1.2 Faiss 安装

需要,先安装 PyTorch:

conda install -c conda-forge faiss-gpu
conda install -c "conda-forge/label/broken" faiss-gpu
pip install faiss-cpu
or
pip install faiss-gpu
conda install -c pytorch faiss-cpu
conda install -c "pytorch/label/nightly" faiss-cpu

2. 初探文本转向量

2.1 逻辑

我们想要实现的逻辑是:使用 fasttext 实现把文本数据转换成向量存储到 Fails 向量数据库,接着获取用户输入文本,进行相似、相关文本检索返回,并从向量转换为自然语言。

版本1

2.2 最小实现

2.1 训练模型

首先,您需要训练一个 FastText 模型(如果您没有现成的模型的话)。

现成模型肯定有,后面会带你下载和使用,现在专心最小模型的实现。

code
import fasttext

# 训练 FastText 模型
model = fasttext.train_unsupervised('data.txt', model='skipgram')
model.save_model('model.bin')

其中,data.txt 是您的文本数据集。

2.2 使用 Faiss 存储文本向量

code
import numpy as np
import faiss

# 加载已经训练好的FastText模型
model = fasttext.load_model('model.bin')

# 转换文本为向量
vectors = []
for line in open('data.txt', 'r'):
    vectors.append(model.get_sentence_vector(line.strip()))

# 创建一个Faiss索引
index = faiss.IndexFlatL2(vectors[0].shape[0])
index.add(np.array(vectors))

# 保存索引
faiss.write_index(index, 'vector.index')
  • get_sentence_vector: 给定一个字符串,获得一个单独的向量表示。这个函数假设接收到的是一行文本。我们根据空白字符(空格、换行、制表符、垂直制表符)以及控制字符如回车、换页和空字符来分割单词。

2.3 检索相似文本

code
# 加载索引
index = faiss.read_index('vector.index')

def search(query_text, top_k=10):
    query_vector = model.get_sentence_vector(query_text)
    D, I = index.search(np.array([query_vector]), top_k)
    return I[0]

# 使用例子
input_text = "输入的文本"
result_ids = search(input_text)
top_k 作用

top_k 参数决定了我们想要从 FAISS 索引中检索出多少个与查询向量最相近的向量。

具体来说:

  • 当我们对 FAISS 索引进行搜索时,我们通常想要找到与给定查询向量最接近的 k 个向量。这里的 k 就是 top_k
  • top_k=10 意味着当你调用 search 函数时,如果不另外指定,它会默认返回与查询文本对应的向量在 FAISS 索引中的前 10 个最相似的向量的索引。
  • 如果你想要返回更多或更少的相似向量,你可以在调用 search 函数时设置不同的 top_k 值。

例如:

  • search(query_text) 默认会返回 10 个最相似的向量索引,因为 top_k 的默认值是 10。
  • search(query_text, top_k=5) 会返回 5 个最相似的向量索引。
  • search(query_text, top_k=20) 会返回 20 个最相似的向量索引。

总之,top_k 参数决定了你想要从索引中检索多少个最相近的向量。

2.4 向量转回文本

这部分实际上是将检索到的索引 ID 转换为原始的文本。当您建立 Faiss 索引时,您可以建立一个与之相对应的原始文本数组。这样,当您检索到文本的索引 ID 时,只需通过这个数组来获取原始文本即可。

import fasttext
import numpy as np
import faiss

# 训练FastText模型
model = fasttext.train_unsupervised('data.txt', model='skipgram')
model.save_model('model.bin')

# 加载模型
model = fasttext.load_model('model.bin')

# 转换文本为向量并建立索引
vectors = []
original_texts = [line.strip() for line in open('data.txt', 'r', encoding="utf-8")]
for line in original_texts:
    vectors.append(model.get_sentence_vector(line.strip()))

index = faiss.IndexFlatL2(vectors[0].shape[0])
index.add(np.array(vectors))
# 保存索引
faiss.write_index(index, 'vector.index')

# 定义检索函数
def search(query_text, top_k=3):
    query_vector = model.get_sentence_vector(query_text)
    _, I = index.search(np.array([query_vector]), top_k)
    return [original_texts[i] for i in I[0]]

# 测试
test_texts = [
    "我想吃水果。",
    "今日阳光明媚。",
    "深度学习是一个有趣的领域。"
]

for t in test_texts:
    results = search(t)
    print(f"对于测试文本:'{t}',最相似的文本是:'{results}'\n")













 











 












对于,计算结果,我们可以在上面代码的 24 行,把 _ 改成变量后输出,你就可以看见最短距离的在 0 号位。

上面我们实现的相似性的检索,和最小 demo 的实现。接下来,我们使用预训练模型来进行测试。

向量 to 文本

将检索结果转换回原始文本的关键步骤是在这里:

original_texts = [line.strip() for line in open('data.txt', 'r')]

这行代码从您的数据集 data.txt 中读取所有文本,并将它们存储在 original_texts 这个列表中。

当我们进行检索并获取到最相似文本的索引(即检索的结果)时:

return [original_texts[i] for i in I[0]]

我们使用上述索引(I[0])来从original_texts 中选取相应的文本。

2.5 余弦相似度

上面使用的是欧式距离,接下来我将带你试一试余弦相似度。

欧氏距离是空间各点的绝对距离,跟各个点所在的位置坐标直接相关,余弦距离整的是空间向量的夹角,体现在方向上的差异,不是位置差异。

code
import fasttext
import numpy as np
import faiss


# 1. 使用 FastText 转换文本为向量
def train_fasttext_model(data_file):
    model = fasttext.train_unsupervised(data_file, model='skipgram')
    model.save_model('model.bin')


# 2. 使用Faiss存储文本向量,并使用余弦相似度
def build_faiss_index(data_file):
    # 加载已经训练好的FastText模型
    model = fasttext.load_model('model.bin')

    # 转换文本为向量并进行 L2 范数归一化
    vectors = []
    for line in open(data_file, 'r', encoding="utf-8"):
        vector = model.get_sentence_vector(line.strip())
        vector /= np.linalg.norm(vector)  # L2范数归一化
        vectors.append(vector)

    # 创建一个Faiss的内积索引
    index = faiss.IndexFlatIP(len(vectors[0]))
    index.add(np.array(vectors))

    # 保存索引
    faiss.write_index(index, 'cosine_vector.index')


# 3. 检索相似文本
def search(query_text, top_k=3):
    model = fasttext.load_model('model.bin')
    index = faiss.read_index('cosine_vector.index')
    # 存储原本的文本列表
    original_texts = [line.strip() for line in open('data.txt', 'r', encoding="utf-8")]
    query_vector = model.get_sentence_vector(query_text)
    query_vector /= np.linalg.norm(query_vector)  # L2范数归一化
    D, I = index.search(np.array([query_vector]), top_k)
    return [original_texts[i] for i in I[0]]


# 4. 如果需要从向量转回文本,可以根据result_ids和原始data.txt进行对应即可。
if __name__ == '__main__':
    # 可以根据需要注释/取消注释以下部分
    # 如果需要训练 FastText 模型,可以调用 train_fasttext_model('data.txt')
    # train_fasttext_model('data.txt')
    # 调用 build_faiss_index('data.txt') 生成索引
    # build_faiss_index('data.txt')

    # 测试
    test_texts = [
        "我想吃水果。",
        "今日阳光明媚。",
        "深度学习是一个有趣的领域。",
        "今天阳光不错"
    ]

    for t in test_texts:
        results = search(t)
        print(f"对于测试文本:'{t}',最相似的文本是:'{results[0]}'\n")

3. 使用现成的模型

3.1 预训练模型

使用下面的代码就可以实现现成的预训练模型的下载:

import fasttext.util
fasttext.util.download_model('zh', if_exists='ignore')
ft = fasttext.load_model('cc.zh.300.bin')

下载看你具体的网速,和你是否有魔法上网。

链接下载:https://fasttext.cc/docs/en/crawl-vectors.htmlopen in new window

实现的代码:

import fasttext
import numpy as np
import faiss

# 加载预训练模型
model = fasttext.load_model('cc.zh.300.bin')

# 转换文本为向量并建立索引
vectors = []
original_texts = [line.strip() for line in open('data.txt', 'r', encoding="utf-8")]
for line in original_texts:
    vectors.append(model.get_sentence_vector(line.strip()))

index = faiss.IndexFlatL2(vectors[0].shape[0])
index.add(np.array(vectors))
faiss.write_index(index, 'cosine_vector.index')

# 定义检索函数
def search(query_text, top_k=1):
    index = faiss.read_index('cosine_vector.index')
    query_vector = model.get_sentence_vector(query_text)
    
    _, I = index.search(np.array([query_vector]), top_k)
    return [original_texts[i] for i in I[0]]

# 测试
test_texts = [
    "我想吃水果。",
    "今日阳光明媚。",
    "深度学习是一个有趣的领域。"
]

for t in test_texts:
    results = search(t)
    print(f"对于测试文本:'{t}',最相似的文本是:'{results[0]}'\n")

测试结果:

对于测试文本:'我想吃水果。',最相似的文本是:'我想吃水果 什么是向量?FastText是一个很强大的文本处理工具。'

对于测试文本:'今日阳光明媚。',最相似的文本是:'我想吃水果 什么是向量?FastText是一个很强大的文本处理工具。'

对于测试文本:'深度学习是一个有趣的领域。',最相似的文本是:'我想吃水果 什么是向量?FastText是一个很强大的文本处理工具。'

上面虽然实现了,但是实际的效果并没有那么好,为什么呢?

FastText 的预训练模型是基于单词的向量,而不是整句的向量。当我们用 get_sentence_vector 方法获取一个句子的向量时,FastText 实际上是在计算句子中所有词的向量的平均值。这种方法可能不总是能够捕捉句子的语义,尤其是在某些测试情境下。

为了提高准确性,我们可以考虑以下策略:

  1. 使用句向量模型:比如句子 BERT(Sentence-BERT)或者 Universal Sentence Encoder。这些模型专门为生成句子或段落的向量而设计。

  2. 微调预训练模型:如果您有标记的数据,可以考虑微调预训练的 FastText 模型以更好地适应您的任务。

  3. 使用 TF-IDF 加权:而不是简单地平均词向量,您可以使用 TF-IDF 对词向量进行加权平均,这样重要的词会有更大的权重。

但是,就您目前的情况来看,我怀疑可能是在标准化或内积计算上存在一些问题。您可以再次检查是否已经正确进行了向量标准化,以及是否正确使用了IndexFlatIP 索引。

此外,我们使用的数据集非常小,只有 10 个句子,因此即使在考虑余弦相似度时,所有句子之间的相似度可能都相对较高。这可能是返回不太相关结果的另一个原因。

建议使用更大的数据集进行更为准确的测试。此外,可以尝试其他的句子编码方法或使用其他预训练的句子向量模型,如 Sentence-BERT。

3.2 使用现成的句子模型

接下来,我们使用 Sentence-BERT(简称 SBERT),它是一个专门为句子和段落提供表示的 BERT 模型。为了实现这个,我们需要使用 sentence-transformers 库。

首先,需要安装此库:

pip install sentence-transformers

接下来,使用 SBERT 代替 FastText 创建句子向量,并使用余弦相似度进行检索。

from sentence_transformers import SentenceTransformer
import numpy as np
import faiss

# 加载SBERT模型
model = SentenceTransformer('distiluse-base-multilingual-cased')

# 转换文本为向量
vectors = []
original_texts = [line.strip() for line in open('data.txt', 'r')]
for line in original_texts:
    vec = model.encode(line.strip())
    # 向量标准化
    vec /= np.linalg.norm(vec)
    vectors.append(vec)

# 使用内积索引
index = faiss.IndexFlatIP(vectors[0].shape[0])
index.add(np.array(vectors))

# 定义检索函数
def search(query_text, top_k=1):
    query_vector = model.encode(query_text)
    # 向量标准化
    query_vector /= np.linalg.norm(query_vector)
    _, I = index.search(np.array([query_vector]), top_k)
    return [original_texts[i] for i in I[0]]

# 测试
test_texts = [
    "我想吃水果。",
    "今日阳光明媚。",
    "深度学习是一个有趣的领域。"
]

for t in test_texts:
    results = search(t)
    print(f"对于测试文本:'{t}',最相似的文本是:'{results[0]}'\n")

这里,我们使用了"distiluse-base-multilingual-cased"模型,它是一个多语言版本的 SBERT 模型,可以处理包括中文在内的多种语言。

Bug 疑难杂症

解决 443 问题,原因经过排查出现在 conda 的构建和版本上,需要使用如下命令进行修复:

  1. 直接启动系统默认命令行
  2. 直接使用如下命令:
conda install conda-build
conda update conda
conda update conda-build
杂物

欢迎关注我公众号:AI悦创,有更多更好玩的等你发现!

公众号:AI悦创【二维码】

AI悦创·编程一对一

AI悦创·推出辅导班啦,包括「Python 语言辅导班、C++ 辅导班、java 辅导班、算法/数据结构辅导班、少儿编程、pygame 游戏开发、Linux、Web」,全部都是一对一教学:一对一辅导 + 一对一答疑 + 布置作业 + 项目实践等。当然,还有线下线上摄影课程、Photoshop、Premiere 一对一教学、QQ、微信在线,随时响应!微信:Jiabcdefh

C++ 信息奥赛题解,长期更新!长期招收一对一中小学信息奥赛集训,莆田、厦门地区有机会线下上门,其他地区线上。微信:Jiabcdefh

方法一:QQopen in new window

方法二:微信:Jiabcdefh

上次编辑于:
贡献者: AndersonHJB
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度