预训练嵌入的使用:迁移通用语义或视觉特征

FreeGuideOnline 最新 2026-06-27

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()