使用估算器、tf.keras 和 tf.data 进行多 GPU 训练

部署运行你感兴趣的模型镜像

文 / Zalando Research 研究科学家 Kashif Rasul

来源 | TensorFlow 公众号

与大多数 AI 研究部门一样,Zalando Research 也意识到了对创意进行尝试和快速原型设计的重要性。随着数据集变得越来越庞大,了解如何利用我们拥有的共享资源来高效快速地训练深度学习模型变得大有用处。

TensorFlow 的估算器 API 对于在分布式环境中使用多个 GPU 来训练模型非常有用。本文将主要介绍这一工作流程。我们先使用 Fashion-MNIST 小数据集训练一个用 tf.keras 编写的自定义估算器,然后在文末介绍一个较实际的用例。

请注意:TensorFlow 团队一直在开发另一项很酷的新功能(在我写这篇文章时,该功能仍处于 Master 阶段),使用这项新功能,您只需多输入几行代码即可训练 tf.keras 模型, 而无需先将该模型转化为估算器!其工作流程也很赞。下面我着重讲讲估算器 API。选择哪一个由您自己决定!
注:功能链接
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/distribute/python/examples/keras_mnist.py#L106-L114

TL; DR:基本上,我们需要记住,对于 tf.keras. 模型,我们只要通过 tf.keras.estimator.model_to_estimator 方法将其转化为 tf.estimator.Estimator 对象,即可使用 tf.estimator API 来进行训练。转化完成后,我们可以使用估算器提供的机制用不同的硬件配置训练模型。

您可以从此笔记本下载本文中的代码并亲自运行。
注:笔记本链接
https://github.com/kashif/tf-keras-tutorial/blob/master/7-estimators-multi-gpus.ipynb

import os
import time

#!pip install -q -U tensorflow-gpu
import tensorflow as tf

import numpy as np

导入 Fashion-MNIST 数据集
我们用 Fashion-MNIST 数据集随手替换一下 MNIST,这里面包含几千张 Zalando 时尚文章的灰度图像。获取训练和测试数据非常简单,如下所示:

(train_images, train_labels), (test_images, test_labels) = 
   tf.keras.datasets.fashion_mnist.load_data()

我们想把这些图像的像素值从 0 到 255 之间的一个数字转换为 0 到 1 之间的一个数字,并将该数据集转换为 [B, H, W ,C] 格式,其中 B 代表批处理的图像数,H 和 W 分别是高度和宽度,C 是我们数据集的通道数(灰度为 1):

TRAINING_SIZE = len(train_images)
TEST_SIZE = len(test_images)

train_images = np.asarray(train_images, dtype=np.float32) / 255
# Convert the train images and add channels
train_images = train_images.reshape((TRAINING_SIZE, 28, 28, 1))

test_images = np.asarray(test_images, dtype=np.float32) / 255
# Convert the test images and add channels
test_images = test_images.reshape((TEST_SIZE, 28, 28, 1))

接下来,我们想将标签从整数编号(例如,2 或套衫)转换为独热编码(例如,0,0,1,0,0,0,0,0,0,0)。为此,我们要使用 tf.keras.utils.to_categorical 函数:

# How many categories we are predicting from (0-9)
LABEL_DIMENSIONS = 10

train_labels = tf.keras.utils.to_categorical(train_labels, 
                                            LABEL_DIMENSIONS)

test_labels = tf.keras.utils.to_categorical(test_labels,
                                           LABEL_DIMENSIONS)

# Cast the labels to floats, needed later
train_labels = train_labels.astype(np.float32)
test_labels = test_labels.astype(np.float32)

构建 tf.keras 模型
我们会使用 Keras 功能 API 来创建神经网络。Keras 是一个高级 API,可用于构建和训练深度学习模型,其采用模块化设计,使用方便,易于扩展。tf.keras 是 TensorFlow 对这个 API 的实现,其支持 Eager Execution、tf.data 管道和估算器等。

在架构方面,我们会使用 ConvNet。一个非常笼统的说法是,ConvNet 是卷积层 (Conv2D) 和池化层 (MaxPooling2D) 的堆栈。但最重要的是,ConvNet 将每个训练示例当作一个 3D 形状张量(高度、宽度、通道),对于灰度图像,张量从通道 = 1 开始,然后返回一个 3D 张量。

因此,在 ConvNet 部分之后,我们需要将张量平面化,并添加密集层,其中最后一个返回 LABEL_DIMENSIONS 大小的向量,并附带 tf.nn.softmax 激活:

inputs = tf.keras.Input(shape=(28,28,1))  # Returns a placeholder

x = tf.keras.layers.Conv2D(filters=32, 
                          kernel_size=(3, 3), 
                          activation=tf.nn.relu)(inputs)

x = tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=2)(x)

x = tf.keras.layers.Conv2D(filters=64, 
                          kernel_size=(3, 3), 
                          activation=tf.nn.relu)(x)

x = tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=2)(x)

x = tf.keras.layers.Conv2D(filters=64, 
                          kernel_size=(3, 3), 
                          activation=tf.nn.relu)(x)

x = tf.keras.layers.Flatten()(x)

x = tf.keras.layers.Dense(64, activation=tf.nn.relu)(x)
predictions = tf.keras.layers.Dense(LABEL_DIMENSIONS,
                                   activation=tf.nn.softmax)(x)

现在,我们可以定义学习模型,请选择优化器(我们从 TensorFlow 中选择一个,而不使用来自 tf.keras. optimizers 的优化器)并进行编译:

model = tf.keras.Model(inputs=inputs, outputs=predictions)

optimizer = tf.train.AdamOptimizer(learning_rate=0.001)

