为了账号安全,请及时绑定邮箱和手机立即绑定

如何用Qdrant数据库搭建一个基于向量的电影推荐系统?

电影推荐

介绍

随着机器学习时代的到来,电影推荐已经发展到了目前使用 transformer 和向量数据库的阶段。

从基于传统推荐系统的支持向量机开始,我们现在进入了由变压器主导的世界。在这篇文章中,我们将探讨如何将数千个视频档案高效地存储在向量数据库中,以实现最优质的推荐引擎。

在众多的向量数据库中,我们将特别关注Qdrant DB,因为它具有独特的属性——HNSW最近邻搜索算法,正如我在之前的文章中提到的。https://medium.com/@akriti.upadhyay/employing-qdrantdb-to-conduct-advanced-similarity-searches-for-image-data-daf7a1f0b8b7

咱们继续吧!

传统的推荐系统

机器学习算法,例如支持向量机(SVM)等,随着变压器被引入到人工智能领域后,已经演变为传统电影推荐系统的一部分。电影推荐系统利用机器学习算法来预测用户对电影的喜好和评分。这些系统可以分为三种主要类型:

  • 协同过滤: 通过收集许多拥有相似意见的用户偏好来预测用户的兴趣。
  • 基于内容的过滤: 根据物品属性和描述推荐内容,侧重于用户过去的偏好。
  • 混合系统: 结合协同过滤和基于内容的过滤方法,以增强效果并解决冷启动和数据稀疏性等问题。

传统推荐系统

各种机器学习技术,例如用于基于实例的学习的最近邻方法、用于协同过滤技术的矩阵分解,以及通过神经网络的深度学习,都有助于提高推荐系统的质量。这些系统会遇到冷启动问题和数据稀疏性等难题。伦理问题、可扩展性以及上下文信息的整合进一步增加了设计有效和负责任推荐系统的复杂性。

矢量数据库的入门

向量数据库在高效相似性搜索中变得非常有用。在电影推荐系统中,进行相似性搜索特别有用,目标是找到与用户已经看过并喜欢的电影相似的电影。通过将电影表示成高维空间中的向量,我们可以用距离度量(如余弦相似度或欧氏距离)来识别彼此“接近”的影片,表示它们相似。

向量数据库的工作方式

随着电影数量和用户数量的增长,数据库的规模也在扩大。向量型数据库被设计来处理大规模数据并保持高效查询性能。这种可扩展性对电影推荐系统来说非常重要,尤其是大型流媒体平台所使用的,拥有庞大电影库和用户群体的系统。

在这个上下文中,我们将使用Qdrant数据库,因为它利用了快速近似最近邻搜索,特别是采用HNSW算法和余弦相似度搜索。

