当前位置: 首页 > news >正文

CLIP4Clip实战:如何用预训练CLIP模型提升视频检索效果(附代码)

CLIP4Clip实战:如何用预训练CLIP模型提升视频检索效果(附代码)

最近在折腾一个视频内容管理平台,客户的需求很明确:用户上传一段描述,比如“一只橘猫在沙发上打哈欠”,系统得从海量视频库里快速、准确地找出匹配的片段。这听起来像是典型的跨模态检索问题,但真做起来,发现传统方法要么精度不够,要么计算开销大得吓人。直到我开始深入研究基于预训练大模型的方案,才找到了一个兼顾效果与效率的突破口——CLIP4Clip

你可能对OpenAI的CLIP模型不陌生,它在图文匹配上表现惊艳。但视频是一连串的帧,直接套用CLIP处理单张图片的思路行不通。CLIP4Clip的核心思想,就是巧妙地将CLIP从图像领域“迁移”到视频领域,利用其强大的视觉-语言对齐能力,同时引入对视频时序信息的建模。对于开发者而言,这意味着一套现成的、效果拔群的解决方案,无需从零开始在海量视频-文本数据上训练模型,大大降低了落地门槛。本文将带你从零开始,手把手实现一个基于CLIP4Clip的视频检索原型系统,我会分享环境搭建、数据处理、模型推理以及效果优化的完整流程,并附上可直接运行的代码片段。

1. 环境准备与依赖安装

工欲善其事,必先利其器。CLIP4Clip的实现依赖于PyTorch和一些特定的视觉、自然语言处理库。为了避免版本冲突,强烈建议使用虚拟环境。这里我选择conda来管理。

首先,创建一个新的Python环境(这里以Python 3.8为例,其他兼容版本亦可):

conda create -n clip4clip_env python=3.8 conda activate clip4clip_env

接下来安装核心的PyTorch。请根据你的CUDA版本前往PyTorch官网获取对应的安装命令。例如,对于CUDA 11.3:

pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 torchaudio==0.12.1 --extra-index-url https://download.pytorch.org/whl/cu113

然后,安装CLIP4Clip项目本身及其直接依赖。最便捷的方式是从GitHub克隆源码并安装:

git clone https://github.com/ArrowLuo/CLIP4Clip.git cd CLIP4Clip pip install -r requirements.txt

注意:原项目的requirements.txt可能未包含所有依赖。根据我的经验,你很可能还需要手动安装以下包:

pip install ftfy regex tqdm pandas pip install opencv-python-headless # 用于视频帧处理,headless版本无需GUI支持

最后,为了后续方便地加载预训练模型,需要安装OpenAI的CLIP库:

pip install git+https://github.com/openai/CLIP.git

至此,基础环境就搭建好了。你可以运行一个简单的导入测试来验证:

import torch import clip from module.clip4clip_model import CLIP4Clip print("环境检查通过!")

2. 理解CLIP4Clip的核心架构与数据流

在动手写代码之前,我们需要厘清CLIP4Clip是如何工作的。它不是一个全新的模型,而是在CLIP这个“巨人”肩膀上搭建的适配器。其处理流程可以清晰地分为三步:

  1. 特征提取:分别使用CLIP预训练好的视觉编码器(ViT)和文本编码器(Transformer)来提取视频帧特征和文本特征。
  2. 时序融合:视频由多帧组成,需要将单帧特征融合成一个全局的视频表征。CLIP4Clip探索了多种融合方式,这是其创新重点。
  3. 相似度计算:计算融合后的视频特征与文本特征之间的相似度,用于排序和检索。

其中,第二步的时序融合策略是性能的关键。原始论文提出了三种相似度计算器(Similarity Calculator),对应不同的融合复杂度:

计算器类型核心机制是否引入新参数特点与适用场景
无参数型 (MeanP)对所有帧的特征进行简单平均池化计算最快,完全复用CLIP权重,适合对速度要求极高的场景或作为基线。
序列型 (SeqTransf/LSTM)使用Transformer编码器或LSTM对帧序列进行建模能捕捉帧间时序关系,精度通常优于平均池化,但增加了计算量。
紧密型 (Tight)将文本和所有帧特征拼接,送入一个Transformer进行跨模态交互理论上能实现最细粒度的交互,计算复杂度最高,训练也更需技巧。

