主頁 >  其他 > 《Python深度學習》讀書筆記:第5章 深度學習用于計算機視覺

《Python深度學習》讀書筆記:第5章 深度學習用于計算機視覺

2022-02-22 08:05:44 其他

目錄

第5章 深度學習用于計算機視覺

5.1 卷積神經網路簡介

5.1.1 卷積運算

5.1.2 最大池化運算

5.2 在小型資料集上從頭開始訓練一個卷積神經網路

5.2.1 深度學習與小資料問題的相關性

5.2.2 下載資料

5.2.3 構建網路

5.2.4 資料預處理

5.2.5 使用資料增強

5.3 使用預訓練的卷積神經網路

5.3.1 特征提取

5.3.2 微調模型

5.3.3 小結

5.4 卷積神經網路的可視化

5.4.1 可視化中間激活


第5章 深度學習用于計算機視覺

本章將介紹卷積神經網路,也叫convnet,它是計算機視覺應用幾乎都在使用的一種深度學習模型,

5.1 卷積神經網路簡介

實體化一個小型的卷積神經網路:

from keras import layers
from keras import models

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activaition='relu'))

重要的是,卷積神經網路接收形狀為(image_height, image_width, image_channels)的輸入張量(不包括批量維度),我們來看一下目前卷積神經網路的架構:

print(model.summary())
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 26, 26, 32)        320       
                                                                 
 max_pooling2d (MaxPooling2D  (None, 13, 13, 32)       0         
 )                                                               
                                                                 
 conv2d_1 (Conv2D)           (None, 11, 11, 64)        18496     
                                                                 
 max_pooling2d_1 (MaxPooling  (None, 5, 5, 64)         0         
 2D)                                                             
                                                                 
 conv2d_2 (Conv2D)           (None, 3, 3, 64)          36928     
                                                                 
=================================================================
Total params: 55,744
Trainable params: 55,744
Non-trainable params: 0
_________________________________________________________________
None

在卷積神經網路上添加分類器:

model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))

print(model.summary())

現在的網路架構如下:

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 26, 26, 32)        320       
                                                                 
 max_pooling2d (MaxPooling2D  (None, 13, 13, 32)       0         
 )                                                               
                                                                 
 conv2d_1 (Conv2D)           (None, 11, 11, 64)        18496     
                                                                 
 max_pooling2d_1 (MaxPooling  (None, 5, 5, 64)         0         
 2D)                                                             
                                                                 
 conv2d_2 (Conv2D)           (None, 3, 3, 64)          36928     
                                                                 
 flatten (Flatten)           (None, 576)               0         
                                                                 
 dense (Dense)               (None, 64)                36928     
                                                                 
 dense_1 (Dense)             (None, 10)                650       
                                                                 
=================================================================
Total params: 93,322
Trainable params: 93,322
Non-trainable params: 0
_________________________________________________________________
None

在MNIST影像上訓練卷積神經網路:

from keras.datasets import mnist
from keras.utils.np_utils import to_categorical

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255

test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255

train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
model.fit(train_images, train_labels, epochs=5, batch_size=64)

完整代碼:

from keras.datasets import mnist
from keras.utils.np_utils import to_categorical
from keras import layers
from keras import models

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))


