在機器學習模型訓練流程中,有效管理和處理影像資料集至關重要。這包含將資料集分割為訓練、驗證和測試集,以及選擇合適的儲存格式以提升效率。TensorFlow Records(TFRecords)是一種高效的二進位格式,適用於儲存大量影像資料及其相關資訊,例如標籤、維度等。本文將詳細介紹如何使用 TensorFlow 和 Apache Beam 建立、處理和讀取 TFRecords 檔案,並探討影像預處理的關鍵步驟。

透過 Apache Beam,我們可以構建可擴充套件且容錯的資料處理流程,並在 Google Cloud Dataflow 等平台上執行。這使得處理大規模影像資料集變得更加便捷和高效。此外,文章也說明瞭如何使用 TFRecorder 套件簡化 TFRecord 檔案的建立流程,並提供程式碼範例示範如何讀取和解析 TFRecord 檔案,以及如何使用 tf.data API 建立訓練資料集。最後,文章也強調了影像預處理的重要性,並概述了常見的預處理步驟,例如影像縮放、裁剪和資料增強,以提升模型的效能和泛化能力。

建立資料集

當我們收集了一系列影像並對其進行標註後,便可以開始訓練機器學習(ML)模型。然而,我們需要將資料集分成三個部分:訓練集、驗證集和測試集。同時,我們也會將影像資料以更高效的格式儲存,以便於機器學習使用。讓我們來看看這兩個步驟,以及我們的訓練程式如何讀取這種格式的檔案。

資料分割

影像和標籤的資料集必須分成三個部分:訓練、驗證和測試。實際的比例可以由我們自行決定,但常見的比例是80:10:10。

訓練資料集是提供給模型的範例集合。最佳化器利用這些範例來調整模型的權重,以減少在訓練資料集上的錯誤或損失。然而,訓練結束時在訓練資料集上的損失並不是模型效能的可靠衡量指標。為了評估模型的效能,我們需要使用在訓練過程中未曾展示給模型的範例,這就是驗證資料集的目的。

如果我們只訓練一個模型,並且只訓練一次,那麼我們只需要訓練和驗證資料集(在這種情況下,常見的比例是80:20)。然而,我們很可能會使用不同的超引數重試訓練——也許我們會改變學習率,或減少丟棄率,或在模型中增加幾層。我們針對驗證資料集最佳化的超引數越多,模型在驗證資料集上的表現就越被納入模型結構中。因此,驗證資料集不再是模型在全新資料上表現的可靠估計。

我們對模型的最終評估(使用在訓練資料集上擬合的模型,並使用在驗證資料集上最佳化的引數)是在測試資料集上進行的。

在每次訓練執行開始時分割資料集並不是一個好主意。如果這樣做,每個實驗都會有不同的訓練和驗證資料集,這違背了保留一個真正獨立的測試資料集的目的。相反,我們應該只分割一次,然後在所有的超引數調優實驗中繼續使用相同的訓練和驗證資料集。因此,我們應該儲存訓練、驗證和測試CSV檔案,並在整個模型生命週期中一致地使用它們。

有時,我們可能希望對資料集進行交叉驗證。為此,我們多次訓練模型,每次使用不同的分割方式,將前90%的資料分成訓練和驗證資料集(測試資料集保持相同的10%)。在這種情況下,我們會寫出多個訓練和驗證檔案。交叉驗證在小型資料集中很常見,但在大型資料集(如用於影像模型的資料集)的機器學習中則較少見。

TensorFlow Records

上一節中提到的CSV檔案格式不適合大規模機器學習,因為它依賴於將影像資料儲存為個別的JPEG檔案,這並不是很高效。一個更高效的資料格式是TensorFlow Records(TFRecords)。我們可以使用Apache Beam將JPEG影像檔案轉換為TFRecords。

首先,我們定義一個方法來建立一個TFRecord,給定影像檔案名稱和影像的標籤:

def create_tfrecord(filename, label, label_int):
    img = read_and_decode(filename)
    dims = img.shape
    img = tf.reshape(img, [-1])  # 轉換為1D陣列
    return tf.train.Example(features=tf.train.Features(feature={
        'image': _float_feature(img),
        'shape': _int64_feature([dims[0], dims[1], dims[2]]),
        'label': _string_feature([label]),
        'label_int': _int64_feature([label_int])
    })).SerializeToString()

內容解密:

  1. read_and_decode(filename):讀取並解碼影像檔案。
  2. tf.reshape(img, [-1]):將影像資料轉換為1D陣列,以便於儲存。
  3. tf.train.Example:建立一個TFRecord範例,包含影像資料、形狀、標籤和標籤整數值。
  4. SerializeToString():將TFRecord範例序列化為字串。

