(详细过程)使用Inception V3模型完成迁移学习--实现花朵分类

1.

TensorFlow提供了完成迁移学习所需要的数据集。该数据集可以在http://download.tensorflow.org/example_images/flower_photos.tgz下载。每个子文件夹都存储了一种类别的花的图片,子文件夹的名称就是花的类别的名称。

平均每一种花有734张图片,图片都是RGB色彩模式的。这些图片的大小不一,inception V3模型能够容忍这些大小不一的图片。

可以通过以下网址下载打包的inception V3模型。

https://storage.googleapis.com/download.tensorflow.org/models/inception_dec_2015.zip

第一个文件的内容是该模型在ILSVRC大赛中解决1000分类问题时的所有类别,我们不需要这个文件;最后一个tensorflow_inception_graph.pb文件是我们所需要的模型文件。

 2.flower_photos_dispose.py

flower_photos_dispose.py主要完成和图片处理相关的内容,比如将图片名整理成一个字典、获得并返回图片的路径以及计算得到特征向量等。

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

input_data = "D:/Study/InceptionV3_flower_classfiy/flower_photos"
#特征向量文件的保存路径(将原始图像通过inception V3计算得到的特征向量保存到文件中,可以避免重复计算)
CACHE_DIR = "D:/Study/InceptionV3_flower_classfiy/bottleneck"


def create_image_dict():
    '''
    从数据文件夹中读取所有图片名并组织成列表的形式,随后按训练、验证和测试集分开。
    在将图片按训练、验证和测试集分开的时候,是根据随机得到的一个分数值判断这个图片被分到哪一类数据。
    这带有一定的偶然性,并不能确保有多少张图片属于某一个数据集。
    '''
    result = {}
    # path是flower_photos文件夹的路径,同时也包含了其子文件夹的路径
    # directory的数据形式为一个列表,打印其内容为:
    # /flower_photos,/flower_photos/daisy,
    # /flower_photos/tulips, flower_photos/roses,
    # /flower_photos/dandelion, /flower_photos/sunflowers
    path_list = [x[0] for x in os.walk(input_data)]
    is_root_dir = True
    for sub_dirs in path_list:
        if is_root_dir:
            is_root_dir = False
            continue  # continue会跳出当前循环执行下一轮的循环

        # extension_name列表列出了图片文件可能的扩展名
        extension_name = ['jpg', 'jpeg', 'JPG', 'JPEG']
        # 创建保存图片文件名的列表
        images_list = []
        for extension in extension_name:
            # join()函数用于拼接路径,用extension_name列表中的元素作为后缀名,比如:
            # /flower_photos/daisy/*.jpg
            # /flower_photos/daisy/*.jpeg
            # /flower_photos/daisy/*.JPG
            # /flower_photos/daisy/*.JPEG
            file_glob = os.path.join(sub_dirs, '*.' + extension)

            # 使用glob()函数获取满足正则表达式的文件名,例如对于
            # /flower_photos/daisy/*.jpg,glob()函数会得到该路径下
            # 所有后缀名为.jpg的文件,例如下面这个例子:
            # /flower_photos/daisy/7924174040_444d5bbb8a.jpg
            images_list.extend(glob.glob(file_glob))

        # basename()函数会舍弃一个文件名中保存的路径,比如对于
        # /flower_photos/daisy,其结果是仅仅保留daisy
        # flower_category就是图片的类别,这个类别通过子文件夹名获得
        dir_name = os.path.basename(sub_dirs)
        flower_category = dir_name

        # 初始化每个类别的flower photos对应的训练集图片名列表、测试集图片名列表
        # 和验证集图片名列表
        training_images = []
        testing_images = []
        validation_images = []

        for image_name in images_list:
            # 对于images_name列表中的图片文件名,它也包含了路径名,但我们不需要
            # 路径名所以这里使用basename()函数获取文件名
            image_name = os.path.basename(image_name)
            # random.randint()函数产生均匀分布的整数
            score = np.random.randint(100)
            if score < 10:
                validation_images.append(image_name)
            elif score < 20:
                testing_images.append(image_name)
            else:
                training_images.append(image_name)

        # 每执行一次最外层的循环,都会刷新一次result,result是一个字典,
        # 它的key为flower_category,它的value也是一个字典,以数据集分类的形式存储了
        # 所有图片的名称,最后函数将result返回
        result[flower_category] = {
            "dir": dir_name,
            "training": training_images,
            "testing": testing_images,
            "validation": validation_images,
        }
    return result


