迁移网络实现原理

补发一段对于迁移网络的学习笔记。
手动训练一些层数较深的神经网络会花费大量的时间。我们可以利用一些常见的神经网络模型,使用已经训练好的参数,对图像的特征进行提取,这样来实现避免手动训练参数而花费太多时间的作用。

函数主题非常简单,以Inception-v3来作为特征提取网络,我们将待训练图片通过Inception-v3,得到特征向量,使用一个全连接层将特征向量与label标签链接起来,这时我们需要训练的就只有一个全连接层。

在使用该迁移网络对新的图片进行判断时,只需要获得特征向量后再经过全连接层即可。

这里使用的是谷歌提供的训练好的Inception-v3模型: https://storage.googleapis.com/download.tensorflow.org/models/inception_dec_2015.zip

案例使用的数据集: http://download.tensorflow.org/example_images/flower_photos.tgz

数据集文件解压后,包含5个子文件夹,子文件夹的名称为花的名称,代表了不同的类别。平均每一种花有734张图片,图片是RGB色彩模式,大小也不相同。

主要代码:

def main():
    image_list = create_image_list(TEST_PERCENTAGE, VALIDATION_PERCENTAGE)
    # 从图片文件夹中读取出图片

    n_classes = len(image_list.keys())
    # 分类个数,这里应该是5

    with gfile.FastGFile(os.path.join(MODEL_DIR, MODEL_FILE), 'rb') as f:
        graph_def = tf.GraphDef()
        graph_def.ParseFromString(f.read())
        # 从文件中提取模型并还原成graph

    bottleneck_tensor, jpeg_data_tensor = tf.import_graph_def(graph_def, return_elements=[
        BOTTLENECK_TENSOR_NAME, JPEG_DATA_TENSOR_NAME
    ])
    # 从Inception-v3中得到获取特征和label的张量。

    bottleneck_input = tf.placeholder(tf.float32, [None, BOTTLENECK_TENSOR_SIZE],
                                      name='BottleneckInputPlaceholder')
    # 特征向量

    ground_truth_input = tf.placeholder(tf.float32, [None, n_classes], name='GroundTruthInput')
    # 真实正确的label值

    with tf.name_scope('final_training_ops'):
        weights = tf.Variable(tf.truncated_normal(
            [BOTTLENECK_TENSOR_SIZE, n_classes], stddev=0.001
        ))
        biases = tf.Variable(tf.zeros([n_classes]))
        logits = tf.matmul(bottleneck_input, weights) + biases
        final_tensor = tf.nn.softmax(logits)
        # 一个全连接层

    cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=ground_truth_input)
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(cross_entropy_mean)
    #训练

    with tf.name_scope('evaluation'):
        correct_prediction = tf.equal(tf.arg_max(final_tensor, 1),
                                      tf.arg_max(ground_truth_input, 1))
        evaluation_step = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
        #正确率

    with tf.Session() as sess:
        init = tf.initialize_all_variables()
        sess.run(init)

        for i in range(STEPS):
            train_bottlenecks, train_ground_truth = get_random_cached_bottlenecks(
                sess, n_classes, image_list, BATCH, 'training', jpeg_data_tensor, bottleneck_tensor
            )
            # 利用从Inception-v3中提取出的张量计算训练组图片的label和特征

            sess.run(train_step, feed_dict={bottleneck_input:train_bottlenecks,
                                            ground_truth_input:train_ground_truth})
            if i%100 == 0 or i+1 == STEPS:
                validation_bottles, validation_ground_truth = get_random_cached_bottlenecks(
                    sess, n_classes, image_list, BATCH, 'validation', jpeg_data_tensor, bottleneck_tensor
                )
              # 利用从Inception-v3中提取出的张量计算确认组图片的label和特征

                validation_accuracy = sess.run(evaluation_step, feed_dict={
                    bottleneck_input:validation_bottles,
                    ground_truth_input:validation_ground_truth
                })
                print('Step %d: Validation accuracy on random sampled %d examples =%.lf%%' %
                      (i, BATCH, validation_accuracy*100))
                # 计算出正确率后输出

            test_bottlenecks, test_ground_truth = get_test_bottlenecks(
                sess, image_list, n_classes, jpeg_data_tensor, bottleneck_tensor
            )
            # 利用从Inception-v3中提取出的张量计算测试组图片的label和特征

            test_accuracy = sess.run(evaluation_step, feed_dict={bottleneck_input:test_bottlenecks,
                                                                    ground_truth_input:test_ground_truth})
            print('Final test accuracy = %.lf%%' % (test_accuracy*100))