对于大多数初次尝试或追求部署简便性的项目,我推荐从无参数型序列型(Transformer)开始。它们提供了不错的性能提升,同时结构相对清晰。

数据流方面,我们需要准备两种输入:

  • 视频:需要解码成一系列图像帧。通常的做法是等间隔采样,例如每秒取1帧(1 fps)。对于短视频(<30秒),采样所有帧也是可行的;对于长视频,采样是关键的性能与精度的权衡点。
  • 文本:就是用户的查询语句,例如“a person is skiing down a mountain”。

模型输出的就是一个相似度分数,分数越高,表示视频内容与文本描述越匹配。

3. 数据预处理与特征提取实战

理论清晰后,我们进入实战环节。假设我们有一个视频数据集,比如MSR-VTT(一个常用的视频检索基准数据集),或者是你自己的业务视频集。数据处理管道通常包括视频解码、帧采样、图像预处理和特征缓存。

3.1 视频帧采样与预处理

首先,我们需要一个函数来从视频文件中提取帧。这里使用opencv

import cv2 import numpy as np from PIL import Image def extract_frames(video_path, max_frames=12, fps=1): """ 从视频中均匀采样帧。 Args: video_path: 视频文件路径。 max_frames: 最大采样帧数。 fps: 目标采样帧率(每秒帧数)。实际采样会受视频原fps和时长限制。 Returns: frames: 一个PIL.Image对象的列表。 """ cap = cv2.VideoCapture(video_path) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) video_fps = cap.get(cv2.CAP_PROP_FPS) # 计算采样间隔,确保不超过max_frames interval = max(int(video_fps / fps), 1) target_indices = list(range(0, total_frames, interval))[:max_frames] frames = [] frame_idx = 0 while len(frames) < len(target_indices): ret, frame = cap.read() if not ret: break if frame_idx in target_indices: # 将BGR转换为RGB,并转为PIL Image frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) pil_img = Image.fromarray(frame_rgb) frames.append(pil_img) frame_idx += 1 cap.release() # 如果视频太短,采样帧不足,用最后一帧填充(简单处理) while len(frames) < max_frames: frames.append(frames[-1] if frames else Image.new('RGB', (224, 224))) return frames

3.2 使用CLIP进行特征提取

接下来,我们需要加载CLIP模型,并对采样的帧和文本进行预处理与编码。这里我们直接使用OpenAI的CLIP库:

import torch import clip # 加载CLIP模型和预处理函数 device = "cuda" if torch.cuda.is_available() else "cpu" model_name = "ViT-B/32" # 与CLIP4Clip论文中使用的骨干网络一致 clip_model, preprocess = clip.load(model_name, device=device, jit=False) clip_model.eval() def encode_video_frames(frames): """将帧列表编码为特征向量""" # 预处理每一帧 processed_frames = torch.stack([preprocess(frame) for frame in frames]).to(device) # 使用CLIP的视觉编码器提取特征 with torch.no_grad(): frame_features = clip_model.encode_image(processed_frames) # frame_features形状: [num_frames, feature_dim] return frame_features def encode_text(query_text): """将文本查询编码为特征向量""" text_tokens = clip.tokenize([query_text]).to(device) with torch.no_grad(): text_features = clip_model.encode_text(text_tokens) # text_features形状: [1, feature_dim] return text_features

3.3 构建特征缓存系统

在实际应用中,视频库通常是固定的。为了避免每次检索都重复进行耗时的视频解码和特征提取,我们可以预先计算所有视频的特征并保存到磁盘,构建一个特征缓存库。这是一个非常实用的工程优化。

我们可以设计一个简单的缓存格式,例如使用numpy保存特征,并用一个JSON文件记录视频路径与特征文件的映射关系。

