在职前端Leader学习/转行 AI Agent -DAY81

2026.10.19 09:48

学习路线

rust 复制代码
Python -> Python数据分析 -> LangChain -> LangGraph -> FastAPI + SQLAIchemy -> 机器学习 -> 深度学习 -> NLP -> Coze -> Dify -> 大模型应用基础 -> 大模型微调 -> 多模态 -> vibeCoding -> Hermes -> Docker

复习巩固总结

1. Transformer

flowchart BT subgraph inp[&#34;输入部分&#34;] direction LR inputs[&#34;Inputs&#34;] --> inEmb[&#34;Input Embedding&#34;] inPe[&#34;Positional Encoding&#34;] --> inSum[&#34;+&#34;] inEmb --> inSum outputs[&#34;Outputs<br/>shifted right&#34;] --> outEmb[&#34;Output Embedding&#34;] outPe[&#34;Positional Encoding&#34;] --> outSum[&#34;+&#34;] outEmb --> outSum end subgraph enc[&#34;编码器部分 · N×(论文里是 6)&#34;] direction BT eMha[&#34;Multi-Head Attention&#34;] --> eNorm1[&#34;Add & Norm&#34;] eNorm1 --> eFf[&#34;Feed Forward&#34;] eFf --> eNorm2[&#34;Add & Norm&#34;] end subgraph dec[&#34;解码器部分 · N×(论文里是 6)&#34;] direction BT dMask[&#34;Masked<br/>Multi-Head Attention&#34;] --> dNorm1[&#34;Add & Norm&#34;] dNorm1 --> dMha[&#34;Multi-Head Attention&#34;] dMha --> dNorm2[&#34;Add & Norm&#34;] dNorm2 --> dFf[&#34;Feed Forward&#34;] dFf --> dNorm3[&#34;Add & Norm&#34;] end subgraph outp[&#34;输出部分&#34;] direction BT linear[&#34;Linear&#34;] --> softmax[&#34;Softmax&#34;] --> probs[&#34;Output Probabilities&#34;] end inSum --> eMha inSum -.->|残差| eNorm1 eNorm1 -.->|残差| eNorm2 outSum --> dMask outSum -.->|残差| dNorm1 dNorm1 -.->|残差| dNorm2 dNorm2 -.->|残差| dNorm3 eNorm2 -->|编码器输出,作为 K、V| dMha dNorm3 --> linear classDef emb fill:#f7c6d0,stroke:#c46b84,color:#1a1a1a classDef pe fill:#f3e8ff,stroke:#7b3fa0,color:#1a1a1a classDef attn fill:#f6c98a,stroke:#c47b2a,color:#1a1a1a classDef ff fill:#9fd0e8,stroke:#2b6f9e,color:#1a1a1a classDef norm fill:#efe6a8,stroke:#a08a20,color:#1a1a1a classDef head fill:#b7e1b7,stroke:#2e7d32,color:#1a1a1a class inEmb,outEmb emb class inPe,outPe,inputs,outputs,inSum,outSum pe class eMha,dMask,dMha attn class eFf,dFf ff class eNorm1,eNorm2,dNorm1,dNorm2,dNorm3 norm class linear,softmax,probs head style inp fill:#f6efff,stroke:#7b3fa0 style enc fill:#e8f1fb,stroke:#2171b5 style dec fill:#fde8e8,stroke:#c23b3b style outp fill:#e7f6e7,stroke:#2e7d32

2. Transformers

2.1 Pipeline

2.1.1 情感分类
python 复制代码
def dm01_test_classification():
    # 需求: 用 中文预训练模型进行 情感分析.
    # 参1: 任务类型, 这里是: 情感分析任务.
    # 参2: 指定模型的路径, 我用的是: 本地路径, 你可以用相对路径. 但是: 路径要合法(不能出现中文, 空格, 特殊符号等)
    my_model = pipeline(task='sentiment-analysis', model=os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'chinese_sentiment'))
    output = my_model('我爱北京天安门, 天安门上太阳升!')
    print(f'output: {output}')
2.1.2 特征提取
python 复制代码
# todo 2.特征抽取任务 → 属于: 不带任务头输出(模型把'通用特征'提取出来 → 模型自己定义的 任务结果)
# 大白话解释:
#     不带任务头输出: 只给'原材料'(文本的原始特征), 适合: 你自己后续开发新任务. 例如: 买一堆手机零件, 你自己接着组装, 改装.
#     带头任务头输出: 直接给'成品'(针对具体任务的结果), 适合: 直接用模型完成指定任务. 例如: 买一部现成的能打电话, 刷视频的完整手机(直接用)
def dm02_test_feature_extraction():
    # 1. 创建1个pipeline对象.
    # 参1: 任务类型, 这里是: 特征提取(抽取)任务.
    # 参2: 指定模型的路径, 我用的是: 本地路径, 你可以用相对路径. 但是: 路径要合法(不能出现中文, 空格, 特殊符号等)
    my_model = pipeline(task='feature-extraction', model=os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'bert-base-chinese'))

    # 2. 将文本输入模型进行 特征抽取.
    output = my_model('人生该如何起头')

    # 3. 打印结果.
    print(f'output: {output}')  #
    print(f'类型: {type(output)}')  # <class 'list'>
    print(f'形状: {np.array(output).shape}')  # [batch_size, seq_len, d_model] → [1, 9, 768]

    # 思考: 不是7个字吗? 怎么是9个? → 因为有 开头[CLS] 和 结尾[SEP], 只要用bert-base-chinese, 就会自动加1个开头和1个结束标记.
2.1.3 完形填空
python 复制代码
# todo 3. 完形填空 → 一次只能预测1个[MASK], 如果要多个[MASK], 则必须通过循环实现.
# 例如: 我[MASK]你
# 例如: 我[MASK]你, 你[MASK]我    一次只能预测1个MASK, 如果要多个[MASK], 则必须通过循环实现.
def dm03_test_fill_mask():
    # 1. 创建1个pipeline对象.
    # 参1: 模型类型, 这里是: 完形填空任务.
    # 参2: 模型路径, 我用的是: 本地路径, 你可以用相对路径. 但是: 路径要合法(不能出现中文, 空格, 特殊符号等)
    my_model = pipeline(task='fill-mask', model=os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'chinese-bert-wwm'))

    # 2. 将文本输入模型进行 完形填空.
    # output = my_model('我[MASK]你')
    output = my_model('我想明天去[MASK]家吃饭')

    # 3. 打印结果.
    print(f'output: {output}')

    # 结果如下
    """
    output: [
        {'score': 0.4332510530948639, 'token': 2695, 'token_str': '愛', 'sequence': '我 爱 你'},
        {'score': 0.19461670517921448, 'token': 3221, 'token_str': '是', 'sequence': '我 是 你'},
        {'score': 0.06783866137266159, 'token': 4263, 'token_str': '爱', 'sequence': '我 爱 你'},
        {'score': 0.02951931208372116, 'token': 1469, 'token_str': '和', 'sequence': '我 和 你'},
        {'score': 0.02699531987309456, 'token': 5645, 'token_str': '與', 'sequence': '我 與 你'}
    ]
    """
2.1.4 阅读理解
python 复制代码
# todo 4.阅读理解, → 抽取式问答, 输入一段文本和一个问题, 让模型输出结果.
def dm04_test_question_answering():
    # 1. 创建1个pipeline对象.
    # 参1: 任务类型, 这里是: 阅读理解任务.
    # 参2: 模型路径, 我用的是: 本地路径, 你可以用相对路径. 但是: 路径要合法(不能出现中文, 空格, 特殊符号等)
    my_model = pipeline(task='question-answering', model=os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'chinese_pretrain_mrc_roberta_wwm_ext_large'))

    # 2. 给模型送数据, 做: 预测.
    # 2.1 定义问答句子.
    context = '我叫芬哥, 我是一名教师, 我的喜好是乒乓球和台球'
    questions = ['我叫啥?', '我是做什么的?', '我的爱好是什么?']

    # 2.2 将上述的数据传给模型.
    output = my_model(question=questions, context=context)

    # 3. 打印结果.
    print(f'output: \n{output}')

    # 结果格式如下:
    """
    [
        {'score': 0.9944425821304321, 'start': 2, 'end': 4, 'answer': '芬哥'},
        {'score': 0.9843989610671997, 'start': 10, 'end': 12, 'answer': '教师'},
        {'score': 0.07795657217502594, 'start': 19, 'end': 25, 'answer': '乒乓球和台球'}
    ]
    """
