訂閱
糾錯(cuò)
加入自媒體

基于卷積神經(jīng)網(wǎng)絡(luò)的圖像分類(lèi)

現(xiàn)在是學(xué)習(xí)卷積神經(jīng)網(wǎng)絡(luò)及其在圖像分類(lèi)中的應(yīng)用了。

什么是卷積?

卷積運(yùn)算是使用具有恒定大小的“窗口”移動(dòng)圖像,并將圖像像素與卷積窗口相乘以獲得輸出圖像的過(guò)程。讓我們看看下面的例子:

我們看到一個(gè)9x9圖像和一個(gè)3x3卷積濾波器,其恒定權(quán)重為3 0 3 2 0 2 1 0 1,以及卷積運(yùn)算的計(jì)算。嘗試使用如下所示的濾波器遍歷圖像,并更好地了解輸出圖像的這些像素是如何通過(guò)卷積計(jì)算的。

窗口、濾波器、核、掩碼是提到“卷積濾波器”的不同方式,我們也將在本文中使用這些術(shù)語(yǔ)。

填充

填充是在輸入圖像邊框上添加額外像素的過(guò)程,主要是為了保持輸出圖像的大小與輸入圖像的大小相同。最常見(jiàn)的填充技術(shù)是添加零(稱為零填充)。

跨步

步幅是我們使用卷積窗口遍歷圖像時(shí)每次迭代的步長(zhǎng)。在下面的例子中,我們實(shí)際上看到步幅是1,所以我們將窗口移動(dòng)了1,F(xiàn)在讓我們看另一個(gè)例子,更好地理解水平步幅=1,垂直步幅=2:

因此,填充大小、步幅大小、濾波器大小和輸入大小會(huì)影響輸出圖像的大小,根據(jù)這些不同的參數(shù),輸出圖像大小的公式如下:

到目前為止,我們只研究了一個(gè)應(yīng)用于輸入圖像的卷積運(yùn)算,現(xiàn)在讓我們看看什么是卷積神經(jīng)網(wǎng)絡(luò),以及我們?nèi)绾斡?xùn)練它們。

卷積神經(jīng)網(wǎng)絡(luò)(CNN)

如果將層作為卷積窗口,窗口中的每個(gè)像素實(shí)際上是一個(gè)權(quán)重(而不是我們?cè)谇耙黄恼轮袑W(xué)習(xí)的全連接的神經(jīng)網(wǎng)絡(luò)),那么這是一個(gè)卷積神經(jīng)網(wǎng)絡(luò)。

我們的目標(biāo)是訓(xùn)練模型,以在最后以最小的成本更新這些權(quán)重。因此,與前面的例子相反,我們的卷積濾波器中沒(méi)有任何常量值,我們應(yīng)該讓模型為它們找到最佳值。

所以一個(gè)簡(jiǎn)單的CNN是一些卷積運(yùn)算的序列,如下所示:

基于CNN的圖像分類(lèi)

但是如何利用CNN實(shí)現(xiàn)圖像分類(lèi)呢?

圖像分類(lèi)的唯一區(qū)別是,我們現(xiàn)在處理的是圖像,而不是房?jī)r(jià)、房間號(hào)等結(jié)構(gòu)化數(shù)據(jù)。

每個(gè)卷積運(yùn)算都會(huì)參與提取圖像特征,例如耳朵、腳、狗的嘴等。隨著卷積層的加深,該特征提取步驟會(huì)更深入,而在第一層,我們只獲得圖像的一些邊緣。

因此,卷積層負(fù)責(zé)提取重要的特征,最后,為了有一個(gè)完整的圖像分類(lèi)模型,我們只需要一些全連接的輸出節(jié)點(diǎn),根據(jù)它們的權(quán)重來(lái)決定圖像的正確類(lèi)別!

我們假設(shè)狗有一個(gè)分類(lèi)問(wèn)題。在這種情況下,在訓(xùn)練結(jié)束時(shí),一些輸出節(jié)點(diǎn)將代表狗類(lèi)特征和一些貓類(lèi)特征。

如果通過(guò)這些卷積層的輸入圖像在激活函數(shù)結(jié)束時(shí)狗類(lèi)提供了更高的值,那么他將分類(lèi)為狗。否則分類(lèi)為貓。讓我們可視化一下這個(gè)過(guò)程:

CNN+全連接的神經(jīng)網(wǎng)絡(luò)創(chuàng)建了一個(gè)圖像分類(lèi)模型!

在討論用于圖像分類(lèi)的常見(jiàn)CNN體系結(jié)構(gòu)之前,讓我們先來(lái)看一些更復(fù)雜、更真實(shí)的CNN示例:

當(dāng)我們談?wù)揅NN層時(shí),我們并不是只討論一層中的一個(gè)卷積核;實(shí)際上,多個(gè)卷積核可以創(chuàng)建一個(gè)卷積層。所以我們把所有這些卷積濾波器一個(gè)接一個(gè)地應(yīng)用到我們的輸入圖像上,然后我們傳遞到下一個(gè)卷積層。1個(gè)卷積層中卷積核的數(shù)量有3個(gè),與圖像的“通道大小”相同。

