基于 GPT 和 Qdrant DB 向量数据库, 我构建了一个电影推荐系统

电影推荐系统自从机器学习时代开始以来就不断发展,逐步演进到当前的 transformers 和向量数据库的时代。

在本文中,我们将探讨如何在向量数据库中高效存储数千个视频文件,以构建最佳的推荐引擎。

在众多可用的向量数据库中,我们将关注 Qdrant DB,因为它具有独特的特性——HNSW ANN 搜索算法,正如我在之前的文章中讨论的那样。

传统推荐系统

随着支持向量机(SVM)等机器学习算法的发展,引入 transformers 到人工智能领域,传统电影推荐系统得以形成。电影推荐系统利用机器学习算法预测用户对电影的偏好和评分。这些系统主要分为三种类型:

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


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

向量数据库的引入

向量数据库已经成为进行高效相似性搜索的有益工具。在电影推荐系统中,使用相似性搜索特别有用,其目标是找到与用户已经观看并喜欢的电影相似的电影。

通过将电影表示为高维空间中的向量,我们可以利用距离度量(如余弦相似性或欧氏距离)来识别彼此“接近”的电影,表示它们相似。

随着电影和用户数量的增长,数据库的规模也在增大。向量数据库旨在处理大规模数据,并保持高查询性能。这种可扩展性对于电影推荐系统至关重要,特别是对于那些使用庞大的电影库和用户基础的大型流媒体平台。

在这个背景下,我们将使用 Qdrant 数据库,因为它利用快速的近似最近邻搜索,具体来说是 HNSW 算法与余弦相似性搜索。

通俗易懂讲解大模型系列

  • 做大模型也有1年多了,聊聊这段时间的感悟!

  • 用通俗易懂的方式讲解:大模型算法工程师最全面试题汇总

  • 用通俗易懂的方式讲解:我的大模型岗位面试总结:共24家,9个offer

  • 用通俗易懂的方式讲解:大模型 RAG 在 LangChain 中的应用实战

  • 用通俗易懂的方式讲解:一文讲清大模型 RAG 技术全流程

  • 用通俗易懂的方式讲解:如何提升大模型 Agent 的能力?

  • 用通俗易懂的方式讲解:ChatGPT 开放的多模态的DALL-E 3功能,好玩到停不下来!

  • 用通俗易懂的方式讲解:基于扩散模型(Diffusion),文生图 AnyText 的效果太棒了

  • 用通俗易懂的方式讲解:在 CPU 服务器上部署 ChatGLM3-6B 模型

  • 用通俗易懂的方式讲解:使用 LangChain 和大模型生成海报文案

  • 用通俗易懂的方式讲解:ChatGLM3-6B 部署指南

  • 用通俗易懂的方式讲解:使用 LangChain 封装自定义的 LLM,太棒了

  • 用通俗易懂的方式讲解:基于 Langchain 和 ChatChat 部署本地知识库问答系统

  • 用通俗易懂的方式讲解:在 Ubuntu 22 上安装 CUDA、Nvidia 显卡驱动、PyTorch等大模型基础环境

  • 用通俗易懂的方式讲解:Llama2 部署讲解及试用方式

  • 用通俗易懂的方式讲解:基于 LangChain 和 ChatGLM2 打造自有知识库问答系统

  • 用通俗易懂的方式讲解:一份保姆级的 Stable Diffusion 部署教程,开启你的炼丹之路

  • 用通俗易懂的方式讲解:对 embedding 模型进行微调,我的大模型召回效果提升了太多了

  • 用通俗易懂的方式讲解:LlamaIndex 官方发布高清大图,纵览高级 RAG技术

  • 用通俗易懂的方式讲解:使用 LlamaIndex 和 Eleasticsearch 进行大模型 RAG 检索增强生成

  • 用通俗易懂的方式讲解:基于 Langchain 框架,利用 MongoDB 矢量搜索实现大模型 RAG 高级检索方法

  • 用通俗易懂的方式讲解:使用Llama-2、PgVector和LlamaIndex,构建大模型 RAG 全流程