def get_image_path(image_lists, image_dir, flower_category, image_index, data_category):
    '''
    根据传递进来的参数返回一个带路径的图片名。
    '''
    # category_list用列表的形式保存了某一类花的某一个数据集的内容,
    # 其中参数flower_category从函数get_random_bottlenecks()传递过来
    category_list = image_lists[flower_category][data_category]

    # actual_index是一个图片在category_list列表中的位置序号
    # 其中参数image_index也是从函数get_random_bottlenecks()传递过来
    actual_index = image_index % len(category_list)

    # image_name就是图片的文件名
    image_name = category_list[actual_index]

    # sub_dir得到flower_photos中某一类花所在的子文件夹名
    sub_dir = image_lists[flower_category]["dir"]

    # 拼接路径,这个路径包含了文件名,最终返回给create_bottleneck()函数
    # 作为每一个图片对应的特征向量的文件
    full_path = os.path.join(image_dir, sub_dir, image_name)
    return full_path


def create_bottleneck(sess, image_lists, flower_category, image_index,
                      data_category, jpeg_data_tensor, bottleneck_tensor):
    '''
    获取一张图片经过inception V3模型处理之后的特征向量。
    在获取特征向量的时候,先在CACHR_DIR路径下寻找已经计算且保存下来的特征向量并读取,
    将其内容作为列表返回;如果找不到该文件则通过inception V3模型计算特征向量,然后将计算
    得到的特征向量保存到文件(.txt),最后返回计算得到的特征向量列表。
    '''
    # sub_dir得到的是flower_photos下某一类花的文件夹名,这类花由
    # flower_photos参数确定,花的文件夹名由dir参数确定
    sub_dir = image_lists[flower_category]["dir"]

    # 拼接路径,路径名就是在CACHE_DIR路径的基础上加上sub_dir
    sub_dir_path = os.path.join(CACHE_DIR, sub_dir)

    # 判断拼接出的路径是否存在,如果不存在,则在CACHE_DIR下创建相应的子文件夹
    if not os.path.exists(sub_dir_path):
        os.makedirs(sub_dir_path)

    # 获取一张图片对应的特征向量的全名,这个全名包括了路径名,而且会在图片的.jpg后面
    # 用.txt作为后缀,获取没有.txt缀的文件名使用了get_image_path()函数,
    # 该函数会返回带路径的图片名
    bottleneck_path = get_image_path(image_lists, CACHE_DIR, flower_category,
                                     image_index, data_category) + ".txt"

    # 如果指定名称的特征向量文件不存在,则通过InceptionV3模型计算得到该特征向量
    # 计算的结果也会存入文件
    if not os.path.exists(bottleneck_path):
        # 获取原始的图片名,这个图片名包含了原始图片的完整路径
        image_path = get_image_path(image_lists, input_data, flower_category,
                                    image_index, data_category)
        # 读取图片的内容
        image_data = gfile.FastGFile(image_path, "rb").read()

        # 将当前图片输入到InceptionV3模型,并计算瓶颈张量的值,所得瓶颈张量的值
        # 就是这张图片的特征向量,但是得到的特征向量是四维的,所以还需要通过squeeze()
        # 函数压缩成一维的,以方便作为全连层的输入
        bottleneck_values = sess.run(bottleneck_tensor, feed_dict={jpeg_data_tensor: image_data})
        bottleneck_values = np.squeeze(bottleneck_values)

        # 将计算得到的特征向量存入文件,存入文件前需要为两个值之间加入逗号作为分隔
        # 这样可以方便从文件读取数据时的解析过程
        bottleneck_string = ','.join(str(x) for x in bottleneck_values)
        with open(bottleneck_path, "w") as bottleneck_file:
            bottleneck_file.write(bottleneck_string)
    else:
        # else是特征向量文件已经存在的情况,此时会直接从bottleneck_path获取
        # 特征向量数据
        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


