paddlepaddle圖像分類(lèi)與visualDL嘗鮮體驗(yàn)
paddlepaddle圖像分類(lèi)
很早之前,有寫(xiě)過(guò)關(guān)于TensorFlow, MXNet中如何訓(xùn)練一個(gè)靠譜的圖像分類(lèi)器,這里我會(huì)先使用paddlepaddle官方的例子,來(lái)學(xué)習(xí)下如何使用paddlepaddle構(gòu)建一個(gè)靠譜的分類(lèi)器。
數(shù)據(jù)介紹
官方文檔上使用的數(shù)據(jù)是flowers-102,這個(gè)數(shù)據(jù)集早在當(dāng)初tflearn學(xué)習(xí)深度學(xué)習(xí)網(wǎng)絡(luò)的時(shí)候就有接觸過(guò),還是比較簡(jiǎn)單的,paddlepaddle把它寫(xiě)成數(shù)據(jù)接口
模型介紹
paddlepaddle的模型介紹model overview。這里我們?cè)趯?shí)驗(yàn)當(dāng)中使用大名鼎鼎的resnet-50:
import paddle.v2 as paddle __all__ = ['resnet_imagenet', 'resnet_cifar10'] def conv_bn_layer(input, ch_out, filter_size, stride, padding, active_type=paddle.activation.Relu(), ch_in=None): tmp = paddle.layer.img_conv( input=input, filter_size=filter_size, num_channels=ch_in, num_filters=ch_out, stride=stride, padding=padding, act=paddle.activation.Linear(), bias_attr=False) return paddle.layer.batch_norm(input=tmp, act=active_type) def shortcut(input, ch_out, stride): if input.num_filters != ch_out: return conv_bn_layer(input, ch_out, 1, stride, 0, paddle.activation.Linear()) else: return input def basicblock(input, ch_out, stride): short = shortcut(input, ch_out, stride) conv1 = conv_bn_layer(input, ch_out, 3, stride, 1) conv2 = conv_bn_layer(conv1, ch_out, 3, 1, 1, paddle.activation.Linear()) return paddle.layer.addto( input=[short, conv2], act=paddle.activation.Relu()) def bottleneck(input, ch_out, stride): short = shortcut(input, ch_out * 4, stride) conv1 = conv_bn_layer(input, ch_out, 1, stride, 0) conv2 = conv_bn_layer(conv1, ch_out, 3, 1, 1) conv3 = conv_bn_layer(conv2, ch_out * 4, 1, 1, 0, paddle.activation.Linear()) return paddle.layer.addto( input=[short, conv3], act=paddle.activation.Relu()) def layer_warp(block_func, input, ch_out, count, stride): conv = block_func(input, ch_out, stride) for i in range(1, count): conv = block_func(conv, ch_out, 1) return conv def resnet_imagenet(input, class_dim, depth=50): cfg = { 18: ([2, 2, 2, 1], basicblock), 34: ([3, 4, 6, 3], basicblock), 50: ([3, 4, 6, 3], bottleneck), 101: ([3, 4, 23, 3], bottleneck), 152: ([3, 8, 36, 3], bottleneck) } stages, block_func = cfg[depth] conv1 = conv_bn_layer( input, ch_in=3, ch_out=64, filter_size=7, stride=2, padding=3) pool1 = paddle.layer.img_pool(input=conv1, pool_size=3, stride=2) res1 = layer_warp(block_func, pool1, 64, stages[0], 1) res2 = layer_warp(block_func, res1, 128, stages[1], 2) res3 = layer_warp(block_func, res2, 256, stages[2], 2) res4 = layer_warp(block_func, res3, 512, stages[3], 2) pool2 = paddle.layer.img_pool( input=res4, pool_size=7, stride=1, pool_type=paddle.pooling.Avg()) out = paddle.layer.fc(input=pool2, size=class_dim, act=paddle.activation.Softmax()) return out def resnet_cifar10(input, class_dim, depth=32): # depth should be one of 20, 32, 44, 56, 110, 1202 assert (depth - 2) % 6 == 0 n = (depth - 2) / 6 nStages = {16, 64, 128} conv1 = conv_bn_layer( input, ch_in=3, ch_out=16, filter_size=3, stride=1, padding=1) res1 = layer_warp(basicblock, conv1, 16, n, 1) res2 = layer_warp(basicblock, res1, 32, n, 2) res3 = layer_warp(basicblock, res2, 64, n, 2) pool = paddle.layer.img_pool( input=res3, pool_size=8, stride=1, pool_type=paddle.pooling.Avg()) out = paddle.layer.fc(input=pool, size=class_dim, act=paddle.activation.Softmax()) return out
運(yùn)行
進(jìn)入對(duì)應(yīng)目錄后
python train.py resnet
即可完成
但是事實(shí)上其實(shí)不是這樣的,paddlepaddle安裝whl和tensorflow一樣,gpu版本都會(huì)對(duì)應(yīng)不同的cuda和cudnn,經(jīng)常會(huì)出一些配置問(wèn)題,所以直接在系統(tǒng)中安裝其實(shí)是一個(gè)不好的選擇,所以***是不要選擇直接安裝,而是使用nvidia-docker,同理在tensorflow,mxnet中,感覺(jué)nvidia-docker也是很好的。
nvidia-docker安裝
- 安裝cuda、cudnn,***的;
- 根據(jù)系統(tǒng)選擇對(duì)應(yīng)版本的docker;
- 安裝nvidia-docker:
docker volume ls -q -f driver=nvidia-docker | xargs -r -I{} -n1 docker ps -q -a -f volume={} | xargs -r docker rm -f sudo apt-get purge -y nvidia-docker docker volume ls -q -f driver=nvidia-docker | xargs -r -I{} -n1 docker ps -q -a -f volume={} | xargs -r docker rm -f sudo apt-get purge -y nvidia-docker curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - curl -s -L https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list sudo apt-get update sudo apt-get install -y nvidia-docker2 sudo pkill -SIGHUP dockerd
- 拉paddlepaddle鏡像
docker pull paddlepaddle/paddle:latest-gpu
- 進(jìn)入docker,
nvidia-docker run -it -v $PWD:/work -v /data:/data paddlepaddle/paddle:latest-gpu /bin/bash
。這里稍微注意下,paddlepaddle的官方鏡像源里面缺少一些必須的包,比如opencv,好像有點(diǎn)問(wèn)題,還有vim啥的也都沒(méi)有裝,這里為了運(yùn)行***把這些環(huán)境一次性都解決,然后docker commit,保存對(duì)鏡像的修改。
基本這樣,你就可以在本機(jī)上有一個(gè)完全干凈的docker環(huán)境,你就可以隨便折騰啦。這里如果有運(yùn)行不起來(lái)的問(wèn)題,可在下方評(píng)論,我具體也不記得缺哪些東西,不過(guò)都很好解決的
docker中運(yùn)行
這里我運(yùn)行過(guò)很長(zhǎng)時(shí)間的一個(gè)demo,但是因?yàn)闆](méi)有保存好信息,被覆蓋了,所以只能暫時(shí)演示一下。
使用自己的數(shù)據(jù)集來(lái)訓(xùn)練模型
實(shí)驗(yàn)完官方的flower-102之后,我們這里使用自己的數(shù)據(jù)集來(lái)訓(xùn)練模型,數(shù)據(jù)集是之前收集到的鑒黃數(shù)據(jù),數(shù)據(jù)集主要包括三類(lèi):porn\sexy\normal,大概有500w張左右。 首先,我們需要生成如下格式,格式為圖像路徑+"\t"+label,其中l(wèi)abel為0表示normal,1表示sexy,2表示porn。 生成腳本如下:
import random import os import codecs import sys def gen_datalist(data_dir, class_label, data_type="train", shuffle=True, suffix_list=["jpg", "jpeg", "JPEG", "jpg"]): all_files = [] for root, dirs, files in os.walk(data_dir): print "processing {0}".format(root) for file_name in files: file_name = os.path.join(root, file_name) suffix = file_name.split(".")[-1] if suffix in suffix_list: all_files.append(file_name) if shuffle: print "shuffle now" random.shuffle(all_files) print "begin to write to {0}".format(data_type+"_"+class_label+".lst") with codecs.open(data_type+"_"+class_label+".lst", "w", encoding="utf8") as fwrite: for each_file in all_files: fwrite.write(each_file+"\t"+class_label+"\n") if __name__ == "__main__": argv = sys.argv data_dir = argv[1] class_label = argv[2] gen_datalist(data_dir, class_label)
有了腳本運(yùn)行之后,發(fā)現(xiàn)了一些cv2庫(kù)中none 沒(méi)有shape的問(wèn)題,調(diào)試之后發(fā)現(xiàn),原來(lái)收集的數(shù)據(jù)中,有部分大小為0或者很小的圖像,這部分應(yīng)該是有問(wèn)題的數(shù)據(jù),寫(xiě)了個(gè)濾除腳本,刪除這些數(shù)據(jù)之后就妥了
from paddle.v2.image import load_and_transform import paddle.v2 as paddle def filter_imgs(file_path = "train.lst", write_file = "valid_train.lst"): fwrite = open(write_file, "w") with open(file_path, 'r') as fread: error=0 for line in fread.readlines(): img_path = line.strip().split("\t")[0] try: img = paddle.image.load_image(img_path) img = paddle.image.simple_transform(img, 256, 224, True) fwrite.write(line) except: error += 1 print error filter_imgs()
從頭開(kāi)始訓(xùn)練模型
在paddlepaddle中訓(xùn)練模型
image = paddle.layer.data( name="image", type=paddle.data_type.dense_vector(DATA_DIM)) conv, pool, out = resnet.resnet_imagenet(image, class_dim=CLASS_DIM) cost = paddle.layer.classification_cost(input=out, label=lbl) parameters = paddle.parameters.create(cost) optimizer = paddle.optimizer.Momentum( momentum=0.9, regularization=paddle.optimizer.L2Regularization(rate=0.0005 * BATCH_SIZE), learning_rate=learning_rate / BATCH_SIZE, learning_rate_decay_a=0.1, learning_rate_decay_b=128000 * 35, learning_rate_schedule="discexp", ) train_reader = paddle.batch( paddle.reader.shuffle( # flowers.train(), # To use other data, replace the above line with: reader.train_reader('valid_train0.lst'), buf_size=1000), batch_size=BATCH_SIZE) def event_handler(event): if isinstance(event, paddle.event.EndIteration): if event.batch_id % 1 == 0: print "\nPass %d, Batch %d, Cost %f, %s" % ( event.pass_id, event.batch_id, event.cost, event.metrics) if isinstance(event, paddle.event.EndPass): with gzip.open('params_pass_%d.tar.gz' % event.pass_id, 'w') as f: trainer.save_parameter_to_tar(f) result = trainer.test(reader=test_reader) print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics) trainer.train( reader=train_reader, num_passes=200, event_handler=event_handler)
- 需要配置resnet網(wǎng)絡(luò),確定好input和out,配置cost函數(shù),構(gòu)建parameter;
- 構(gòu)建optimizer,使用momentum的sgd;
- 構(gòu)建reader,設(shè)置訓(xùn)練數(shù)據(jù)讀取,配置上文提到的圖片路徑\tlabel的文件;
- event_handler是用來(lái)記錄batch_id\pass的事件處理函數(shù),傳入train函數(shù),訓(xùn)練過(guò)程中會(huì)完成相應(yīng)工作;
pretrain model + finetuning
resnet官方提供一個(gè)在imagenet上訓(xùn)練好的pretrained model,運(yùn)行model_download.sh。
sh model_download.sh ResNet50
會(huì)下載Paddle_ResNet50.tar.gz, 這個(gè)文件是paddlepaddle在ImageNet上訓(xùn)練的模型文件,我們這里使用這個(gè)文件的參數(shù)做初始化,我們需要在代碼,參數(shù)初始化的時(shí)候,使用這里的參數(shù),修改代碼如下:
if args.retrain_file is not None and ''!=args.retrain_file: print("restore parameters from {0}".format(args.retrain_file)) exclude_params = [param for param in parameters.names() if param.startswith('___fc_layer_0__')] parameters.init_from_tar(gzip.open(args.retrain_file), exclude_params)
首先,我們需要指定init_from_tar的參數(shù)文件為Paddle_ResNet50.tar.gz, 大家知道ImageNet是在1000類(lèi)上的一個(gè)模型,它的輸出為1000個(gè)節(jié)點(diǎn),所以我們這里需要稍作修改,我們?cè)黾右粋€(gè)exclud_params,指定***一層___fc_layer_0__的參數(shù),不要從文件當(dāng)中初始化.
pretrain model + freeze layers + finetuning
查了文檔和代碼知道,只需要在某層增加is_static=True
,就可以freeze掉該層的參數(shù),使該層參數(shù)不更新,但是我在使用這部分時(shí)遇到了bug,提了issue (core dumped with is_static=True)[https://github.com/PaddlePaddle/Paddle/issues/8355],出現(xiàn)core的問(wèn)題,無(wú)法正常使用,后面能夠搞定了,再更新這部分內(nèi)容。
代碼改進(jìn)
examples里面的代碼reader部分在處理data.lst時(shí),太過(guò)粗糙,沒(méi)有考慮到數(shù)據(jù)如果出現(xiàn)一些問(wèn)題時(shí),訓(xùn)練代碼會(huì)直接掛掉,這部分的代碼至少要保證足夠的魯棒性
def train_reader(train_list, buffered_size=1024): def reader(): with open(train_list, 'r') as f: lines = [line.strip() for line in f] for line in lines: try: img_path, lab = line.strip().split('\t') yield img_path, int(lab) except: print "record in {0} get error".format(train_list) continue return paddle.reader.xmap_readers(train_mapper, reader, cpu_count(), buffered_size)
visualDL實(shí)踐
可視化acc\loss
之前有在小的demo上體驗(yàn)過(guò)visualDL,在比較大的數(shù)據(jù)訓(xùn)練過(guò)程上沒(méi)試驗(yàn)過(guò),這次鑒黃數(shù)據(jù)上測(cè)試,打印出loss和acc看看,當(dāng)小數(shù)量的step的時(shí)候,看起來(lái)是沒(méi)有問(wèn)題的 。
但是但step較大的時(shí)候,acc打印不出來(lái)了,同樣的代碼,出錯(cuò)信息也看不出來(lái),各種莫名的報(bào)錯(cuò),看樣子和使用的代碼沒(méi)有什么關(guān)系,應(yīng)該是visualDL本身的容錯(cuò)做的不夠。
出錯(cuò)提示:
這部分和之前提過(guò)的一個(gè)issue很類(lèi)似: Unexpected error: <type 'exceptions.RuntimeError'> 因?yàn)樾畔⒘坎粔颍鋵?shí)很難自己這邊做問(wèn)題分析,希望visualDL把這塊容錯(cuò)做好一些。
這塊時(shí)間應(yīng)該有些問(wèn)題,我也不知道,我總覺(jué)的有點(diǎn)問(wèn)題 是我用的姿勢(shì)不對(duì)嗎 ?
可視化graph
使用有問(wèn)題,使用paddlepaddle保存好的模型指定給model_pb 出現(xiàn)如下問(wèn)題, 看了repo中的這部分的demo都是直接curl下來(lái)一個(gè)model.pb的文件,然后可視化,沒(méi)有找到能直接導(dǎo)出paddlepaddle保存模型的導(dǎo)入到visualdl中, 可能是我的使用方式有問(wèn)題, 保存模型方式如下圖:
莫非需要先把paddlepaddle模型轉(zhuǎn)換為onnx格式?
可視化image
有問(wèn)題,暫時(shí)沒(méi)有測(cè)試,之后更新后同步
總結(jié)
paddlepaddle現(xiàn)在在dl這塊還只是剛開(kāi)始,example里面的demo和tensorflow最開(kāi)始一樣,并不能完全hold住實(shí)際業(yè)務(wù)需求,當(dāng)初tensorflow的時(shí)候也有種種的問(wèn)題,后來(lái)經(jīng)過(guò)社區(qū)的幫助,到現(xiàn)在很多源碼幾乎都是開(kāi)箱即用,paddlepaddle現(xiàn)在可能在文檔與demo上還是0.7版本的tensorflow,不過(guò)希望能更加努力,畢竟作為同行,在參與了一些分布式dl模型的工作之后,深知其中的艱辛。visualdl相當(dāng)棒的工具,支持onnx的模型可視化,雖然在測(cè)試過(guò)程中感覺(jué)有些瑕疵,但是十分支持,希望能快速發(fā)展,個(gè)人也在閱讀這部分源碼學(xué)習(xí), histogram的相關(guān)功能沒(méi)有測(cè)試,挺有用的 尤其在訓(xùn)練跑偏的時(shí)候可以快速可視化參數(shù)的分布。***,強(qiáng)烈希望visualdl能把文檔弄的更友好一些,加油。
另外,大年三十,祝大家新年快樂(lè),狗年跑模型妥妥的收斂