2.1.5 文本摘要
python 复制代码
# todo 5. 文本摘要 → '提炼精华'
def dm05_test_summarization():
    # 1. 创建1个pipeline对象.
    # 参1: 模型类型, 这里是: 文本摘要任务.
    # 参2: 模型路径, 我用的是: 本地路径, 你可以用相对路径. 但是: 路径要合法(不能出现中文, 空格, 特殊符号等)
    my_model = pipeline(task='summarization', model=os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'distilbart-cnn-12-6'))

    # 2. 定义数据, 输入模型, 获取预测结果.
    text = "BERT is a transformers model pretrained on a large corpus of English data " \
           "in a self-supervised fashion. This means it was pretrained on the raw texts " \
           "only, with no humans labelling them in any way (which is why it can use lots " \
           "of publicly available data) with an automatic process to generate inputs and " \
           "labels from those texts. More precisely, it was pretrained with two objectives:Masked " \
           "language modeling (MLM): taking a sentence, the model randomly masks 15% of the " \
           "words in the input then run the entire masked sentence through the model and has " \
           "to predict the masked words. This is different from traditional recurrent neural " \
           "networks (RNNs) that usually see the words one after the other, or from autoregressive " \
           "models like GPT which internally mask the future tokens. It allows the model to learn " \
           "a bidirectional representation of the sentence.Next sentence prediction (NSP): the models" \
           " concatenates two masked sentences as inputs during pretraining. Sometimes they correspond to " \
           "sentences that were next to each other in the original text, sometimes not. The model then " \
           "has to predict if the two sentences were following each other or not."

    output = my_model(text)

    # 3. 输出结果.
    print(f'output: \n{output}')
2.1.6 NER
python 复制代码
# todo 6. NER任务 → 命名实体识别, 在一段文本中找到特定的'实体'
# 字母解释: B → 实体的开头, I → 实体的内容/中间.  O: 不是实体(例如: 在, 的这些字)
# 命名实体识别: 全称是Named Entity Recognition, 也就是识别出这段文本中可能存在的<<命名实体>>,
# 例如: 人名(PER, person), 地名(LOC, location), 机构名(ORG, organization) 以及其它实体(MISC, miscellaneous)
def dm06_test_ner():
    # 1.创建1个pipeline对象.
    my_model = pipeline(task='ner', model=os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'roberta-base-finetuned-cluener2020-chinese'))

    # 2.给模型发送数据, 获取预测结果.
    output = my_model('我叫夯哥, 我是一名教师, 我的喜好是乒乓球和台球')

    # 3.输出结果.
    print(f'output: \n{output}')

    # 结果格式如下:
    """
    [
        {'entity': 'B-position', 'score': 0.95450044, 'index': 10, 'word': '教', 'start': 10, 'end': 11},
        {'entity': 'I-position', 'score': 0.9760174, 'index': 11, 'word': '师', 'start': 11, 'end': 12}
    ]
    """

    # 2.给模型发送数据, 获取预测结果.
    # output = my_model('我爱北京天安门, 天安门上太阳升!')
    # 结果格式如下:
    """
    [
        {'entity': 'B-address', 'score': 0.89396185, 'index': 3, 'word': '北', 'start': 2, 'end': 3},
        {'entity': 'I-address', 'score': 0.87884074, 'index': 4, 'word': '京', 'start': 3, 'end': 4},
        {'entity': 'I-address', 'score': 0.5274998, 'index': 5, 'word': '天', 'start': 4, 'end': 5},
        {'entity': 'I-address', 'score': 0.7829927, 'index': 6, 'word': '安', 'start': 5, 'end': 6},
        {'entity': 'I-address', 'score': 0.7299224, 'index': 7, 'word': '门', 'start': 6, 'end': 7},
        {'entity': 'B-address', 'score': 0.6551384, 'index': 9, 'word': '天', 'start': 9, 'end': 10},
        {'entity': 'I-address', 'score': 0.53630906, 'index': 10, 'word': '安', 'start': 10, 'end': 11},
        {'entity': 'I-address', 'score': 0.51451707, 'index': 11, 'word': '门', 'start': 11, 'end': 12}
    ]
    """
    output = my_model('我爱武汉黄鹤楼, 黄鹤楼上黄鹤飞!')

    # 3.输出结果.
    print(f'output: \n{output}')

    # 结果格式如下:
    """
    [
        {'entity': 'B-address', 'score': 0.8150746, 'index': 3, 'word': '武', 'start': 2, 'end': 3},
        {'entity': 'I-address', 'score': 0.83502835, 'index': 4, 'word': '汉', 'start': 3, 'end': 4},
        {'entity': 'I-scene', 'score': 0.4844989, 'index': 5, 'word': '黄', 'start': 4, 'end': 5},
        {'entity': 'I-scene', 'score': 0.7377188, 'index': 6, 'word': '鹤', 'start': 5, 'end': 6},
        {'entity': 'I-scene', 'score': 0.7756539, 'index': 7, 'word': '楼', 'start': 6, 'end': 7},
        {'entity': 'B-scene', 'score': 0.5279873, 'index': 9, 'word': '黄', 'start': 9, 'end': 10},
        {'entity': 'I-scene', 'score': 0.67429155, 'index': 10, 'word': '鹤', 'start': 10, 'end': 11},
        {'entity': 'I-scene', 'score': 0.74127746, 'index': 11, 'word': '楼', 'start': 11, 'end': 12}
    ]
    """

2.2 自动模型

2.2.1 情感分类
python 复制代码
"""
案例:
    通过自动模型的方式 完成NLP常见的6个需求.
"""

import os

import torch                # Pytorch深度学习框架, 张量计算, 自动求导...

# AutoConfig: 自动配置模型参数.
# AutoModel: 自动加载模型(即: 通用的模型加载类)
# AutoTokenizer: 自动加载和模型匹配的分词器(即: 通用的分词器加载类)
from transformers import AutoConfig, AutoModel, AutoTokenizer

# AutoModelForSequenceClassification: 用于 文本分类 的 模型.
# AutoModelForMaskedLM: 用于 掩码语言建模 的 模型.
# AutoModelForQuestionAnswering: 用于 问题回答 的 模型.
from transformers import AutoModelForSequenceClassification, AutoModelForMaskedLM, AutoModelForQuestionAnswering

# AutoModelForSeq2SeqLM  用于 序列到序列的 建模(例如: 文本摘要)
# AutoModelForTokenClassification 用于 做token级分类(例如: NER) 的建模
from transformers import AutoModelForSeq2SeqLM, AutoModelForTokenClassification

from rich import print          # 输出美化(效果不明显, 你写不写都行)


# todo 1.情感分类任务.
# 例如: 情绪分类 → '今天开心极了...'        积极的情绪
# 例如: 商品分类 → '手机壳', '数码配件'...   '连衣裙' → 服饰
def dm01_text_classification():
    # 1. 加载 Tokenizer → 分词器对象, 把文本 转成 模型可接收的输入格式.
    my_tokenizer = AutoTokenizer.from_pretrained(os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'chinese_sentiment'))

    # 2. 加载模型.
    my_model = AutoModelForSequenceClassification.from_pretrained(os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'chinese_sentiment'))

    # 3. 文本转张量.
    # 3.1 定义文本.
    message = '人生该如何起头'

    # 3.2 思路1: return_tensors='pt' 返回的是: 二维张量(Tensor)
    msg_tensor1 = my_tokenizer.encode(
        text=message,            # 待编码的文本
        return_tensors='pt',     # 返回张量的类型, 默认为: None, 可选: 'pt', 'tf', 'np'
        padding='max_length',    # 是否填充
        truncation=True,         # 是否截断
        max_length=5,            # (指定输入的)最大长度
    )
    # 格式为: msg_tensor1: tensor([[ 101, 782, 4495, 6421, 102]]), 类型: <class 'torch.Tensor'>
    # print(f'msg_tensor1: {msg_tensor1}, 类型: {type(msg_tensor1)}')

    # 3.3 思路2: 不用return_tensors='pt', 返回的是 一维列表, 需要手动转成张量.
    msg_list2 = my_tokenizer.encode(
        text=message,            # 待编码的文本
        padding='max_length',    # 是否填充
        truncation=True,         # 是否截断
        max_length=5,            # (指定输入的)最大长度
    )
    # 格式为: msg_list2: [101, 782, 4495, 6421, 102], 类型: <class 'list'>
    # print(f'msg_list2: {msg_list2}, 类型: {type(msg_list2)}')

    # 手动把上述的列表转成二维张量.
    msg_tensor2 = torch.tensor([msg_list2])
    # print(f'msg_tensor2: {msg_tensor2}, 类型: {type(msg_tensor2)}')

    # 4. 把数据送给模型进行推理.
    # 4.1 设置模型为: 推理模式(评估模式) → 会关闭Dropout等训练特有的层.
    my_model.eval()
    # 4.2 直接调用模型(模型推理)
    # result = my_model(msg_tensor1)
    result = my_model(msg_tensor2)
    print(f'result(包含模型输出的字典): {result}')

    # 4.3 使用 return_dict=False, 可以返回: 元组.
    # result2 = my_model(msg_tensor1, return_dict=False)
    result2 = my_model(msg_tensor2, return_dict=False)
    print(f'result2(包含模型输出的元组): {result2}')