(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255

test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255

train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
model.fit(train_images, train_labels, epochs=5, batch_size=64)

test_loss, test_acc = model.evaluate(test_images, test_labels)

print(test_acc)
Epoch 1/5
938/938 [==============================] - 33s 24ms/step - loss: 0.1748 - accuracy: 0.9453
Epoch 2/5
938/938 [==============================] - 26s 28ms/step - loss: 0.0481 - accuracy: 0.9848
Epoch 3/5
938/938 [==============================] - 21s 22ms/step - loss: 0.0331 - accuracy: 0.9895
Epoch 4/5
938/938 [==============================] - 21s 22ms/step - loss: 0.0250 - accuracy: 0.9926
Epoch 5/5
938/938 [==============================] - 20s 22ms/step - loss: 0.0191 - accuracy: 0.9940
313/313 [==============================] - 4s 9ms/step - loss: 0.0348 - accuracy: 0.9898
0.989799976348877

5.1.1 卷積運算

密集連接層和圈進層的根本區別在于,Dense層從輸入特征空間學到的是全域模式(比如對于MNIST數字,全域模式就是涉及所有像素的模式),而卷積層學到的是區域模式,對于影像來說,學到的就是在輸入影像的二維小視窗中發現的模式,

這個重要特性使卷積神經網路具有以下兩個有趣的性質:

1.卷積神經網路學到的模式具有平移不變性,卷積神經網路在影像右下角學到某個模式之后,它可以在任何地方識別這個模式,比如左上角,對于密集連接網路來說,如果模式出現在新的位置,它只能重新學習這個模式,這使得卷積神經網路在處理影像時可以高效利用資料(因為視覺世界從根本上具有平移不變性),它只需要更少的訓練樣本就可以學到具有泛化能力的資料表示,

2.卷積神經網路可以學到模式的空間層次結構,第一個卷積層將學習較小的區域模式(比如邊緣),第二個卷積層將學習由第一層特征組成的更大的模式,以此類推,這使得卷積神經網路可以有效地學習越來越復雜、越來越抽象的視覺概念(因為視覺世界從根本上具有空間層次結構),

對于包含兩個空間軸(高度和寬度)和一個深度軸(也叫通道軸)的3D張量,其卷積也叫特征圖,對于RGB影像,深度軸的維度大小等于3, 因為影像有3個顏色通道:紅色、綠色和藍色,對于黑白影像(比如MNIST數字影像),深度等于1(表示灰度等級),卷積運算從輸入特征圖中提取圖塊,并對所有這些圖塊應用相同的變換,生成輸出特征圖,該輸出特征圖仍是一個3D張量,具有寬度和高度,其深度可以任意取值,因為輸出深度是層的引數,深度軸的不同通道不再像RGB那樣代表特定顏色,而是代表過濾器,過濾器對輸入資料的某一方面進行編碼 ,

特征圖:深度軸的每個維度都是一個特征(或過濾器),而2D張量output[:, :, n]是這個過濾器在輸入上的回應的二維空間圖,

卷積由以下兩個關鍵引數所定義,

1.從輸入中提取的圖塊尺寸,

2.輸出特征圖的深度:卷積所計算的過濾器的數量,

卷積的作業原理:在3D輸入特征圖上滑動這些3x3或5x5的視窗,在每個可能的位置停止并提取周圍特征的3D圖塊[形狀為(window_height, window_width, input_depth)],然后每個3D圖塊與學到的同一個權重矩陣[叫作卷積核]做張量積,轉換成形狀為(output_depth,)的1D向量,然后對所有這些向量進行空間重組,使其轉換為形狀為(height, width, output_depth)的3D輸出特征圖,輸出特征圖中的每個空間位置都對應于輸入特征圖中的相同位置,

輸出的寬度和高度可能與輸入的寬度和高度不同,不同的原因可能有兩點:

(1)邊界效應,可以通過對輸入特征圖進行填充來抵消;

(2)使用了步幅,

1.理解邊界效應與填充

如果你希望輸出特征圖的空間維度與輸入相同,那么可以使用填充,填充是在輸入特征圖的每一邊添加適當資料的行和列,使得每個輸入方塊都能作為卷積視窗的中心,

2.理解卷積步幅

影響輸出尺寸的另一個因素是步幅的概念,目前為止,對卷積的描述都假設卷積視窗的中心方塊都是相鄰的,但兩個連續視窗的距離是卷積的一個引數,叫作步幅,默認值為1,也可以使用步進卷積,即步幅大于1的卷積,

步幅為2意味著特征圖的寬度和高度都被做了2倍下采樣(除了邊界效應引起的變化),雖然步進卷積對某些型別的模型可能有用,但在實踐中很少使用,

為了對特征圖進行下采樣,我們不用步幅,而是通常使用最大池化運算,

5.1.2 最大池化運算

在卷積神經網路示例中,你可能注意到,在每個MaxPooling2D層之后,特征圖的尺寸都會減半,例如,在第一個MaxPooling2D層之前,特征圖的尺寸是26x26,但最大池化運算將其減半為13x13,這就是最大池化的作用:對特征圖進行下采樣,與步進卷積類似,

最大池化是從輸入特征圖中提取視窗,并輸出每個通道的最大值,它使用硬編碼的max張量運算對區域圖塊進行變換,而不是使用學到的線性變換,最大池化與卷積的最大不同之處在于,最大池化通常使用2x2的視窗和步幅2,其目的是將特征圖下采樣2倍,與此相對的是,卷積通常使用3x3視窗和步幅1,

from keras import layers
from keras import models

model_no_max_pool = models.Sequential()
model_no_max_pool.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model_no_max_pool.add(layers.Conv2D(64, (3, 3), activation='relu'))
model_no_max_pool.add(layers.Conv2D(64, (3, 3), activation='relu'))

print(model_no_max_pool.summary())
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 26, 26, 32)        320       
                                                                 
 conv2d_1 (Conv2D)           (None, 24, 24, 64)        18496     
                                                                 
 conv2d_2 (Conv2D)           (None, 22, 22, 64)        36928     
                                                                 
