Udacity P7 經(jīng)歷筆記

  • 獲得訓(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ò)程

  1. 使用遷移學(xué)習(xí)直接訓(xùn)練模型

首先使用了VGG19的預(yù)訓(xùn)練模型。

從keras上直接采用,預(yù)訓(xùn)練多次得分在2.3上下。觀察當(dāng)時(shí)的訓(xùn)練過(guò)程,
image.png

loss函數(shù)~2.0,而準(zhǔn)確率還可以接近1。一定是哪里出了問(wèn)題。

在第一時(shí)間想到的是模型上的問(wèn)題(其實(shí)不是)。

  1. 使用遷移學(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ò)誤。

  1. 從頭搭建網(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)練

  1. 從頭搭建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

  1. 采用遷移學(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

  1. 準(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

  2. 使用模型融合
    使用整合訓(xùn)練,普通堆疊。堆疊方法由上一篇翻譯文得出:kaggle 模型融合指導(dǎo)

  • 預(yù)測(cè)測(cè)試集,制作提交文件

預(yù)測(cè)的時(shí)候的幾個(gè)細(xì)節(jié):

  1. 仍然是內(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è)。
  2. 由于測(cè)試集沒(méi)有子目錄,所以不能使用第一步的方法取得文件路徑。這里使用的命令是:for maindir, subdir, file_name in os.walk('test'): 必須要取得文件名的原因是在提交文件CSV里面index為img名稱(chēng)。
  3. 在準(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)注的地方
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容