深度學習中超大規模數據集的處理

在機器學習項目中,如果使用的是比較小的數據集,數據集的處理上可以非常簡單:加載每個單獨的圖像,對其進行預處理,然後輸送給神經網絡。但是,對於大規模數據集(例如ImageNet),我們需要創建一次只訪問一部分數據集的數據生成器(比如mini batch),然後將小批量數據傳遞給網絡。其實,這種方法在我們之前的示例中也有所涉及,在使用數據增強技術提升模型泛化能力一文中,我就介紹了通過數據增強技術批量擴充數據集,雖然那裏並沒有使用到超大規模的數據集。Keras提供的方法允許使用磁盤上的原始文件路徑作爲訓練輸入,而不必將整個數據集存儲在內存中。

然而,這種方法的缺點也是很明顯,非常低效。加載磁盤上的每個圖像都需要I/O操作,學過計算機的同學都知道,I/O操作最耗時,這無疑會在整個訓練管道中引入延遲。本來訓練深度學習網絡就夠慢的,I/O瓶頸應儘可能避免。

HDF5

這個時候,該HDF5文件登場了。HDF是用於存儲和分發科學數據的一種自我描述、多對象文件格式。HDF最早由美國國家超級計算應用中心NCSA開發,目前在非盈利組織HDF小組維護下繼續發展。當前流行的版本是HDF5。HDF5擁有一系列的優異特性,使其特別適合進行大量科學數據的存儲和操作,如它支持非常多的數據類型,靈活、通用、跨平臺、可擴展、高效的I/O性能,支持幾乎無限量(高達EB)的單文件存儲等,詳見其官方介紹:https://support.hdfgroup.org/HDF5/

image

HDF5文件格式爲何如此牛X?估計你也和我一樣有強烈的好奇心。但是當我看到長達200頁的spec,還是決定放棄深究其細節,畢竟我們需要聚焦到深度學習上。再說,python提供了hdf5庫,讓讀寫hdf5文件簡單得如同讀寫普通文本文件。藉助h5py模塊,實現一個HDF5數據集讀寫類非常容易:

class HDF5DatasetWriter:
  def __init__(self, dims, output_path, data_key="images", buf_size=1000):
    # check to see if the output path exists, and if so, raise an exception
    if os.path.exists(output_path):
      raise ValueError("the supplied `output_path` already exists and cannot be overwritten.", output_path)

    # open the HDF5 database for writing and create two datasets: one to store images/features
    # and another to store the class labels
    self.db = h5py.File(output_path, "w")
    self.data = self.db.create_dataset(data_key, dims, dtype="float")
    self.labels = self.db.create_dataset("labels", (dims[0],), dtype="int")

    self.buf_size = buf_size
    self.buffer = {"data": [], "labels": []}
    self.idx = 0


  def add(self, rows, labels):
    self.buffer["data"].extend(rows)
    self.buffer["labels"].extend(labels)

    if len(self.buffer["data"]) >= self.buf_size:
      self.flush()


  def flush(self):
    i = self.idx + len(self.buffer["data"])
    self.data[self.idx:i] = self.buffer["data"]
    self.labels[self.idx:i] = self.buffer["labels"]
    self.idx = i
    self.buffer = {"data": [], "labels": []}


  def store_class_labels(self, class_labels):
    dt = h5py.special_dtype(vlen=str)
    labelset = self.db.create_dataset("label_names", (len(class_labels),), dtype=dt)
    labelset[:] = class_labels


  def close(self):
    if len(self.buffer["data"]) > 0:
      self.flush()

    self.db.close()

其中主要用到的方法就是h5py.File和create_dataset,前一個方法生成HDF5文件,後一個方法創建數據集。

貓狗數據集

理論掌握再多,還是不如實例來得直接。對於個人開發者而言,收集超大規模數據集幾乎是一個不可能完成的任務,幸運的是,由於互聯網的開放性以及機器學習領域的共享精神,很多研究機構提供數據集公開下載。我們這裏選用kaggle大賽使用的Kaggle: Dogs vs. Cats dataset。你可以前往 http://pyimg.co/xb5lb 下載,也可以在公衆號平臺對話框中回覆"數據集"關鍵字,獲取百度網盤下載鏈接。

請下載kaggle - dogs vs cats下的train.zip文件。下載train.zip文件後,解開壓縮文件,你可以看到train目錄下包含貓狗圖片文件,從文件名可以推斷出其所屬的類別:

