数据科学竞赛-人脸表情识别
人臉表情識(shí)別
簡(jiǎn)介
這是科賽網(wǎng)曾今的一次計(jì)算機(jī)視覺類的分類賽,屬于一個(gè)視覺基礎(chǔ)任務(wù)。關(guān)于人臉表情識(shí)別,Kaggle等平臺(tái)也舉辦過相關(guān)的比賽,難度并不算大,但是對(duì)技巧的要求比較高。本文詳述該比賽的主要步驟,并構(gòu)建多個(gè)模型對(duì)比效果。
數(shù)據(jù)探索
這部分主要是對(duì)數(shù)據(jù)集的簡(jiǎn)單探索,由于計(jì)算機(jī)視覺類比賽數(shù)據(jù)多為圖片或者視頻格式,無需像挖掘賽那樣進(jìn)行比較復(fù)雜的EDA。這部分的代碼可以在文末給出的Github倉(cāng)庫(kù)的EDA.ipynb文件中找到。
數(shù)據(jù)集的目錄形式如下(數(shù)據(jù)集分享于百度云,提取碼為zczc),由于本比賽只需要將五種區(qū)分度比較明顯的表情識(shí)別出來,所以共五個(gè)文件夾,每個(gè)文件夾內(nèi)的圖片為一類。
首先,生成了如下的csv格式的數(shù)據(jù)集說明文件如下,包含兩列,分別為文件的相對(duì)路徑和標(biāo)簽。對(duì)該說明文件進(jìn)行分析更加的方便,因?yàn)檫@個(gè)文件包含大多數(shù)數(shù)據(jù)集的信息,且Pandas提供了很多分析的API,很容易對(duì)表格數(shù)據(jù)處理。
首先觀察類別的分布,很遺憾,數(shù)據(jù)的類別分布如下。這是因?yàn)橛械谋砬閿?shù)據(jù)確實(shí)很少,盡管我們確實(shí)希望數(shù)據(jù)的分布是均衡的,但是這里的影響也不是很大,后面通過數(shù)據(jù)增強(qiáng),會(huì)對(duì)這個(gè)情況有所改善。
隨后,隨機(jī)采樣10張圖片展示并顯示標(biāo)簽,結(jié)果如下。通過這一步的觀察,不難發(fā)現(xiàn),其實(shí)圖片中不僅僅是目標(biāo)-人臉表情,有很多背景干擾因素,這就要求需要進(jìn)行人臉檢測(cè)及圖片裁減了,也就是輸入深度模型中的不應(yīng)該是原圖而應(yīng)該是裁減后的人臉圖片。
數(shù)據(jù)預(yù)處理
這部分主要對(duì)圖片進(jìn)行人臉截取,實(shí)現(xiàn)的原理是基于目標(biāo)檢測(cè)的思路,分為傳統(tǒng)方法和深度方法,為了更快得到結(jié)果,這里使用軟件包face_recognition進(jìn)行人臉檢測(cè)(注意處理單圖片多人臉情況),這是一個(gè)非常簡(jiǎn)單的封裝好的人臉識(shí)別庫(kù),可以通過pip安裝(若安裝出錯(cuò)一般是dlib問題,安裝dlib的我回來文件即可)。
其人臉檢測(cè)的結(jié)果保存到本地后結(jié)果如下。注意,這個(gè)數(shù)據(jù)生成的過程時(shí)間較長(zhǎng)。
核心代碼如下,具體代碼見文末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) # 人臉檢測(cè),大部分為單個(gè),但也有多個(gè)檢測(cè)結(jié)果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)))數(shù)據(jù)加載
由于數(shù)據(jù)集已經(jīng)被處理為很規(guī)范的數(shù)據(jù)集格式,這里直接調(diào)用TF2中Keras接口解析數(shù)據(jù)集(自己寫迭代器也是合適的,這里為了開發(fā)的速度采用封裝好的API),返回一個(gè)迭代器,同時(shí)在該接口中設(shè)置一些數(shù)據(jù)增強(qiáng)手段,這里主要使用水平翻轉(zhuǎn)。
具體代碼如下,關(guān)于如何使用Keras的數(shù)據(jù)加載接口可以見我之前的博客。
代碼如下,具體整個(gè)項(xiàng)目的代碼見文末Github。
class DataSet(object):def __init__(self, root_folder):self.folder = root_folderself.df_desc = pd.read_csv(self.folder + 'description.csv', encoding="utf8")def get_generator(self, batch_size=32, da=True):if da:# 數(shù)據(jù)增強(qiáng)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模型構(gòu)建
主要嘗試了構(gòu)建一個(gè)簡(jiǎn)單的CNN模型進(jìn)行訓(xùn)練和預(yù)測(cè),這是模型較淺,這是考慮到可能的部署后的速度而采取的設(shè)計(jì)方法;此外,使用ResNet50模型結(jié)構(gòu)嘗試訓(xùn)練和預(yù)測(cè)。本文采用TensorFlow2.0構(gòu)建模型進(jìn)行訓(xùn)練,這是TF2是一個(gè)很不錯(cuò)的深度學(xué)習(xí)框架,相比于TF1很容易學(xué)習(xí)和使用,具體的可以查看我TensorFlow2系列的教程博客。
def CNN(input_shape=(224, 224, 3), n_classes=5):# inputinput_layer = Input(shape=input_shape)x = Conv2D(32, (1, 1), strides=1, padding='same', activation='relu')(input_layer)# block1x = 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)# fcx = 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 modeldef 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對(duì)比兩個(gè)模型的效果如下圖,訓(xùn)練集很快達(dá)到接近100%的準(zhǔn)確率,驗(yàn)證集卻幾乎不怎么變化,說明模型的效果還是比較一般的,這主要是因?yàn)椴煌惖臄?shù)據(jù)量差距太大(有的類別幾百個(gè)樣本有的類別幾萬個(gè)樣本),模型很難訓(xùn)練,或者說很快達(dá)到極限。
這里注意,表情識(shí)別比賽只是一個(gè)簡(jiǎn)單的分類賽,采用合適的深度特征提取網(wǎng)絡(luò)即可取得不錯(cuò)的效果,對(duì)于這種分類問題一般采用端到端的模型設(shè)計(jì)即可,即使用分類損失交叉熵進(jìn)行預(yù)測(cè)結(jié)果衡量,優(yōu)化器選擇的是Adam,當(dāng)然,為了更加適應(yīng)任務(wù)可以自己設(shè)計(jì)合適的損失函數(shù)如focal損失,當(dāng)然,這也只是錦上添花而已,真正需要優(yōu)化的還是模型的結(jié)構(gòu)。此外,也可以考慮RGB轉(zhuǎn)為Gray圖從而減少冗余信息等手段,或者引入更多的數(shù)據(jù)集進(jìn)行訓(xùn)練。
補(bǔ)充說明
真正構(gòu)建實(shí)際系統(tǒng)是可以在模型應(yīng)用的預(yù)測(cè)端進(jìn)行一個(gè)增廣預(yù)測(cè),將得到的結(jié)果進(jìn)行加權(quán)從而得到真正的預(yù)測(cè)結(jié)果,具體可以參考我之前的項(xiàng)目。所有代碼開源于我的Github,歡迎Star或者Fork。
總結(jié)
以上是生活随笔為你收集整理的数据科学竞赛-人脸表情识别的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Carbon和Polacode教程
- 下一篇: Docker教程-深度学习环境配置