深度学习之Tensorflow 入门实践

跟其他很多想学这门技术的同学一样,刚开始也是很懵逼,不知如何开始下手;于是网上搜索各种学习路线,其中Andrew Ng的机器学习课在很多地方被推荐。于是乎——虽然是英语的,但也图文并茂,基本能理解。从这里了解到神经网络原来就是拟合数据的方程矩阵,揭开了神秘面纱;也知道了过拟合欠拟合等基本概念。
机器学习-Andrew Ng

后来看到第8周,感觉有点无聊。然后去网上搜索到这篇博客零基础入门深度学习(3) - 神经网络和反向传播算法,里面的内容基本上也是之前Nadrew Ng视频里面讲过的内容,但是这篇作文更偏向实战。照着他的样子推导了一下反向传播的公式;实例代码是手写数字识别,代码很繁杂,向前传播与反向传播都是for循环实现的,不容易看明白;然后我用numpy矩阵乘法重新实现了一下,发现真的可以识别,太惊喜了。之前看Andrew Ng视频看得打瞌睡,现在突然来了精神。

for x, y in zip(inputs, labels):
    
    # 计算输出值
    z2 = w12.dot(x)
    a2 = sigmoid(z2)
    
    z3 = w23.dot(a2)
    a3 = softmax(z3)
    #     print('output:',a3)
    
    # 反向传播
    #######################
    label = np.zeros(10)
    label[y] = 1
    #     print('lable:',label)
    #     print('output:',get_result(a3),'label:',y)
    
    delta3 = a3 - label
    #     print('delta3:',delta3)
    
    # 更新w23
    w23 = w23 + µ * delta3.reshape(len(delta3), 1).dot(a2.reshape(1, len(a2)))
    
    # 计算a2节点误差delta2
    delta2 = a2 * (1 - a2) * w23.T.dot(delta3)
    
    # 更新w12
    #     print(x.shape,w12.shape,delta2.shape)
    w12 = w12 + µ * delta2.reshape(len(delta2), 1).dot(x.reshape(1, len(x)))
    
    #计算代价函数值:
    J=0.5*np.sum(np.array(list(map(lambda x,y:np.square(x-y),a3,label))))
    print(J)

完整代码

同系列的文章,这篇说卷积网络的也说的很详细:零基础入门深度学习(4) - 卷积神经网络;卷积的公式推导就直接放弃了(看着头大);为了获得卷积的客观认识,专门生成了一个高斯滤波来卷积了一张图片matplotlib.ipynb

有了这样的背景常识后,开始了Tensorflow的学习

当然这个时候都是围绕手写数字识别进行的
手写数字中文Tensorflow文档
MNIST机器学习入门.ipynb
MNIST机器学习入门-优化.ipynb
卷积神经网络-CNN.ipynb

这样的学习感觉还是有点零散,然后买了本《Tensorflow:实战Google深度学习框架》第2版 郑泽宇 梁博文 顾思宇 著,把里面大部分代码过了一遍。

《Tensorflow:实战Google深度学习框架》第2版 代码
因为tensorflow用的是1.4.0,版本不一致容易出错,所以运行代码还是推荐用docker。
docker run -d -v /Users/yetongxue/Desktop/jupyter_notebook/notebooks:/notebooks --restart=always -p 8889:8888 -p 6007:6006 tensorflow/tensorflow:1.4.0

这里面比较有用的几个点如下:

1、模型的保存与恢复
tensorflow_5.4_model_saver.ipynb
保存一般有两种方式,一是保存为.ckpt

#模型保存
import tensorflow as tf

v1_=tf.Variable(tf.constant(1.0,shape=[1]),name='v1_')
v2_=tf.Variable(tf.constant(2.0,shape=[1]),name='v2_')
result=v1_+v2_

saver=tf.train.Saver()
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    saver.save(sess,'./tmp/model_saver/model.ckpt')

另一种是保存为.pd

#存储
import tensorflow as tf
from tensorflow.python.framework import graph_util

v1=tf.Variable(tf.constant(1.0,shape=[1]),name='v1')
v2=tf.Variable(tf.constant(2.0,shape=[1]),name='v2')

result=v1+v2

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    graph_def=tf.get_default_graph().as_graph_def()
    
    out_graph_def=graph_util.convert_variables_to_constants(
        sess,
        graph_def,
        ['add']
    )
    
    with tf.gfile.GFile('./tmp/convert_vtoc/model.pd','wb') as f:
        f.write(out_graph_def.SerializeToString())

这两种保存方式的恢复也是不同的

#读取模型(jupyter kernel should restart!)
import tensorflow as tf

saver=tf.train.import_meta_graph('./tmp/model_saver/model.ckpt.meta')

