
1. 从零上手TensorFlow先搞清楚它到底在解决什么问题很多人第一次接触TensorFlow脑子里冒出来的第一个问题往往是“这玩意儿跟NumPy有什么区别我用NumPy也能算矩阵为什么还要多学一套框架”这个问题如果没想明白后面学起来就会觉得处处别扭——一会儿是张量一会儿是计算图一会儿又是自动微分感觉像是在学一门新语言而不是一个工具库。我当初也是这么过来的。真正让我理解TensorFlow价值的是一个很具体的场景我需要训练一个图像分类模型手写反向传播的梯度公式推到第三层就快疯了而且每次改网络结构都得重新推导一遍。TensorFlow解决的核心问题就是自动微分和计算图的组织管理。你只需要定义前向计算过程反向传播的梯度它帮你算而且能自动利用GPU并行加速。这是NumPy做不到的。所以TensorFlow本质上是一个端到端的机器学习计算框架它的核心能力可以拆成三块张量运算类似NumPy但支持GPU、自动微分训练模型的关键、计算图管理把复杂的计算组织成可复用的结构。围绕这三块能力它又衍生出了数据管道、模型保存、分布式训练、部署推理等一整套工具链。这篇文章适合谁看如果你已经会用Python懂基本的矩阵运算想入门深度学习但不知道从哪里下手那这篇内容就是写给你的。我不会一上来就甩一堆API文档而是按照一个从业者实际学习和使用的路径把TensorFlow的核心概念、实操步骤、容易踩的坑都讲清楚。读完之后你应该能独立完成一个完整的模型训练流程并且知道每一步为什么要这么做。2. 环境搭建版本选择与安装的坑比你想的多2.1 TensorFlow 1.x还是2.x别犹豫直接上2.x这个问题在几年前还有讨论价值现在已经很明确了新项目一律用TensorFlow 2.x。1.x的静态计算图模式先定义图再执行虽然在某些生产场景下仍有优势但它的学习曲线陡峭调试困难而且官方早已停止维护。2.x默认启用动态图模式Eager Execution写起来跟普通Python代码几乎一样直观调试的时候可以直接print中间结果体验好了不止一个档次。不过有一点需要注意2.x虽然默认是动态图但它通过tf.function装饰器可以把Python函数编译成静态图兼顾灵活性和性能。这个机制后面会详细讲先有个印象就行。2.2 安装方式的选择pip、conda还是Docker三种方式我都用过各自适合不同的场景pip安装最直接pip install tensorflow一行搞定。适合个人开发、学习实验。但要注意Python版本兼容性TensorFlow 2.15要求Python 3.9到3.11版本不对会直接报错。conda安装适合需要管理多个虚拟环境的场景。conda的好处是它会帮你处理好CUDA和cuDNN的依赖关系省去手动配置GPU环境的麻烦。Docker镜像适合团队协作和生产部署。官方提供了优化好的镜像环境一致性有保障。缺点是镜像体积大首次拉取比较慢。我个人的建议是学习和实验用pip加虚拟环境就够了别一上来就折腾Docker容易在环境配置上消耗太多精力。2.3 GPU支持不是装了就能用如果你有NVIDIA显卡想用GPU加速有几个关键点必须确认检查项要求查看方式显卡计算能力3.5以上官网查显卡型号对应的Compute CapabilityCUDA版本与TF版本匹配nvidia-smi查看驱动支持的CUDA版本cuDNN版本与CUDA匹配需要注册开发者账号下载显卡驱动满足最低版本要求nvidia-smi查看装好之后用这段代码验证import tensorflow as tf print(tf.config.list_physical_devices(GPU)) print(tf.test.is_built_with_cuda())如果输出是空列表或者False说明GPU没配置成功。最常见的原因是CUDA版本和TensorFlow版本不匹配。比如TensorFlow 2.15需要CUDA 12.2和cuDNN 8.9你装了CUDA 11.8就会报错。这时候别急着重装先查一下官方文档的版本对应表确认清楚了再动手。提示如果GPU配置反复失败先用CPU版本把流程跑通确认代码逻辑没问题之后再回头折腾GPU环境。不要在环境配置上卡太久学习的重心应该放在框架本身。3. 张量与核心运算理解TensorFlow的基本语言3.1 张量到底是什么——从标量到高维数组TensorFlow的名字里就有“Tensor”这个词翻译过来就是张量。听起来很玄乎其实它就是一个多维数组的统称。用生活化的方式理解标量0维张量就是一个数字比如3.14向量1维张量一列数字比如[1, 2, 3]矩阵2维张量一张表格有行有列3维及以上可以想象成一摞表格或者一个立方体里填满数字在TensorFlow里创建一个张量import tensorflow as tf # 从Python列表创建 a tf.constant([1, 2, 3]) print(a.shape) # (3,) print(a.dtype) # dtype: int32 # 创建全零张量 b tf.zeros((2, 3)) # 创建全一张量 c tf.ones((2, 3)) # 创建正态分布随机张量 d tf.random.normal((2, 3), mean0.0, stddev1.0)跟NumPy数组最大的区别在于TensorFlow的张量默认是不可变的immutable。你不能像NumPy那样直接a[0] 5去修改某个元素。这个设计看起来不方便但它是为了支持自动微分和计算图优化——如果张量可以被随意修改追踪计算依赖关系就会变得极其复杂。3.2 自动微分TensorFlow最核心的能力自动微分是TensorFlow区别于NumPy的根本所在。用一个简单的例子来说明它的价值假设我们要拟合一个线性函数y wx b损失函数是均方误差。用NumPy的话你需要手动推导出损失对w和b的偏导数公式然后写代码实现。但如果网络有几十层手动推导根本不现实。TensorFlow的GradientTape解决了这个问题w tf.Variable(2.0) b tf.Variable(1.0) with tf.GradientTape() as tape: x tf.constant([1.0, 2.0, 3.0]) y_true tf.constant([3.0, 5.0, 7.0]) y_pred w * x b loss tf.reduce_mean(tf.square(y_pred - y_true)) # 自动计算梯度 gradients tape.gradient(loss, [w, b]) print(gradients) # 输出loss对w和b的偏导数GradientTape的工作机制是在with语句块内它会记录所有涉及被监视变量这里是w和b的计算操作形成一个“磁带”记录。退出语句块后调用tape.gradient()就能沿着记录反向计算出梯度。这个“磁带”只能使用一次用完就释放了如果需要重复求导要设置persistentTrue。注意只有被tf.Variable包装的变量才会被自动追踪。普通的tf.constant创建的张量不会被追踪除非你手动调用tape.watch()。这个细节在调试时经常让人困惑——明明代码没报错但梯度返回None大概率就是忘了用Variable。3.3 常用运算操作速查TensorFlow的运算API非常丰富但日常使用中最高频的其实就那么几十个。我整理了一份常用操作对照表操作类型NumPy写法TensorFlow写法说明矩阵乘法np.matmul(a,b)tf.matmul(a,b)支持批量维度逐元素乘a * ba * b用法一致求和np.sum(a)tf.reduce_sum(a)可指定axis求均值np.mean(a)tf.reduce_mean(a)可指定axis变形a.reshape(...)tf.reshape(a,...)元素总数不变转置a.Ttf.transpose(a)可指定perm拼接np.concatenatetf.concat沿指定轴拼接索引a[1:3]a[1:3]基本一致从NumPy迁移过来的话大部分操作直觉上都能对应上主要注意reduce_前缀和tf.前缀就行。4. 模型构建的三条路从简单到灵活4.1 Sequential API搭积木一样的建模方式如果你要建的模型是一条直线走到底的每一层的输出只喂给下一层那Sequential就是最省事的选择model tf.keras.Sequential([ tf.keras.layers.Dense(128, activationrelu, input_shape(784,)), tf.keras.layers.Dropout(0.3), tf.keras.layers.Dense(64, activationrelu), tf.keras.layers.Dense(10, activationsoftmax) ])这种写法就像搭积木一层叠一层代码可读性极强。但它有个硬性限制不支持多输入、多输出或层之间有跳跃连接的结构。比如ResNet里的残差连接skip connection用Sequential就表达不了。4.2 Functional API有向无环图的表达能力Functional API的思路是把模型看作一个函数输入张量进去输出张量出来中间怎么连你自己定。这就能表达任意有向无环图结构inputs tf.keras.Input(shape(784,)) x tf.keras.layers.Dense(128, activationrelu)(inputs) x tf.keras.layers.Dropout(0.3)(x) x tf.keras.layers.Dense(64, activationrelu)(x) outputs tf.keras.layers.Dense(10, activationsoftmax)(x) model tf.keras.Model(inputsinputs, outputsoutputs)残差连接也很容易实现inputs tf.keras.Input(shape(64,)) x tf.keras.layers.Dense(64, activationrelu)(inputs) x tf.keras.layers.Dense(64)(x) x tf.keras.layers.Add()([x, inputs]) # 跳跃连接 x tf.keras.layers.Activation(relu)(x)我个人的经验是能用Functional API就用Functional API。它的灵活性比Sequential高很多但代码复杂度增加有限。只有在需要自定义训练循环或层内部有复杂控制流的时候才需要下沉到第三种方式。4.3 自定义层与模型子类化终极灵活性当Keras提供的层满足不了需求时比如你要实现一个论文里的新型注意力机制就需要自己写层class MyDenseLayer(tf.keras.layers.Layer): def __init__(self, units): super().__init__() self.units units def build(self, input_shape): self.w self.add_weight( shape(input_shape[-1], self.units), initializerglorot_uniform, trainableTrue ) self.b self.add_weight( shape(self.units,), initializerzeros, trainableTrue ) def call(self, inputs): return tf.matmul(inputs, self.w) self.b关键点在于build()方法它在你第一次调用这个层的时候自动执行此时你才知道输入的形状才能正确地初始化权重。call()方法定义前向计算逻辑。这种延迟初始化的设计是为了支持形状不确定的输入。三种方式的适用场景总结一下Sequential快速原型验证结构简单的模型Functional API大多数实际项目需要多输入输出或跳跃连接子类化研究场景需要自定义层或复杂训练逻辑5. 训练流程的完整拆解compile、fit与回调5.1 compile配置训练参数模型建好之后用compile来指定优化器、损失函数和评估指标model.compile( optimizertf.keras.optimizers.Adam(learning_rate1e-3), losstf.keras.losses.SparseCategoricalCrossentropy(), metrics[accuracy] )这里有几个容易搞混的地方损失函数的选择SparseCategoricalCrossentropy和CategoricalCrossentropy的区别在于标签的格式。前者接受整数标签如[0, 2, 1]后者接受one-hot编码如[[1,0,0],[0,0,1],[0,1,0]]。用错了不会报错但loss会算得莫名其妙。学习率的设置Adam的默认学习率是0.001大多数情况下够用。但如果训练loss震荡厉害可以降到1e-4试试如果loss下降太慢可以适当提高到3e-3。我一般会先用默认值跑几轮观察loss曲线再决定要不要调。5.2 fit训练循环的封装fit把训练循环封装好了你只需要提供数据和训练轮数history model.fit( x_train, y_train, epochs20, batch_size32, validation_split0.2, verbose1 )batch_size的选择有个经验法则太小如1或2会导致训练不稳定太大如超过1024会降低泛化能力。32到256之间是比较安全的范围。如果显存不够优先减小batch_size而不是降低模型复杂度。validation_split0.2表示从训练数据中划出20%作为验证集。注意这个划分是在fit内部完成的每次调用结果可能不同。如果需要固定的验证集应该提前手动划分好再分别传入。5.3 回调函数训练过程中的自动化操作回调函数是TensorFlow里非常实用的一个机制它允许你在训练的各个阶段每轮开始、每轮结束、每个batch结束等插入自定义逻辑。最常用的三个callbacks [ # 早停验证loss连续5轮不下降就停止训练 tf.keras.callbacks.EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ), # 学习率衰减验证loss不下降时降低学习率 tf.keras.callbacks.ReduceLROnPlateau( monitorval_loss, factor0.5, patience3 ), # 模型检查点保存验证loss最低的模型 tf.keras.callbacks.ModelCheckpoint( best_model.keras, monitorval_loss, save_best_onlyTrue ) ]EarlyStopping的restore_best_weightsTrue这个参数很重要。不加的话训练停止时模型保留的是最后一轮的权重可能已经过拟合了。加上之后会自动恢复到验证loss最低那一轮的权重。提示ReduceLROnPlateau和EarlyStopping搭配使用效果很好。学习率降低后模型往往还能再提升一点如果降低学习率后仍然不下降那才是真的到头了。6. 数据管道tf.data的高效使用6.1 为什么不用NumPy数组直接喂小数据集用NumPy数组直接传给fit没问题但数据量一大就会出问题。假设你有10GB的图像数据全部加载到内存里可能直接OOM。tf.data.Dataset提供了一种流式加载的机制数据可以边读边用内存占用可控。另外tf.data还支持并行预处理、预取、缓存等优化能显著提升训练速度。我实测过一个图像分类任务用tf.data做数据管道比直接用NumPy数组快了将近40%因为数据加载和GPU计算可以重叠进行。6.2 构建Dataset的常见模式从内存中的数组创建dataset tf.data.Dataset.from_tensor_slices((x_train, y_train)) dataset dataset.shuffle(1000).batch(32).prefetch(tf.data.AUTOTUNE)从文件创建以图像为例def load_and_preprocess(path, label): image tf.io.read_file(path) image tf.image.decode_jpeg(image, channels3) image tf.image.resize(image, [224, 224]) image image / 255.0 return image, label dataset tf.data.Dataset.from_tensor_slices((file_paths, labels)) dataset dataset.map(load_and_preprocess, num_parallel_callstf.data.AUTOTUNE) dataset dataset.shuffle(1000).batch(32).prefetch(tf.data.AUTOTUNE)这里几个关键操作的作用shuffle(buffer_size)从缓冲区中随机采样buffer_size越大随机性越好但内存占用也越大map(num_parallel_calls)并行执行预处理函数AUTOTUNE让TensorFlow自动决定并行数batch(n)把n个样本打包成一个batchprefetch(AUTOTUNE)在当前batch计算时提前准备下一个batch的数据6.3 数据增强的集成方式数据增强是防止过拟合的有效手段。用tf.data可以在管道中直接做增强def augment(image, label): image tf.image.random_flip_left_right(image) image tf.image.random_brightness(image, max_delta0.2) image tf.image.random_crop(image, size[200, 200, 3]) return image, label train_dataset train_dataset.map(augment, num_parallel_callstf.data.AUTOTUNE)注意增强只应该应用于训练集验证集和测试集不能做增强。所以通常的做法是构建两个Dataset一个带增强一个不带。7. 模型保存与加载别让训练成果白费7.1 两种保存格式的选择TensorFlow 2.x支持两种保存格式SavedModel格式默认保存完整的模型信息包括架构、权重、训练配置。可以直接用tf.keras.models.load_model()加载不需要重新定义模型结构。适合部署场景。H5格式Keras早期的格式保存架构和权重但有些自定义层可能保存不了。现在用得越来越少了。# 保存为SavedModel model.save(my_model.keras) # 加载 loaded_model tf.keras.models.load_model(my_model.keras)7.2 只保存权重更灵活的选择有时候你只需要保存训练好的参数模型结构在代码里定义# 保存权重 model.save_weights(weights.ckpt) # 加载权重需要先构建相同结构的模型 model.load_weights(weights.ckpt)这种方式的好处是文件小而且加载时不受框架版本影响。缺点是必须保证代码中的模型结构和保存时完全一致否则会报形状不匹配的错误。7.3 训练中断后的恢复训练大模型往往需要几个小时甚至几天中途可能因为各种原因中断。用ModelCheckpoint回调定期保存检查点中断后可以从最近的检查点恢复checkpoint_path training_checkpoints/cp-{epoch:04d}.ckpt cp_callback tf.keras.callbacks.ModelCheckpoint( filepathcheckpoint_path, save_weights_onlyTrue, save_freqepoch ) # 恢复训练 model.load_weights(tf.train.latest_checkpoint(training_checkpoints)) model.fit(..., initial_epochloaded_epoch)initial_epoch参数告诉fit从第几轮开始计数这样学习率调度器等依赖epoch的组件才能正确工作。8. 性能优化让训练跑得更快8.1 tf.function把Python代码编译成图TensorFlow 2.x默认是动态图模式每行代码立即执行。这种方式调试方便但性能不如静态图。tf.function装饰器可以把Python函数编译成静态计算图获得更好的性能tf.function def train_step(x, y): with tf.GradientTape() as tape: predictions model(x, trainingTrue) loss loss_fn(y, predictions) gradients tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(gradients, model.trainable_variables)) return loss第一次调用时TensorFlow会追踪函数执行过程构建计算图。后续调用直接执行图省去了Python解释器的开销。实测下来对于小模型可能提升不明显但对于层数多、操作密集的模型速度提升可以达到2到3倍。注意tf.function内部不能使用Python的print、列表append等副作用操作因为这些操作在图的追踪阶段只执行一次。如果需要在图中打印信息用tf.print。8.2 混合精度训练用更少的显存跑更大的模型混合精度是指同时使用float16和float32两种精度。float16计算快、显存占用少但数值范围小容易溢出float32数值稳定但慢。混合精度的思路是前向计算和反向传播用float16权重更新用float32。policy tf.keras.mixed_precision.Policy(mixed_float16) tf.keras.mixed_precision.set_global_policy(policy)开启之后Dense和Conv层的计算会自动用float16显存占用大约能减少30%到40%训练速度也能提升。但要注意最后的输出层应该保持float32否则softmax等操作可能数值不稳定。Keras会自动处理这一点你不需要手动改。8.3 分布式训练多卡并行的基本配置如果你有多张GPU用MirroredStrategy可以轻松实现数据并行strategy tf.distribute.MirroredStrategy() print(fNumber of devices: {strategy.num_replicas_in_sync}) with strategy.scope(): model build_model() model.compile(...) model.fit(...)关键点是模型的构建和编译必须在strategy.scope()内完成这样变量才会被正确地复制到各张GPU上。数据分发和梯度聚合由Strategy自动处理你不需要改训练代码。9. 常见报错与排查思路9.1 形状不匹配最常见的错误类型形状错误几乎每个TensorFlow使用者都会遇到。典型的报错信息是ValueError: Input 0 of layer dense_1 is incompatible with the layer: expected axis -1 of input shape to have value 784, but received input with shape (32, 128)排查思路从报错信息中找到出问题的层检查它的input_shape和实际传入的数据形状。常见原因包括数据预处理时reshape的维度算错了上一层输出的维度跟下一层期望的输入对不上batch维度被意外地当成了特征维度一个实用的技巧是在模型定义后用model.summary()打印每层的输出形状一目了然地看到哪一层开始对不上。9.2 Loss变成NaN数值不稳定的排查Loss突然变成NaN通常有几个原因学习率太大梯度爆炸导致参数飞到无穷大。解决办法是降低学习率或者加梯度裁剪。除零错误比如自定义loss里有除法操作分母可能为零。加一个极小值1e-7到分母上。log(0)交叉熵损失里如果预测概率为0log会变成负无穷。用tf.clip_by_value把概率限制在[1e-7, 1-1e-7]范围内。数据里有异常值输入数据里如果有inf或nan会传播到整个网络。训练前用tf.debugging.check_numerics检查一下。梯度裁剪的写法gradients tape.gradient(loss, model.trainable_variables) gradients [tf.clip_by_norm(g, 1.0) for g in gradients] optimizer.apply_gradients(zip(gradients, model.trainable_variables))9.3 GPU显存不足OOM的应对策略显存不足的报错信息是ResourceExhaustedError。应对策略按优先级排列减小batch_size最直接有效用混合精度训练减少约40%显存占用简化模型结构减少层数或每层的单元数用梯度累积模拟大batch每次小batch计算梯度累积几次后再更新参数梯度累积的实现accum_steps 4 for i, (x, y) in enumerate(dataset): with tf.GradientTape() as tape: loss compute_loss(model, x, y) / accum_steps grads tape.gradient(loss, model.trainable_variables) if accumulated_grads is None: accumulated_grads grads else: accumulated_grads [ag g for ag, g in zip(accumulated_grads, grads)] if (i 1) % accum_steps 0: optimizer.apply_gradients(zip(accumulated_grads, model.trainable_variables)) accumulated_grads None10. 从实验到部署模型导出的关键步骤10.1 导出为SavedModel供生产使用训练好的模型要给别人用最通用的格式是SavedModelmodel.save(exported_model)加载时不需要原始代码loaded tf.keras.models.load_model(exported_model) result loaded.predict(new_data)SavedModel目录里包含了一个pb文件存储计算图结构和一个variables目录存储权重。这个格式跨平台可以在Python、C、Java等环境中加载。10.2 推理优化冻结图与算子融合如果对推理速度有要求可以做图优化。TensorFlow提供了Grappler优化器能自动做算子融合、常量折叠等优化from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2 # 转换为具体函数 full_model tf.function(lambda x: model(x)) full_model full_model.get_concrete_function( tf.TensorSpec(model.inputs[0].shape, model.inputs[0].dtype) ) # 冻结图 frozen_func convert_variables_to_constants_v2(full_model) frozen_func.graph.as_graph_def()冻结之后权重变成了常量图结构也简化了推理速度会有明显提升。10.3 服务化部署的基本思路模型最终要对外提供服务常见的做法是用TensorFlow Serving或者自己写一个HTTP接口。如果只是内部使用用Flask包装一下就够了from flask import Flask, request, jsonify import numpy as np app Flask(__name__) model tf.keras.models.load_model(exported_model) app.route(/predict, methods[POST]) def predict(): data request.json[data] arr np.array(data).reshape(1, -1) prediction model.predict(arr).tolist() return jsonify({prediction: prediction})生产环境需要考虑的问题更多请求批处理、超时控制、模型版本管理、监控告警等。但核心思路是一样的——把模型加载到内存里接收请求返回预测结果。11. 我踩过的那些坑与实用建议11.1 随机种子可复现性的前提深度学习实验有个让人头疼的问题同样的代码跑两次结果不一样。这是因为权重初始化、数据打乱、Dropout等操作都涉及随机数。要保证可复现需要在程序开头设置种子import random import numpy as np import tensorflow as tf seed 42 random.seed(seed) np.random.seed(seed) tf.random.set_seed(seed)但要注意即使设置了种子在GPU上运行时由于并行计算的非确定性结果仍可能有微小差异。如果对可复现性要求极高需要设置tf.config.experimental.enable_op_determinism()但这会降低性能。11.2 学习率最重要的超参数如果只能调一个超参数那一定是学习率。我的经验是先用默认值Adam的1e-3跑几轮观察loss曲线loss下降太慢尝试3e-3或1e-2loss震荡或变成NaN降到1e-4或3e-4配合ReduceLROnPlateau使用让学习率自动衰减还有一个技巧是学习率预热前几个epoch用很小的学习率然后逐渐增加到目标值。这对Transformer类模型特别有效能避免训练初期的震荡。11.3 过拟合的判断与应对训练过程中要同时关注训练集和验证集的loss。如果训练loss持续下降但验证loss开始上升就是过拟合的信号。应对手段按优先级增加数据最有效但往往最难数据增强图像翻转、裁剪、颜色抖动等Dropout在全连接层后加Dropout(0.3)到Dropout(0.5)权重正则化在层里加kernel_regularizertf.keras.regularizers.l2(1e-4)早停验证loss不下降就停止训练这些手段可以组合使用但不要一次性全加上否则很难判断哪个起了作用。11.4 调试技巧小数据先跑通我的习惯是先用100条数据跑通整个流程确认没有形状错误、没有NaN、loss能下降然后再换全量数据。这样能把“代码bug”和“模型效果不好”两个问题分开。如果小数据上loss都降不下去那肯定是代码有问题跟数据量无关。另外model.summary()和tf.debugging.check_numerics()是两个非常实用的调试工具。前者帮你确认模型结构后者帮你定位数值异常的位置。11.5 版本兼容性别忽视的小问题TensorFlow的版本更新比较频繁不同版本之间的API可能有变化。比如tf.keras在2.15之后跟Keras 3.0的整合方式有调整一些旧的写法会报warning甚至报错。建议在项目开始时就把版本固定下来写进requirements.txt避免因为环境变化导致代码跑不了。tensorflow2.15.0 numpy1.26.0如果遇到API变更的问题最可靠的办法是查官方文档的迁移指南而不是在网上搜博客——很多博客的内容已经过时了。12. 下一步可以往哪些方向深入把基础的模型训练流程跑通之后TensorFlow还有很多值得探索的方向。如果你做的是计算机视觉可以研究tf.keras.applications里预训练模型的迁移学习用少量数据就能达到不错的效果。如果做自然语言处理tf.keras.layers.TextVectorization和注意力机制的实现是重点。如果关注部署TensorFlow Lite移动端和TensorFlow.js浏览器端能把模型带到更多平台上。我个人的建议是先在一个具体项目上把整个流程走完一遍从数据准备到模型训练到部署推理哪怕模型很简单、效果一般。走完一遍之后你对整个框架的理解会完全不一样再去看那些高级特性也会更容易上手。光看文档不动手永远学不会。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。