TFRecord是一個字典,包含兩個主要鍵:影像和標籤。由於不同的影像可能具有不同的大小,我們還需要儲存原始影像的形狀。為了節省在訓練過程中查詢標籤索引的時間,我們還將標籤儲存為整數。

除了效率之外,TFRecords還允許我們將影像後設資料(如標籤、邊界框,甚至額外的機器學習輸入,如影像的位置和時間戳)嵌入資料本身。這樣,我們就不需要依賴臨時機制,如檔案/目錄名稱或外部檔案來編碼後設資料。

影像本身是一個浮點數陣列——為了提高效率,我們在寫入TFRecords之前進行了JPEG解碼和縮放。這樣,在迭代訓練資料集時就不需要重複這些操作:

def read_and_decode(filename):
    img = tf.io.read_file(filename)
    img = tf.image.decode_jpeg(img, channels=IMG_CHANNELS)
    img = tf.image.convert_image_dtype(img, tf.float32)
    return img

內容解密:

  1. tf.io.read_file(filename):讀取影像檔案。
  2. tf.image.decode_jpeg(img, channels=IMG_CHANNELS):解碼JPEG影像。
  3. tf.image.convert_image_dtype(img, tf.float32):將影像資料轉換為浮點數格式。

除了效率之外,在寫入TFRecords之前解碼影像和縮放其值到[0, 1]還有兩個其他優點。首先,這將資料轉換為TensorFlow Hub中影像模型所需的確切格式。其次,這允許讀取程式碼使用資料,而無需知道檔案是JPEG、PNG還是其他影像格式。

Apache Beam管道包括取得訓練、驗證和測試CSV檔案,建立TFRecords,並寫入三個資料集,並帶有適當的字首。例如,訓練TFRecord檔案是使用以下程式碼建立的:

with beam.Pipeline() as p:
    (p
     | 'input_df' >> beam.Create(train.values)
     | 'create_tfr' >> beam.Map(lambda x: create_tfrecord(x[0], x[1], LABELS.index(x[1])))
     | 'write' >> beam.io.tfrecordio.WriteToTFRecord('output/train', file_name_suffix='.gz')
    )

內容解密:

  1. beam.Create(train.values):建立一個包含訓練資料的PCollection。
  2. beam.Map(lambda x: create_tfrecord(x[0], x[1], LABELS.index(x[1]))):將create_tfrecord函式應用於每個元素,建立TFRecord。
  3. beam.io.tfrecordio.WriteToTFRecord:將TFRecord寫入檔案,並指設定檔案名稱字首和字尾。

雖然在寫入TFRecords之前解碼和縮放畫素值有很多優點,但浮點數畫素資料往往比原始位元流佔用更多的空間。在上述程式碼中,透過壓縮TFRecord檔案來解決這個問題。當我們指設定檔案名稱字尾為.gz時,TFRecord寫入器將自動壓縮輸出檔案。

大規模執行

上述程式碼對於轉換少量影像來說是可行的,但當你有成千上萬甚至數百萬張影像時,你需要一個更具擴充套件性、更穩健的解決方案。該解決方案需要具備容錯能力,能夠分佈到多台機器上,並且能夠使用標準的DevOps工具進行監控。通常,我們還希望將輸出管道傳輸到成本高效的二進位制儲存中,當新的影像流式傳入時。理想情況下,我們希望這一切都能以無伺服器的方式實作,這樣我們就不需要自己管理和擴充套件這套基礎設施。

在Google Cloud Dataflow上執行Apache Beam以建立視覺資料集

在生產環境中,需要確保資料處理流程具有彈性、監控、串流處理和自動擴充套件能力。其中一種解決方案是在Google Cloud Dataflow上執行Apache Beam程式碼,而不是在Jupyter notebook中執行。

使用Apache Beam建立資料處理流程

首先,我們需要建立一個Apache Beam的Pipeline,並指定執行環境為DataflowRunner:

with beam.Pipeline('DataflowRunner', options=opts) as p:
    # 資料處理流程

其中,opts包含了Cloud專案和執行區域等資訊。除了DataflowRunner之外,Apache Beam還支援其他執行環境,如Apache Spark和Apache Flink。

將資料集分割步驟納入Pipeline

為了確保資料處理流程的完整性,我們可以在Pipeline中包含資料集分割步驟:

with beam.Pipeline(RUNNER, options=opts) as p:
    splits = (p
              | 'read_csv' >> beam.io.ReadFromText(arguments['all_data'])
              | 'parse_csv' >> beam.Map(lambda line: line.split(','))
              | 'create_tfr' >> beam.Map(lambda x: create_tfrecord(x[0], x[1], LABELS.index(x[1])))
              | 'assign_ds' >> beam.Map(assign_record_to_split)
             )

其中,assign_record_to_split()函式負責將每個記錄分配到訓練、驗證或測試資料集中:

def assign_record_to_split(rec):
    rnd = np.random.rand()
    if rnd < 0.8:
        return ('train', rec)
    if rnd < 0.9:
        return ('valid', rec)
    return ('test', rec)

將分割後的資料集寫入TFRecord檔案

接下來,我們可以將分割後的資料集寫入TFRecord檔案:

for s in ['train', 'valid', 'test']:
    _ = (splits
         | 'only_{}'.format(s) >> beam.Filter(lambda x: x[0] == s)
         | '{}_records'.format(s) >> beam.Map(lambda x: x[1])
         | 'write_{}'.format(s) >> beam.io.tfrecordio.WriteToTFRecord(
             os.path.join(OUTPUT_DIR, s), file_name_suffix='.gz')
        )

執行上述程式碼後,Dataflow服務將建立TFRecord檔案,並將其儲存在指定的輸出目錄中。

使用TFRecorder簡化TFRecord檔案的建立

如果您已經有Pandas或CSV檔案中的資料,可以使用TFRecorder Python套件簡化TFRecord檔案的建立:

import pandas as pd
import tfrecorder

csv_file = './all_data_split.csv'
df = pd.read_csv(csv_file, names=['split', 'image_uri', 'label'])
df.tensorflow.to_tfr(output_dir='gs://BUCKET/data/output/path')

在Cloud Dataflow上執行TFRecorder

要在Cloud Dataflow上執行TFRecorder,需要新增一些引數:

df.tensorflow.to_tfr(
    output_dir='gs://my/bucket',
    runner='DataflowRunner',
    project='my-project',
    region='us-central1',
    tfrecorder_wheel='/path/to/my/tfrecorder.whl')

讀取TFRecord檔案

要讀取TFRecord檔案,可以使用tf.data.TFRecordDataset

train_dataset = tf.data.TFRecordDataset(
    tf.data.Dataset.list_files(
        'gs://practical-ml-vision-book/flowers_tfr/train-*')
)

資料治理和監管考量

在建立視覺資料集時,需要考慮資料治理和監管問題。例如,將影像資料從原始資料湖中提取出來可能會違反資料治理政策。因此,需要確保提取出的資料集符合相關法規和政策要求。

內容解密:

此段落主要闡述了使用Apache Beam在Google Cloud Dataflow上建立視覺資料集的方法,以及如何使用TFRecorder簡化TFRecord檔案的建立。並強調了在建立視覺資料集時需要考慮的資料治理和監管問題。

此圖示說明瞭在Cloud Dataflow中執行資料集建立流程的過程。

影像資料集的解析與預處理

TensorFlow Record 資料解析

在處理 TensorFlow Record(TFRecord)檔案時,我們需要根據寫入檔案的記錄結構定義解析方式。以下是一個典型的 feature_description 定義,用於描述 TFRecord 中的資料結構:

feature_description = {
    'image': tf.io.VarLenFeature(tf.float32),
    'shape': tf.io.VarLenFeature(tf.int64),
    'label': tf.io.FixedLenFeature([], tf.string, default_value=''),
    'label_int': tf.io.FixedLenFeature([], tf.int64, default_value=0),
}

內容解密:

  1. 'image':使用 tf.io.VarLenFeature(tf.float32) 表示影像資料是可變長度的浮點數陣列。
  2. 'shape':同樣是可變長度的整數陣列,用於儲存影像的維度資訊。
  3. 'label':使用 tf.io.FixedLenFeature([], tf.string) 表示標籤是固定長度的字串,預設值為空字串。
  4. 'label_int':固定長度的 64 位元整數,用於儲存標籤的整數表示,預設值為 0。

這個定義與建立 TFRecord 時使用的 tf.train.Example 結構相符:

return tf.train.Example(features=tf.train.Features(feature={
    'image': _float_feature(img),
    'shape': _int64_feature([dims[0], dims[1], dims[2]]),
    'label': _string_feature(label),
    'label_int': _int64_feature([label_int])
}))

為何使用可變長度與固定長度特徵?

  • 影像資料和其維度資訊是可變長度的,因此使用 VarLenFeature
  • 標籤資訊是固定的,因此使用 FixedLenFeature

解析 TFRecord 資料

解析單一 TFRecord 資料範例的函式實作如下:

def parse_tfr(proto):
    feature_description = {
        'image': tf.io.VarLenFeature(tf.float32),
        'shape': tf.io.VarLenFeature(tf.int64),
        'label': tf.io.FixedLenFeature([], tf.string, default_value=''),
        'label_int': tf.io.FixedLenFeature([], tf.int64, default_value=0),
    }
    rec = tf.io.parse_single_example(proto, feature_description)
    shape = tf.sparse.to_dense(rec['shape'])
    img = tf.reshape(tf.sparse.to_dense(rec['image']), shape)
    return img, rec['label_int']

