2017年8月24日 星期四

按照類別下載 ImageNet 訓練圖片的一小部分

影像辨識之 caffe 初體驗 當中所用到的模型及權重矩陣, 它的訓練資料來自 ImageNet。 因為著作權的關係 blah blah... 所以 Princeton 大學跟 Stanford 大學不敢直接提供所有圖檔讓大家下載。 事實上, 總共有一千四百多萬張圖檔, 對於我們這些沒有 GPU 的人來說, 全部下載根本沒有意義, 也太不環保了。 比較實用的, 是根據標籤下載其中幾個類別的圖片, 以便做小規模的 transfer learning 等等。

首先請 下載超連結清單。 解壓縮之後會得到一個 fall11_urls.txt (2011 年秋天版) 文字檔。 可以用 wc -l fall11_urls.txt 數一下, 共有大約一千四百二十萬列。 太大了, 想用 vim 開, 卡了好幾分鐘, 放棄。 改用 head fall11_urls.txt 查看, 看到檔案內容長得像這樣:

n00004475_6590 http://farm4.static.flickr.com/...
n00004475_15899 http://farm4.static.flickr.com/...
n00004475_32312 http://farm3.static.flickr.com/...
n00004475_35466 http://farm4.static.flickr.com/...
n00004475_39382 http://2.bp.blogspot.com/_SrRTF...
n00004475_41022 http://fortunaweb.com.ar/wp-con...
n00004475_42770 http://farm4.static.flickr.com/...
n00004475_54295 http://farm4.static.flickr.com/...
n00005787_13 http://www.powercai.net/Photo/U...
n00005787_32 http://www.web07.cn/uploads/Pho...

每列前面是相片代號, 後面是網址。 相片代號的前半部 ("n" 加八個數字) 是 wnid, 也就是分類代號。 例如用瀏覽器打開 http://www.image-net.org/synset?wnid=n01882714, 就會看到 ImageNet 裡面蠻熱門的相片 -- 無尾熊。 頁面上方的標題是這類相片的英文描述。

所以我們還需要 這個壓縮檔 當中的 synset_words.txt -- 分類代號跟英文描述的對照表。 (也就是 caffe 初體驗當中, get_ilsvrc_aux.sh 指令抓回來的那個檔案。)

time sed 's/_.*//' fall11_urls.txt | sort | uniq -c | perl -pe 's/\s*(\d+)\s+(\w+)/$2,$1/' > freq_wnid.csv
sed 's#, *#/#g; s/ /,/; s/ /_/g' synset_words.txt | sort > wnid_text.csv
join -t , -a 1 -a 2 freq_wnid.csv wnid_text.csv | sort -t , -k 2 -nr > wnid_freq_text.csv

在較慢的機器上, 上面第一個指令可能要執行好幾分鐘。 它把網址清單 "_" 字元之後全部砍掉, 數一下每一類別的圖片有多少張, 並且把結果變成 「類別代號,張數」 格式的 csv 檔。 第二句話處理 「類別代號-文字描述」 檔, 先把類別文字描述裡的逗號變成除號 (以免跟 csv 格式的逗號混淆)、 把第一個空格變逗號、 把剩下所有的空格變底線; 最後根據類別代號排序, 因為下一句的 join 指令期待兩個輸入檔案都要按照相同的欄位事先排序好。 第三句以類別代號欄位作為共同的 key, 把兩個檔案合併 (就像資料庫的 join 一樣), 再根據每個類別出現的相片張數由大到小排序。

最後這個 wnid_freq_text.csv 檔, 可以拿來查詢每個類別的代號、 圖片張數、 英文描述。 從這裡可以看到: 最熱門的幾類圖片分別是約克夏㹴 (n02094433)、 獅子狗 (n02086240)、 無尾熊 (n01882714) 等等。 第六名的 「網球單打」 (n00483313)、 第十一名的 「囓齒類」 (n02329401) 則不屬於 ImageNet 的訓練辭彙; 但一樣可從上述 ...synset?wnid=... 的網址撈出一些圖片、 查看那個 wnid 的意義。 如果只對 ImageNet 的訓練辭彙有興趣, 則可以這樣做: grep -i ',[a-z]' wnid_freq_text.csv > imagenet.csv 出來的結果用 wc 去數, 正好一千列。

再來, 請下載我寫的 (執行效率很高的 ^_^) imnetget.perl 、 把它變成可執行檔。 再建立一個文字檔, 叫做 ape-wnid.txt 好了, 內容包含以下:

# 這一句是註解
n02481823 chimpanzee
n02482474 chimpanzee
n02482286 chimpanzee
n02482060 chimpanzee
n02482650 bonobo
n02480855 gorilla
n02481235 gorilla
n02481366 gorilla
n02481500 gorilla
n02481103 gorilla
n02480495 orangutan
n02483362 gibbon
n02483708 siamang

(其實 imnetget.perl 只用到每列前面 wnid 的部分; 下面另一個程式才會用到後面的文字名稱。) 請執行: ./imnetget.perl -n 200 -i 7 ape-wnid.txt < fall11_urls.txt > cmd.sh 這會印出一堆 wget 下載指令、 存放在 cmd.sh 裡面。 它會逐列審視 fall11_urls.txt 這個超大索引檔, 凡是遇到 wnid-name.txt 裡面提到的類型的圖片 (按照 類別 wnid 分開數) 就印一個 wget 指令準備把第 7 張、 第 207、 407、 607、 ... 張下載回來, 並且 按照 wnid-name.txt 裡面指定的名稱 按照 wnid, 再加上那張圖在該類別裡面的序號來命名, 像這樣:

n02480495-0007.jpg
n02480495-0207.jpg
n02480495-0407.gif
...
n02483708-0807.jpg
n02483708-1007.jpg

[9/9] 為什麼要按照 wnid 而不是按照人看得懂的文字來幫圖檔命名呢? 因為視不同的應用而定, 有時可能好幾個 wnid 會對應到同一類的圖片。 以上面的例子來說, 畫出 wordnet 名詞從屬關係繪圖 之後, 可以發現 n02481823 n02482474 n02482286 n02482060 通通都對應到黑猩猩。 如果用類別名稱來命名圖檔, 可能會撞山。 而且, 在另外的應用當中, 也許我們會想把本例當中所有物種全部歸為同一類 -- ape (猿類)。 我們希望在不同的應用場合當中, 相同的圖片還是保持相同的檔名, 所以用 wnid 來命名。 那如果需要按照類別名稱 (而非 wnid) 來數圖片出現的次數呢? 在 同一個 gist 我放了另一個小程式 wnid2name.perl , 可以這樣按照類別統計: perl -ne 'print "$1\n" if m/\b(n\d+)\b/' cmd.sh | ./wnid2name.perl ape-wnid.txt | sort | uniq -c

總之, 如果 cmd.sh 的內容看來正確, 就可以用 source cmd.sh 執行。 [8/27 更新] 關於禮貌: 預設每印 20 個 wget 就會印一個 sleep 30, 以免惹人厭甚至被當成 DOS 攻擊而遭封鎖。 你可以在命令列上加上 -s 20/50 改成每印 50 個 wget 就印一個 sleep 20 之類的。

但是很多網址都已經失效; 抓回來的, 有些是 html 錯誤訊息。 先用 file *.jpg | grep -v 'image data' 抓出 「非圖片檔」。 確認無誤後, 這樣刪除: rm $(file *.jpg | grep -v 'image data' | sed 's/:.*//')。 圖檔當中, 有些檔案大小為 0 bytes; 有些 (例如 flickr) 則是一張顯示錯誤訊息的圖。 基本上 2100 bytes 以下的, 都可以算是失敗的圖。 可以這樣挑出來、 刪掉: rm $(find . -size -2100c ) 。 刪完之後, 如果某些類別的圖片張數不夠, 就編輯 wnid-name.txt、 用 # 把已足額的類別註解掉、 再用 "-n 200 -i 8" 重跑一次 imnetget.perl 。

抓的圖片類別跟張數都少少的, 無法大規模訓練, 但後續可以拿來做 t-sne 視覺化, 或做 transfer learning

拉拉熊和丁丁的故事 告訴我們處理文字檔的能力很重要。 在機器學習的領域, 更是如此。 請跟我一起呼籲: 我們的資訊教育別再教 office 了! 教 regexp 跟命令列才符合時代需求啊!

2 則留言:

  1. 抓回來的圖檔改成 「以 wnid 命名」 而不是以 「人看得懂的類別名稱命名」。 好幾個 wnid 可能會對應到同一個類別名稱。 另外補一個小程式 wnid2name.perl 可把 stdin 裡面所有的 wnid 都改成對應的類別名稱。

    回覆刪除
  2. 謝謝分享,我抓"miss"圖檔是用 identify 收集 "miss" 圖檔的 signature 來比對

    回覆刪除