數據科學競賽-人臉表情識別

人臉表情識別

簡介

這是科賽網曾今的一次計算機視覺類的分類賽,屬於一個視覺基礎任務。關於人臉表情識別,Kaggle等平臺也舉辦過相關的比賽,難度並不算大,但是對技巧的要求比較高。本文詳述該比賽的主要步驟,並構建多個模型對比效果。

數據探索

這部分主要是對數據集的簡單探索,由於計算機視覺類比賽數據多爲圖片或者視頻格式,無需像挖掘賽那樣進行比較複雜的EDA。這部分的代碼可以在文末給出的Github倉庫的EDA.ipynb文件中找到。

數據集的目錄形式如下(數據集分享於百度雲,提取碼爲2fb9),由於本比賽只需要將五種區分度比較明顯的表情識別出來,所以共五個文件夾,每個文件夾內的圖片爲一類。

在這裏插入圖片描述

首先,生成了如下的csv格式的數據集說明文件如下,包含兩列,分別爲文件的相對路徑和標籤。對該說明文件進行分析更加的方便,因爲這個文件包含大多數數據集的信息,且Pandas提供了很多分析的API,很容易對錶格數據處理。

在這裏插入圖片描述

首先觀察類別的分佈,很遺憾,數據的類別分佈如下。這是因爲有的表情數據確實很少,儘管我們確實希望數據的分佈是均衡的,但是這裏的影響也不是很大,後面通過數據增強,會對這個情況有所改善。

在這裏插入圖片描述

隨後,隨機採樣10張圖片展示並顯示標籤,結果如下。通過這一步的觀察,不難發現,其實圖片中不僅僅是目標-人臉表情,有很多背景干擾因素,這就要求需要進行人臉檢測及圖片裁減了,也就是輸入深度模型中的不應該是原圖而應該是裁減後的人臉圖片。

在這裏插入圖片描述

數據預處理

這部分主要對圖片進行人臉截取,實現的原理是基於目標檢測的思路,分爲傳統方法和深度方法,爲了更快得到結果,這裏使用軟件包face_recognition進行人臉檢測(注意處理單圖片多人臉情況),這是一個非常簡單的封裝好的人臉識別庫,可以通過pip安裝(若安裝出錯一般是dlib問題,安裝dlib的我回來文件即可)。

其人臉檢測的結果保存到本地後結果如下。注意,這個數據生成的過程時間較長。

在這裏插入圖片描述

核心代碼如下,具體代碼見文末Github。

def preprocess():
    categories = os.listdir(data_folder)
    for category in categories:
        in_path = os.path.join(data_folder, category)
        out_path = os.path.join(generate_folder, category + '_face')
        if not os.path.exists(out_path):
            os.mkdir(out_path)
        for file in glob(in_path + '/*.jpg'):
            file_name = file.split('\\')[-1]
            print(file_name)
            img = face_recognition.load_image_file(file)
            if max(img.shape) > 2000:
                if img.shape[0] > img.shape[1]:
                    img = cv2.resize(img, (2000, int(2000 * img.shape[1] / img.shape[0])))
                else:
                    img = cv2.resize(img, (int(2000 * img.shape[0] / img.shape[1]), 2000))
            locations = face_recognition.face_locations(img)  # 人臉檢測,大部分爲單個,但也有多個檢測結果
            if len(locations) <= 0:
                print("no face")
            else:
                for i, (a, b, c, d) in enumerate(locations):
                    image_split = img[a:c, d:b, :]
                    image_split = scale_img(image_split)
                    Image.fromarray(image_split).save(os.path.join(out_path, file_name + '_{}.png'.format(i)))

數據加載

由於數據集已經被處理爲很規範的數據集格式,這裏直接調用TF2中Keras接口解析數據集(自己寫迭代器也是合適的,這裏爲了開發的速度採用封裝好的API),返回一個迭代器,同時在該接口中設置一些數據增強手段,這裏主要使用水平翻轉。
具體代碼如下,關於如何使用Keras的數據加載接口可以見我之前的博客

代碼如下,具體整個項目的代碼見文末Github。

class DataSet(object):

    def __init__(self, root_folder):
        self.folder = root_folder
        self.df_desc = pd.read_csv(self.folder + 'description.csv', encoding="utf8")

    def get_generator(self, batch_size=32, da=True):
        if da:
            # 數據增強
            train_gen = ImageDataGenerator(rescale=1 / 255., validation_split=0.2, horizontal_flip=True, shear_range=0.2,
                                           width_shift_range=0.1)
        else:
            train_gen = ImageDataGenerator(rescale=1 / 255., validation_split=0.2, horizontal_flip=False)
        img_size = (64, 64)
        train_generator = train_gen.flow_from_dataframe(dataframe=self.df_desc,
                                                        directory='.',
                                                        x_col='file_id',
                                                        y_col='label',
                                                        batch_size=batch_size,
                                                        class_mode='categorical',
                                                        target_size=img_size,
                                                        subset='training')
        valid_generator = train_gen.flow_from_dataframe(dataframe=self.df_desc,
                                                        directory=".",
                                                        x_col="file_id",
                                                        y_col="label",
                                                        batch_size=batch_size,
                                                        class_mode="categorical",
                                                        target_size=img_size,
                                                        subset='validation')
        return train_generator, valid_generator

模型構建

主要嘗試了構建一個簡單的CNN模型進行訓練和預測,這是模型較淺,這是考慮到可能的部署後的速度而採取的設計方法;此外,使用ResNet50模型結構嘗試訓練和預測。本文采用TensorFlow2.0構建模型進行訓練,這是TF2是一個很不錯的深度學習框架,相比於TF1很容易學習和使用,具體的可以查看我TensorFlow2系列的教程博客

def CNN(input_shape=(224, 224, 3), n_classes=5):
    # input
    input_layer = Input(shape=input_shape)
    x = Conv2D(32, (1, 1), strides=1, padding='same', activation='relu')(input_layer)
    # block1
    x = Conv2D(64, (3, 3), strides=1, padding='same')(x)
    x = PReLU()(x)
    x = Conv2D(64, (5, 5), strides=1, padding='same')(x)
    x = PReLU()(x)
    x = MaxPooling2D(pool_size=(2, 2), strides=2)(x)
    # fc
    x = Flatten()(x)
    x = Dense(2048, activation='relu')(x)
    x = Dropout(0.5)(x)
    x = Dense(1024, activation='relu')(x)
    x = Dropout(0.5)(x)
    x = Dense(n_classes, activation='softmax')(x)

    model = Model(inputs=input_layer, outputs=x)
    return model


def ResNet_pretrained(input_shape=(224, 224, 3), n_classes=5):
    input_layer = Input(shape=input_shape)
    densenet121 = ResNet50(include_top=False, weights=None, input_tensor=input_layer)
    x = GlobalAveragePooling2D()(densenet121.output)
    x = Dropout(0.5)(x)
    x = Dense(n_classes, activation='softmax')(x)

    model = Model(input_layer, x)
    return model

對比兩個模型的效果如下圖,訓練集很快達到接近100%的準確率,驗證集卻幾乎不怎麼變化,說明模型的效果還是比較一般的,這主要是因爲不同類的數據量差距太大(有的類別幾百個樣本有的類別幾萬個樣本),模型很難訓練,或者說很快達到極限。

在這裏插入圖片描述

這裏注意,表情識別比賽只是一個簡單的分類賽,採用合適的深度特徵提取網絡即可取得不錯的效果,對於這種分類問題一般採用端到端的模型設計即可,即使用分類損失交叉熵進行預測結果衡量,優化器選擇的是Adam,當然,爲了更加適應任務可以自己設計合適的損失函數如focal損失,當然,這也只是錦上添花而已,真正需要優化的還是模型的結構。此外,也可以考慮RGB轉爲Gray圖從而減少冗餘信息等手段,或者引入更多的數據集進行訓練。

補充說明

真正構建實際系統是可以在模型應用的預測端進行一個增廣預測,將得到的結果進行加權從而得到真正的預測結果,具體可以參考我之前的項目。所有代碼開源於我的Github,歡迎Star或者Fork。

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