model.compile(loss='categorical_crossentropy',
             optimizer=optimizer,
             metrics=['accuracy'])

创建估算器
使用已编译的 Keras 模型创建估算器,也就是我们所说的 model_to_estimator 方法。请注意,Keras 模型的初始模型状态保存在创建的估算器中。

那估算器有哪些优点呢?首先要提以下几点:

  • 您可以在本地主机或分布式多 GPU 环境中运行基于估算器的模型,而无需更改您的模型;
  • 估算器能够简化模型开发者之间的共享实现;
  • 估算器能够为您构建图形,所以有点像 Eager Execution,没有明确的会话。

那么我们要如何训练简单的 tf.keras 模型来使用多 GPU?我们可以使用 tf.contrib.distribute.MirroredStrategy 范式,通过同步训练进行图形内复制。如需了解更多关于此策略的信息,请观看分布式 TensorFlow 训练讲座。
注:分布式 TensorFlow 链接
https://www.youtube.com/watch?v=bRMGoPqsn20

基本上,每个工作器 GPU 都有一个网络拷贝,并会获取一个数据子集,据以计算本地梯度,然后等待所有工作器以同步方式结束。然后,工作器通过 Ring All-reduce 运算互相传递其本地梯度,这通常要进行优化,以减少网络带宽并增加吞吐量。在所有梯度到达后,每个工作器会计算其平均值并更新参数,然后开始下一步。理想情况下,您在单个节点上有多个高速互联的 GPU。

要使用此策略,我们首先要用已编译的 tf.keras 模型创建一个估算器,然后通过 RunConfig config 赋予其 MirroredStrategy 配置。默认情况下,该配置会使用全部 GPU,但您也可以赋予其一个 num_gpus 选项,以使用特定数量的 GPU:

NUM_GPUS = 2

strategy = tf.contrib.distribute.MirroredStrategy(num_gpus=NUM_GPUS)
config = tf.estimator.RunConfig(train_distribute=strategy)

estimator = tf.keras.estimator.model_to_estimator(model,
                                                 config=config)

创建估算器输入函数
要通过管道将数据传递到估算器,我们需要定义一个数据导入函数,该函数返回批量数据的 tf.data 数据集(图像、标签)。下面的函数接收 numpy 数组,并通过 ETL 过程返回数据集。

请注意,最后我们还调用了预读取方法,该方法会在训练时将数据缓冲到 GPU,以便下一批数据准备就绪并等待 GPU,而不是在每次迭代时让 GPU 等待数据。GPU 可能仍然没有得到充分利用,要改善这一点,我们可以使用融合版转换运算(如 shuffle_and_repeat),而不是两个单独的运算。不过,我在这里选用的是简单用例。

def input_fn(images, labels, epochs, batch_size):

     # Convert the inputs to a Dataset. (E)
    ds = tf.data.Dataset.from_tensor_slices((images, labels))    

    # Shuffle, repeat, and batch the examples. (T)
    SHUFFLE_SIZE = 5000
    ds = ds.shuffle(SHUFFLE_SIZE).repeat(epochs).batch(batch_size)
    ds = ds.prefetch(2)    

    # Return the dataset. (L)
    return ds

训练估算器
首先,我们定义一个 SessionRunHook 类,用于记录随机梯度下降法每次迭代的次数:

class TimeHistory(tf.train.SessionRunHook):
    def begin(self):
       self.times = []    

    def before_run(self, run_context):
       self.iter_time_start = time.time()    

    def after_run(self, run_context, run_values):
       self.times.append(time.time() - self.iter_time_start)

亮点在这里!我们可以对估算器调用 train 函数,并通过 hooks 参数,向其赋予我们定义的 input_fn (包含批次大小和我们希望的训练回合次数)和 TimeHistory 实例:

time_hist = TimeHistory()

BATCH_SIZE = 512
EPOCHS = 5

estimator.train(lambda:input_fn(train_images,
                               train_labels,
                               epochs=EPOCHS,
                               batch_size=BATCH_SIZE),
               hooks=[time_hist])

性能
现在,我们可以使用时间钩子来计算训练的总时间和平均每秒训练的图像数量(平均吞吐量):

total_time = sum(time_hist.times)
print(f"total time with {NUM_GPUS} GPU(s): {total_time} seconds")