我們學(xué)習(xí)了如何在應(yīng)用卷積運(yùn)算后計(jì)算輸出圖像大小,現(xiàn)在你應(yīng)該知道卷積層的通道大小直接是輸出通道大小,因?yàn)槲覀儜?yīng)用1個(gè)卷積運(yùn)算來(lái)獲得1個(gè)輸出圖像。因此,如果CNN層內(nèi)部有5個(gè)卷積核,我們?cè)谶@一層的末尾得到5個(gè)輸出核。

如果我們使用RGB圖像,我們有3個(gè)通道,而灰度圖像有1個(gè)通道。在這種情況下,我們應(yīng)用1個(gè)卷積核3次,以獲得輸出通道。因此,輸出圖像的通道大小不會(huì)改變,但卷積層的參數(shù)總數(shù)會(huì)改變。1個(gè)CNN層的參數(shù)總數(shù)計(jì)算如下:

因此,對(duì)于我們前面的例子,我們可以說(shuō)n=64 m=64 l=1 k=32。

池化

這是圖像分類(lèi)中使用的另一個(gè)重要術(shù)語(yǔ)。這是一種用于減少CNN模型參數(shù)的方法。

逐次減少參數(shù)的數(shù)量,并且只從特征映射中選擇最重要的特征(來(lái)自每個(gè)卷積層的輸出映射。正如我們之前所說(shuō),更深的卷積層具有更具體的特征)是很重要的。有兩種常見(jiàn)的池類(lèi)型:

最大池

其基本思想是再次使用窗口,此時(shí)不使用任何權(quán)重,在遍歷特征圖時(shí),選擇最大像素值作為輸出。

我們使用相同的公式來(lái)計(jì)算帶有卷積運(yùn)算的最大池的輸出映射大小,正如我前面提到的。重要的一點(diǎn)是,由于其目的是減少參數(shù)大小,因此給定padding size=0和stride size=池內(nèi)核的大小是一種合理且常用的方法,就像我們?cè)诒纠兴龅哪菢印?/p>

平均池

我們不是根據(jù)最大像素計(jì)算輸出,而是根據(jù)池內(nèi)核中像素的平均值計(jì)算輸出。

通用卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)

ImageNet大規(guī)模視覺(jué)識(shí)別挑戰(zhàn)賽(ILSVRC)多年來(lái)一直是一項(xiàng)非常受歡迎的比賽。我們將考察不同年份該競(jìng)賽的一些獲勝者。這些是圖像分類(lèi)任務(wù)中最常見(jiàn)的架構(gòu),與其他模型相比,它們具有更高的性能。

ImageNet是一個(gè)數(shù)據(jù)集,有1281167張訓(xùn)練圖像、50000張驗(yàn)證圖像和100000張1000個(gè)類(lèi)的測(cè)試圖像。

驗(yàn)證數(shù)據(jù)集:除了用于任何模型的訓(xùn)練步驟的訓(xùn)練數(shù)據(jù)集和訓(xùn)練步驟完成后用于測(cè)試模型的測(cè)試圖像,以計(jì)算模型精度性能外,它是模型以前沒(méi)有見(jiàn)過(guò)的數(shù)據(jù),即在訓(xùn)練階段不參與反向傳播權(quán)重更新階段,但用于測(cè)試,以便真實(shí)地跟蹤訓(xùn)練階段的進(jìn)度。

· AlexNet (2012)

該模型由8層組成,5層卷積,3層全連接。

用于RGB輸入圖像(3通道)

包含6000萬(wàn)個(gè)參數(shù)

ImageNet測(cè)試數(shù)據(jù)集的最終誤差為15.3%

該模型首次使用了ReLU激活函數(shù)。除了最后一個(gè)完全連接的層具有Softmax激活功能外,ReLu用作整個(gè)模型的激活功能

使用0.5%的Dropout。

使用動(dòng)量為0.9且批量為128的隨機(jī)梯度下降法

使用標(biāo)準(zhǔn)差=0.01的零平均高斯分布初始化權(quán)重

偏置以恒定值1初始化。

學(xué)習(xí)率初始化為0.01,“權(quán)重衰減正則化”應(yīng)用為0.0005

在上圖中,我們看到了AlexNet體系結(jié)構(gòu),我們有1000個(gè)完全連接層的節(jié)點(diǎn),因?yàn)镮mageNet數(shù)據(jù)集有1000個(gè)類(lèi)。

在這個(gè)架構(gòu)中,我們還遇到了一些我以前沒(méi)有提到的術(shù)語(yǔ):

動(dòng)量梯度下降:這是對(duì)梯度下降計(jì)算的優(yōu)化,我們將之前梯度下降的導(dǎo)數(shù)添加到我們的梯度下降計(jì)算中。我們用動(dòng)量超參數(shù)乘以這個(gè)附加部分,對(duì)于這個(gè)架構(gòu),動(dòng)量超參數(shù)是0.9。

高斯分布的權(quán)重初始化:在開(kāi)始訓(xùn)練模型之前,有不同的方法初始化我們的權(quán)重。例如,將每個(gè)權(quán)重設(shè)為0是一種方法,但這是一種糟糕的方法!與此相反,根據(jù)高斯分布初始化所有權(quán)重是一種常用的方法。我們只需要選擇分布的平均值和標(biāo)準(zhǔn)差,我們的權(quán)重將在這個(gè)分布范圍內(nèi)。

