大模型是如何把向量解码成文字输出的

hidden state 向量

当我们把一句话输入模型后,例如 "Hello world":

复制代码
token IDs: [15496, 995]

经过 Embedding + Transformer 层后,会得到每个 token 的中间表示,形状为:

复制代码
hidden_states: (batch_size, seq_len, hidden_dim) 比如:  (1, 2, 768)

这是 Transformer 层的输出,即每个 token 的向量表示。

hidden state → logits:映射到词表空间

🔹 使用 输出投影矩阵(通常是 embedding 的转置)

为了从 hidden state 还原出词,我们需要得到它在词表上每个 token 的"分数",这叫 logits。实现方式如下:

复制代码
logits = hidden_state @ W_out.T + b

其中:

  • W_out 是词嵌入矩阵(Embedding matrix),形状为 (vocab_size, hidden_dim)
  • @ 是矩阵乘法,hidden_state 形状是 (seq_len, hidden_dim)
  • 得到的 logits 形状是 (seq_len, vocab_size)

所以,每个位置的 hidden state 都被映射成一个 词表大小的分布。

logits → token ID:选出最可能的 token

现在每个位置我们都有了一个 logits 向量,例如:

复制代码
logits = [2.1, -0.5, 0.3, 6.9, ...]  # 长度 = vocab_size

有几种选择方式:

方法 说明
argmax(logits) 选最大值,对应 greedy decoding
softmax → sample 转成概率分布后随机采样
top-k sampling 从 top-k 个中采样,控制多样性
top-p (nucleus) 从累计概率在 p 范围内采样

例如:

python 复制代码
probs = softmax(logits)
token_id = torch.argmax(probs).item()  

token ID → token 字符串片段

token ID 其实对应的是某个词表里的编号,比如:

python 复制代码
tokenizer.convert_ids_to_tokens(50256)  # 输出: <|endoftext|>
tokenizer.convert_ids_to_tokens(15496)  # 输出: "Hello"

如果是多个 token ID,可以:

python 复制代码
tokenizer.convert_ids_to_tokens([15496, 995])  # 输出: ["Hello", " world"]

tokens → 拼接成文本(decode)

tokens 是"子词"或"子字符",例如:

复制代码
["Hel", "lo", " world", "!"]

通过 tokenizer.decode() 会自动合并它们为字符串:

python 复制代码
tokenizer.decode([15496, 995])  # 输出: "Hello world"

它会处理空格、子词连接等细节,恢复为人类可读的句子。

多轮生成:把预测作为输入继续生成

在生成任务(如 GPT)中,模型是逐 token 生成的。

流程如下:

复制代码
输入: "你好"
↓
tokenize → [token IDs]
↓
送入模型 → 得到下一个 token 的 logits
↓
选出 token ID → decode 成文字
↓
拼接到输入后,继续送入模型 → 下一轮生成
↓
...
直到生成 EOS(终止符)或达到最大长度

总结流程图:

复制代码
(1) 输入文本 → tokenizer → token IDs
(2) token IDs → Embedding → hidden_states(中间层向量)
(3) hidden_states × W.T → logits(词表得分)
(4) logits → sampling → token ID
(5) token ID → token → decode → 文本
(6) 拼接文本 → 重复生成(自回归)

示例代码

python 复制代码
"""
大语言模型解码过程详解
===========================
本示例展示了大语言模型如何将隐藏状态向量解码成文本输出
使用GPT-2模型作为演示,展示从输入文本到预测下一个token的完整流程
"""

import torch
import numpy as np
import matplotlib.pyplot as plt
from transformers import GPT2LMHeadModel, GPT2Tokenizer

# 设置随机种子,确保结果可复现
torch.manual_seed(42)


def display_token_probabilities(probabilities, tokens, top_k=5):
    """可视化展示token的概率分布(仅展示top_k个)"""
    # 获取前k个最大概率及其索引
    top_probs, top_indices = torch.topk(probabilities, top_k)
    top_probs = top_probs.detach().numpy()
    top_tokens = [tokens[idx] for idx in top_indices]

    print(f"\n前{top_k}个最可能的下一个token:")
    for token, prob in zip(top_tokens, top_probs):
        print(f"  {token:15s}: {prob:.6f} ({prob * 100:.2f}%)")

    # 可视化概率分布
    plt.figure(figsize=(10, 6))
    plt.bar(top_tokens, top_probs)
    plt.title(f"Top {top_k} The probability distribution of the next token")
    plt.ylabel("probability")
    plt.xlabel("Token")
    plt.xticks(rotation=45)
    plt.tight_layout()
    plt.show()


def main():
    print("Step 1: 加载预训练模型和分词器")
    # 从Hugging Face加载预训练的GPT-2模型和分词器
    tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
    model = GPT2LMHeadModel.from_pretrained("gpt2")
    model.eval()  # 将模型设置为评估模式

    print("\nStep 2: 准备输入文本")
    input_text = "Artificial intelligence is"
    print(f"输入文本: '{input_text}'")

    # 将输入文本转换为模型需要的格式
    inputs = tokenizer(input_text, return_tensors="pt")
    input_ids = inputs["input_ids"]
    attention_mask = inputs["attention_mask"]

    # 展示分词结果
    tokens = tokenizer.convert_ids_to_tokens(input_ids[0])
    print(f"分词结果: {tokens}")
    print(f"Token IDs: {input_ids[0].tolist()}")

    print("\nStep 3: 运行模型前向传播")
    # 使用torch.no_grad()避免计算梯度,节省内存
    with torch.no_grad():
        # output_hidden_states=True 让模型返回所有层的隐藏状态
        outputs = model(
            input_ids=input_ids,
            attention_mask=attention_mask,
            output_hidden_states=True
        )

        # 获取最后一层的隐藏状态
        # hidden_states的形状: [层数, batch_size, seq_len, hidden_dim]
        last_layer_hidden_states = outputs.hidden_states[-1]
        print(f"隐藏状态形状: {last_layer_hidden_states.shape}")

        # 获取序列中最后一个token的隐藏状态
        last_token_hidden_state = last_layer_hidden_states[0, -1, :]
        print(f"最后一个token的隐藏状态形状: {last_token_hidden_state.shape}")
        print(f"隐藏状态前5个值: {last_token_hidden_state[:5].tolist()}")

    print("\nStep 4: 手动计算logits")
    # 从模型中获取输出嵌入矩阵的权重
    lm_head_weights = model.get_output_embeddings().weight  # [vocab_size, hidden_dim]
    print(f"语言模型输出嵌入矩阵形状: {lm_head_weights.shape}")

    # 通过点积计算logits
    # logits代表每个词汇表中token的分数
    logits = torch.matmul(last_token_hidden_state, lm_head_weights.T)  # [vocab_size]
    print(f"Logits形状: {logits.shape}")
    print(f"Logits值域: [{logits.min().item():.4f}, {logits.max().item():.4f}]")

    print("\nStep 5: 应用softmax转换为概率")
    # 使用softmax将logits转换为概率分布
    probabilities = torch.softmax(logits, dim=0)
    print(f"概率总和: {probabilities.sum().item():.4f}")  # 应该接近1

    # 找出概率最高的token
    next_token_id = torch.argmax(probabilities).item()
    next_token = tokenizer.decode([next_token_id])
    print(f"预测的下一个token (ID: {next_token_id}): '{next_token}'")

    # 展示完整的句子
    complete_text = input_text + next_token
    print(f"生成的文本: '{complete_text}'")

    # 展示top-k的概率分布
    display_token_probabilities(probabilities, tokenizer.convert_ids_to_tokens(range(len(probabilities))), top_k=10)

    print("\nStep 6: 比较与模型内置解码结果")
    # 获取模型内置的logits输出
    model_outputs = model(input_ids=input_ids, attention_mask=attention_mask)
    model_logits = model_outputs.logits
    print(f"模型输出的logits形状: {model_logits.shape}")

    # 获取最后一个token位置的logits
    last_token_model_logits = model_logits[0, -1, :]

    # 验证我们手动计算的logits与模型输出的logits是否一致
    is_close = torch.allclose(logits, last_token_model_logits, rtol=1e-4)
    print(f"手动计算的logits与模型输出的logits是否一致: {is_close}")

    # 如果不一致,计算差异
    if not is_close:
        diff = torch.abs(logits - last_token_model_logits)
        print(f"最大差异: {diff.max().item():.8f}")
        print(f"平均差异: {diff.mean().item():.8f}")

    print("\nStep 7: 使用模型进行文本生成")
    # 使用模型的generate方法生成更多文本
    # 生成时传递 attention_mask 和 pad_token_id
    generated_ids = model.generate(
        input_ids,
        max_length=input_ids.shape[1] + 10,  # 生成10个额外的token
        temperature=1.0,
        do_sample=True,
        top_k=50,
        top_p=0.95,
        num_return_sequences=1,
        attention_mask=attention_mask,  # 添加 attention_mask
        pad_token_id=tokenizer.eos_token_id  # 明确设置 pad_token_id 为 eos_token_id
    )

    generated_text = tokenizer.decode(generated_ids[0], skip_special_tokens=True)
    print(f"模型生成的文本:\n'{generated_text}'")


if __name__ == "__main__":
    main()

Roberta代码案例

python 复制代码
import torch
from transformers import RobertaTokenizer, RobertaForMaskedLM
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns

# 设置中文字体显示(如果需要显示中文)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 1. 加载预训练的RoBERTa模型和tokenizer
tokenizer = RobertaTokenizer.from_pretrained('roberta-base')
model = RobertaForMaskedLM.from_pretrained('roberta-base')

# 2. 定义一个带有[MASK]标记的示例句子
text = f"The capital of France is {tokenizer.mask_token}."
print(f"原始文本: {text}")

# 3. 对文本进行编码,转换为模型的输入格式
inputs = tokenizer(text, return_tensors="pt")
print(f"\n标记化后的输入ID: {inputs['input_ids'][0].tolist()}")
print(f"对应的标记: {tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])}")

# 4. 找到[MASK]标记的位置
mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1]
if mask_token_index.numel() == 0:
    raise ValueError("没有找到[MASK]标记,请检查输入文本。")
print(f"\n[MASK]标记的位置: {mask_token_index.item()}")

# 5. 前向传播,获取预测结果(添加output_hidden_states=True)
with torch.no_grad():
    outputs = model(**inputs, output_hidden_states=True)

# 6. 获取[MASK]位置的预测分数
logits = outputs.logits
mask_token_logits = logits[0, mask_token_index, :]

# 7. 找出前5个最可能的标记
top_5_tokens = torch.topk(mask_token_logits, 5, dim=1)
top_5_token_indices = top_5_tokens.indices[0].tolist()
top_5_token_scores = top_5_tokens.values[0].tolist()

print("\n预测结果:")
for i, (index, score) in enumerate(zip(top_5_token_indices, top_5_token_scores)):
    token = tokenizer.decode([index])
    probability = torch.softmax(mask_token_logits, dim=1)[0, index].item()
    print(f"  {i + 1}. '{token}' - 分数: {score:.2f}, 概率: {probability:.4f}")

# 8. 获取向量表示 - 确保获取到hidden_states
last_hidden_states = outputs.hidden_states[-1]  # 现在这一行应该可以工作了


# 9. 可视化[MASK]位置的向量
def visualize_vector(vector, title):
    plt.figure(figsize=(10, 6))
    plt.bar(range(len(vector)), vector)
    plt.title(title)
    plt.xlabel('维度')
    plt.ylabel('激活值')
    plt.tight_layout()
    plt.show()


# 10. 可视化解码过程
def visualize_decoding_process():
    # 获取模型最终层的权重矩阵
    decoder_weights = model.lm_head.decoder.weight.detach()

    # 获取[MASK]位置的隐藏状态向量
    mask_hidden_state = last_hidden_states[0, mask_token_index].squeeze()

    # 计算点积得分
    scores = torch.matmul(mask_hidden_state, decoder_weights.t())

    # 获取前5个最高分词的索引和分数
    top_indices = torch.topk(scores, 5).indices.tolist()
    top_tokens = [tokenizer.decode([idx]) for idx in top_indices]

    # 可视化注意力/解码过程
    plt.figure(figsize=(12, 6))

    # 可视化隐藏状态向量与词表中向量的相似度
    plt.subplot(1, 2, 1)
    sns.heatmap(scores.reshape(1, -1)[:, top_indices].detach().numpy(),
                annot=True, fmt=".2f", cmap="YlGnBu",
                xticklabels=top_tokens)
    plt.title("词向量与词表的相似度分数")
    plt.xlabel("候选词")
    plt.ylabel("点积分数")

    # 可视化softmax后的概率分布
    plt.subplot(1, 2, 2)
    probabilities = torch.softmax(scores, dim=0)[top_indices].detach().numpy()
    plt.bar(top_tokens, probabilities)
    plt.title("解码后的概率分布")
    plt.xlabel("候选词")
    plt.ylabel("概率")

    plt.tight_layout()
    plt.show()


# 可视化[MASK]位置的向量
mask_vector = last_hidden_states[0, mask_token_index].squeeze().detach().numpy()
visualize_vector(mask_vector[:50], "MASK位置的词向量表示(仅显示前50维)")

# 显示解码过程
visualize_decoding_process()

# 12. 显示最终预测结果
predicted_token_id = top_5_token_indices[0]
predicted_token = tokenizer.decode([predicted_token_id])
print(f"\n最终预测结果: '{predicted_token}'")
print(f"完整句子: {text.replace(tokenizer.mask_token, predicted_token)}")

更详细的代码案例

python 复制代码
"""
大语言模型向量解码过程详解 - 使用BERT模型
=============================================
本示例展示了大语言模型如何将隐藏状态向量解码成文本输出
"""
import time
from datetime import datetime

import torch
import numpy as np
import matplotlib.pyplot as plt
from transformers import BertTokenizer, BertForMaskedLM
from typing import List, Tuple, Dict

# 设置随机种子,确保结果可复现
torch.manual_seed(42)