# todo 7.测试代码
if __name__ == '__main__':
    # 1. 测试Transformers的自动模型方式处理NLP任务 → 情感分类
    dm01_text_classification()
2.2.2 特征提取
python 复制代码
# todo 2. 特征提取任务.
def dm02_feature_extraction():
    # 1. 加载 Tokenizer → 分词器对象, 把文本 转成 模型可接收的输入格式.
    my_tokenizer = AutoTokenizer.from_pretrained(os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'bert-base-chinese'))

    # 2. 加载模型.
    my_model = AutoModel.from_pretrained(os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'bert-base-chinese'))

    # 3. 文本转张量.
    # 3.1 定义遍历, 记录: 待处理的文本.
    messsage = ['你是谁', '人生该如何起头']

    # 3.2 把上述的文本 转成 模型可接收的输入格式.
    # 返回值是字典形式, 其中各个键(key)的意思是:
    # input_ids: 文本的id序列 → 对应模型的 vocab.txt 文件
    # token_type_ids: 文本的token序列
    # attention_mask: 注意力掩码, 1(真实), 0(填充)
    msg_tensor = my_tokenizer.encode_plus(
        text = messsage,            # 待处理的文本
        return_tensors='pt',       # 返回张量的类型
        padding='max_length',       # 是否填充
        truncation=True,            # 是否截断
        max_length=30,              # (指定输入的)最大长度
    )
    # print(f'msg_tensor: {msg_tensor}')

    # 4. 给模型喂数据, 提取特征.
    my_model.eval()         # 切换到: 评估模式(推理模式)
    output = my_model(**msg_tensor)

    # 5. 获取模型输出 → 打印结果.
    # print(f'output: \n{output}')
    # 最后一个(隐藏状态)层输出 → [batch_size, seq_len, hidden_size]        # [1, 30, 768]
    print(f'output.last_hidden_state: {output.last_hidden_state.shape}')

    # 池化层输出 → [batch_size, hidden_size], [1, 768]
    print(f'output.pooler_output: {output.pooler_output.shape}')
2.2.3 完形填空
python 复制代码
# todo 3. 完形填空任务.
def dm03_fill_mask():
    # 0. 定义遍历, 记录: 模型名.
    model_name = os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'chinese-bert-wwm')

    # 1. 加载Tokenizer分词器.
    my_tokenizer = AutoTokenizer.from_pretrained(model_name)

    # 2. 加载模型.
    my_model = AutoModelForMaskedLM.from_pretrained(model_name)

    # 3. 文本转张量.
    input = my_tokenizer.encode_plus('我想明天去[MASK]家吃饭', return_tensors='pt')
    # print(f'input: {input}')

    # 4. 给模型喂数据, 获取模型输出.
    my_model.eval()        # 评估模式
    output = my_model(**input)

    # 5. 获取结果, 并打印.
    # print(f'output: \n{output}')
    # print(f'output.logits: {output.logits.shape}')        # [1, 11, 21128]

    # [MASK]位置的索引: [1, 11, 21128] → [11, 21128] → [21128]
    mask_predict_idx = torch.argmax(output.logits[0][6]).item()
    print(f'[MASK]位置的索引: {mask_predict_idx}')

    # 打印概率最高的字 → '你'
    print(f'概率最高的字的: {my_tokenizer.convert_ids_to_tokens(mask_predict_idx)}')
2.2.4 阅读理解
python 复制代码
# todo 4. 阅读理解 → 抽取式问答任务, 即: 给定一段文本和问题, 让模型输出结果.
def dm04_question_answering():
    # 0. 定义遍历, 记录: 模型名
    model_name = os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'chinese_pretrain_mrc_roberta_wwm_ext_large')

    # 1. 加载分词器对象Tokenizer
    my_tokenizer = AutoTokenizer.from_pretrained(model_name)
    # 2. 加载模型.
    my_model = AutoModelForQuestionAnswering.from_pretrained(model_name)

    # 3. 准备文本. 细节: 上下文最好不要写空格, 会影响模型定位, 空格会被删除, 可以换成标签符号.
    context = '我叫张三 我是一个程序员 我的喜好是乒乓球'
    questions = ['我是谁?', '我是做什么的?', '我的爱好是什么?']

    # 4. 该模型是针对于每个问题进行问答推理的, 即: 逐个问题生成, 此处要用for循环.
    for question in questions:
        # 4.1 把上述的问题 转成 模型可接收的输入格式.
        # 参1: 当前的问题. 参2: 包含答案的上下文文本. 参3: 返回张量的形式.
        input = my_tokenizer.encode_plus(question, context, return_tensors='pt')
        # 返回值字典, 各个键(key)的意思是:
        #   input_ids: [CLS] + 问题 + [SEP] + 上下文 + [SEP]        注意: 空格不统计.
        #   token_type_ids: 0 → 问题, 1 → 上下文
        #   attention_mask: 1 → 真实, 0 → 填充
        print(f'input: {input}')

        # 4.2 给模型喂数据, 获取模型输出.
        my_model.eval()
        output = my_model(**input)

        # 4.3 获取结果
        # print(f'output: \n{output}')
        print(f'output: {output.start_logits.shape}')      # [1, 26], 即: [CLS] + 问题 + [SEP] + 上下文 + [SEP] 表示: 答案的开头位置索引.
        print(f'output: {output.end_logits.shape}')        # [1, 26], 即: [CLS] + 问题 + [SEP] + 上下文 + [SEP] 表示: 答案的结束位置索引.
        # print('\n' * 3)

        # 4.4 计算模型预测的答案(开头的位置索引, 结束的位置索引)
        start, end = torch.argmax(output.start_logits), torch.argmax(output.end_logits) + 1  # 因为切片时, 包左不包右, 所以: 结束位置索引 + 1
        print(f'start: { start}, end: {end}')

        # 4.5 获取模型预测的答案.
        answer = my_tokenizer.convert_ids_to_tokens(input.input_ids[0][start:end])

        # 4.6 打印最终效果.
        print(f'question: {question}, answer: {answer}')
        print('\n' * 3)

NLP 学习

162. 自动模型方式_文本摘要任务(理解)

