从RNN到Prompt微调:生物医学NLP零基础学习路线与实战

从RNN到Prompt微调:生物医学NLP零基础学习路线与实战 在医学文献、电子病历、临床指南、基因注释这些资料里藏着一大批高质量文本数据。生物医学背景的同学往往比一般工程师更懂这些数据的含义却在面对算法模型时容易卡住。很多医学生想转AI方向第一反应是去刷公开课刷到RNN就开始吃力再往后看到Transformer直接放弃。这套路本身就有问题——零基础转NLP更需要一条清晰的学习路线而不是资料合集。这篇文章就围绕一条主线来写RNN → Transformer → Prompt微调 → 生物医学NLP实战。读完之后你会理解NLP中的序列建模为什么从循环网络走向注意力机制大模型时代为什么大家开始谈Prompt微调以及如何把这类技术落地到医学文本场景。文章以PyTorch和Hugging Face生态为示例环境重点讲思路配套的代码可以直接改写复现。1. 背景与核心概念1.1 生物医学背景转AI为什么首选NLP先问一个问题医学信息里结构化数据占比高还是非结构化文本占比高答案是后者。电子病历中的主诉、现病史、诊断意见文献中的方法学描述影像报告中的文字结论药物说明中的相互作用信息这些都是文本。想让计算机辅助医生处理这些信息自然要依赖NLP。所以生物医学背景的人转AINLP是最自然的切入点。它不需要像计算机视觉那样大量依赖图像处理经验核心工具是语言——生物医学专业的同学本身就擅长语言和结构化阅读。另一个原因是NLP技术栈相对成熟尤其从预训练模型出现之后开发者不需要自己设计复杂特征工程直接借助开源模型就能处理真实任务。1.2 NLP模型演进的主线NLP模型经历了一条清晰的演进路线从基于规则到统计机器学习再到深度学习的循环神经网络RNN接着是Transformer最后是预训练大模型。本文重点讲后三个环节因为它们构成了从「小模型定制」到「大模型微调」的完整逻辑。RNN出现是为了解决序列建模问题。语言是连续的前一个词会影响后一个词RNN通过循环结构保留历史信息。但RNN的长距离依赖能力有限训练也难以并行化。Transformer用注意力机制取代循环使得任意两个位置的词都能直接建立关联并且可以并行计算。大模型则是在大量文本上预训练再通过Prompt微调适配具体任务。理解了这条主线以后再学BERT、GPT、LLaMA都会轻松很多。1.3 生物医学NLP的典型任务生物医学NLP不是单一任务而是一个任务族。目前常见的方向包括医学命名实体识别NER识别文本中的疾病、药物、症状、解剖部位等实体。关系抽取判断两个实体之间是否存在治疗关系、副作用关系。文本分类对病历、文献、问题进行分类比如分诊、疾病类型判断。医学问答根据医学知识回答患者或医生的问题。临床概念标准化把口语化描述映射到标准ICD编码或医学本体术语。这些任务并不需要全部掌握。新手可以从一个NER任务或文本分类任务入手因为数据集好构造评估指标直观技术栈也和通用NLP一致。后面的实战环节我会以医学NER为例带大家走一遍完整流程。2. 环境准备与版本说明2.1 基础环境本文所有示例代码基于Python和PyTorch。建议使用虚拟环境管理依赖避免系统环境被不同项目污染。可以按如下方式创建python -m venv nlp_med source nlp_med/bin/activate # Linux/macOS # 或 nlp_med\Scripts\activate # Windows具体Python版本建议在3.8到3.10之间。不同时间安装的PyTorch版本差异较大下面命令表示安装常见稳定版本具体版本号以你当前环境的官方源为准pip install torch torchvision pip install transformers datasets evaluate scikit-learn pandas如果计算机有NVIDIA显卡并希望用GPU加速需要单独安装匹配CUDA版本的PyTorch。建议到PyTorch官网选择对应命令不要盲目复制。2.2 需要用到的核心库transformersHugging Face生态的核心库预训练模型、Tokenizer、Trainer都从这里导入。datasets加载和预处理数据集。peft用于LoRA等高效微调方法。torch/pytorch深度学习框架。scikit-learn计算评估指标。有些同学会问一定要GPU吗做小规模实验时CPU也可以跑小模型比如BERT-base可能很慢但BioBERT这种规模的模型在CPU上也能跑只是时间较长。建议先用小规模数据验证流程再上GPU。2.3 示例项目结构为方便后续操作建议项目按以下结构组织biomedical_nlp/ ├── data/ # 原始数据和预处理后数据 ├── models/ # 训练好的模型存储位置 ├── scripts/ │ ├── train_ner.py # 训练脚本 │ ├── predict.py # 推理脚本 │ └── preprocess.py # 数据预处理脚本 └── requirements.txt # 依赖文件这种结构并不复杂但对于医学生开始写代码很重要——数据和代码隔离脚本职责明确避免后期项目变大时找不到文件。3. RNN原理拆解循环网络为什么能处理序列3.1 为什么需要RNN在传统神经网络中输入和输出都是定长的比如把一张图片识别成类别。但文本是变长序列而且每个词的理解依赖上下文。比如「cell」在生物医学文本里可能是「细胞」也可能是「牢房」甚至「电池」。只有看了前后文才能判断。RNN的思路是在时间步上共享同一组权重每个时刻都接收当前输入和上一个时刻的隐状态输出当前隐状态再传递下去。这样网络就有了「记忆」。隐藏状态更新公式可以简单写成h_t tanh(W_ih * x_t W_hh * h_{t-1} b)其中x_t是当前词向量h_t是当前时刻的隐状态。这个公式不需要背得很熟但要理解信息会沿着时间轴传递。3.2 RNN最小示例人名分类以「人名分类」为例我们可以构建一个最简单的RNN分类模型。任务输入是一个人名字符序列输出它属于哪个语言群体或文化类别。这是一个非常经典的学习案例。先定义一个简化版本的数据结构# 脚本scripts/train_rnn_name_classifier.py import torch import torch.nn as nn # 定义字符表 CHARS abcdefghijklmnopqrstuvwxyz char2idx {c: i1 for i, c in enumerate(CHARS)} # 0 留作 padding NUM_CHARS len(char2idx) 1 class NameRNN(nn.Module): def __init__(self, input_size, hidden_size, num_classes): super().__init__() self.hidden_size hidden_size self.embedding nn.Embedding(input_size, hidden_size) self.rnn nn.GRU(hidden_size, hidden_size, batch_firstTrue) self.fc nn.Linear(hidden_size, num_classes) def forward(self, x): emb self.embedding(x) # [batch, seq_len, hidden] out, _ self.rnn(emb) # [batch, seq_len, hidden] last out[:, -1, :] # 取最后一个时刻的隐状态 return self.fc(last)这里用了GRU而不是原始RNNGRU和LSTM都是为了缓解梯度消失问题而设计的变体结构上增加了门控机制但入门阶段可以先把它们当作一种更稳定的RNN来看待。3.3 RNN存在什么问题RNN最大的问题有两个。第一长距离依赖能力不足。如果输入序列很长信息经过多个时间步传递后会衰减网络很难捕捉到句子开头和结尾之间的依赖关系。第二训练效率低。RNN必须按时间步顺序计算当前时刻依赖上一时刻的结果因此很难并行化。在动辄几千上万词的文本上这种代价很致命。此外RNN还面临梯度消失或梯度爆炸的问题。为了解决这个问题LSTM和GRU被设计出来但这仍然是沿着同一条技术路线的修补。Transformer走了一条不同的路——不做循环直接让所有位置两两交互。4. Transformer架构取代RNN的注意力革命4.1 自注意力机制的核心思想Transformer的核心是自注意力机制Self-Attention。它解决了一个很朴素的问题一个词在当前语境里应该重点关注哪些其他词比如句子「患者服用了阿司匹林后出现了胃部不适」模型在处理「阿司匹林」时可能最需要关注「胃部不适」这两个词因为它们之间存在副作用关联。自注意力机制做的就是为每个词计算一组权重表示它对其他词的注意力强度。具体做法先通过线性变换把每个词向量映射为三个向量——Query、Key、Value。然后计算某个词的Query与所有词的Key的点积经过缩放和softmax得到注意力权重再用权重对Value做加权求和。公式如下Attention(Q, K, V) softmax(QK^T / sqrt(d_k)) V这里的sqrt(d_k)是缩放因子作用是防止点积结果过大导致softmax进入饱和区。4.2 多头注意力与位置编码单个自注意力层只能捕捉一种关系模式但语言中的关系是多样的。Transformer把自注意力扩展到多个头——多头注意力Multi-Head Attention每个头学习不同的注意力模式最后拼接在一起。就像一位医生从症状、用药、检查结果、病史多个角度同时分析同一个病例。另一个关键机制是位置编码Positional Encoding。自注意力本身对位置不敏感把句子顺序打乱每个词的注意力结果仍然一样。这不符合语言规律因为「A打B」和「B打A」含义完全不同。Transformer通过加入位置编码曲线让模型感知词序。4.3 最小自注意力实现下面是一个简化版的自注意力实现用来理解核心计算流程。这个代码强调逻辑不追求最优性能import torch import torch.nn as nn import math class SelfAttention(nn.Module): def __init__(self, embed_dim): super().__init__() self.query nn.Linear(embed_dim, embed_dim) self.key nn.Linear(embed_dim, embed_dim) self.value nn.Linear(embed_dim, embed_dim) self.scale math.sqrt(embed_dim) def forward(self, x): # x: [batch, seq_len, embed_dim] Q self.query(x) K self.key(x) V self.value(x) scores torch.matmul(Q, K.transpose(-2, -1)) / self.scale weights torch.softmax(scores, dim-1) out torch.matmul(weights, V) return out实际Transformer还会加上残差连接、LayerNorm和前馈网络但这些组件都可以在后续学习过程中逐步理解。4.4 为什么最终是Transformer结合前面的分析Transformer胜出的原因可以归纳为三点。第一长距离依赖能力强。任意两个词之间的交互路径长度都是1理论上不存在信息衰减问题。第二并行性好。自注意力可以一次性计算序列中所有位置的关系不需要逐步迭代这会大幅提升训练速度。第三可扩展性强。Transformer架构可以堆更多层、扩大参数规模在大数据上持续取得收益。后来的BERT、GPT、T5以及各类大模型底座都是Transformer。这也是为什么你今天去看任何大模型的技术博客几乎都会先讲Transformer。5. Prompt微调大模型时代的适配方式5.1 从预训练到微调的范式转变在Transformer出现之后NLP进入预训练时代。先在海量无标注文本上训练一个语言模型再在有标签的小数据上进行微调。BERT、RoBERTa为代表的模型在这个范式下取得了巨大成功。传统做法的核心是「全量微调」把预训练模型当成一个初始化权重在目标任务数据上继续训练更新全部参数。这样做的优点是效果好但缺点也很明显——每个任务都要存一份完整模型训练成本高标注数据需求量也不小。5.2 Prompt微调为什么出现到了大模型时代模型的参数量动辄几十亿、上百亿全量微调变得不现实。研究者发现大型语言模型本身已经隐含了大量知识只要改变输入形式给它设计合适的提示模板Prompt模型就能以较少样本完成任务。Prompt微调的思路不是大规模修改模型参数而是通过设计提示文本让模型「回忆」出已经学会的能力。比如对于医学文本分类传统微调可能需要专门加一个分类头Prompt方式则