class DecodingVisualizer:
    """用于可视化大语言模型解码过程的工具类"""

    def __init__(self, model_name: str = "bert-base-uncased", use_cuda: bool = True):
        """初始化模型和分词器"""
        print(f"正在加载 {model_name} 模型和分词器...")
        # 添加设备自动检测
        self.device = torch.device("cuda" if torch.cuda.is_available() and use_cuda else "cpu")
        # 注意:这里需要根据use_cuda参数决定使用哪个设备,而不是只检查可用性

        # 添加低内存加载选项
        self.tokenizer = BertTokenizer.from_pretrained(model_name)
        print(f"正在加载 {model_name} (设备: {self.device})...")
        start_time = time.time()

        self.model = BertForMaskedLM.from_pretrained(
            model_name,
            low_cpu_mem_usage=True,
            torch_dtype=torch.float16 if self.device.type == "cuda" else torch.float32
        ).to(self.device)  # 确保这里没有遗漏.to(self.device)

        self.model.eval()  # 将模型设置为评估模式

        # 获取模型配置
        self.hidden_size = self.model.config.hidden_size
        self.vocab_size = self.model.config.vocab_size

        # 获取MASK token ID
        self.mask_token_id = self.tokenizer.mask_token_id
        self.mask_token = self.tokenizer.mask_token

        load_time = time.time() - start_time
        print(f"加载完成! 耗时: {load_time:.2f}s")
        print(f"模型加载完成! 隐藏层维度: {self.hidden_size}, 词表大小: {self.vocab_size}")
        print(f"MASK token: '{self.mask_token}', ID: {self.mask_token_id}")

    def prepare_masked_input(self, text: str) -> Dict[str, torch.Tensor]:
        """掩码输入准备函数"""
        # 更智能的掩码位置选择
        words = text.split()
        if not words:
            return {
                "inputs": self.tokenizer(text, return_tensors="pt").to(self.device), # 注意:这里需要将inputs张量移动到设备上
                "original_text": text,
                "masked_text": text,
                "original_word": "",
                "masked_position": 0
            }

        # 选择内容词进行掩码(避免掩码停用词)
        content_pos = []
        stopwords = {"the", "a", "an", "is", "are", "of", "to"}
        for i, word in enumerate(words):
            if word.lower() not in stopwords:
                content_pos.append(i)

        # 如果没有内容词,则选择最后一个词
        masked_pos = content_pos[-1] if content_pos else len(words) - 1

        original_word = words[masked_pos]
        words[masked_pos] = self.mask_token
        masked_text = " ".join(words)

        inputs = self.tokenizer(
            masked_text,
            return_tensors="pt",
            max_length=512,
            truncation=True,
            padding="max_length"  # 固定长度便于批处理
        ).to(self.device)

        return {
            "inputs": inputs,
            "original_text": text,
            "masked_text": masked_text,
            "original_word": original_word,
            "masked_position": masked_pos + 1  # 考虑[CLS] token
        }

    def decode_step_by_step(self, text: str) -> None:
        """
        详细展示BERT模型解码过程的每个步骤

        参数:
            text: 要处理的输入文本
            verbose: 是否打印详细过程

        返回:
            包含完整解码信息的字典:
            {
                "input_text": str,
                "masked_text": str,
                "hidden_state": torch.Tensor,
                "logits": torch.Tensor,
                "predictions": List[Tuple[str, float]],
                "top_k_predictions": List[Tuple[str, float]]
            }
        """
        print("\n" + "="*60)
        print("BERT模型解码过程演示")
        print("="*60)

        # 准备带掩码的输入
        masked_data = self.prepare_masked_input(text)
        inputs = masked_data["inputs"]
        original_text = masked_data["original_text"]
        masked_text = masked_data["masked_text"]
        original_word = masked_data["original_word"]

        print(f"原始文本: '{original_text}'")
        print(f"掩码文本: '{masked_text}'")
        print(f"被掩码的词: '{original_word}'")

        # 分词结果
        input_ids = inputs["input_ids"]
        token_type_ids = inputs["token_type_ids"]
        attention_mask = inputs["attention_mask"]

        tokens = self.tokenizer.convert_ids_to_tokens(input_ids[0])
        print(f"\n分词结果: {tokens}")
        print(f"Token IDs: {input_ids[0].tolist()}")

        # 查找[MASK]的位置
        mask_positions = [i for i, id in enumerate(input_ids[0]) if id == self.mask_token_id]
        if mask_positions:
            mask_position = mask_positions[0]
            print(f"[MASK]的位置: {mask_position}, Token: '{tokens[mask_position]}'")
        else:
            print("未找到[MASK]标记,使用最后一个token作为示例")
            mask_position = len(tokens) - 2  # 避免[SEP]标记

        # Step 1: 运行模型前向传播
        print("\n【Step 1: 运行模型前向传播】")
        with torch.no_grad():
            outputs = self.model(
                input_ids=input_ids,
                token_type_ids=token_type_ids,
                attention_mask=attention_mask,
                output_hidden_states=True
            )

            # 获取最后一层的隐藏状态
            last_hidden_states = outputs.hidden_states[-1]
            print(f"隐藏状态形状: {last_hidden_states.shape}")

            # 获取[MASK]位置的隐藏状态
            mask_hidden_state = last_hidden_states[0, mask_position, :]
            print(f"[MASK]位置的隐藏状态形状: {mask_hidden_state.shape}")
            print(f"隐藏状态前5个值: {mask_hidden_state[:5].tolist()}")

        # Step 2优化: 添加详细解释和性能优化
        print("\n【Step 2: 解码向量生成logits(解码过程的核心)】")
        print("解码过程实质上是将隐藏状态向量映射到词表空间的一个线性变换")
        print(f"数学表达式: logits = hidden_state × W^T + b")

        # 使用更高效的矩阵运算
        with torch.no_grad():
            cls_weights = self.model.cls.predictions.decoder.weight
            cls_bias = self.model.cls.predictions.decoder.bias

            print(f"解码器权重矩阵形状: {cls_weights.shape}")
            print(f"解码器偏置向量形状: {cls_bias.shape}")

            # 使用einsum进行高效矩阵乘法
            manual_logits = torch.einsum(
                "d,vd->v",
                mask_hidden_state,
                cls_weights
            ) + cls_bias

            # 添加温度系数调节
            temperature = 1.0  # 可调节参数
            tempered_logits = manual_logits / temperature

            # 验证一致性时添加容差说明
            model_logits = outputs.logits[0, mask_position, :]
            is_close = torch.allclose(
                manual_logits,
                model_logits,
                rtol=1e-3,
                atol=1e-5
            )
        print(f"\n手动计算的logits与模型输出是否一致: {is_close}")

        if not is_close:
            diff = torch.abs(manual_logits - model_logits)
            print(f"最大差异: {diff.max().item():.8f}")
            print(f"平均差异: {diff.mean().item():.8f}")
            print("注: 小的数值差异可能是由于计算精度造成的")

        # Step 3: 从logits到概率
        print("\n【Step 3: 将logits转换为概率】")
        with torch.no_grad():
            # 使用softmax转换为概率分布
            probabilities = torch.softmax(manual_logits, dim=0)
            print(f"概率总和: {probabilities.sum().item():.4f}")  # 应该接近1

            # 找出概率最高的token
            top_probs, top_indices = torch.topk(probabilities, 5)
            predicted_token_id = top_indices[0].item()
            predicted_token = self.tokenizer.convert_ids_to_tokens([predicted_token_id])[0]
            predicted_word = self.tokenizer.decode([predicted_token_id])

            print(f"\n预测的token (ID: {predicted_token_id}): '{predicted_token}'")
            print(f"解码后的单词: '{predicted_word}'")

            # 原始被遮蔽的词的概率
            if original_word:
                original_word_ids = self.tokenizer.encode(original_word, add_special_tokens=False)
                if original_word_ids:
                    original_id = original_word_ids[0]
                    original_prob = probabilities[original_id].item()
                    print(f"原始单词 '{original_word}' (ID: {original_id}) 的概率: {original_prob:.6f} ({original_prob*100:.2f}%)")

            # 展示前10个最可能的tokens
            self._display_token_probabilities(probabilities, top_k=10)

    def _display_token_probabilities(self, probabilities: torch.Tensor, top_k: int = 5) -> None:
        # 获取前k个最大概率及其索引
        top_probs, top_indices = torch.topk(probabilities, top_k)
        top_tokens = [self.tokenizer.convert_ids_to_tokens([idx.item()])[0] for idx in top_indices]
        top_words = [self.tokenizer.decode([idx.item()]) for idx in top_indices]

        # 创建使用更精确比例的图形
        fig, ax = plt.subplots(figsize=(16, 9), constrained_layout=True)

        # 使用更适合数据对比的渐变色调
        colors = plt.cm.Blues(np.linspace(0.6, 0.9, top_k))

        # 绘制条形图,适当增加条形宽度以提高可读性
        bars = ax.barh(range(top_k), top_probs.tolist(), color=colors, height=0.6)

        # 自定义Y轴刻度,同时显示token和对应的实际内容
        ax.set_yticks(range(top_k))
        labels = [f"{w} ({t})" if t != w else w for t, w in zip(top_tokens, top_words)]
        ax.set_yticklabels(labels, fontsize=12)

        # 添加更突出的标题与标签
        ax.set_title("Token Prediction Probabilities", fontsize=18, fontweight='bold', pad=20)
        ax.set_xlabel("Probability", fontsize=15, fontweight='semibold', labelpad=12)

        # 去掉多余的Y轴标签,因为标签已经在刻度上显示
        ax.set_ylabel("")

        # 动态设置X轴范围,确保最高概率条形图占据约80%的宽度
        max_prob = top_probs[0].item()
        ax.set_xlim(0, max(max_prob * 1.25, 0.05))

        # 添加更清晰的数据标签
        for i, (bar, prob) in enumerate(zip(bars, top_probs)):
            width = bar.get_width()
            ax.text(
                width + 0.005,
                i,
                f"{prob:.4f} ({prob:.1%})",
                ha='left',
                va='center',
                fontsize=13,
                fontweight='bold',
                color='#333333'
            )

        # 添加半透明的网格线以便于阅读
        ax.grid(axis='x', linestyle='--', alpha=0.4, color='gray')

        # 反转Y轴使最高概率在上方
        ax.invert_yaxis()

        # 美化图表边框和背景
        ax.spines['top'].set_visible(False)
        ax.spines['right'].set_visible(False)
        ax.spines['left'].set_linewidth(0.5)
        ax.spines['bottom'].set_linewidth(0.5)

        # 设置浅色背景以提高对比度
        ax.set_facecolor('#f8f8f8')

        # 添加概率条形图的圆角效果
        for bar in bars:
            bar.set_edgecolor('white')
            bar.set_linewidth(1)

        plt.show()

    def visualize_linear_transformation(self, text: str) -> None:
        """可视化向量解码的线性变换过程"""
        print("\n" + "=" * 60)
        print("可视化向量解码的线性变换过程")
        print("=" * 60)

        # 准备带掩码的输入
        masked_data = self.prepare_masked_input(text)
        inputs = masked_data["inputs"]

        # 寻找[MASK]位置
        input_ids = inputs["input_ids"]
        mask_positions = [i for i, id in enumerate(input_ids[0]) if id == self.mask_token_id]
        if mask_positions:
            mask_position = mask_positions[0]
        else:
            mask_position = len(input_ids[0]) - 2  # 避免[SEP]

        # 运行模型获取隐藏状态
        with torch.no_grad():
            outputs = self.model(
                **inputs,
                output_hidden_states=True
            )
            last_hidden_states = outputs.hidden_states[-1]
            mask_hidden_state = last_hidden_states[0, mask_position, :]

            # 获取解码器权重
            cls_weights = self.model.cls.predictions.decoder.weight

            # 为了可视化,我们只取前2维隐藏状态和几个样本词
            reduced_hidden = mask_hidden_state[:2].cpu().numpy()  # 添加cpu()

            # 选取几个常见词的权重向量
            common_words = ["the", "is", "and", "of", "to", "a", "in", "for", "with"]
            word_ids = []
            for word in common_words:
                word_ids.extend(self.tokenizer.encode(word, add_special_tokens=False))

            # 确保我们有不重复的IDs
            word_ids = list(set(word_ids))[:8]  # 取前8个
            word_tokens = [self.tokenizer.convert_ids_to_tokens([id])[0] for id in word_ids]

            # 获取这些词的权重向量
            word_vectors = cls_weights[word_ids, :2].cpu().numpy()  # 添加cpu()

            # 可视化
            plt.figure(figsize=(10, 8))

            # 绘制隐藏状态向量
            plt.scatter(reduced_hidden[0], reduced_hidden[1], c='red', s=100, marker='*',
                        label='Hidden state vector')

            # 绘制词向量
            plt.scatter(word_vectors[:, 0], word_vectors[:, 1], c='blue', s=50)

            # 添加词标签
            for i, token in enumerate(word_tokens):
                plt.annotate(token, (word_vectors[i, 0], word_vectors[i, 1]),
                            fontsize=10, alpha=0.8)

            # 计算这些词的logits(向量点积)
            logits = np.dot(reduced_hidden, word_vectors.T)

            # 绘制从隐藏状态到各词向量的连线,线宽表示logit值
            max_logit = np.max(np.abs(logits))
            for i, token in enumerate(word_tokens):
                # 归一化logit值作为线宽
                width = 0.5 + 3.0 * (logits[i] + max_logit) / (2 * max_logit)
                # 用颜色表示logit的正负
                color = 'green' if logits[i] > 0 else 'red'
                alpha = abs(logits[i]) / max_logit
                plt.plot([reduced_hidden[0], word_vectors[i, 0]],
                         [reduced_hidden[1], word_vectors[i, 1]],
                         linewidth=width, alpha=alpha, color=color)

            plt.title("The relationship between the hidden state vector and the word vector (2D projection)")
            plt.xlabel("dimension1")
            plt.ylabel("dimension2")
            plt.grid(True, alpha=0.3)
            plt.legend()
            plt.tight_layout()
            plt.show()

            # 展示与这些词的点积(logits)
            print("\n隐藏状态与词向量的点积(logits):")
            for i, token in enumerate(word_tokens):
                print(f"  与 '{token}' 的点积: {logits[i]:.4f}")

            # 将logits转换为概率
            probs = np.exp(logits) / np.sum(np.exp(logits))
            print("\n转换为概率后:")
            for i, token in enumerate(word_tokens):
                print(f"  '{token}' 的概率: {probs[i]:.4f} ({probs[i]*100:.2f}%)")

    def demonstrate_bert_mlm(self, text: str, positions_to_mask=None) -> None:
        """演示BERT掩码语言模型的完整预测过程"""
        print("\n" + "=" * 60)
        print("BERT掩码语言模型演示")
        print("=" * 60)

        # 分词
        inputs = self.tokenizer(
            text,
            return_tensors="pt",
            padding=True,
            truncation=True
        ).to(self.device)  # 添加.to(self.device)将输入移动到正确的设备

        input_ids = inputs["input_ids"]
        tokens = self.tokenizer.convert_ids_to_tokens(input_ids[0])

        print(f"原始文本: '{text}'")
        print(f"分词结果: {tokens}")

        # 如果没有指定要掩码的位置,则随机选择
        if positions_to_mask is None:
            # 排除[CLS]和[SEP]标记
            valid_positions = list(range(1, len(tokens) - 1))
            # 随机选择15%的token进行掩码
            num_to_mask = max(1, int(len(valid_positions) * 0.15))
            positions_to_mask = np.random.choice(valid_positions, num_to_mask, replace=False)

        # 应用掩码
        masked_input_ids = input_ids.clone()
        for pos in positions_to_mask:
            if pos < len(tokens):
                original_token = tokens[pos]
                original_id = input_ids[0, pos].item()
                masked_input_ids[0, pos] = self.mask_token_id
                print(f"位置 {pos}: 将 '{original_token}' (ID: {original_id}) 替换为 '{self.mask_token}'")

        # 运行模型
        with torch.no_grad():
            outputs = self.model(
                input_ids=masked_input_ids,
                token_type_ids=inputs["token_type_ids"],
                attention_mask=inputs["attention_mask"]
            )

            predictions = outputs.logits

            # 对每个掩码位置进行预测
            print("\n预测结果:")
            for pos in positions_to_mask:
                if pos < len(tokens):
                    # 获取该位置的logits
                    logits = predictions[0, pos, :]

                    # 应用softmax获取概率
                    probs = torch.softmax(logits, dim=0)

                    # 获取概率最高的token
                    top_probs, top_indices = torch.topk(probs, 5)

                    # 显示预测结果
                    original_token = tokens[pos]
                    original_id = input_ids[0, pos].item()

                    print(f"\n位置 {pos} 原始token: '{original_token}' (ID: {original_id})")
                    print("Top 5预测:")

                    for i, (index, prob) in enumerate(zip(top_indices, top_probs)):
                        predicted_token = self.tokenizer.convert_ids_to_tokens([index])[0]
                        print(f"  {i + 1}. '{predicted_token}': {prob:.6f} ({prob * 100:.2f}%)")

                    # 检查原始token的排名和概率
                    original_prob = probs[original_id].item()
                    original_rank = torch.where(torch.argsort(probs, descending=True) == original_id)[0].item() + 1
                    print(
                        f"  原始token '{original_token}' 排名: #{original_rank}, 概率: {original_prob:.6f} ({original_prob * 100:.2f}%)")

        # 恢复掩码后的文本
        predicted_ids = torch.argmax(outputs.logits, dim=-1)
        predicted_tokens = []

        for i in range(len(tokens)):
            if i in positions_to_mask:
                # 使用预测的token
                predicted_token = self.tokenizer.convert_ids_to_tokens([predicted_ids[0, i].item()])[0]
                predicted_tokens.append(predicted_token)
            else:
                predicted_tokens.append(tokens[i])

        # 解码回文本
        predicted_text = self.tokenizer.convert_tokens_to_string(predicted_tokens)
        print(f"\n恢复后的文本: '{predicted_text}'")

    def _nucleus_sampling(self, logits: torch.Tensor, p: float = 0.9) -> torch.Tensor:
        """
        实现nucleus sampling (也称为top-p sampling)

        Args:
            logits: 模型输出的logits
            p: 概率质量阈值(默认0.9)

        Returns:
            采样得到的token ID
        """
        # 计算softmax概率
        probs = torch.softmax(logits, dim=-1)

        # 按概率从大到小排序
        sorted_probs, sorted_indices = torch.sort(probs, descending=True)

        # 计算累积概率
        cumulative_probs = torch.cumsum(sorted_probs, dim=-1)

        # 找到累积概率超过p的位置
        nucleus = cumulative_probs < p

        # 确保至少选择一个token(如果所有nucleus都是False)
        if not nucleus.any():
            nucleus[0] = True

        # 将概率低于阈值的token概率设为0
        nucleus_probs = torch.zeros_like(probs)
        nucleus_probs[sorted_indices[nucleus]] = sorted_probs[nucleus]

        # 重新归一化概率
        if nucleus_probs.sum() > 0:
            nucleus_probs = nucleus_probs / nucleus_probs.sum()
        else:
            # 如果所有概率都为0,则使用原始概率的top-1
            nucleus_probs[sorted_indices[0]] = 1.0

        # 采样
        return torch.multinomial(nucleus_probs, num_samples=1)

    def compare_decoding_strategies(self, text: str = None):
        """比较不同解码策略的结果"""
        if text is None:
            # 使用更具歧义性的例子,让不同策略有可能生成不同结果
            text = "The scientist made a [MASK] discovery that changed the field."
            print(f"使用示例文本: '{text}'")

        # 扩展策略集合,使用多种参数
        strategies = {
            "贪婪解码": lambda logits: torch.argmax(logits, dim=-1),
            "Top-K=3": lambda logits: torch.multinomial(
                self._top_k_sampling(logits, k=3),
                num_samples=1
            ),
            "Top-K=10": lambda logits: torch.multinomial(
                self._top_k_sampling(logits, k=10),
                num_samples=1
            ),
            "Top-P=0.5": lambda logits: self._nucleus_sampling(logits, 0.5),
            "Top-P=0.9": lambda logits: self._nucleus_sampling(logits, 0.9)
        }

        # Top-K采样函数
        def _top_k_sampling(self, logits, k=5):
            values, _ = torch.topk(logits, k)
            min_value = values[-1]
            # 创建一个掩码,保留top-k的值
            mask = logits >= min_value
            filtered_logits = logits.clone()
            # 将非top-k的logits设为负无穷
            filtered_logits[~mask] = float('-inf')
            # 应用softmax获取概率分布
            probs = torch.softmax(filtered_logits, dim=-1)
            return probs

        # 为类添加辅助方法
        self._top_k_sampling = _top_k_sampling.__get__(self, type(self))

        masked_data = self.prepare_masked_input(text)
        inputs = masked_data["inputs"]

        # 查找[MASK]的位置
        input_ids = inputs["input_ids"]
        mask_positions = [i for i, id in enumerate(input_ids[0]) if id == self.mask_token_id]
        if mask_positions:
            mask_position = mask_positions[0]
        else:
            # 如果没有找到MASK标记,使用默认位置
            mask_position = masked_data["masked_position"]

        print(f"\n掩码词: '{self.mask_token}'")
        print(f"掩码文本: '{masked_data['masked_text']}'")

        with torch.no_grad():
            outputs = self.model(**inputs)
            logits = outputs.logits[0, mask_position]
            probs = torch.softmax(logits, dim=-1)

            # 获取原始单词的概率和排名
            if masked_data["original_word"]:
                original_word_tokens = self.tokenizer.encode(
                    masked_data["original_word"],
                    add_special_tokens=False
                )
                if original_word_tokens:
                    original_id = original_word_tokens[0]
                    original_prob = probs[original_id].item()
                    original_rank = torch.where(torch.argsort(probs, descending=True) == original_id)[0].item() + 1
                    print(
                        f"原始词: '{masked_data['original_word']}', 概率: {original_prob:.4f}, 在词表中排名: #{original_rank}")

            # 获取总体词汇表预测的Top 10
            top_probs, top_indices = torch.topk(probs, 10)
            print("\n模型Top-10预测词:")
            for i, (index, prob) in enumerate(zip(top_indices, top_probs)):
                token = self.tokenizer.decode([index.item()])
                print(f"  {i + 1}. '{token}': {prob:.4f}")

        print("\n不同解码策略比较结果:")
        results = {}
        for name, strategy in strategies.items():
            # 对每个策略运行5次,查看随机性效果
            strategy_results = []
            for i in range(5 if "Top" in name else 1):  # 贪婪解码是确定性的,只需运行一次
                pred_id = strategy(logits).item()
                pred_token = self.tokenizer.decode([pred_id])
                pred_prob = probs[pred_id].item()
                strategy_results.append((pred_token, pred_prob))

            results[name] = strategy_results

        # 显示结果
        for name, strategy_results in results.items():
            print(f"\n{name}:")
            if len(strategy_results) == 1:
                token, prob = strategy_results[0]
                print(f"  预测词: '{token}', 概率: {prob:.4f}")
            else:
                # 对于随机策略,分析多次运行的结果
                tokens = [r[0] for r in strategy_results]
                # 显示唯一词及其出现次数
                unique_tokens = {}
                for token in tokens:
                    if token not in unique_tokens:
                        unique_tokens[token] = 0
                    unique_tokens[token] += 1

                # 显示结果
                print(f"  5次采样结果:")
                for token, count in unique_tokens.items():
                    prob = next(p for t, p in strategy_results if t == token)
                    print(f"  '{token}': {count}/5次, 概率: {prob:.4f}")

        # 额外尝试几个更具歧义的文本
        if text == "The scientist made a [MASK] discovery that changed the field.":
            extra_examples = [
                "The weather forecast for tomorrow is [MASK].",
                "She felt [MASK] after hearing the unexpected news.",
                "The movie was both entertaining and [MASK]."
            ]

            print("\n\n更多测试示例:")
            for example in extra_examples:
                print(f"\n文本: '{example}'")

                # 使用top-p和贪婪解码做对比
                masked_data = self.prepare_masked_input(example)
                inputs = masked_data["inputs"]

                # 查找[MASK]的位置
                input_ids = inputs["input_ids"]
                mask_positions = [i for i, id in enumerate(input_ids[0]) if id == self.mask_token_id]
                if mask_positions:
                    mask_position = mask_positions[0]
                else:
                    mask_position = masked_data["masked_position"]

                with torch.no_grad():
                    outputs = self.model(**inputs)
                    logits = outputs.logits[0, mask_position]

                    # 使用贪婪解码
                    greedy_id = torch.argmax(logits, dim=-1).item()
                    greedy_token = self.tokenizer.decode([greedy_id])

                    # 使用top-p解码
                    topp_results = []
                    for _ in range(5):
                        topp_id = self._nucleus_sampling(logits, 0.7).item()
                        topp_token = self.tokenizer.decode([topp_id])
                        topp_results.append(topp_token)

                    print(f"  贪婪解码: '{greedy_token}'")
                    print(f"  Top-P=0.7 采样 (5次): {', '.join([f'\'{r}\'' for r in topp_results])}")