=================================================================
Total params: 55,744
Trainable params: 55,744
Non-trainable params: 0
_________________________________________________________________
None

正在上傳…重新上傳取消正在上傳…重新上傳取消

這種架構有如下兩點問題:

1.這種架構不利于學習特征的空間層級結構,

2.最后一層的特征圖對每個樣本共有22x22x64=30976個元素,這太多了,如果你將其展平并在上面添加一個大小為512的Dense層,那一層將會有1580萬個引數,這對于這樣一個小模型來說太多了,會導致嚴重的過擬合,

簡而言之,使用下采樣的原因,一是減少需要處理的特征圖的元素個數,二是通過讓連續的卷積層的觀察視窗越來越大(即視窗覆寫原始輸入的比例越來越大),從而引入空間過濾器的層級結構,

最大池化不是實作這種下采樣的唯一方法,還可以在前一個卷積層中使用步幅來實作,此外,還可以使用平均池化來代替最大池化,其方法是將每個區域輸入圖塊變換為取該圖塊各通道的平均值,而不是最大值,但最大池化的效果往往比這些替代方法更好,

簡而言之,原因在于特征中往往編碼了某種模式或概念在特征圖的不同位置是否存在(因此得名特征圖),而觀察不同特征的最大值而不是平均值能夠給出更多的資訊,因此,最合理的子采樣策略是首先生成密集的特征圖(通過步進卷積)或對輸入圖塊取平均,因為后兩種方法可能導致錯過或淡化特征是否存在的資訊,

5.2 在小型資料集上從頭開始訓練一個卷積神經網路

5.2.1 深度學習與小資料問題的相關性

深度學習的一個基本特性就是能夠獨立地在訓練資料中找到有趣的特征,無須人為的特征工程,而這只有擁有大量訓練樣本時才能實作,對于輸入樣本的維度非常高(比如影像)的問題尤其如此,

由于卷積神經網路學到的是區域的、平移不變的特征,它對于感知問題可以高效地利用資料,雖然資料相對較少,但在非常小的影像資料集上從頭開始訓練一個卷積神經網路,仍然可以得到不錯的結果,而且無須任何自定義的特征工程,

此外,深度學習模型本質上具有高度的可復用性,比如,已有一個在大規模資料集上訓練的影像分類模型或語音轉文本模型,你只需做很小的修改就能將其復用與完全不同的問題,特別是在計算機視覺領域,許多預訓練的模型(通常都是在ImageNet資料集上訓練得到的)現在都可以公開下載,并可以用于在資料很少的情況下構建強大的視覺模型,

5.2.2 下載資料

將影像復制到訓練、驗證和測驗的目錄:

import os
import shutil

