TensorFlow 2.0 初体验:从 iOS 开发者的视角看 AI 入门
去年底,公司接了个“智能相册”的需求——产品经理画了一张大饼:“用户上传照片后,系统能自动识别场景、人物、甚至情绪,还能按时间线智能分组。”听起来很酷,但对我们这支以 iOS 和后端 API 为主的团队来说,AI?那是另一个宇宙的事。
我,一个写了六年 iOS 的老 Swifter,平时连 pip install 都不怎么敲(Mac 上的 Homebrew 早就够用了),突然被拉进这个项目,负责“对接模型服务”。领导原话是:“你逻辑强,学得快,先跑通个 demo,下周五演示。” 好吧,又是熟悉的“下周交付”节奏。当晚回到家,泡了杯速溶咖啡(上海租房太贵,舍不得买手冲),打开 MacBook Pro,开始硬着头皮啃 TensorFlow 2.0。
说实话,一开始我对 TensorFlow 的印象还停留在 1.x 时代那种“写个加法都要建图、会话、初始化”的噩梦里。但这次一上手 2.0,发现 Google 真的听劝了——Eager Execution 默认开启,Keras 直接整合进来,API 简洁得让我这个前端思维的人直呼“这不就是 PyTorch 的感觉吗?”(别打我,iOS 开发偶尔也会看隔壁 Android 团队在干嘛)。
今天这篇技术分享,就结合我们“智能相册”的实战经验,聊聊 TensorFlow 2.0 的基础概念。不讲数学推导,不堆公式,只讲我在项目中踩过的坑、调过的参、以及最后怎么让模型在测试集上准确率干到 85%+ 的综合心得。
为什么选 TensorFlow 2.0?而不是 Core ML?
先说清楚背景:我们最终目标是把模型部署到 iOS App 里,用 Core ML 跑推理。那为什么不直接用 Create ML?因为业务复杂度高——需要同时识别 场景类型(如海滩、办公室、餐厅)、人物数量、以及是否包含宠物。Create ML 的图像分类模板只能做单标签,而我们需要多任务输出。
于是架构定下来了:在服务器用 TensorFlow 2.0 训练一个多输出模型 → 导出为 SavedModel → 用 tfcoreml 工具转成 .mlmodel → 集成进 iOS App。
所以,我的学习路径很明确:先搞懂 TF 2.0 怎么训练、怎么评估,再搞定转换。下面这些概念,都是我在实际调试中反复验证过的。
数据准备:别低估“脏数据”的杀伤力
我们的训练数据来自内部员工自愿上传的照片(约 5000 张),标注由外包团队完成。本以为数据 ready 就能开训,结果第一次跑完 val_loss 直接爆到 3.0+,准确率不到 40%。
问题出在哪?标签不一致。比如同一张“咖啡馆自拍”,有人标“餐厅”,有人标“休闲”,还有人标“人物”。更离谱的是,有几张猫的照片被标成了“人物”(估计标注员眼花了)。
实战经验:
在 tf.data.Dataset 加载阶段,我就加了数据清洗逻辑:
def clean_label(label):
# 统一场景标签映射
scene_map = {
'cafe': 'restaurant',
'bar': 'restaurant',
'office_building': 'office'
}
if label in scene_map:
return scene_map[label]
return label
# 在 dataset pipeline 中应用
dataset = dataset.map(lambda x, y: (x, clean_label(y)))
另外,数据增强是必须的。手机拍的照片角度、光线千奇百怪,不增强等于裸奔。TF 2.0 的 ImageDataGenerator 虽然老派,但对新手友好:
datagen = ImageDataGenerator(
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True,
zoom_range=0.2,
rescale=1./255 # 别忘了归一化!
)
吐槽一句:运维同事一开始把图片存在 NFS 上,IO 慢得像蜗牛。后来改成本地 SSD +
tf.data.AUTOTUNE自动并行加载,训练速度直接翻倍。果然,AI 项目 80% 的时间花在数据上,这话真不假。
模型搭建:Keras 是真香
TF 2.0 最大的改进,就是把 Keras 当作高级 API 官方推荐。对我这种不想手动管理计算图的人来说,简直是福音。
我们的模型结构其实不复杂:共享的 CNN 主干 + 三个独立的全连接头(场景分类、人物计数、宠物检测)。用函数式 API 写起来清晰又灵活:
from tensorflow.keras import layers, Model
# 共享主干:用预训练的 MobileNetV2(轻量,适合移动端)
base_model = tf.keras.applications.MobileNetV2(
input_shape=(224, 224, 3),
include_top=False,
weights='imagenet'
)
base_model.trainable = False # 先冻结,后面 fine-tune
# 输入
inputs = layers.Input(shape=(224, 224, 3))
# 特征提取
x = base_model(inputs, training=False)
x = layers.GlobalAveragePooling2D()(x)
# 场景分类头(10 类)
scene_output = layers.Dense(128, activation='relu')(x)
scene_output = layers.Dropout(0.5)(scene_output)
scene_output = layers.Dense(10, activation='softmax', name='scene')(scene_output)
# 人物数量头(回归问题,0-5 人)
person_output = layers.Dense(64, activation='relu')(x)
person_output = layers.Dropout(0.3)(person_output)
person_output = layers.Dense(1, activation='relu', name='person_count')(person_output) # 注意:这里不用 sigmoid!
# 宠物检测头(二分类)
pet_output = layers.Dense(32, activation='relu')(x)
pet_output = layers.Dropout(0.3)(pet_output)
pet_output = layers.Dense(1, activation='sigmoid', name='has_pet')(pet_output)
# 构建模型
model = Model(inputs=inputs, outputs=[scene_output, person_output, pet_output])
注意几个细节:
- 多输出必须命名,否则后面计算 loss 时会报错。
- 人物数量是回归任务,输出层不能用 softmax/sigmoid,直接用 ReLU 限制非负即可(实际训练中发现 tanh 效果反而差)。
- 预训练模型记得冻结,先 train head,再 fine-tune。
损失函数与优化器:别乱配,会翻车
多任务学习最头疼的就是 loss 权重分配。一开始我天真地认为“三个 loss 直接相加就行”,结果模型完全偏向场景分类(因为它的 loss 值最大),人物和宠物几乎没学到。
查了论文 + GitHub issues,才知道要动态调整 loss 权重,或者至少做归一化。我们采用简单粗暴但有效的方法:根据验证集上的初始 loss 比例反向赋权。
# 先跑一个 epoch,记录各任务 loss
initial_losses = model.evaluate(val_dataset, verbose=0)
scene_loss_init = initial_losses[1] # 假设索引1是scene loss
person_loss_init = initial_losses[2]
pet_loss_init = initial_losses[3]
# 计算权重(让初始总 loss ≈ 1)
total = scene_loss_init + person_loss_init + pet_loss_init
w_scene = 1.0 / scene_loss_init
w_person = 1.0 / person_loss_init
w_pet = 1.0 / pet_loss_init
# 编译模型
model.compile(
optimizer='adam',
loss={
'scene': 'categorical_crossentropy',
'person_count': 'mse', # 回归用 MSE
'has_pet': 'binary_crossentropy'
},
loss_weights={
'scene': w_scene,
'person_count': w_person,
'has_pet': w_pet
},
metrics={
'scene': 'accuracy',
'person_count': 'mae',
'has_pet': 'accuracy'
}
)
这个 trick 让三个任务的 loss 下降曲线终于同步了!算法工程师可能觉得 low,但对我们这种“兼职 AI”的开发者来说,实用主义万岁。
优化器方面,Adam 默认参数 (lr=0.001) 起手,后期如果 loss 卡住,就用 ReduceLROnPlateau 自动降学习率:
reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(
monitor='val_loss',
factor=0.5,
patience=3,
min_lr=1e-6
)
训练与调优:耐心是第一生产力
我们用 4 块 Tesla T4(感谢公司云资源),batch_size=32,跑了 50 个 epoch。前 20 个 epoch 只训练 heads,后 30 个解冻 MobileNetV2 的最后 30 层做 fine-tune。
关键配置对比表:
| 阶段 | 学习率 | 训练层 | Epochs | Val Accuracy (Scene) |
|---|---|---|---|---|
| Head Only | 0.001 | 仅新增 Dense 层 | 20 | 72.3% |
| Fine-tune | 0.0001 | 最后 30 层 + heads | 30 | 85.6% |
可以看到,fine-tune 带来了 13%+ 的提升。但要注意:学习率必须降低,否则预训练权重会被破坏。
另外,早停(Early Stopping)一定要加!我们有一次因为没加,多跑了 10 个 epoch,结果过拟合了,test accuracy 反而下降。血泪教训:
early_stop = tf.keras.callbacks.EarlyStopping(
monitor='val_loss',
patience=5,
restore_best_weights=True # 自动恢复最佳权重!
)
模型导出与 iOS 集成:别卡在最后一步
训练完只是开始。要把模型塞进 iPhone,还得过两关:
- 导出为 SavedModel
- 转换为 Core ML 格式
SavedModel 导出很简单:
model.save('smart_album_model')
# 或者用 tf.saved_model.save(model, 'path')
但转换时遇到大坑:tfcoreml 不支持某些 TF 2.0 ops,尤其是 tf.function 装饰的自定义层。我们的模型因为用了预训练 MobileNetV2,还好没触发这个问题。
转换命令:
pip install tfcoreml
tfcoreml.convert(
tf_model_path='smart_album_model',
mlmodel_path='SmartAlbum.mlmodel',
output_feature_names=['scene', 'person_count', 'has_pet'],
input_name_shape_dict={'input_1': [1, 224, 224, 3]}
)
成功生成 .mlmodel 后,拖进 Xcode,Swift 调用简直丝滑:
import CoreML
let model = try? SmartAlbum(configuration: MLModelConfiguration())
let pixelBuffer = // 从 UIImage 转来的 CVPixelBuffer
let prediction = try? model?.prediction(input: SmartAlbumInput(image: pixelBuffer))
print("场景: \(prediction?.scene ?? "")")
print("人数: \(prediction?.personCount ?? 0)")
print("有宠物: \(prediction?.hasPet ?? false)")
实测 iPhone 12 上单图推理耗时 ~45ms,完全满足 App 要求(产品经理居然没再提“能不能更快点”)。
总结:iOS 开发者也能玩转 AI
回过头看,从零开始搞 TensorFlow 2.0,其实没想象中那么恐怖。Google 这次真的把易用性做到了极致——Eager Execution 让调试像写 Python 脚本,Keras 抽象掉大部分底层细节,丰富的文档和社区支持也省了不少事。
当然,过程中也踩了不少坑:
- 数据清洗比算法选择更重要
- 多任务 loss 权重不能拍脑袋定
- 模型转换是隐藏关卡,提前查兼容性
- 移动端部署要考虑模型大小和推理速度(MobileNetV2 真香)
现在,“智能相册”功能已经上线两周,用户反馈还不错。虽然离“精准识别用户情绪”还有距离(那是下个版本的故事了),但至少我们证明了:一个专注客户端的团队,也能快速集成 AI 能力。
如果你和我一样,是个被临时抓壮丁的“非专业 AI 工程师”,别慌。TensorFlow 2.0 的设计哲学就是“让机器学习变得简单”。从一个小任务开始,跑通 end-to-end 流程,你会发现:AI 并没有那么神秘,它只是另一种“算法”而已——只不过,这次的输入是像素,输出是可能性。
最后送大家一句我在 GitHub issue 里看到的话:“Don’t fear the tensor.”
(完)
P.S. 写完这篇已经是凌晨两点,窗外上海的雨还没停。明天还要改 iOS 的 UI 适配,产品经理说“那个 AI 按钮不够圆润”……唉,程序员的命,都是咖啡续的。

评论 0