到这里main函数的功能已经实现了。现在需要做的只是把需要的函数补齐。需要的函数有:

  • 从图片文件夹中读取图片: create_image_list
  • 利用从Inception-v3中提取出的张量计算组图片的label和特征 :get_random_cached_bottlenecks

从图片文件夹中读取图片 create_image_list:

传入参数测试集和确认集各占的百分比,随机划取相应比例的图片数据来进行训练集,测试集,确认集的标记。
如图,取一个0-100的随机数,如果在0-10区间内,当前图片归为确定集,如果在10-20区间内,当前图片归为测试集,其余的80%的任意取值,都归为训练集。

最终result的结构:

具体代码如下:

VALIDATION_PERCENTAGE = 10
TEST_PERCENTAGE = 10
INPUT_DATA = 'D://python//flower_photos//flower_photos'
def create_image_list(test_percentage, validation_percentage):
    result = {}
    sub_dirs = [x[0] for x in os.walk(INPUT_DATA)]
    is_root_dir = True
    for sub_dir in sub_dirs:
        if is_root_dir:
            is_root_dir = False
            continue
        extensions = ['jpg', 'jpeg', 'JPG', 'JPEG']
        file_list = []
        dir_name = os.path.basename(sub_dir)
        for extionsion in extensions:
            file_glob = os.path.join(INPUT_DATA, dir_name, '*.'+extionsion)
            file_list.extend(glob.glob(file_glob))
        if not file_list: continue

        label_name = dir_name.lower()
        training_images = []
        testing_images = []
        validation_images = []
        for file_name in file_list:
            base_name = os.path.basename(file_name)

            chance = np.random.randint(100)
            if chance < validation_percentage:
                validation_images.append(base_name)
            elif chance < (test_percentage + validation_percentage):
                testing_images.append(base_name)
            else:
                training_images.append(base_name)

        result[label_name] = {
            'dir':dir_name,
            'training':training_images,
            'testing':testing_images,
            'validation':validation_images
        }
    return result

利用张量计算图片的label和特征 :get_random_cached_bottlenecks

我们可以使用函数嵌套,通过传递不同的参数,如“training”,"validation",来获取不同类别的样本图片,并将样本图片进行处理,得到处理后的特征矩阵。
函数的主要步骤为:

  • 随机选取一个类别
  • 在该类别的category中随机选取一个图片
  • 计算该图片的特征
  • 重复前3步how_many次
def get_random_cached_bottlenecks(sess, n_classes, image_lists, how_many, category,
                                  jpeg_data_tensor, bottleneck_tensor):
    '''
    :param n_classes: 分类个数,这里应该为5
    :param image_lists: 图片分类和位置信息,由函数create_image_list求得
    :param how_many: BATCH数目,每次需要抽取的样本个数
    :param category: 需要获取的类别:test/train/validation
    :param jpeg_data_tensor: 数据输入张量
    :param bottleneck_tensor: 特征生成张量
    :return: how_many个category类型的图像数据经过处理后的特征矩阵和分类矩阵
    '''
    bottlenecks = []
    ground_truths = []
    for _ in range(how_many):
        label_index = random.randrange(n_classes)
        # 类别选取
        label_name = list(image_lists.keys())[label_index]
        image_index = random.randrange(65536)
        # 图片选取
        bottleneck = get_or_create_bottleneck(sess,image_lists,label_name,
                                              image_index, category,
                                              jpeg_data_tensor, bottleneck_tensor)
        # 特征计算
        ground_truth = np.zeros(n_classes, dtype=np.float32)
        ground_truth[label_index] = 1.0
        # label的one-hot编码
        bottlenecks.append(bottleneck)
        ground_truths.append(ground_truth)

    return bottlenecks, ground_truths