import json import os import numpy as np def build_feature_cache(video_dir, cache_dir, max_frames=12): """ 为视频目录中的所有视频构建特征缓存。 """ os.makedirs(cache_dir, exist_ok=True) mapping = {} video_extensions = ('.mp4', '.avi', '.mov', '.mkv') video_files = [f for f in os.listdir(video_dir) if f.lower().endswith(video_extensions)] for idx, vid_file in enumerate(video_files): print(f"Processing ({idx+1}/{len(video_files)}): {vid_file}") video_path = os.path.join(video_dir, vid_file) # 1. 提取帧 frames = extract_frames(video_path, max_frames=max_frames) # 2. 编码特征 frame_features = encode_video_frames(frames).cpu().numpy() # [T, D] # 3. 保存特征 feature_file = os.path.splitext(vid_file)[0] + '.npy' feature_path = os.path.join(cache_dir, feature_file) np.save(feature_path, frame_features) # 4. 记录映射 mapping[vid_file] = feature_file # 保存映射关系 with open(os.path.join(cache_dir, 'mapping.json'), 'w') as f: json.dump(mapping, f, indent=2) print("特征缓存构建完成!")

4. 实现CLIP4Clip检索与相似度计算

特征准备就绪后,我们就可以实现CLIP4Clip的核心逻辑了。我们将实现前面提到的两种相似度计算器:无参数型(Mean Pooling)和序列型(Transformer)。

4.1 无参数型(Mean Pooling)检索

这是最简单直接的方法,将视频所有帧的特征取平均,然后与文本特征计算余弦相似度。

import torch.nn.functional as F def mean_pooling_similarity(video_features, text_features): """ 无参数型相似度计算。 Args: video_features: [T, D] 或 [B, T, D], T为帧数,D为特征维度 text_features: [1, D] 或 [B, D] Returns: similarity_score: 标量或 [B] """ # 沿帧维度(维度1)取平均 pooled_video_feat = video_features.mean(dim=1) # 形状变为 [B, D] # 归一化 pooled_video_feat = F.normalize(pooled_video_feat, p=2, dim=-1) text_features = F.normalize(text_features, p=2, dim=-1) # 计算余弦相似度 similarity = (pooled_video_feat * text_features).sum(dim=-1) # [B] return similarity # 示例:检索单个查询 def retrieve_mean_pooling(query_text, feature_cache_dir, mapping_file, top_k=5): # 加载映射和所有缓存特征 with open(mapping_file, 'r') as f: mapping = json.load(f) video_ids = [] all_video_feats = [] for vid_file, feat_file in mapping.items(): feat_path = os.path.join(feature_cache_dir, feat_file) feat = torch.from_numpy(np.load(feat_path)).unsqueeze(0).to(device) # [1, T, D] all_video_feats.append(feat) video_ids.append(vid_file) all_video_feats = torch.cat(all_video_feats, dim=0) # [N, T, D], N为视频总数 text_feat = encode_text(query_text) # [1, D] # 批量计算相似度 with torch.no_grad(): # 扩展文本特征以匹配视频批量大小 text_feat_expanded = text_feat.expand(all_video_feats.size(0), -1) # [N, D] scores = mean_pooling_similarity(all_video_feats, text_feat_expanded) # [N] # 获取Top-K结果 top_scores, top_indices = torch.topk(scores, k=top_k) results = [] for score, idx in zip(top_scores.cpu().numpy(), top_indices.cpu().numpy()): results.append({'video_id': video_ids[idx], 'score': float(score)}) return results

4.2 序列型(Transformer)检索

为了捕捉帧间的时序信息,我们可以引入一个轻量级的Transformer编码器。这个编码器将帧序列作为输入,输出融合了时序信息的视频表征。

