概述
前言
一直在学习计算机视觉方面的基础知识,前几天接触到了第一个实际的使用CNN来进行图像分类的Kaggle上的猫狗分类的问题,虽然最后效果不太理想,但是个人觉得对于图像分类问题,这个项目的解决过程和方法具有很强的借鉴意义,这里进行一次详细的总结。
项目条件
- ubuntu 18.04 LTS
- tensorflow-gpu
- pycharm
- GTX 1050
- Kaggle 竞赛网址:https://www.kaggle.com/c/dogs-vs-cats (数据集可以从从这个网站里下载)
项目要达到的目的
通过对训练集中的25000张猫狗的照片进行有监督的训练,来预测测试集中12500张图片是小狗的概率。
数据总览
训练集中,有25000张照片,可以看到每一种中要么有一只狗,要么有一只猫,这一点在测试集上也是一样。
通过查看,每张图片的像素值也各有差异。
在测试集中,情况和训练集差不多,只不过就是少了图片的类别标签。
处理数据
神经网络不可能对图片直接处理,当然要先把图片转化为某种数字形式,代码如下,注释我写的很详细:
import tensorflow as tf
import numpy as np
import os
#引入必要的python 模块
def get_all_files(file_path, is_random=True):
"""
获取图片路径及其标签
:param file_path: a sting, 图片所在目录
:param is_random: True or False, 是否乱序
:return:
"""
image_list = [] #图片列表
label_list = [] #标签列表
cat_count = 0
dog_count = 0 #对猫狗的数量进行计数
for item in os.listdir(file_path): #item 表示file_path文件夹里的所有文件,用来遍历
item_path = file_path + '/' + item #item_path用来表示每一张照片的相对路径
item_label = item.split('.')[0] # 文件名形如 cat.0.jpg,只需要取第一个,这里要么是'cat',要么是'dog'
if os.path.isfile(item_path):
image_list.append(item_path) #如果item_path是一个文件的话,就将其加入到列表“image_list”中
else:
raise ValueError('文件夹中有非文件项.') #python raise语句抛出一个指定异常
if item_label == 'cat': # 猫标记为'0'
label_list.append(0)
cat_count += 1
else: # 狗标记为'1'
label_list.append(1)
dog_count += 1
print('数据集中有%d只猫,%d只狗.' % (cat_count, dog_count))
image_list = np.asarray(image_list) #python asarray函数可以把列表转化为ndarray类型的数据
label_list = np.asarray(label_list)
# 乱序文件
if is_random:
rnd_index = np.arange(len(image_list)) #生成一个从1到25000的ndarray的数组
np.random.shuffle(rnd_index) #把rnd_index随机洗牌
image_list = image_list[rnd_index]
label_list = label_list[rnd_index]
return image_list, label_list #返回图片的路径列表和标签列表
def get_test_files(file_path):
'''
因为test文件没有标签,而且为了后面生成按照1-12500 顺序排序的文件,所以这里另写一个针对test文件的函数
'''
image_list = [] #图片列表
label_list = [] # 标签列表,虽然没有标签,但是为了在处理时保持统一,还是有加上
for i in range(12500): #这里i为(0 -- 12499 )
num = i+1 #这里num为(1 -- 12500 )
item_path = file_path+'/'+str(num)+'.jpg'
# 这里item_path通过字符串拼接的方式,得到每一个图片文件的相对路径
if os.path.isfile(item_path):
image_list.append(item_path)
label_list.append(0)
else:
raise ValueError('文件夹中有非文件项.') #python raise语句刨出一个指定异常
image_list = np.asarray(image_list) #python asarray函数可以把列表转化为ndarray类型的数据
label_list = np.asarray(label_list)
return image_list,label_list #返回图片的路径列表和标签列表
def get_batch(train_list, image_size, batch_size, capacity, is_random=True):
"""
获取训练批次
:param train_list: 2-D list, [image_list, label_list]
:param image_size: a int, 训练图像大小
:param batch_size: a int, 每个批次包含的样本数量
:param capacity: a int, 队列容量
:param is_random: True or False, 是否乱序
:return:
"""
intput_queue = tf.train.slice_input_producer(train_list, shuffle=False)
#这个函数是用来生成文件名队列,
# 从路径中读取图片
image_train = tf.read_file(intput_queue[0]) #要使用read_file函数来读取文件名队列中的文件
image_train = tf.image.decode_jpeg(image_train, channels=3) # 这里是jpg格式,返回值格式为:uint8
#图像在存储时并不是直接记录这些矩阵中的数字,而是记录经过压缩编码之后的结果。所以要将一张图象还原成一个三维矩阵。需要解码的过程,这里的函数实现的就是这个功能
image_train = tf.image.resize_images(image_train, [image_size, image_size]) #这个函数的第一个输入值是原始图像,第二个是要转换成的图像大小
image_train = tf.cast(image_train, tf.float32) / 255. # 转换数据类型并归一化
#tf.cast()函数的作用是执行 tensorflow 中张量数据类型转换,比如读入的图片如果是int8类型的,一般在要在训练前把图像的数据格式转换为float32
# 图片标签
label_train = intput_queue[1]
# 获取批次
if is_random:
image_train_batch, label_train_batch = tf.train.shuffle_batch([image_train, label_train],
batch_size=batch_size,
capacity=capacity,
min_after_dequeue=100,
num_threads=2)
else:
image_train_batch, label_train_batch = tf.train.batch([image_train, label_train],
batch_size=1,
capacity=capacity,
num_threads=1)
return image_train_batch, label_train_batch
if __name__ == '__main__':
import matplotlib.pyplot as plt
# 测试图片读取
image_dir = 'test'
train_list = get_test_files(image_dir)
image_train_batch, label_train_batch = get_batch(train_list, 256, 1, 200, False)
# image_train_batch是shape=(1, 256, 256, 3)的张量,label_train_batch是shape=(1,)的张量
sess = tf.Session()
coord = tf.train.Coordinator() #创建一个线程协调器,用来管理之后在Session中启动的所有线程
threads = tf.train.start_queue_runners(sess=sess, coord=coord) #启动入队线程,由多个或单个线程,按照设定规则,把文件读入Filename Queue中
try:
for step in range(10):
if coord.should_stop():
break
image_batch, label_batch = sess.run([image_train_batch, label_train_batch])
if label_batch[0] == 0:
label = 'Cat'
else:
label = 'Dog'
plt.imshow(image_batch[0]), plt.title(label)
plt.show()
except tf.errors.OutOfRangeError:
print('Done.')
finally:
coord.request_stop()
coord.join(threads=threads)
sess.close()
卷积模型
import tensorflow as tf
import tensorflow.contrib.layers as layers
#在tf.contrib.layers内部,有许多产生layer操作及其相关权重和偏差变量的函数。这些大部分都是用来构建不同深度学习架构的
def inference(images, n_classes): #images 是256*256*3,n_classes代表类别的数量
# conv1, shape = [kernel_size, kernel_size, channels, kernel_numbers]
with tf.variable_scope("conv1",reuse=tf.AUTO_REUSE) as scope: #变量作用域
weights = tf.get_variable("weights",
shape=[3, 3, 3, 16],
dtype=tf.float32,
initializer=tf.truncated_normal_initializer(stddev=0.1, dtype=tf.float32))
biases = tf.get_variable("biases",
shape=[16],
dtype=tf.float32,
initializer=tf.constant_initializer(0.1))
conv = tf.nn.conv2d(images, weights, strides=[1, 1, 1, 1], padding="SAME")
pre_activation = tf.nn.bias_add(conv, biases)
conv1 = tf.nn.relu(pre_activation, name="conv1")
# pool1 && norm1
with tf.variable_scope("pooling1_lrn",reuse=tf.AUTO_REUSE) as scope:
pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
padding="SAME", name="pooling1")
norm1 = tf.nn.lrn(pool1, depth_radius=4, bias=1.0, alpha=0.001/9.0,
beta=0.75, name='norm1') #LRN对局部神经元的活动创建竞争机制,使得响应比较大的值相对更大,比较小的值就会更小。提高了模型的泛化能力。
# conv2
with tf.variable_scope("conv2",reuse=tf.AUTO_REUSE) as scope:
weights = tf.get_variable("weights",
shape=[3, 3, 16, 16],
dtype=tf.float32,
initializer=tf.truncated_normal_initializer(stddev=0.1, dtype=tf.float32))
biases = tf.get_variable("biases",
shape=[16],
dtype=tf.float32,
initializer=tf.constant_initializer(0.1))
conv = tf.nn.conv2d(norm1, weights, strides=[1, 1, 1, 1], padding="SAME")
pre_activation = tf.nn.bias_add(conv, biases)
conv2 = tf.nn.relu(pre_activation, name="conv2")
# pool2 && norm2
with tf.variable_scope("pooling2_lrn",reuse=tf.AUTO_REUSE) as scope:
pool2 = tf.nn.max_pool(conv2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
padding="SAME", name="pooling2")
norm2 = tf.nn.lrn(pool2, depth_radius=4, bias=1.0, alpha=0.001/9.0,
beta=0.75, name='norm2')
# full-connect1
with tf.variable_scope("fc1",reuse=tf.AUTO_REUSE) as scope:
reshape = layers.flatten(norm2)
#对于输入的P,将每一个example展开为1-D的Tensor, 但是依然保留batch-size。它返回一个[batch_size, k]的Tensor。
#通常在CNN的最后一步连接到Fully Connected 的网络之前会将其展开,例如CNN的conv层输出的tensor的shape为[batch_size, height, width, channel],
# 刚展开会就是[batch_size, height * width * channel]。
dim = reshape.get_shape()[1].value #reshape[0]是batch
weights = tf.get_variable("weights",
shape=[dim, 128],
dtype=tf.float32,
initializer=tf.truncated_normal_initializer(stddev=0.005, dtype=tf.float32))
biases = tf.get_variable("biases",
shape=[128],
dtype=tf.float32,
initializer=tf.constant_initializer(0.1))
fc1 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name="fc1")
#
# full_connect2
with tf.variable_scope("fc2",reuse=tf.AUTO_REUSE) as scope:
weights = tf.get_variable("weights",
shape=[128, 128],
dtype=tf.float32,
initializer=tf.truncated_normal_initializer(stddev=0.005, dtype=tf.float32))
biases = tf.get_variable("biases",
shape=[128],
dtype=tf.float32,
initializer=tf.constant_initializer(0.1))
fc2 = tf.nn.relu(tf.matmul(fc1, weights) + biases, name="fc2")
# softmax
with tf.variable_scope("softmax_linear",reuse=tf.AUTO_REUSE) as scope:
weights = tf.get_variable("weights",
shape=[128, n_classes],
dtype=tf.float32,
initializer=tf.truncated_normal_initializer(stddev=0.005, dtype=tf.float32))
biases = tf.get_variable("biases",
shape=[n_classes],
dtype=tf.float32,
initializer=tf.constant_initializer(0.1))
softmax_linear = tf.add(tf.matmul(fc2, weights), biases, name="softmax_linear")
#softmax_linear = tf.nn.softmax(softmax_linear)
#softmax_linear是[8,2]的矩阵
return softmax_linear
def losses(logits, labels): #计算损失值
with tf.variable_scope('loss'):
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,
labels=labels)
#这里的交叉熵函数的输入值不是经过softmax函数处理过的,是原始数据
loss = tf.reduce_mean(cross_entropy)
return loss
def evaluation(logits, labels):
with tf.variable_scope("accuracy"):
correct = tf.nn.in_top_k(logits, labels, 1)
#这里解释一下,tf.nn.in_top_k方法的作用,例如logits=[[0.1,0.9],[0.6,0.4]],labels=[1,1],对于logits来说,其每个元素的最大值的索引为[1,0],所以最后的结果
#为[true,false],如果0代表dog,1代表cat,预测的结果也就是[cat,dog],实际为[cat,cat],那么返回值也就是[true,falase]
correct = tf.cast(correct, tf.float16)
# cast方法可以把一组bool类型的张量转化为形如[0,1,1,1]类型的张量
accuracy = tf.reduce_mean(correct)
#reduce_mean的作用是用来求平均值,这里省掉了一个参数axis,当axis=1时是按行进行计算,是0的时候就按列进行计算
return accuracy
训练和评估
import time
from input_data import *
from model import *
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
# 训练模型
def training():
N_CLASSES = 2 #分类的类别数量,这里是狗和猫
IMG_SIZE = 208 #需要把图片转化为的大小
BATCH_SIZE = 8 #每批处理的数量
CAPACITY = 200 # 队列容量
MAX_STEP = 10000 #循环的步数
LEARNING_RATE = 1e-4
# 测试图片读取
image_dir = 'train' #训练集所在的目录
logs_dir = 'logs_1' # 检查点保存路径,这里是用来保存训练完成以后的各个参数
sess = tf.Session()
train_list = get_all_files(image_dir, True) #返回图片的路径列表和标签列表
image_train_batch, label_train_batch = get_batch(train_list, IMG_SIZE, BATCH_SIZE, CAPACITY, True)
# image_train_batch是shape=(8, 256, 256, 3)的张量,label_train_batch是shape=(8,)的张量
train_logits = inference(image_train_batch, N_CLASSES) #返回一个[8,2]的矩阵
train_loss = losses(train_logits, label_train_batch) #这里train_loss是一个标量
train_acc = evaluation(train_logits, label_train_batch) #准确率
train_op = tf.train.AdamOptimizer(LEARNING_RATE).minimize(train_loss) #使用adam优化来训练模型,减少loss
var_list = tf.trainable_variables() #表示可以训练的参数的数量
paras_count = tf.reduce_sum([tf.reduce_prod(v.shape) for v in var_list])
#reduce_sum 表示对张量进行求和
print('参数数目:%d' % sess.run(paras_count), end='nn') #计算参数的数量
saver = tf.train.Saver() #实例化一个Saver()类,用来进行参数的保存和读取
sess.run(tf.global_variables_initializer())
coord = tf.train.Coordinator() #创建一个线程协调器
threads = tf.train.start_queue_runners(sess=sess, coord=coord) #把文件名队列推进真正的内存队列
s_t = time.time()
try:
for step in range(MAX_STEP):
if coord.should_stop():
break
_, loss, acc = sess.run([train_op, train_loss, train_acc])
#这里 train_op 的返回值没有意义,所以并不需要,没有意义的值以’_‘来代替。
tl=sess.run(train_logits)
if step % 100 == 0: # 实时记录训练过程并显示
runtime = time.time() - s_t
print('train_logits:' + str(tl))
print('Step: %6d, loss: %.8f, accuracy: %.2f%%, time:%.2fs, time left: %.2fhours'
% (step, loss, acc * 100, runtime, (MAX_STEP - step) * runtime / 360000))
s_t = time.time()
if step % 1000 == 0 or step == MAX_STEP - 1: # 保存检查点
checkpoint_path = os.path.join(logs_dir, 'model.ckpt') #将多个路径进行组合
saver.save(sess, checkpoint_path, global_step=step)
# global_step 把训练的step作为文件名的后追加在后面。
except tf.errors.OutOfRangeError:
print('Done.')
finally:
coord.request_stop()
coord.join(threads=threads)
sess.close()
# 测试检查点
def eval():
N_CLASSES = 2
IMG_SIZE = 208
BATCH_SIZE = 1
CAPACITY = 200
MAX_STEP = 12500
test_dir = 'test'
logs_dir = 'logs_1' # 检查点目录
sess = tf.Session()
test_list = get_test_files(test_dir)
image_test_batch, label_test_batch = get_batch(test_list, IMG_SIZE, BATCH_SIZE, CAPACITY, False)
test_logits = inference(image_test_batch, N_CLASSES)
test_logits = tf.nn.softmax(test_logits) # 用softmax转化为百分比数值
# 载入检查点
saver = tf.train.Saver()
print('n载入检查点...')
ckpt = tf.train.get_checkpoint_state(logs_dir)
if ckpt and ckpt.model_checkpoint_path:
# print(ckpt.model_checkpoint_path)
global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]
# 这里的global_step是最大的那个数。
saver.restore(sess, ckpt.model_checkpoint_path)
print('载入成功,global_step = %sn' % global_step)
else:
print('没有找到检查点')
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
dogOrCat = []
try:
for step in range(MAX_STEP): #这里一次只能处理10张,一次循环处理一张
if coord.should_stop():
break
prediction = sess.run(test_logits)
dogOrCat.append(prediction[0][1])
if (step % 100 == 0):
print('%d images have been dealed with!'%step)
except tf.errors.OutOfRangeError:
print('Done.')
finally:
coord.request_stop()
coord.join(threads=threads)
sess.close()
return dogOrCat
if __name__ == '__main__':
training()
result = eval()
name = ['label']
test = pd.DataFrame(columns=name, data=result)
test.to_csv('testcsv.csv', encoding='utf-8')
最后
以上就是甜甜野狼为你收集整理的以Kaggle中猫狗大战为例,总结图像分类问题的一般步骤的全部内容,希望文章能够帮你解决以Kaggle中猫狗大战为例,总结图像分类问题的一般步骤所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复