已知图片和张量计算特征:get_or_create_bottleneck

这里采用的思想是:
为了节省程序运行时间,每一个图片的特征提取结果都存放在本地的txt文件中,再次运行程序的时候检查是否有对应图片的特征文件,如果有,直接读取存储好的特征信息,否则,从tensor中计算出特征矩阵,然后存放在本地,方便下次直接读取。
当然也可以直接求解后使用。

def get_bottleneck_path(image_lists, label_name, index, category):
    return get_image_path(image_lists, CACHE_DIR, label_name, index, category) + '.txt'


def run_bottleneck_on_image(sess, image_data, image_data_tensor, bottleneck_tensor):
    bottleneck_values = sess.run(bottleneck_tensor, {image_data_tensor:image_data})
    bottleneck_values = np.squeeze(bottleneck_values)

    return bottleneck_values


def get_or_create_bottleneck(sess, image_lists, label_name, index,
                             category, jepg_data_tensor, bottleneck_tensor):
    label_lists = image_lists[label_name]
    sub_dir = label_lists['dir']
    sub_dir_path = os.path.join(CACHE_DIR, sub_dir)  # 将多个路径组合返回
    print("sub_dir_path: ", sub_dir_path)
    if not os.path.exists(sub_dir_path):  # 文件是否存在
        os.makedirs(sub_dir_path)  # 创建
    bottleneck_path = get_bottleneck_path(image_lists, label_name, index, category)
    print("bottleneck_path: ", bottleneck_path)
    if not os.path.exists(bottleneck_path):
        image_path = get_image_path(image_lists, INPUT_DATA, label_name, index, category)
        image_data = gfile.FastGFile(image_path, 'rb').read()
        # 根据文件路径读取图片信息

        bottleneck_values = run_bottleneck_on_image(sess, image_data, jepg_data_tensor, bottleneck_tensor)
        # 图片特征信息提取
        bottleneck_string = ','.join(str(x) for x in bottleneck_values)
        with open(bottleneck_path, 'w') as bottleneck_file:
            bottleneck_file.write(bottleneck_string)
            # 图片特征信息保存。

    else:
        with open(bottleneck_path, 'r') as bottleneck_file:
            bottleneck_string = bottleneck_file.read()
        bottleneck_values = [float(x) for x in bottleneck_string.split(',')]
    return bottleneck_values

最终运行结果:

以上是根据《TensorFlow 实战Google深度学习框架》中的代码给出的解析,鉴于函数嵌套比较多,可读性较差,以下是我自己整理的代码,便于了解主要的处理步骤。

import glob
import tensorflow as tf
import os.path
import numpy as np
import random
from tensorflow.python.platform import gfile

BOTTLENECK_TENSOR_SIZE = 2048
BOTTLENECK_TENSOR_NAME = 'pool_3/_reshape:0'
JPEG_DATA_TENSOR_NAME = 'DecodeJpeg/contents:0'
MODEL_DIR = 'D://python//Inception_dec_2015'
MODEL_FILE = 'tensorflow_inception_graph.pb'
INPUT_DATA = 'D://python//flower_photos//flower_photos'

VALIDATION_PERCENTAGE = 10
TEST_PERCENTAGE = 10
LEARNING_RATE = 0.01
STEPS = 4000
BATCH = 100


def create_image_list(test_percentage, validation_percentage):
    result = {}
    sub_dirs = [x[0] for x in os.walk(INPUT_DATA)]

    is_root_dir = True

    for sub_dir in sub_dirs:
        if is_root_dir:
            is_root_dir = False
            continue
        extensions = ['jpg', 'jpeg', 'JPG', 'JPEG']
        file_list = []
        dir_name = os.path.basename(sub_dir)

        for extionsion in extensions:
            file_glob = os.path.join(INPUT_DATA, dir_name, '*.' + extionsion)
            file_list.extend(glob.glob(file_glob))
        if not file_list: continue

        label_name = dir_name.lower()
        training_images = []
        testing_images = []
        validation_images = []
        for file_name in file_list:
            base_name = os.path.basename(file_name)

            chance = np.random.randint(100)
            if chance < validation_percentage:
                validation_images.append(base_name)
            elif chance < (test_percentage + validation_percentage):
                testing_images.append(base_name)
            else:
                training_images.append(base_name)

        result[label_name] = {
            'dir': dir_name,
            'training': training_images,
            'testing': testing_images,
            'validation': validation_images
        }
    return result


