特别鸣谢麻花小姐姐提供的猫的图片(有一张的确会识别成狗,估计是训练数据的不够全面的问题)
学习自中国大学MOOC TensorFlow学习课程
目的:
- 探索猫和狗的示例数据
- 建立和训练神经网络以识别两者之间的差异
- 评估培训和验证准确性
需要处理数据-尤其是将其大小调整为一致的形状
数据预处理
# In this exercise you will train a CNN on the FULL Cats-v-dogs dataset
# This will require you doing a lot of data preprocessing because
# the dataset isn't split into training and validation for you
# This code block has all the required inputs
import os
import zipfile
import random
import tensorflow as tf
from tensorflow.keras.optimizers import RMSprop
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from shutil import copyfile
import shutil
# This code block downloads the full Cats-v-Dogs dataset and stores it as
# cats-and-dogs.zip. It then unzips it to /tmp
# which will create a tmp/PetImages directory containing subdirectories
# called 'Cat' and 'Dog' (that's how the original researchers structured it)
# If the URL doesn't work,
# . visit https://www.microsoft.com/en-us/download/confirmation.aspx?id=54765
# And right click on the 'Download Manually' link to get a new URL
!wget --no-check-certificate \
"https://download.microsoft.com/download/3/E/1/3E1C3F21-ECDB-4869-8368-6DEBA77B919F/kagglecatsanddogs_3367a.zip" \
-O "/tmp/cats-and-dogs.zip"
local_zip = '/tmp/cats-and-dogs.zip'
zip_ref = zipfile.ZipFile(local_zip, 'r')
zip_ref.extractall('/tmp')
zip_ref.close()
看看文件数
print(len(os.listdir('PetImages/Cat/')))
print(len(os.listdir('PetImages/Dog/')))
12501
12501
创建目录
# Use os.mkdir to create your directories
# You will need a directory for cats-v-dogs, and subdirectories for training
# and testing. These in turn will need subdirectories for 'cats' and 'dogs'
try:
#YOUR CODE GOES HERE
os.mkdir('cats-v-dogs')
os.mkdir('cats-v-dogs/training')
os.mkdir('cats-v-dogs/testing')
os.mkdir('cats-v-dogs/training/cats')
os.mkdir('cats-v-dogs/training/dogs')
os.mkdir('cats-v-dogs/testing/cats')
os.mkdir('cats-v-dogs/testing/dogs')
except OSError:
print('ERR')
简而言之:训练集是用于告诉神经网络模型“这就是猫的样子”,“这就是狗的样子”等的数据。验证数据集是猫和狗的图像神经网络不会作为训练的一部分,因此您可以测试其在评估图像中是否包含猫或狗时表现的好坏。
在此示例中要注意的一件事:我们没有将图像明确标记为猫或狗。
稍后,您将看到使用了一个称为ImageGenerator
的东西-并将其编码为从子目录读取图像,并自动从该子目录的名称中为它们加上标签。因此,例如,您将拥有一个“培训”目录,其中包含一个“猫”目录和一个“狗”目录。ImageGenerator将为您适当地标记图像,从而减少了编码步骤。
让我们定义以下每个目录:
# Write a python function called split_data which takes
# a SOURCE directory containing the files
# a TRAINING directory that a portion of the files will be copied to
# a TESTING directory that a portion of the files will be copie to
# a SPLIT SIZE to determine the portion
# The files should also be randomized, so that the training set is a random
# X% of the files, and the test set is the remaining files
# SO, for example, if SOURCE is PetImages/Cat, and SPLIT SIZE is .9
# Then 90% of the images in PetImages/Cat will be copied to the TRAINING dir
# and 10% of the images will be copied to the TESTING dir
# Also -- All images should be checked, and if they have a zero file length,
# they will not be copied over
#
# os.listdir(DIRECTORY) gives you a listing of the contents of that directory
# os.path.getsize(PATH) gives you the size of the file
# copyfile(source, destination) copies a file from source to destination
# random.sample(list, len(list)) shuffles a list
def split_data(SOURCE, TRAINING, TESTING, SPLIT_SIZE):
# YOUR CODE STARTS HERE
files = []
for filename in os.listdir(SOURCE):
file = SOURCE + filename
if os.path.getsize(file) > 0:
files.append(filename)
else:
print(filename + " is zero length, so ignoring.")
training_length = int(len(files) * SPLIT_SIZE)
testing_length = int(len(files) - training_length)
shuffled_set = random.sample(files, len(files))
training_set = shuffled_set[0:training_length]
testing_set = shuffled_set[-testing_length:]
for filename in training_set:
this_file = SOURCE + filename
destinastion = TRAINING + filename
copyfile(this_file, destinastion)
for filename in testing_set:
this_file = SOURCE + filename
destinastion = TESTING + filename
copyfile(this_file, destinastion)
CAT_SOURCE_DIR = "PetImages/Cat/"
TRAINING_CATS_DIR = "cats-v-dogs/training/cats/"
TESTING_CATS_DIR = "cats-v-dogs/testing/cats/"
DOG_SOURCE_DIR = "PetImages/Dog/"
TRAINING_DOGS_DIR = "cats-v-dogs/training/dogs/"
TESTING_DOGS_DIR = "cats-v-dogs/testing/dogs/"
def create_dir(file_dir):
if os.path.exists(file_dir):
print('true')
#os.rmdir(file_dir)
shutil.rmtree(file_dir)#删除再建立
os.makedirs(file_dir)
else:
os.makedirs(file_dir)
create_dir(TRAINING_CATS_DIR)
create_dir(TESTING_CATS_DIR)
create_dir(TRAINING_DOGS_DIR)
create_dir(TESTING_CATS_DIR)
split_size = .9
split_data(CAT_SOURCE_DIR, TRAINING_CATS_DIR, TESTING_CATS_DIR, split_size)
split_data(DOG_SOURCE_DIR, TRAINING_DOGS_DIR, TESTING_DOGS_DIR, split_size)
true
true
true
true
666.jpg is zero length, so ignoring.
11702.jpg is zero length, so ignoring.
看看目录结构:
print(len(os.listdir('cats-v-dogs/training/cats/')))
print(len(os.listdir('cats-v-dogs/training/dogs/')))
print(len(os.listdir('cats-v-dogs/testing/cats/')))
print(len(os.listdir('cats-v-dogs/testing/dogs/')))
11250
11250
1250
1250
文件名:
train_cat_fnames = os.listdir( TRAINING_CATS_DIR )
train_dog_fnames = os.listdir( TRAINING_DOGS_DIR )
print(train_cat_fnames[:10])
print(train_dog_fnames[:10])
['1.jpg', '10.jpg', '10000.jpg', '10001.jpg', '10002.jpg', '10003.jpg', '10004.jpg', '10005.jpg', '10007.jpg', '10009.jpg']
['0.jpg', '1.jpg', '10.jpg', '100.jpg', '1000.jpg', '10000.jpg', '10001.jpg', '10002.jpg', '10003.jpg', '10005.jpg']
对于猫和狗,我们都有1,000张训练图像和500张验证图像。
现在,让我们看一些图片,以更好地了解猫和狗数据集的外观。首先,配置matplot参数:
%matplotlib inline
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
# Parameters for our graph; we'll output images in a 4x4 configuration
nrows = 4
ncols = 4
pic_index = 0 # Index for iterating over images
现在,显示一批8张猫和8张狗的照片
# Set up matplotlib fig, and size it to fit 4x4 pics
fig = plt.gcf()
fig.set_size_inches(ncols*4, nrows*4)
pic_index+=8
next_cat_pix = [os.path.join(TRAINING_CATS_DIR, fname)
for fname in train_cat_fnames[ pic_index-8:pic_index]
]
next_dog_pix = [os.path.join(TRAINING_DOGS_DIR, fname)
for fname in train_dog_fnames[ pic_index-8:pic_index]
]
for i, img_path in enumerate(next_cat_pix+next_dog_pix):
# Set up subplot; subplot indices start at 1
sp = plt.subplot(nrows, ncols, i + 1)
sp.axis('Off') # Don't show axes (or gridlines)
img = mpimg.imread(img_path)
plt.imshow(img)
plt.show()
output_11_0.png
卷积神经网络模型构建
由于这些图像具有各种形状和大小,在训练神经网络之前,需要调整图像大小的统一
下一步就是定义将要训练的模型,以从这些图像中识别猫或狗
从头开始构建小型模型,以达到〜72%的精度。需要它们的大小统一。我们为此选择了150x150
注意这次输入的形状参数大小为150x150,颜色深度为3(24位,3字节)
# DEFINE A KERAS MODEL TO CLASSIFY CATS V DOGS
# USE AT LEAST 3 CONVOLUTION LAYERS
model = tf.keras.models.Sequential([
# YOUR CODE HERE
tf.keras.layers.Conv2D(16, (3, 3), activation='relu', input_shape=(150, 150, 3)),
tf.keras.layers.MaxPool2D(2, 2),
tf.keras.layers.Conv2D(32, (3, 3), activation='relu'),
tf.keras.layers.MaxPool2D(2, 2),
tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
tf.keras.layers.MaxPool2D(2, 2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(512, activation='relu'),
tf.keras.layers.Dense(1, activation='sigmoid')
])
#请注意,由于我们面临的是两类分类问题,即二进制分类问题,因此我们将以sigmoid激活结束网络,以便网络的输出将是介于0和1之间的单个标量,从而编码当前图像是1类(而不是0类)。
#梯度下降法的初始学习率为0.001; 修正算法是RMSprop; 损失函数是二元交叉熵(因为是二分类问题); 网络性能的度量是用精确度
model.compile(optimizer=RMSprop(lr=0.001), loss='binary_crossentropy', metrics=['acc'])
Next, we'll configure the specifications for model training. We will train our model with the binary_crossentropy
loss, because it's a binary classification problem and our final activation is a sigmoid. (For a refresher on loss metrics, see the Machine Learning Crash Course.) We will use the rmsprop
optimizer with a learning rate of 0.001
. During training, we will want to monitor classification accuracy.
NOTE: In this case, using the RMSprop optimization algorithm is preferable to stochastic gradient descent (SGD), because RMSprop automates learning-rate tuning for us. (Other optimizers, such as Adam and Adagrad, also automatically adapt the learning rate during training, and would work equally well here.)
数据生成器将读取源文件夹中的图片,将它们转换为float32张量,然后将它们(带有标签)馈送到我们的网络中。将批量生产20张大小为150x150的图像及其标签(二进制)。
通过将像素值归一化在[0, 1]范围内(最初所有值都在[0, 255]范围内)来预处理图像。
#数据预处理归一化和构建generator
TRAINING_DIR = "cats-v-dogs/training/" #YOUR CODE HERE
train_datagen = ImageDataGenerator(rescale=1.0/255.) #YOUR CODE HERE
train_generator = train_datagen.flow_from_directory(TRAINING_DIR,
batch_size=100, #每一批次中包含的样本数是100
class_mode='binary', #分类方式是二分类
target_size=(150, 150)) #YOUR CODE HERE
VALIDATION_DIR = "cats-v-dogs/testing/" #YOUR CODE HERE
validation_datagen = ImageDataGenerator(rescale=1.0/255.) #YOUR CODE HERE
validation_generator = validation_datagen.flow_from_directory(VALIDATION_DIR,
batch_size=100,
class_mode='binary',
target_size=(150, 150)) #YOUR CODE HERE
Found 22498 images belonging to 2 classes.
Found 2500 images belonging to 2 classes.
模型训练
In Keras this can be done via the keras.preprocessing.image.ImageDataGenerator
class using the rescale
parameter. This ImageDataGenerator
class allows you to instantiate generators of augmented image batches (and their labels) via .flow(data, labels)
or .flow_from_directory(directory)
. These generators can then be used with the Keras model methods that accept data generators as inputs: fit_generator
, evaluate_generator
, and predict_generator
history = model.fit_generator(train_generator,
epochs=2,
verbose=1, #记录日志
validation_data=validation_generator)
# The expectation here is that the model will train, and that accuracy will be > 95% on both training and validation
# i.e. acc:A1 and val_acc:A2 will be visible, and both A1 and A2 will be > .9
D:\anaconda\envs\TF2_4\lib\site-packages\tensorflow\python\keras\engine\training.py:1844: UserWarning: `Model.fit_generator` is deprecated and will be removed in a future version. Please use `Model.fit`, which supports generators.
warnings.warn('`Model.fit_generator` is deprecated and '
Epoch 1/2
88/225 [==========>...................] - ETA: 2:02 - loss: 1.3803 - acc: 0.5360
D:\anaconda\envs\TF2_4\lib\site-packages\PIL\TiffImagePlugin.py:771: UserWarning: Possibly corrupt EXIF data. Expecting to read 32 bytes but only got 0. Skipping tag 270
warnings.warn(
D:\anaconda\envs\TF2_4\lib\site-packages\PIL\TiffImagePlugin.py:771: UserWarning: Possibly corrupt EXIF data. Expecting to read 5 bytes but only got 0. Skipping tag 271
warnings.warn(
D:\anaconda\envs\TF2_4\lib\site-packages\PIL\TiffImagePlugin.py:771: UserWarning: Possibly corrupt EXIF data. Expecting to read 8 bytes but only got 0. Skipping tag 272
warnings.warn(
D:\anaconda\envs\TF2_4\lib\site-packages\PIL\TiffImagePlugin.py:771: UserWarning: Possibly corrupt EXIF data. Expecting to read 8 bytes but only got 0. Skipping tag 282
warnings.warn(
D:\anaconda\envs\TF2_4\lib\site-packages\PIL\TiffImagePlugin.py:771: UserWarning: Possibly corrupt EXIF data. Expecting to read 8 bytes but only got 0. Skipping tag 283
warnings.warn(
D:\anaconda\envs\TF2_4\lib\site-packages\PIL\TiffImagePlugin.py:771: UserWarning: Possibly corrupt EXIF data. Expecting to read 20 bytes but only got 0. Skipping tag 306
warnings.warn(
D:\anaconda\envs\TF2_4\lib\site-packages\PIL\TiffImagePlugin.py:771: UserWarning: Possibly corrupt EXIF data. Expecting to read 48 bytes but only got 0. Skipping tag 532
warnings.warn(
D:\anaconda\envs\TF2_4\lib\site-packages\PIL\TiffImagePlugin.py:793: UserWarning: Corrupt EXIF data. Expecting to read 2 bytes but only got 0.
warnings.warn(str(msg))
225/225 [==============================] - 237s 1s/step - loss: 1.0034 - acc: 0.5763 - val_loss: 0.5510 - val_acc: 0.7384
Epoch 2/2
225/225 [==============================] - 86s 383ms/step - loss: 0.5261 - acc: 0.7330 - val_loss: 0.4793 - val_acc: 0.7732
绘制精确度和loss曲线
# PLOT LOSS AND ACCURACY
%matplotlib inline
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
#-----------------------------------------------------------
# Retrieve a list of list results on training and test data
# sets for each training epoch
#-----------------------------------------------------------
acc=history.history['acc']
val_acc=history.history['val_acc']
loss=history.history['loss']
val_loss=history.history['val_loss']
epochs=range(len(acc)) # Get number of epochs
#------------------------------------------------
# Plot training and validation accuracy per epoch
#------------------------------------------------
plt.plot(epochs, acc, 'r', "Training Accuracy")
plt.plot(epochs, val_acc, 'b', "Validation Accuracy")
plt.title('Training and validation accuracy')
plt.figure()
#------------------------------------------------
# Plot training and validation loss per epoch
#------------------------------------------------
plt.plot(epochs, loss, 'r', "Training Loss")
plt.plot(epochs, val_loss, 'b', "Validation Loss")
plt.title('Training and validation loss')
# Desired output. Charts with training and validation metrics. No crash :)
Text(0.5, 1.0, 'Training and validation loss')
output_18_1.png
output_18_2.png
上传图片来预测猫和狗i的类别
# Here's a codeblock just for fun. You should be able to upload an image here
# and have it classified without crashing
import numpy as np
from google.colab import files
from keras.preprocessing import image
uploaded = files.upload()
for fn in uploaded.keys():
# predicting images
path = '/content/' + fn
img = image.load_img(path, target_size=(150, 150)) # YOUR CODE HERE
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
images = np.vstack([x])
classes = model.predict(images, batch_size=10)
print(classes[0])
if classes[0]>0.5:
print(fn + " is a dog")
else:
print(fn + " is a cat")
先传小姐姐的一张猫的图看看
# predicting images
import numpy as np
from keras.preprocessing import image
path = 'C:/Users/Robin/Desktop/ff19d284a95c6fed76894b856989377.jpg'
img = image.load_img(path, target_size=(150, 150))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
images = np.vstack([x])
classes = model.predict(images, batch_size=10)
print(classes[0])
if classes[0]>0.5:
print("This is a dog")
else:
print("This is a cat")
[1.]
This is a dog
img #这时候明显识别错误错误,明明图像是猫;测试过增加训练的
output_21_0.png
再传小姐姐的另外一张猫的图
path = 'C:/Users/Robin/Desktop/2.jpg'
img = image.load_img(path, target_size=(150, 150))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
images = np.vstack([x])
classes = model.predict(images, batch_size=10)
print(classes[0])
if classes[0]>0.5:
print("This is a dog")
else:
print("This is a cat")
[0.35068443]
This is a cat
img #这张就很正常地识别对了
output_23_0.png
再传一张在家附近随手拍的狗的照片
path = 'C:/Users/Robin/Desktop/3.jpg'
img = image.load_img(path, target_size=(150, 150))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
images = np.vstack([x])
classes = model.predict(images, batch_size=10)
print(classes[0])
if classes[0]>0.5:
print("This is a dog")
else:
print("This is a cat")
[1.]
This is a dog
img #狗
output_25_0.png
可视化中间隐藏层输出
import numpy as np
import random
from tensorflow.keras.preprocessing.image import img_to_array, load_img
# Let's define a new Model that will take an image as input, and will output
# intermediate representations for all layers in the previous model after
# the first.
successive_outputs = [layer.output for layer in model.layers[1:]]
#visualization_model = Model(img_input, successive_outputs)
visualization_model = tf.keras.models.Model(inputs = model.input, outputs = successive_outputs)
# Let's prepare a random input image of a cat or dog from the training set.
cat_img_files = [os.path.join(TRAINING_CATS_DIR, f) for f in train_cat_fnames]
dog_img_files = [os.path.join(TRAINING_DOGS_DIR, f) for f in train_dog_fnames]
img_path = random.choice(cat_img_files + dog_img_files)
img = load_img(img_path, target_size=(150, 150)) # this is a PIL image
x = img_to_array(img) # Numpy array with shape (150, 150, 3)
x = x.reshape((1,) + x.shape) # Numpy array with shape (1, 150, 150, 3)
# Rescale by 1/255
x /= 255.0
# Let's run our image through our network, thus obtaining all
# intermediate representations for this image.
successive_feature_maps = visualization_model.predict(x)
# These are the names of the layers, so can have them as part of our plot
layer_names = [layer.name for layer in model.layers]
# -----------------------------------------------------------------------
# Now let's display our representations
# -----------------------------------------------------------------------
for layer_name, feature_map in zip(layer_names, successive_feature_maps):
if len(feature_map.shape) == 4:
#-------------------------------------------
# Just do this for the conv / maxpool layers, not the fully-connected layers
#-------------------------------------------
n_features = feature_map.shape[-1] # number of features in the feature map
size = feature_map.shape[ 1] # feature map shape (1, size, size, n_features)
# We will tile our images in this matrix
display_grid = np.zeros((size, size * n_features))
#-------------------------------------------------
# Postprocess the feature to be visually palatable
#-------------------------------------------------
for i in range(n_features):
x = feature_map[0, :, :, i]
x -= x.mean()
x /= x.std ()
x *= 64
x += 128
x = np.clip(x, 0, 255).astype('uint8')
display_grid[:, i * size : (i + 1) * size] = x # Tile each filter into a horizontal grid
#-----------------
# Display the grid
#-----------------
scale = 20. / n_features
plt.figure( figsize=(scale * n_features, scale) )
plt.title ( layer_name )
plt.grid ( False )
plt.imshow( display_grid, aspect='auto', cmap='viridis' )
<ipython-input-39-ae9384d0203a>:55: RuntimeWarning: invalid value encountered in true_divide
x /= x.std ()
output_27_1.png
output_27_2.png
output_27_3.png
output_27_4.png
output_27_5.png
可见,图像的原始像素过渡到越来越抽象和紧凑的表示形式。下游的表示开始突出显示网络要注意的内容,并且显示“活性”的功能越来越少。大多数设置为零。这称为“稀疏”。表示稀疏性是深度学习的关键特征。
这些表示承载的图像原始像素信息越来越少,但是承载的图像类别信息却越来越精细。您可以将卷积网络convnet(或通常称为深层网络)视为信息蒸馏管道( information distillation pipeline)。
#释放资源
import os, signal
#在Windows中,signal()只能叫SIGABRT, SIGFPE,SIGILL,SIGINT,SIGSEGV,或 SIGTERM。ValueError在其他情况下,将引发A。
#os.kill(os.getpid(), signal.SIGKILL)
os.kill(os.getpid(), signal.SIGINT)
网友评论