TensorFlow2.0(10):加載自定義圖片數據集到Dataset

 

前面的博客中我們說過,在加載數據和預處理數據時使用tf.data.Dataset對象將極大將我們從建模前的數據清理工作中釋放出來,那麼,怎麼將自定義的數據集加載為DataSet對象呢?這對很多新手來說都是一個難題,因為絕大多數案例教學都是以mnist數據集作為例子講述如何將數據加載到Dataset中,而英文資料對這方面的介紹隱藏得有點深。本文就來捋一捋如何加載自定義的圖片數據集實現圖片分類,後續將繼續介紹如何加載自定義的text、mongodb等數據。

 

如果你已有數據集,那麼,請將所有數據存放在同一目錄下,然後將不同類別的圖片分門別類地存放在不同的子目錄下,目錄樹如下所示:

$ tree flower_photos -L 1

flower_photos
├── daisy
├── dandelion
├── LICENSE.txt
├── roses
├── sunflowers
└── tulips

 

所有的數據都存放在flower_photos目錄下,每一個子目錄(daisy、dandelion等等)存放的都是一個類別的圖片。如果你已有自己的數據集,那就按上面的結構來存放,如果沒有,想操作學習一下,你可以通過下面代碼下載上述圖片數據集:

In [ ]:

import tensorflow as tf
import pathlib
data_root_orig = tf.keras.utils.get_file(origin='https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
                                         fname='flower_photos', untar=True)
data_root = pathlib.Path(data_root_orig)
print(data_root)  # 打印出數據集所在目錄

 

下載好后,建議將整個flower_photos目錄移動到項目根目錄下。

In [1]:

import tensorflow as tf
import random
import pathlib
data_path = pathlib.Path('./data/flower_photos')
all_image_paths = list(data_path.glob('*/*'))  
all_image_paths = [str(path) for path in all_image_paths]  # 所有圖片路徑的列表
random.shuffle(all_image_paths)  # 打散

image_count = len(all_image_paths)
image_count

Out[1]:

3670

 

查看一下前5張:

In [2]:

all_image_paths[:5]

Out[2]:

['data/flower_photos/sunflowers/9448615838_04078d09bf_n.jpg',
 'data/flower_photos/roses/15222804561_0fde5eb4ae_n.jpg',
 'data/flower_photos/daisy/18622672908_eab6dc9140_n.jpg',
 'data/flower_photos/roses/459042023_6273adc312_n.jpg',
 'data/flower_photos/roses/16149016979_23ef42b642_m.jpg']

 

讀取圖片的同時,我們也不能忘記圖片與標籤的對應,要創建一個對應的列表來存放圖片標籤,不過,這裏所說的標籤不是daisy、dandelion這些具體分類名,而是整型的索引,畢竟在建模的時候y值一般都是整型數據,所以要創建一個字典來建立分類名與標籤的對應關係:

In [3]:

label_names = sorted(item.name for item in data_path.glob('*/') if item.is_dir())
label_names

Out[3]:

['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']

In [4]:

label_to_index = dict((name, index) for index, name in enumerate(label_names))
label_to_index

Out[4]:

{'daisy': 0, 'dandelion': 1, 'roses': 2, 'sunflowers': 3, 'tulips': 4}

In [5]:

all_image_labels = [label_to_index[pathlib.Path(path).parent.name] for path in all_image_paths]

In [6]:

for image, label in zip(all_image_paths[:5], all_image_labels[:5]):
    print(image, ' --->  ', label)

 

data/flower_photos/sunflowers/9448615838_04078d09bf_n.jpg  --->   3
data/flower_photos/roses/15222804561_0fde5eb4ae_n.jpg  --->   2
data/flower_photos/daisy/18622672908_eab6dc9140_n.jpg  --->   0
data/flower_photos/roses/459042023_6273adc312_n.jpg  --->   2
data/flower_photos/roses/16149016979_23ef42b642_m.jpg  --->   2

 

好了,現在我們可以創建一個Dataset了:

In [7]:

ds = tf.data.Dataset.from_tensor_slices((all_image_paths, all_image_labels))

 

不過,這個ds可不是我們想要的,畢竟,裏面的元素只是圖片路徑,所以我們要進一步處理。這個處理包含讀取圖片、重新設置圖片大小、歸一化、轉換類型等操作,我們將這些操作統統定義到一個方法里:

In [8]:

def load_and_preprocess_from_path_label(path, label):
    image = tf.io.read_file(path)  # 讀取圖片
    image = tf.image.decode_jpeg(image, channels=3)
    image = tf.image.resize(image, [192, 192])  # 原始圖片大小為(266, 320, 3),重設為(192, 192)
    image /= 255.0  # 歸一化到[0,1]範圍
    return image, label

In [9]:

image_label_ds  = ds.map(load_and_preprocess_from_path_label)

In [10]:

image_label_ds 

Out[10]:

<MapDataset shapes: ((192, 192, 3), ()), types: (tf.float32, tf.int32)>

 

這時候,其實就已經將自定義的圖片數據集加載到了Dataset對象中,不過,我們還能秀,可以繼續shuffle隨機打散、分割成batch、數據repeat操作。這些操作有幾點需要注意:
(1)先shuffle、repeat、batch三種操作順序有講究:

  • 在repeat之後shuffle,會在epoch之間數據隨機(當有些數據出現兩次的時候,其他數據還沒有出現過)
  • 在batch之後shuffle,會打亂batch的順序,但是不會在batch之間打亂數據。

(2)shuffle操作時,buffer_size越大,打亂效果越好,但消耗內存越大,可能造成延遲。

 

推薦通過使用 tf.data.Dataset.apply 方法和融合過的 tf.data.experimental.shuffle_and_repeat 函數來執行這些操作:

In [12]:

ds = image_label_ds.apply(tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))
BATCH_SIZE = 32
ds = ds.batch(BATCH_SIZE)

 

好了,至此,本文內容其實就結束了,因為已經將自定義的圖片數據集加載到了Dataset中。

下面的內容作為擴展閱讀。

 

擴展

 

上面的方法是簡單的在每次epoch迭代中單獨讀取每個文件,在本地使用 CPU 訓練時這個方法是可行的,但是可能不足以進行GPU訓練並且完全不適合任何形式的分佈式訓練。

 

可以使用tf.data.Dataset.cache在epoch迭代過程間緩存計算結果。這能極大提升程序效率,特別是當內存能容納全部數據時。

在被預處理之後(解碼和調整大小),圖片就被緩存了:

In [13]:

ds = image_label_ds.cache()  # 緩存
ds = ds.apply(tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))

 

使用內存緩存的一個缺點是必須在每次運行時重建緩存,這使得每次啟動數據集時有相同的啟動延遲。如果內存不夠容納數據,使用一個緩存文件:

In [14]:

ds = image_label_ds.cache(filename='./cache.tf-data')
ds = ds.apply(tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))

 

參考

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

您可能也會喜歡…