def get_random_bottlenecks(sess, num_classes, image_lists, batch_size, data_category, jpeg_data_tensor,
                           bottleneck_tensor):
    #随机产生一个batch的特征向量及其对应的labels

    # 定义bottlenecks用于存储得到的一个batch的特征向量
    # 定义labels用于存储这个batch的label标签
    bottlenecks = []
    labels = []

    for i in range(batch_size):
        # random_index是从五个花类中随机抽取的类别编号
        # image_lists.keys()的值就是五种花的类别名称
        random_index = random.randrange(num_classes)
        flower_category = list(image_lists.keys())[random_index]

        # image_index就是随机抽取的图片的编号,在get_image_path()函数中
        # 我们会看到如何通过这个图片编号和random_index确定类别找到图片的文件名
        image_index = random.randrange(65536)

        # 调用get_or_create_bottleneck()函数获取或者创建图片的特征向量
        # 这个函数调用了get_image_path()函数
        bottleneck = create_bottleneck(sess, image_lists, flower_category, image_index,
                                       data_category, jpeg_data_tensor, bottleneck_tensor)

        # 首先生成每一个标签的答案值,再通过append()函数组织成一个batch列表
        # 函数将完整的列表返回
        label = np.zeros(num_classes, dtype=np.float32)
        label[random_index] = 1.0
        labels.append(label)
        bottlenecks.append(bottleneck)
    return bottlenecks, labels



def get_test_bottlenecks(sess, image_lists, num_classes, jpeg_data_tensor, bottleneck_tensor):
    '''
    获取全部的测试数据。
    用两个for循环遍历所有用于测试的花名,并根据create_bottlenecks()函数获取特征向量数据
    '''
    bottlenecks = []
    labels = []

    #flower_category_list是image_lists中键的列表,打印出来就是这样:
    #['roses', 'sunflowers', 'daisy', 'dandelion', 'tulips']
    flower_category_list = list(image_lists.keys())

    data_category = "testing"

    #枚举所有的类别和每个类别中的测试图片
    #在外层的for循环中,label_index是flower_category_list列表中的元素下标
    #flower_category就是该列表中的值
    for label_index, flower_category in enumerate(flower_category_list):

        #在内层的for循环中,通过flower_category和"testing"枚举image_lists中每一类花中
        #用于测试的花名,得到的名字就是unused_base_name,但我们只需要image_index
        for image_index, unused_base_name in enumerate(image_lists[flower_category]
                                                                        ["testing"]):

            #调用create_bottleneck()函数创建特征向量,因为在进行训练或验证的过程中
            #用于测试的图片并没有生成相应的特征向量,所以这里要一次性全部生成
            bottleneck = create_bottleneck(sess, image_lists, flower_category,
                                                    image_index,data_category,
                                          jpeg_data_tensor, bottleneck_tensor)

            #接下来就和get_random_bottlenecks()函数相同了
            label = np.zeros(num_classes, dtype=np.float32)
            label[label_index] = 1.0
            labels.append(label)
            bottlenecks.append(bottleneck)
    return bottlenecks, labels

2.InceptionV3.py

InceptionV3.py文件实现了模型文件读取以及增加一层全连层之后的训练、验证、测试的过程。

在这里将会调用flower_photos_dispose.py文件中的一些函数生成用于训练或验证的一个batch的数据或者生产用于测试的数据。

import tensorflow as tf
import os
import flower_photos_dispose as fd
from tensorflow.python.platform import gfile

model_path = "D:/Study/InceptionV3_flower_classfiy"
model_file = "tensorflow_inception_graph.pb"

num_steps = 4000
BATCH_SIZE = 100

bottleneck_size = 2048  # InceptionV3模型瓶颈层的节点个数

# 调用create_image_lists()函数获得该函数返回的字典
image_lists = fd.create_image_dict()
num_classes = len(image_lists.keys())  # num_classes=5,因为有5类

# 读取已经训练好的Inception-v3模型。
with gfile.FastGFile(os.path.join(model_path, model_file), 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())

# 使用import_graph_def()函数加载读取的InceptionV3模型后会返回
# 图像数据输入节点的张量名称以及计算瓶颈结果所对应的张量,函数原型为
# import_graph_def(graph_def,input_map,return_elements,name,op_dict,producer_op_list)
bottleneck_tensor, jpeg_data_tensor = tf.import_graph_def(graph_def,
                                                          return_elements=["pool_3/_reshape:0",
                                                                           "DecodeJpeg/contents:0"])

x = tf.placeholder(tf.float32, [None, bottleneck_size], name='BottleneckInputPlaceholder')
y_ = tf.placeholder(tf.float32, [None, num_classes], name='GroundTruthInput')