python 复制代码
# todo 5. 文本摘要: 摘要生成任务的输入一段文本, 输出是有一段概况, 简单的文字.
def dm05_summarization():
    # 1. 定义输入文本 → 一段关于BERT模型预训练方法的加英文介绍.
    text = "BERT is a transformers model pretrained on a large corpus of English data " \
           "in a self-supervised fashion. This means it was pretrained on the raw texts " \
           "only, with no humans labelling them in any way (which is why it can use lots " \
           "of publicly available data) with an automatic process to generate inputs and " \
           "labels from those texts. More precisely, it was pretrained with two objectives:Masked " \
           "language modeling (MLM): taking a sentence, the model randomly masks 15% of the " \
           "words in the input then run the entire masked sentence through the model and has " \
           "to predict the masked words. This is different from traditional recurrent neural " \
           "networks (RNNs) that usually see the words one after the other, or from autoregressive " \
           "models like GPT which internally mask the future tokens. It allows the model to learn " \
           "a bidirectional representation of the sentence.Next sentence prediction (NSP): the models" \
           " concatenates two masked sentences as inputs during pretraining. Sometimes they correspond to " \
           "sentences that were next to each other in the original text, sometimes not. The model then " \
           "has to predict if the two sentences were following each other or not."

    # 2. 定义遍历, 记录: 模型名
    model_name = os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'distilbart-cnn-12-6')
    # 3. 加载分词器对象Tokenizer 和 模型对象.
    my_tokenizer = AutoTokenizer.from_pretrained(model_name)
    my_model = AutoModelForSeq2SeqLM.from_pretrained(model_name)

    # 4. 将输入文本 转成 模型可接收的输入格式.
    input = my_tokenizer(text, return_tensors='pt')
    print(f'input: {input}')

    # 5. 把输入文本喂给模型, 获取预测结果.
    # 5.1 切换模型模式为评估模式.
    my_model.eval()
    # 5.2 模型预测, 文本摘要
    output = my_model.generate(input['input_ids'])
    print(f'output: {output}')

    # 6. 打印预测结果, 即: 把文本只要 → 可读问题
    # 扩展: 只能将ids还原为token, 如果感兴趣, 你可以看一下BPE(Byte-Pair Encoding), 字节对编码(切词), GPT底层就是用它(BPE)切词的.
    print(my_tokenizer.convert_ids_to_tokens(output[0]))

    # decode()解码
    # skip_special_tokens: 跳过特殊标记 → 即: 去除token前后的特殊字符.
    # clean_up_tokenization_spaces: 是否保留原始分词的空格.
    print([my_tokenizer.decode(g, skip_special_tokens=True, clean_up_tokenization_spaces=True) for g in output])
    print([my_tokenizer.decode(g, skip_special_tokens=False, clean_up_tokenization_spaces=False) for g in output])


# todo 7.测试代码
if __name__ == '__main__':
    # 1. 测试Transformers的自动模型方式处理NLP任务 → 情感分类
    # dm01_text_classification()

    # 2. 测试Transformers的自动模型方式处理NLP任务 → 特征提取
    # dm02_feature_extraction()

    # 3. 测试Transformers的自动模型方式处理NLP任务 → 完形填空
    # dm03_fill_mask()

    # 4. 测试Transformers的自动模型方式处理NLP任务 → 阅读理解
    # dm04_question_answering()

    # 5. 测试Transformers的自动模型方式处理NLP任务 → 文本摘要
    dm05_summarization()

拓展QA:

为什么 distilbart-cnn-12-6 要在 convert_ids_to_tokens 之后再 decode?前面的模型都是直接输出,没有 decode 这一步。

decode 看的是模型吐出来的是什么,不是模型名叫不叫 distilbart。分词器有两个方向:文字交给模型之前要编码成编号,编号要给人看的时候再解码成文字。convert_ids_to_tokens 和 decode 都是把编号变回文字,前者一个编号对一个碎片,后者把碎片拼成一句。这两步是并列的两种读法,不是必须先转换再解码。

前面四个任务的输出都不是新写出来的一句话,所以没有 decode。情感分类返回每个类别一个分数,取得分最高的类别编号,对上标签名。特征提取返回向量,形状是 [句数, 词数, 768],里面没有词。完形填空在 [MASK] 那个位置上给词表里每个词一个分数,取得分最高的一个编号,convert_ids_to_tokens 就能看到那个字。阅读理解返回答案在原文里的开头下标和结尾下标,用这两个下标去切原来的 input_ids,切出来的还是输入里已有的词。

摘要是序列到序列。generate() 按词表一个一个新写出一串编号,这串编号输入里原来没有。distilbart-cnn-12-6 用 BPE 切词,一个英文单词常常被切成好几段,段首用 Ġ 表示前面有空格,序列两头还有 <s>、</s>。convert_ids_to_tokens 只能把这些碎片列出来。decode 才把碎片拼回句子,skip_special_tokens=True 去掉两端的特殊标记,clean_up_tokenization_spaces=True 把 BPE 留下的空格整理掉。

为什么 input 的时候没有做 encode?

做了,只是没有写成 encode 或 encode_plus。input = my_tokenizer(text, return_tensors='pt') 是在调用分词器本身,内部走的就是编码:把这句话切成 token,再变成 input_ids。返回的是字典,里面有 input_ids 和 attention_mask,和 encode_plus 同一类结果。

三种写法的差别在返回什么,不在有没有编码。encode 只给出编号。encode_plus 和直接调用分词器都给出整份输入字典。摘要这里后面用 input['input_ids'] 交给 generate(),所以用直接调用就够了。阅读理解要同时送问题和上下文,才写成 encode_plus(question, context)。

这和模型有关吗?什么情况下 input 不需要 encode,output 不需要 decode,怎么判断?

和模型名字无关,看手上这份数据是文字还是编号。

输入不需要再 encode 的情况:它已经是 input_ids 这种编号张量。还是人写的句子,就必须编码,中文英文都一样。直接调用分词器或 encode_plus 已经在做这件事,不必再套一层 encode。

输出不需要 decode 的情况:返回的不是新生成的编号序列。类别分数、向量、原文里的下标,结果已经能读,或者从输入里把对应的词切出来。返回的是 generate() 新写的一串编号,摘要、翻译、续写都是这种,要用 decode 拼成句子。convert_ids_to_tokens 只用来看切词,不能代替 decode。

BPE 是干什么的?GPT 底层怎么用它?

BPE 是 Byte-Pair Encoding,字节对编码,一种切词方法。训练词表时先把文字拆成最小单位,英文里常从字母或字节开始,再统计哪两个相邻碎片最常一起出现,把这一对合并成一个新词,反复合并,直到词表达到预定大小。常见词整词留下,少见的词切成几段还能拼回去的碎片。词表里没有的新词也能用已有碎片拼出来,不会变成未知词。

摘要结果里的 Ġ 就是这种切法留下的。Ġ 表示这个碎片前面原来有一个空格,单词中间被切开的后半段没有 Ġ。convert_ids_to_tokens 把这些碎片列出来,decode 按规则拼回句子,空格也就还原了。

GPT 底层用 BPE 做输入和输出的切词。GPT-2 用的是字节级 BPE:先按 UTF-8 的字节切开,再做上面的合并,词表大约 5 万。一句话进模型之前,分词器按这份词表切成 token,再变成 input_ids,模型只看见编号。生成时模型每次从这份词表里预测下一个编号,decode 再把编号拼回文字。GPT 处理的是 BPE 词表里的碎片,句子和单个汉字都要先过这一层。

AutoTokenizer 也用 BPE 吗?

AutoTokenizer 自己不切词。它读模型目录里的配置,加载和这个模型一起训练的那份分词器。用不用 BPE,看加载出来的是哪一种。

这份代码里只有摘要的 distilbart-cnn-12-6 用 BPE。它和 GPT-2 同一套字节级 BPE,所以 convert_ids_to_tokens 里会出现 Ġ。前面几个不是。bert-base-chinese、chinese-bert-wwm、阅读理解的中文 RoBERTa 用的是 BERT 的 WordPiece,子词用 ## 接在后面,中文 RoBERTa 沿用的也是这套词表,不是英文 RoBERTa 的 BPE。chinese_sentiment 同样是 BERT 系的 WordPiece。调用方式都是 AutoTokenizer.from_pretrained,切词规则跟着模型走。

管道已经能直接给出结果,为什么还要写成这么长的自动模型?自动模型好在哪里?

只想马上看到摘要句子时,用管道。pipeline(task='summarization', model=...) 把原文送进去,分词、generate、decode 包在一次调用里,交回整理好的 summary_text。自动模型和它用的是同一颗序列到序列的头,预测不会因为拆开就变准。写得长,是因为这三步都露在外面。

露出来之后,生成和拼句的规则可以改。generate() 上能设最长最短长度、束搜索条数、重复惩罚。output 是新写的编号,convert_ids_to_tokens 能对上 Ġ 和 <s>、</s>,decode 的两个开关决定留不留特殊标记、空格怎么整理。管道按摘要任务的默认规则交一句。

