前言

本篇是我训练营 TensorFlow 篇的第 1 次学习,主要目标是使用 TensorFlow 完成 MNIST 手写数字识别——和 PyTorch 篇 P1 周完全相同的任务、完全相同的数据集、几乎完全相同的网络结构(LeNet-5 风格 CNN)。
所以本周最重要的学习重点不是重新理解什么是卷积层、池化层,而是理解: TensorFlow 和 PyTorch 虽然代码写法不同,但底层完成的是同一套深度学习过程。

感谢 K同学啊 老师的教学,以及 ChatGPT 和 Kimi。

P1 与 T1 的主要对比

对比项目 P1:PyTorch 实现 MNIST T1:TensorFlow 实现 MNIST
数据加载 torchvision.datasets.MNIST + DataLoader datasets.mnist.load_data(),直接得到 numpy 数组
数据形状 [N, 1, 28, 28]通道在前,NCHW) (N, 28, 28, 1)通道在后,NHWC)
归一化 ToTensor() 自动完成 手动除以 255.0
模型定义 class Model(nn.Module) + forward() models.Sequential 按层堆叠
训练方式 手写 train/test 循环(zero_grad → backward → step) model.compile() + model.fit() 一键完成
损失函数 nn.CrossEntropyLoss() SparseCategoricalCrossentropy(from_logits=True)
优化器 SGD(lr=0.01) adam
网络结构 conv(32)→pool→conv(64)→pool→fc(1600→64)→fc(64→10) 完全相同的 LeNet-5 变体
模型参数量 121,930 121,930(一模一样)
训练轮数 5 10
单 batch 数据形状 [32, 1, 28, 28] (32, 28, 28, 1)(fit 默认 batch_size=32)
最终测试/验证准确率 98.1%(5 轮) 99.11%(第 9 轮最高 99.22%)

一、准备工作

1. AMD 显卡安装 TensorFlow

  1. TensorFlow 2.11 起,Windows 原生 GPU 支持被官方取消(连 NVIDIA 卡都不再支持),Windows 原生只能装 CPU 版;
  2. 微软曾经为 Windows 上的 AMD/Intel 显卡提供过官方方案——DirectML 插件tensorflow-directml-plugin),走 DirectX 12 调用显卡;但这个插件已经停止更新,版本停留在 TensorFlow 2.10 + Python 3.10(2022 年的组合)。

所以为了学习 我新建了py3.10环境安装TensorFlow 2.10,TF 2.10 只兼容 numpy<1.24,但 pip 默认会装 numpy 1.26,一 import tensorflow 就会报 module 'numpy' has no attribute 'object' 之类的错。所以必须 pip install "numpy==1.23.5"

conda create -n py310 python=3.10 -y

conda activate py310

python -m pip install --upgrade pip

pip install tensorflow-cpu==2.10

pip install tensorflow-directml-plugin

pip install "numpy==1.23.5"

2. 设置 GPU

环境就绪,按教程设置 GPU 使用方式:

import tensorflow as tf
gpus = tf.config.list_physical_devices("GPU")

if gpus:
    gpu0 = gpus[0] #如果有多个GPU,仅使用第0个GPU
    tf.config.experimental.set_memory_growth(gpu0, True) #设置GPU显存用量按需使用
    tf.config.set_visible_devices([gpu0],"GPU")
    
