自监督学习在TensorFlow中需手动构建代理任务,无现成API;SimCLR是推荐入门方案,需双视图增强、投影头与NT-Xent损失;MAE易OOM,须XLA加速与预生成掩码;下游迁移应仅保留backbone并统一归一化参数。

自监督学习在TensorFlow里没有现成API,得自己搭代理任务
TensorFlow本身不提供tf.keras.layers.SelfSupervisedPretraining这类封装——所有“自监督”逻辑都得你手动设计输入变换、定义预测目标、构造损失。核心不是调用某个函数,而是把无标签数据变成有监督信号的过程。
常见错误是直接套用监督训练流程:用model.fit()喂原始图像,却没改y_true。结果模型根本不知道该学什么,loss不降、特征无区分度。
- 代理任务必须显式生成标签:比如旋转角度(0/90/180/270)、遮蔽块的像素值、对比样本对的相似性(1或0)
- 数据预处理层要和模型联合训练:不能只用
tf.image做离线增强,得把变换逻辑写进tf.data.Dataset.map(),并确保梯度可传回(例如用tf.py_function时需注意是否断梯度) - 损失函数得匹配任务:旋转预测用
sparse_categorical_crossentropy,遮蔽重建常用mean_squared_error,对比学习则要手写NT-Xent loss
对比学习(SimCLR)是最稳妥的入门选择
相比旋转预测或Jigsaw,SimCLR结构清晰、梯度稳定、下游迁移效果可复现,适合第一次在TensorFlow里跑通自监督流程。
关键点在于两个视图(view)必须来自同一张图但独立增强,且投影头(projection head)输出要归一化后算余弦相似度。
- 增强链必须用
tf.keras.layers.Random...*或自定义tf.function,避免tf.image.random_*在batch内不同步 - batch内负样本数直接影响loss质量:batch size至少设为256,否则
tf.linalg.diag_part()取正样本相似度时容易数值不稳定 - 别在主干网络后直接接分类头:SimCLR要求先过
tf.keras.Sequential([Dense,ReLU,Dense])投影到128维,再tf.nn.l2_normalize
示例片段:
def contrastive_loss(z_i, z_j, temperature=0.1): batch_size = tf.shape(z_i)[0] z = tf.concat([z_i, z_j], axis=0) # [2b, d] sim_matrix = tf.matmul(z, z, transpose_b=True) / temperature sim_i_j = tf.diag_part(sim_matrix[:batch_size, batch_size:]) sim_j_i = tf.diag_part(sim_matrix[batch_size:, :batch_size]) positive_samples = tf.concat([sim_i_j, sim_j_i], axis=0) # [2b] negative_mask = tf.eye(2 * batch_size, dtype=tf.bool) negative_samples = tf.boolean_mask(sim_matrix, ~negative_mask) negative_samples = tf.reshape(negative_samples, (2 * batch_size, -1)) logits = tf.concat([positive_samples[:, None], negative_samples], axis=1) labels = tf.zeros(2 * batch_size, dtype=tf.int32) return tf.keras.losses.sparse_categorical_crossentropy(labels, logits, from_logits=True)
遮蔽图像建模(MAE)在TensorFlow里容易OOM
MAE需要随机遮蔽75% patch并重建像素,但tf.image.extract_patches + tf.scatter_nd组合极易触发显存爆炸,尤其在224×224输入下。
根本原因不是模型大,而是动态掩码导致每个step的计算图不同,XLA编译失效,且tf.Variable初始化时未预留足够显存。
- 必须用
tf.function(jit_compile=True)强制XLA,否则训练速度慢3倍以上 - 遮蔽索引不能用
tf.random.shuffle实时生成:改用预生成的tf.constant索引表,通过tf.gather查表,避免图重构建 - 重建头(decoder)权重不能和encoder共享:即使同结构也要用独立
tf.keras.layers.Dense,否则梯度更新冲突
预训练后如何正确提取特征用于下游任务
很多人把整个自监督模型(含projection head)直接冻住,然后接新分类头——这会导致特征分布偏移,下游finetune收敛慢甚至掉点。
真正有效的做法是:只保留backbone(如ResNet50的conv5_block3_out之后部分),删掉所有projection和prediction层,再在顶部加新head。
- 检查layer name:用
model.layers[-2].name确认最后一个是projection层,不是backbone输出 - 冻结策略要分层:backbone前几层(stem + stage1)通常保持可训,stage4以后才freeze,否则小数据集上泛化差
- 加载权重时别用
by_name=True硬匹配:自监督模型的layer name和标准ResNet不同,应手动赋值backbone.set_weights(...)
最易被忽略的是:自监督预训练用的归一化参数(如ImageNet的mean/std)必须和下游数据一致。哪怕你用了自己的数据集做预训练,下游推理时也得用同一套tf.image.per_image_standardization或固定常量。

