# 定义一层全连接层
with tf.name_scope("final_training_ops"):
    weights = tf.Variable(tf.truncated_normal([bottleneck_size, num_classes], stddev=0.001))
    biases = tf.Variable(tf.zeros([num_classes]))
    logits = tf.matmul(x, weights) + biases
    final_tensor = tf.nn.softmax(logits)

# 定义交叉熵损失函数以及train_step使用的随机梯度下降优化器
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y_)
cross_entropy_mean = tf.reduce_mean(cross_entropy)
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy_mean)

# 定义计算正确率的操作
correct_prediction = tf.equal(tf.argmax(final_tensor, 1), tf.argmax(y_, 1))
evaluation_step = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

with tf.Session() as sess:
    init = tf.global_variables_initializer()
    sess.run(init)
    for i in range(num_steps):
        # 使用get_random_bottlenecks()函数产生训练用的随机的特征向量数据及其对应的label
        # 在run()函数内开始训练的过程
        train_bottlenecks, train_labels = fd.get_random_bottlenecks(sess, num_classes,
                                                                    image_lists, BATCH_SIZE,
                                                                    "training",
                                                                    jpeg_data_tensor, bottleneck_tensor)
        sess.run(train_step, feed_dict={x: train_bottlenecks, y_: train_labels})

        # 进行相关的验证,同样是使用get_random_bottlenecks()函数产生随机的特征向量及其
        # 对应的label
        if i % 100 == 0:
            validation_bottlenecks, validation_labels = fd.get_random_bottlenecks(sess,
                                                                                  num_classes, image_lists,
                                                                                  BATCH_SIZE, "validation",
                                                                                  jpeg_data_tensor, bottleneck_tensor)
            validation_accuracy = sess.run(evaluation_step, feed_dict={
                x: validation_bottlenecks,
                y_: validation_labels})
            print("Step %d: Validation accuracy = %.1f%%" % (i, validation_accuracy * 100))

    # 在最后的测试数据上测试正确率,这里调用的是get_test_bottlenecks()函数,返回
    # 所有图片的特征向量作为特征数据
    test_bottlenecks, test_labels = fd.get_test_bottlenecks(sess, image_lists, num_classes,
                                                            jpeg_data_tensor, bottleneck_tensor)
    test_accuracy = sess.run(evaluation_step, feed_dict={x: test_bottlenecks,
                                                         y_: test_labels})
    print("Finally test accuracy = %.1f%%" % (test_accuracy * 100))

'''
Step 0: Validation accuracy = 46.0%
Step 100: Validation accuracy = 81.0%
Step 200: Validation accuracy = 93.0%
Step 300: Validation accuracy = 89.0%
Step 400: Validation accuracy = 83.0%
Step 500: Validation accuracy = 84.0%
Step 600: Validation accuracy = 90.0%
Step 700: Validation accuracy = 90.0%
Step 800: Validation accuracy = 93.0%
Step 900: Validation accuracy = 91.0%
Step 1000: Validation accuracy = 87.0%
Step 1100: Validation accuracy = 92.0%
Step 1200: Validation accuracy = 93.0%
Step 1300: Validation accuracy = 93.0%
Step 1400: Validation accuracy = 84.0%
Step 1500: Validation accuracy = 92.0%
Step 1600: Validation accuracy = 90.0%
Step 1700: Validation accuracy = 83.0%
Step 1800: Validation accuracy = 89.0%
Step 1900: Validation accuracy = 95.0%
Step 2000: Validation accuracy = 91.0%
Step 2100: Validation accuracy = 91.0%
Step 2200: Validation accuracy = 94.0%
Step 2300: Validation accuracy = 93.0%
Step 2400: Validation accuracy = 90.0%
Step 2500: Validation accuracy = 94.0%
Step 2600: Validation accuracy = 94.0%
Step 2700: Validation accuracy = 88.0%
Step 2800: Validation accuracy = 93.0%
Step 2900: Validation accuracy = 88.0%
Step 3000: Validation accuracy = 88.0%
Step 3100: Validation accuracy = 95.0%
Step 3200: Validation accuracy = 95.0%
Step 3300: Validation accuracy = 93.0%
Step 3400: Validation accuracy = 90.0%
Step 3500: Validation accuracy = 86.0%
Step 3600: Validation accuracy = 93.0%
Step 3700: Validation accuracy = 89.0%
Step 3800: Validation accuracy = 96.0%
Step 3900: Validation accuracy = 95.0%
Finally test accuracy = 94.6%
'''

 

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值