换模型时,AutoTokenizer 和 AutoModelForSeq2SeqLM 按目录里的配置加载对应的类。BART、T5、Pegasus 都走这两行,任务头不用改成各自的类名。这是相对具体模型省事的地方。管道换模型同样是改 model= 的路径。

已经分好词、补齐成一批 input_ids 时,直接送进 generate()。要接着微调,也是拿到这个模型对象和损失再训。管道包好的是推理。完形填空要改取词方式、阅读理解要改答案落在哪一段,也是同一类原因:中间张量在手里,规则才能自己定。

2026.10.19 13:27:51

163. 自动模型方式_NER任务(理解)

python 复制代码
# todo 6. NER任务(Named Entity Recognition) 命名实体识别.
# 命名实体识别: 本质上是一个分类任务(又叫序列标注任务), 实体词识别是句法分析的基础, 而句法分析又是NLP任务的核心.
def dm06_ner():
    # 0. 定义遍历, 记录: 模型名.
    model_name = os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'roberta-base-finetuned-cluener2020-chinese')

    # 1. 加载分词器.
    my_tokenizer = AutoTokenizer.from_pretrained(model_name)
    # 2. 加载模型.
    my_model = AutoModelForTokenClassification.from_pretrained(model_name)
    # 3. 加载配置文件.
    config = AutoConfig.from_pretrained(model_name)

    # 4. 数据张量化 → 把输入文本转换成模型可接收的输入格式.
    input = my_tokenizer.encode_plus('我爱北京天安门, 天安门上太阳升!', return_tensors='pt')
    print(f'input: {input}')

    # 5. 把数据喂给模型, 获取结果
    my_model.eval()
    logits = my_model(input.input_ids).logits
    print(f'logits: {logits.shape}')       # [1, 18, 32]

    # 6. 处理预处理结果, 并显示.
    # 6.1 把输入id 转回 token形式(用于格式化)
    # 格式为: ['[CLS]', '我', '爱', '北', '京', '天', '安', '门', ',', '天', '安', '门', '上', '太', '阳', '升', '!', '[SEP]']
    input_tokens = my_tokenizer.convert_ids_to_tokens(input.input_ids[0])
    print(f'input_tokens: {input_tokens}')

    # 6.2 用zip()函数, 把 token 和 预测的标签概率分布组合到一起.
    outputs = []        # 格式为: [('我', 'O'), ('爱', 'O'), ('北京', 'LOC')...]
    # 遍历每个token(18个) 和 预测的概率标签分布:  [18, 32]
    for token, value in zip(input_tokens, logits[0]):
        # 过滤掉特殊字符.
        if token in my_tokenizer.all_special_tokens:
            continue
        # 走到这里, 说明不是特殊字符, 就获取: 32个标签的概率分布中, 最大概率标签的索引
        idx = torch.argmax(value).item()
        # print(token, idx)

        # 构建元组, 即: (token, 标签)
        outputs.append((token, config.id2label[idx]))

    # 7. 最终输出结果, 格式为: [('我', 'O'), ('爱', 'O'), ('北京', 'LOC')...]
    # 格式为: [('我', 'O'), ('爱', 'O'), ('北', 'B-address'), ('京', 'I-address'), ('天', 'I-address'), ('安', 'I-address'), ('门', 'I-address'), (',', 'O'), ('天', 'B-address'), ('安', 'I-address'), ('门', 'I-address'), ('上', 'O'), ('太', 'O'), ('阳', 'O'), ('升', 'O'), ('!', 'O')]
    print(f'outputs: {outputs}')


# todo 7.测试代码
if __name__ == '__main__':
    # 1. 测试Transformers的自动模型方式处理NLP任务 → 情感分类
    # dm01_text_classification()

    # 2. 测试Transformers的自动模型方式处理NLP任务 → 特征提取
    # dm02_feature_extraction()

    # 3. 测试Transformers的自动模型方式处理NLP任务 → 完形填空
    # dm03_fill_mask()

    # 4. 测试Transformers的自动模型方式处理NLP任务 → 阅读理解
    # dm04_question_answering()

    # 5. 测试Transformers的自动模型方式处理NLP任务 → 文本摘要
    # dm05_summarization()

    # 6. 测试Transformers的自动模型方式处理NLP任务 → NER
    dm06_ner()

总结QA:

以文本分类任务为例,说一下自动模型方式实现情感分类的流程?

先从 transformers 导入 AutoTokenizer 和 AutoModelForSequenceClassification。情感分类用 chinese_sentiment。后面五步是:加载分词器,加载带分类头的模型,把句子编码成张量,把张量送进模型,打印分类头的输出。

python 复制代码
from transformers import AutoTokenizer, AutoModelForSequenceClassification

# 1. 加载 tokenizer
my_tokenizer = AutoTokenizer.from_pretrained('./chinese_sentiment')
# 2. 加载模型
my_model = AutoModelForSequenceClassification.from_pretrained('./chinese_sentiment')
# 3. 准备文本转张量并编码
msg_tensor = my_tokenizer.encode(text=message, return_tensors='pt', padding=True, truncation=True, max_length=20)
# 4. 给模型喂数据
output1 = my_model(msg_tensor)
# 5. 输出结果
print('情感分类模型头输出output1--->', output1)

encode 只返回 input_ids。课件这一步写成 padding=True、max_length=20,路径写成 ./chinese_sentiment。课堂代码路径是 pretrainedmodel/chinese_sentiment,padding='max_length'、max_length=5。课堂里还准备了一条不带 return_tensors 的列表,再用 torch.tensor 包成二维张量,实际送进模型的是这份,喂之前调用 my_model.eval()。

自动模型方式可以完成多种 NLP 任务,说出下面类的具体含义?

类 含义
AutoTokenizer 自动加载和当前模型匹配的分词器
AutoModel 自动加载模型本体,不带任务头,特征提取用它
AutoModelForSequenceClassification 句子级分类,例如情感分类
AutoModelForMaskedLM 掩码语言建模,完形填空
AutoModelForQuestionAnswering 阅读理解,从原文里抽出答案
AutoModelForSeq2SeqLM 序列到序列,例如文本摘要
AutoModelForTokenClassification 每个 token 一个标签,例如 NER

164. 具体模型方式_完形填空任务(掌握)

具体模型方式和自动模型几乎一致,只是加载模型和分词器的 API 稍有不同,所以这里只演示完形填空这一个案例。除了第三方库不一样,调用方式稍有不同,其它都一样。一次调用只能预测一个 [MASK],多个 [MASK] 要循环。

python 复制代码
"""
案例:
    具体模型方式完成 NLP任务 → 其方式 和 自动模型几乎一致.
    只是在加载模型 和 分词器的API上稍有不同, 所以这里我们只演示 1个 案例.

细节(记忆):
    除了第3方库不一样, 调用方式稍有不同, 其它都一模一样.
"""

# 导包
import os

import torch                  # Pytorch深度学习框架, 张量计算, 自动求导...

# BertTokenizer: 自动加载和模型匹配的分词器(即: 通用的分词器加载类)
# BertModelForMaskedLM: 用于 掩码语言建模的 模型.
from transformers import BertTokenizer, BertForMaskedLM


# todo 1. 完形填空 → 一次只能预测1个[MASK], 如果要多个[MASK], 则必须通过循环实现.
def dm01_fill_mask():
    # 0. 定义遍历, 记录: 模型名.
    model_name = os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'chinese-bert-wwm')

    # 1. 加载Tokenizer分词器.
    my_tokenizer = BertTokenizer.from_pretrained(model_name)

    # 2. 加载模型.
    my_model = BertForMaskedLM.from_pretrained(model_name)

    # 3. 文本转张量.
    input = my_tokenizer.encode_plus('我想明天去[MASK]家吃饭', return_tensors='pt')
    # print(f'input: {input}')

    # 4. 给模型喂数据, 获取模型输出.
    my_model.eval()        # 评估模式
    output = my_model(**input)

    # 5. 获取结果, 并打印.
    # print(f'output: \n{output}')
    # print(f'output.logits: {output.logits.shape}')        # [1, 11, 21128]

    # [MASK]位置的索引: [1, 11, 21128] → [11, 21128] → [21128]
    mask_predict_idx = torch.argmax(output.logits[0][6]).item()
    print(f'[MASK]位置的索引: {mask_predict_idx}')

    # 打印概率最高的字 → '你'
    print(f'概率最高的字的: {my_tokenizer.convert_ids_to_tokens(mask_predict_idx)}')