kaggle_dogs_vs_cats/train/cat.11866.jpg
...
kaggle_dogs_vs_cats/train/dog.11046.jpg

構建數據集

由於Kaggle: Dogs vs. Cats dataset的類別包含在文件名中間,我們很容易寫出如下代碼提取類別標籤:

train_paths = list(paths.list_images(config.IMAGES_PATH))
train_labels = [p.split(os.path.sep)[-1].split(".")[0] for p in train_paths]

接下來劃分數據集,學過吳恩達《機器學習》課程的同學可能知道,通常我們將數據集劃分爲 訓練集、驗證集和測試集 ,通常比例爲6:2:2,但是對於大規模數據集來說,驗證集和測試集分配20%,數量太大,也沒有必要,這時通常給一個兩千左右的固定值即可。

split = train_test_split(train_paths, train_labels, test_size=config.NUM_TEST_IMAGES, stratify=train_labels,
                         random_state=42)
(train_paths, test_paths, train_labels, test_labels) = split

split = train_test_split(train_paths, train_labels, test_size=config.NUM_VAL_IMAGES, stratify=train_labels,
                         random_state=42)
(train_paths, val_paths, train_labels, val_labels) = split

接下來就是遍歷圖片文件,並分別爲訓練集、驗證集和測試集生成HDF5文件。

datasets = [
  ("train", train_paths, train_labels, config.TRAIN_HDF5),
  ("val", val_paths, val_labels, config.VAL_HDF5),
  ("test", test_paths, test_labels, config.TEST_HDF5)
]

aap = AspectAwarePreprocessor(256, 256)
(R, G, B) = ([], [], [])

for (dtype, paths, labels, output_path) in datasets:
  writer = HDF5DatasetWriter((len(paths), 256, 256, 3), output_path)

  # loop over the image paths
  for (i, (path, label)) in enumerate(zip(paths, labels)):
    image = cv2.imread(path)
    image = aap.preprocess(image)

    if dtype == "train":
      (b, g, r) = cv2.mean(image)[:3]
      R.append(r)
      G.append(g)
      B.append(b)

    writer.add([image], [label])

  writer.close()

注意到,代碼中累計了RGB均值,可以使用以下代碼計算RGB均值:

D = {"R": np.mean(R), "G": np.mean(G), "B": np.mean(B)}
f = open(config.DATASET_MEAN, "w")
f.write(json.dumps(D))
f.close()

爲啥需要RGB均值呢?這就涉及到深度學習中的一個正則化技巧,在我們之前的代碼中,都是RGB值除以255.0進行正則化,但實踐表明,將RGB值減去均值,效果更好,所以在此計算RGB的均值。需要注意的是,正則化只針對訓練數據集,目的是讓訓練出的模型具有更強的泛化能力。

構建數據集用時最長的是訓練數據集,用時大約兩分半,而驗證集和測試集則比較快,大約20秒。這額外的3分鐘時間是否值得花,在後面的文章中,我們將繼續分析。

讓我們看看最後生成的HDF5文件:

-rw-rw-r-- 1 alex alex  3932182048 Feb 18 11:33 test.hdf5
-rw-rw-r-- 1 alex alex 31457442048 Feb 18 11:31 train.hdf5
-rw-rw-r-- 1 alex alex  3932182048 Feb 18 11:32 val.hdf5

是的,你沒看錯,train.hdf5高達30G,害得我不得不刪掉硬盤上許多文件,才騰出這麼多空間。

爲什麼這樣,要知道原始的圖像包train.zip文件才500多M?這是因爲,JPEG和PNG等圖像文件格式使用了數據壓縮算法,以保持較小的圖像文件大小。但是,在我們的處理中,將圖像存儲爲原始NumPy陣列(即位圖)。雖然這樣大大增加了存儲成本,但也有助於加快訓練時間,因爲不必浪費處理器時間解碼圖像。

在下一篇文章中,我將演示如何讀取HDF5文件,進行貓狗識別模型訓練。

以上實例均有完整的代碼,點擊閱讀原文,跳轉到我在github上建的示例代碼。
另外,我在閱讀《Deep Learning for Computer Vision with Python》這本書,在微信公衆號後臺回覆“計算機視覺”關鍵字,可以免費下載這本書的電子版。

往期回顧

  1. 提高模型準確率:組合模型
  2. 再談遷移學習:微調網絡
  3. 站在巨人的肩膀上:遷移學習
  4. 聊一聊rank-1和rank-5準確度
  5. 使用數據增強技術提升模型泛化能力

image

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章