技术交流&资料

技术要学会分享、交流,不建议闭门造车。一个人可以走的很快、一堆人可以走的更远。

成立了大模型技术交流群,本文完整代码、相关资料、技术交流&答疑,均可加我们的交流群获取,群友已超过2000人,添加时最好的备注方式为:来源+兴趣方向,方便找到志同道合的朋友。

方式①、微信搜索公众号:机器学习社区,后台回复:加群
方式②、添加微信号:mlc2060,备注:来自CSDN + 技术交流

在这里插入图片描述

推荐系统架构

在使用向量数据库的同时,让我们了解一下这里的推荐系统是如何工作的。电影的推荐基于模型在一个电影中观察到的情感。该架构分为两个部分:

候选生成

候选生成是推荐系统功能的最重要部分。对于数十万个视频,初始步骤涉及根据口音或语言对内容进行筛选。例如,对于一部西班牙电影,它将在推荐中仅显示西班牙电影。这个筛选过程称为启发式筛选。

第二步是根据视频的转录将其转换为文本嵌入。Hugging Face上有许多模型可以将文本信息转换为向量嵌入。然而,为了获得文本信息,我们首先需要提取视频的音频格式。使用像 Whisper 或 SpeechRecognition 这样的音频转文本模型,我们可以检索文本信息作为转录。

利用嵌入模型,我们将文本信息转换为向量嵌入。将这些向量存储在一个安全可靠的数据库中至关重要。此外,向量数据库简化了我们的相似性搜索。我们将保存嵌入到 Qdrant 数据库中。

在非常短的响应时间内,我们将基于Qdrant数据库的余弦相似性搜索获取相似的视频。这检索相似视频构成了候选生成的最后一步。

重新排序

重新排序主要是在推荐系统中进行的,以根据文本信息中表达的情感来排列电影。借助大型语言模型,我们将能够获得文本信息的意见分数。根据意见分数,电影将被重新排名进行推荐。

代码实现与 Qdrant

在了解了推荐系统的架构之后,现在是时候在代码中实现理论了。我们理解了理论,知道如何分析电影转录的情感,但关键问题是如何将mp4格式的视频文件转换为文本嵌入。

对于这个代码实现,我从YouTube上提取了30个电影预告片。我们需要安装将来使用的重要库。

!pip install -q torch 
!pip install -q openai 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 os
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 could not understand audio")
                transcript = "Error: Could not understand audio"
            except sr.RequestError as e:
                print("Could not request results from Google Speech Recognition service; {0}".format(e))
                transcript = "Error: Could not request results from Google Speech Recognition service; {0}".format(e)

        # 将转录写入CSV文件
        writer.writerow([video_file, transcript])

然后,我们将在数据框格式中查看我们的转录。

data = pd.read_csv('/content/my_directory/transcripts.csv')
data.head()

有一些“SpeechRecognition”无法理解的转录,因此我们将从数据框中消除这一行。

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

现在,我们将创建一个具有内存数据库的QdrantClient实例。

client = QdrantClient(":memory:")

我们将创建一个集合,其中将存储我们的向量嵌入,使用余弦相似性搜索测量距离。

my_collection = "text_collection"
client.recreate_collection(
    collection_name=my_collection,
    vectors_config=models.VectorParams(size=768, distance=models.Distance.COSINE)
)