# todo 2. 测试代码
if __name__ == '__main__':
    # 1. 具体模型方式 → 演示: 完形填空.
    dm01_fill_mask()

总结QA:

以完型填空任务为例,说一下具体模型方式实现的流程?

先导入 torch,再从 transformers 导入具体类。完形填空用 BertTokenizer 和 BertForMaskedLM。课件这一步的模型名写成 bert-base-chinese,导入里还写了 BertModel。课堂代码用的是 chinese-bert-wwm,只加载分词器和带掩码头的模型。

python 复制代码
import torch
from transformers import BertTokenizer, BertForMaskedLM, BertModel

# 1. 加载 tokenizer
modename = "bert-base-chinese"
my_tokenizer = BertTokenizer.from_pretrained(modename)
# 2. 加载模型
my_model = BertForMaskedLM.from_pretrained(modename)
# 3. 文本转张量
input = my_tokenizer.encode_plus('我想明天去[MASK]家吃饭', return_tensors='pt')
# 4. 给模型送数据提取特征
output = my_model(**input)
# 5. 取概率最高
mask_pred_idx = torch.argmax(output.logits[0][6]).item()

encode_plus 交出 input_ids。**input 把这份字典拆开送进模型。logits[0][6] 是 [MASK] 那个位置在词表上的一行分数,argmax 取出概率最高的编号。

在 Transformers 库,说一说自动模型(AutoModel)和具体方式两者的区别和联系?

两者都能做 NLP 任务。调用分成三层,从上到下是管道、自动模型、具体模型,底下接到 BERT、GPT-2、T5、DistilBERT、GPT-3、PEGASUS 这些预训练模型。

自动模型按任务类型,让 Transformers 统一管理各厂商的预训练模型。完形填空只要这家模型支持这个任务,框架就把输入和输出规定好,换模型时类名不用改。

具体模型是开发者自己点名某一个预训练模型,例如直接写 BertForMaskedLM。同一件完形填空,各厂商的输入和输出可以不一样,由开发者自己把控。这样更灵活,写起来也更费劲。

可以把它想成 Hugging Face 先规定一个函数,名字叫 my_calc(),功能要覆盖加减乘除、正弦余弦、开方、指数。张三、李四各自按这个规则写实现。自动模型按统一的函数名去调用。具体模型是直接打开某一家的实现代码。

练习题:

阅读下面程序,input_ids、token_type_ids、attention_mask 代表什么意思?

python 复制代码
def dm03_test_fill_mask():
    modename = "chinese-bert-wwm"
    # modename = "bert-base-chinese"
    my_tokenizer = AutoTokenizer.from_pretrained(modename)
    my_model = AutoModelForMaskedLM.from_pretrained(modename)

    input = my_tokenizer.encode_plus('我想明天去[MASK]家吃饭.', return_tensors='pt')
    print('input--->', input)

    my_model.eval()
    output = my_model(**input)
    print('output.logits--->', output.logits.shape)  # [1, 12, 21128]

    mask_pred_idx = torch.argmax(output.logits[0][6]).item()
    print('打印概率最高的字:', my_tokenizer.convert_ids_to_tokens(mask_pred_idx))

这句话带句号,编码后一共 12 个位置,所以 input_ids 是 [1, 12]。打印出来的字典是:

text 复制代码
input---> {'input_ids': tensor([[101, 2769, 2682, 3209, 1921, 1343, 103, 2157, 1391, 7649, 119, 102]]),
           'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]),
           'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}

input_ids 是句子从文字变成编号之后的结果。101 是 [CLS],103 是 [MASK],102 是 [SEP]。token_type_ids 是句子分类信息,这里只有一句话,所以全是 0。attention_mask 是掩码,1 是真实字,0 是填充,这里没有填充,所以全是 1。

logits 是 [MASK] 的预测结果,也就是词表上的分类分数,形状是 [1, 12, 21128]。convert_ids_to_tokens 把编号变回字。这次概率最高的字是「她」。

阅读下面程序,错误的地方是?

  • A. 加载分词器:my_tokenizer = AutoTokenizer.from_pretrained(modename)
  • B. 加载模型:my_model = AutoModelForMaskedLM.from_pretrained(modename)
  • C. 文本转张量:input = my_tokenizer.encode_plus('我想明天去[MASK]家吃饭.', return_tensors='pt ')
  • D. 送数据:output = my_model(input)

答案:D。模型要的是把 encode_plus 返回的字典拆开,写成 output = my_model(**input)。my_model(input) 把整份字典当成一个位置参数送了进去。

拓展QA:

NLP 只有课上这六种应用场景吗?Transformers 用六个类来调用,再遇到别的场景,怎么确定用哪个类?是看下载下来的预训练模型的 README 吗?

NLP 的场景比这六项多。情感分类、特征提取、完形填空、阅读理解、文本摘要、NER,是管道和自动模型已经配好任务头、课上拿来演示的六件。很多业务名字不同,输入输出的形状却落在同一类里。整句一个标签,情感、垃圾邮件、主题、意图识别,都用 AutoModelForSequenceClassification。每个字一个标签,NER、词性标注,都用 AutoModelForTokenClassification。把长文写成另一段文字,摘要、翻译、改写,都用 AutoModelForSeq2SeqLM。按下一个词往下续,GPT 那种续写和对话,用 AutoModelForCausalLM。只要每个字的向量、任务头自己接,用 AutoModel。

选类先看这件任务的输入和输出,再去对模型自己声明的类。整句进、一个类别出,用句子分类。每个 token 一个标签,用 token 分类。在 [MASK] 上从词表里挑一个字,用掩码语言模型。从原文里切出一段答案,用问答。输入一段、新生成一段,编码器-解码器用 AutoModelForSeq2SeqLM,只靠解码器续写用 AutoModelForCausalLM。

README 能用。模型页面和下载目录里的说明,通常带一段示例,里面的 from_pretrained 就是作者推荐的那个类。更直接的是模型目录里的 config.json,architectures 写着 BertForMaskedLM 这种名字,它和 AutoModelForMaskedLM、BertForMaskedLM 是同一颗任务头。模型卡片上的 pipeline_tag 则对应管道里的任务名,例如 fill-mask、text-classification。

对不上任何现成任务头时,用 AutoModel 取出向量,自己加一层分类头再微调。

类已经选对了,不看模型底层,怎么知道这个 Auto 类返回什么?是有规范,还是 Auto 永远就那几步?不同模型的接口数量和作用,按理不该一样。

跨任务看,接口确实不一样。句子分类交出每个类别的分数,完形填空交出词表上的分数,阅读理解交出每个位置的起止分数,摘要和续写除了分数还有 generate()。特征提取没有这些任务头,交出的是向量。所以不存在一份对所有模型都相同的返回值。

同一颗任务头内部,Hugging Face 把字段名规定死了。AutoModelForMaskedLM 不管最后加载的是 BERT 还是别的掩码模型,forward 返回的都是带 logits 的对象,形状是 [句数, 词数, 词表大小]。句子分类的 logits 是 [句数, 类别数],token 分类是 [句数, 词数, 标签数],问答是 start_logits 和 end_logits,形状都是 [句数, 词数]。本体模型是 last_hidden_state,形状 [句数, 词数, 隐藏维度],有的模型另外给 pooler_output。传入 labels 时,多数任务头还会多一个 loss。

课上反复出现的加载、编码、eval、前向、取一个字段,是推理骨架,每一步都在。取哪一个字段、这个字段的最后一维代表什么,跟着任务头变,不跟着厂商变。这就是自动模型把各家收成同一个类的原因。具体模型如果离开这套字段名,就得对着那一家的实现自己读。

不翻底层时,有三处能对上这份规范。直接 print(output),返回对象会把字段名打出来。这个 Auto 类的文档写着返回类型,例如 MaskedLMOutput、SequenceClassifierOutput。字段的最后一维对模型目录里的 config.json:vocab_size 是词表,num_labels 和 id2label 是类别。

各家仍会有差别,差别在可选项上。BERT 有 token_type_ids,GPT 系没有这段。pooler_output 不是每个模型都有。hidden_states 和 attentions 要在调用时加上 output_hidden_states=True 才会出现。这些不影响那颗任务头必有的那个主字段。

换一个只知道大致作用的模型,Auto 实际怎么用起来?id2label 这种要不要专门引入 AutoConfig?其他 AutoXxx 用不用、怎么用?

用到哪一个类,文件里才写哪一行 import。AutoConfig 没有被规定成每段代码的必选项。情感分类、完形填空、摘要都没有单独加载它。NER 用了,是因为 argmax 得到的是标签编号,要查 id2label 才能变成 B-address 这种名字。

下载下来的模型目录是一套固定角色的文件,换模型时先看目录,再决定写哪几个 Auto。

text 复制代码
模型目录/
├── config.json                模型配置。architectures、vocab_size、hidden_size、num_labels、id2label、label2id
├── pytorch_model.bin          权重。和下面的 safetensors 通常只保留一种
├── model.safetensors          权重。新格式,加载时不执行代码
├── tokenizer_config.json      用哪一种分词器,以及填充、截断的默认设置
├── vocab.txt                  WordPiece 词表。BERT、中文 BERT 用这份
├── vocab.json                 BPE 词表。GPT、BART、RoBERTa 用这份
├── merges.txt                 BPE 的合并规则,和 vocab.json 成对出现
├── special_tokens_map.json    [CLS]、[SEP]、[MASK]、<s>、</s> 这些特殊标记
├── tokenizer.json             快速分词器。有它时 AutoTokenizer 会优先用
├── generation_config.json     generate() 的默认长度、束搜索。摘要和续写才常见
└── README.md                  模型卡片。任务说明和一段调用示例

config.json 是模型的配置。architectures 决定加载哪颗任务头,vocab_size、hidden_size、num_labels、id2label、label2id 都在这里。pytorch_model.bin 或 model.safetensors 是权重。tokenizer_config.json 记录用哪一种分词器。BERT 系的词表在 vocab.txt,GPT、BART 那套 BPE 是 vocab.json 加 merges.txt。special_tokens_map.json 写着 [CLS]、[SEP]、[MASK] 这些特殊标记。README.md 是模型卡片,里面通常有一段可以直接照着改路径的示例。

一次推理通常只出现两个 Auto。AutoTokenizer.from_pretrained(目录) 读分词器那几份文件,把句子变成 input_ids。AutoModelForXxx.from_pretrained(目录) 读 config.json 和权重,Xxx 按上一问的任务形状来选。后面仍是编码、eval、前向或 generate、取主字段。

AutoConfig 在你要单独看配置的时候再加载,AutoConfig.from_pretrained(目录) 只读 config.json,不把权重读进内存。模型已经 from_pretrained 之后,同一份配置就在 my_model.config 上,my_model.config.id2label[idx] 和 config.id2label[idx] 查的是同一张表。课上 NER 把它写成单独一行,是为了看清这张表从配置来。分类模型如果只打印 logits、再用自己的列表对标签,也可以不碰它。

其他 Auto 按任务加,不加全套。只要向量用 AutoModel。续写用 AutoModelForCausalLM。generate() 如果要改默认长度,目录里有时还有 generation_config.json,不写这行也会用模型自带的默认值。图像、语音的 AutoProcessor 不在这门课的文本路径里。

类 作用
AutoConfig 只读 config.json。查 architectures、id2label,不加载权重
AutoTokenizer 按分词器配置把文字变成 input_ids
AutoModel 加载模型本体,不带任务头,返回 last_hidden_state
AutoModelForSequenceClassification 整句一个标签。情感、主题、意图
AutoModelForTokenClassification 每个 token 一个标签。NER、词性
AutoModelForMaskedLM 完形填空。[MASK] 位置上的词表分数
AutoModelForQuestionAnswering 阅读理解。每个位置的 start_logits、end_logits
AutoModelForSeq2SeqLM 摘要、翻译、改写。前向给分数,generate() 写出新编号
AutoModelForCausalLM 续写、对话。按下一个词生成
AutoProcessor 图像、语音等多模态输入的打包。文本任务用不到

想改模型配置,比如把学习率调一下,是改 Transformers 库,还是改模型目录里的 config?

学习率不写进 config.json,也不改 Transformers 的源码。它是训练时优化器的步长,和深度学习里一样,写在优化器上。

python 复制代码
optimizer = torch.optim.Adam(my_model.parameters(), lr=1e-4)

这行写在你自己的训练脚本里,放在 from_pretrained 之后。my_model 是已经读进内存的那个对象,parameters() 拿出它的权重,Adam 按 lr 去更新这些内存里的数。pretrainedmodel 目录里的 config.json、权重文件、Transformers 库的源码都不用打开改。

下载好的模型里没有一套正在跑的反向更新。它带的是网络结构和已经训好的权重。调用模型时走的是前向:把 input_ids 算成 logits 或损失。反向是 PyTorch 的自动求导,要等你自己写 loss.backward() 才会发生。optimizer.step() 再按 Adam 的规则改权重。只写上面那一行、不调用这两步,权重还是刚加载时的数。课上现在的 eval() 加前向,就是只做推理,优化器不会插进去改结果。

config.json 记的是模型结构。层数、隐藏维度、词表大小、类别数、id2label 在这里。加载时 from_pretrained 会读它,读完就挂在 my_model.config 上。手改 json 里的 hidden_size、num_hidden_layers,而权重还是原来的形状,加载会对不上。

库的代码不动。学习率改优化器。结构和标签看 config.json,读进内存之后用 my_model.config。

要更新模型的常见参数,分别改哪里?

Trainer、save_pretrained 课上还没讲,不是漏掉的步骤。下面只使用已经学过的加载、config 和优化器。

想改的 改哪里 怎么做
学习率 优化器 torch.optim.Adam(my_model.parameters(), lr=1e-4)。数字只影响这一次训练
训练轮数 自己的循环 for 跑几遍数据,模型对象上没有这个字段
批大小 组 batch 的地方 一次送进模型的句数,在你准备张量时决定
最大长度、填充、截断 分词器这次的调用 encode_plus(..., max_length=20, padding='max_length', truncation=True)
摘要长度、束搜索 generate() 的参数 my_model.generate(input_ids, max_length=128, num_beams=4)。不写就用模型自带的默认值
标签名 my_model.config.id2label 来自 config.json。这次运行里给 my_model.config.id2label[idx] 换名字,立刻生效
类别数、层数、隐藏维度 一般不改已下载的模型 这些数字决定权重的形状。只改 json,权重对不上,加载会失败

这一轮在内存里改 my_model.config 或调用参数,只对这次运行有效。把改动写回模型目录,要等后面的保存方法,现在还没用到。

上面这张表不全是微调。微调是拿已经训练好的权重,换上自己的数据,用较小的学习率继续训练,让权重跟着新任务更新。学习率、轮数、批大小是做这件事时要设的数。最大长度、generate() 的长度和束搜索、标签名字,只影响这一次怎么调用、怎么读结果,权重不动,所以还不是微调。层数和隐藏维度动了,等于换了一副和原权重对不上的结构,也不是微调。

165. 迁移学习_中文分类案例_数据加载(掌握)

这一节做中文语料的评论分类。评论是二分类:正面评价记为 1,负面评价记为 0。

用迁移学习来做。预训练的 BERT 负责提取文本特征,后面再接全连接层和 softmax,把特征分成这两类。BERT 的权重来自已经训好的模型,新接上的全连接层按这个评论分类任务来学习。

python 复制代码
"""
案例:
    NLP任务 → 中文分类 案例.
任务介绍:
    直接加载预训练模型进行输入文本的特征表示, 后接自定义网络进行微调输出结果.
数据介绍:
    数据文件有3个, 分别是: train.csv, test.csv, validation.csv 三个文件的数据格式都是一样的.
        label                   text
        标签(1:好评,0:差评)        文本
"""

# 导包
import os

import torch                                 # Pytorch深度学习框架, 提供张量计算 和 自动微分功能.
import torch.nn as nn                        # neural_network, 神经网络, 例如: 线性层, 卷积层...
from torch.utils.data import DataLoader      # 数据加载器, 支持: 批量处理数据
from datasets import load_dataset            # HuggingFace的数据集加载工具, 可以加载: 本地文件, 或者公开数据源(GLUE, SQuAD).
import time                                  # 时间模块
from tqdm import tqdm                        # 导入进度条库
from transformers import BertTokenizer, BertModel   # 导入BERT相关组件(中文分词器, 预训练的BERT模型)
from torch.optim import AdamW                # 导入AdaW优化器, 内置权重衰减 → 防止过拟合...
from rich import print                       # 终端打印美化.

# 使用GPU或者MPS(苹果的M系列芯片)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# todo 1. 加载分词器.
my_tokenizer = BertTokenizer.from_pretrained(os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'bert-base-chinese'))
# todo 2. 加载模型.
my_pre_model = BertModel.from_pretrained(os.path.join(os.path.dirname(__file__), 'pretrainedmodel', 'bert-base-chinese')).to(device)


