在機器學習模型訓練流程中,有效管理和處理影像資料集至關重要。這包含將資料集分割為訓練、驗證和測試集,以及選擇合適的儲存格式以提升效率。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()
內容解密:
read_and_decode(filename):讀取並解碼影像檔案。tf.reshape(img, [-1]):將影像資料轉換為1D陣列,以便於儲存。tf.train.Example:建立一個TFRecord範例,包含影像資料、形狀、標籤和標籤整數值。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
內容解密:
tf.io.read_file(filename):讀取影像檔案。tf.image.decode_jpeg(img, channels=IMG_CHANNELS):解碼JPEG影像。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')
)
內容解密:
beam.Create(train.values):建立一個包含訓練資料的PCollection。beam.Map(lambda x: create_tfrecord(x[0], x[1], LABELS.index(x[1]))):將create_tfrecord函式應用於每個元素,建立TFRecord。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),
}
內容解密:
'image':使用tf.io.VarLenFeature(tf.float32)表示影像資料是可變長度的浮點數陣列。'shape':同樣是可變長度的整數陣列,用於儲存影像的維度資訊。'label':使用tf.io.FixedLenFeature([], tf.string)表示標籤是固定長度的字串,預設值為空字串。'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']
內容解密:
tf.io.parse_single_example(proto, feature_description):根據定義好的feature_description解析單一 proto 資料。tf.sparse.to_dense(rec['shape']):將稀疏張量轉換為稠密張量,用於儲存影像維度資訊。tf.reshape(tf.sparse.to_dense(rec['image']), shape):將稀疏的影像資料轉換為稠密張量並重塑為正確的形狀。return img, rec['label_int']:傳回解析後的影像資料及其對應的整數標籤。
建立訓練資料集
使用 tf.data.TFRecordDataset 和 map 方法將解析函式應用於所有 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)
內容解密:
tf.data.TFRecordDataset:建立一個從指定路徑讀取 TFRecord 檔案的資料集。.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 將稀疏張量表示為三個獨立的稠密張量:indices、values 和 dense_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此圖示展示了從原始影像到輸入模型的預處理流程。
詳細解說
- 原始影像:首先,我們有需要處理的原始影像資料。
- 影像縮放:調整影像的大小,以滿足模型的輸入需求。
- 影像裁剪:裁剪影像以聚焦於感興趣的區域,或去除無關的邊緣部分。
- 資料增強:透過隨機旋轉、翻轉等操作,增加訓練資料的多樣性,提升模型的泛化能力。
- 輸入模型:最終,將預處理後的影像輸入機器學習模型進行訓練或推斷。