-
獲得訓(xùn)練圖片的路徑和標(biāo)簽
load_files()
函數(shù)讀取之后輸出的是文本文檔
filenames 的輸出是文件的路徑
target 的輸出是子文件夾的名字。在這里子文件夾名字是 c0,c1,c2,c3...c9。但是讀取出來(lái)的是0,1,2,3,....9。不過(guò)不會(huì)影響使用,后面要轉(zhuǎn)化成為獨(dú)熱編碼。
*特別注意:如果讀取的文件夾下面沒(méi)有子文件夾,命令用不了,也就是沒(méi)有'target'的值。
def img_load(path):
data = load_files(path)
files = data['filenames']
#targets = np_utils.to_categorical(np.array(data['target']), 10)
targets = data['target']
return files, targets
-
獲得文件名&路徑
for maindir, subdir, file_name_list in os.walk(dirname):
命令讀取文件目錄,文件上一級(jí)目錄,文件名。os.walk()輸出的是三個(gè)值(列表)。
如果要組成完整的文件目錄,需要再加工一步:
apath = os.path.join(maindir, filename)
def path_and_name(dirname):
result = []
for maindir, subdir, file_name_list in os.walk(dirname):
for filename in file_name_list:
apath = os.path.join(maindir, filename)
result.append(apath)
name = file_name_list
return result,name
-
讀取圖片為array
cv2.imread().astpye(np.uint8)
讀取圖片的時(shí)候,默認(rèn)格式是np.float64,在這里為了省內(nèi)存,用np.uint8
矩陣2個(gè)常用的命令
np.expand_dims(x,axis=0)
此為增加維度命令。axis值代表的是在什么地方增加維度。0代表在最前面增加
x=x.transpose((2,0,1))
此為轉(zhuǎn)換維度順序。原本是(0,1,2)的3維矩陣,轉(zhuǎn)換成了(2,0,1)。最常見(jiàn)的情況就是將顏色通道放在最前面/最后面
要查看某個(gè)矩形的形狀 x.shape
def img_read(path,img_cols=299, img_rows=299):
img = cv2.imread(path).astype(np.uint8)
img_newsize = cv2.resize(img,(img_cols,img_rows))
#img_newsize.transpose((2,0,1))
#np.expand_dims(img_newsize, axis=2)
return img_newsize
-
按司機(jī)ID來(lái)劃分訓(xùn)練集
由于一個(gè)司機(jī)ID的圖像是從視頻里面截取出來(lái)的,所以如果訓(xùn)練的時(shí)候,訓(xùn)練集合驗(yàn)證集都有同一個(gè)ID的圖像。基本可以說(shuō)是判斷準(zhǔn)確率100%了。這樣訓(xùn)練的分?jǐn)?shù)高,實(shí)際在其他地方很低。
劃分ID是讀取了CSV的文件。使用的命令包括
pd.read_csv()
讀取CSV文件之后,list.index代表了第一列
隨后使用set()簡(jiǎn)單粗暴的去除重復(fù)。
list.classname 可以獲得分類(lèi)的名字,其中'classname'是表格中的表頭內(nèi)容
list.img可以獲得圖片的名字
- 用遷移學(xué)習(xí)提取特征
這是一次失敗的嘗試。最開(kāi)始使用可VGG19來(lái)提取特征,然后分類(lèi),效果很不理想。推斷是VGG19預(yù)訓(xùn)練的模型(在ImageNet預(yù)訓(xùn)練)和司機(jī)的圖片契合度太差了。
def found_features(path):
base_model = VGG19(weights = 'imagenet')
x = img_read(path)
x = np.expand_dims(x, axis=0)
base_model = bese_model
model = Model(inputs=base_model.input,
outputs=base_model.get_layer('block4_pool').output)
return model.predict(x)
-
保存model文件
先創(chuàng)建一個(gè)文件夾來(lái)保存h5文件,判斷如果文件夾不存在,則創(chuàng)立新的文件夾'cache'。使用命令:
if not os.path.isdir('cache'): os.mkdir('cache')
保存文件,在這里使用overwrite=True
def save_model(model, index, cross=''):
json_string = model.to_json()
if not os.path.isdir('cache'):
os.mkdir('cache')
json_name = 'architecture' + str(index) + cross + '.json'
weight_name = 'model_weights' + str(index) + cross + '.h5'
#open(os.path.join('cache', json_name), 'w').write(json_string)
model.save_weights(os.path.join('cache', weight_name), overwrite=True)
-按司機(jī)ID來(lái)創(chuàng)立列表
一級(jí)列表有26個(gè)子列表(司機(jī)ID26個(gè)),每個(gè)二級(jí)列表里包含的是一個(gè)司機(jī)ID對(duì)應(yīng)的圖片路徑。
同樣的標(biāo)簽也按同樣的方法來(lái)創(chuàng)立成兩級(jí)列表,與圖片路徑一一對(duì)應(yīng)。
來(lái)獲取一個(gè)路徑末尾文件名的方法如下:
os.path.basename(X_path[ii])
獲得pandas.DataFrame 文件某個(gè)index下的值的方法:
img_list.loc['c1'].values
為了方便查看進(jìn)度,只打印一排內(nèi)容,在后面加上 , 表示不換行。
def X_and_y(X_path,y_,img_list,ID):
print('按司機(jī)ID分類(lèi)數(shù)據(jù)')
X = []
y = []
nu = 0
for i in ID:
X_array = []
y_array = []
for ii in range(len(X_path)):
if os.path.basename(X_path[ii]) in img_list.loc[i].values:
#x_ar = found_features(X_path[ii]) #將在ID下圖片的提取特征
x_ar = X_path[ii]
X_array.append(x_ar) #形式為 [[特征 ],[特征 ] ...]
y_array.append(y_[ii])
print("{}/{} Data named {}.........{}files\r".
format(nu+1,len(ID),i,len(img_list.loc[i]))),
X.append(X_array) #形式為 [ [[ID1_特征 ],[ID1_特征 ] ...] ,[[ID2_特征 ] [ID2_特征 ] ...] ,...]
y.append(y_array)
nu += 1
return X,y
-
執(zhí)行函數(shù),分別獲得訓(xùn)練圖片路徑以及標(biāo)簽的雙層列表
ID,classname,list_img_name,img_list = driver_id('driver_imgs_list.csv',index_col = 'subject')
X_path,y_ = img_load('train')
X_data,y_data = X_and_y(X_path,y_,img_list,ID)
-
自己先做一個(gè)測(cè)試集出來(lái)檢查算法
從訓(xùn)練集中拿出兩個(gè)ID下的子列表出來(lái),做成自己的訓(xùn)練集。這個(gè)時(shí)候就要把原來(lái)的訓(xùn)練集和測(cè)試集##減去##兩個(gè)ID,使用的是:del X_data[:2]。 這個(gè)命令只能執(zhí)行一次,執(zhí)行1次就會(huì)扣除兩個(gè)子列表。
既然是做測(cè)試集,這里就不需要再按ID分類(lèi)了,在這一步就把兩層列表變?yōu)橐粚樱?/p>
test_fe,test_la = X_data[:2],y_data[:2]
del X_data[:2]
del y_data[:2]
#制作自己的test先自己測(cè)試一次
test_feature = []
test_label = []
for a in test_fe:
for i in a:
test_feature.append(i)
for b in test_la:
for ii in b:
test_label.append(ii)
print(type(test_feature),len(test_label))
print(len(X_data))
print(len(y_data))
-
模型搭建的種種過(guò)程
-
使用遷移學(xué)習(xí)直接訓(xùn)練模型
首先使用了VGG19的預(yù)訓(xùn)練模型。

loss函數(shù)~2.0,而準(zhǔn)確率還可以接近1。一定是哪里出了問(wèn)題。
在第一時(shí)間想到的是模型上的問(wèn)題(其實(shí)不是)。
-
使用遷移學(xué)習(xí)提取特征
采用了VGG19/Xception 來(lái)提取特征。在這里遇到的麻煩是:
(1) VGG輸入圖片格式為(244,244,3),而Xception輸入圖片格式為(299,299,3)。因此在前面定義的def img_read()函數(shù)中,需要更改一下圖片的reshape。
(2)提取出來(lái)的特征展平做全連接層的時(shí)候出現(xiàn)了內(nèi)存錯(cuò)誤。
-
從頭搭建網(wǎng)絡(luò)
從頭搭建VGG16網(wǎng)絡(luò),采用的是序貫?zāi)P?。搭建好網(wǎng)絡(luò)之后載入下載的VGG16權(quán)重。
然后使用model.layers.pop() 去除最后一個(gè)輸出層(1000個(gè)分類(lèi))繼續(xù)添加全連接層和softmax分類(lèi)層。
訓(xùn)練幾次之后發(fā)現(xiàn)效果仍然不佳。采用了如下方法:
(1)添加dropout,估計(jì)是過(guò)擬合。添加之后訓(xùn)練過(guò)程的分?jǐn)?shù)與之前無(wú)大的進(jìn)步。
(2)仔細(xì)檢查特征和標(biāo)簽。發(fā)現(xiàn)是在寫(xiě)入時(shí)有誤(下一節(jié)仔細(xì)分析)
(3)對(duì)模型使用正則。
model.add(Dense(64, input_dim=64,kernel_regularizer=regularizers.l2(0.001), activity_regularizer=regularizers.l1(0.001)))
(4)調(diào)整學(xué)習(xí)率 lr=le-1/ -2/ -3/ -4 /-5
(5)不載入權(quán)重,從頭訓(xùn)練
-
從頭搭建Xception網(wǎng)絡(luò)
Xception搭建需要使用函數(shù)式模型。函數(shù)式模型可以有多個(gè)輸入以及多個(gè)輸出。
載入Xception 沒(méi)有top的權(quán)重,然后繼續(xù)分類(lèi)。在這里我犯了一個(gè)觀念上的錯(cuò)誤,在添加后面的層的時(shí)候還使用序貫?zāi)P偷淖龇?,?bào)錯(cuò)。
為了使模型可視化,有兩中辦法:
(1)采用 model.summary() 輸出簡(jiǎn)單的模型內(nèi)容??梢钥吹絆utput Shape,Param, Connected to 這三個(gè)內(nèi)容。
(2)將模型打印成PNG圖片保存:
from keras.utils import plot_model
plot_model(model, to_file='model.png')
可以接收兩個(gè)參數(shù):
show_shapes:指定是否顯示輸出數(shù)據(jù)的形狀,默認(rèn)為False
show_layer_names:指定是否顯示層名稱(chēng),默認(rèn)為T(mén)rue
-
采用遷移學(xué)習(xí),融合多個(gè)模型
融合了Xception以及InceptionV3模型,將兩個(gè)模型的倒數(shù)第二層作為輸出。沒(méi)有在各自遷移學(xué)習(xí)模型里用池化層。
使用函數(shù)模型將兩個(gè)結(jié)合在一起,結(jié)合之前由于輸入的形狀不一致,((None,10,10,2048)(None,8,8,2048)),因此采用GAP將輸出轉(zhuǎn)為一維,然后再進(jìn)行聯(lián)合。
在這里有幾個(gè)注意的地方:
(1)在這里使用的是tensor
(2)在輸入數(shù)據(jù)前需要使用Input()函數(shù)
第一次完全在訓(xùn)練之后發(fā)現(xiàn)分?jǐn)?shù)只達(dá)到了0.4,第二次采用方法為InceptionV3不載入預(yù)訓(xùn)練權(quán)重。
兩次訓(xùn)練的分?jǐn)?shù)為0.4和0.6
-
準(zhǔn)備訓(xùn)練數(shù)據(jù)
在訓(xùn)練的時(shí)候才讀入圖片為(299,299,3)的格式。在這里有幾個(gè)注意點(diǎn):
(1)根據(jù)訓(xùn)練集的大小先創(chuàng)建好空的矩陣,再逐個(gè)寫(xiě)入。train_feature_A = np.zeros(shape=[number_of_train_A,input_l,input_w, input_c])
(2)標(biāo)簽要轉(zhuǎn)換成為獨(dú)熱編碼:np_utils.to_categorical(np.array(y_data_A[i][N]),10))
(3)在訓(xùn)練時(shí)候前幾次使用了keras自帶的數(shù)據(jù)預(yù)處理命令preprocess_input(train_feature_A)。經(jīng)過(guò)測(cè)驗(yàn),這樣的預(yù)處理出來(lái)預(yù)測(cè)值非常極端。后來(lái)直接采用的是不預(yù)處理的 np.uint8 格式數(shù)據(jù)。
(4)訓(xùn)練過(guò)程中最常常遇到的問(wèn)題是內(nèi)存不足的問(wèn)題。最終的解決辦法是每一折訓(xùn)練都保存權(quán)重文件。如果內(nèi)存不足,重新載入權(quán)重繼續(xù)訓(xùn)練。在這里的訓(xùn)練時(shí)長(zhǎng)就比較長(zhǎng)了:
image.png 使用模型融合
使用整合訓(xùn)練,普通堆疊。堆疊方法由上一篇翻譯文得出:kaggle 模型融合指導(dǎo)
-
預(yù)測(cè)測(cè)試集,制作提交文件
預(yù)測(cè)的時(shí)候的幾個(gè)細(xì)節(jié):
- 仍然是內(nèi)存問(wèn)題。由于預(yù)測(cè)文件高達(dá)有79726個(gè),在準(zhǔn)備測(cè)試集的時(shí)候。采用創(chuàng)建空矩陣再逐個(gè)寫(xiě)入的辦法,發(fā)現(xiàn)內(nèi)存又不夠了。所以每次預(yù)測(cè)都先退出清空內(nèi)存之后,只運(yùn)行載入模型和訓(xùn)練好權(quán)重再預(yù)測(cè),沒(méi)有辦法接上一歩訓(xùn)練模型后繼續(xù)預(yù)測(cè)。
- 由于測(cè)試集沒(méi)有子目錄,所以不能使用第一步的方法取得文件路徑。這里使用的命令是:
for maindir, subdir, file_name in os.walk('test'):必須要取得文件名的原因是在提交文件CSV里面index為img名稱(chēng)。 - 在準(zhǔn)備提交文件的時(shí)候,由于后面采用了兩個(gè)模型整合預(yù)測(cè),占用內(nèi)存增大,無(wú)論如何都無(wú)法創(chuàng)建測(cè)試集數(shù)目大小的空矩陣。
于是采用了逐行寫(xiě)入CSV的辦法,每一個(gè)圖片單獨(dú)預(yù)測(cè),得到10個(gè)分類(lèi)的概率之后寫(xiě)入文件:
def write_csv(path):
path = all_path(path)
header = ['img', 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7','c8', 'c9']
number_all = len(path)
with open('subm/kjkj.csv', 'wb')as f:
f_csv = csv.DictWriter(f,header)
f_csv.writeheader()
path = path
n =1
for i in path:
img_name = os.path.basename(i)
img_arr = img_read(i)
img_arr = np.expand_dims(img_arr, axis=0)
p = model.predict(img_arr)
row = [{'img':img_name,
'c0':p[0][0],
'c1':p[0][1],
'c2':p[0][2],
'c3':p[0][3],
'c4':p[0][4],
'c5':p[0][5],
'c6':p[0][6],
'c7':p[0][7],
'c8':p[0][8],
'c9':p[0][9] }]
f_csv.writerows(row)
print('pre {} complete!.........{}/{}\r'.format(n,n,number_all)),
n+=1
-
使用CMA查看模型關(guān)注的地方