我们将使用一个预训练模型来帮助我们从数据集中提取嵌入层。我们将使用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 extract_movie_name(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(extract_movie_name)

# 显示数据框
data[['Video File', 'Movie_Name', 'Transcript']]

现在,我们将创建一个帮助函数,通过它我们将为每个电影预告片转录获取嵌入。

def get_embeddings(row):
    tokenizer = AutoTokenizer.from_pretrained('gpt2')
    tokenizer.add_special_tokens({'pad_token': '[PAD]'})

    inputs = tokenizer(row['Transcript'], padding=True, truncation=True, max_length=128, return_tensors="pt")

    # 对以下操作禁用梯度计算。
    with torch.no_grad():
      outputs = model(**inputs).last_hidden_state.mean(dim=1).cpu().numpy()

    # 返回计算得到的嵌入。
    return outputs

然后,我们将嵌入函数应用于我们的数据集。之后,我们将保存嵌入,以便不必再次加载它们。

data['embeddings'] = data.apply(get_embeddings, axis=1)
np.save("vectors", np.array(data['embeddings']))

现在,我们将为每个电影转录创建一个包含元数据的负载。

payload = data[['Transcript', 'Movie_Name', 'embeddings']].to_dict(orient="records")

我们将创建一个用于标记化嵌入的辅助函数。然后,我们将循环遍历“Transcript”列中的每个转录,以创建文本嵌入。

# 设置向量嵌入的预期大小
expected_vector_size = 768

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

    # 将关注掩码扩展到与标记嵌入的大小匹配
    input_mask_expanded = (attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float())

    # 计算考虑到关注掩码的标记嵌入的总和
    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)

为了在Qdrant数据库集合中为每个转录分配明确的ID,我们将创建一个ID列表,然后更新组合的ID、向量和负载。

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

# 将PyTorch张量转换为浮点数列表
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
                  )
              )

使用情感分析模型,您可以生成情感分数,其中情感极性在-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: {sentiment_score_example}")

对于此示例,生成的情感分数将为0.75。现在,我们将将计算情感分数的辅助函数应用于“data”数据框。

data['Sentiment Score'] = data['Transcript'].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']

在上述代码中,我为嵌入分配了更大的权重,因为它们捕获语义内容和电影转录之间的相似性。内在内容相似性在确定总体意见分数时更为关键。 “Sentiment”列定义了电影转录的情感语气。我为情感分数分配了较低的权重,因为情感作为因素在计算总体意见分数时不像语义内容那样关键。权重是任意的


然后创建一个电影推荐函数,其中您传递电影名称并获取所需数量的推荐电影。

def get_recommendations(movie_name):
    # 找到对应于给定电影名称的行
    query_row = data[data['Movie_Name'] == movie_name]

    if not query_row.empty:
      # 将'Opinion_Score'列转换为NumPy数组
      opinion_scores_array = np.array(data['Opinion_Score'].tolist())
      # 将'Opinion_Score'向量上插入到Qdrant集合
      opinion_scores_ids = list(range(len(data)))
      # 将'Opinion_Score'数组转换为列表的列表
      opinion_scores_list = opinion_scores_array.reshape(-1, 1).tolist()

      client.upsert(
          collection_name=my_collection,
          points=models.Batch(
              ids=opinion_scores_ids,
              vectors=opinion_scores_list
              )
          )
      # 基于要查找相似电影的意见分数定义查询向量
      query_opinion_score = np.array([0.8] * 768)  # 根据需要进行调整

      # 执行相似性搜索
      search_results = client.search(
          collection_name=my_collection,
          query_vector=query_opinion_score.tolist(),
          limit=3)

       # 从搜索结果中提取电影推荐
      recommended_movie_ids = [result.id for result in search_results]
      recommended_movies = data.loc[data.index.isin(recommended_movie_ids)]

      # 显示推荐电影
      print("Recommended Movies:")
      print(recommended_movies[['Movie_Name', 'Opinion_Score']])
    else:
      print(f"Movie '{movie_name}' not found in the dataset.")

# 示例用法:
get_recommendations("Star Wars_ The Last Jedi Trailer (Official)")

通过这样,我们能够使用Qdrant数据库创建一个电影推荐系统。

结论

矢量数据库有许多用途。在这些用例中,电影推荐系统在余弦相似性搜索和大型语言模型的帮助下取得了显著的改进。

使用 Qdrant 数据库创建电影推荐系统非常有趣、令人兴奋,也很容易。