avg_time_per_batch = np.mean(time_hist.times)
print(f"{BATCH_SIZE*NUM_GPUS/avg_time_per_batch} images/second with
       {NUM_GPUS} GPU(s)")

使用两块 K80 GPU 进行训练时的 Fashion-MNIST 训练吞吐量和总时间,采用不同 NUM_GPUS,显示缩放不良

评估估算器
为了检验模型的性能,我们要对估算器调用评估方法:

estimator.evaluate(lambda:input_fn(test_images, 
                                  test_labels,
                                  epochs=1,
                                  batch_size=BATCH_SIZE))

视网膜 OCT (光学相干断层成像术)图像示例
为了测试模型在处理较大数据集时的扩展性能,我们使用 视网膜 OCT 图像数据集,这是 Kaggle 众多大型数据集中的一个。该数据集由活人视网膜的横截面 X 光图像组成,分为四个类别:NORMAL、CNV、DME 和 DRUSEN:

光学相干断层成像术的代表图像,选自 Kermany 等人所著的《通过基于图像的深度学习技术确定医学诊断和可治疗疾病》(Identifying Medical Diagnoses and Treatable Diseases by Image-Based Deep Learning)

该数据集共有 84,495 张 JPEG 格式的 X 光图像,尺寸多为 512x496,可以通过 Kaggle CLI 下载:
注:CLI 链接
https://github.com/Kaggle/kaggle-api

#!pip install kaggle
#!kaggle datasets download -d paultimothymooney/kermany2018

下载完成后,训练集和测试集图像类位于各自的文件夹内,因此我们可以将模式定义为:

labels = ['CNV', 'DME', 'DRUSEN', 'NORMAL']

train_folder = os.path.join('OCT2017', 'train', '**', '*.jpeg')
test_folder = os.path.join('OCT2017', 'test', '**', '*.jpeg')

接下来,我们要编写估算器的输入函数,该函数可以提取任何文件模式,并返回已缩放图像和独热编码标签作为 tf.data.Dataset。这次,我们遵循输入管道性能指南中的最佳实践。请特别注意,如果 prefetch 的 buffer_size 为 None,则 TensorFlow 会自动使用最优的预读取缓冲区大小:
注:输入管道性能指南链接
https://www.tensorflow.org/performance/datasets_performance

1    def input_fn(file_pattern, labels, 
2                        image_size=(224,224), 
3                        shuffle=False,
4                        batch_size=64, 
5                        num_epochs=None,
6                        buffer_size=4096,
7                        prefetch_buffer_size=None): 
8
9            table = tf.contrib.lookup.index_table_from_tensor(mapping=tf.constant(labels))
10          num_classes = len(labels) 
11
12          def _map_func(filename):
13                label = tf.string_split([filename], delimiter=os.sep).values[-2]
14                image = tf.image.decode_jpeg(tf.read_file(filename), channels=3)
15                image = tf.image.convert_image_dtype(image, dtype=tf.float32) 
16                image = tf.image.resize_images(image, size=image_size)
17                return (image, tf.one_hot(table.lookup(label), num_classes))
18
19          dataset = tf.data.Dataset.list_files(file_pattern, shuffle=shuffle)
20
21          if num_epochs is not None and shuffle:
22                dataset = dataset.apply(
23                    tf.contrib.data.shuffle_and_repeat(buffer_size, num_epochs))
24          elif shuffle:
25                dataset = dataset.shuffle(buffer_size)
26          elif num_epochs is not None:
27                dataset = dataset.repeat(num_epochs)
28
29          dataset = dataset.apply(
30                tf.contrib.data.map_and_batch(map_func=_map_func,
31                                        batch_size=batch_size,
32                                        num_parallel_calls=os.cpu_count()))
33          dataset = dataset.prefetch(buffer_size=prefetch_buffer_size)
34
35          return dataset 

这次训练该模型时,我们将使用一个经过预训练的 VGG16,并且只重新训练其最后 5 层:

keras_vgg16 = tf.keras.applications.VGG16(input_shape=(224,224,3),
                                         include_top=False)

output = keras_vgg16.output
output = tf.keras.layers.Flatten()(output)
prediction = tf.keras.layers.Dense(len(labels),
                                  activation=tf.nn.softmax)(output)

model = tf.keras.Model(inputs=keras_vgg16.input,
                      outputs=prediction)

for layer in keras_vgg16.layers[:-4]:
   layer.trainable = False

现在,我们万事皆备,可以按照上述步骤进行,并使用 NUM_GPUS GPU 在几分钟内训练我们的模型:

model.compile(loss='categorical_crossentropy',               optimizer=tf.train.AdamOptimizer(),              metrics=['accuracy'])

NUM_GPUS = 2
strategy = tf.contrib.distribute.MirroredStrategy(num_gpus=NUM_GPUS)
config = tf.estimator.RunConfig(train_distribute=strategy)
estimator = tf.keras.estimator.model_to_estimator(model,                                                  config=config)
BATCH_SIZE = 64
EPOCHS = 1

estimator.train(input_fn=lambda:input_fn(train_folder,                                         labels,                                         shuffle=True,                                         batch_size=BATCH_SIZE,                                         buffer_size=2048,                                         num_epochs=EPOCHS,                                         prefetch_buffer_size=4),                hooks=[time_hist])

训练结束后,我们可以评估测试集的准确度,应该在 95% 左右(对初始基线来说还不错):

estimator.evaluate(input_fn=lambda:input_fn(test_folder,
                                           labels, 
                                           shuffle=False,
                                           batch_size=BATCH_SIZE,
                                           buffer_size=1024,
                                           num_epochs=1))

使用两块 K80 GPU 进行训练时的 Fashion-MNIST 训练吞吐量和总时间,采用不同 NUM_GPUS,显示线性缩放

总结
我们在上文中介绍了如何使用估算器 API 在多个 GPU 上轻松训练 Keras 深度学习模型,如何编写符合最佳实践的输入管道,以充分利用我们的资源(线性缩放),以及如何通过钩子为我们的训练吞吐量计时。

请务必注意,最后我们主要关注的是测试集错误。您可能会注意到,测试集的准确度会随着 NUM_GPUS 值的增加而下降。其中一个原因可能是,使用 BATCH_SIZE*NUM_GPUS 的批量大小时,MirroredStrategy 能够有效地训练模型,而当我们增加 GPU 数量时,可能需要调整 BATCH_SIZE 或学习率。为便于制图,文中除 NUM_GPUS 之外的所有其他超参数均保持不变,但实际上我们需要调整这些超参数。

数据集和模型的大小也会影响这些方案的缩放效果。在读取或写入小数据时,GPU 的带宽较差,如果是较为老旧的 GPU(如 K80),则情形尤其如此,而且可能会造成上面 Fashion-MNIST 图中所示情况。

致谢
感谢 TensorFlow 团队,特别是 Josh Gordon,以及 Zalando Research 的各位同事,特别是 Duncan Blythe、Gokhan Yildirim 和 Sebastian Heinz,感谢他们帮忙修改草稿。

您可能感兴趣的与本文相关的镜像

TensorFlow-v2.15

TensorFlow-v2.15

TensorFlow

TensorFlow 是由Google Brain 团队开发的开源机器学习框架,广泛应用于深度学习研究和生产环境。 它提供了一个灵活的平台,用于构建和训练各种机器学习模型

import tensorflow as tf import numpy as np import cv2 import os import json from tqdm import tqdm class ObjectRecognitionDeployer: def __init__(self, model_path, class_labels): """ 初始化部署 :param model_path: 模型文件路径 (KerasTFLite) :param class_labels: 类别标签列表 """ self.class_labels = class_labels self.model_path = model_path self.interpreter = None self.input_details = None self.output_details = None # 根据模型类型加载 if model_path.endswith('.tflite'): self.load_tflite_model(model_path) else: self.model = tf.keras.models.load_model(model_path) self.input_shape = self.model.input_shape[1:3] def load_tflite_model(self, model_path): """加载并配置TFLite模型""" # 加载模型 self.interpreter = tf.lite.Interpreter(model_path=model_path) self.interpreter.allocate_tensors() # 获取输入输出详细信息 self.input_details = self.interpreter.get_input_details() self.output_details = self.interpreter.get_output_details() # 保存输入形状 self.input_shape = tuple(self.input_details[0]['shape'][1:3]) # 安全地打印模型元数据 self.print_model_metadata(model_path) def print_model_metadata(self, model_path): """安全地打印TFLite模型元数据""" try: from tflite_support import metadata displayer = metadata.MetadataDisplayer.with_model_file(model_path) print("--- 模型元数据 ---") print(displayer.get_metadata_json()) print("--- 关联文件 ---") print(displayer.get_packed_associated_file_list()) except (ImportError, ValueError) as e: print(f"警告: 无法获取模型元数据 - {str(e)}") print("使用输入/输出详细信息代替:") print(f"输入: {self.input_details}") print(f"输出: {self.output_details}") def preprocess_image(self, image, input_size, input_dtype=np.float32): """ 预处理图像 :param image: 输入图像 (numpy数组或文件路径) :param input_size: 模型输入尺寸 (height, width) :param input_dtype: 期望的输入数据类型 :return: 预处理后的图像张量 """ if isinstance(image, str): if not os.path.exists(image): raise FileNotFoundError(f"图像文件不存在: {image}") img = cv2.imread(image) if img is None: raise ValueError(f"无法读取图像: {image}") else: img = image # 调整尺寸颜色空间 img = cv2.resize(img, (input_size[1], input_size[0])) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 根据数据类型进行归一化 if input_dtype == np.uint8: img = img.astype(np.uint8) # 量化模型使用uint8 else: # 浮点模型使用float32 img = img.astype(np.float32) / 255.0 # 添加批次维度 img = np.expand_dims(img, axis=0) return img def predict(self, image): """ 执行预测 :param image: 输入图像 (numpy数组或文件路径) :return: 预测结果 (类别名称, 置信度) """ if self.interpreter is not None: # TFLite模型推理 return self.predict_tflite(image) else: # Keras模型推理 return self.predict_keras(image) def predict_keras(self, image): """使用Keras模型预测""" # 预处理 img = self.preprocess_image(image, self.input_shape, np.float32) # 预测 predictions = self.model.predict(img, verbose=0)[0] class_idx = np.argmax(predictions) confidence = predictions[class_idx] class_name = self.class_labels[class_idx] return class_name, confidence def predict_tflite(self, image): """使用TFLite模型预测""" # 获取输入数据类型 input_dtype = self.input_details[0]['dtype'] # 预处理 img = self.preprocess_image(image, self.input_shape, input_dtype) # 设置输入张量 self.interpreter.set_tensor(self.input_details[0]['index'], img) # 执行推理 self.interpreter.invoke() # 获取输出 output_data = self.interpreter.get_tensor(self.output_details[0]['index']) predictions = output_data[0] # 解析结果 class_idx = np.argmax(predictions) confidence = predictions[class_idx] # 如果输出是量化数据,需要反量化 if self.output_details[0]['dtype'] == np.uint8: # 反量化输出 scale, zero_point = self.output_details[0]['quantization'] confidence = scale * (confidence - zero_point) class_name = self.class_labels[class_idx] return class_name, confidence def benchmark(self, image, runs=100): """ 模型性能基准测试 :param image: 测试图像 :param runs: 运行次数 :return: 平均推理时间(ms), 内存占用(MB) """ # 预热运行 self.predict(image) # 计时测试 start_time = tf.timestamp() for _ in range(runs): self.predict(image) end_time = tf.timestamp() avg_time_ms = (end_time - start_time).numpy() * 1000 / runs # 内存占用 if self.interpreter: # 计算输入张量内存占用 input_size = self.input_details[0]['shape'] dtype_size = np.dtype(self.input_details[0]['dtype']).itemsize mem_usage = np.prod(input_size) * dtype_size / (1024 * 1024) else: # 估算Keras模型内存 mem_usage = self.model.count_params() * 4 / (1024 * 1024) # 假设32位浮点数 return avg_time_ms, mem_usage def create_metadata(self, output_path): """ 创建并保存模型元数据文件 :param output_path: 元数据文件输出路径 """ metadata = { "model_type": "tflite" if self.model_path.endswith('.tflite') else "keras", "class_labels": self.class_labels, "input_size": self.input_shape, "input_dtype": str(self.input_details[0]['dtype']) if self.interpreter else "float32", "quantization": None } if self.interpreter and self.input_details[0]['dtype'] == np.uint8: metadata["quantization"] = { "input_scale": float(self.input_details[0]['quantization'][0]), "input_zero_point": int(self.input_details[0]['quantization'][1]), "output_scale": float(self.output_details[0]['quantization'][0]), "output_zero_point": int(self.output_details[0]['quantization'][1]) } with open(output_path, 'w') as f: json.dump(metadata, f, indent=4) return metadata def convert_to_tflite_with_metadata(self, output_path, quantize=False, representative_data_dir=None): """ 将Keras模型转换为TFLite格式并添加元数据 :param output_path: 输出TFLite文件路径 :param quantize: 是否进行量化 :param representative_data_dir: 代表性数据集目录 """ if not self.model_path.endswith(('.keras', '.h5')): raise ValueError("需要Keras模型格式进行转换") # 加载Keras模型 keras_model = tf.keras.models.load_model(self.model_path) # 创建转换 converter = tf.lite.TFLiteConverter.from_keras_model(keras_model) if quantize: # 量化配置 converter.optimizations = [tf.lite.Optimize.DEFAULT] # 设置代表性数据集生成 converter.representative_dataset = lambda: self.representative_dataset( representative_data_dir, input_size=self.input_shape ) # 设置输入输出类型 converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] converter.inference_input_type = tf.uint8 converter.inference_output_type = tf.uint8 # 转换模型 tflite_model = converter.convert() # 保存模型 with open(output_path, 'wb') as f: f.write(tflite_model) print(f"TFLite模型已保存到: {output_path}") # 添加元数据 self.add_tflite_metadata(output_path) return output_path def representative_dataset(self, data_dir=None, input_size=(224, 224), num_samples=100): """ 生成代表性数据集用于量化 :param data_dir: 真实数据目录 :param input_size: 输入尺寸 (height, width) :param num_samples: 样本数量 """ # 优先使用真实数据 if data_dir and os.path.exists(data_dir): image_files = [os.path.join(data_dir, f) for f in os.listdir(data_dir) if f.lower().endswith(('.png', '.jpg', '.jpeg'))] # 限制样本数量 image_files = image_files[:min(len(image_files), num_samples)] print(f"使用 {len(image_files)} 张真实图像进行量化校准") for img_path in tqdm(image_files, desc="量化校准"): try: # 读取并预处理图像 img = cv2.imread(img_path) if img is None: continue img = cv2.resize(img, (input_size[1], input_size[0])) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = img.astype(np.float32) / 255.0 # 转换为float32并归一化 img = np.expand_dims(img, axis=0) yield [img] except Exception as e: print(f"处理图像 {img_path} 时出错: {str(e)}") else: # 使用随机数据作为备选 print(f"使用随机数据生成 {num_samples} 个样本进行量化校准") for _ in range(num_samples): # 生成随机图像,归一化到[0,1]范围,使用float32类型 data = np.random.rand(1, input_size[0], input_size[1], 3).astype(np.float32) yield [data] def add_tflite_metadata(self, model_path): """为TFLite模型添加元数据""" # 创建标签文件 labels_path = os.path.join(os.path.dirname(model_path), "labels.txt") with open(labels_path, 'w') as f: for label in self.class_labels: f.write(f"{label}\n") # 创建元数据 metadata_path = os.path.join(os.path.dirname(model_path), "metadata.json") self.create_metadata(metadata_path) print(f"元数据已创建: {metadata_path}") print(f"标签文件已创建: {labels_path}") # 使用示例 if __name__ == "__main__": # 类别标签 CLASS_LABELS = ['book', 'cup', 'glasses', 'phone', 'shoe'] # 初始化部署 deployer = ObjectRecognitionDeployer( model_path='optimized_model.keras', class_labels=CLASS_LABELS ) # 转换为带元数据的TFLite格式 tflite_path = 'model_quantized.tflite' # 使用真实数据目录进行量化校准 REPRESENTATIVE_DATA_DIR = 'path/to/representative_dataset' # 替换为实际路径 deployer.convert_to_tflite_with_metadata( tflite_path, quantize=True, representative_data_dir=REPRESENTATIVE_DATA_DIR ) # 重新加载带元数据的模型 tflite_deployer = ObjectRecognitionDeployer( model_path=tflite_path, class_labels=CLASS_LABELS ) # 测试预测 test_image = 'test_image.jpg' class_name, confidence = tflite_deployer.predict(test_image) print(f"预测结果: {class_name}, 置信度: {confidence:.2f}") # 性能测试 avg_time, mem_usage = tflite_deployer.benchmark(test_image) print(f"平均推理时间: {avg_time:.2f} ms") print(f"内存占用: {mem_usage:.2f} MB") # 创建元数据文件 metadata = deployer.create_metadata('model_metadata.json') print("模型元数据:", json.dumps(metadata, indent=4)) import tensorflow as tf import numpy as np import cv2 import os import json from tqdm import tqdm class ObjectRecognitionDeployer: def __init__(self, model_path, class_labels): """ 初始化部署 :param model_path: 模型文件路径 (KerasTFLite) :param class_labels: 类别标签列表 """ self.class_labels = class_labels self.model_path = model_path self.interpreter = None self.input_details = None self.output_details = None # 根据模型类型加载 if model_path.endswith('.tflite'): self.load_tflite_model(model_path) else: self.model = tf.keras.models.load_model(model_path) self.input_shape = self.model.input_shape[1:3] def load_tflite_model(self, model_path): """加载并配置TFLite模型""" # 加载模型 self.interpreter = tf.lite.Interpreter(model_path=model_path) self.interpreter.allocate_tensors() # 获取输入输出详细信息 self.input_details = self.interpreter.get_input_details() self.output_details = self.interpreter.get_output_details() # 保存输入形状 self.input_shape = tuple(self.input_details[0]['shape'][1:3]) # 安全地打印模型元数据 self.print_model_metadata(model_path) def print_model_metadata(self, model_path): """安全地打印TFLite模型元数据""" try: from tflite_support import metadata displayer = metadata.MetadataDisplayer.with_model_file(model_path) print("--- 模型元数据 ---") print(displayer.get_metadata_json()) print("--- 关联文件 ---") print(displayer.get_packed_associated_file_list()) except (ImportError, ValueError) as e: print(f"警告: 无法获取模型元数据 - {str(e)}") print("使用输入/输出详细信息代替:") print(f"输入: {self.input_details}") print(f"输出: {self.output_details}") def preprocess_image(self, image, input_size, input_dtype=np.float32): """ 预处理图像 :param image: 输入图像 (numpy数组或文件路径) :param input_size: 模型输入尺寸 (height, width) :param input_dtype: 期望的输入数据类型 :return: 预处理后的图像张量 """ if isinstance(image, str): if not os.path.exists(image): raise FileNotFoundError(f"图像文件不存在: {image}") img = cv2.imread(image) if img is None: raise ValueError(f"无法读取图像: {image}") else: img = image # 调整尺寸颜色空间 img = cv2.resize(img, (input_size[1], input_size[0])) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 根据数据类型进行归一化 if input_dtype == np.uint8: img = img.astype(np.uint8) # 量化模型使用uint8 else: # 浮点模型使用float32 img = img.astype(np.float32) / 255.0 # 添加批次维度 img = np.expand_dims(img, axis=0) return img def predict(self, image): """ 执行预测 :param image: 输入图像 (numpy数组或文件路径) :return: 预测结果 (类别名称, 置信度) """ if self.interpreter is not None: # TFLite模型推理 return self.predict_tflite(image) else: # Keras模型推理 return self.predict_keras(image) def predict_keras(self, image): """使用Keras模型预测""" # 预处理 img = self.preprocess_image(image, self.input_shape, np.float32) # 预测 predictions = self.model.predict(img, verbose=0)[0] class_idx = np.argmax(predictions) confidence = predictions[class_idx] class_name = self.class_labels[class_idx] return class_name, confidence def predict_tflite(self, image): """使用TFLite模型预测""" # 获取输入数据类型 input_dtype = self.input_details[0]['dtype'] # 预处理 img = self.preprocess_image(image, self.input_shape, input_dtype) # 设置输入张量 self.interpreter.set_tensor(self.input_details[0]['index'], img) # 执行推理 self.interpreter.invoke() # 获取输出 output_data = self.interpreter.get_tensor(self.output_details[0]['index']) predictions = output_data[0] # 解析结果 class_idx = np.argmax(predictions) confidence = predictions[class_idx] # 如果输出是量化数据,需要反量化 if self.output_details[0]['dtype'] == np.uint8: # 反量化输出 scale, zero_point = self.output_details[0]['quantization'] confidence = scale * (confidence - zero_point) class_name = self.class_labels[class_idx] return class_name, confidence def benchmark(self, image, runs=100): """ 模型性能基准测试 :param image: 测试图像 :param runs: 运行次数 :return: 平均推理时间(ms), 内存占用(MB) """ # 预热运行 self.predict(image) # 计时测试 start_time = tf.timestamp() for _ in range(runs): self.predict(image) end_time = tf.timestamp() avg_time_ms = (end_time - start_time).numpy() * 1000 / runs # 内存占用 if self.interpreter: # 计算输入张量内存占用 input_size = self.input_details[0]['shape'] dtype_size = np.dtype(self.input_details[0]['dtype']).itemsize mem_usage = np.prod(input_size) * dtype_size / (1024 * 1024) else: # 估算Keras模型内存 mem_usage = self.model.count_params() * 4 / (1024 * 1024) # 假设32位浮点数 return avg_time_ms, mem_usage def create_metadata(self, output_path): """ 创建并保存模型元数据文件 :param output_path: 元数据文件输出路径 """ metadata = { "model_type": "tflite" if self.model_path.endswith('.tflite') else "keras", "class_labels": self.class_labels, "input_size": self.input_shape, "input_dtype": str(self.input_details[0]['dtype']) if self.interpreter else "float32", "quantization": None } if self.interpreter and self.input_details[0]['dtype'] == np.uint8: metadata["quantization"] = { "input_scale": float(self.input_details[0]['quantization'][0]), "input_zero_point": int(self.input_details[0]['quantization'][1]), "output_scale": float(self.output_details[0]['quantization'][0]), "output_zero_point": int(self.output_details[0]['quantization'][1]) } with open(output_path, 'w') as f: json.dump(metadata, f, indent=4) return metadata def convert_to_tflite_with_metadata(self, output_path, quantize=False, representative_data_dir=None): """ 将Keras模型转换为TFLite格式并添加元数据 :param output_path: 输出TFLite文件路径 :param quantize: 是否进行量化 :param representative_data_dir: 代表性数据集目录 """ if not self.model_path.endswith(('.keras', '.h5')): raise ValueError("需要Keras模型格式进行转换") # 加载Keras模型 keras_model = tf.keras.models.load_model(self.model_path) # 创建转换 converter = tf.lite.TFLiteConverter.from_keras_model(keras_model) if quantize: # 量化配置 converter.optimizations = [tf.lite.Optimize.DEFAULT] # 设置代表性数据集生成 converter.representative_dataset = lambda: self.representative_dataset( representative_data_dir, input_size=self.input_shape ) # 设置输入输出类型 converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] converter.inference_input_type = tf.uint8 converter.inference_output_type = tf.uint8 # 转换模型 tflite_model = converter.convert() # 保存模型 with open(output_path, 'wb') as f: f.write(tflite_model) print(f"TFLite模型已保存到: {output_path}") # 添加元数据 self.add_tflite_metadata(output_path) return output_path def representative_dataset(self, data_dir=None, input_size=(224, 224), num_samples=100): """ 生成代表性数据集用于量化 :param data_dir: 真实数据目录 :param input_size: 输入尺寸 (height, width) :param num_samples: 样本数量 """ # 优先使用真实数据 if data_dir and os.path.exists(data_dir): image_files = [os.path.join(data_dir, f) for f in os.listdir(data_dir) if f.lower().endswith(('.png', '.jpg', '.jpeg'))] # 限制样本数量 image_files = image_files[:min(len(image_files), num_samples)] print(f"使用 {len(image_files)} 张真实图像进行量化校准") for img_path in tqdm(image_files, desc="量化校准"): try: # 读取并预处理图像 img = cv2.imread(img_path) if img is None: continue img = cv2.resize(img, (input_size[1], input_size[0])) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = img.astype(np.float32) / 255.0 # 转换为float32并归一化 img = np.expand_dims(img, axis=0) yield [img] except Exception as e: print(f"处理图像 {img_path} 时出错: {str(e)}") else: # 使用随机数据作为备选 print(f"使用随机数据生成 {num_samples} 个样本进行量化校准") for _ in range(num_samples): # 生成随机图像,归一化到[0,1]范围,使用float32类型 data = np.random.rand(1, input_size[0], input_size[1], 3).astype(np.float32) yield [data] def add_tflite_metadata(self, model_path): """为TFLite模型添加元数据""" # 创建标签文件 labels_path = os.path.join(os.path.dirname(model_path), "labels.txt") with open(labels_path, 'w') as f: for label in self.class_labels: f.write(f"{label}\n") # 创建元数据 metadata_path = os.path.join(os.path.dirname(model_path), "metadata.json") self.create_metadata(metadata_path) print(f"元数据已创建: {metadata_path}") print(f"标签文件已创建: {labels_path}") # 使用示例 if __name__ == "__main__": # 类别标签 CLASS_LABELS = ['book', 'cup', 'glasses', 'phone', 'shoe'] # 初始化部署 deployer = ObjectRecognitionDeployer( model_path='optimized_model.keras', class_labels=CLASS_LABELS ) # 转换为带元数据的TFLite格式 tflite_path = 'model_quantized.tflite' # 使用真实数据目录进行量化校准 REPRESENTATIVE_DATA_DIR = 'path/to/representative_dataset' # 替换为实际路径 deployer.convert_to_tflite_with_metadata( tflite_path, quantize=True, representative_data_dir=REPRESENTATIVE_DATA_DIR ) # 重新加载带元数据的模型 tflite_deployer = ObjectRecognitionDeployer( model_path=tflite_path, class_labels=CLASS_LABELS ) # 测试预测 test_image = 'test_image.jpg' class_name, confidence = tflite_deployer.predict(test_image) print(f"预测结果: {class_name}, 置信度: {confidence:.2f}") # 性能测试 avg_time, mem_usage = tflite_deployer.benchmark(test_image) print(f"平均推理时间: {avg_time:.2f} ms") print(f"内存占用: {mem_usage:.2f} MB") # 创建元数据文件 metadata = deployer.create_metadata('model_metadata.json') print("模型元数据:", json.dumps(metadata, indent=4)) 上述代码我已经成功执行,并且我的ObjectRecognitionDeployer类路径导入代码是from 计算机视觉.test2 import ObjectRecognitionDeployer
06-23
from tensorflow.keras.preprocessing.image import ImageDataGenerator import matplotlib.pyplot as plt from model import AlexNet_v1 import tensorflow as tf import json import os def main(): # 获取当前工作目录 current_dir = os.getcwd() # 设置flower_data的路径为当前目录下的flower_data文件夹q image_path = os.path.join(current_dir, "flower_data") # flower data set path # 构造训练验证数据的路径 train_dir = os.path.join(image_path, "train") validation_dir = os.path.join(image_path, "val") # 确保训练验证数据的路径存在 assert os.path.exists(train_dir), "cannot find {}".format(train_dir) assert os.path.exists(validation_dir), "cannot find {}".format(validation_dir) # 创建保存模型权重的目录 if not os.path.exists("save_weights"): os.makedirs("save_weights") im_height = 224 # 图像高度 im_width = 224 # 图像宽度 batch_size = 32 # 批处理大小 epochs = 10 # 训练轮数 # 载入图像数据并预处理 train_image_generator = ImageDataGenerator(rescale=1. / 255, # 将像素值归一化到0到1之间 horizontal_flip=True) # 随机水平翻转 validation_image_generator = ImageDataGenerator(rescale=1. / 255) # 仅进行归一化 # 从目录中生成训练数据 train_data_gen = train_image_generator.flow_from_directory(directory=train_dir, batch_size=batch_size, shuffle=True, # 随机打乱数据 target_size=(im_height, im_width), class_mode='categorical') # 类别模式为one-hot编码 total_train = train_data_gen.n # 训练集总数 # 获取类别字典 class_indices = train_data_gen.class_indices # 反转字典的键值对 inverse_dict = dict((val, key) for key, val in class_indices.items()) # 将字典写入JSON文件 json_str = json.dumps(inverse_dict, indent=4) with open('class_indices.json', 'w') as json_file: json_file.write(json_str) # 从目录中生成验证数据 val_data_gen = validation_image_generator.flow_from_directory(directory=validation_dir, batch_size=batch_size, shuffle=False, # 不打乱验证数据 target_size=(im_height, im_width), class_mode='categorical') # 类别模式为one-hot编码 total_val = val_data_gen.n # 验证集总数 print("使用 {} 张图像进行训练, 使用 {} 张图像进行验证.".format(total_train, total_val)) # 载入图像信息 sample_training_images, sample_training_labels = next(train_data_gen) # label是one-hot编码 # 绘制训练样本图像 # def plotImages(images_arr): # fig, axes = plt.subplots(1, 5, figsize=(20, 20)) # axes = axes.flatten() # for img, ax in zip(images_arr, axes): # ax.imshow(img) # ax.axis('off') # plt.tight_layout() # plt.show() # # plotImages(sample_training_images[:5]) # 创建模型 model = AlexNet_v1(im_height=im_height, im_width=im_width, num_classes=5) # 显示模型结构 model.summary() # 使用Keras高层API进行训练 model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0005), # Adam优化 loss=tf.keras.losses.CategoricalCrossentropy(from_logits=False), # 损失函数 metrics=["accuracy"]) # 评价指标 # 设置回调函数以保存最佳模型 callbacks = [tf.keras.callbacks.ModelCheckpoint(filepath='./save_weights/myAlex.h5', save_best_only=True, save_weights_only=True, monitor='val_loss')] # 使用fit方法进行模型训练 history = model.fit(x=train_data_gen, steps_per_epoch=total_train // batch_size, # 每个epoch的步数 epochs=epochs, validation_data=val_data_gen, validation_steps=total_val // batch_size, # 验证集的步数 callbacks=callbacks) # 绘制损失准确率的图像 history_dict = history.history train_loss = history_dict["loss"] # 训练损失 train_accuracy = history_dict["accuracy"] # 训练准确率 val_loss = history_dict["val_loss"] # 验证损失 val_accuracy = history_dict["val_accuracy"] # 验证准确率 # 图1:绘制损失 plt.figure() plt.plot(range(epochs), train_loss, label='train_loss') plt.plot(range(epochs), val_loss, label='val_loss') plt.legend() plt.xlabel('epochs') plt.ylabel('loss') # 图2:绘制准确率 plt.figure() plt.plot(range(epochs), train_accuracy, label='train_accuracy') plt.plot(range(epochs), val_accuracy, label='val_accuracy') plt.legend() plt.xlabel('epochs') plt.ylabel('accuracy') plt.show() if __name__ == '__main__': main() 对每条代码的含义进行分析阐述
11-25
# %% import json from transformers import AutoTokenizer from transformers import TFT5ForConditionalGeneration, AutoTokenizer import tensorflow as tf import tf_keras as keras # %% # 加载数据 data_file = "yizi_couple_dataset.jsonl" samples = [] with open(data_file, 'r', encoding='utf-8') as f: for line in f: dialog = json.loads(line.strip()) # 只取正向对话(避免 rejected 回应) if any(turn.get("label") == "rejected" for turn in dialog["turns"]): continue # 过滤掉负面样本 history = [] for turn in dialog["turns"]: if turn["speaker"] == "User": history.append(f"用户: {turn['utterance']}") elif turn["speaker"] == "Assistant": # 构造训练目标:[context] => [response] context = "\n".join(history + [f"用户: {turn['utterance']}"]) response = f"{turn['role']}: {turn['utterance']}" samples.append({ "id": dialog["id"], "scene": dialog["scene"], "relationship": dialog["relationship"], "emotion_arc": " → ".join(dialog["emotion_arc"]), "context": context, "response": response, "inner_thought": turn.get("inner_thought"), "body_language": turn.get("body_language") }) history.append(response) # %% model_name = "t5-small" tokenizer = AutoTokenizer.from_pretrained(model_name) max_length = 512 def preprocess_function(examples): inputs = [] targets = [] for ex in examples: prefix = ( f"[Scene] {ex['scene']} " f"[Relationship] {ex['relationship']} " f"[Emotion] {ex['emotion_arc']} " f"[Context]\n{ex['context']}" ) inputs.append(prefix) targets.append(ex["response"]) model_inputs = tokenizer(inputs, max_length=max_length, padding="max_length", truncation=True, return_tensors="tf") with tokenizer.as_target_tokenizer(): labels = tokenizer(targets, max_length=128, padding="max_length", truncation=True, return_tensors="tf").input_ids model_inputs["labels"] = labels return model_inputs # %% tokenized_data = preprocess_function(samples) dataset = tf.data.Dataset.from_tensor_slices({ "input_ids": tokenized_data["input_ids"], "attention_mask": tokenized_data["attention_mask"], "labels": tokenized_data["labels"] }) batch_size = 4 dataset = dataset.shuffle(1000).batch(batch_size).prefetch(tf.data.AUTOTUNE) # %% model = TFT5ForConditionalGeneration.from_pretrained(model_name) # 编译模型 model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=3e-5), loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=["accuracy"] ) # 训练 model.fit(dataset, epochs=3) # %% def generate_response(scene, relationship, emotion_arc, dialogue_history): input_text = ( f"[Scene] {scene} " f"[Relationship] {relationship} " f"[Emotion] {' → '.join(emotion_arc)} " f"[Context]\n{dialogue_history}" ) input_ids = tokenizer(input_text, return_tensors="tf", max_length=512, truncation=True).input_ids outputs = model.generate(input_ids, max_length=100, num_beams=5, early_stopping=True) response = tokenizer.decode(outputs[0], skip_special_tokens=True) return response # 测试 history = "用户: 伊孜,你在看什么书呀?这么认真" resp = generate_response( scene="书房,伊孜刚回家正在书桌看书", relationship="情侣,彼此相知,互相依恋", emotion_arc=["平常", "亲密"], dialogue_history=history ) print(resp) # 应输出类似:“伊孜: 在看一本心理学的书呀~ 你突然问这个,是想试探我吗?” 这里用的模型有
最新发布
12-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值