
简介这是一份关于主动学习与半监督学习的MATLAB算法例程面向机器学习初学者和需要处理标记数据稀缺场景的研究者集中展示了两类策略的典型实现。压缩包内仅1个MATLAB脚本文件大小9KB代码精简适合快速阅读和复用。已有195人学习。文件内部可能涵盖基于熵、边际距离等主动学习策略以及自训练、协同训练、低密度分离等半监督学习算法并涉及特征提取、降维、交叉验证等配套步骤。通过运行与调试这些例程读者能直观对比不同策略在数据不充分时的效果理解模型如何主动选择高价值样本或利用未标记数据提升性能为文本分类、图像识别等实际项目提供可直接参考的MATLAB实现思路。1. 主动学习与半监督学习的完整例程包这批代码能省下多少标注成本标注预算只有五千张模型精度卡在 82% 上不去——这是不少数据冷启动项目的真实开局。主动学习负责从一万张未标注数据中挑出最值得标注的五百张半监督学习让模型从海量未标注数据里自己挖掘监督信号。这个例程包把不确定性采样、查询委员会、伪标签、一致性正则等常见算法按可运行例程组织在一起适合标注预算有限、想快速横向对比多算法效果的从业者。下文按原理边界、跑通最小例程、换自己的数据、排坑、组装实验框架展开读完就能把包里的代码跑起来并用到自己的项目上。2. 先把分类边界理清主动学习与半监督学习各自解决什么问题2.1 主动学习查询循环不确定性采样为什么是默认起点主动学习把“训练模型”从一次性行为拆成一个循环在少量已标注样本上训练模型对未标注数据池做预测按查询策略计算每个样本的信息量把信息量最高的 n 个样本交给标注者标注完成后再并入训练集进入下一轮。这个循环省的是标注人力该做的训练迭代一步都不会少。查询策略是主动学习的灵魂。例程包里最常见的三种不确定性采样策略分别是最小置信度取每个样本预测概率的最大值值越小说明模型连最可能的类别都没把握优先挑出。边际采样取 top1 和 top2 概率的差值差值越小说明模型在这两个类别之间摇摆这类样本对分类边界最有区分价值。熵对完整概率分布计算信息熵熵越大预测分布越均匀模型的整体不确定性越高。三者的区别在于“不确定性”的定义口径。最小置信度实现最省甚至不需要完整概率向量但它只看 top1把其余类别的信息全丢弃了。边际采样用了 top2 的差值做分类任务时通常比最小置信度稳定。熵从信息论角度看最严谨但隐含假设模型输出的概率校准是好的这一点在深度模型上经常不成立所以熵策略搭配温度缩放或 label smoothing 会更稳。import numpy as np def query_by_uncertainty(proba, strategyentropy, n_query10): 从预测概率矩阵中挑出最不确定的 n_query 个样本索引。 参数 proba : ndarray, shape (n_samples, n_classes)模型预测概率 strategy : strleast_confidence / margin / entropy n_query : int本轮要挑出的标注样本数量 if strategy least_confidence: # 最高类别概率越低说明模型越没有把握 scores 1.0 - np.max(proba, axis1) elif strategy margin: # top1 与 top2 越接近说明样本落在类别边界附近 sorted_p np.sort(proba, axis1)[:, ::-1] scores 1.0 - (sorted_p[:, 0] - sorted_p[:, 1]) elif strategy entropy: # 熵越大类别分布越均匀不确定性越高 scores -np.sum(proba * np.log(proba 1e-12), axis1) else: raise ValueError(funknown strategy: {strategy}) # 分数降序排列取前 n_query 个样本索引 selected np.argsort(scores)[::-1][:n_query] return selected这段代码把三种策略合并到一个函数里是因为例程做对比实验时只需要切换 strategy 参数不用复制三套调用逻辑。参数上n_query 是每轮让标注者标多少张一般取初始标注量的 5%10% 比较合理太大浪费标注预算太小模型每轮学到的信息不够整体迭代轮数会被拉长。margin 策略在类别极不平衡的数据上要留个心眼如果某个类在训练集里先验概率就很低模型对这个类的预测天然犹豫margin 会把大量样本指向这一类反而让标注者疲于标注模型已经半懂的困难样本这时改成熵或结合多样性采样更稳。2.2 半监督学习两大支柱伪标签与一致性正则半监督学习不依赖额外人工标注从大量未标注数据里挖监督信号。主流实现里有两类方法长期并存。伪标签的思路最直接先用已标注数据训一个模型对未标注数据做预测把置信度高于阈值的预测结果当作伪标签并入训练集。优点是几乎不增加训练框架复杂度缺点是确认偏差——模型对某一类有系统性误判时错误的伪标签会在后续轮次自我强化。例程里常见的应对有两处提高阈值只留高置信度预测每轮只用有限数量的伪标签避免噪声在训练中占主导。一致性正则走另一条路同一个样本做两次不同的数据增强模型对两个增广视图的输出应当一致。代表性实现包括 Mean Teacher用教师网络的指数移动平均输出当回归目标以及 FixMatch弱增强视图产生伪标签强增强视图负责学习。一致性正则的优点是每轮训练几乎都能从全部未标注数据上拿到梯度缺点是计算量比普通训练更大结果对增强策略非常敏感。增强太弱正则项约束约等于零增强太强样本语义被破坏模型学到的其实是扭曲后的分布。import torch def generate_pseudo_labels(logits_unlabeled, threshold0.95): 为未标注样本生成伪标签并用掩码过滤低置信度样本。 logits_unlabeled : torch.Tensor, shape (N, C)未标注样本的模型输出 threshold : float伪标签保留的置信度阈值 返回: mask : bool 张量哪些样本的伪标签被保留 pseudo_label : 伪标签类别索引 max_prob : 每个样本的最高置信度 probs torch.softmax(logits_unlabeled, dim1) max_prob, pseudo_label torch.max(probs, dim1) # 只保留置信度不低于阈值的样本 mask max_prob threshold return mask, pseudo_label, max_probthreshold 是伪标签流程里最值得调的参数。取 0.9 时保留的伪标签多但混入噪声也大取 0.99 时干净但每轮能供给训练的信号太少。例程默认给 0.95 是折中实际使用时建议按验证集类别分布去扫描 0.9 到 0.99同时观察确认偏差有没有被压住。伪标签的确认偏差不是一次训练就能消除的它需要每一轮阈值和数据配比都稳定这也是为什么例程里几乎都会把 threshold 单独拎出来作为配置项。2.3 合用的时机与选型判断主动学习解决的是“标签从哪来”半监督解决的是“标签不够怎么办”。实际项目里两者不是二选一先跑半监督预训练让模型表征先行收敛再用主动学习的查询策略从剩余未标注数据里挑 hard sample 给人工标注标注结果回到训练集后进入下一轮半监督训练。这个串联流程在标注预算和时间成本都受限的项目里性价比最高。场景首选方案原因标注人力紧张每张标签都很贵主动学习查询策略保证每张标在刀刃上未标注数据海量标签几乎为零半监督学习一致性正则持续从无标签数据获取梯度长期迭代项目标注持续进行主动学习半监督串联SSL 预训练拿到好表征AL 再精挑 hard sample做选型时还要看数据本身的分布情况。如果未标注数据和已标注数据来自不同分布半监督的一致性正则会引入大量错误梯度。凡是依赖“模型自己预测自己”作为监督信号的方法都对分布偏移敏感。这也是为什么建议先在基于同分布的数据上把例程跑通再去碰真实场景中的脏数据。3. 用 Linux 解压命令跑通最小例程从 zip 包到第一条训练日志3.1 检查 zip 完整性并解压到独立目录拿到例程包我建议不要在 GUI 里双击解压。在 Linux 终端里先列出包内容确认 zip 有没有在传输过程中损坏再决定下一步。# 列出压缩包内文件列表并测试 zip 完整性 unzip -l active_learning_semi_supervised.zip unzip -t active_learning_semi_supervised.zip # 确认无损坏后解压到独立目录 unzip active_learning_semi_supervised.zip -d al_ssl_pkg cd al_ssl_pkg-t参数会扫描整个 zip 的 CRC 校验出现 “No errors detected” 才代表文件完整。如果报 CRC 错误说明文件下载或拷贝过程出了问题重新传一次比尝试修复更省时间。另一个常见现象是解压时提示要密码zip 文件头里有一个加密标志位有些打包工具误设了这个位但没有真正加密内容俗称伪加密。如果你能确认文件来源没有设置过密码用带文件管理能力的压缩工具打开后直接拖出文件通常能绕过这个伪加密标记而不是真的去猜一个不存在的密码。解压后的目录结构如果组织得规整通常长下面这样主动学习和半监督学习各占一个包examples/ 下放可直接运行的脚本configs/ 下放实验配置al_ssl_pkg/ ├── README.md ├── requirements.txt ├── configs/ │ ├── active_learning.yaml │ └── semi_supervised.yaml ├── al/ │ ├── query_strategies.py │ └── runners/ ├── ssl/ │ ├── pseudo_label.py │ ├── fixmatch.py │ └── mean_teacher.py ├── examples/ │ ├── run_active_learning.py │ └── run_semisupervised.py └── data/ ├── labeled/ └── unlabeled/如果解压后和这个结构不完全一致以包内 README 为准。重要的是确认 examples/ 与 requirements.txt 存在否则大概率不是能从入口直接跑的代码包得先自己补目录。3.2 创建虚拟环境并安装依赖这类算法例程大多基于 PyTorch 或 scikit-learn不同版本之间经常互踩最稳妥的办法是单独建一个虚拟环境python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install --upgrade pip pip install -r requirements.txt虚拟环境把项目依赖和系统 Python 隔离开避免“昨天能跑今天报错”的版本地狱。装完依赖后建议跑一个快速自检python -c import torch, sklearn; print(torch.__version__, sklearn.__version__)如果 import 阶段报错优先看 torch 和 CUDA 版本是否匹配。如果确定只在 CPU 上跑直接装 CPU 版 torch 能省掉大量驱动相关的排错时间。requirements.txt 里如果锁死了 torch 版本而你只有特定 CUDA 版本可以手动安装对应轮子包再回来装其他依赖。3.3 最小主动学习例程的参数怎么设包里的入口脚本如果是 run_active_learning.py最小可跑的命令大致是这样python examples/run_active_learning.py \ --dataset cifar10 \ --initial-label 200 \ --n-query 20 \ --rounds 5 \ --query entropy参数含义分别是initial-label 作为第一批随机采样的种子标注量n-query 是每轮要挑出来送标注的样本数rounds 是查询循环轮数query 指定用哪种查询策略。跑起来后日志里每个 round 会输出本轮挑选了多少样本、测试集准确率、未标注池剩余多少样本。判断例程是否正常主要看两个信号每个 round 的准确率有没有在往上走每轮挑选出来的样本是不是集中在模型之前判断错误的区域。如果准确率一直原地踏步优先检查数据预处理里是否把未标注池的样本也参与了训练这是最容易写错的地方。3.4 最小半监督例程的参数怎么设已标注数据很少、未标注数据很多时跑半监督脚本的命令类似python examples/run_semisupervised.py \ --method fixmatch \ --label-ratio 0.1 \ --unlabeled-ratio 0.8 \ --threshold 0.95 \ --epochs 30label-ratio 指示初始标注数据占比unlabeled-ratio 指示参与训练的未标注数据占比threshold 是伪标签置信度阈值。FixMatch 这类一致性正则实现训练中会额外打印 supervised loss 和 consistency loss 两个指标理想状态是两者同步下降。如果 consistency loss 掉得很快但 supervised loss 不动说明模型只是在增强视图之间保持一致并没有学到真实的分类边界这时候需要调小 unlabeled-ratio或者调高 threshold 过滤掉更多不可靠伪标签。4. 换到自己的数据上五个必调参数与三类数据组织方式4.1 已标注与未标注目录怎么组织这类例程包默认数据格式是图像分类常见的目录式组织每个类一个文件夹文件夹名就是类别名。跑自己的数据时建议先按主动学习和半监督学习的需要把数据拆成已标注和未标注两个部分dataset/ ├── labeled/ │ ├── class_a/ │ ├── class_b/ │ └── class_c/ └── unlabeled/ ├── image_001.jpg ├── image_002.jpg └── ...已标注目录采用“标签即文件夹名”的结构加载代码可以直接用 PyTorch 的 ImageFolder 读取不需要额外维护标注表。未标注目录不需要子文件夹脚本会用与 labeled 相同的图像预处理管线读入只参与半监督分支或者作为主动学习的候选池。这个拆分方式是最常见的做法优点是主流程代码几乎不用改缺点是同一个样本如果同时出现在 labeled 和 unlabeled 中会重复计算换数据前要检查两边路径有没有重叠。有的例程也会把未标注数据组织成和 labeled 相同的子目录结构但里面放的是同一个类的扩充样本。这种组织方式在伪标签方法里比较常见因为每个未标注样本有先验的类信息阈值过滤时更容易判断伪标签对错。如果你的任务不是图像分类而是文本分类或目标检测目录结构要相应调整核心原则不变已标注数据必须能直接映射到标签未标注数据必须与已标注数据共享同一个预处理通道。4.2 五个必调参数与调整方向换到自己数据上以下五个参数几乎一定要动参数默认值参考调整方向initial_label50~500按总数据量的 1%~5% 设定太少模型学不动n_query10~50占 initial_label 的 5%~10%影响标注节奏unlabeled_ratio0.5~0.9数据噪声大就调小数据干净可调大threshold0.9~0.99伪标签噪声大就调高反之调低queryentropy类不平衡时从 margin 切到 entropy 或结合多样性initial_label 决定了模型的起跑线。种子标注质量差后面所有查询策略都会放大这个误差。n_query 决定每轮标注量rounds 决定轮数三者乘起来约等于总标注预算所以先定预算再反推参数比从参数反推预算直观得多。unlabeled_ratio 和 threshold 是半监督侧最重要的两个旋钮比例越大越依赖无标注分支但数据分布一旦有异质样本模型容易被错误信号带跑。query 的选择则要看类别分布是否均衡。这五个参数之间不是独立的。比如 threshold 调高之后真正能通过的伪标签变少unlabeled_ratio 如果维持 0.9可用训练样本会严重不足反过来unlabeled_ratio 调低后threshold 可以适当放宽到 0.9 附近来补偿样本量。例程里的默认值只是让脚本能跑通落在你自己的数据上务必成对调整。4.3 小样本场景下的参数调整思路如果整个标注集只有几百张建议先跑一个不带任何主动学习和半监督的 baseline确认模型在已标注数据上能正常收敛再逐步打开两类算法。否则很难判断效果是策略带来的还是数据本身的性质。小样本下initial_label 至少覆盖每个类别 5~10 张query 策略优先选 entropy 而不是 margin半监督侧的 unlabeled_ratio 从 0.5 开始扫别一上来就 0.9因为未标注数据里极可能混着类外样本。类外样本对半监督学习的破坏力比想象中大。一致性正则会让模型强行把类外样本套进已知类别伪标签则直接把类外样本标成最接近的已知类两种机制都会污染特征空间。所以小样本场景下与其追求算法复杂度不如先花时间做一遍未标注数据的离群样本剔除用 KNN 或者简单的密度估计都能起到作用。先把噪声控制住再谈查询策略和一致性正则带来的增益这个顺序不要搞反。5. 避坑与排查跑主动学习和半监督例程最容易翻车的 5 个现场5.1 现象解压时提示 CRC 错误或要求输入密码原因zip 包在传输过程中字节不完整这是 CRC 报错的最常见来源要求密码则多半是打包工具误设了加密标志位也就是伪加密文件本身并没有真正的密码保护。解决换用命令行重新下载下载后先执行 unzip -t 做完整性校验。确认文件完整再解压。如果是伪加密导致无法解压用支持直接拖拽的压缩工具打开 zip把内容拖出来即可。不要花时间去找密码破解工具伪加密没有真实密钥纯属白费功夫。5.2 现象pip install 阶段一直报版本冲突原因全局环境里已经有旧版 torch 或 opencvrequirements.txt 里的新版本与之冲突。很多例程的 requirements 会把 torch 版本锁死但你的 CUDA 版本不一定匹配。解决先建虚拟环境再装依赖安装命令前加 python -m venv。如果虚拟环境里仍然冲突把 requirements.txt 里与 torch 强相关的行注释掉改成手动安装对应 CUDA 版本的轮子包再回来装其他依赖。不要用 pip 的 --force-reinstall 硬装大概率会装出一个 torch 和 torchvision 版本不匹配的黑匣子。5.3 现象主动学习跑了几轮准确率进入平台期原因查询策略只看置信度而模型概率校准差选出的样本未必是真正的难样本。另一个常见原因是 n_query 设得太小模型每轮只学了几十个样本收益不明显。解决在分类头后加温度缩放校准概率再把策略从 least_confidence 换成 entropy。同时检查 n_query 和 initial_label 的比例如果一轮只挑 5 个样本而初始标注有 500 个模型的迭代速度当然追不上标注者的手速。适当调大 n_query或者减少 rounds 但保证每轮样本量足够。5.4 现象半监督侧 Loss 持续下降验证集精度纹丝不动原因未标注数据里有大量类别外样本一致性正则强行让模型把类外样本套进已知类别训练损失确实在降但学到的是被扭曲的特征空间。解决先降低 unlabeled_ratio观察验证集是否恢复响应再调高 threshold过滤掉更多不可靠的伪标签。如果条件允许对未标注数据做一次离群样本剔除方法不限KNN 距离分布或者置信度直方图都可以。类外样本这个问题靠调参数基本压不住必须从数据入口根治。5.5 现象伪标签训练后模型预测全部偏向某一个类原因类别不均衡叠加确认偏差。模型在多数类上的置信度天然更高伪标签越训越往优势类倾斜最后把少数类直接吞掉。解决对伪标签按类别做配额限制每个类每轮最多保留固定数量的伪标签例程里如果没实现自己加一个 Counter 过滤也很快。同时检查 threshold 是否过高高阈值在类别先验不均衡下会无限放大优势类因为只有优势类能达到那个高置信度。把 threshold 降下来再配合类别加权损失双管齐下才压得住。6. 进阶用法把查询策略与一致性正则组装成你自己的实验骨架例程的意义在于你不需要从零实现算法但直接把代码搬进生产项目又往往不够。更合理的做法是把例程里的查询策略、伪标签生成、一致性损失三个模块拆开组装成自己的一套实验骨架。# 伪代码一轮主动学习 半监督联合训练的主循环 for round_idx in range(n_rounds): # 1. 半监督预训练 step supervised_loss train_step(model, labeled_loader) consistency_loss consistency_reg(model, unlabeled_loader) loss supervised_loss consistency_weight * consistency_loss loss.backward() optimizer.step() # 2. 用训练后的模型在候选池上做预测 proba_pool predict_pool(model, unlabeled_pool) # 3. 查询策略挑样本送人工标注 selected_idx query_by_uncertainty(proba_pool, strategyentropy, n_query20) # 4. 模拟打标并并回 labeled 集合 labeled_set labeled_set pseudo_label(selected_idx)这个骨架把第 2 章和第 3 章例程里的模块按职责拆开训练循环只管损失查询策略只管挑样本二者之间只通过一个索引数组通信。好处是换查询策略不需要动训练逻辑换半监督方法不需要动数据管道。我在自己项目里跑对比实验时所有策略对比都基于这个结构省掉了重复开发的时间。至于那批调参玄学——n_query 和 threshold 就是最有价值的两个旋钮其他多数参数对结果的影响都在 1 个点以内。把这两个旋钮先扫一遍比盲目试十种查询策略更有效。血泪经验是先把不均衡数据和类外噪声清洗干净再考虑上不上主动学习否则后面所有调参都是在给脏数据找补。希望帮到你。本文还有配套的精品资源点击获取