內容解密:

  1. tf.io.parse_single_example(proto, feature_description):根據定義好的 feature_description 解析單一 proto 資料。
  2. tf.sparse.to_dense(rec['shape']):將稀疏張量轉換為稠密張量,用於儲存影像維度資訊。
  3. tf.reshape(tf.sparse.to_dense(rec['image']), shape):將稀疏的影像資料轉換為稠密張量並重塑為正確的形狀。
  4. return img, rec['label_int']:傳回解析後的影像資料及其對應的整數標籤。

建立訓練資料集

使用 tf.data.TFRecordDatasetmap 方法將解析函式應用於所有 TFRecord 檔案:

train_dataset = tf.data.TFRecordDataset(
    [filename for filename in tf.io.gfile.glob('gs://practical-ml-vision-book/flowers_tfr/train-*')]
).map(parse_tfr)

內容解密:

  1. tf.data.TFRecordDataset:建立一個從指定路徑讀取 TFRecord 檔案的資料集。
  2. .map(parse_tfr):將 parse_tfr 解析函式應用到資料集中的每個元素,將原始資料轉換為可用的影像和標籤。

稀疏張量簡介

稀疏張量是一種高效表示大多數元素為零的張量的資料結構。它只儲存非零元素及其索引,從而節省儲存空間。例如,對於二維張量:

[[0, 0, 3, 0, 0, 0, 0, 5, 0, 0],
 [0, 2, 0, 0, 0, 0, 4, 0, 0, 0]]

可以表示為:

  • 非零值:[3, 5, 2, 4]
  • 索引:[[0, 2], [0, 7], [1, 1], [1, 6]]
  • 稠密形狀:(2, 10)

TensorFlow 將稀疏張量表示為三個獨立的稠密張量:indicesvaluesdense_shape,並支援直接在稀疏張量上進行高效運算。

第六章:影像預處理

在第五章中,我們瞭解瞭如何建立機器學習所需的訓練資料集。這是標準影像處理流程的第一步(參見圖 6-1)。接下來的階段是對原始影像進行預處理,以便將它們輸入模型進行訓練或推斷。在本章中,我們將探討為什麼需要對影像進行預處理,如何設定預處理流程以確保生產環境中的可重現性,以及如何在 Keras/TensorFlow 中實作各種預處理操作。

圖 6-1:原始影像在訓練(上)和推斷(下)過程中都需要經過預處理才能輸入模型。

為什麼需要預處理?

原始影像通常包含多種幹擾因素,例如不同的光照條件、拍攝角度等。這些因素可能會對模型的訓練和推斷造成負面影響。因此,我們需要對影像進行預處理,以提升模型的泛化能力。

如何實作預處理?

我們可以使用 Keras/TensorFlow 中的預處理 API,例如 tf.image,來實作諸如影像縮放、裁剪、翻轉等操作。這些操作可以有效地提升模型的訓練效率和準確性。

使用 Plantuml 圖表展示預處理流程

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title 影像資料集建立與TensorFlowRecords應用

package "資料集建立流程" {
    package "資料分割" {
        component [訓練集 80%] as train
        component [驗證集 10%] as valid
        component [測試集 10%] as test
    }

    package "TFRecords 格式" {
        component [影像編碼] as encode
        component [標籤嵌入] as label
        component [序列化] as serialize
    }
}

package "Apache Beam 處理" {
    component [Dataflow 執行] as dataflow
    component [分散式處理] as distributed
    component [批次轉換] as batch
}

package "影像預處理" {
    component [JPEG 解碼] as decode
    component [縮放正規化] as normalize
    component [資料增強] as augment
}

train --> encode : 原始影像
valid --> encode
test --> encode
encode --> label : 後設資料
label --> serialize : TFRecord
dataflow --> distributed : 大規模
decode --> normalize : [0,1] 浮點數
normalize --> augment : tf.data

note right of serialize
  TFRecord 包含:
  - 特徵選擇
  - 特徵轉換
  - 降維處理
end note

note right of eval
  評估指標:
  - 準確率/召回率
  - F1 Score
  - AUC-ROC
end note

@enduml

此圖示展示了從原始影像到輸入模型的預處理流程。

詳細解說

  1. 原始影像:首先,我們有需要處理的原始影像資料。
  2. 影像縮放:調整影像的大小,以滿足模型的輸入需求。
  3. 影像裁剪:裁剪影像以聚焦於感興趣的區域,或去除無關的邊緣部分。
  4. 資料增強:透過隨機旋轉、翻轉等操作,增加訓練資料的多樣性,提升模型的泛化能力。
  5. 輸入模型:最終,將預處理後的影像輸入機器學習模型進行訓練或推斷。