with tf.Session() as sess:
    saver.restore(sess,'./tmp/model_saver/model.ckpt')
    
    #通过张量名称来获取张量
    print sess.run(tf.get_default_graph().get_tensor_by_name('v1_:0'))

但是平常开发,我们更多的时候会使用到别人预训练好的模型,比如:Tensorflow官方维护的这些模型

Model TF-Slim File Checkpoint Top-1 Accuracy Top-5 Accuracy
Inception V1 Code inception_v1_2016_08_28.tar.gz 69.8 89.6
Inception V2 Code inception_v2_2016_08_28.tar.gz 73.9 91.8
Inception V3 Code inception_v3_2016_08_28.tar.gz 78.0 93.9
Inception V4 Code inception_v4_2016_09_09.tar.gz 80.2 95.2
Inception-ResNet-v2 Code inception_resnet_v2_2016_08_30.tar.gz 80.4 95.3
ResNet V1 50 Code resnet_v1_50_2016_08_28.tar.gz 75.2 92.2
ResNet V1 101 Code resnet_v1_101_2016_08_28.tar.gz 76.4 92.9
ResNet V1 152 Code resnet_v1_152_2016_08_28.tar.gz 76.8 93.2
ResNet V2 50^ Code resnet_v2_50_2017_04_14.tar.gz 75.6 92.8
ResNet V2 101^ Code resnet_v2_101_2017_04_14.tar.gz 77.0 93.7
ResNet V2 152^ Code resnet_v2_152_2017_04_14.tar.gz 77.8 94.1
ResNet V2 200 Code TBA 79.9* 95.2*
VGG 16 Code vgg_16_2016_08_28.tar.gz 71.5 89.8
VGG 19 Code vgg_19_2016_08_28.tar.gz 71.1 89.8
MobileNet_v1_1.0_224 Code mobilenet_v1_1.0_224.tgz 70.9 89.9
MobileNet_v1_0.50_160 Code mobilenet_v1_0.50_160.tgz 59.1 81.9
MobileNet_v1_0.25_128 Code mobilenet_v1_0.25_128.tgz 41.5 66.3
MobileNet_v2_1.4_224^* Code mobilenet_v2_1.4_224.tgz 74.9 92.5
MobileNet_v2_1.0_224^* Code mobilenet_v2_1.0_224.tgz 71.9 91.0
NASNet-A_Mobile_224# Code nasnet-a_mobile_04_10_2017.tar.gz 74.0 91.6
NASNet-A_Large_331# Code nasnet-a_large_04_10_2017.tar.gz 82.7 96.2
PNASNet-5_Large_331 Code pnasnet-5_large_2017_12_13.tar.gz 82.9 96.2

压缩包下载下来只有.ckpt文件(模型中每个变量的取值),没有ckpt.meta(整个计算图的结构)
这个时候,我们就需要从Code来获取计算图结构,这里以恢复VGG16模型举例,代码如下:

import vgg
import tensorflow as tf

IMAGE_SIZE = vgg.vgg_16.default_image_size
VGG16_CKPT = 'tmp/vgg_16.ckpt'
TMP_DIR = 'tmp'

def get_vgg_16_graph(path=VGG16_CKPT):
    """获取 vgg16 计算图 """
    x = tf.placeholder(tf.float32, [None, IMAGE_SIZE, IMAGE_SIZE, 3])
    
    vgg.vgg_16(x)
    saver = tf.train.Saver()
    
    with tf.Session() as sess:
        saver.restore(sess, path)
        writer = tf.summary.FileWriter(TMP_DIR, sess.graph)
        writer.close()

执行tensorboard --logdir=TMP_DIR,访问localhost:6006便可查看vgg16的计算图

另外一种从.pd文件恢复模型:

def get_pool_3_reshape_values(sess, images):
    """
    :param images 图片路径数组
    通过inception-v3,将图片处理成pool_3_reshape数据,以供自定义全连接网络训练使用
    """
    with tf.gfile.FastGFile(INCEPTION_V3_PD, 'rb') as f:
        graph_def = tf.GraphDef()
        graph_def.ParseFromString(f.read())

        decode_jpeg_contents_tensor, pool_3_reshape_tensor = tf.import_graph_def(
            graph_def,
            return_elements=[DECODE_JPEG_CONTENTS, POOL_3_RESHAPE_NAME]
        )
    print decode_jpeg_contents_tensor, pool_3_reshape_tensor

    images_2048 = []
    for path in images:
        img = get_pool_3_reshape_sigal_image_values(sess, pool_3_reshape_tensor, path)
        images_2048.append(img)

    return images_2048

完整代码——这份代码是使用inception-v3做迁移学习的时候,将训练图片用训练好的inception-v3提取特征之后作为自定义分类网络的输入 的数据准备代码

