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%
'''