original_dataset_dir = r'E:\train\train'

base_dir = r'E:\train\cats_and_dogs_small'
os.mkdir(base_dir)

train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)

train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)

train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)

validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)

validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)

test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)

test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)

fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_cats_dir, fname)
    shutil.copyfile(src, dst)

fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_cats_dir, fname)
    shutil.copyfile(src, dst)

fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_cats_dir, fname)
    shutil.copyfile(src, dst)

fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_dogs_dir, fname)
    shutil.copyfile(src, dst)

fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_dogs_dir, fname)
    shutil.copyfile(src, dst)

fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_dogs_dir, fname)
    shutil.copyfile(src, dst)

print('total training cat image:', len(os.listdir(train_cats_dir)))
print('total training dog images:', len(os.listdir(train_dogs_dir)))
print('total validation cat images:', len(os.listdir(validation_cats_dir)))
print('total validation dog images:', len(os.listdir(validation_dogs_dir)))
print('total test cat images:', len(os.listdir(test_cats_dir)))
print('total test cat images:', len(os.listdir(test_dogs_dir)))
total training cat image: 1000
total training dog image: 1000
total validation cat image: 500
total validation dog image: 500
total test cat image: 500
total test dog image: 500

所以我們的確有2000張訓練影像、1000張驗證影像和1000張測驗影像,每個分組中兩個類別的樣本數相同,這是一個平衡的二分類問題,分類精度可作為衡量成功的指標,

5.2.3 構建網路

你面對的是一個二分類問題,所以網路最后一層是使用sigmoid激活的單一單元(大小為1的Dense層),這個單元將對某個類別的概率進行編碼,

將貓狗分類的小型卷積神經網路實體化:

from keras import layers
from keras import models

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

print(model.summary())

配置模型用于訓練:

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])

5.2.4 資料預處理

現在,資料以JPEG檔案的形式形式保存在硬碟中,所以資料預處理步驟大致如下:

(1)讀取影像檔案;

(2)將JPEG檔案解碼為RGB像素網格;

(3)將這些像素網路轉換為浮點數張量;

(4)將像素值(0~255范圍內)縮放到[0, 1]區間(神經網路喜歡處理較小的輸入值),

使用ImageDataGenerator從目錄中讀取影像:

from keras.preprocessing.image import ImageDataGenerator

train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
    train_dir,
    target_size=(150, 150),
    batch_size=20,
    class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
    validation_dir,
    target_size=(150, 150),
    batch_size=20, 
    class_mode='binary'
)

利用批量生成器擬合模型:

history = model.fit_generator(
    train_generator,
    steps_per_epoch=100,
    epochs=30,
    validation_data=validation_generator,
    validation_steps=50
)

保存模型:

model.save('cats_and_dogs_small_1.h5')

繪制訓練程序中的損失曲線和精度曲線:

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

從這些影像中都能看出過擬合的特征,訓練精度隨著時間線性增加,直到接近100%,而驗證精度則停留在70%~72%,驗證損失僅在5輪后就達到最小值,然后保持不變,而訓練損失則一直線性下降,直到接近于0,因為訓練樣本相對較少(2000個),所以過擬合是你最關心的問題,

5.2.5 使用資料增強

過擬合的原因是學習樣本太少,導致無法訓練出能夠泛化到新資料的模型,如果擁有無限的資料,那么模型能夠觀察到資料分布的所有內容,這樣就永遠不會過擬合,資料增強是從現有的訓練樣本中生成更多的訓練資料,其方法是利用多種能夠生成可信影像的隨機變換來增加樣本,其目標是,模型在訓練時不會兩次查看完全相同的影像,這讓模型能夠觀察到資料的更多內容,從而具有更好的泛化能力,

利用ImageDataGenerator來設定資料增強:

datagen = ImageDataGenerator(
    rotation_range=40,
    width_shift_range=0.2,
    heigth_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
    fill_mode='nearest'
)

顯示幾個隨機增強后的訓練影像:

# 顯示幾個隨機增強后的訓練影像
fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]
img_path = fnames[3]  # 選擇一張影像進行增強
img = image.load_img(img_path, target_size=(150, 150))  # 讀取影像并調整大小
x = image.img_to_array(img)  # 將其轉換為形狀(150,150,3)的Numpy陣列
x = x.reshape((1,) + x.shape)  # 將其形狀改變為(1,150,150,3)
i = 0
for batch in datagen.flow(x, batch_size=1):
    plt.figure(i)
    imgplot = plt.imshow(image.array_to_img(batch[0]))
    i += 1
    if i % 4 == 0:
        break

plt.show()

如果使用這種資料增強來訓練一個新網路,那么網路將不會兩次看到同樣的輸入,但網路看到的輸入仍然是高度相關的,因為這些輸入都來自于少量的原始影像,你無法生成新資訊,而只能混合現有資訊,因此,這種方法可能不足以完全消除過擬合,為了進一步降低過擬合,還需要向模型中添加一個Dropout層,添加到密集連接分類器之前,

定義一個包含dropout的新卷積神經網路:

# 定義一個包含dropout的新卷積神經網路
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activtion='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metircs=['acc'])

利用資料增強生成器訓練卷積神經網路:

# 利用資料增強生成訓練卷積神經網路
train_datagen = ImageDataGenerator(
    rescale=1./255,
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
)

test_datagen = ImageDataGenerator(rescale=1./255)  # 注意,不能增強驗證資料

train_generator = train_datagen.flow_from_directory(
    train_dir,  # 目標目錄
    target_size=(150, 150),  # 將所有影像的大小調整為150x150
    batch_size=32,
    class_mode='binary'  # 因為使用了binary_crossentropy損失,所以需要用二進制標簽
)

validation_generator = test_datagen.flow_from_directory(
    validation_dir,
    target_size=(150, 150),
    batch_size=32,
    class_mode='binary'
)

history = model.fit(
    train_generator,
    steps_per_epoch=100,
    epochs=100,
    validation_data=validation_generator,
    validation_steps=50
)

保存模型:

# 保存模型
model.save('cats_and_dogs_small_2.h5')

我們再次繪制結果:

通過進一步使用正則化方法以及調節網路引數(比如每個卷積層的過濾器個數或網路中的層數),你可以得到更高的精度,但只靠從頭開始訓練自己的卷積神經網路,再想提高精度就十分困難,因為可用的資料太少,想要在這個問題上進一步提高精度,下一步需要使用預訓練的模型,

5.3 使用預訓練的卷積神經網路

想要將深度學習應用于小型影像資料集,一種常用且非常高效的方法是使用預訓練網路,預訓練網路是一個保存好的網路,之前已在大型資料集(通常是大規模影像分類任務) 上訓練好,如果這個原始資料集足夠大且足夠通用,那么預訓練網路學到的特征的空間層次結構可以有效地作為視覺世界的通用模型,因此這些特征可用于各種不同的計算機視覺問題,即使這些新問題涉及的類別和原始任務完全不同,

使用預訓練網路有兩種方法:特征提取和微調模型,

5.3.1 特征提取

特征提取是使用之前網路學到的表示來從新樣本中提取出有趣的特征,然后將這些特征輸入一個新的分類器,從頭開始訓練,

用于影像分類的卷積神經網路包含兩部分:首先是一系列池化層和卷積層,最后是一個密集連接分類器,第一部分叫作模型的卷積基,對于卷積神經網路而言,特征提取就是取出之前訓練好的網路的卷積基,在上面運行新資料,然后在輸出上面訓練的一個新的分類器,

卷積基學到的表示可能更加通用,因此更適合重復使用,卷積神經網路的特征圖表示通用概念在影像中是否存在,無論面對什么樣的計算機視覺問題,這種特征圖都可能很有用,但是,分類器學到的表示必然是針對于模型訓練的類別,其中僅包含某個類別出現在整張影像中的概率資訊,此外,密集連接層的表示不再包含物體在輸入影像中的位置資訊,密集連接層舍棄了空間的概念,而物體位置資訊仍然由卷積特征圖所描述,如果物體位置對于問題很重要,那么密集連接層的特征在很大程度上是無用的,

