1. 背景介绍
1.1 关键词提取
关键词提取是信息检索和文本挖掘中的一项重要技术,它涉及从文本中识别和提取出最能代表文档内容的词语或短语。如下图所示,对于亚马逊上面的商品标题,在构建底层索引时,通常需要对标题做分词,提取里面核心词,用于构建倒排索引或者用于关键词匹配计算等。关键词提取技术可以通过多种方法实现,包括无监督学习和有监督学习的方法。
无监督关键词提取方法
无监督方法不依赖于预先标注的数据,而是通过算法自动发现文本中的关键词。这些方法包括:
- 基于统计特征的方法:如TF-IDF算法,通过词频(TF)和逆文档频率(IDF)来评估词的重要性。
- 基于词图模型的方法:如TextRank算法,构建词与词之间的关系图,并通过网络分析来确定关键词。
- 基于主题模型的方法:如LDA模型,通过主题分布来提取关键词。
- **基于语言模型的方法: 采用基于BERT, GPT这类大语言模型。
有监督关键词提取方法
有监督方法将关键词提取视为一个分类问题,需要预先标注的训练数据来训练模型。这些方法包括:
- 训练分类器:通过机器学习算法,如支持向量机(SVM)或随机森林,来区分关键词和非关键词。
关键词提取实现步骤
传统关键词提取的过程通常包括以下步骤:
- 文本预处理:包括去除停用词、标点符号,进行词干提取或词形还原。
- 特征提取:根据所选算法提取特征,如TF-IDF值。
- 关键词候选生成:根据特征值生成候选关键词列表。
- 评估和排序:评估每个候选词的重要性并进行排序。
- 选择关键词:选择排名最高的词作为最终的关键词。
2. 基于BERT模型的关键词提取
2.1 算法原理
BERT(Bidirectional Encoder Representations from Transformers)是由Google在这篇论文中首次提出的一种预训练深度双向Transformers模型,用于语言理解。
基于BERT模型进行关键词提取的方法主要利用了BERT模型强大的语义理解能力。BERT(Bidirectional Encoder Representations from Transformers)通过在大量文本上进行预训练,学习到了丰富的语言特征,这使得它在处理自然语言时能够考虑到整个文本的上下文信息。
使用BERT进行关键词提取的基本步骤通常包括:
- 文档表示:首先使用BERT模型获取整个文档的嵌入向量,这通常意味着要通过模型获取一个能够代表整个文档的向量表示。
- 候选词/短语生成:然后,从文档中提取候选词或短语,并使用相同的模型为这些候选词/短语生成嵌入向量。
- 相似度计算:通过计算文档向量与候选词/短语向量之间的相似度(常用的是余弦相似度),来评估候选词/短语与文档内容的匹配程度。
- 关键词提取:最后,根据计算出的相似度对候选词/短语进行排序,选择最相似的词/短语作为关键词。
BERT之所以适用于关键词提取,是因为它能够捕获到复杂的语义关系和长距离的依赖,这对于理解文档的主旨和提取关键信息至关重要。BERT的双向训练机制让它能够同时考虑词语的前后文信息,生成的嵌入向量能够很好地表示词语在特定上下文中的含义。
此外,还有一些基于BERT的关键词提取工具和框架,如KeyBERT,它是一个利用BERT嵌入来查找与文档最相似的关键词和关键短语的库。KeyBERT通过简单的余弦相似度度量来实现这一目标,并且易于使用,可以通过pip安装,只需几行代码即可实现关键词提取 。
2.1 工程实践
这类我们展示如何使用PyTorch开发一个基于Bert模型进行商品标题中关键词提取的程序。
首先,我们需要确保系统里面已经安装transformers库,这是由Hugging Face提供的一个非常流行的库,它包含了预训练的BERT模型和其他许多自然语言处理工具。
安装方法如下:
pip instal transformers torch
接着,我们使用Pytorch开发核心代码:
from transformers import BertTokenizer, BertModel import torch # 确保你的设备可以运行BERT模型 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 加载预训练的BERT模型和分词器 tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') model = BertModel.from_pretrained('bert-base-uncased') model.to(device) def extract_keywords(title, num_keywords=5): # 对标题进行分词 tokens = tokenizer.tokenize(title) # 将分词结果转换为BERT模型可以处理的格式 tokens = ['[CLS]'] + tokens + ['[SEP]'] token_ids = tokenizer.convert_tokens_to_ids(tokens) token_type_ids = [0] * len(token_ids) # 单句子输入,所有token_type_ids为0 attention_mask = [1] * len(token_ids) # 标记每个token是实际的输入 # 将数据发送到设备 token_ids = torch.tensor([token_ids]).to(device) token_type_ids = torch.tensor([token_type_ids]).to(device) attention_mask = torch.tensor([attention_mask]).to(device) # 模型预测 with torch.no_grad(): outputs = model(input_ids=token_ids, token_type_ids=token_type_ids, attention_mask=attention_mask) # 获取每个token的词向量 embeddings = outputs[0] # (batch_size, sequence_length, hidden_states) embeddings = embeddings.squeeze(0) # 移除batch_size维度 # 忽略特殊标记CLS和SEP embeddings = embeddings[1:-1] tokens = tokens[1:-1] # 基于词向量的平均值选择关键词 keyword_scores = torch.mean(embeddings, dim=1) # (sequence_length - 2, hidden_states) sorted_scores, sorted_indices = torch.sort(keyword_scores, descending=True, dim=0) # 获取最高分的词 keywords_indices = sorted_indices[:num_keywords].cpu().numpy() keywords = [tokens[index] for index in keywords_indices] return keywords # 示例标题 title = "2024 New Arrival! Women's Fashion Summer Dress" keywords = extract_keywords(title, num_keywords=5) print("Extracted Keywords:", keywords)
上面这个代码,我们直接使用预训练的Bert模型对商品标题进行关键词提取。代码相对简单,主要逻辑:
- 加载模型和分词器:从Hugging Face的模型库中加载预训练的BERT模型和分词器。
- 定义提取关键词的函数:这个函数接受一个标题和要提取的关键词数量。
- 分词和格式化:将标题分词并添加BERT所需的特殊标记([CLS]和[SEP])。
- 模型预测:将处理后的输入数据传递给BERT模型,获取每个token的词向量。
- 选择关键词:基于词向量的平均值计算每个token的重要性得分,并选择得分最高的词作为关键词。
3 改进优化V1
在上面代码中,我们使用预训练的Bert模型进行关键词提取,对于关键词提取精度要求不是特别高的场景,基本上可以满足需求,然而如果我们需要提取的语料与通用语料库差异较大时,或者我们对于提取的关键词有更高精度的业务要求时,那么我们就需要对模型进行改进优化。
这里假设我们收集了大量的特定领域商品标题数据,那么可以采用对BERT模型进行微调的方式,来进一步提升模型提取的关键词准确性。
简要而言,可以遵循如下几个步骤:
1. 数据准备
- 数据收集:确保你的商品标题数据是清洗过的,并且每个标题都标注了正确的关键词。
- 数据标注:如果没有标注,你需要手动或通过众包方式标注关键词。这可能包括商品的主要特征、品牌、型号等。
2. 数据预处理
- 分词:使用与BERT模型相匹配的分词器对标题进行分词。
- 构建输入:为BERT模型构建输入,包括input IDs、token type IDs和attention mask。
- 标签处理:将标注的关键词转换为模型可以理解的格式,例如,可以使用标签索引或one-hot编码。
3. 微调BERT模型
- 加载预训练模型:加载BERT的预训练权重。
- 添加自定义层:在BERT模型的基础上添加一个或多个自定义层,以适应关键词提取任务。
- 损失函数:定义一个损失函数,如交叉熵损失,用于训练模型。
4. 训练模型
- 设置优化器:选择一个优化器,如Adam,设置学习率和其他超参数。
- 批处理:将数据分批输入模型进行训练。
- 反向传播:在每个批次后计算损失,并通过反向传播更新模型权重。
5. 评估和调整
- 验证集:使用一部分数据作为验证集,以监控模型在训练过程中的表现。
- 超参数调整:根据验证集的表现调整学习率、批次大小等超参数。
- 早停:如果验证集上的性能不再提升,可以提前停止训练以避免过拟合。
6. 微调示例代码
套用一句IT工程师们常用的一句话: Talk is cheap. Show me the code!
以下是一个简化的代码示例,展示如何使用PyTorch和transformers库对BERT模型进行微调:
from transformers import BertTokenizer, BertForSequenceClassification, AdamW from torch.utils.data import Dataset, DataLoader class KeywordExtractionDataset(Dataset): def __init__(self, titles, keywords, tokenizer, max_len=128): self.titles = titles self.keywords = keywords self.tokenizer = tokenizer self.max_len = max_len def __len__(self): return len(self.titles) def __getitem__(self, idx): title = self.titles[idx] keyword = self.keywords[idx] inputs = self.tokenizer.encode_plus( title, None, add_special_tokens=True, max_length=self.max_len, padding='max_length', return_token_type_ids=True, truncation=True ) inputs['labels'] = 1 if keyword else 0 # Simplified example return inputs # 假设 titles 和 keywords 是已经准备好的数据列表 tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') model = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2) dataset = KeywordExtractionDataset(titles, keywords, tokenizer) dataloader = DataLoader(dataset, batch_size=16, shuffle=True) optimizer = AdamW(model.parameters(), lr=2e-5) for epoch in range(num_epochs): model.train() for batch in dataloader: inputs = {k: v.to(device) for k, v in batch.items() if k != 'labels'} inputs['labels'] = batch['labels'].to(device) outputs = model(**inputs) loss = outputs.loss loss.backward() optimizer.step() optimizer.zero_grad()
7. 模型部署
- 保存和加载模型:训练完成后,保存模型权重,以便将来使用或进一步微调。
- 集成到应用:将模型集成到实际的应用或服务中,进行关键词提取。
通过这些步骤,你可以有效地利用商品标题数据对BERT模型进行微调,以提高关键词提取的准确性和相关性。
4. 更进一步优化V2
当然,在上面步骤6中,我们使用收集到的数据对预训练的Bert模型进行微调,实际做得是SFT。在应用过程中,我们可能面临两个实际问题:
- 收集的训练数据量有限,无法收集到大量的相关领域数据; 尤其是对训练数据中关键词的标注,通常是非常耗费人力成本,无法获得大量的有标注数据;
- 训练的机器资源有限或者是有多种不同的分领域语料数据需要处理。比如需要对不同语种的商品标题数据进行关键词提取,如果是对同一个Bert模型进行统一微调,可能带来整体模型效果不佳,然而逐一进行分语种的微调,在模型训练和部署上消耗又比较大。
那么,在这种情况下,采用基于LoRA的微调方案,可能是比较好的一种选择,不仅训练数据相对可以少些,而且即使做分语言的关键词提取,整体模型参数增加有效,结合MOE等技术,还可以进一步优化公共部分模型参数。关于LoRA的相关介绍,感兴趣的朋友,可以查看笔者之前的博文《人工智能-大语言模型-微调技术-LoRA及背后原理简介》 。
LoRA(Low-Rank Adaptation)是一种微调预训练模型的技术,它通过在模型权重矩阵中引入低秩结构来进行参数更新,从而减少微调过程中的参数数量。这种方法可以有效地减少计算资源消耗和避免过拟合,同时保持模型性能。
要在PyTorch中结合LoRA进行模型微调,你可以按照以下步骤操作:
1. 定义LoRA模块
首先,你需要定义一个LoRA模块,这个模块将被插入到BERT模型的特定层中。以下是一个简单的LoRA模块实现:
import torch import torch.nn as nn class LoRALayer(nn.Module): def __init__(self, input_dim, output_dim, rank=4): super(LoRALayer, self).__init__() self.rank = rank self.A = nn.Parameter(torch.randn(output_dim, rank)) self.B = nn.Parameter(torch.randn(rank, input_dim)) def forward(self, x): return torch.matmul(self.A, torch.matmul(x, self.B).transpose(-1, -2))
2. 集成LoRA到BERT模型
接下来,你需要在BERT模型的适当位置插入LoRA模块。这通常在自注意力和前馈网络的输出部分进行:
from transformers import BertModel, BertConfig class BertModelWithLoRA(BertModel): def __init__(self, config): super().__init__(config) self.config = config self.lora_layers = nn.ModuleDict() for i, layer in enumerate(self.encoder.layer): self.lora_layers[f"layer_{i}"] = LoRALayer(config.hidden_size, config.hidden_size, rank=4) def forward(self, input_ids, attention_mask=None, token_type_ids=None, position_ids=None, head_mask=None, inputs_embeds=None, encoder_hidden_states=None, encoder_attention_mask=None): outputs = () for i, layer in enumerate(self.encoder.layer): if i in self.lora_layers: lora_output = self.lora_layers[f"layer_{i}"](layer.output) outputs = layer.output + lora_output # Residual connection else: outputs = layer.output # Continue with the rest of the BERT model return outputs
3. 微调模型
使用上述修改后的模型进行微调。你需要准备数据集、定义损失函数和优化器:
from torch.utils.data import DataLoader, Dataset class CustomDataset(Dataset): def __init__(self, data): self.data = data def __len__(self): return len(self.data) def __getitem__(self, idx): return self.data[idx] # 准备数据 dataset = CustomDataset(data) # 假设 data 是准备好的数据集 dataloader = DataLoader(dataset, batch_size=32, shuffle=True) # 初始化模型和优化器 model = BertModelWithLoRA(BertConfig.from_pretrained('bert-base-uncased')) optimizer = torch.optim.Adam(model.parameters(), lr=5e-5) # 微调 model.train() for epoch in range(num_epochs): for batch in dataloader: inputs = {k: v.to(device) for k, v in batch.items()} outputs = model(**inputs) loss = outputs.loss loss.backward() optimizer.step() optimizer.zero_grad()
4. 保存和加载模型
训练完成后,保存模型的权重,并在需要时加载:
model.save_pretrained('path_to_save_model') model = BertModelWithLoRA.from_pretrained('path_to_save_model')
通过这种方式,你可以有效地使用LoRA技术对BERT模型进行微调,减少参数数量,同时保持或提高模型的性能。这种方法特别适用于资源受限的环境或需要快速部署的场景。
当然,如果你对LoRA算法代码比较熟悉,也可以直接依赖原始的LoRA代码对上面代码进行重写:
git clone https://github.com/microsoft/LoRA.git
此外,由于LoRA算法现在已经被集成到PEFT库,可以使用Huggingface里面的PEFT库进行重写:
git clone https://github.com/huggingface/peft.git pip install peft