def main():
    """主函数"""
    print("初始化BERT模型解码可视化器...")
    # 您可以指定不同的预训练模型,如"bert-large-uncased"或"bert-base-chinese"等
    visualizer = DecodingVisualizer("bert-base-uncased")

    # 示例1: 基本解码过程
    # 展示模型如何从隐藏状态向量解码出词汇表中的token
    input_text = "The neural network transforms vectors into tokens through [MASK]."
    visualizer.decode_step_by_step(input_text)

    # 示例2: 可视化线性变换过程
    # 展示隐藏状态和词向量之间的关系
    input_text2 = "Language models convert hidden states to vocabulary logits by [MASK]."
    visualizer.visualize_linear_transformation(input_text2)

    # 示例3: 完整BERT掩码语言模型演示
    # 展示BERT如何预测被掩码的多个位置
    input_text3 = "Deep learning models perform vector transformations to process natural language."
    # 指定具体位置进行掩码演示,这些索引对应分词后的位置
    positions_to_mask = [4, 7, 11]  # 对应某些单词位置
    visualizer.demonstrate_bert_mlm(input_text3, positions_to_mask)

    # 示例4: 比较不同解码策略
    # 使用更具歧义性的文本能更好地展示不同解码策略的差异
    print("\n" + "=" * 60)
    print("不同解码策略比较")
    print("=" * 60)
    # 不传入参数,让函数使用默认的歧义性更强的文本
    visualizer.compare_decoding_strategies()

    # 可选:尝试其他歧义性文本来比较解码策略
    # ambiguous_texts = [
    #     "The weather forecast for tomorrow is [MASK].",
    #     "She felt [MASK] after hearing the unexpected news."
    # ]
    # for text in ambiguous_texts:
    #     visualizer.compare_decoding_strategies(text)