某個卷積層提取的表示的通用性(以及可復用性)取決于該層在模型中的深度,模型中更靠近底部的層提取的是區域的、高度通用的特征圖(比如視覺邊緣、顏色和紋理),而更靠近頂部的層提取的是更加抽象的概念(比如“貓耳朵”或“狗眼睛”) ,因此,如果你的新資料集與原始模型訓練的資料集有很大差異,那么最好只使用模型的前幾層來做特征提取,而不是使用整個卷積基,

將VGG16卷積基實體化:

from keras.applications.vgg16 import VGG16

conv_base = VGG16(weights='imagenet',
                  include_top=False,
                  input_shape=(150, 150, 3))
print(conv_base.summary())
Model: "vgg16"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_1 (InputLayer)        [(None, 150, 150, 3)]     0         
                                                                 
 block1_conv1 (Conv2D)       (None, 150, 150, 64)      1792      
                                                                 
 block1_conv2 (Conv2D)       (None, 150, 150, 64)      36928     
                                                                 
 block1_pool (MaxPooling2D)  (None, 75, 75, 64)        0         
                                                                 
 block2_conv1 (Conv2D)       (None, 75, 75, 128)       73856     
                                                                 
 block2_conv2 (Conv2D)       (None, 75, 75, 128)       147584    
                                                                 
 block2_pool (MaxPooling2D)  (None, 37, 37, 128)       0         
                                                                 
 block3_conv1 (Conv2D)       (None, 37, 37, 256)       295168    
                                                                 
 block3_conv2 (Conv2D)       (None, 37, 37, 256)       590080    
                                                                 
 block3_conv3 (Conv2D)       (None, 37, 37, 256)       590080    
                                                                 
 block3_pool (MaxPooling2D)  (None, 18, 18, 256)       0         
                                                                 
 block4_conv1 (Conv2D)       (None, 18, 18, 512)       1180160   
                                                                 
 block4_conv2 (Conv2D)       (None, 18, 18, 512)       2359808   
                                                                 
 block4_conv3 (Conv2D)       (None, 18, 18, 512)       2359808   
                                                                 
 block4_pool (MaxPooling2D)  (None, 9, 9, 512)         0         
                                                                 
 block5_conv1 (Conv2D)       (None, 9, 9, 512)         2359808   
                                                                 
 block5_conv2 (Conv2D)       (None, 9, 9, 512)         2359808   
                                                                 
 block5_conv3 (Conv2D)       (None, 9, 9, 512)         2359808   
                                                                 
 block5_pool (MaxPooling2D)  (None, 4, 4, 512)         0         
                                                                 
=================================================================
Total params: 14,714,688
Trainable params: 14,714,688
Non-trainable params: 0
_________________________________________________________________
None

最后的特征圖形狀為(4, 4, 512),我們將在這個特征上添加一個密集連接分類器,下一步有兩種方法可供選擇:

(1)在你的資料集上運行卷積基,將輸出保存成硬碟中的Numpy陣列,然后用這個資料作為輸入,輸入到獨立的密集連接分類其中,

(2)在頂部添加Dense層來擴展已有模型,并在輸入資料上端到端地運行整個模型,

1.不使用資料增強的快速特征提取

# 使用預訓練的卷積基提取特征
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
from keras.applications.vgg16 import VGG16

# 將VGG16卷積實體化
conv_base = VGG16(weights='imagenet',
                  include_top=False,
                  input_shape=(150, 150, 3))

base_dir = r'E:\train'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')

datagen = ImageDataGenerator(rescale=1./255)
batch_size = 20