了解更多关于Qdrant DB的信息,请访问官网(https://qdrant.tech/)。

如需了解更多关于HNSW算法的工作原理及余弦相似度查找的详细信息,请参阅这篇文章文章

Qdrant

推荐系统架构

在使用向量数据库期间,让我们来理解一下这里的推荐系统是如何工作的。电影的推荐是根据模型在一部电影中观察到的情绪进行的。架构可以分为两个部分:

候选人生成

候选生成是推荐系统运行中最重要的环节。面对成千上万的视频内容,第一步是根据语言筛选内容。比如,如果用户在看一部西班牙语电影,系统只会推荐其他西班牙语电影。这个筛选过程叫做启发式过滤。

生成候选

第二个步骤是将视频转换为基于字幕的文本嵌入。Hugging Face 上提供了许多可将文本信息转换为向量嵌入的模型。然而,为了获取文本信息,我们首先需要提取视频的音频内容。使用语音转文本模型(如 Whisper 或 SpeechRecognition),我们可以得到作为字幕的文本信息。

通过使用嵌入模型,我们将文本信息转换为向量表示。将这些向量存储在一个安全可靠的数据库中至关重要。这使得我们的相似性搜索变得更加简单。我们将把这些表示存储在Qdrant数据库中。

在极短的响应时间内,我们将通过Qdrant数据库的余弦相似度搜索快速找到相似视频。这一视频检索是候选生成过程中的最后一步。

重新排列

重新排序本质上是在推荐系统中根据文本信息中的情感分析来对电影进行排序。借助大型语言模型,我们可以利用获得文本信息中的意见评分。然后根据评分结果,我们对电影进行重新排序,以便更好地进行推荐。

重新排名

使用 Qdrant 的代码实现:

理解了推荐系统的架构之后,现在是时候将理论应用到代码中去。我们理解了理论,也知道如何分析电影字幕的情感,但关键在于如何将mp4格式的视频文件转换成文本表示。

为了实现这段代码,我从YouTube上下载了30部电影的预告片。我们需要安装一些将来要用到的重要软件库。

    !pip install -q torch   
    !pip install -q moviepy  
    !pip install SpeechRecognition  
    !pip install -q transformers  
    !pip install -q datasets  
    !pip install -q qdrant_client

然后我们将导入所有在代码中需要用到的包。

import os  
import moviepy.editor as mp  
import glob  
import speech_recognition as sr  
import csv  
import numpy as np  
import pandas as pd  
from qdrant_client import QdrantClient  
from qdrant_client.http import models  
from transformers import AutoModel, AutoTokenizer  
import torch

现在,我们现在新建一个文件夹,用于存放我们的音频转录。

    # 设置你的路径  
    path = "/content/my_directory"  

    # 创建文件夹  
    os.makedirs(path, exist_ok=True)

在创建目录之后,我们将通过以下代码将视频转换成文本,如下所示:

    #包含视频文件的目录  
    source_videos_file_path = r"/content/drive/MyDrive/qdrant_videos"  

    #存储音频文件的目录  
    destination_audio_files_path = r"/content/my_directory/audios"  

    #用于存储字幕的CSV文件  
    csv_file_path = r"/content/my_directory/transcripts.csv"  

    #如果目标目录不存在,则创建目录  
    os.makedirs(destination_audio_files_path, exist_ok=True)  

    #初始化识别器类(用于识别语音)  
    r = sr.Recognizer()  

    #以写入模式打开CSV文件,准备写入视频文件和字幕信息  
    with open(csv_file_path, 'w', newline='') as csvfile:  
        #创建CSV写入器  
        writer = csv.writer(csvfile)  
        #写入标题行  
        writer.writerow(["Video File", "Transcript"])  

        #逐帧处理视频  
        for video_file in glob.glob(os.path.join(source_videos_file_path, '*.mp4')):  
            #将视频文件转换为音频文件  
            video_clip = mp.VideoFileClip(video_file)  
            audio_file_path = os.path.join(destination_audio_files_path, os.path.basename(video_file).replace("'", "").replace(" ", "_") + '.wav')  
            video_clip.audio.write_audiofile(audio_file_path)  

            #将音频转换为文本  
            with sr.AudioFile(audio_file_path) as source:  
                #读取音频文件  
                audio_text = r.listen(source)  
                #将音频转换为文本  
                try:  
                    transcript = r.recognize_google(audio_text)  
                except sr.UnknownValueError:  
                    print("Google Speech Recognition 无法理解音频")  
                    transcript = "错误:无法识别音频内容"  
                except sr.RequestError as e:  
                    print("无法从Google Speech Recognition 服务请求结果;错误详情:{0}".format(e))  
                    transcript = "错误:无法从Google Speech Recognition 服务请求结果;错误详情:{0}".format(e)  

            #将字幕信息写入CSV文件  
            writer.writerow([video_file, transcript])

然后,我们会看到转写本的表格形式。

读取CSV文件'/content/my_directory/transcripts.csv'中的数据,并查看数据的前几行。

有些转录文本 ‘SpeechRecognition’ 无法理解,因此,我们将从数据框中移除该行。

data = data[~data['Transcript'].str.startswith('Error')]  
data.head()

现在,我们将创建一个使用内存数据库的_QdrantClient_实例。

    # QdrantClient 是一个用于与 Qdrant 数据库交互的客户端类,":memory:" 表示使用内存中的数据库实例。
    client = QdrantClient(":memory:")

我们将创建一个向量嵌入集合来存储我们的向量嵌入表示,并使用余弦相似度来计算距离。

我的集合 = "text_collection"
client.重新创建集合(
    集合名称=我的集合,
    向量配置=models.向量参数(大小=768, 距离=models.余弦距离)
)

我们将使用一个预训练的模型来帮助我们从数据集中提取嵌入层的内容。我们将通过transformers库中的GPT-2模型来实现这一目标。

    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    tokenizer = AutoTokenizer.from_pretrained('gpt2')
    model = AutoModel.from_pretrained('gpt2')#.to(device) # 启用此选项以使用GPU

我们需要找到电影名字并创建一个新的列,这样我们就能知道这些嵌入对应的是哪部电影。

    def 提取电影名称(file_path):  
        file_name = file_path.split("/")[-1]  # 获取文件路径的最后一部分  
        movie_name = file_name.replace(".mp4", "").strip()  
        return movie_name  

    # 应用该函数来创建新列  
    data['Movie_Name'] = data['Video File'].apply(提取电影名称)  

    # 显示数据框  
    data[['Video File', 'Movie_Name', 'Transcript']]  # 显示视频文件、电影名称和字幕列

现在,我们将创建一个帮助函数来获取每个电影预告片文本的嵌入表示。

    def 获取嵌入向量(row):  
        tokenizer = AutoTokenizer.from_pretrained('gpt2')  
        tokenizer.add_special_tokens({'pad_token': '[PAD]'})  

        输入数据 = tokenizer(row['Transcript'], padding=True, truncation=True, max_length=128, return_tensors="pt")  

        # 禁用以下操作的梯度计算,以提高效率。  
        with torch.no_grad():  
          结果 = model(**输入数据).last_hidden_state.mean(dim=1).cpu().numpy()  

        # 返回计算出的嵌入向量。  
        return 结果

然后,我们将嵌入表示应用于数据集。这样,我们以后不再需要重新加载它们。

# 将get_embeddings函数应用于data数据集的每一行
data['embeddings'] = data.apply(get_embeddings, axis=1)  
# 将embeddings列的数组保存为vectors.npy文件
np.save("vectors", np.array(data['embeddings']))

插入‘ embeddings’列之后的数据

现在,我们将为每个电影台词创建一个包含相关信息的数据包。

# 将数据框中的"Transcript", "Movie_Name", "embeddings"列转换为字典列表
payload = data[['Transcript', 'Movie_Name', 'embeddings']].to_dict(orient="records")

我们将创建一个辅助函数来对token嵌入进行均值池化。然后,我们将遍历transcript列中的每个转录,以生成文本嵌入。

    # 设置向量嵌入的预期尺寸
    expected_vector_size = 768

    # 定义用于对token嵌入进行均值池化的函数
    def mean_pooling(model_output, attention_mask):
        # 从模型输出中提取token嵌入
        token_embeddings = model_output[0]

        # 扩展注意力掩码以匹配token嵌入的尺寸
        input_mask_expanded = (attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float())

        # 计算token嵌入的总和,并考虑到注意力掩码
        sum_embeddings = torch.sum(token_embeddings * input_mask_expanded, 1)

        # 计算注意力掩码的总和(夹住以避免除零)
        sum_mask = torch.clamp(input_mask_expanded.sum(1), min=1e-9)

        # 返回均值池化的嵌入结果
        return sum_embeddings / sum_mask

    # 初始化一个列表来存储文本嵌入
    text_embeddings = []

    # 遍历data变量中的'Transcript'列中的每个转录文本
    for transcript in data['Transcript']:
        # 对转录文本进行分词,确保填充和截断,并返回PyTorch张量格式的数据
        inputs = tokenizer(transcript, padding=True, truncation=True, max_length=128, return_tensors="pt")

        # 使用分词输入对模型进行推理
        with torch.no_grad():
            embs = model(**inputs)

        # 使用定义的函数计算均值池化的嵌入
        embedding = mean_pooling(embs, inputs["attention_mask"])

        # 将嵌入裁剪或填充到期望的大小
        embedding = embedding[:, :expected_vector_size]

        # 将得到的嵌入添加到列表
        text_embeddings.append(embedding)

我们将创建一个ID列表来使用,然后将这些ID、向量和payload一起插入到Qdrant数据库集合中,给每个转录文本分配一个明确的ID。

    ids = list(range(len(data)))  

    # 将PyTorch张量转换成浮点数列表并截取前expected_vector_size个数
    text_embeddings_list = [[float(num) for num in emb.numpy().flatten().tolist()[:expected_vector_size]] for emb in text_embeddings]  

    client.upsert(collection_name=my_collection,  
                  points=models.Batch(  
                      ids=ids,  
                      vectors=text_embeddings_list,  
                      payloads=payload  # 注意:payloads可能需要进一步定义或解释,以确保上下文中的清晰度
                      )  
                  )

通过使用情感分析模型,你可以得到一个情感分数,该分数的情感极性会在-1到1之间。-1表示负面情绪,0表示中性情绪,1表示正面情绪。

from textblob import TextBlob  

定义一个计算情感得分的函数:
def calculate_sentiment_score(text):  
    # 创建一个TextBlob对象  
    blob = TextBlob(text)  
    # 获取情感极性(-1到1,其中-1表示负面,0表示中性,1表示正面)  
    sentiment_score = blob.sentiment.polarity  
    return sentiment_score  

示例:
text_example = data['Transcript'].iloc[0]  
sentiment_score_example = calculate_sentiment_score(text_example)  
print(f"情感分数: {sentiment_score_example}")

对于这个例子,情感分数将是0.75。现在,我们将用来计算情感分数的辅助函数应用到‘data’表格。

    data['情感分数'] = data['对话记录'].apply(calculate_sentiment_score)  
    data.head()

你可以对每个电影剧本的向量嵌入取平均,再与情感得分结合起来,得出最终的意见得分。

    data['avg_embeddings'] = data['embeddings'].apply(lambda x: np.mean(x, axis=0))  # 计算平均嵌入
    data['Opinion_Score'] = 0.7 * data['avg_embeddings'] + 0.3 * data['Sentiment']  # 意见得分 = 0.7 * 平均嵌入 + 0.3 * 情感值

在这里,我给这些嵌入赋予了更高的权重,因为它们捕捉到了电影剧本之间的语义相似性。内容相似性对于评估整体意见得分更为关键。“Sentiment”列定义了电影剧本的情感色彩。我给它分配了较低的权重,因为情感不如语义内容重要。这些权重是随意设定的,就像我们在划分数据集时给训练集和测试集分配权重一样。

然后创建一个电影推荐功能,这个功能可以输入一部电影的名字,就能得到相应数量的推荐电影。

    def 获取电影推荐(电影名称):  
        # 找到对应给定电影名称的行  
        匹配行 = 数据[数据['Movie_Name'] == 电影名称]  

        if not 匹配行.empty:  
          # 将 'Opinion_Score' 列转换为 NumPy 数组  
          评分数组 = np.array(数据['Opinion_Score'].tolist())  
          # 将 'Opinion_Score' 向量插入到 Qdrant 集合里  
          评分ID = list(range(len(数据)))  
          # 将 'Opinion_Score' 数组转换为列表的列表  
          评分列表 = 评分数组.reshape(-1, 1).tolist()  

          客户端.upsert(  
              collection_name=我的集合,  
              points=models.Batch(  
                  ids=评分ID,  
                  vectors=评分列表  
                  )  
              )  
          # 基于您想要查找的相似电影的评分定义查询向量  
          查询评分 = np.array([0.8] * 768)  # 更改根据需要  

          # 执行相似搜索  
          搜索结果 = 客户端.search(  
              collection_name=我的集合,  
              query_vector=查询评分.tolist(),  
              limit=3)  

           # 从搜索结果中提取推荐电影  
          推荐电影ID = [结果.id for 结果 in 搜索结果]  
          推荐影片 = 数据.loc[数据.index.isin(推荐电影ID)]  

          # 打印推荐电影列表  
          打印("推荐电影列表:")  
          打印(推荐影片[['Movie_Name', 'Opinion_Score']])  
        else:  
          打印(f"电影 '{电影名称}' 未找到。")  

    # 示例:  
    获取电影推荐("Star Wars_ The Last Jedi Trailer (Official)")

这样一来,我们就可以用Qdrant数据库来创建一个电影推荐系统了。

推荐电影,及评分

结论部分

向量数据库(Vector数据库)有许多实际应用。在这些应用中,电影推荐系统借助余弦相似度搜索和大语言模型得到了显著提升。

用Qdrant数据库来搭建电影推荐系统既好玩又刺激,还很简单。

借助Qdrant优秀的近似最近邻搜索功能及其处理大负载的能力,你可以创建自己的数据集,并享受基于向量搜索的电影推荐系统的实验乐趣。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消