權(quán)重衰減優(yōu)化:在本文中使用SGD(隨機(jī)梯度下降)的情況下,L2正則化也是如此!

· VGG16(2014)

VGG架構(gòu)是一個(gè)16層模型,具有13個(gè)卷積和3個(gè)全連接。

它有1.38億個(gè)參數(shù)

測(cè)試數(shù)據(jù)集有7.5%的錯(cuò)誤

與AlexNet相反,每個(gè)卷積層中的所有核都使用相同的大小。這是3x3的核,步幅=1,填充=1。最大池為2x2,步長(zhǎng)=2

與AlexNet類(lèi)似,ReLu用于隱藏層,Softmax用于輸出層。動(dòng)量為0.9的SGD,衰減參數(shù)為0.00005的權(quán)重衰減正則化,初始學(xué)習(xí)率為0.01,使用高斯分布的權(quán)重初始化。

作為AlexNet之間的一個(gè)小差異,VGG16體系結(jié)構(gòu)使用批量大。256,將偏差初始化為0,而不是1,并且具有224x224x3的輸入圖像大小。

有一個(gè)新版本名為VGG19,共有19層。

· ResNet(2015)

該架構(gòu)的名稱來(lái)自Residual Blocks,其中Residual Blocks是“輸入”和“卷積和激活函數(shù)后的輸出”的組合。

有不同版本的ResNet具有不同數(shù)量的層,如Resnet18、Resnet34、Resnet50、Resnet101和Resnet152。在下圖中,我們?cè)谧髠?cè)看到一個(gè)“正!钡18層架構(gòu),在右側(cè)看到ResNet版本。

紅線表示它們具有相同的尺寸,因此可以直接組合,而藍(lán)線表示尺寸不同,因此應(yīng)使用零填充或在使用1x1卷積內(nèi)核使用兼容的填充調(diào)整大小。

ResNet152實(shí)現(xiàn)了測(cè)試數(shù)據(jù)集的%3.57錯(cuò)誤,它有58M個(gè)參數(shù)。當(dāng)我們將參數(shù)大小和小誤差與以前的架構(gòu)進(jìn)行比較時(shí),效果很好,對(duì)嗎?

除了可訓(xùn)練參數(shù)的數(shù)量外,浮點(diǎn)運(yùn)算(每秒浮點(diǎn)運(yùn)算)也是一個(gè)重要因素。讓我們比較一下我們迄今為止研究的模型:

如果你想研究更多類(lèi)型的卷積神經(jīng)網(wǎng)絡(luò),建議你搜索Inception、SeNet(2017年ILSVRC獲獎(jiǎng)?wù)撸┖蚆obileNet。

現(xiàn)在是我們將VGG16與Python和Tensorflow結(jié)合使用來(lái)應(yīng)用圖像分類(lèi)的時(shí)候了!

VGG架構(gòu)實(shí)現(xiàn)

# import necessary layers  

from tensorflow.keras.layers import Input, Conv2D , Dropout, MaxPool2D, Flatten, Dense

from tensorflow.keras import Model

from tensorflow.keras.preprocessing.image import ImageDataGenerator

from tensorflow.keras.regularizers import l2

import tensorflow as tf
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint

import os

import matplotlib.pyplot as plt

import sys

from tensorflow.keras.callbacks import CSVLogger

MODEL_FNAME = "trained_model.h5"

base_dir = "dataset"

tmp_model_name = "tmp.h5"

INPUT_SIZE = 224

BATCH_SIZE = 16

physical_devices = tf.config.list_physical_devices()

print("DEVICES : ", physical_devices)

print('Using:')

print(' u2022 Python version:',sys.version)

print(' u2022 TensorFlow version:', tf.__version__)

print(' u2022 tf.keras version:', tf.keras.__version__)

print(' u2022 Running on GPU' if tf.test.is_gpu_available() else ' u2022 GPU device not found. Running on CPU')

count = 0

previous_acc = 0

