第一步保存日志文件,用重定向即可:
$TOOLS/caffe train --solver=$SOLVERFILE 2>&1 |tee out.log
第二步直接繪制:
python plot_training_log.py 2 testloss.png out.log
這個plot_training_log.py在這個目錄下caffe-fast-rcnn/tools/extra
2是選擇畫哪種類型的圖片,具體數字是代表哪個類型可以查看幫助信息看到:
0: Test accuracy vs. Iters
1: Test accuracy vs. Seconds
2: Test loss vs. Iters
3: Test loss vs. Seconds
4: Train learning rate vs. Iters
5: Train learning rate vs. Seconds
6: Train loss vs. Iters
7: Train loss vs. Seconds
testloss.png是生成圖片的名字,要求必須是png類型的文件
out.log是之前生成的日志文件
有個教程讓你先生成解析日志文件:
python parse_log.py out.log ./
注意最后一個是./,是保存的路徑,最后會生成.train和.test兩個文件。
實際上我覺得沒有必要執行這一步,直接繪制曲線就好,繪制曲線中間也會生成這兩個文件,因為plot_training_log.py本身要調用parse_log.py的shell腳本。並且生成的文件第一行是自帶'#',但是用這個解析生成的反而是不帶的。
跑項目代碼時,生成的日志文件有一點問題,一個正常的日志文件應該是這樣:
而我的日志文件是這樣;
即在Iteration前我的日志文件沒有I0619 10:29:45.757735 8944 solver.cpp:280] Solving deeplab_largeFOV 這句話,在parse_log.sh里有這樣一句:grep '] Solving ' $1 > aux3.txt,要尋找 '] Solving ',如果沒有,生成的aux3.txt就為空,
因為aux4.txt是由aux3.txt來的,這樣就無法生成aux4.txt,也就報錯說不能paste和rm aux4.txt。在extract_seconds.py中也是通過尋找sovling來確定開始時間的。如果單獨用parse_log.py生成日志文件,不會報aux4.txt的錯誤,但會報extract_seconds.py
的錯誤。所以在Iteration 0前面一行加上沒有這句話,就能解決問題。
在parse_log.sh中兩行代碼:
grep '] Solving ' $1 > aux3.txt # grep 'Testing net' $1 >> aux3.txt grep 'Train net' $1 >> aux3.txt
這兩行代碼都是搜索含有字符串的行然后寫入aux3.txt。因為我的日志中沒有] Solving,並且我的是訓練日志,也沒有Testing net,所以在沒有添加] Solving的時候去運行腳本就報了:無法paste,rm aux4.txt的錯誤。實際上是因為沒有任何東西寫進aux3.txt,
aux3.txt是空的,所以運行$DIR/extract_seconds.py aux3.txt aux4.txt就不會生成aux4.txt。當然也就沒辦法paste,rm。修改的方法可以把Testing net改成Train net,這樣可以在日志文件中找到行寫入aux3.txt。或者在日志中添加] Solving讓能有東西寫進
aux3.txt。其實兩種改法都可以,反正這個腳本之后要刪除這些臨時文件,然后再取生成.train的東西,這樣改只是為了讓程序不報錯,能正常運行。
plot_training_log.py.example里的load_data函數也需要改一下,原本的代碼是:
def load_data(data_file, field_idx0, field_idx1): data = [[], []] with open(data_file, 'r') as f: for line in f: line = line.strip() if line[0] != '#': fields = line.split() data[0].append(float(fields[field_idx0].strip())) data[1].append(float(fields[field_idx1].strip())) return data
這段代碼是從中間生成的日志文件讀取你需要項的數據,!= '#'其實就是不讀取第一行的項信息,這是中間文件:
直接用會報字符串無法轉換成float的錯誤,原代碼里對每一行split空格后生成的list,不是只含這4個數字的list,而是含有許多空格的list,所以當然float無法轉換空格字符。需要做的就是split掉所有的空格,生成一個大小為4只包含4個數字的list。
這里需要注意個問題,日志文件中兩個數字間的間隔的空格數是不一樣的,有的是4個,有的是5個,代碼需要實現無論多少個空格,都split掉。
修改代碼:
def load_data(data_file, field_idx0, field_idx1): data = [[], []] with open(data_file, 'r') as f: for line in f: line = line.strip() if line[0] != '#': line=','.join(filter(lambda x: x, line.split(' '))) print line fields = line.split(',') print fields data[0].append(float(fields[field_idx0].strip())) data[1].append(float(fields[field_idx1].strip())) return data
這個的filter實現了功能。filter先split生成了一個每個空格占一個位置的list。
以下這張圖大概模擬了一下過程:
可以看到split之后一個空格占一個位置。實際上我發現,這個filter函數,要想完成我需要的功能,只能處理list中任何帶空格的位置都只能有一個空格,如果包含兩個或其他多個,就不能實現過濾掉空格的功能,下圖做了演示:
實際上,這樣做很麻煩,split函數里面什么都不加,就會處理掉所有空格,無論空格多少個,即split(),如下圖:
我最初繪制的loss曲線是將日志中每個loss都顯示,但曲線誤差大,不平滑,不便於分析:
造成這種原因是,在項目中,batch_size大小是2,即每次處理兩張圖片,在終端每20個迭代期顯示一次loss,也就是每個loss是40張圖片的,有可能某幾張圖片的loss比較大,就造成這一段迭代期的誤差大。基於此,我將圖像顯示換成連續10個loss的平均,相當於200個迭代期的平均loss,這樣下來波動就小了很多,方便分析:
生成的日志文件,無論是40000迭代期,還是80000,在20000時都要進入ipython,造成日志的格式不對,導致畫不出圖像,在后面的迭代期也會顯示一些奇怪的東西,反正都需要刪除掉,之后就能正常顯示圖像: