人類大腦可以輕而易舉的識(shí)別圖像中的獅子和豹子竣灌,識(shí)別各種符號(hào)和文字,識(shí)別人臉腐魂,等等帐偎,但這些對(duì)于計(jì)算機(jī)來說就很難。因?yàn)槲覀兊拇竽X尤其擅長(zhǎng)圖像識(shí)別蛔屹。
題外削樊,其實(shí)也并非完全如此,對(duì)于陌生的內(nèi)容兔毒,我們并不能做的太好漫贞,比如你可能看過太多的日文字幕,但再看到屏幕上的日文符號(hào)的時(shí)候育叁,你還記得它是否在《名偵探柯南》中出現(xiàn)過嗎迅脐?同樣,我們對(duì)于華人明星的臉幾乎從不會(huì)搞混誰是誰豪嗽,但對(duì)于歐美明不太知名的明星恐怕就經(jīng)常分不清哪個(gè)是哪個(gè)谴蔑。人類也經(jīng)常被各種視覺錯(cuò)覺所欺騙,比如經(jīng)常去看的3D電影龟梦,一塊幕布竟然被我們看出了立體感隐锭,不覺得奇怪嗎?這里面一定隱藏著什么一些未被重視的算法计贰。
在過去的幾年钦睡,計(jì)算機(jī)科學(xué)家在圖像識(shí)別上取得了重大突破,尤其是CNN卷積神經(jīng)網(wǎng)絡(luò)的使用躁倒,計(jì)算機(jī)已經(jīng)能夠解決很多有難度的圖像識(shí)別任務(wù)荞怒,甚至某些領(lǐng)域比人做的還好。
計(jì)算機(jī)視覺算法已經(jīng)攻克了學(xué)院派的ImageNet測(cè)試標(biāo)準(zhǔn)秧秉,成功的模型不斷進(jìn)步: QuocNet, AlexNet, Inception (GoogLeNet), BN-Inception-v2褐桌,Google谷歌內(nèi)部和外部的科學(xué)家正在探討下一代圖像識(shí)別模型,Inception-v3福贞。
Inception-v3是基于2012年以來ImageNet圖片挑戰(zhàn)賽數(shù)據(jù)訓(xùn)練得到的撩嚼,這個(gè)挑戰(zhàn)賽是計(jì)算機(jī)視覺的標(biāo)桿性的比賽,需要識(shí)別1000種分類挖帘,比如斑馬完丽、斑點(diǎn)狗和洗碗機(jī)等,比如下面是AlexNet模型識(shí)別的一些分類:
為了對(duì)比模型,我們檢查模型預(yù)測(cè)失敗率最高的5個(gè)預(yù)測(cè)要拂,AlexNet在2012年top5錯(cuò)誤率是15.3%,Inception是6.67%抠璃,Inception-v2是4.9,Inception-v3是3.46%脱惰。而人類的top-5錯(cuò)誤率是5.1%搏嗡。
本篇教程將介紹如何使用Inception-v3識(shí)別1000種分類,也會(huì)介紹如何從這個(gè)模型種提取更高特征拉一,以便于用其他識(shí)別任務(wù)采盒。
準(zhǔn)備工作
首先從百度云盤下載相關(guān)文件(密碼:q3kf)
其中包含了saved_model和imgs兩個(gè)文件夾
- saved_model,這里是谷歌Inception項(xiàng)目基于眾多圖片訓(xùn)練好的模型蔚润,以及分類名稱對(duì)照表數(shù)據(jù)(label_map_proto.pbtxt磅氨,nameid2name.txt)。這里是已保存的模型嫡纠,而不是checkpoint文件烦租。
- imgs,只是一張將被識(shí)別的熊貓圖片。
這次我將tensorflow升級(jí)到了1.7除盏,推薦通過下面命令行升級(jí)
pip3 install --upgrade pip
pip3 install --upgrade tensorflow
代碼結(jié)構(gòu)
import os
import re #regular expressions正則表達(dá)式操作模塊
import numpy as np
import tensorflow as tf
#準(zhǔn)備目錄
dir_path = os.path.dirname(os.path.realpath(__file__))
model_dir=os.path.join(dir_path,'saved_model')
image_path=os.path.join(dir_path, 'imgs/panda.jpg')
proto_path=os.path.join(dir_path, 'saved_model/label_map_proto.pbtxt')
name_path=os.path.join(dir_path, 'saved_model/nameid2name.txt')
model_path=os.path.join(model_dir, 'classify_image_graph_def.pb')
#我們的主要代碼將添加在這里
#入口函數(shù)
def main(_):
#在這里運(yùn)行測(cè)試
#模塊或應(yīng)用
if __name__ == '__main__':
tf.app.run()
加載分類名稱對(duì)照表函數(shù)
首先看一下兩個(gè)文件的內(nèi)容:
分類類別編號(hào)(target_class即nodeid)和名稱編號(hào)( target_class_string即nameid)的對(duì)應(yīng)關(guān)系label_map_proto.pbtxt:
# -*- protobuffer -*-
# LabelMap from ImageNet 2012 full data set UID to int32 target class.
entry {
target_class: 449
target_class_string: "n01440764"
}
entry {
target_class: 450
target_class_string: "n01443537"
}
名稱編號(hào)(n開頭)和名稱的對(duì)應(yīng)關(guān)系nameid2name.txt:
n00004475 organism, being
n00005787 benthos
n00006024 heterotroph
n00006484 cell
n00007846 person, individual, someone, somebody, mortal, soul
我們向代碼添加獲取分類的函數(shù)get_class,并喂它創(chuàng)建兩個(gè)方法:
- load叉橱,用來讀取兩個(gè)文件,逐行讀取痴颊,然后拆分赏迟,重組成為兩個(gè)字典{nodeid:nameid}和{nameid:name},再把兩個(gè)字典合并成為{nodeid:name}
-
get_name_by_nodeid,根據(jù)上面的字典蠢棱,通過nodeid獲取name锌杀。
并在init里面自動(dòng)使用load()方法讀取。
在main里面運(yùn)行并測(cè)試,增加和修改的部分代碼如下:
#用來把分類節(jié)點(diǎn)nodeid轉(zhuǎn)為物體名稱字符串的函數(shù)
class get_class():
def __init__(self):
self.class_list=self.load() #載入泻仙,生成{nodeid:name}字典
#從文本載入名稱id映射數(shù)據(jù)
def load(self):
#讀取nameid和name的對(duì)應(yīng)關(guān)系
nameid2name = {} #字典{nid:name}
lines1 = tf.gfile.GFile(name_path).readlines()
p = re.compile(r'[n\d]*[ \S,]*') #定義正則匹配方式
for line in lines1:
#findall將n00004475 organism, being分解成['n00004475','organism, being']
parsed_items = p.findall(line)
nid = parsed_items[0]
name = parsed_items[2]
nameid2name[nid] = name
#讀取nodeid和nameid的對(duì)應(yīng)關(guān)系
nodeid2nameid = {}
lines2 = tf.gfile.GFile(proto_path).readlines()
for line in lines2:
#參考數(shù)據(jù)格式entry {
# target_class: 449
# target_class_string: "n01440764"
#}
if line.startswith(' target_class:'):
target_class = int(line.split(': ')[1])
if line.startswith(' target_class_string:'):
target_class_string = line.split(': ')[1]
nodeid2nameid[target_class] = target_class_string[1:-2]
#合并成nodeid和name的對(duì)應(yīng)關(guān)系
nodeid2name = {}
for key, val in nodeid2nameid.items():
if val not in nameid2name:
tf.logging.fatal('對(duì)應(yīng)失敗: %s', val)
name = nameid2name[val]
nodeid2name[key] = name
print(nodeid2name) #這里打印結(jié)果糕再!
return nodeid2name
#根據(jù)nodeid獲取分類名
def get_name_by_nodeid(self, nodeid):
if nodeid not in self.class_list:
return ''
return self.class_list[nodeid]
#入口函數(shù)
def main(_):
getclass = get_class()
運(yùn)行將會(huì)打印出結(jié)果,編號(hào)對(duì)應(yīng)了名稱。
{449: 'tench, Tinca tinca', 450: 'goldfish, Carassius auratus', 442: 'great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias', 443: 'tiger shark,...
這個(gè)操作其實(shí)沒什么意義玉转,但是如果沒有這個(gè)450到'goldfish, Carassius auratus'(金魚)的轉(zhuǎn)換突想,那么稍后預(yù)測(cè)出我們的圖片是565,我們也沒法知道這是什么鬼。
讀取已保存的模型
我們使用tf.gfile
來讀取保存的pd文件model_path=os.path.join(model_dir, 'classify_image_graph_def.pb')
,把內(nèi)容恢復(fù)到tf.GraphDef
tensorflow的計(jì)算圖中猾担。
下面是增加和修改部分的代碼袭灯,運(yùn)行后沒有輸出結(jié)果。
#從保存的模型讀取graph
def create_graph():
with tf.gfile.FastGFile(model_path, 'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
_ = tf.import_graph_def(graph_def, name='')
#入口函數(shù)
def main(_):
create_graph()
對(duì)圖片進(jìn)行預(yù)測(cè)的函數(shù)
簽名create_graph載入已保存模型創(chuàng)建了圖绑嘹,我們只要從graph中找出最終輸出的張量softmax稽荧,然后利用feed_dict重新喂食我們的圖片,就能run出預(yù)測(cè)結(jié)果工腋。
當(dāng)然別忘了用我們辛苦編寫的get_class來把預(yù)測(cè)的nodeid改為可以讀懂的名稱再打印出來姨丈。
增加和修改部分的代碼如下:
#執(zhí)行預(yù)測(cè)
def predict_image():
image_data = tf.gfile.FastGFile(image_path, 'rb').read() #讀取圖片
create_graph() #創(chuàng)建計(jì)算圖
with tf.Session() as sess:
softmax_tensor = sess.graph.get_tensor_by_name('softmax:0') #從計(jì)算圖中提取張量
predictions = sess.run(softmax_tensor,{'DecodeJpeg/contents:0': image_data}) #輸入feed_dict進(jìn)行運(yùn)算
predictions = np.squeeze(predictions) #去掉冗余的1維形狀,比如把張量形狀從(1,3,1)變?yōu)?3)
#輸出打印
getclass = get_class()
top5 = predictions.argsort()[-5:][::-1]
print('\n預(yù)測(cè)結(jié)果是:')
for node_id in top5:
name_string = getclass.get_name_by_nodeid(node_id)
score = predictions[node_id]
print('%s (score = %.5f)' % (name_string, score))
#入口函數(shù)
def main(_):
predict_image()
這次運(yùn)行就能預(yù)測(cè)大熊貓(giant panda)了擅腰!輸出類似下面的結(jié)果,0.89107表示這個(gè)圖片有89.107%的可能是大熊貓:
預(yù)測(cè)結(jié)果是:
giant panda, panda, panda bear, coon bear, Ailuropoda (score = 0.89107)
indri, indris, Indri indri, Indri brevicaudatus (score = 0.00779)
lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens (score = 0.00296)
custard apple (score = 0.00147)
earthstar (score = 0.00117)
結(jié)語
使用谷歌Inception項(xiàng)目訓(xùn)練好的模型來預(yù)測(cè)圖片蟋恬,其實(shí)關(guān)鍵代碼很簡(jiǎn)單,就是載入模型趁冈,重新生成計(jì)算圖歼争,進(jìn)行預(yù)測(cè)。但大篇幅的代碼都是get_class來轉(zhuǎn)換文字的箱歧。
下面是全部代碼:
import os
import re #regular expressions正則表達(dá)式操作模塊
import numpy as np
import tensorflow as tf
#準(zhǔn)備目錄
dir_path = os.path.dirname(os.path.realpath(__file__))
model_dir=os.path.join(dir_path,'saved_model')
image_path=os.path.join(dir_path, 'imgs/panda.jpg')
proto_path=os.path.join(dir_path, 'saved_model/label_map_proto.pbtxt')
name_path=os.path.join(dir_path, 'saved_model/nameid2name.txt')
model_path=os.path.join(model_dir, 'classify_image_graph_def.pb')
#用來把分類節(jié)點(diǎn)nodeid轉(zhuǎn)為物體名稱字符串的函數(shù)
class get_class():
def __init__(self):
self.class_list=self.load() #載入矾飞,生成{nodeid:name}字典
#從文本載入名稱id映射數(shù)據(jù)
def load(self):
#讀取nameid和name的對(duì)應(yīng)關(guān)系
nameid2name = {} #字典{nid:name}
lines1 = tf.gfile.GFile(name_path).readlines()
p = re.compile(r'[n\d]*[ \S,]*') #定義正則匹配方式
for line in lines1:
#findall將n00004475 organism, being分解成['n00004475','organism, being']
parsed_items = p.findall(line)
nid = parsed_items[0]
name = parsed_items[2]
nameid2name[nid] = name
#讀取nodeid和nameid的對(duì)應(yīng)關(guān)系
nodeid2nameid = {}
lines2 = tf.gfile.GFile(proto_path).readlines()
for line in lines2:
#參考數(shù)據(jù)格式entry {
# target_class: 449
# target_class_string: "n01440764"
#}
if line.startswith(' target_class:'):
target_class = int(line.split(': ')[1])
if line.startswith(' target_class_string:'):
target_class_string = line.split(': ')[1]
nodeid2nameid[target_class] = target_class_string[1:-2]
#合并成nodeid和name的對(duì)應(yīng)關(guān)系
nodeid2name = {}
for key, val in nodeid2nameid.items():
if val not in nameid2name:
tf.logging.fatal('對(duì)應(yīng)失敗: %s', val)
name = nameid2name[val]
nodeid2name[key] = name
return nodeid2name
#根據(jù)nodeid獲取分類名
def get_name_by_nodeid(self, nodeid):
if nodeid not in self.class_list:
return ''
return self.class_list[nodeid]
#從保存的模型讀取graph
def create_graph():
with tf.gfile.FastGFile(model_path, 'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
_ = tf.import_graph_def(graph_def, name='')
#執(zhí)行預(yù)測(cè)
def predict_image():
image_data = tf.gfile.FastGFile(image_path, 'rb').read() #讀取圖片
create_graph() #創(chuàng)建計(jì)算圖
with tf.Session() as sess:
softmax_tensor = sess.graph.get_tensor_by_name('softmax:0') #從計(jì)算圖中提取張量
predictions = sess.run(softmax_tensor,{'DecodeJpeg/contents:0': image_data}) #輸入feed_dict進(jìn)行運(yùn)算
predictions = np.squeeze(predictions) #去掉冗余的1維形狀,比如把張量形狀從(1,3,1)變?yōu)?3)
#輸出打印
getclass = get_class()
top5 = predictions.argsort()[-5:][::-1]
print('\n預(yù)測(cè)結(jié)果是:')
for node_id in top5:
name_string = getclass.get_name_by_nodeid(node_id)
score = predictions[node_id]
print('%s (score = %.5f)' % (name_string, score))
#入口函數(shù)
def main(_):
predict_image()
#模塊或應(yīng)用
if __name__ == '__main__':
tf.app.run()
探索人工智能的新邊界
如果您發(fā)現(xiàn)文章錯(cuò)誤呀邢,請(qǐng)不吝留言指正洒沦;
如果您覺得有用,請(qǐng)點(diǎn)喜歡价淌;
如果您覺得很有用申眼,感謝轉(zhuǎn)發(fā)~
END