借助 Qdrant 的最佳近似最近邻搜索和处理大型负载的能力,您可以创建自己的数据集,并尽情尝试基于矢量搜索的电影推荐系统。来自akriti.upadhyay

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】Shell 命令以及运行原理

Shell 命令以及运行原理 当用户登录 Linux 系统的时候,系统会给用户创建一个新的进程,一般叫做 bash(命令行解释器)。 Linux 严格意义上说的是一个操作系统,我们称之为 “核心( kernel )” &…

Paimon教程

教程来自尚硅谷 目录 教程来自尚硅谷1. 概述1.1 简介1.2 核心特性1.3 文件布局1.3.1 LSM Trees 2. 集成Flink2.1 安装&配置2.2 Catalog 3. 进阶使用3.1 写入性能3.1.1 并行度3.1.2 Compaction3.1.3 触发Compaction的Sorted Run数3.1.4 写入初始化3.1.5 内存 3.2 读取性能3.…

在Windows 11上安装Domino 14和Traveler 14

大家好,才是真的好。 是的,没错,在过去,很多人都喜欢将他们的Domino服务器安装在自己的pc电脑和个人操作系统上,从遥远的windows 2000、xp;windows 7以及到现在的Windows 10和11。 这也造成了在使用Domin…

【设计模式】代理模式的实现方式与使用场景

1. 概述 代理模式是一种结构型设计模式,它通过创建一个代理对象来控制对另一个对象的访问,代理对象在客户端和目标对象之间充当了中介的角色,客户端不再直接访问目标对象,而是通过代理对象间接访问目标对象。 那在中间加一层代理…

抓包工具Fidder

介绍 Fiddler是强大的抓包工具,它的原理是以web代理服务器的形式进行工作的,代理地址:127.0.0.1,默认端口号:8888。代理就是在客户端和服务器之间设置一道官咖,客户端将请求数据发送出去之后,代理服务器会…

为什么电脑降价了?

周末,非常意外地用不到3000元买到了一款2023年度发布的华为笔记本I5,16G,500G,基本是主流配置,我非常意外,看了又看,不是什么Hwawii,或者Huuawe。然后也不是二手。为什么呢?因为在ALU和FPU之外&…

一零七七、将Hexo cl Hexo g Hexo s通过systemctl命令管理

背景: 服务器需要执行hexo s来运行项目,但这个命令是基于前台的,故想直接嫁接在systemctl命令基础上来控制环境: Centos 8 前置环境就不说了,Hexo安装好,起码装完自己得先看hexo命令生效没,前置环境做好后…

项目篇:基于UDP通信模型的网络聊天室

思维导图 基于UDP通信模型的网络聊天室 消息分类及数据包结构 服务器端 #include <head.h> #define SER_PORT 8888 #define SER_IP "192.168.232.133" typedef struct mb {struct sockaddr_in cin;char name[20];struct mb *next; }*member; //群发消息 int …

【英文干货】【Word_Search】找单词游戏(第1天)

本期主题&#xff1a;Mindfulness&#xff08;意识力&#xff09; 本期单词&#xff1a; Awareness 意识 Breathing 呼吸 Calm 平静的 De-Stress 减压 Feelings 感受&#xff0c;情感 Inspection 调查 Meditation 冥想 Peace 和平 Quiet 安静的 Recollection 回忆 R…

当世界加速离你而去

当世界加速离你而去 会不会这个标题显的太悲观&#xff0c;也可能是耳机里正在放着To Be Frank的原因。 对于阳历跨年我是没有太多的感觉&#xff0c;而且跨年夜忙着约会&#xff0c;所以2023年的跨年文章今天才出来。 一年的时间一晃就过了。2022年12月9日时候彻底结束了风控…

ELK 分离式日志(1)

目录 一.ELK组件 ElasticSearch&#xff1a; Kiabana&#xff1a; Logstash&#xff1a; 可以添加的其它组件&#xff1a; ELK 的工作原理&#xff1a; 二.部署ELK 节点都设置Java环境: 每台都可以部署 Elasticsearch 软件&#xff1a; 修改elasticsearch主配置文件&…

