MSCNN
基於MSCNN的人羣密度估計:
網絡結構
網上已經有很多介紹mscnn網絡結構的文章了,我就不再贅述。只說一下網絡中Multi-Scale Blob結構的實現,在keras中實現很簡單,看看源碼就很容易理解了。論文中分了兩種msb結構,一種是正常的,一個塊中包含4個卷積層,卷積核大小分別是(9,7,5,3);還有一個稱爲mini msb塊,包含3個卷積層,卷積核的大小爲(7,5,3)。MSCNN的網絡模型的代碼主要參考了https://www.jianshu.com/p/29a213f22b40,特此感謝!在輸出層千萬不能使用relu,否則會導致預測的輸出全部爲0,而且損失值還在一直下降!在最後的輸出層我試過relu(sigmoid(x))、relu(tanth(x))、sigmoid(x)、tanth(x),最終還是tanth(x)靠譜!
def MSB(filter_num):
def f(x):
params = {
'strides': 1,
'activation': 'relu',
'padding': 'same',
'kernel_regularizer': l2(5e-4)
}
x1 = Conv2D(filters=filter_num, kernel_size=(9, 9), **params)(x)
x2 = Conv2D(filters=filter_num, kernel_size=(7, 7), **params)(x)
x3 = Conv2D(filters=filter_num, kernel_size=(5, 5), **params)(x)
x4 = Conv2D(filters=filter_num, kernel_size=(3, 3), **params)(x)
x = concatenate([x1, x2, x3, x4])
x = BatchNormalization()(x)
return x
return f
def MSB_mini(filter_num):
def f(x):
params = {
'strides': 1,
'activation': 'relu',
'padding': 'same',
'kernel_regularizer': l2(5e-4)
}
x2 = Conv2D(filters=filter_num, kernel_size=(7, 7), **params)(x)
x3 = Conv2D(filters=filter_num, kernel_size=(5, 5), **params)(x)
x4 = Conv2D(filters=filter_num, kernel_size=(3, 3), **params)(x)
x = concatenate([x2, x3, x4])
x = BatchNormalization()(x)
x = Activation('relu')(x)
return x
return f
然後在主幹網將這兩種塊有機的結合起來
def MSCNN(input_shape):
input_tensor = Input(shape=input_shape)
# block1
x = Conv2D(filters=64, kernel_size=(9, 9), strides=1, padding='same', activation='relu')(input_tensor)
# block2
x = MSB(4*16)(x)
x = MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(x)
# block3
x = MSB(4*32)(x)
x = MSB(4*32)(x)
x = MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(x)
x = MSB_mini(3*64)(x)
x = MSB_mini(3*64)(x)
# x = MSB(4*64)(x)
x = Conv2D(1000, (1, 1), activation='relu', kernel_regularizer=l2(5e-4))(x)
x = Conv2D(1, (1, 1), activation='tanh')(x)
model = Model(inputs=input_tensor, outputs=x)
return model
關於損失函數
- 論文中直接使用了mse作爲損失函數,我就不寫公式了,體現在代碼中就一行。keras支持多種損失函數,具體請參加官方文檔。這裏想多囉嗦一句,keras對用戶很友好,封裝了很多API,極大的減少了代碼量而且結構清晰,但是這樣對於初學者來說,在享受便捷的同時卻缺少了對細節的理解,不利於成長!如果想對深度學習有更深入的理解,而不是僅成爲一個名副其實的“調包俠”,還是乖乖的先使用tf的原生API來寫比較好,對於理解模型和公式都有好處!(在實際中的一點感悟,還請莫笑)
model.compile(optimizer=Adam(lr=3e-4), loss='mse')
- 另一種損失函數
在使用MSE作爲損失函數的時候,只考慮了像素誤差,而忽略了估計密度圖和真實密度圖之間的全局和局部的相關性。中科院提出的DS Net中使用了多尺度密度水平一致性損失。給出的公式如下:
其中 s 是用於一致性檢查的尺度級別數,P 是平均池化操作,爲平均池化的指定輸出大小。在論文中作者採用三個尺度級別,每個輸出尺寸分別爲 1×1、2×2 和 4×4。輸出大小爲 1×1 的第一個尺度級別捕獲密度水平的全局特徵,而其他兩個尺度級別表示圖像塊的局部密度水平。MSE的損失函數如下:
則總的損失函數爲上式兩個損失的加權求和
其中取100或1000
這篇論文沒有給出源碼,我自己寫了實現,不過我的最終效果同直接使用mse區別不大,也可能是因爲我沒有使用論文中的DS Net的原因。
def get_avgpoolLoss(y_true, y_pred, k):
loss = KTF.mean((abs(AveragePooling2D(pool_size=(k, k), strides=(1, 1))(y_true) -
AveragePooling2D(pool_size=(k, k), strides=(1, 1))(y_pred)))) / k
return loss
def denseloss(y_true, y_pred, e=1000):
Le = KTF.mean(KTF.square(y_pred-y_true), axis=-1)
Lc = get_avgpoolLoss(y_true, y_pred, 1)
Lc += get_avgpoolLoss(y_true, y_pred, 2)
Lc += get_avgpoolLoss(y_true, y_pred, 4)
shp = KTF.get_variable_shape(y_pred)
Lc = Lc / (shp[1] * shp[2])
return Le + e * Lc
使用的時候,直接將損失函數的名稱改爲denseloss即可
model.compile(optimizer=Adam(lr=3e-4), loss=denseloss)
網絡訓練
將數據生成器寫好後,直接訓練即可。數據的生成請參看 數據集製作和數據生成器
def get_callbacks():
early_stopping = EarlyStopping(monitor='val_loss', patience=20)
reduce_lr = ReduceLROnPlateau(monitor='loss', factor=0.1, patience=5, min_lr=1e-7, verbose=True)
models_path = os.path.join(ROOT_DIR, 'models')
if not os.path.exists(models_path):
os.mkdir(models_path)
model_checkpoint = ModelCheckpoint(os.path.join(models_path, 'mscnn_model_weights.h5'), monitor='val_loss',
verbose=True, save_best_only=True, save_weights_only=True)
callbacks = [early_stopping, reduce_lr, model_checkpoint, TensorBoard(log_dir='../tensorlog')]
return callbacks
model.fit_generator(CrowDataset().gen_train(batch_size, 224),
steps_per_epoch=CrowDataset().get_train_num() // batch_size,
validation_data=CrowDataset().gen_valid(batch_size, 224),
validation_steps=CrowDataset().get_valid_num() // batch_size,
epochs=int(args_['epochs']),
callbacks=callbacks)
預測與輸出
由於本項目將所有大於100人的圖片都輸出100人,所以在輸出的時候 第一步使用密度等級網絡將圖片分成3類,然後僅僅將輸出的標籤爲1的圖片送入mscnn再進行人數預測,最終給出所有圖片中包含的人數。在我1080ti的顯卡下,每秒輸出20到30張。
# 密度等級分類模型
dense_net = DenseLevelNet(VGG_Model, Dense_Model)
dense_model = dense_net.model()
dense_model.load_weights(Dense_Model, by_name=True)
# 具體人數分類模型
crow_model = MSCNN((224, 224, 3))
crow_model.load_weights(Mscnn_Model)
for img_name in tqdm(images):
try:
img = imopen(img_name)
img = np.expand_dims(img, axis=0)
dense_prob = dense_model.predict(img)
dense_level = np.argmax(dense_prob, axis=1)
dense_level = dense_level[0]
if dense_level == 0:
crow_count = 0
elif dense_level == 2:
crow_count = 100
else:
dmap = crow_model.predict(img)
dmap = np.squeeze(dmap, axis=-1)
crow_count = int(np.sum(dmap))
res.append([os.path.split(img_name)[1], crow_count])
except Exception as e:
print(img_name)
res.append([os.path.split(img_name)[1], -1])