def extract_features(directory, sample_count):
    features = np.zeros(shape=(sample_count, 4, 4, 512))
    labels = np.zeros(shape=sample_count)
    generator = datagen.flow_from_diretory(
        directory,
        target_size=(150, 150),
        batch_size=batch_size,
        class_mode='binary'
    )
    i = 0
    for inputs_batch, labels_batch in generator:
        features_batch = conv_base.predict(inputs_batch)
        features[i * batch_size : (i + 1) * batch_size] = features_batch
        labels[i * batch_size : (i + 1) * batch_size] = labels_batch
        i += 1
        if i * batch_size >= sample_count:
            break
        if i * batch_size >= sample_count:
            break
    return features, labels


train_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)

# 將提取的特征形狀展平為(samples, 8192)
train_features = np.reshape(train_features, (2000, 4 * 4 * 512))
validation_features = np.reshape(validation_features, (1000, 4 * 4 * 512))
test_features = np.reshape(test_features, (1000, 4 * 4 * 512))

定義并訓練密集連接分類器:

from keras import models
from keras import layers
from tensorflow import optimizers

model = models.Sequential()
model.add(layers.Dense(256, activation='relu', input_dim=4 * 4 * 512))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
              loss='binary_crossentropy',
              metircs=['acc'])

history = model.fit(train_features, train_labels,
                    epochs=30,
                    batch_size=20,
                    validation_data=(validation_features, validation_labels))

繪制結果:

我們的驗證精度達到了約90%,比從頭開始訓練的小型模型效果要好得多,從圖中也可以看出,雖然dropout比率相當大,但模型幾乎從一開始就過擬合,這是因為本方法沒有使用資料增強,而資料增強對防止小型影像資料集的過擬合非常重要,

2.使用資料增強的特征提取

擴展conv_base模型,然后在輸入資料上端到端地運行模型,模型的行為和層類似,所以你可以向Sequential模型中添加一個模型(比如conv_base),就像添加一個層一樣,

在卷積基上添加一個密集連接分類器:

from keras import models
from keras import layers
from keras.applications.vgg16 import VGG16

conv_base = VGG16(weights='imagenet',
                  include_top=False,
                  input_shape=(150, 150, 3))

model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

print(model.summary())

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 vgg16 (Functional)          (None, 4, 4, 512)         14714688  
                                                                 
 flatten (Flatten)           (None, 8192)              0         
                                                                 
 dense (Dense)               (None, 256)               2097408   
                                                                 
 dense_1 (Dense)             (None, 1)                 257       
                                                                 
=================================================================
Total params: 16,812,353
Trainable params: 16,812,353
Non-trainable params: 0
_________________________________________________________________
None

在編譯和訓練模型之前,一定要“凍結”卷積基,凍結一個或多個層是指在訓練程序中保持其權重不變,如果不這么做,那么卷積基之前學到的表示將會在訓練程序中被修改,因為其上添加的Dense層是隨機初始化的,所以非常大的權重更新將會在網路中傳播,對之前學到的表示造成很大破壞,

print('This is the number of trainable weights before freezing the conv base:', len(model.trainable_weights))
conv_base.trainable = False
print('This is the number of trainable weights after freezing the conv base:', len(model.trainable_weights))
This is the number of trainable weights before freezing the conv base: 30
This is the number of trainable weights after freezing the conv base: 4

利用凍結的卷積基端到端地訓練模型:

train_generator = train_datagen.flow_from_directory(
    train_dir,
    target_size=(150, 150),
    batch_size=20,
    class_mode='binary'
)

validation_generator = test_datagen.flow_from_directory(
    validation_dir,
    target_size=(150, 150),
    batch_size=20,
    class_mode='binary'
)

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=2e-5),
              metrics=['acc'])

history = model.fit_generator(
    train_generator,
    steps_per_epoch=100,
    epochs=30,
    validation_data=validation_generator,
    validation_steps=50
)

5.3.2 微調模型

另一種廣泛使用的模型復用方法是模型微調,與特征提取互為補充,對于用于特征提取的凍結的模型基,微調是指將其頂部的幾層“解凍”,并將這解凍的幾層和新增加的部分聯合訓練,之所以叫作微調,是因為它只是略微調整了所復用模型中更加抽象的表示,以便讓這些表示與手頭的問題更加相關,