FOR XML PATH 函数与同一分组下的字符串拼接

FOR XML PATH 简单介绍 FOR XML PATH 语句是将查询结果集以XML形式展现&#xff0c;通常情况下最常见的用法就是将多行的结果&#xff0c;拼接展示在同一行。 首先新建一张测试表并插入数据&#xff1a; CREATE TABLE #Test (Name varchar(70),Hobby varchar(70) );insert #T…

EHS管理系统为何需要物联网的加持?

EHS是Environment、Health、Safety的缩写&#xff0c;是从欧美企业引进的管理体系&#xff0c;在国外也被称为HSE。EHS是指健康、安全与环境一体化的管理。 而在国内&#xff0c;整个EHS市场一共被分成三类&#xff1b; 一类是EHS管培体系&#xff0c;由专门的EHS机构去为公司…

Watch(监视器)+(综合案例)

Watch侦听器&#xff08;监视器&#xff09; 作用&#xff1a;监视数据变化&#xff0c;执行一些业务逻辑或异步操作 语法&#xff1a; ①简单写法 → 简单类型数据&#xff0c;直接监视 ②完整写法 → 添加额外配置项 ①简单写法 <!DOCTYPE html> <html lang"…

CLion调试Nodejs源码

【环境】 macOS node-v20.11.0源码 CLion 2023.3.2 【1】下载源码 https://nodejs.org/en/download/ 【2】编译源码 解压后的目录如下 进入解压后的目录进行编译 ./configure --debug make -C out BUILDTYPEDebug -j 4需要好久… 编译成功之后在node-v20.11.0目录下会有一个…

DALL·E 3功能:开启创意无限的新纪元

在人工智能的黄金时代&#xff0c;MidTool以其DALLE 3功能引领了一个全新的创意革命。这项技术不仅仅是一个简单的图像生成工具&#xff0c;它是一种将想象力转化为视觉现实的魔法。在这篇文章中&#xff0c;我们将深入探讨MidTool的DALLE 3功能&#xff0c;并揭示它如何成为艺…

三、MySQL之创建和管理表

一、基础知识 1.1 一条数据存储的过程 存储数据是处理数据的第一步 。只有正确地把数据存储起来,我们才能进行有效的处理和分析。否则,只 能是一团乱麻,无从下手。 在 MySQL 中, 一个完整的数据存储过程总共有 4 步,分别是创建数据库、确认字段、创建数据表、插入数据。 …

EasyDarwin计划新增将各种流协议(RTSP、RTMP、HTTP、TCP、UDP)、文件转推RTMP到其他视频直播平台,支持转码H.264、文件直播推送

之前我们尝试做过EasyRTSPLive&#xff08;将RTSP流转推RTMP&#xff09;和EasyRTMPLive&#xff08;将各种RTSP/RTMP/HTTP/UDP流转推RTMP&#xff0c;这两个服务在市场上都得到了比较多的好评&#xff0c;其中&#xff1a; 1、EasyRTSPLive用的是EasyRTSPClient取流&#xff…

技术浪潮下的程序员职业困境:一对谷歌工程师夫妻的悲剧启示

目录 前言1 裁员潮下的程序员1.1 技术变革带来的裁员潮1.2 程序员职业危机&#xff1a;技能匮乏成为致命伤 2 一对谷歌工程师夫妻的悲剧2.1 事件经过2.2 心理压力和职业困境的交织 3 技术浪潮下的程序员职业适应策略3.1 持续学习与技能更新3.2 多元化技能与职业规划3.3 职业心理…

EasyExcelFactory 导入导出功能的实战使用

EasyExcelFactory 导入导出功能的实战使用分享&#xff1a; 1、jar包引入 <!-- 阿里巴巴Excel处理--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.6</version></dependen…
最新文章