本章将详细介绍多模态知识库的核心模块设计,重点关注数据采集预处理、特征提取向量化等关键技术环节。我们将深入讲解每个模块的设计原理、实现方法和最佳实践。
多模态知识库的数据源主要包括:
文本数据
图像数据
音频数据
视频数据
爬虫系统
API接口采集
import requests from datetime import datetime class APIDataCollector: def __init__(self, api_key): self.api_key = api_key self.headers = {'Authorization': f'Bearer {api_key}'} def collect_text_data(self, endpoints): """文本数据采集""" results = [] for endpoint in endpoints: try: response = requests.get(endpoint, headers=self.headers) if response.status_code == 200: results.append({ 'type': 'text', 'content': response.text, 'source': endpoint, 'timestamp': datetime.now().isoformat() }) except Exception as e: print(f"采集失败 {endpoint}: {e}") return results # 使用示例 collector = APIDataCollector('your_api_key') text_data = collector.collect_text_data(['https://api.example.com/text'])
用户上传采集
import os from flask import Flask, request, jsonify from werkzeug.utils import secure_filename app = Flask(__name__) app.config['UPLOAD_FOLDER'] = '/tmp/uploads' # 支持的文件类型 ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'mp3', 'mp4'} def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/upload', methods=['POST']) def upload_file(): if 'file' not in request.files: return jsonify({'error': '没有文件上传'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'error': '没有选择文件'}), 400 if file and allowed_file(file.filename): # 安全的文件名处理 filename = secure_filename(file.filename) file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) file.save(file_path) # 根据文件类型进行分类 file_ext = filename.rsplit('.', 1)[1].lower() if file_ext in ['txt', 'pdf']: data_type = 'text' elif file_ext in ['png', 'jpg', 'jpeg']: data_type = 'image' elif file_ext in ['mp3']: data_type = 'audio' elif file_ext in ['mp4']: data_type = 'video' else: data_type = 'unknown' return jsonify({ 'success': True, 'filename': filename, 'path': file_path, 'type': data_type }) return jsonify({'error': '不支持的文件类型'}), 400 if __name__ == '__main__': # 确保上传目录存在 os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) app.run(debug=True, host='0.0.0.0', port=5000)
日志收集系统
import logging import json from datetime import datetime from logging.handlers import RotatingFileHandler class MultimodalDataLogger: def __init__(self, log_file_path): self.log_file_path = log_file_path self.setup_logger() def setup_logger(self): """设置日志记录器""" self.logger = logging.getLogger('multimodal_data') self.logger.setLevel(logging.INFO) # 文件处理器(轮转日志) file_handler = RotatingFileHandler( self.log_file_path, maxBytes=10*1024*1024, # 10MB backupCount=5 ) # 格式化器 formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) file_handler.setFormatter(formatter) self.logger.addHandler(file_handler) def log_text_event(self, user_id, content, event_type='text_input'): """记录文本事件""" log_data = { 'timestamp': datetime.now().isoformat(), 'user_id': user_id, 'event_type': event_type, 'content_type': 'text', 'content': content, 'metadata': { 'length': len(content), 'word_count': len(content.split()) } } self.logger.info(json.dumps(log_data, ensure_ascii=False)) # 使用示例 logger = MultimodalDataLogger('/tmp/multimodal_data.log') logger.log_text_event('user123', '这是一个示例文本', 'chat_message')
文本清洗
import re import unicodedata class TextCleaner: def __init__(self): pass def clean_text(self, text: str) -> str: """基本文本清洗""" if not text: return "" # 去除前后空格 text = text.strip() # 统一换行符 text = re.sub(r'\r\n', '\n', text) text = re.sub(r'\r', '\n', text) # 去除多余空白行 lines = [line.strip() for line in text.split('\n') if line.strip()] text = '\n'.join(lines) # 去除特殊字符 text = re.sub(r'[^\w\s\u4e00-\u9fff\.,;:!?()\-"\']', ' ', text) # 统一空格 text = re.sub(r'\s+', ' ', text) return text.strip() # 使用示例 cleaner = TextCleaner() dirty_text = " 这是一个测试文本!有一些 多余空格。" cleaned_text = cleaner.clean_text(dirty_text) print(f"清洗后文本: {cleaned_text}")
文本分词
import jieba class TextTokenizer: def __init__(self): pass def tokenize(self, text: str): """文本分词""" if not text: return [] return list(jieba.cut(text, cut_all=False)) # 使用示例 tokenizer = TextTokenizer() text = "多模态知识库构建是一个重要的技术领域。" tokens = tokenizer.tokenize(text) print(f"分词结果: {tokens}")
图像基础预处理
import cv2 import numpy as np class ImagePreprocessor: def __init__(self, target_size=(224, 224)): self.target_size = target_size def resize_image(self, image: np.ndarray) -> np.ndarray: """调整图像大小""" try: # 保持长宽比的resize h, w = image.shape[:2] target_w, target_h = self.target_size # 计算缩放比例 scale = min(target_w / w, target_h / h) new_w = int(w * scale) new_h = int(h * scale) # 调整大小 resized = cv2.resize(image, (new_w, new_h), interpolation=cv2.INTER_AREA) # 创建目标大小的画布 canvas = np.zeros((target_h, target_w, 3), dtype=np.uint8) # 计算居中位置 y_offset = (target_h - new_h) // 2 x_offset = (target_w - new_w) // 2 # 将调整后的图像放置在画布中心 canvas[y_offset:y_offset+new_h, x_offset:x_offset+new_w] = resized return canvas except Exception as e: print(f"调整图像大小失败: {e}") return image # 使用示例 preprocessor = ImagePreprocessor(target_size=(224, 224)) # processed_image = preprocessor.resize_image(image)
词袋模型
from sklearn.feature_extraction.text import CountVectorizer from sklearn.feature_extraction.text import TfidfVectorizer class TextFeatureExtractor: def __init__(self): self.bow_vectorizer = CountVectorizer(max_features=10000) self.tfidf_vectorizer = TfidfVectorizer(max_features=10000) def extract_bow_features(self, texts): """提取词袋特征""" return self.bow_vectorizer.fit_transform(texts) def extract_tfidf_features(self, texts): """提取TF-IDF特征""" return self.tfidf_vectorizer.fit_transform(texts) # 使用示例 extractor = TextFeatureExtractor() texts = ["这是一个文本示例", "这是另一个文本示例"] bow_features = extractor.extract_bow_features(texts) tfidf_features = extractor.extract_tfidf_features(texts) print(f"词袋特征形状: {bow_features.shape}") print(f"TF-IDF特征形状: {tfidf_features.shape}")
Word2Vec词向量
from gensim.models import Word2Vec import jieba class Word2VecExtractor: def __init__(self, vector_size=100, window=5): self.vector_size = vector_size self.window = window self.model = None def train(self, texts): """训练Word2Vec模型""" # 分词 sentences = [list(jieba.cut(text)) for text in texts] # 训练模型 self.model = Word2Vec( sentences=sentences, vector_size=self.vector_size, window=self.window, min_count=1, workers=4 ) def get_word_vector(self, word): """获取词向量""" if self.model and word in self.model.wv: return self.model.wv[word] else: return np.zeros(self.vector_size) def get_document_vector(self, text): """获取文档向量(平均词向量)""" words = list(jieba.cut(text)) vectors = [self.get_word_vector(word) for word in words] if vectors: return np.mean(vectors, axis=0) else: return np.zeros(self.vector_size) # 使用示例 w2v_extractor = Word2VecExtractor(vector_size=100) texts = ["这是一个文本示例", "这是另一个文本示例"] w2v_extractor.train(texts) doc_vector = w2v_extractor.get_document_vector("这是一个测试文档") print(f"文档向量形状: {doc_vector.shape}")
BERT特征提取
import torch from transformers import AutoTokenizer, AutoModel import numpy as np class BERTFeatureExtractor: def __init__(self, model_name='bert-base-chinese'): self.tokenizer = AutoTokenizer.from_pretrained(model_name) self.model = AutoModel.from_pretrained(model_name) self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') self.model.to(self.device) self.model.eval() def extract_features(self, text): """提取BERT特征""" # 分词 inputs = self.tokenizer(text, return_tensors='pt', truncation=True, max_length=512) inputs = {k: v.to(self.device) for k, v in inputs.items()} # 模型推理 with torch.no_grad(): outputs = self.model(**inputs) # 获取[CLS]标记的向量表示 cls_vector = outputs.last_hidden_state[:, 0, :].cpu().numpy() return cls_vector # 使用示例 bert_extractor = BERTFeatureExtractor() text = "这是一个测试文本用于BERT特征提取" features = bert_extractor.extract_features(text) print(f"BERT特征形状: {features.shape}")
SIFT特征提取
import cv2 import numpy as np class SIFTFeatureExtractor: def __init__(self, nfeatures=0, nlevels=4, contrastThreshold=0.04, edgeThreshold=10, sigma=1.6): self.sift = cv2.SIFT_create( nfeatures=nfeatures, nlevels=nlevels, contrastThreshold=contrastThreshold, edgeThreshold=edgeThreshold, sigma=sigma ) def extract_features(self, image): """提取SIFT特征""" # 转换为灰度图像 if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image # 检测关键点和描述符 keypoints, descriptors = self.sift.detectAndCompute(gray, None) return keypoints, descriptors def match_features(self, descriptors1, descriptors2): """特征匹配""" # 创建BFMatcher bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True) # 匹配特征 matches = bf.match(descriptors1, descriptors2) # 按距离排序 matches = sorted(matches, key=lambda x: x.distance) return matches # 使用示例 sift_extractor = SIFTFeatureExtractor() # keypoints, descriptors = sift_extractor.extract_features(image) # matches = sift_extractor.match_features(descriptors1, descriptors2)
ResNet特征提取
import torch import torch.nn as nn import torchvision.transforms as transforms from PIL import Image import numpy as np class ResNetFeatureExtractor: def __init__(self, model_name='resnet50'): # 加载预训练的ResNet模型 self.model = torch.hub.load('pytorch/vision', model_name, pretrained=True) # 移除最后的分类层 self.model = nn.Sequential(*list(self.model.children())[:-1]) # 设置为评估模式 self.model.eval() # 图像预处理 self.transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') self.model.to(self.device) def extract_features(self, image_path): """提取ResNet特征""" # 加载图像 image = Image.open(image_path).convert('RGB') # 预处理 input_tensor = self.transform(image).unsqueeze(0) input_tensor = input_tensor.to(self.device) # 提取特征 with torch.no_grad(): features = self.model(input_tensor) # 展平特征向量 features = features.squeeze().cpu().numpy() return features # 使用示例 resnet_extractor = ResNetFeatureExtractor() # features = resnet_extractor.extract_features('/tmp/example.jpg') # print(f"ResNet特征形状: {features.shape}")
MFCC特征提取
import librosa import numpy as np class AudioFeatureExtractor: def __init__(self, sr=22050, n_mfcc=13, n_fft=2048, hop_length=512): self.sr = sr self.n_mfcc = n_mfcc self.n_fft = n_fft self.hop_length = hop_length def extract_mfcc(self, audio_path): """提取MFCC特征""" # 加载音频文件 y, sr = librosa.load(audio_path, sr=self.sr) # 提取MFCC特征 mfcc = librosa.feature.mfcc(y=y, sr=self.sr, n_mfcc=self.n_mfcc, n_fft=self.n_fft, hop_length=self.hop_length) # 计算统计特征 mfcc_mean = np.mean(mfcc, axis=1) mfcc_std = np.std(mfcc, axis=1) mfcc_delta = librosa.feature.delta(mfcc) mfcc_delta2 = librosa.feature.delta(mfcc, order=2) # 组合特征 features = np.concatenate([mfcc_mean, mfcc_std, np.mean(mfcc_delta, axis=1), np.mean(mfcc_delta2, axis=1)]) return features # 使用示例 audio_extractor = AudioFeatureExtractor() # mfcc_features = audio_extractor.extract_mfcc('/tmp/audio.wav')
FAISS向量数据库
import faiss import numpy as np class FAISSVectorDB: def __init__(self, dimension=768, nlist=100): self.dimension = dimension self.nlist = nlist # 创建IVF索引 quantizer = faiss.IndexFlatL2(dimension) self.index = faiss.IndexIVFFlat(quantizer, dimension, nlist) self.is_trained = False # 存储向量数据 self.vectors = [] self.metadata = [] def train(self, vectors): """训练索引""" vectors = np.array(vectors, dtype=np.float32) self.index.train(vectors) self.is_trained = True def add_vectors(self, vectors, metadata=None): """添加向量到数据库""" vectors = np.array(vectors, dtype=np.float32) if not self.is_trained: self.train(vectors) # 添加向量到索引 self.index.add(vectors) # 存储元数据 if metadata: self.metadata.extend(metadata) # 存储原始向量 self.vectors.extend(vectors) def search(self, query_vector, k=5): """搜索相似向量""" query_vector = np.array([query_vector], dtype=np.float32) # 搜索 distances, indices = self.index.search(query_vector, k) return distances[0], indices[0] # 使用示例 faiss_db = FAISSVectorDB(dimension=768, nlist=100) # vectors = np.random.rand(1000, 768).astype(np.float32) # faiss_db.add_vectors(vectors) # distances, indices = faiss_db.search(np.random.rand(768), k=5)
量化压缩
import faiss import numpy as np class VectorQuantizer: def __init__(self, dimension=768, nbits=8): self.dimension = dimension self.nbits = nbits # 创建量化器 self.quantizer = faiss.IndexFlatL2(dimension) self.index = faiss.IndexIVFPQ(self.quantizer, dimension, 100, 8, nbits) # 存储原始向量 self.original_vectors = [] self.quantized_vectors = [] def quantize(self, vectors): """量化向量""" vectors = np.array(vectors, dtype=np.float32) # 训练量化器 self.index.train(vectors) # 量化向量 quantized = self.index.quantizer.reconstruct( self.index.assign(vectors)[0] ) self.original_vectors.extend(vectors) self.quantized_vectors.extend(quantized) return quantized def get_compression_ratio(self): """计算压缩率""" if not self.original_vectors: return 0 original_size = np.array(self.original_vectors).nbytes quantized_size = np.array(self.quantized_vectors).nbytes return original_size / quantized_size # 使用示例 quantizer = VectorQuantizer(dimension=768, nbits=8) # vectors = np.random.rand(1000, 768).astype(np.float32) # quantized_vectors = quantizer.quantize(vectors) # compression_ratio = quantizer.get_compression_ratio() # print(f"压缩率: {compression_ratio:.2f}")
本章详细介绍了多模态知识库的核心模块设计,重点讲解了数据采集与预处理、特征提取与向量化等关键技术环节。
数据采集策略:
数据预处理流程:
特征提取技术:
向量化存储:
数据采集阶段:
特征提取阶段:
存储优化阶段:
通过本章的学习,读者应该能够掌握多模态知识库核心模块的设计和实现,为后续的系统开发和优化打下坚实基础。