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

前缀树约束大语言模型解码

随着计算资源和大规模数据集的发展,大语言模型因其庞大的参数量和训练数据,在各项任务中表现极其优异,但是模型在面临代码生成、结构化数据输出等场景时会存在非法输出,这就需要对模型解码过程进行有效干预。

文章目录

    • 1.背景
    • 2.前缀树
      • 2.1前缀树的构建
      • 2.2前缀树的插入
      • 2.3前缀树的查找
    • 3.前缀树约束解码
      • 3.1约束解码逻辑
      • 3.2约束解码实现
    • 4.总结

1.背景

随着计算资源和大规模数据集的发展,大语言模型因其庞大的参数量和训练数据,在各项任务中表现极其优异,但是模型在面临代码生成、结构化数据输出等场景时会存在非法输出,这就需要对模型解码过程进行有效干预。

2.前缀树

前缀树(字典树)是一种树结构,每一个节点代表一个字符串前缀,每个节点可以连接多个节点(N叉树形式),子节点代表字符串是由当前节点和通往该节点路径的所有字符组成的。
那么前缀树的构建、插入、查找就成为了较为重要的前置基础,因此下文将会对前缀树的各项操作进行说明。

注:为了巩固练习,可以做下leetcode的这道题目:前缀树练习.

2.1前缀树的构建

对于树的初始化,首先需要给定一个根节点,前缀树的根可以用一个空的哈希表来初始化。

def __init__(self):self.root = {}

2.2前缀树的插入

对于前缀树的插入,需要从根节点开始遍历,若找到前缀树中存在对应字符,则继续在当前节点的孩子节点进行查找,直到找不到对应字符;若前缀树中未出现对应字符,则在当前节点挂载该字符,并未其创建一个空哈希表,已被挂载其孩子节点。

def insert(self, word):cur = self.rootfor w in word:if w not in cur:cur[w] = {}cur = cur[w]cur['#'] = True

2.3前缀树的查找

对于前缀树的查找,本质与插入的思想一致,从根节点开始遍历,若未找到相应字符返回False,若找到相应字符则在其孩子节点中继续查找剩余字符。

def startsWith(self, prefix):cur = self.rootfor w in prefix:if w not in cur:return Falsecur = cur[w]return True

3.前缀树约束解码

有了前缀树的基础之后,那么我们可以来学习如何用前缀树来约束LLM解码。
LLM在解码最后阶段会计算词表中所有词的logit,然后进行softmax之后选择分数最高的词作为下一个token的输出(贪心解码策略),因此我们可以拿到模型计算的logit进行修改,将输出范围外的token设置为很大的负数,再返回给模型进行解码。

3.1约束解码逻辑

①设置last_token,从这个词之后要对解码过程进行约束,并找到这个词在序列中的索引。
②判断last_token之后的词是否符合前缀树中的路径。
③若合法,则获取最后一个词下面挂载的孩子节点,对孩子节点以外的字符进行屏蔽;若不合法,则终止序列生成。
④将logit传回LLM。

import itertools
import torch
from transformers import LogitsProcessorclass TrieLogitsProcessor(LogitsProcessor):def __init__(self, trie, tokenizer, num_beams, last_token=':'):self.trie = trieself.num_beams = num_beams # 候选序列的个数self.last_token = tokenizer(last_token)['input_ids'][1:][0] # 从这个词之后,要对解码进行约束self.eos_token_id = tokenizer.eos_token_id # 结束标志def __call__(self, input_ids, scores):mask = torch.ones_like(scores, dtype=torch.bool)for i, input_id in enumerate(input_ids):sequence = input_id.tolist()# 找到last_token在序列中的索引if self.last_token in sequence:index_from_end = len(sequence) - 1 - sequence[::-1].index(self.last_token)sub_sequence = sequence[index_from_end + 1:]cur = self.trie# 判断序列是否合法for s in sub_sequence:if s in cur.children:cur = cur.children[s]else:cur = Nonebreakif cur:next_states = cur.childrenelse: next_states = {self.eos_token_id}scores[i, list(next_states)] = -1e10valid_token_ids = list(next_states)# 对不合法的token进行屏蔽mask[i, valid_token_ids] = Falsescores[i] = scores[i].masked_fill(mask[i], -float(1e12))if len(next_states) < self.num_beams:complementary_ids = list(i for i in range(self.num_beams - len(next_states)))scores[i, list(complementary_ids)] = -1e10return scores

3.2约束解码实现

最后在model.generate中可以接收一个参数logits_processor,可以将上述约束解码策略传入。

outputs = model.generate(input_ids,logits_processor=logits_processor,max_length=50,num_beams=num_beams,num_return_sequences=num_beams,output_scores=True,return_dict_in_generate=True,early_stopping=True)

4.总结

通过构建一个包含所有有效输出模式的前缀树,我们在解码阶段动态地屏蔽那些不符合预定规则的token,从而显著提升模型在垂直领域或特定任务中的输出准确性和可靠性,减少不合法输出。

http://www.xdnf.cn/news/19816.html

相关文章:

  • 05 Centos 7尝试是否有网络
  • 深入浅出 RabbitMQ-RabbitMQ消息确认机制(ACK)
  • 解锁WebRTC在数字人领域的无限潜能
  • 【音视频】火山引擎实时、低延时拥塞控制算法的优化实践
  • centos系统如何判断是是x86还是x64?
  • ansible变量+管理机密
  • AV1 HEADERS详解
  • 专为 SOC 分析师和 MSSP 设计的威胁搜寻指南
  • flink中的窗口的介绍
  • mysql5.6+分页时使用 limit+order by 会出现数据重复问题
  • Mysql杂志(七)
  • Shell脚本入门:从零到精通
  • C# 原型模式(C#中的克隆)
  • “转”若惊鸿,电磁“通”——耐达讯自动化RS485转Profinet点亮能源新章
  • 【NestJS】HTTP 接口传参的 5 种方式(含前端调用与后端接收)
  • 【卷积神经网络】卷积神经网络的三大核心优势:稀疏交互、参数共享与等变表示
  • C++之基于正倒排索引的Boost搜索引擎项目介绍
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘black’问题
  • 【提示词】...(后续单元)在Prompt 的作用
  • 【linux仓库】万物至简的设计典范:如何用‘文件’这一个概念操纵整个Linux世界?
  • 在Docker中安装MySQL时3306端口占用问题
  • 论文学习30:LViT: Language Meets Vision Transformerin Medical Image Segmentation
  • 使用云手机进行游戏搬砖划算吗?
  • 国内真实的交换机、路由器和分组情况
  • 【保姆级喂饭教程】把chrome谷歌浏览器中的插件导出为CRX安装包
  • LeetCode 925.长按键入
  • 数据结构:希尔排序 (Shell Sort)
  • 【51单片机】【protues仿真】基于51单片机呼叫系统
  • 基于Force-closure评估的抓取计算流程
  • 生成知识图谱与技能树的工具指南:PlantUML、Mermaid 和 D3.js