print(gpus)
[PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU')]

这一段的意思是

  • set_memory_growth(gpu0, True):让显存按需增长。默认情况下 TensorFlow 启动时会一口气把整块显卡的显存几乎全部占满(防止碎片化),打开这个开关后改成用多少占多少——对我这种还要同时开浏览器、编辑器的学习机器很友好;
  • set_visible_devices([gpu0], "GPU"):让 TensorFlow 只能看到第 0 块 GPU。多卡机器上可以用这个指定用哪张卡。

这一步在概念上对应 PyTorch 的 device = torch.device("cuda" if ...),只是 PyTorch 是"先声明设备、后面手动 .to(device) 搬运数据和模型",而 TensorFlow 是"启动时全局设好,之后框架自动把计算放到 GPU 上"。


3. 关于 MNIST 数据集

本周数据集和 P1 周完全相同——MNIST 手写数字数据集,经典入门数据,P1 笔记里已有详细介绍,这里只列关键参数:

  • 训练集 60,000 张 + 测试集 10,000 张,共 70,000 张 28 × 28 灰度图;
  • 数字范围 0-9,共 10 类;
  • 原始像素值 0-255,归一化后缩放到 0-1

4. 导入数据

import tensorflow as tf
from tensorflow.keras import datasets, layers, models
import matplotlib.pyplot as plt

# 导入mnist数据,依次分别为训练集图片、训练集标签、测试集图片、测试集标签
(train_images, train_labels), (test_images, test_labels) = datasets.mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11490434/11490434 [==============================] - 2s 0us/step

P1 使用:

train_ds = torchvision.datasets.MNIST(
    'data',
    train=True,
    transform=torchvision.transforms.ToTensor(),
    download=True
)

两者拿到的都是 MNIST,但组织数据的方式不同。

PyTorch 更强调:

Dataset
↓
DataLoader
↓
一个 batch 一个 batch 地取数据

TensorFlow T1 则直接得到完整 NumPy 数组:

train_images
train_labels
test_images
test_labels

后续直接交给model.fit(),Keras 会在训练时再自动按照 batch 划分数据。


5. 归一化

# 将像素的值标准化至0到1的区间内。(对于灰度图片来说,每个像素最大值是255,每个像素最小值是0,也就是直接除以255就可以完成归一化。)
train_images, test_images = train_images / 255.0, test_images / 255.0
# 查看数据维数信息
train_images.shape,test_images.shape,train_labels.shape,test_labels.shape
((60000, 28, 28), (10000, 28, 28), (60000,), (10000,))

P1 周归一化是 ToTensor() ,对于常见 8-bit 图片,ToTensor() 在转换成 Tensor 的同时已经完成了类似的像素缩放。本周要手动除以 255。归一化的作用两个框架里都一样:让不同量纲的特征处于同一数值量级、加快收敛速度。


6. 可视化图片

# 将数据集前20个图片数据可视化显示
# 进行图像大小为20宽、10长的绘图(单位为英寸inch)
plt.figure(figsize=(20,10))
# 遍历MNIST数据集下标数值0~49
for i in range(20):
    # 将整个figure分成2行10列,绘制第i+1个子图。
    plt.subplot(2,10,i+1)
    # 设置不显示x轴刻度
    plt.xticks([])
    # 设置不显示y轴刻度
    plt.yticks([])
    # 设置不显示子图网格线
    plt.grid(False)
    # 图像展示,cmap为颜色图谱,"plt.cm.binary"为matplotlib.cm中的色表
    plt.imshow(train_images[i], cmap=plt.cm.binary)
    # 设置x轴标签显示为图片对应的数字
    plt.xlabel(train_labels[i])
# 显示图片
plt.show()

![在这里插入数据示例图片]
这一部分和 P1 的目的完全相同:检查数据是否正确,并直观理解训练图片和标签。
区别是 P1 中通常从 DataLoader 取一个 batch:

imgs, labels = next(iter(train_dl))

TensorFlow 中则可以直接访问:

train_images[i]
train_labels[i]

7. 调整图片格式(NHWC vs NCHW,本周重点差异)

#调整数据到我们需要的格式
train_images = train_images.reshape((60000, 28, 28, 1))
test_images = test_images.reshape((10000, 28, 28, 1))

train_images.shape,test_images.shape,train_labels.shape,test_labels.shape
((60000, 28, 28, 1), (10000, 28, 28, 1), (60000,), (10000,))

为什么要 reshape?
TensorFlow 刚读入 MNIST 时(60000, 28, 28),卷积神经网络还需要通道维。MNIST 是灰度图,所以通道数为 1,

train_images = train_images.reshape((60000, 28, 28, 1))
test_images  = test_images.reshape((10000, 28, 28, 1))

得到[N, H, W, C],也就是[60000, 28, 28, 1
TensorFlow 默认常见格式是NHWC,即通道 C 放在最后。而 PyTorch P1 一个 batch 是[32, 1, 28, 28],即[N, C, H, W],PyTorch 常用NCHW
同一批 32 张 MNIST 图片:

TensorFlow:[32, 28, 28, 1]
PyTorch:   [32, 1, 28, 28]

图片本身没有改变,只是通道所在的位置不同


二、构建 CNN 网络模型

1. Sequential 堆叠:和 P1 逐行对照

本周使用的是最简单的 CNN 模型 LeNet-5 风格的网络——而且和 P1 周 PyTorch 版是同一个结构。把两边代码放在一起看,差异一目了然:

# 创建并设置卷积神经网络
# 卷积层:通过卷积操作对输入图像进行降维和特征抽取
# 池化层:是一种非线性形式的下采样。主要用于特征降维,压缩数据和参数的数量,减小过拟合,同时提高模型的鲁棒性。
# 全连接层:在经过几个卷积和池化层之后,神经网络中的高级推理通过全连接层来完成。
model = models.Sequential([
    # 设置二维卷积层1,设置32个3*3卷积核,activation参数将激活函数设置为ReLu函数,input_shape参数将图层的输入形状设置为(28, 28, 1)
    # ReLu函数作为激活励函数可以增强判定函数和整个神经网络的非线性特性,而本身并不会改变卷积层
    # 相比其它函数来说,ReLU函数更受青睐,这是因为它可以将神经网络的训练速度提升数倍,而并不会对模型的泛化准确度造成显著影响。
    layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
    #池化层1,2*2采样
    layers.MaxPooling2D((2, 2)),                   
    # 设置二维卷积层2,设置64个3*3卷积核,activation参数将激活函数设置为ReLu函数
    layers.Conv2D(64, (3, 3), activation='relu'),  
    #池化层2,2*2采样
    layers.MaxPooling2D((2, 2)),                   
    
    layers.Flatten(),                    #Flatten层,连接卷积层与全连接层
    layers.Dense(64, activation='relu'), #全连接层,特征进一步提取,64为输出空间的维数,activation参数将激活函数设置为ReLu函数
    layers.Dense(10)                     #输出层,输出预期结果,10为输出空间的维数
])
# 打印网络结构
model.summary()

这一段的意思是Sequential 就是"按顺序把层堆起来",数据从第一层流进、最后一层流出,不需要像 PyTorch 那样自己写 forward() 定义数据怎么流。逐层和 P1 对照:

P1(PyTorch) T1(TensorFlow/Keras) 说明
卷积 1 nn.Conv2d(1, 32, kernel_size=3) Conv2D(32, (3, 3), input_shape=(28, 28, 1)) PyTorch 参数是输入通道, 输出通道;Keras 只写输出通道数,输入通道从 input_shape 里自己推
激活 F.relu() 在 forward 里手动调用 activation='relu' 层内参数 Keras 把激活函数并进卷积层
池化 nn.MaxPool2d(2) MaxPooling2D((2, 2)) 作用一样
展平 torch.flatten(x, start_dim=1) layers.Flatten() 独立成层 Keras 里展平是一层,会出现在 summary 里
全连接 nn.Linear(1600, 64) Dense(64) Keras 只写输出维度,输入维度自动推断——不用像 P1 那样手算 1600
输出 nn.Linear(64, 10) Dense(10) 10 个类别分数

整体感受:Keras 把"维度推导"全部藏进了框架内部,写起来快;PyTorch 要求你自己算清楚每一步的 shape。对我这种从 P2、P3 周一路手动推导 shape 过来的人来说,Keras 省事是省事,但前面练的推导功力在这里反而成了底气——summary() 里每个数字我都验算得出。

2. 模型结构与 shape 推导

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 26, 26, 32)        320       
                                                                 
 max_pooling2d (MaxPooling2D  (None, 13, 13, 32)       0         
 )                                                               
                                                                 
 conv2d_1 (Conv2D)           (None, 11, 11, 64)        18496     
                                                                 
 max_pooling2d_1 (MaxPooling  (None, 5, 5, 64)         0         
 2D)                                                             
                                                                 
 flatten (Flatten)           (None, 1600)              0         
                                                                 
 dense (Dense)               (None, 64)                102464    
                                                                 
 dense_1 (Dense)             (None, 10)                650       
                                                                 
=================================================================
Total params: 121,930
Trainable params: 121,930
Non-trainable params: 0
_________________________________________________________________

总参数量 121,930——和 P1 周 PyTorch 版的 torchinfo 输出一模一样,这也证明两边搭的确实是同一个网络。shape 推导规则和 P2 周学的完全相同(3×3 卷积无 padding,尺寸减 2;2×2 池化,尺寸减半):

输入 (28, 28, 1)
  ↓ Conv2D(32, 3×3)     (26, 26, 32)    28-2=26
  ↓ MaxPooling2D(2×2)   (13, 13, 32)    26/2=13
  ↓ Conv2D(64, 3×3)     (11, 11, 64)    13-2=11
  ↓ MaxPooling2D(2×2)   (5, 5, 64)      11/2=5(向下取整)
  ↓ Flatten             5×5×64 = 1600
  ↓ Dense(64)           64
  ↓ Dense(10)           10 个数字类别分数

参数量验算(方法见 P2、P4 周笔记):

计算式 参数量
conv2d 1 × 32 × 3 × 3 + 32 320
conv2d_1 32 × 64 × 3 × 3 + 64 18,496
dense 1600 × 64 + 64 102,464
dense_1 64 × 10 + 10 650
合计 121,930

Output Shape 里的 None 就是 batch 维度——Keras 用 None 表示"这一维不确定,运行时才知道",对应 PyTorch 打印里的 -1(P10 周刚踩过的那个概念)。


三、训练模型

1. 编译模型

"""
这里设置优化器、损失函数以及metrics
"""
# model.compile()方法用于在配置训练方法时,告知训练时用的优化器、损失函数和准确率评测标准
model.compile(
	# 设置优化器为Adam优化器
    optimizer='adam',
	# 设置损失函数为交叉熵损失函数(tf.keras.losses.SparseCategoricalCrossentropy())
    # from_logits为True时,会将y_pred转化为概率(用softmax),否则不进行转换,通常情况下用True结果更稳定
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    # 设置性能指标列表,将在模型训练时监控列表中的指标
    metrics=['accuracy'])

这一段的意思是compile 相当于把 P1 周散在各处的三样东西——优化器、损失函数、评估指标——一次性装配好。

  • optimizer='adam':P1 周用的是 SGD(lr=0.01),本周用 Adam(P9 周笔记里有 SGD 与 Adam 的详细对比);
  • loss=SparseCategoricalCrossentropy(from_logits=True):对应 P1 的 nn.CrossEntropyLoss(),两个关键词都值得拆开:
    • Sparse(稀疏):指标签直接给类别索引(如 5),而不是 one-hot 向量(如 [0,0,0,0,0,1,0,0,0,0])。MNIST 的标签本来就是 0-9 的整数,所以用 Sparse 版;P10 周那种 [7, 69] 的 one-hot 标签才需要普通版 CategoricalCrossentropy
    • from_logits=True:告诉损失函数"模型输出的是原始分数(logits),还没过 softmax",损失函数内部会先套 softmax 再算交叉熵。我们的输出层 Dense(10) 没接激活函数,输出的正是 logits,所以设 True,数值上更稳定。PyTorch 的 nn.CrossEntropyLoss() 其实也是同样的约定(期望 logits),只是没把这个开关暴露出来;
  • metrics=['accuracy']:训练时顺带监控准确率。PyTorch 里这行对应的是 P1 周自己在循环里写的 train_acc += (pred.argmax(1) == y)...——Keras 一句话搞定。

2. 训练模型

"""
这里设置输入训练数据集(图片及标签)、验证数据集(图片及标签)以及迭代次数epochs
关于model.fit()函数的具体介绍可参考我的博客:
https://blog.csdn.net/qq_38251616/article/details/122321757
"""
history = model.fit(
    # 输入训练集图片
	train_images, 
    # 输入训练集标签
	train_labels, 
    # 设置10个epoch,每一个epoch都将会把所有的数据输入模型完成一次训练。
	epochs=10, 
	# 设置验证集
    validation_data=(test_images, test_labels))

这一段的意思是model.fit() 是 Keras 高层 API 的集合,P1 周我们手写的整个训练循环(取 batch → 前向传播 → 算 loss → zero_grad()backward()step() → 统计 acc/loss → 每个 epoch 后再跑一遍测试循环),在这里被压缩成了一个函数调用。validation_data 传入测试集后,每个 epoch 结束会自动在测试集上评估一次,对应 P1 周手写的 test() 函数。

fit 默认 batch_size=32,所以每个 epoch 是 60000 / 32 = 1875 个 step——和 P1 周算过的"1875 个批次"对上了。返回值 history 里保存了每个 epoch 的 loss、accuracy、val_loss、val_accuracy,后面画曲线用。

Epoch 1/10
1875/1875 [==============================] - 9s 4ms/step - loss: 0.1321 - accuracy: 0.9594 - val_loss: 0.0465 - val_accuracy: 0.9844
Epoch 2/10
1875/1875 [==============================] - 9s 5ms/step - loss: 0.0459 - accuracy: 0.9856 - val_loss: 0.0369 - val_accuracy: 0.9876
Epoch 3/10
1875/1875 [==============================] - 7s 4ms/step - loss: 0.0324 - accuracy: 0.9897 - val_loss: 0.0323 - val_accuracy: 0.9894
Epoch 4/10
1875/1875 [==============================] - 5s 3ms/step - loss: 0.0233 - accuracy: 0.9924 - val_loss: 0.0297 - val_accuracy: 0.9905
Epoch 5/10
1875/1875 [==============================] - 8s 4ms/step - loss: 0.0186 - accuracy: 0.9939 - val_loss: 0.0281 - val_accuracy: 0.9911
Epoch 6/10
1875/1875 [==============================] - 9s 5ms/step - loss: 0.0131 - accuracy: 0.9956 - val_loss: 0.0342 - val_accuracy: 0.9905
Epoch 7/10
1875/1875 [==============================] - 9s 5ms/step - loss: 0.0109 - accuracy: 0.9967 - val_loss: 0.0379 - val_accuracy: 0.9886
Epoch 8/10
1875/1875 [==============================] - 9s 5ms/step - loss: 0.0096 - accuracy: 0.9969 - val_loss: 0.0346 - val_accuracy: 0.9900
Epoch 9/10
1875/1875 [==============================] - 8s 4ms/step - loss: 0.0063 - accuracy: 0.9980 - val_loss: 0.0312 - val_accuracy: 0.9922
Epoch 10/10
1875/1875 [==============================] - 8s 4ms/step - loss: 0.0070 - accuracy: 0.9977 - val_loss: 0.0385 - val_accuracy: 0.9911

训练结果分析

本次共训练 10 个 epoch。训练侧收敛良好:训练 loss 从 0.1321 持续降至 0.0070(降幅约 95%),训练准确率从 95.94% 升至 99.77%。验证侧表现同样稳健:第 1 个 epoch 验证准确率就达到 98.44%,第 9 个 epoch 达到最高的 99.22%,最终第 10 轮为 99.11%。

与 PyTorch P1 训练结果对比

P1 的实际训练结果为:

Epoch: 1, Train_acc:75.0%, Train_loss:0.812, Test_acc:91.3%, Test_loss:0.279
Epoch: 2, Train_acc:93.9%, Train_loss:0.203, Test_acc:95.6%, Test_loss:0.135
Epoch: 3, Train_acc:96.2%, Train_loss:0.127, Test_acc:97.1%, Test_loss:0.095
Epoch: 4, Train_acc:97.1%, Train_loss:0.097, Test_acc:97.5%, Test_loss:0.079
Epoch: 5, Train_acc:97.5%, Train_loss:0.082, Test_acc:98.1%, Test_loss:0.062

P1 最终:

Train Accuracy = 97.5%
Test Accuracy  = 98.1%

T1 最佳:

Train Accuracy = 99.80%
Val Accuracy   = 99.22%

对比:

指标 PyTorch P1 TensorFlow T1
网络结构 两层 CNN 两层 CNN
参数量 121,930 121,930
batch_size 32 默认 32
优化器 SGD Adam
学习率 0.01 默认约 0.001
epochs 5 10
P1 / T1 最佳验证准确率 98.1% 99.22%
最终训练准确率 97.5% 99.77%
最终验证准确率 98.1% 99.11%

如果只看最终数字,TensorFlow T1 的最佳验证准确率比 P1 高,但这里**不能直接得出“TensorFlow 比 PyTorch 更准确”**的结论。
因为两次实验同时改变了:

  1. 优化器:SGD → Adam;
  2. 学习率不同;
  3. 训练轮数:5 → 10;
  4. 运行设备:CPU → AMD GPU;
  5. 两个框架内部的初始化和训练实现也可能存在差异。

四、模型预测

1. 查看测试图片

plt.imshow(test_images[1])

![在这里插入测试图片]

2. 输出预测结果

pre = model.predict(test_images) # 对所有测试图片进行预测
pre[1] # 输出第一张图片的预测结果
array([ -8.327816 ,   6.986119 ,  32.324245 , -17.994097 , -12.542312 ,
       -31.822721 ,  -7.8399806, -19.464483 , -16.72772  , -13.3654   ],
      dtype=float32)

pre 的 shape 是 (10000, 10)——每张测试图片对应 10 个输出值,分别代表这张图片是数字 0-9 中每一个的可能性(并非概率),数值越大可能性越大。因为编译时设了 from_logits=True,输出层没有接 softmax,所以这 10 个数是原始 logits(可正可负、加起来不等于 1)。取预测类别的标准做法:

import numpy as np
pre[1].argmax()   

输出:2,与图片上的真实数字一致,和 P1 周 PyTorch 版的 pred.argmax(1) 是同一个操作,只是 PyTorch 里我们写在 test 函数里批量做,这里单独对一张图做。


五、绘制 Accuracy 与 Loss 曲线

TensorFlow 的 model.fit() 会返回history,其中已经保存了每个 epoch 的训练记录。可以直接绘图:

import matplotlib.pyplot as plt
from datetime import datetime

# 获取当前时间戳字符串
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

plt.figure(figsize=(12, 4))

plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Training Accuracy')
plt.plot(history.history['val_accuracy'], label='Validation Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title(f'Training and Validation Accuracy | Time: {timestamp}')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title(f'Training and Validation Loss | Time: {timestamp}')
plt.legend()

plt.tight_layout()
# 带时间戳保存图片
plt.savefig(f'train_curve_{timestamp}.png', dpi=300, bbox_inches='tight')
plt.show()

在这里插入图片描述


总结

本周是 TensorFlow 入门的 T1 周,完成的任务仍然是经典的 MNIST 手写数字识别。如果只看数据集和 CNN 结构,这一周与 PyTorch P1 非常相似:两者都使用 60000 张训练图片和 10000 张测试图片,输入都是 28×28 灰度图,网络都由两层 3×3 卷积、两层最大池化和两层全连接组成,最终 Flatten 后都是 1600 个特征,甚至两套模型的参数量都完全相同,都是 121,930
TensorFlow 与 PyTorch 的区别:
第一个明显区别是数据格式。PyTorch 默认常用 NCHW,所以 P1 中一个 batch 是 [32,1,28,28];TensorFlow 默认常用 NHWC,所以同样的数据在 T1 中是 [32,28,28,1]。两者表示的是完全相同的图片,只是通道维的位置不同。
第二个区别是训练代码的封装程度。PyTorch P1 中我需要自己编写训练和测试函数,并明确写出 optimizer.zero_grad()loss.backward()optimizer.step();TensorFlow 中这些步骤全部被封装进了 model.fit()。因此 TensorFlow 看起来代码更简单,但背后仍然进行了前向传播、损失计算、反向传播和参数更新。
第三个比较重要的收获是理解了损失函数与 logits。TensorFlow 最后一层 Dense(10) 没有 Softmax,所以使用 SparseCategoricalCrossentropy(from_logits=True);PyTorch P1 的 Linear(64,10) 同样没有 Softmax,而 CrossEntropyLoss() 可以直接处理 logits。原来看起来完全不同的两套代码,在底层逻辑上实际上是对应的。

与 PyTorch P1 相比,P1 第 5 轮测试准确率为 98.1%,而 T1 最佳验证准确率达到 99.22%。不过不能因此直接认为 TensorFlow 比 PyTorch 更强,因为两次实验的优化器、学习率、训练轮数和运行设备都不同。同一个数据集、同一个 CNN,可以使用完全不同的框架写法完成,但底层的数据流、卷积、损失、反向传播和参数更新逻辑仍然是相通的。

Logo

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