if __name__ == "__main__":
    main()
相关推荐
Stara05119 分钟前
YOLO11改进——融合BAM注意力机制增强图像分类与目标检测能力
人工智能·python·深度学习·目标检测·计算机视觉·yolov11
movigo7_dou14 分钟前
关于深度学习局部视野与全局视野的一些思考
人工智能·深度学习
itwangyang52026 分钟前
AIDD-人工智能药物设计-大语言模型在医学领域的革命性应用
人工智能·语言模型·自然语言处理
热心网友俣先生41 分钟前
2025年泰迪杯数据挖掘竞赛B题论文首发+问题一二三四代码分享
人工智能·数据挖掘
LitchiCheng1 小时前
MuJoCo 机械臂关节路径规划+轨迹优化+末端轨迹可视化(附代码)
人工智能·深度学习·机器人
前端小菜鸡zhc1 小时前
大模型之提示词工程
人工智能
zy_destiny1 小时前
【非机动车检测】用YOLOv8实现非机动车及驾驶人佩戴安全帽检测
人工智能·python·算法·yolo·机器学习·安全帽·非机动车
that's boy1 小时前
字节跳动开源 LangManus:不止是 Manus 平替,更是下一代 AI 自动化引擎
运维·人工智能·gpt·自动化·midjourney·gpt-4o·deepseek
stormsha1 小时前
使用Python进行AI图像生成:从GAN到风格迁移的完整指南
人工智能·python·生成对抗网络