最近在尝试学习使用卷积神经网络对猫狗进行识别,准备猫、狗图片各500张图片,图片是大小不一的彩色图片。
大致的实现思路:
- 各准备猫、狗500张图片,并对其命名“cat.XXX.jpg”、“dog.XXX.jpg”,计划20%猫、20%狗作为验证,剩下做训练;
- 图片大小归一化:使用OpenCV3把图片归一为64*64的彩色图片;
- 读取图片内容(numpy数组,batchszie*64*64*3)、标签值(0、1)、图片名(cat.XXX.jpg、dog.XXX.jpg)、类别名(cat、dog)
- 设置batchsize、权重、偏置、全连接特征数等参数;
- 图像输入(?,64,64,3)---->conv1(?,64,64,3)---->pool1(?,32,32,32)---->conv2(?,32,32,32)---->pool2(?,16,16,32)---->conv3(?,16,16,64)---->pool3(?,8,8,64),输出值转置成一维数组(?,4096)---->FC1(?,1024)---->FC(?,2)
- 保存训练好的模型,以便后续预测使用。
好了,如下是实现的代码,训练模型:
import dataset
import tensorflow as tf
import time
from datetime import timedelta
import math
import random
import numpy as np
# 确定好随机种子,以后每次随机结果相同。在每次执行代码时,使每次切分后的训练集、验证集输入结果相同,便于查找问题。
from numpy.random import seed
seed(10)
from tensorflow import set_random_seed
set_random_seed(20)
# 一次迭代32张图像
batch_size = 32
# 指定类别
classes = ['dogs', 'cats']
num_classes = len(classes)
# 判断神经网络是否出现过拟合。80%做训练,20%做测试
validation_size = 0.2
# 输入图像大小都要一样,图像:h=w=64
img_size = 64
# 彩色图,3通道
num_channels = 3
# 指定数据位置文件夹
train_path = 'training_data'
# 使用OpenCV加载所有的训练和验证图片、图片标签在内存中
data = dataset.read_train_sets(train_path, img_size, classes, validation_size=validation_size)
print("Complete reading input data. Will now print a snippet of it")
print("Number of files in Training-set:\t\t{}".format(len(data.train.labels)))
print("Number of files in Validation-set:\t{}".format(len(data.valid.labels)))
session = tf.Session()
x = tf.placeholder(tf.float32, shape=[None, img_size, img_size, num_channels], name='x')
y_true = tf.placeholder(tf.float32, shape=[None, num_classes], name='y_true')
y_true_cls = tf.argmax(y_true, axis=1)
# 网络参数
filter_size_conv1 = 3
num_filters_conv1 = 32
filter_size_conv2 = 3
num_filters_conv2 = 32
filter_size_conv3 = 3
num_filters_conv3 = 64
# 常用的参数:1024、4096、256、512
fc_layer_size = 1024
# 权重参数
def create_weights(shape):
return tf.Variable(tf.truncated_normal(shape, stddev=0.05))
# 偏置参数
def create_biases(size):
return tf.Variable(tf.constant(0.05, shape=[size]))
def create_convolutional_layer(input, num_input_channels, conv_filter_size, num_filters):
# 3 3 3 32
weights = create_weights(shape=[conv_filter_size, conv_filter_size, num_input_channels, num_filters])
biases = create_biases(num_filters)
# 卷积层
layer = tf.nn.conv2d(input=input, filter=weights, strides=[1, 1, 1, 1], padding='SAME')
layer += biases
layer = tf.nn.relu(layer)
layer = tf.nn.max_pool(value=layer, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
return layer
def create_flatten_layer(layer):
# layer形状:[? 8 8 64],?代表batchsize
layer_shape = layer.get_shape()
# layer_shape[1:4](可以取到1,不可以取到4)拿到后三维的元素个数(8*8*64)
num_features = layer_shape[1:4].num_elements()
# 把卷积得到的立方体特征值layer拉长,-1代表batchsize
layer = tf.reshape(layer, [-1, num_features])
return layer
def create_fc_layer(input, num_inputs,
num_outputs, use_relu=True):
# 定义训练权重、偏置
weights = create_weights(shape=[num_inputs, num_outputs])
biases = create_biases(num_outputs)
layer = tf.matmul(input, weights) + biases
# 选择保留70%的点,防止过拟合
layer = tf.nn.dropout(layer, keep_prob=0.7)
if use_relu:
layer = tf.nn.relu(layer)
return layer
layer_conv1 = create_convolutional_layer(input=x, num_input_channels=num_channels,
conv_filter_size=filter_size_conv1,
num_filters=num_filters_conv1)
layer_conv2 = create_convolutional_layer(input=layer_conv1, num_input_channels=num_filters_conv1,
conv_filter_size=filter_size_conv2,
num_filters=num_filters_conv2)
layer_conv3 = create_convolutional_layer(input=layer_conv2, num_input_channels=num_filters_conv2,
conv_filter_size=filter_size_conv3,
num_filters=num_filters_conv3)
layer_flat = create_flatten_layer(layer_conv3)
layer_fc1 = create_fc_layer(input=layer_flat, num_inputs=layer_flat.get_shape()[1:4].num_elements(),
num_outputs=fc_layer_size, use_relu=True)
layer_fc2 = create_fc_layer(input=layer_fc1, num_inputs=fc_layer_size,
num_outputs=num_classes, use_relu=False)
y_pred = tf.nn.softmax(layer_fc2, name='y_pred')
y_pred_cls = tf.argmax(y_pred, axis=1)
session.run(tf.global_variables_initializer())
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=layer_fc2, labels=y_true)
cost = tf.reduce_mean(cross_entropy)
optimizer = tf.train.AdamOptimizer(learning_rate=1e-4).minimize(cost)
correct_prediction = tf.equal(y_pred_cls, y_true_cls)
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
session.run(tf.global_variables_initializer())
def show_progress(epoch, feed_dict_train, feed_dict_validate, val_loss, i):
acc = session.run(accuracy, feed_dict=feed_dict_train)
val_acc = session.run(accuracy, feed_dict=feed_dict_validate)
msg = "Training Epoch {0}---- iterations: {1}---- Training Accuracy: {2:>6.1%}, Validation Accuracy: {3:>6.1%}, Validation Loss: {4:.3f}"
print(msg.format(epoch + 1, i, acc, val_acc, val_loss))
total_iterations = 0
# 保存、读训练好的模型
saver = tf.train.Saver()
def train(num_iteration):
global total_iterations
for i in range(total_iterations, total_iterations + num_iteration):
x_batch, y_true_batch, _, cls_batch = data.train.next_batch(batch_size)
x_valid_batch, y_valid_batch, _, valid_cls_batch = data.valid.next_batch(batch_size)
feed_dict_tr = {x: x_batch, y_true: y_true_batch}
feed_dict_val = {x: x_valid_batch, y_true: y_valid_batch}
session.run(optimizer, feed_dict=feed_dict_tr)
# data.train.num_examples=800,每训练完一个Epoch,保存一次模型
if i % int(data.train.num_examples / batch_size) == 0:
# if i % 800 == 0:
val_loss = session.run(cost, feed_dict=feed_dict_val)
# 800张测试图片,每次喂32张,即喂800/32=25次完成一个Epoch,i=8000,所以模型一共运行了8000/25=320个Epoch
epoch = int(i / int(data.train.num_examples / batch_size))
show_progress(epoch, feed_dict_tr, feed_dict_val, val_loss, i)
# 保存整个session,也可以保存其他值。“global_step=i”代表当前保存的是第几次模型、第几次迭代结果、
# .meta结尾文件:存储整个网络的结构图;.index结尾文件:;.data结尾文件:存的权重参数
saver.save(session, './dogs-cats-model/dog-cat.ckpt', global_step=i)
total_iterations += num_iteration
train(num_iteration=8000)
处理照片所用的dataset类,类似于Java的JavaBean思想:
import cv2
import os
import glob
from sklearn.utils import shuffle
import numpy as np
def load_train(train_path, image_size, classes):
images = []
labels = []
img_names = []
cls = []
print('Going to read training images')
for fields in classes:
index = classes.index(fields)
print('Now going to read() files (Index:{})'.format(fields, index))
path = os.path.join(train_path, fields, '*g')
# 把所有的文件名称拿到手
files = glob.glob(path)
for fl in files:
image = cv2.imread(fl)
image = cv2.resize(image, (image_size, image_size), 0, 0, cv2.INTER_LINEAR)
image = image.astype(np.float32)
# 预处理:对当前矩阵做乘法,做归一化
image = np.multiply(image, 1.0 / 255.0)
images.append(image)
label = np.zeros(len(classes))
# 做标签
label[index] = 1.0
labels.append(label)
flbase = os.path.basename(fl)
img_names.append(flbase)
cls.append(fields)
images = np.array(images)
labels = np.array(labels)
img_names = np.array(img_names)
# 图片对应类别
cls = np.array(cls)
return images, labels, img_names, cls
class DataSet(object):
def __init__(self, images, labels, img_names, cls):
self._num_examples = images.shape[0]
self._images = images
self._labels = labels
self._img_names = img_names
self._cls = cls
self._epochs_done = 0
self._index_in_epoch = 0
@property
def images(self):
return self._images
@property
def labels(self):
return self._labels
@property
def img_names(self):
return self._img_names
@property
def cls(self):
return self._cls
@property
def num_examples(self):
return self._num_examples
@property
def epochs_done(self):
return self._epochs_done
def next_batch(self, batch_size):
start = self._index_in_epoch
self._index_in_epoch += batch_size
# 防止大于图片总数最大值:500
if self._index_in_epoch > self._num_examples:
self._epochs_done += 1
start = 0
self._index_in_epoch = batch_size
assert batch_size <= self._num_examples
end = self._index_in_epoch
# 传索引切片回去
return self._images[start:end], self._labels[start:end], self._img_names[start:end], self._cls[start:end]
def read_train_sets(train_path, image_size, classes, validation_size):
class DataSets(object):
pass
data_sets = DataSets()
images, labels, img_names, cls = load_train(train_path, image_size, classes)
# 打乱顺序,洗牌(变量对应关系不会打乱)
images, labels, img_names, cls = shuffle(images, labels, img_names, cls)
# 验证集
if isinstance(validation_size, float):
validation_size = int(validation_size * images.shape[0])
# 200个验证集
validation_images = images[:validation_size]
validation_labels = labels[:validation_size]
validation_img_names = img_names[:validation_size]
validation_cls = cls[:validation_size]
# 800训练集
train_images = images[validation_size:]
train_labels = labels[validation_size:]
train_img_names = img_names[validation_size:]
train_cls = cls[validation_size:]
data_sets.train = DataSet(train_images, train_labels, train_img_names, train_cls)
data_sets.valid = DataSet(validation_images, validation_labels, validation_img_names, validation_cls)
return data_sets
加载上述训练好的模型对未知图片进行预测:
import tensorflow as tf
import numpy as np
import os,glob,cv2
import sys,argparse
# 训练、测试时对图像做的预处理应相同
image_size = 64
num_channels = 3
images = []
# 单张图片测试,也可以测试多张图片
path = 'dog.1015.jpg'
image = cv2.imread(path)
image = cv2.resize(image, (image_size, image_size), 0, 0, cv2.INTER_LINEAR)
images.append(image)
images = np.array(images, dtype=np.uint8)
images = images.astype('float32')
images = np.multiply(images, 1.0/255.0)
x_batch = images.reshape(1, image_size, image_size, num_channels)
# 加载已训练好的模型
sess = tf.Session()
# 第一步:导入网络结构图
saver = tf.train.import_meta_graph('./dogs-cats-model/dog-cat.ckpt-6825.meta')
# 第二步:加载权重参数W
saver.restore(sess, './dogs-cats-model/dog-cat.ckpt-6825')
# 加载图
graph = tf.get_default_graph()
#
y_pred = graph.get_tensor_by_name('y_pred:0')
#
x = graph.get_tensor_by_name('x:0')
y_true = graph.get_tensor_by_name('y_true:0')
y_test_images = np.zeros((1, 2))
feed_dict_testing = {x: x_batch, y_true: y_test_images}
result = sess.run(y_pred, feed_dict=feed_dict_testing)
res_label = ['dog', 'cat']
print(res_label[result.argmax()])
模型训练的部分执行结果,8000次训练完,正确率大概可以达到93%左右:
Training Epoch 25---- iterations: 600---- Training Accuracy: 71.9%, Validation Accuracy: 56.2%, Validation Loss: 0.731
Training Epoch 26---- iterations: 625---- Training Accuracy: 65.6%, Validation Accuracy: 65.6%, Validation Loss: 0.421
Training Epoch 27---- iterations: 650---- Training Accuracy: 81.2%, Validation Accuracy: 68.8%, Validation Loss: 0.579
Training Epoch 28---- iterations: 675---- Training Accuracy: 75.0%, Validation Accuracy: 65.6%, Validation Loss: 0.551
Training Epoch 29---- iterations: 700---- Training Accuracy: 71.9%, Validation Accuracy: 53.1%, Validation Loss: 0.671
Training Epoch 30---- iterations: 725---- Training Accuracy: 78.1%, Validation Accuracy: 75.0%, Validation Loss: 0.581
Training Epoch 31---- iterations: 750---- Training Accuracy: 65.6%, Validation Accuracy: 59.4%, Validation Loss: 0.777
Training Epoch 32---- iterations: 775---- Training Accuracy: 71.9%, Validation Accuracy: 75.0%, Validation Loss: 0.448
Training Epoch 33---- iterations: 800---- Training Accuracy: 81.2%, Validation Accuracy: 75.0%, Validation Loss: 0.584
Training Epoch 34---- iterations: 825---- Training Accuracy: 84.4%, Validation Accuracy: 71.9%, Validation Loss: 0.541
Training Epoch 35---- iterations: 850---- Training Accuracy: 65.6%, Validation Accuracy: 50.0%, Validation Loss: 0.775
Training Epoch 36---- iterations: 875---- Training Accuracy: 75.0%, Validation Accuracy: 78.1%, Validation Loss: 0.454
Training Epoch 37---- iterations: 900---- Training Accuracy: 84.4%, Validation Accuracy: 59.4%, Validation Loss: 0.727
预测程序执行结果:
dog
Process finished with exit code 0
参数设置不同,训练的结果也不同。