前面的博客中咱們說過,在加載數據和預處理數據時使用tf.data.Dataset對象將極大將咱們從建模前的數據清理工做中釋放出來,那麼,怎麼將自定義的數據集加載爲DataSet對象呢?這對不少新手來講都是一個難題,由於絕大多數案例教學都是以mnist數據集做爲例子講述如何將數據加載到Dataset中,而英文資料對這方面的介紹隱藏得有點深。本文就來捋一捋如何加載自定義的圖片數據集實現圖片分類,後續將繼續介紹如何加載自定義的text、mongodb等數據。javascript
若是你已有數據集,那麼,請將全部數據存放在同一目錄下,而後將不一樣類別的圖片分門別類地存放在不一樣的子目錄下,目錄樹以下所示:css
$ tree flower_photos -L 1html
flower_photos ├── daisy ├── dandelion ├── LICENSE.txt ├── roses ├── sunflowers └── tulipshtml5
全部的數據都存放在flower_photos目錄下,每個子目錄(daisy、dandelion等等)存放的都是一個類別的圖片。若是你已有本身的數據集,那就按上面的結構來存放,若是沒有,想操做學習一下,你能夠經過下面代碼下載上述圖片數據集:java
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目錄移動到項目根目錄下。node
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
3670
查看一下前5張:python
all_image_paths[:5]
['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值通常都是整型數據,因此要建立一個字典來創建分類名與標籤的對應關係:jquery
label_names = sorted(item.name for item in data_path.glob('*/') if item.is_dir()) label_names
['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']
label_to_index = dict((name, index) for index, name in enumerate(label_names)) label_to_index
{'daisy': 0, 'dandelion': 1, 'roses': 2, 'sunflowers': 3, 'tulips': 4}
all_image_labels = [label_to_index[pathlib.Path(path).parent.name] for path in all_image_paths]
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了:linux
ds = tf.data.Dataset.from_tensor_slices((all_image_paths, all_image_labels))
不過,這個ds可不是咱們想要的,畢竟,裏面的元素只是圖片路徑,因此咱們要進一步處理。這個處理包含讀取圖片、從新設置圖片大小、歸一化、轉換類型等操做,咱們將這些操做通通定義到一個方法裏:android
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
image_label_ds = ds.map(load_and_preprocess_from_path_label)
image_label_ds
<MapDataset shapes: ((192, 192, 3), ()), types: (tf.float32, tf.int32)>
這時候,其實就已經將自定義的圖片數據集加載到了Dataset對象中,不過,咱們還能秀,能夠繼續shuffle隨機打散、分割成batch、數據repeat操做。這些操做有幾點須要注意: (1)先shuffle、repeat、batch三種操做順序有講究:
(2)shuffle操做時,buffer_size越大,打亂效果越好,但消耗內存越大,可能形成延遲。
推薦經過使用 tf.data.Dataset.apply 方法和融合過的 tf.data.experimental.shuffle_and_repeat 函數來執行這些操做:
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迭代過程間緩存計算結果。這能極大提高程序效率,特別是當內存能容納所有數據時。
在被預處理以後(解碼和調整大小),圖片就被緩存了:
ds = image_label_ds.cache() # 緩存 ds = ds.apply(tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))
使用內存緩存的一個缺點是必須在每次運行時重建緩存,這使得每次啓動數據集時有相同的啓動延遲。若是內存不夠容納數據,使用一個緩存文件:
ds = image_label_ds.cache(filename='./cache.tf-data') ds = ds.apply(tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))
https://tensorflow.google.cn/tutorials/load_data/images