预训练嵌入的使用:迁移通用语义或视觉特征
python import numpy as np
def load_glove(path): embeddings = {} with open(path, 'r', encoding='utf-8') as f: for line in f: values = line.split() word = values[0] vector = np.asarray(values[1:], dtype='float32') embeddings[word] = vector return embeddings
glove = load_glove('glove.6B.100d.txt') print(glove['engineer'][:10]) # 前10维示意
**生成句子嵌入**:将句子中所有词的向量取平均,作为整个句子的表示。虽然粗糙,但在短文本分类中效果不错。
```python
def sentence_vector(sentence, embeddings, dim=100):
words = sentence.lower().split()
vecs = [embeddings[w] for w in words if w in embeddings]
if not vecs:
return np.zeros(dim)
return np.mean(vecs, axis=0)
sent_vec = sentence_vector("deep learning is powerful", glove)
何时选用:当你需要轻量级、高速度且任务对词义消歧要求不高时(如关键词抽取、粗略主题聚类)。输入给下游模型(如SVM或简单神经网络)即可。
3.2 上下文感知嵌入:BERT 系列模型
原理:同一个词会根据上下文得到不同的向量。“苹果”在“I ate an apple”和“Apple released new iPad”中向量差异显著。这些嵌入来自Transformer架构,能捕捉更深层的句法和语义。
使用预训练BERT提取句子嵌入 我们利用Hugging Face Transformers库,几行代码搞定。
from transformers import AutoModel, AutoTokenizer
import torch
# 加载预训练模型(这里用轻量的 distilbert)
model_name = 'distilbert-base-uncased'
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
# 输入文本
text = "Transfer learning with pretrained embeddings saves a lot of time."
inputs = tokenizer(text, return_tensors='pt', truncation=True, padding=True)
# 获取最后一层隐藏状态
with torch.no_grad():
outputs = model(**inputs)
# 取 [CLS] token 的向量作为整句表示,或对所有token取平均池化
cls_embedding = outputs.last_hidden_state[:, 0, :].squeeze().numpy()
print(cls_embedding.shape) # (768,)
更优的句子嵌入专用模型:如果主要目标是得到高质量的句子向量,可直接使用 sentence-transformers 库:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2') # 轻量且高效
embeddings = model.encode(["This is a sentence.", "Another one here."])
生成的两个向量可以直接比较余弦相似度,用于语义搜索或聚类。
下游任务使用模式:
- 特征提取(冻结BERT):只将BERT作为固定的特征生成器,在后面接一个分类器(如逻辑回归)。适合数据量少、不想微调的场合。
- 微调(Fine-tuning):将BERT与任务层联合训练,更新部分甚至全部参数。需要更多数据,但常带来最佳效果。
4. 图像预训练嵌入实战
图像嵌入从卷积神经网络(CNN)或Vision Transformer中提取,这些模型在ImageNet(140万张自然图像)上训练,学会了检测边、角、纹理、物体部件等通用视觉特征。
4.1 使用预训练CNN提取特征向量
经典方案:取掉全连接分类层,将最后的池化层输出作为图像嵌入。ResNet、EfficientNet等都是极佳的提取器。
PyTorch示例(ResNet-50)
import torch
from torchvision import models, transforms
from PIL import Image
# 准备预训练模型,并设置为评估模式
model = models.resnet50(pretrained=True)
feature_extractor = torch.nn.Sequential(*list(model.children())[:-1]) # 去掉最后的fc层
feature_extractor.eval()
# 图像预处理(必须与预训练时一致)
preprocess = 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]),
])
# 加载图片并转换
img = Image.open('your_image.jpg')
img_tensor = preprocess(img).unsqueeze(0)
# 提取特征
with torch.no_grad():
features = feature_extractor(img_tensor)
features = torch.flatten(features, 1).numpy() # 形状 (1, 2048)
得到的 2048 维向量就是这个图像的高度抽象表示。你可以:
- 将所有图片转换为嵌入后,用余弦相似度进行以图搜图。
- 把这些特征作为输入,训练一个线性分类器来处理你自己的图片分类任务。
- 用t-SNE可视化嵌入,检查数据分布。
4.2 利用中间特征图进行更精细的任务
如果你要做物体检测或语义分割,单纯的全图嵌入不够,需要保留空间结构。此时通常直接截取骨架网络(Backbone)的多层输出,然后接上FPN等结构。作为初学者,先从整体嵌入开始,了解之后再深入研究。
4.3 Vision Transformer (ViT) 嵌入
类似BERT,现代ViT模型同样可以提取整图嵌入。使用 transformers 库:
from transformers import ViTImageProcessor, ViTModel
from PIL import Image
model_name = 'google/vit-base-patch16-224'
processor = ViTImageProcessor.from_pretrained(model_name)
model = ViTModel.from_pretrained(model_name)
img = Image.open('your_image.jpg')
inputs = processor(images=img, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
# 取 [CLS] token 作为图像嵌入
image_embedding = outputs.last_hidden_state[:, 0, :].numpy()
5. 核心决策:特征提取 vs. 微调
特征提取:冻结预训练权重,仅在上面添加并训练自定义分类器。
- 优点:极快,CPU友好,极小数据(每类几十张)也能用,零风险过拟合预训练知识。
- 缺点:无法自适应调整底层特征,对与预训练任务差异很大的数据可能表现不佳。
微调:解冻部分(通常是顶部几层)或整个预训练模型,与任务联合训练。
- 优点:潜力上限更高,能使预训练模型适应你的特定领域。
- 缺点:需要较多数据(至少几千张图/千条文本),训练时间长,小数据极易过拟合。
实用建议:
- 如果你的数据量 < 1000 且与预训练数据分布接近 → 特征提取。
- 数据量 > 5000,且期望高准确率 → 微调。
- 针对文本任务,BERT类模型微调一般需要几千条标注,而用 sentence-transformers 提取嵌入+分类器往往百条也能跑出不错效果。
6. 综合实例:用预训练嵌入搭建电影评论情感分类器
我们以IMDB影评数据集(二分类)为例,展示BERT微调和FastText特征提取两种方案。
6.1 使用FastText预训练词向量 + 逻辑回归
import fasttext.util
fasttext.util.download_model('en', if_exists='ignore') # 下载fasttext英文模型
ft = fasttext.load_model('cc.en.300.bin')
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_files
# 假设你的数据集已加载为 texts (list of str) 和 labels
# texts = [...], labels = [...]
def text_to_vector(sentence):
words = sentence.split()
# 对每个词取向量,并平均
word_vecs = [ft.get_word_vector(w) for w in words]
if not word_vecs:
return np.zeros(300)
return np.mean(word_vecs, axis=0)
X = np.array([text_to_vector(t) for t in texts])
X_train, X_test, y_train, y_test = train_test_split(X, labels, test_size=0.2)
clf = LogisticRegression(max_iter=200)
clf.fit(X_train, y_train)
print(f"FastText embedding + LR accuracy: {clf.score(X_test, y_test):.3f}")
6.2 微调DistilBERT
from transformers import DistilBertForSequenceClassification, Trainer, TrainingArguments
from datasets import Dataset
# 准备数据集为 transformers Dataset 格式
train_dataset = Dataset.from_dict({'text': X_train_texts, 'label': y_train})
test_dataset = Dataset.from_dict({'text': X_test_texts, 'label': y_test})
def tokenize(batch):
return tokenizer(batch['text'], padding=True, truncation=True)
tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
train_dataset = train_dataset.map(tokenize, batched=True)
test_dataset = test_dataset.map(tokenize, batched=True)
model = DistilBertForSequenceClassification.from_pretrained('distilbert-base-uncased', num_labels=2)
training_args = TrainingArguments(
output_dir='./results',
num_train_epochs=2,
per_device_train_batch_size=16,
per_device_eval_batch_size=64,
evaluation_strategy='epoch',
save_strategy='epoch',
logging_dir='./logs',
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=test_dataset,
)
trainer.train()