if not os.path.exists(MODEL_FNAME):
 

 """ Create VGG Model"""

  # input

  input = Input(shape =(INPUT_SIZE,INPUT_SIZE,3))
   

  weight_initializer = tf.keras.initializers.RandomNormal(mean=0.0, stddev=0.01, seed=None)

  bias_initializer=tf.keras.initializers.Zeros()
   

  # 1st Conv Block
 

   x = Conv2D (filters =64, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(input)

   x = Conv2D (filters =64, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

   x = MaxPool2D(pool_size =2, strides =2, padding ='same')(x)
   

  # 2nd Conv Block
   

   x = Conv2D (filters =128, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

   x = Conv2D (filters =128, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

   x = MaxPool2D(pool_size =2, strides =2, padding ='same')(x)
   

  # 3rd Conv block
   

   x = Conv2D (filters =256, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

   x = Conv2D (filters =256, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

   x = Conv2D (filters =256, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

   x = MaxPool2D(pool_size =2, strides =2, padding ='same')(x)
   

  # 4th Conv block
   

   x = Conv2D (filters =512, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

   x = Conv2D (filters =512, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

   x = Conv2D (filters =512, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

   x = MaxPool2D(pool_size =2, strides =2, padding ='same')(x)
   
          # 5th Conv block
   

   x = Conv2D (filters =512, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

   x = Conv2D (filters =512, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

   x = Conv2D (filters =512, kernel_size =3, padding ='same', activation='relu',kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

   x = MaxPool2D(pool_size =2, strides =2, padding ='same')(x)
   

   # Fully connected layers
   

   x = Flatten()(x)

   x = Dropout(0.5)(x)

   x = Dense(units = 4096, activation ='relu', kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)
          x = Dropout(0.5)(x)

   x = Dense(units = 4096, activation ='relu', kernel_initializer=weight_initializer,kernel_regularizer=l2(0.00005),bias_initializer=bias_initializer)(x)

  output = Dense(units = 2, activation ='softmax')(x)
   

  # creating the model
   

  model = Model (inputs=input, outputs =output)
   

  m = model

  m.save(tmp_model_name)

  del m

  tf.keras.backend.clear_session()
   

  model.summary()

 """ Prepare the Dataset for Training"""
   

  train_dir = os.path.join(base_dir, 'train')

  val_dir = os.path.join(base_dir, 'validation')

   

  train_batches = ImageDataGenerator(rescale = 1 / 255.).flow_from_directory(train_dir,
                                                        target_size=(INPUT_SIZE,INPUT_SIZE),
                                                        shuffle=True,
                                                        seed=42,
                                                        batch_size=BATCH_SIZE)

val_batches = ImageDataGenerator(rescale = 1 / 255.).flow_from_directory(val_dir,
                                                        target_size=(INPUT_SIZE,INPUT_SIZE),
                                                        shuffle=True,
                                                        seed=42,
                                                        batch_size=BATCH_SIZE)
   

  """ Train """
   

  class CustomLearningRateScheduler(tf.keras.callbacks.Callback):

      def __init__(self, schedule):

          super(CustomLearningRateScheduler, self).__init__()

          self.schedule = schedule
       

      def on_epoch_end(self, epoch, logs=None):

          if not hasattr(self.model.optimizer, "lr"):

              raise ValueError('Optimizer must have a "lr" attribute.')

          # Get the current learning rate from model's optimizer.

          lr = float(tf.keras.backend.get_value(self.model.optimizer.learning_rate))

          # Call schedule function to get the scheduled learning rate.

          # keys = list(logs.keys())

          # print("keys",keys)

          val_acc = logs.get("val_binary_accuracy")

          scheduled_lr = self.schedule(lr, val_acc)

          # Set the value back to the optimizer before this epoch starts

          tf.keras.backend.set_value(self.model.optimizer.lr, scheduled_lr)
         

  def learning_rate_scheduler(lr, val_acc):

      global count

      global previous_acc
     

      if val_acc == previous_acc:

        #  print("acc ", val_acc, "previous acc ", previous_acc)

          count += 1

      else:

          count = 0
       

      if count >= 5:

          print("acc is the same for 10 epoch, learnin rate decreased by /10")

          count = 0

          lr /= 10

          print("new learning rate:", lr)
           

      previous_acc = val_acc  

      return lr
   

  #compile the model by determining loss function Binary Cross Entropy, optimizer as SGD

  model.compile(optimizer=tf.keras.optimizers.SGD(lr=0.0000001, momentum=0.9),

                loss=tf.keras.losses.BinaryCrossentropy(),

                metrics=[tf.keras.metrics.BinaryAccuracy()],

                sample_weight_mode=[None])
   

  early_stopping = EarlyStopping(monitor='val_loss', patience=10)
       

  checkpointer = ModelCheckpoint(filepath=MODEL_FNAME, verbose=1, save_best_only=True)
   

  csv_logger = CSVLogger('log.csv', append=True, separator=' ')
   

  history=model.fit(train_batches,

      validation_data = val_batches,

      epochs = 100,

      verbose = 1,

      shuffle = True,

      callbacks = [checkpointer,early_stopping,CustomLearningRateScheduler(learning_rate_scheduler),csv_logger])
   

  """ Plot the train and validation Loss """
   

  plt.plot(history.history['loss'])

  plt.plot(history.history['val_loss'])

  plt.title('model loss')

  plt.ylabel('loss')

  plt.xlabel('epoch')

  plt.legend(['train', 'validation'], loc='upper left')

  plt.show()
   

  """ Plot the train and validation Accuracy """
 

  plt.plot(history.history['binary_accuracy'])

  plt.plot(history.history['val_binary_accuracy'])

  plt.title('model accuracy')

  plt.ylabel('accuracy')

  plt.xlabel('epoch')

  plt.legend(['train', 'validation'], loc='upper left')

  plt.show()
 

  print("End of Training")
   

else:
   

  """ Test """
   

  test_dir = os.path.join(base_dir, 'test')
   

  test_batches = ImageDataGenerator(rescale = 1 / 255.).flow_from_directory(test_dir,
                                                      target_size=(INPUT_SIZE,INPUT_SIZE),
                                                      class_mode='categorical',
                                                      shuffle=False,
                                                      seed=42,
                                                      batch_size=1)

  model = tf.keras.models.load_model(MODEL_FNAME)

  model.summary()
   

  # Evaluate on test data

  scores = model.evaluate(test_batches)

  print("metric names",model.metrics_names)
 

  print(model.metrics_names[0], scores[0])

  print(model.metrics_names[1], scores[1])
       

tf.keras.backend.clear_session()

這是使用Python和Tensorflow實(shí)現(xiàn)的VGG16。讓我們稍微檢查一下代碼

· 首先,我們檢查我們的TensorFlow Cuda Cudnn安裝是否正常,以及TensorFlow是否可以找到我們的GPU。因?yàn)槿绻皇沁@樣的話,那就意味著有一個(gè)包沖突或一個(gè)錯(cuò)誤,我們應(yīng)該解決,因?yàn)槭褂肅PU進(jìn)行模型訓(xùn)練太慢了。

· 然后,我們使用Conv2D函數(shù)為卷積層創(chuàng)建VGG16模型,MaxPool2D函數(shù)為最大池層,展平函數(shù)為CNN輸出一個(gè)能夠傳遞到全連接層的展平輸入,Dense函數(shù)為全連接層,Dropout函數(shù)為最后一個(gè)全連接層之間添加Dropout優(yōu)化?梢钥吹剑覀儜(yīng)該在使用相關(guān)層函數(shù)的同時(shí),將權(quán)重初始化、偏差初始化、l2正則化器1x1添加到層中。請(qǐng)注意,正態(tài)分布是高斯分布的同義詞,所以當(dāng)看到weight_initializer = tf.keras.initializers.RandomNormal(mean=0.0, stddev=0.01, seed=None),不要困惑

· 將用兩個(gè)類(lèi)來(lái)測(cè)試這個(gè)模型,而不是用1000個(gè)類(lèi)來(lái)測(cè)試巨大的ImageNet數(shù)據(jù)集,所以我將輸出層從1000個(gè)節(jié)點(diǎn)更改為2個(gè)節(jié)點(diǎn)

· 使用ImageGenerator函數(shù)和flow_from_directory,我們將數(shù)據(jù)集準(zhǔn)備為能夠使用TensorFlow模型的向量。我們這里也給出了批量大小。(由于我的電腦內(nèi)存不足,我可以給出16個(gè),而不是論文中提到的256個(gè)。shuffle參數(shù)意味著數(shù)據(jù)集將在每個(gè)epoch前被打亂

· model.compile()函數(shù)是訓(xùn)練模型之前的最后一部分。我們決定使用哪個(gè)優(yōu)化器(帶動(dòng)量的SGD)、哪個(gè)損失函數(shù)(因?yàn)樵谖覀兊睦又形覀冇袃蓚(gè)類(lèi)),以及在訓(xùn)練期間計(jì)算性能的指標(biāo)(二進(jìn)制精度)。

· 使用model.fit()訓(xùn)練我們的模型。添加了一些選項(xiàng),比如“EarlyStoping”和“ModelCheckPoint”回調(diào)。如果驗(yàn)證損失在10個(gè)epoch內(nèi)沒(méi)有增加,則第一個(gè)epoch停止訓(xùn)練,如果驗(yàn)證損失比前一epoch好,則模型檢查點(diǎn)不僅在最后保存我們的模型,而且在訓(xùn)練期間保存我們的模型。

· CustomLearningRateScheduler是我手動(dòng)實(shí)現(xiàn)的學(xué)習(xí)速率調(diào)度器,用于應(yīng)用“如果驗(yàn)證準(zhǔn)確率停止提高,我們將通過(guò)除以10來(lái)更新學(xué)習(xí)速率”

· 在訓(xùn)練結(jié)束時(shí),我將驗(yàn)證和訓(xùn)練數(shù)據(jù)集的準(zhǔn)確性和損失圖可視化。

· 最后一部分是使用測(cè)試數(shù)據(jù)集測(cè)試模型。我們檢查是否有任何名為“trained_model.h5”的訓(xùn)練模型,如果沒(méi)有,我們訓(xùn)練一個(gè)模型,如果有,我們使用這個(gè)模型來(lái)測(cè)試性能

想解釋一下我在解釋代碼時(shí)使用的一些術(shù)語(yǔ):

· 驗(yàn)證集準(zhǔn)確率:驗(yàn)證集準(zhǔn)確率是一種衡量模型在驗(yàn)證數(shù)據(jù)集上表現(xiàn)的指標(biāo)。它只是檢查在驗(yàn)證數(shù)據(jù)集中有多少圖像預(yù)測(cè)為真。我們也會(huì)對(duì)訓(xùn)練數(shù)據(jù)集進(jìn)行同樣的檢查。但這只是為了我們監(jiān)控模型,只有訓(xùn)練損失用于梯度下降計(jì)算

· 迭代-epoch:1迭代是為一批中的所有圖像提供模型的過(guò)程。Epoch是指為訓(xùn)練數(shù)據(jù)集中的所有圖像提供模型的過(guò)程。例如,如果我們有100張圖片,batch=10,那么1個(gè)epoch將有10次迭代。

· 展平:這只不過(guò)是重塑CNN輸出,使其具有1D輸入,這是從卷積層傳遞到全連接層的唯一方法。

· 現(xiàn)在讓我們檢查一下結(jié)果

即使通過(guò)監(jiān)控驗(yàn)證準(zhǔn)確度來(lái)改變學(xué)習(xí)率,結(jié)果也不是很好,驗(yàn)證準(zhǔn)確度也沒(méi)有提高。但是為什么它不能像VGG16論文中那樣直接工作呢?

1. 在最初的論文中,我們討論了374個(gè)epoch的1000個(gè)輸出類(lèi)和1500000個(gè)圖像。不幸的是,使用這個(gè)數(shù)據(jù)集并訓(xùn)練1000個(gè)類(lèi)的模型需要幾天的時(shí)間,所以正如我之前所說(shuō),我使用了兩個(gè)類(lèi)的數(shù)據(jù)集,并將輸出層更改為有兩個(gè)輸出節(jié)點(diǎn)。

1. 模型體系結(jié)構(gòu)和數(shù)據(jù)集都需要不同的優(yōu)化,因此一個(gè)運(yùn)行良好的模型可能不適用于另一個(gè)數(shù)據(jù)集。

3. 微調(diào)超參數(shù)以改進(jìn)模型與首先了解如何構(gòu)建模型一樣重要。你可能已經(jīng)注意到我們需要微調(diào)多少超參數(shù)

· 權(quán)重和偏差初始化

· 損失函數(shù)選擇

· 初始學(xué)習(xí)率選擇

· 優(yōu)化器選擇(梯度下降法)

· Dropout與否的使用

· 數(shù)據(jù)擴(kuò)充(如果你沒(méi)有足夠的圖像,或者它們太相似,你希望獲得相同圖像的不同版本)

等等…

但在你對(duì)如何優(yōu)化這么多變量感到悲觀之前,想提兩點(diǎn)。

1.遷移學(xué)習(xí)

這是一種非常重要的方法,我們使用預(yù)先訓(xùn)練好的模型,用我們自己的數(shù)據(jù)集對(duì)其進(jìn)行訓(xùn)練。在我們的例子中,我們不會(huì)只使用VGG16體系結(jié)構(gòu),而是一個(gè)已經(jīng)使用VGG16體系結(jié)構(gòu)和ImageNet數(shù)據(jù)集訓(xùn)練過(guò)的模型,我們將使用比ImageNet小得多的數(shù)據(jù)集對(duì)其進(jìn)行重新訓(xùn)練。

這種方法為我們提供了一個(gè)并非未知的起點(diǎn)——一些隨機(jī)權(quán)重。我們的數(shù)據(jù)集可能包含不同的對(duì)象,但不同對(duì)象之間有一些基本的共同特征,比如邊、圓形,為什么不使用它們,而不是從頭開(kāi)始呢?我們將看到這種方法如何減少耗時(shí)并提高精度性能。

2.當(dāng)查看我們的輸出時(shí),發(fā)現(xiàn)問(wèn)題不是因?yàn)槟P屯V箤W(xué)習(xí),而是因?yàn)樗鼪](méi)有開(kāi)始學(xué)習(xí)!精度從0.5開(kāi)始,沒(méi)有改變!這個(gè)具體的結(jié)果給了我們一個(gè)非常明確的信息:我們應(yīng)該降低學(xué)習(xí)率,讓模型開(kāi)始學(xué)習(xí)。請(qǐng)記住,學(xué)習(xí)率是學(xué)習(xí)的步長(zhǎng),每次迭代的梯度下降計(jì)算會(huì)對(duì)權(quán)重產(chǎn)生多大影響。如果這個(gè)速率太大,步驟太大,我們無(wú)法控制權(quán)重更新,因此模型無(wú)法學(xué)習(xí)任何東西。

以下代碼僅包括遷移學(xué)習(xí)的更改,其中采用了帶有預(yù)訓(xùn)練權(quán)重Tensorflow內(nèi)置模型和較低學(xué)習(xí)率(0.001)的模型

# -*- coding: utf-8 -*-

"""

Created on Wed Dec 29 20:56:04 2021

@author: aktas

"""

# import necessary layers  

from tensorflow.keras.preprocessing.image import ImageDataGenerator

from tensorflow.keras.regularizers import l2

import tensorflow as tf

from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint

import os

import matplotlib.pyplot as plt

import sys

from tensorflow.keras.callbacks import CSVLogger

from tensorflow.keras.a(chǎn)pplications import vgg16, imagenet_utils

from tensorflow.keras.layers import Dense, Flatten

from tensorflow.keras.models import Model

MODEL_FNAME = "pretrained_model.h5"

tmp_model_name = "tmp.h5"

base_dir = "dataset"

INPUT_SIZE = 224

BATCH_SIZE = 16

physical_devices = tf.config.list_physical_devices()

print("DEVICES : ", physical_devices)

print('Using:')

print(' u2022 Python version:',sys.version)

print(' u2022 TensorFlow version:', tf.__version__)

print(' u2022 tf.keras version:', tf.keras.__version__)

print(' u2022 Running on GPU' if tf.test.is_gpu_available() else '

u2022 GPU device not found. Running on CPU')

count = 0

previous_acc = 0

if not os.path.exists(MODEL_FNAME):
   

  base_model = vgg16.VGG16(weights='imagenet', include_top=False, input_shape=(INPUT_SIZE,INPUT_SIZE,3))
   

  m = base_model

  m.save(tmp_model_name)

  del m

  tf.keras.backend.clear_session()
   

  print("Number of layers in the base model: ", len(base_model.layers))

  base_model.trainable = False

  last_output = base_model.output

  x = Flatten()(last_output)

  x = Dense(2, activation='softmax')(x)

  model = Model(inputs=[base_model.input], outputs=[x])
   

  model.summary()
   

  """ Prepare the Dataset for Training"""
   
   

  train_dir = os.path.join(base_dir, 'train')
   val_dir = os.path.join(base_dir, 'validation')

   

  train_batches = ImageDataGenerator(rescale = 1 / 255.).flow_from_directory(train_dir,
                                                        target_size=(INPUT_SIZE,INPUT_SIZE),
                                                        shuffle=True,
                                                        seed=42,
                                                        batch_size=BATCH_SIZE)

  val_batches = ImageDataGenerator(rescale = 1 / 255.).flow_from_directory(val_dir,
                                                        target_size=(INPUT_SIZE,INPUT_SIZE),
                                                        shuffle=True,
                                                        seed=42,
                                                        batch_size=BATCH_SIZE)
   

  """ Train """
   

  class CustomLearningRateScheduler(tf.keras.callbacks.Callback):

      def __init__(self, schedule):

          super(CustomLearningRateScheduler, self).__init__()

          self.schedule = schedule
       

      def on_epoch_end(self, epoch, logs=None):

          if not hasattr(self.model.optimizer, "lr"):

              raise ValueError('Optimizer must have a "lr" attribute.')

          # Get the current learning rate from model's optimizer.

          lr = float(tf.keras.backend.get_value(self.model.optimizer.learning_rate))

          # Call schedule function to get the scheduled learning rate.

          # keys = list(logs.keys())

          # print("keys",keys)

          val_acc = logs.get("val_binary_accuracy")

          scheduled_lr = self.schedule(lr, val_acc)

          # Set the value back to the optimizer before this epoch starts

          tf.keras.backend.set_value(self.model.optimizer.lr, scheduled_lr)
         

  def learning_rate_scheduler(lr, val_acc):

      global count

      global previous_acc
     

      if val_acc <= previous_acc:

        #  print("acc ", val_acc, "previous acc ", previous_acc)

          count += 1

      else:

          previous_acc = val_acc  

          count = 0
       

      if count >= 5:

          print("acc is the same for 10 epoch, learnin rate decreased by /10")

          count = 0

          lr /= 10

          print("new learning rate:", lr)
           
     

      return lr
   

  #compile the model by determining loss function Binary Cross Entropy, optimizer as SGD

  model.compile(optimizer=tf.keras.optimizers.SGD(lr=0.001, momentum=0.9),
                 loss=tf.keras.losses.BinaryCrossentropy(),
                 metrics=[tf.keras.metrics.BinaryAccuracy()],
                 sample_weight_mode=[None])
   

  early_stopping = EarlyStopping(monitor='val_loss', patience=10)
       

  checkpointer = ModelCheckpoint(filepath=MODEL_FNAME, verbose=1, save_best_only=True)
   

  csv_logger = CSVLogger('log.csv', append=True, separator=' ')
   

  history=model.fit(train_batches,

      validation_data = val_batches,

      epochs = 100,

      verbose = 1,

      shuffle = True,

      callbacks = [checkpointer,early_stopping,CustomLearningRateScheduler(learning_rate_scheduler),csv_logger])
   

  """ Plot the train and validation Loss """
   

  plt.plot(history.history['loss'])

  plt.plot(history.history['val_loss'])

  plt.title('model loss')

  plt.ylabel('loss')

  plt.xlabel('epoch')

  plt.legend(['train', 'validation'], loc='upper left')

  plt.show()
   

  """ Plot the train and validation Accuracy """
 

  plt.plot(history.history['binary_accuracy'])

  plt.plot(history.history['val_binary_accuracy'])

  plt.title('model accuracy')

  plt.ylabel('accuracy')

  plt.xlabel('epoch')

  plt.legend(['train', 'validation'], loc='upper left')

  plt.show()
 

  print("End of Training")
   

else:
   

  """ Test """
   

  test_dir = os.path.join(base_dir, 'test')
   

  test_batches = ImageDataGenerator(rescale = 1 / 255.).flow_from_directory(test_dir,
                                                      target_size=(INPUT_SIZE,INPUT_SIZE),
                                                      class_mode='categorical',
                                                      shuffle=False,
                                                      seed=42,
                                                      batch_size=1)

  model = tf.keras.models.load_model(MODEL_FNAME)

  model.summary()
   

  # Evaluate on test data

  scores = model.evaluate(test_batches)

  print("metric names",model.metrics_names)
 

  print(model.metrics_names[0], scores[0])

  print(model.metrics_names[1], scores[1])
       

tf.keras.backend.clear_session()

讓我們檢查一下結(jié)果:

性能有了很大的提高,對(duì)吧?!我們看到,驗(yàn)證精度不再被卡住,直到0.97,而訓(xùn)練精度達(dá)到1.00。

一切似乎都很好!

想和大家分享我做的一些實(shí)驗(yàn)及其結(jié)果:

現(xiàn)在,我們可以做以下分析:

如果我們有一個(gè)不兼容的學(xué)習(xí)率,遷移學(xué)習(xí)本身仍然是不夠的。

如果我們將base_model_trainable變量設(shè)置為False,這意味著我們不訓(xùn)練我們所使用的模型,我們只訓(xùn)練我們添加的最后一個(gè)全連接層。(VGG有1000個(gè)輸出,所以我使用include_top=False不使用最后的全連接層,并且我在這個(gè)預(yù)訓(xùn)練模型的末尾添加了一個(gè)自己的全連接層,正如我們?cè)诖a中看到的那樣)

從零開(kāi)始改變了vgg_from_scratch實(shí)現(xiàn)的學(xué)習(xí)率,但validation 準(zhǔn)確率仍然停留在0.5上,所以僅僅優(yōu)化學(xué)習(xí)率本身是不夠的。(至少如果你沒(méi)有時(shí)間在微調(diào)后從頭開(kāi)始訓(xùn)練模型。)

可以通過(guò)此鏈接訪問(wèn)使用的數(shù)據(jù)集,請(qǐng)不要忘記將其與下面的代碼放在同一文件夾中(當(dāng)然,訓(xùn)練完成后,.h和日志文件會(huì)出現(xiàn)????):

https://drive.google.com/drive/folders/1vt8HiybDroEMCvpdGQJx2T50Co4nZNYe?usp=sharing

使用Anaconda和Python=3.7、Tensorflow=2.2.0、Cuda=10.1、Cudnn=7.6.5來(lái)使用GPU訓(xùn)練和測(cè)試我的模型。如果你不熟悉這些術(shù)語(yǔ),這里有一個(gè)快速教程:

下載Anaconda(請(qǐng)為你的計(jì)算機(jī)選擇正確的系統(tǒng))

打開(kāi)Anaconda terminal,并使用conda create-n im_class python=3.7 Anaconda命令創(chuàng)建環(huán)境。環(huán)境可能是Anaconda最重要的屬性,它允許你為不同的項(xiàng)目單獨(dú)工作。你可以將任何包添加到你的環(huán)境中,如果你需要另一個(gè)包的另一個(gè)版本,你可以簡(jiǎn)單地創(chuàng)建另一個(gè)環(huán)境,以避免另一個(gè)項(xiàng)目崩潰,等等。

使用conda activate im_class命令進(jìn)入你的環(huán)境以添加更多包(如果你忘記了這一步,你基本上不會(huì)對(duì)你的環(huán)境進(jìn)行更改,而是在所有anaconda空間中進(jìn)行更改)

使用pip install Tensorflow GPU==2.2.0安裝具有GPU功能的Tensorflow

使用conda Install cudatoolkit=10.1命令安裝CUDA和Cudnn

現(xiàn)在,你已經(jīng)準(zhǔn)備好測(cè)試上述代碼了!

請(qǐng)注意,軟件包版本之間的沖突是一個(gè)巨大的問(wèn)題。這將幫助你在構(gòu)建環(huán)境時(shí)花費(fèi)更少的時(shí)間。

你已經(jīng)完成了卷積神經(jīng)網(wǎng)絡(luò)圖像分類(lèi)教程。你可以嘗試從頭開(kāi)始構(gòu)建任何模型(甚至可能是你自己的模型)對(duì)其進(jìn)行微調(diào),針對(duì)不同的體系結(jié)構(gòu)應(yīng)用遷移學(xué)習(xí),等等。

參考引用

image.png


       原文標(biāo)題 : 基于卷積神經(jīng)網(wǎng)絡(luò)的圖像分類(lèi)

聲明: 本文由入駐維科號(hào)的作者撰寫(xiě),觀點(diǎn)僅代表作者本人,不代表OFweek立場(chǎng)。如有侵權(quán)或其他問(wèn)題,請(qǐng)聯(lián)系舉報(bào)。

發(fā)表評(píng)論

0條評(píng)論,0人參與

請(qǐng)輸入評(píng)論內(nèi)容...

請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字

您提交的評(píng)論過(guò)于頻繁,請(qǐng)輸入驗(yàn)證碼繼續(xù)

  • 看不清,點(diǎn)擊換一張  刷新

暫無(wú)評(píng)論

暫無(wú)評(píng)論

    掃碼關(guān)注公眾號(hào)
    OFweek人工智能網(wǎng)
    獲取更多精彩內(nèi)容
    文章糾錯(cuò)
    x
    *文字標(biāo)題:
    *糾錯(cuò)內(nèi)容:
    聯(lián)系郵箱:
    *驗(yàn) 證 碼:

    粵公網(wǎng)安備 44030502002758號(hào)