束集搜索(Beam search)

        在seq2seq任务重,传统的获取decoder输出的结果过程中,在每一个时间步上,我们只选择概率最大的那个词,作为当前时间步的输出,即在每一个时间步上我们取到的都是最大概率的词。等到解码器获取到 <EOS> 词元结束循环的时候,我们获取到的句子,不一定是最准确的,获得的这个句子可能不通顺。因为贪心的策略,局部最优并不能获取全局最优的结果。

1. 如何解决贪心带来的问题

1.1 计算所有输出的概率       

        我们可以计算全部的输出的概率乘积,选择最大的那一个作为句子的输出,但是我们输出的句子通常都比较长,而且再这样的情况下我们需要计算的数据量会非常大。

1.2 Beam search

        beam search是基于上述贪心策略和1.1中提及方法的的兼顾,我们使用 Beam width 表示每次时间步保存最大概率的个数,即例如当 Beam width = 3 的时候,当前时间步保存了三个,在下一个时间步上也是一样保存三个,即我们通过约束搜索空间的大小来实现提高算法效率

         Beam width = 1 的时候,就是贪心策略,当 Beam width = 所有候选词的时候,就是1.1中计算全部的概率

例如上图,我们的 Beam width = 1 时,我们的输出序列会是 ABB ,并不会得到最好的结果(BBB)。当 Beam width = 2 时,我们在第一个时间步上有最大两个概率 [0.6, 0.4] 故保存 [A, B],目前序列为A和B;在第二个时间步上有最大两个概率 [0.36, 0.36] 故保存 [B, B],目前序列为AB和BB;在第三个时间步上有最大两个概率 [0.324, 0.144] 故保存 [B, B],目前序列为ABB和BBB;所以当 Beam width = 2 时可以获得最好的结果。

1.3 Beam search在seq2seq中如何工作

        进一步,我们再看这个例子,输入句子起始 <s> ,输出只会是 [x, y, w, </s>] 这四个中的一个,我们取 Beam width = 3 

        第一个时间步:选择概率最大的三个词保存 [x, y, w],并把 [x, y, w] 依次作为下一个时间步输入;

        第二个时间步:由 [x, y, w] 依次作为输入分别得到九个输出,选择概率最大的三个保存 [x, y, y],并把 [x, y, y] 依次作为下一个时间步输入;

        第三个时间步:由 [x, y, y] 依次作为输入分别得到九个输出,选择概率最大的三个保存 [x, y, y],并把 [x, x, x] 依次作为下一个时间步输入;

        ...

        重复上述步骤,直到获得结束符 </s> 为当前输出序列为最大概率时候,或者是当前输出序列达到最大句子长度时结束,如果是第二种结束情况的话,输出序列为最大概率的那一个序列。

所以,输出的情况可能是两种:

        1. 输出序列没有达到最大长度的时候,搜索空间中最大概率的序列是以结束符 </s> 结尾的,并将这个序列作为输出序列;

        2. 输出序列达到最大长度的时候,在搜索空间中选择最大概率的序列,并将这个序列加上结束符 </s> 作为输出序列;

### 集束搜索 (Beam Search) 算法实现 集束搜索是一种用于优化解码过程的算法,在自然语言处理NLP)、机器翻译等领域广泛应用。它通过维护一组可能的最佳路径来扩展贪婪搜索的思想,从而提高生成序列的质量。 以下是基于 Python 的简单集束搜索代码示例: ```python import numpy as np def beam_search(scores_fn, start_token, end_token, max_len=10, beam_width=3): """ 使用集束搜索生成序列 参数: scores_fn: 计算给定前缀后的下一个 token 得分函数 输入为当前序列,返回为形状为(vocab_size,)的得分数组 start_token: 起始标记 end_token: 结束标记 max_len: 序列最大长度 beam_width: 束宽 返回: best_sequence: 最优序列及其对应的分数 """ beams = [(start_token, 0)] # 初始束列表,包含起始标记和初始得分为零 sequences = [] # 存储完成的序列 for _ in range(max_len): new_beams = [] for seq, score in beams: if seq[-1] == end_token: # 如果遇到结束标记,则保存该序列并跳过后续计算 sequences.append((seq, score)) continue # 获取下一时刻所有可能token的得分 next_scores = scores_fn(seq) # 将每个候选加入到新束中 for i in range(len(next_scores)): candidate_seq = list(seq) + [i] candidate_score = score + next_scores[i] new_beams.append((candidate_seq, candidate_score)) # 按照得分排序,并截取前beam_width个最佳候选项 sorted_new_beams = sorted(new_beams, key=lambda x: x[1], reverse=True)[:beam_width] # 更新束列表 beams = sorted_new_beams # 添加剩余未完成的序列至sequences sequences += [(b[0], b[1]) for b in beams if b[0][-1] != end_token] # 找到最优序列 best_sequence = max(sequences, key=lambda x: x[1]) return best_sequence # 假设的一个打分函数scores_fn模拟器 vocab_size = 5 np.random.seed(42) def mock_scores_fn(sequence): """模拟一个随机打分函数""" return np.random.rand(vocab_size) best_seq, best_score = beam_search(mock_scores_fn, start_token=[0], end_token=4, max_len=8, beam_width=2) print(f"Best Sequence: {best_seq}, Score: {best_score}") ``` 上述代码定义了一个通用的 `beam_search` 函数,其中输入是一个评分函数 `scores_fn` 和其他必要的参数如起始标记、终止标记以及束宽等。此函数会逐步构建序列直到达到指定的最大长度或者找到足够的有效序列为止[^4]。 #### 关键点解释 - **束宽 (`beam_width`) 控制着每一步保留多少条最有可能的路径**,较大的束宽可以提升准确性但也增加了计算成本。 - 当某个序列到达结束标志时会被立即存储起来不再参与进一步扩展。 - 在每一时间步上都会重新评估所有的可能性并将它们按总累积概率降序排列后选取顶部若干项作为新的候选集合[^5]。 ### 注意事项 实际应用中的 `scores_fn` 可能来自复杂的神经网络模型输出层经过 softmax 处理之后的结果向量;因此需要依据具体场景调整相应部分逻辑以适配不同类型的底层架构需求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值