# todo 3.定义函数, 加载数据集. 即: 从CSV文件加载 训练集, 测试集, 验证集数据.
def dm_file2dataset():
    # 前言: 加载数据有两种方式, 我用 训练集给大家演示一下, 其它都一样.
    # ------------------------------------------- 加载训练数据集 -------------------------------------------
    # 思路1: path 设定为 目标文件的 文件夹路径.
    # 1. load_dataset()方式, 加载csv格式的训练数据.
    # train_dataset = load_dataset(path=os.path.join(os.path.dirname(__file__), 'data'), data_files='train.csv', split='train')

    # 思路2: path 设定为 文件类型.
    train_dataset = load_dataset(path='csv', data_files=os.path.join(os.path.dirname(__file__), 'data', 'train.csv'), split='train')

    # 2. 打印训练集的基本信息.
    print(f'train_dataset: {train_dataset}')
    print(f'训练数据的长度: {len(train_dataset)}')  # 9600
    print(f'训练数据(第1条): {train_dataset[0]}')
    print(f'训练数据(前3条): {train_dataset[:3]}')

    # ------------------------------------------- 加载测试数据集 -------------------------------------------
    test_dataset = load_dataset(path='csv', data_files=os.path.join(os.path.dirname(__file__), 'data', 'test.csv'), split='train')
    print(f'test_dataset: {test_dataset}')
    print(f'测试数据集的长度: {len(test_dataset)}')  # 1200
    print(f'测试数据(前3条): {test_dataset[:3]}')


    # 加载验证集数据集.
    valid_dataset = load_dataset(path='csv', data_files=os.path.join(os.path.dirname(__file__), 'data', 'validation.csv'), split='train')
    print(f'valid_dataset: {valid_dataset}')
    print(f'验证数据集的长度: {len(valid_dataset)}')  # 1200
    print(f'验证数据(前3条): {valid_dataset[:3]}')

    # 3. 返回结果(数据集对象)
    return train_dataset, test_dataset, valid_dataset


# todo n.测试代码.
if __name__ == '__main__':
    # 1.演示: 加载数据集.
    dm_file2dataset()

166. 迁移学习_中文分类案例_数据预处理(掌握)

python 复制代码
# todo 5. 获取数据加载器.
def get_dataloader():
    # 1. 加载训练数据集.
    train_dataset = load_dataset(path='csv', data_files=os.path.join(os.path.dirname(__file__), 'data', 'train.csv'), split='train')
    # 2. 将上述的数据集对象, 封装成数据加载器.
    my_dataloader = DataLoader(
        dataset=train_dataset,       # 输入的数据集对象
        batch_size=8,                # 批次大小(即: 每批包含的样本数)
        shuffle=True,                # 训练数据集是否打乱.
        drop_last=True,              # 训练数据集是否删除最后1个批次(批次数不足1个批次的数据)
        collate_fn=collate_fn1,      # 指定数据整理函数, 即: 每批次的数据都要被这个函数处理.
    )

    # 3. 返回数据加载器.
    return my_dataloader


# todo n.测试代码.
if __name__ == '__main__':
    # 1.演示: 加载数据集.
    # dm_file2dataset()

    # 2. 创建数据加载器, 并测试.
    train_dataloader = get_dataloader()
    # 获取一批次数据, 看看效果.
    for batch in train_dataloader:
        print(batch)
        break        # 只看1批数据即可.

167. 迁移学习_中文分类案例_模型搭建(掌握)

python 复制代码
# todo 6. 自定义下游模型, 基于BERT的文本分类模型.
class AiModel(nn.Module):
    # 1. 初始化函数
    def __init__(self):
        # 1. 初始化父类成员.
        super().__init__()
        # 2. 定义全连接层, 目的: 把bert模型的768维 → 2维(2分类)
        self.linear = nn.Linear(768, 2)

    # 2. 前向传播函数.
    def forward(self, input_ids, token_type_ids, attention_mask):
        """
        前向传播函数, 处理数据, 获取最终的2分类结果.
        :param input_ids: 文本的数字编码, 形状: [batch_size, max_len]
        :param token_type_ids: 句子类型标记, 形状: [batch_size, max_len]
        :param attention_mask: 注意力掩码, 形状: [batch_size, max_len]
        :return:
        """

        # 1. 不计算BERT预训练模型的梯度(冻结参数)
        # 作用: 避免更新预训练模型参数, 仅训练自定义模型(分类层)的参数.
        with torch.no_grad():
            # 2. 获取BERT模型的输出.
            bert_output = my_pre_model(
                input_ids=input_ids,
                token_type_ids=token_type_ids,
                attention_mask=attention_mask,
            )

        # 3. 使用bert的 pooler_output 进行分类.
        # bert_output.pooler_output: [batch_size, 768]
        output = self.linear(bert_output.pooler_output)

        # 4. 返回处理后的分类结果
        return output


# todo 7. 测试模型结构函数.
def use_AiModel():
    # 1. 获取数据加载器.
    my_dataloader = get_dataloader()
    # 2. 实例化模型 → 创建基于BERT的分类模型.
    # 3. 将模型移动到GPU中.
    my_model = AiModel().to(device)
    # 4. 迭代数据并测试输出, 细节: 这里我们仅仅测试1批次的数据, 避免长时间运行.
    for input_ids, token_type_ids, attention_mask, labels in my_dataloader:
        # 4.1 把上述的数据移动到GPU中.
        input_ids = input_ids.to(device)
        token_type_ids = token_type_ids.to(device)
        attention_mask = attention_mask.to(device)
        labels = labels.to(device)

        # 4.2 前向传播, 计算结果.
        output = my_model(input_ids, token_type_ids, attention_mask)
        print(f'output: {output.shape}')        # 形状: [batch_size, 2]
        break        # 我们仅仅测试1批次的数据, 避免长时间运行.


# todo n.测试代码.
if __name__ == '__main__':
    # 1.演示: 加载数据集.
    # dm_file2dataset()

    # # 2. 创建数据加载器, 并测试.
    # train_dataloader = get_dataloader()
    # # 获取一批次数据, 看看效果.
    # for batch in train_dataloader:
    #     print(batch)
    #     break        # 只看1批数据即可.


    # 3. 测试模型结构.
    # 会报警告, 因为你当前用的PyTorch版本没有编译'flash_attention'加速功能.  'flash_attention'是斯坦福大学提出的一种 高效注意力计算优化技术.
    # 如果你追求极致的性能, 可以尝试: 安装编译了'flash_attention'的PyTorch版本, 但是步骤复杂: GPU, CUDA版, 从源码编译PyTorch, 且windows环境配置难度更高.
    use_AiModel()
相关推荐
女生也可以敲代码1 小时前
div上有红色下划线报错:JSX 元素隐式具有类型 “any“,因为不存在接口 “JSX.IntrinsicElements“。ts(7026)
前端
申君健24864182772021 小时前
ArkTS 速成指南(写给有 TypeScript / 前端经验的开发者)
前端
代码猎人1 小时前
前端怎么落地工程化规范
前端
阶进遐1 小时前
浏览器工具自制指南·对话导出(1):需求与选型
前端
朱涛的自习室1 小时前
预算 1000 美金的开源软件,DeepSeek 只花 1 美金就做完了(Day 6)
android·前端·人工智能
excel2 小时前
JavaScript 中 substr、substring 和 slice 的区别
前端
范同学~2 小时前
固定表头+第一列+最后一列
java·服务器·前端
zttbee2 小时前
npm install卡顿
前端·npm·node.js
Amos_Web2 小时前
Rspack 源码解析(二十七):AsyncWasmParserAndGenerator 如何把二进制变成异步模块
前端·rust