2、TFRecord的使用

TFRecord是Tensorflow官方推荐的高效数据读取方式。使用tfrecord的优势在于:Tensorflow有和tfrecord配套的一些函数,可以加快数据的处理。实际读取tfrecord数据时,先以相应的tfrecord文件为参数,创建一个输入队列,这个队列有一定的容量(视具体硬件限制,用户可以设置不同的值),在一部分数据出队列时,tfrecord中的其他数据就可以通过预取进入队列,并且这个过程和网络的计算是独立进行的。也就是说,网络每一个iteration的训练不必等待数据队列准备好再开始,队列中的数据始终是充足的,而往队列中填充数据时,也可以使用多线程加速。

读取原始数据转成TFRecord文件存储

def int64_feature(value):
    return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))


def bytes_feature(value):
    return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))


def to_tfexample(image, label):
    return tf.train.Example(features=tf.train.Features(feature={
        'image': bytes_feature(image),
        'label': int64_feature(label)
    }))


def generate_tfrecord(writer):
    sub_dirs = [_[0] for _ in os.walk(FLOWER_PHOTOS)][1:]

    for index, dirname in enumerate(sub_dirs):
        print 'label:%d, flower name: %s' % (index, os.path.basename(dirname))

        # 拼接glob匹配的文件名
        re = os.path.join(dirname, '*.jpg')
        files = glob.glob(re)[:10] if IS_TEST else glob.glob(re)

        for path in files:
            image = Image.open(path)
            resized = image.resize((IMAGE_SIZE, IMAGE_SIZE))
            image_bytes = resized.tobytes()
            example = to_tfexample(image_bytes, index)
            writer.write(example.SerializeToString())


# 执行数据转换
def test_write():
    with tf.python_io.TFRecordWriter(OUTPUT) as writer:
        generate_tfrecord(writer)

读取TFRecord文件

# 读取TFRecord文件

def read_and_decode(filename_queue):
    reader = tf.TFRecordReader()
    _, serialized_example = reader.read(filename_queue)

    # 解析example
    features = tf.parse_single_example(serialized_example, features={
        'image': tf.FixedLenFeature([], tf.string),
        'label': tf.FixedLenFeature([], tf.int64)
    })
    single_image = tf.decode_raw(features['image'], tf.uint8)
    single_image = tf.reshape(single_image, [IMAGE_SIZE, IMAGE_SIZE, 3])

    single_label = tf.cast(features['label'], tf.int32)

    return single_image, single_label


def get_batch():

    filename_queue = tf.train.string_input_producer([OUTPUT])
    single_image, single_label = read_and_decode(filename_queue)

    image_batch, label_batch = tf.train.shuffle_batch(
        [single_image, single_label],
        batch_size=BATCH_SIZE,
        num_threads=4,
        capacity=50000,
        min_after_dequeue=10000
    )
    return image_batch,label_batch


def test_read():
    image_batch, label_batch = get_batch()

    with tf.Session() as sess:

        init_op = tf.group(tf.global_variables_initializer(), tf.local_variables_initializer())
        sess.run(init_op)

        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(coord=coord)

        # 获取10批次数据
        for _ in range(10):
            _image_batch, _label_batch = sess.run([image_batch,label_batch])
            print _image_batch.shape,_label_batch

        coord.request_stop()
        coord.join(threads)

完整代码
tensorflow_6_5_transfer_learning_datasets.ipynb(含flower_photos数据集下载链接)

3、Tensorflow-image
与图像有关基本操作,比如随机剪切、翻转、标注等API;对数据集进行剪切翻转旋转,一来可以增加数据集数量,二来还可以增加训练出来的模型的泛化能力,所以也是比较有用的。
tensorflow_7_2_image.ipynb

4、Tensorflow-slim
另外,Tensorflow官方维护的模型基本上都用slim封装了,使得定义模型的代码超级简洁,所以slim也是应该了解的。
tensorflow_10_1_tensorflow_slim.ipynb
贴一个别人写的:Tensorflow学习: Slim tutorial,他也是翻译别人的,没翻译完。

写在后面

进行到这里基本上对深度学习-Tensorflow有了客观的认识;其实很多与深度学习有关的项目,也恰恰是利用了深度学习,特别是卷积神经网络强大的特征提取能力,拿到这些特征我们便可以开开心心做一些其他想做事情;去找几个开源项目学习下,一来看看人家是如何一步一步实现的,巩固下代码技能;二来就是感受下深度学习项目的套路,看能不能有所启发。遇到懵逼的数学概念、公式,统计方面的知识也补一补,差不多就这样。

推荐阅读更多精彩内容