def get_necks(sess, n_classes, image_list, how_many, category, tensor_data, tensor_neck):
    bottlenecks = []
    ground_truths = []
    for _ in range(how_many):
        label_index = random.randrange(n_classes)
        label_name = list(image_list.keys())[label_index]
        image_index = random.randrange(65536)

        label_lists = image_list[label_name]
        category_list = label_lists[category]
        mod_index = image_index % len(category_list)
        image_path = INPUT_DATA + "//" + label_name + "//" + category_list[mod_index]
        # 获得图片路径

        image_data = gfile.FastGFile(image_path, 'rb').read()
        neck_values = sess.run(tensor_neck, {tensor_data: image_data})
        neck_values = np.squeeze(neck_values)
        # 获取特征信息

        ground_truth = np.zeros(n_classes, dtype=np.float32)
        ground_truth[label_index] = 1.0
        bottlenecks.append(neck_values)
        ground_truths.append(ground_truth)

    return bottlenecks, ground_truths


def main():
    image_list = create_image_list(TEST_PERCENTAGE, VALIDATION_PERCENTAGE)
    n_classes = len(image_list.keys())

    with gfile.FastGFile(os.path.join(MODEL_DIR, MODEL_FILE), 'rb') as f:
        graph_def = tf.GraphDef()
        graph_def.ParseFromString(f.read())

    tensor_neck, tensor_data = tf.import_graph_def(graph_def, return_elements=[
        BOTTLENECK_TENSOR_NAME, JPEG_DATA_TENSOR_NAME
    ])

    input_neck = tf.placeholder(tf.float32, [None, BOTTLENECK_TENSOR_SIZE],name='BottleInput')
    input_truth = tf.placeholder(tf.float32, [None, n_classes], name='TruthInput')

    with tf.name_scope('final_training_ops'):
        weights = tf.Variable(tf.truncated_normal(
            [BOTTLENECK_TENSOR_SIZE, n_classes], stddev=0.001
        ))
        biases = tf.Variable(tf.zeros([n_classes]))
        logits = tf.matmul(input_neck, weights) + biases
        final_tensor = tf.nn.softmax(logits)

    cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=input_truth)
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(cross_entropy_mean)

    with tf.name_scope('evaluation'):
        correct_prediction = tf.equal(tf.arg_max(final_tensor, 1),
                                      tf.arg_max(input_truth, 1))
        correct_mean = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    with tf.Session() as sess:
        init = tf.initialize_all_variables()
        sess.run(init)
        for i in range(STEPS):
            train_necks, train_truth = get_necks(
                sess, n_classes, image_list, BATCH, 'training', tensor_data, tensor_neck
            )
            sess.run(train_step, feed_dict={input_neck: train_necks,
                                            input_truth: train_truth})
            if i % 20 == 0 or i + 1 == STEPS:
                valid_necks, valid_truth = get_necks(
                    sess, n_classes, image_list, BATCH, 'validation', tensor_data, tensor_neck
                )
                validation_accuracy = sess.run(correct_mean, feed_dict={
                    input_neck: valid_necks,
                    input_truth: valid_truth
                })
                print('Step %d: Validation accuracy on random sampled %d examples =%.lf%%' %
                      (i, BATCH, validation_accuracy * 100))

        test_necks, test_truth = get_necks(
            sess, n_classes, image_list, BATCH, 'testing', tensor_data, tensor_neck
        )

        test_accuracy = sess.run(correct_mean, feed_dict={input_neck: test_necks,
                                                          input_truth: test_truth})
        print('Final test accuracy = %.lf%%' % (test_accuracy * 100))

main()

虽然代码逻辑较简便,但是时间运行时间增加了很多,这主要是频繁地读取图片信息,计算特征的原因。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容