微調網路的步驟如下:

(1)在已經訓練好的基網路上添加自定義網路;

(2)凍結基網路;

(3)訓練所添加的部分;

(4)解凍基網路的一些層;

(5)聯合訓練解凍的這些層和添加的部分,

卷積基中更靠底部的層編碼的是更加通用的可復用特征,而更靠頂部的層編碼的是更專業化的特征,微調這些更專業化的特征更加有用,因為它們需要在你的新問題上改變用途,微調更靠底部的層,得到的回報會更少,

訓練的引數越多,過擬合的風險越大,卷積基有1500萬個引數,所以在你的小型資料集上訓練這么多引數是有風險的,

凍結直到某一層的所有層:

# 凍結直到某一層的所有層
conv_base.trainable = True

set_trainable = False
for layer in conv_base.layers:
    if layer.name == 'block5_conv1':
        set_trainable = True
    if set_trainable:
        layer.trainable = True
    else:
        layer.trainable = False

# 微調模型
model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-5),
              metrics=['acc'])
history = model.fit_generator(
    train_generator,
    steps_per_epoch=100,
    epochs=100,
    validation_data=validation_generator,
    validation_steps=50
)

使曲線變得平滑:

# 使曲線變得平滑
def smooth_curve(points, factor=0.8):
    smoothed_points = []
    for point in points:
        if smoothed_points:
            previous = smoothed_points[-1]
            smoothed_points.append(previous * factor + point * (1 - factor))
        else:
            smoothed_points.append(point)
    return smoothed_points

test_generator = test_datagen.flow_from_directory(
    test_dir, 
    target_size=(150, 150),
    batch_size=20,
    class_mode='binary'
)

test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print('test acc:', test_acc)

5.3.3 小結

1.卷積神經網路是用于計算機視覺任務的最佳機器學習模型,即使在非常小的資料集上也可以從頭開始訓練一個卷積神經網路,而且得到的結果還不錯,

2.在小型資料集上的主要問題是過擬合,在處理影像資料時,資料增強是一種降低過擬合的強大方法;

3.利用特征提取,可以很容易將現有的卷積神經網路復用于新的資料集,對于小型影像資料集,這是一種很有價值的方法;

4.作為特征提取的補充,你還可以使用微調,將現有模型之前學到的一些資料表示應用于新問題,這種方法可以進一步提高模型性能,

5.4 卷積神經網路的可視化

人們常說,深度學習模型是“黑盒”, 即模型學到的表示很難用人類可以理解的方式來提取和呈現,雖然對于某些型別的深度學習模型來說,這種說法部分正確,但對卷積神經網路來說絕不是這樣,卷積神經網路學到的表示非常適合可視化,很大程度上是因為它們是視覺概念的表示,

三種最容易理解也最有用的對這些表示進行可視化和解釋的方法:

1.可視化卷積神經網路的中間輸出(中間激活);

2.可視化卷積神經網路的過濾器;

3.可視化影像中類激活的熱力圖,

5.4.1 可視化中間激活

可視化中間激活,是指對于給定輸入,展示網路中各個卷積層和池化層輸出的特征圖(層的輸出通常被稱為該層的激活,即激活函式的輸出),這讓我們可以看到輸入如何被分解為網路學到的不同過濾器,

(這一章的代碼實作遇到了一些問題,暫且跳過這一章,先進入下一章的學習)

有木有大佬能夠幫忙解答一下如下問題:

使用如下代碼匯入optimizers

from keras import optimizers

運行后顯示:

AttributeError: module 'keras.optimizers' has no attribute 'RMSprop'

將匯入方式改成如下:

from tensorflow import optimizers

則又顯示:

AttributeError: module 'tensorflow.compat.v2.__internal__.distribute' has no attribute 'strategy_supports_no_merge_call'

請問如何解決?

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/430263.html

標籤:AI

上一篇:狀態機游戲AI設計

下一篇:TensorFlow2 入門指南 | 18 keras.callbacks 回呼使用方法

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more