import torch.nn as nn class SequenceTransformer(nn.Module): """一个简单的Transformer编码器,用于融合帧序列特征""" def __init__(self, feature_dim=512, num_layers=2, num_heads=8, dropout=0.1): super().__init__() encoder_layer = nn.TransformerEncoderLayer( d_model=feature_dim, nhead=num_heads, dim_feedforward=feature_dim*4, dropout=dropout, activation='gelu', batch_first=True # 输入形状为 [batch, seq_len, dim] ) self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers) # 可学习的[CLS] token,用于聚合序列信息 self.cls_token = nn.Parameter(torch.randn(1, 1, feature_dim)) def forward(self, x): """ Args: x: 输入特征,形状为 [batch_size, seq_len, feature_dim] Returns: pooled_feat: 聚合后的特征,形状为 [batch_size, feature_dim] """ batch_size = x.size(0) # 为每个序列添加[CLS] token cls_tokens = self.cls_token.expand(batch_size, -1, -1) # [B, 1, D] x = torch.cat((cls_tokens, x), dim=1) # [B, seq_len+1, D] # Transformer编码 encoded = self.transformer_encoder(x) # [B, seq_len+1, D] # 取[CLS] token的输出作为整个序列的表征 pooled_feat = encoded[:, 0, :] # [B, D] return pooled_feat def sequence_transformer_similarity(video_features, text_features, seq_model): """ 序列型相似度计算。 Args: video_features: [B, T, D] text_features: [B, D] seq_model: 训练好的SequenceTransformer实例 """ # 通过序列模型融合视频特征 fused_video_feat = seq_model(video_features) # [B, D] # 归一化并计算相似度 fused_video_feat = F.normalize(fused_video_feat, p=2, dim=-1) text_features = F.normalize(text_features, p=2, dim=-1) similarity = (fused_video_feat * text_features).sum(dim=-1) return similarity

要使用序列型检索器,你需要先在一个小的视频-文本配对数据集上对这个SequenceTransformer进行微调(fine-tuning),或者直接加载论文作者提供的预训练权重。微调过程涉及构建正负样本对,并使用对比学习损失(如InfoNCE Loss)进行训练。由于篇幅限制,这里不展开训练代码,但核心步骤包括:

  1. 加载预提取的帧特征和对应的文本特征。
  2. 构建数据加载器,每个批次包含(视频特征,匹配文本特征)的正样本对,以及通过批次内其他样本构造的负样本。
  3. 定义优化器和损失函数。
  4. 循环训练,更新SequenceTransformer的参数,而CLIP的视觉和文本编码器通常保持冻结(freeze)以节省显存和防止遗忘。

5. 系统集成、评估与优化技巧

将上述模块组合起来,我们就得到了一个完整的视频检索原型系统。但要让它在实际应用中表现良好,还需要考虑评估和优化。

5.1 构建端到端检索流水线

我们可以设计一个VideoRetrievalSystem类来封装所有功能:

class VideoRetrievalSystem: def __init__(self, feature_cache_dir, mapping_file, similarity_type='mean', seq_model_path=None): self.feature_cache_dir = feature_cache_dir with open(mapping_file, 'r') as f: self.mapping = json.load(f) self.video_ids = list(self.mapping.keys()) self.similarity_type = similarity_type # 加载所有视频特征到内存(如果内存足够大) self.all_video_feats = [] for feat_file in self.mapping.values(): feat_path = os.path.join(feature_cache_dir, feat_file) feat = np.load(feat_path) self.all_video_feats.append(feat) # 转换为torch tensor并保持为列表,因为帧数可能不同 # 在实际批量计算时再做padding # 加载CLIP模型 self.device = "cuda" if torch.cuda.is_available() else "cpu" self.clip_model, self.preprocess = clip.load("ViT-B/32", device=self.device, jit=False) self.clip_model.eval() # 加载序列模型(如果选择) if similarity_type == 'seq' and seq_model_path: self.seq_model = SequenceTransformer(feature_dim=512).to(self.device) self.seq_model.load_state_dict(torch.load(seq_model_path)) self.seq_model.eval() else: self.seq_model = None def encode_query(self, query_text): """编码文本查询""" text_tokens = clip.tokenize([query_text]).to(self.device) with torch.no_grad(): text_features = self.clip_model.encode_text(text_tokens) return text_features def retrieve(self, query_text, top_k=10): """执行检索""" text_feat = self.encode_query(query_text) # [1, D] # 这里简化处理:假设所有视频特征已padding为相同长度,实际需更精细处理 # 例如,可以逐个视频计算相似度,或使用masked batch计算 all_scores = [] for vid_feat_np in self.all_video_feats: vid_feat = torch.from_numpy(vid_feat_np).unsqueeze(0).to(self.device) # [1, T, D] if self.similarity_type == 'mean': score = mean_pooling_similarity(vid_feat, text_feat) elif self.similarity_type == 'seq' and self.seq_model: # 需要将vid_feat通过seq_model with torch.no_grad(): fused_vid_feat = self.seq_model(vid_feat) fused_vid_feat = F.normalize(fused_vid_feat, p=2, dim=-1) norm_text_feat = F.normalize(text_feat, p=2, dim=-1) score = (fused_vid_feat * norm_text_feat).sum(dim=-1) else: raise ValueError(f"Unsupported similarity type: {self.similarity_type}") all_scores.append(score.item()) # 排序并返回结果 all_scores = np.array(all_scores) top_indices = np.argsort(all_scores)[-top_k:][::-1] results = [] for idx in top_indices: results.append({ 'video_id': self.video_ids[idx], 'score': float(all_scores[idx]) }) return results

5.2 性能评估指标

如何知道我们的检索系统好不好?需要使用标准的检索评估指标。最常用的是Recall@K(R@K) 和Median Rank(MedR)。

  • Recall@K:在前K个返回结果中,至少有一个是真实相关视频的概率。K通常取1, 5, 10。这个指标更关注检索的准确性
  • Median Rank:所有查询中,真实相关视频在返回结果列表中的排名的中位数。这个指标衡量了检索的整体排序质量,数值越小越好。

我们可以编写一个简单的评估函数,假设我们有一个测试集,其中每个查询文本对应一个或多个真实相关的视频ID。

def evaluate_retrieval_system(system, test_queries, ground_truth, K_list=[1, 5, 10]): """ 评估检索系统。 Args: system: VideoRetrievalSystem实例。 test_queries: 字典,{query_id: query_text}。 ground_truth: 字典,{query_id: list_of_relevant_video_ids}。 K_list: 要计算的Recall@K的K值列表。 Returns: metrics: 包含各项指标的字典。 """ all_ranks = [] recall_at_k = {k: 0 for k in K_list} for qid, query_text in test_queries.items(): relevant_vids = set(ground_truth[qid]) # 执行检索,假设返回top-100结果 results = system.retrieve(query_text, top_k=100) retrieved_vids = [res['video_id'] for res in results] # 计算当前查询的Recall@K for k in K_list: if any(vid in relevant_vids for vid in retrieved_vids[:k]): recall_at_k[k] += 1 # 计算真实相关视频的排名 ranks = [] for rel_vid in relevant_vids: try: rank = retrieved_vids.index(rel_vid) + 1 # 排名从1开始 except ValueError: rank = len(retrieved_vids) + 1 # 未检索到,视为排名在最后 ranks.append(rank) # 取最小排名(即最靠前的结果)作为该查询的排名 if ranks: all_ranks.append(min(ranks)) total_queries = len(test_queries) for k in K_list: recall_at_k[k] /= total_queries medr = np.median(all_ranks) if all_ranks else float('inf') return { 'recall_at_k': recall_at_k, 'median_rank': medr, 'total_queries': total_queries }

5.3 实战优化技巧与避坑指南

在项目落地过程中,我积累了一些优化经验和需要注意的坑:

1. 帧采样策略是双刃剑

  • 更多帧不一定更好:增加采样帧数能带来更多信息,但也会线性增加计算和存储开销,并可能引入冗余帧导致噪声。对于动作变化缓慢的视频,每秒1帧可能足够;对于快速变化的场景,可能需要更高帧率。一个折中的办法是自适应采样,例如根据视频内容复杂度或运动幅度来决定采样密度。
  • 关键帧提取:可以考虑使用视频分析技术(如镜头边界检测)提取关键帧,而非均匀采样,这能在信息量和计算量间取得更好平衡。

2. 特征归一化至关重要CLIP模型输出的特征向量在计算余弦相似度前必须进行L2归一化。这是对比学习预训练任务的内在要求,忽略这一步会导致相似度计算失效。

# 这是正确做法 video_feat_norm = F.normalize(video_feat, p=2, dim=-1) text_feat_norm = F.normalize(text_feat, p=2, dim=-1) similarity = (video_feat_norm * text_feat_norm).sum(dim=-1)

3. 批量处理以提升效率在构建特征缓存或进行大规模检索时,务必使用批量处理。对于特征提取,可以一次处理多帧;对于相似度计算,可以利用矩阵运算一次性计算一个查询与整个视频库的相似度。

# 批量编码多段文本 texts = ["query 1", "query 2", "query 3"] text_tokens = clip.tokenize(texts).to(device) with torch.no_grad(): text_features = clip_model.encode_text(text_tokens) # [3, D] # 批量计算相似度矩阵 (N个视频 vs M个查询) # video_features: [N, D] # text_features: [M, D] similarity_matrix = torch.matmul(video_features, text_features.T) # [N, M]

4. 处理长视频的策略对于超过一分钟的长视频,直接处理所有帧不现实。除了降低采样率,还可以采用分块(chunking)策略:将长视频切成多个短片段(如30秒一段),分别提取特征。检索时,可以计算查询与每个片段的相似度,然后取最高分作为整个视频的分数,或者返回最相关的片段时间戳。

5. 模型微调的数据与技巧如果你想在自己的领域数据上微调序列模型(SequenceTransformer),需要注意:

  • 数据量:至少需要数千个高质量的视频-文本对才能看到明显提升。
  • 冻结CLIP:在微调初期,通常冻结CLIP的视觉和文本编码器,只训练序列融合模块,以防止在小数据上过拟合。
  • 学习率:如论文所指,CLIP4Clip对学习率非常敏感。建议使用较小的学习率(如1e-5到1e-4),并配合学习率预热(warmup)和余弦退火(cosine annealing)策略。
  • 损失函数:使用对称交叉熵损失(Symmetric Cross Entropy Loss)或InfoNCE Loss,这是多模态对比学习的标准配置。

最后,别忘了监控你的检索结果。定期进行人工抽查,看看模型是否犯了明显的错误(例如,用“狗”的查询检索出了“猫”的视频),这能帮助你发现数据或模型中的潜在问题。视频检索是一个充满挑战但也极具价值的领域,CLIP4Clip为我们提供了一个强大的起点。结合具体的业务逻辑和数据特性进行迭代优化,你一定能构建出满足实际需求的智能视频检索系统。

http://www.jsqmd.com/news/447909/

相关文章:

  • Luckysheet+Python局域网协同办公:如何避免数据同步中的常见坑?
  • AIGC检测率从60%降到8%,我只用了这一个方法 - 我要发一区
  • 快速上手lora-scripts:LoRA训练自动化工具使用详解,省时省力
  • Kali Linux实战指南:手把手教你构建基础远程控制工具
  • 跨平台环境变量管理:cross-env与.env文件的实战指南
  • 【ros】ROS1从安装到实战:noetic环境配置与核心功能解析
  • 从QML报错到完美运行:Qt5/6跨版本发布避坑全指南(含platforms插件配置)
  • Cesium性能优化实战:用IndexDB缓存3D地图数据(附完整代码)
  • 深入解析IDENTITY_INSERT:如何正确为标识列指定显式值
  • 从USTC快电子学期末考,透视高速电路设计的核心原理与工程实践
  • 端粒与端粒酶:为什么癌细胞可以无限增殖?揭秘细胞寿命的分子机制
  • CUDA从入门到精通(三)——实战:向量加法与资源管理剖析
  • FireRedASR-AED-L升级指南:从基础使用到批量处理的完整教程
  • 电源设计必看:π型滤波电路实战指南(附计算公式与PCB布局技巧)
  • AIGlasses_for_navigation数据库课程设计案例:导航历史管理与时空数据分析
  • 基于OpenCV直方图匹配的照片马赛克合成技术
  • GLM-4-9B-Chat-1M场景创新:构建专属领域长文本分析引擎
  • TSMaster 2024.08新功能实测:多版本部署与远程控制全攻略
  • CentOS7下Python3.13.3安装全攻略:从依赖安装到环境配置一步到位
  • DeOldify图像上色效果展示:神经科学脑图AI着色标注功能区域
  • SolidWorks动画进阶:用配合关系实现变速直线运动(2023版技巧)
  • Zynq7020实战:FreeRTOS的vTaskDelay卡死?可能是你的systick被偷偷改写了
  • 避坑指南:Loki存储模块初始化失败的5个常见原因及解决方案
  • MogFace人脸检测模型-large场景应用:证件照自动裁剪,人脸居中一键搞定
  • QTabBar样式改造指南:如何让侧边标签文字像浏览器书签一样垂直阅读?
  • Qwen-Image-2512-Pixel-Art-LoRA 模型原理浅析:理解Pixel Art生成中的卷积神经网络应用
  • 春节文化教学新工具:春联生成模型结合词汇学习,让汉语课变得有趣又实用
  • nlp_structbert_sentence-similarity_chinese-large一键部署教程:基于Ubuntu20.04的快速环境搭建
  • 一张显卡也能微调大模型?ms-swift轻量训练实战指南
  • SciTech-Management-Organizing:组织-Hiring招聘-组织架构设计+团队分工+汇报线+ 替补岗+新增岗:招聘需求/人才画像管理