CNTK API文档翻译(24)——使用深度迁移学习进行图像识别

本教程展示了如何在已经训练好的模型中使用迁移学习(Transfer Learning)以及如何用于你自己的领域。本教程需要你的电脑安装了支持CUDA的GPU。

问题

这里有一些花的图片,你需要给他们分类,下面的图片展示了数据中的一些样本数据。
image

在本教程的14期中我们有介绍用于图像识别的残差神经网络,但是关于花朵的图片数量远远小于训练残差神经网络需要的数据量。我们有的只是足够数量的自然场景的图片,如下图:
image

本教程介绍深度迁移学习,使用多个多个数据源来客服数据稀缺的难题。

为什么使用迁移学习

如上所述,迁移学习在你需要对输入的图像分类但是你没有足够的数据训练深度神经网络时非常有用。训练深度神经网络需要大量标记好的数据,但是经常你没有这样的数据。如果之前已经训练过一个神经网络,你可以使用迁移学习个一小部分已标记的图像来将其转换成你的问题所需要的网络。

什么是迁移学习

通过迁移学习,我们可以让一个已经训练好的模型适配我们自己的问题,这是基于基本模型训练中学到的概念和特征的。我们使用一个深度卷积训练来自ImageNet的数据的深度卷积网络,但是去掉他最后一个分类层,拿一个用于预测新领域类别标签的全连接层取而代之。

旧的分类层和新的预测层的输入值是一样的,所以我们可以重用之前训练好的特征。然后我们训练改过的神经网络的最后一层的权重或者整个网络的权重。

迁移学习可以用于当我们有较少的图片数据集且在相似的领域有一个已经训练好的模型时。从零开始训练一个深度神经网络需要上万张图片,但是将一个已经训练好了的模型适配到自己的需要的就少多了。

在我们的教程中,就是把一个使用ImageNet图片训练而成的神经网络用于花朵,或者羊和狼。迁移学习已经成功被用于翻译、语言合成以及其他更多的领域,这是一种引导我们学习非常方便的方法。

引入CNTK和其他需要的模块

Microsoft’s Cognitive Toolkit在Python里面的库是cntk,里面包含了很多有用的子模块,包括IO,定义网络层,训练模型以及审查训练好了的模型。我们在进行迁移学习时需要用到里面的很多,同事我们也需要用到一些常用的模块来下载、解压、读取以及构建矩阵。

from __future__ import print_function
import glob
import os
import numpy as np
from PIL import Image
# Some of the flowers data is stored as .mat files
from scipy.io import loadmat
from shutil import copyfile
import sys
import tarfile
import time

# Loat the right urlretrieve based on python version
try: 
    from urllib.request import urlretrieve 
except ImportError: 
    from urllib import urlretrieve

import zipfile

# Useful for being able to dump images into the Notebook
import IPython.display as D

# Import CNTK and helpers
import cntk as C

我们设定了两种运行模式:

  • 快速模式:isFast变量设置成True。这是我们的默认模式,在这个模式下我们会训练更少的次数,也会使用更少的数据,这个模式保证功能的正确性,但训练的结果还远远达不到可用的要求。
  • 慢速模式:我们建议学习者在学习的时候试试将isFast变量设置成False,这会让学习者更加了解本教程的内容。

快速模式下我们将训练100个周期,结汇的精度会比较低,不过对开发来说也够了。如果我们训练1000-2000个周期,得到的结果会好很多。

isFast = True

下载数据

现在让我们下载需要的数据集。在本教程中我们会使用两个数据集——一个数据集包含大量花朵的图片,另一个数据集包含少量羊和狼的照片。下面会对数据进行详细的说明,不过现在我们只需要下载和解压数据。

下面代码的第一部分我们检测代码是否在内部测试环境下运行,如果是的话就将数据下载到本地。

多说一句,我们检测是否是CNTK的测试机器是通过已经定义的系统变量的。然后我么选择合适的运算设备(GPU/CPU),一般来说,我们会使用CNTK的默认策略,选取最好的设备(有GPU就用GPU,没有用CPU)。

C.device.try_set_default_device(C.device.gpu(0))
# Check for an environment variable defined in CNTK's test infrastructure
def is_test(): return 'CNTK_EXTERNAL_TESTDATA_SOURCE_DIRECTORY' in os.environ

# Select the right target device when this notebook is being tested
# Currently supported only for GPU 
# Setup data environment for pre-built data sources for testing
if is_test(): 
    if 'TEST_DEVICE' in os.environ:
        if os.environ['TEST_DEVICE'] == 'cpu':
            raise ValueError('This notebook is currently not support on CPU') 
        else:
            C.device.try_set_default_device(C.device.gpu(0))
    sys.path.append(os.path.join(*"../Tests/EndToEndTests/CNTKv2Python/Examples".split("/")))
    import prepare_test_data as T
    T.prepare_resnet_v1_model()
    T.prepare_flower_data()
    T.prepare_animals_data()

注意我们将数据的根目录设置为与CNTK的例子目录一致,所以你在运行的时候可能部分数据已经存在了。如果你想你的输入数据和输入数据都放在别处,可以更改数据根目录。download_unless_exists函数会尝试多次下载,但是如果失败了你会看到一个错误。download_unless_exists函数和write_to_file函数都需要在磁盘写入数据,如果你的数据根目录是只读的或者硬盘满了,你也会看到错误。

# By default, we store data in the Examples/Image directory under CNTK
# If you're running this _outside_ of CNTK, consider changing this
data_root = os.path.join('..', 'Examples', 'Image')

datasets_path = os.path.join(data_root, 'DataSets')
output_path = os.path.join('.', 'temp', 'Output')

def ensure_exists(path):
    if not os.path.exists(path):
        os.makedirs(path)

def write_to_file(file_path, img_paths, img_labels):
    with open(file_path, 'w+') as f:
        for i in range(0, len(img_paths)):
            f.write('%s\t%s\n' % (os.path.abspath(img_paths[i]), img_labels[i]))

def download_unless_exists(url, filename, max_retries=3):
    '''Download the file unless it already exists, with retry. Throws if all retries fail.'''
    if os.path.exists(filename):
        print('Reusing locally cached: ', filename)
    else:
        print('Starting download of {} to {}'.format(url, filename))
        retry_cnt = 0
        while True:
            try:
                urlretrieve(url, filename)
                print('Download completed.')
                return
            except:
                retry_cnt += 1
                if retry_cnt == max_retries:
                    print('Exceeded maximum retry count, aborting.')
                    raise
                print('Failed to download, retrying.')
                time.sleep(np.random.randint(1,10))

def download_model(model_root = os.path.join(data_root, 'PretrainedModels')):
    ensure_exists(model_root)
    resnet18_model_uri = 'https://www.cntk.ai/Models/ResNet/ResNet_18.model'
    resnet18_model_local = os.path.join(model_root, 'ResNet_18.model')
    download_unless_exists(resnet18_model_uri, resnet18_model_local)
    return resnet18_model_local

def download_flowers_dataset(dataset_root = os.path.join(datasets_path, 'Flowers')):
    ensure_exists(dataset_root)
    flowers_uris = [
        'http://www.robots.ox.ac.uk/~vgg/data/flowers/102/102flowers.tgz',
        'http://www.robots.ox.ac.uk/~vgg/data/flowers/102/imagelabels.mat',
        'http://www.robots.ox.ac.uk/~vgg/data/flowers/102/setid.mat'
    ]
    flowers_files = [
        os.path.join(dataset_root, '102flowers.tgz'),
        os.path.join(dataset_root, 'imagelabels.mat'),
        os.path.join(dataset_root, 'setid.mat')
    ]
    for uri, file in zip(flowers_uris, flowers_files):
        download_unless_exists(uri, file)
    tar_dir = os.path.join(dataset_root, 'extracted')
    if not os.path.exists(tar_dir):
        print('Extracting {} to {}'.format(flowers_files[0], tar_dir))
        os.makedirs(tar_dir)
        tarfile.open(flowers_files[0]).extractall(path=tar_dir)
    else:
        print('{} already extracted to {}, using existing version'.format(flowers_files[0], tar_dir))

    flowers_data = {
        'data_folder': dataset_root,
        'training_map': os.path.join(dataset_root, '6k_img_map.txt'),
        'testing_map': os.path.join(dataset_root, '1k_img_map.txt'),
        'validation_map': os.path.join(dataset_root, 'val_map.txt')
    }

    if not os.path.exists(flowers_data['training_map']):
        print('Writing map files ...')
        # get image paths and 0-based image labels
        image_paths = np.array(sorted(glob.glob(os.path.join(tar_dir, 'jpg', '*.jpg'))))
        image_labels = loadmat(flowers_files[1])['labels'][0]
        image_labels -= 1

        # read set information from .mat file
        setid = loadmat(flowers_files[2])
        idx_train = setid['trnid'][0] - 1
        idx_test = setid['tstid'][0] - 1
        idx_val = setid['valid'][0] - 1

        # Confusingly the training set contains 1k images and the test set contains 6k images
        # We swap them, because we want to train on more data
        write_to_file(flowers_data['training_map'], image_paths[idx_train], image_labels[idx_train])
        write_to_file(flowers_data['testing_map'], image_paths[idx_test], image_labels[idx_test])
        write_to_file(flowers_data['validation_map'], image_paths[idx_val], image_labels[idx_val])
        print('Map files written, dataset download and unpack completed.')
    else:
        print('Using cached map files.')

    return flowers_data

def download_animals_dataset(dataset_root = os.path.join(datasets_path, 'Animals')):
    ensure_exists(dataset_root)
    animals_uri = 'https://www.cntk.ai/DataSets/Animals/Animals.zip'
    animals_file = os.path.join(dataset_root, 'Animals.zip')
    download_unless_exists(animals_uri, animals_file)
    if not os.path.exists(os.path.join(dataset_root, 'Test')):
        with zipfile.ZipFile(animals_file) as animals_zip:
            print('Extracting {} to {}'.format(animals_file, dataset_root))
            animals_zip.extractall(path=os.path.join(dataset_root, '..'))
            print('Extraction completed.')
    else:
        print('Reusing previously extracted Animals data.')

    return {
        'training_folder': os.path.join(dataset_root, 'Train'),
        'testing_folder': os.path.join(dataset_root, 'Test')
    }

print('Downloading flowers and animals data-set, this might take a while...')
flowers_data = download_flowers_dataset()
animals_data = download_animals_dataset()
print('All data now available to the notebook!')

训练模型结构

在本次任务重,我们选取了ResNet_18作为我们已经训练好的基本模型。我们通过迁移学习将其改造成可以用来给花朵和动物分类。这个模型是一个使用残差网络技术的卷积神经网络。卷积神经网络是由输入层,卷积层,转换层等一层接一层,直到可以识别复杂的特征,特别是深度卷积层的引入,使复杂模式识别成为可能。Keras的作者在其一篇精彩的博文中深入的描述和解释了卷积神经网络如何”see the world”。

残差深度学习是起源于微软研究院的技术,用于将主要信号传递下去,用了这项技术后,神经网络可以从层之间不同的残差部分学习。这项技术经过实践证明可以让训练深度神经网络时免受梯度几何级下降的困扰。这个单元绕过卷积层,直达下一次ReLU之前,不过也有些人认为通过减少这些旁路同道中的非线性计算,我们可以构建更深层的神经网络。这是目前研究的热点,不过最让人兴奋的还是通过迁移学习,你可以得到原模型中的所有有点。
image

如果你想了解更多深度残差网络的结构,看Kaiming He的GitHub.

print('Downloading pre-trained model. Note: this might take a while...')
base_model_file = download_model()
print('Downloading pre-trained model complete!')

查看模型

我们下面打印出ResNet_18中所有的层来展示如何查看一个模型。在使用其他模型时,你只需要了解最后的隐藏层和特征层。CNTK的cntk.graph模块下提供了get_node_outputs方法,可以方便的查看模型的细节。我们可以把最后一个隐藏层当做我们在给图片分类之前的层。

# define base model location and characteristics
base_model = {
    'model_file': base_model_file,
    'feature_node_name': 'features',
    'last_hidden_node_name': 'z.x',
    # Channel Depth x Height x Width
    'image_dims': (3, 224, 224)
}

# Print out all layers in the model
print('Loading {} and printing all layers:'.format(base_model['model_file']))
node_outputs = C.logging.get_node_outputs(C.load_model(base_model['model_file']))
for l in node_outputs: print("  {0} {1}".format(l.name, l.shape))

花朵数据集

花朵数据及来自牛津视觉几何组,包含102种英国常见的花朵。数据集中大概有8000张图片,分为训练数据集、测试数据集和验证数据集。http://www.robots.ox.ac.uk/~vgg/data/flowers/102/index.html 这个网址包含更多数据细节。

这个数据由大量的原始图片和两个.mat格式的矩阵组成。这些矩阵是1-based(不知道怎么翻译)矩阵,包含了标签的ID和训练/测试/验证的分类。我们将其转换成0-based标签,然后在训练/测试/验证数据集的索引文件中写入CNTK可用的图像/标签对。

让我们看看一些我们用到的数据:

def plot_images(images, subplot_shape):
    plt.style.use('ggplot')
    fig, axes = plt.subplots(*subplot_shape)
    for image, ax in zip(images, axes.flatten()):
        ax.imshow(image.reshape(28, 28), vmin = 0, vmax = 1.0, cmap = 'gray')
        ax.axis('off')
    plt.show()

flowers_image_dir = os.path.join(flowers_data['data_folder'], 'extracted', 'jpg')


for image in ['08093', '08084', '08081', '08058']:
    D.display(D.Image(os.path.join(flowers_image_dir, 'image_{}.jpg'.format(image)), width=100, height=100))

image
image
image
image

训练迁移学习模型

下面的代码中,我们先加载已经训练好的ResNet_18模型,并复制一份,用于去掉最后一个特征层。我们复制他,所以我们可以多次使用这个已经训练好的模型来干不同的事情,当然如果你只用执行一个任务,这就不是必须的了,不过这其实也是我们不使用CloneMethod.share函数的原因,我们需要训练出信的参数。如果变量freeze_weights的值是True,我们将冻结我们复制的模型中所有图层的权重参数,只训练最后一个新的特征层的权重参数。如果你想复制网络中更高的层,这就非常有用(比如复制第一个卷积层之后的所有层来获取图片的基本特征)。

我们使用find_by_name函数寻找最后一个隐藏层(z,x),复制他以及所有他的前辈,然后附加到新的用于分类的全连接层上。

import cntk.io.transforms as xforms
ensure_exists(output_path)
np.random.seed(123)

# Creates a minibatch source for training or testing
def create_mb_source(map_file, image_dims, num_classes, randomize=True):
    transforms = [xforms.scale(width=image_dims[2], height=image_dims[1], channels=image_dims[0], interpolations='linear')]
    return C.io.MinibatchSource(C.io.ImageDeserializer(map_file, C.io.StreamDefs(
            features=C.io.StreamDef(field='image', transforms=transforms),
            labels=C.io.StreamDef(field='label', shape=num_classes))),
            randomize=randomize)

# Creates the network model for transfer learning
def create_model(model_details, num_classes, input_features, new_prediction_node_name='prediction', freeze=False):
    # Load the pretrained classification net and find nodes
    base_model = C.load_model(model_details['model_file'])
    feature_node = C.logging.find_by_name(base_model, model_details['feature_node_name'])
    last_node = C.logging.find_by_name(base_model, model_details['last_hidden_node_name'])

    # Clone the desired layers with fixed weights
    cloned_layers = C.combine([last_node.owner]).clone(
        C.CloneMethod.freeze if freeze else C.CloneMethod.clone,
        {feature_node: C.placeholder(name='features')})

    # Add new dense layer for class prediction
    feat_norm = input_features - C.Constant(114)
    cloned_out = cloned_layers(feat_norm)
    z = C.layers.Dense(num_classes, activation=None, name=new_prediction_node_name) (cloned_out)

    return z

现在我们训练这个模型就跟训练其他CNTK模型一样,实例化一个输入源(本例中是来自图片数据的MinibatchSource),定义成本函数,训练一定数量的周期。因为我们是要训练一个多类别分类器网络,所以最后一层是一个基于使用Softmax和交叉熵成本函数的层,误差函数使用分类误差,这些再cntk.ops中都有现成的方法。

当训练一个之前训练过的模型是,我们将已经存在的权重适配到自己的领域。因为这些权重一般都不让更正了,所以需要更少的样本和更少的周期,但是能得到较好的效果。

# Trains a transfer learning model
def train_model(model_details, num_classes, train_map_file,
                learning_params, max_images=-1):
    num_epochs = learning_params['max_epochs']
    epoch_size = sum(1 for line in open(train_map_file))
    if max_images > 0:
        epoch_size = min(epoch_size, max_images)
    minibatch_size = learning_params['mb_size']

    # Create the minibatch source and input variables
    minibatch_source = create_mb_source(train_map_file, model_details['image_dims'], num_classes)
    image_input = C.input_variable(model_details['image_dims'])
    label_input = C.input_variable(num_classes)

    # Define mapping from reader streams to network inputs
    input_map = {
        image_input: minibatch_source['features'],
        label_input: minibatch_source['labels']
    }

    # Instantiate the transfer learning model and loss function
    tl_model = create_model(model_details, num_classes, image_input, freeze=learning_params['freeze_weights'])
    ce = C.cross_entropy_with_softmax(tl_model, label_input)
    pe = C.classification_error(tl_model, label_input)

    # Instantiate the trainer object
    lr_schedule = C.learning_rate_schedule(learning_params['lr_per_mb'], unit=C.UnitType.minibatch)
    mm_schedule = C.momentum_schedule(learning_params['momentum_per_mb'])
    learner = C.momentum_sgd(tl_model.parameters, lr_schedule, mm_schedule, 
                           l2_regularization_weight=learning_params['l2_reg_weight'])
    trainer = C.Trainer(tl_model, (ce, pe), learner)

    # Get minibatches of images and perform model training
    print("Training transfer learning model for {0} epochs (epoch_size = {1}).".format(num_epochs, epoch_size))
    C.logging.log_number_of_parameters(tl_model)
    progress_printer = C.logging.ProgressPrinter(tag='Training', num_epochs=num_epochs)
    # loop over epochs
    for epoch in range(num_epochs):
        sample_count = 0
        # loop over minibatches in the epoch
        while sample_count < epoch_size: 
            data = minibatch_source.next_minibatch(min(minibatch_size, epoch_size - sample_count), input_map=input_map)
            # update model with it
            trainer.train_minibatch(data)
            # count samples processed so far
            sample_count += trainer.previous_minibatch_sample_count
            # log progress
            progress_printer.update_with_trainer(trainer, with_metric=True)
            if sample_count % (100 * minibatch_size) == 0:
                print ("Processed {0} samples".format(sample_count))

        progress_printer.epoch_summary(with_metric=True)

    return tl_model

当我们使用一个图片评估训练好了的模型时,我们需要将图片转换成模型接受的格式。在本例中,我们使用Image模块从我图片的路径中加载进内存,缩放到模型需要的大小,调换色彩通道(RGB→BGR),然后转换成依据图片高度,宽度和色彩通道的连续数组,也就是一个224×224×3的扁平状态的数组。

我们评估的模型没有添加Softmax和Error层,所以最后一层是一个特征层。为了评估模型,我们将输入数据传入model.eval函数,这会对结果进行softmax运算,生成概率,然后使用Numpy中的argmax函数来决定预测的类别。然后我们就能和真实的标签作比较,得到模型整体的精度。

# Evaluates a single image using the re-trained model
def eval_single_image(loaded_model, image_path, image_dims):
    # load and format image (resize, RGB -> BGR, CHW -> HWC)
    try:
        img = Image.open(image_path)

        if image_path.endswith("png"):
            temp = Image.new("RGB", img.size, (255, 255, 255))
            temp.paste(img, img)
            img = temp
        resized = img.resize((image_dims[2], image_dims[1]), Image.ANTIALIAS)
        bgr_image = np.asarray(resized, dtype=np.float32)[..., [2, 1, 0]]
        hwc_format = np.ascontiguousarray(np.rollaxis(bgr_image, 2))

        # compute model output
        arguments = {loaded_model.arguments[0]: [hwc_format]}
        output = loaded_model.eval(arguments)

        # return softmax probabilities
        sm = C.softmax(output[0])
        return sm.eval()
    except FileNotFoundError:
        print("Could not open (skipping file): ", image_path)
        return ['None']



# Evaluates an image set using the provided model
def eval_test_images(loaded_model, output_file, test_map_file, image_dims, max_images=-1, column_offset=0):
    num_images = sum(1 for line in open(test_map_file))
    if max_images > 0:
        num_images = min(num_images, max_images)
    if isFast:
        #We will run through fewer images for test run
        num_images = min(num_images, 300)

    print("Evaluating model output node '{0}' for {1} images.".format('prediction', num_images))

    pred_count = 0
    correct_count = 0
    np.seterr(over='raise')
    with open(output_file, 'wb') as results_file:
        with open(test_map_file, "r") as input_file:
            for line in input_file:
                tokens = line.rstrip().split('\t')
                img_file = tokens[0 + column_offset]
                probs = eval_single_image(loaded_model, img_file, image_dims)

                if probs[0]=='None':
                    print("Eval not possible: ", img_file)
                    continue

                pred_count += 1
                true_label = int(tokens[1 + column_offset])
                predicted_label = np.argmax(probs)
                if predicted_label == true_label:
                    correct_count += 1

                #np.savetxt(results_file, probs[np.newaxis], fmt="%.3f")
                if pred_count % 100 == 0:
                    print("Processed {0} samples ({1:.2%} correct)".format(pred_count, (float(correct_count) / pred_count)))
                if pred_count >= num_images:
                    break
    print ("{0} of {1} prediction were correct".format(correct_count, pred_count))
    return correct_count, pred_count, (float(correct_count) / pred_count)

最终,通过上面所有的函数,我们就可以用花朵的数据集来训练和评估模型了。

随便调整下面的learning_params参数,然后观察结果。你可以更改max_epochs来训练更长时间,更改mb_size来调整取样包大小,更改lr_per_mb来调整学习效率。

注意如果你已经训练过一次了,你就可能会想将force_retraining设置为True来重新训练

你可以看到在训练和评估中,最终达到的准确率大概在94%。此时你可以选择训练更长时间,或者考虑检查混淆矩阵,看看是否特定的花朵有更高的被误测的概率。你也可以简单的换一个模型,看看是否有更好的效果,甚或者从模型更早的节点开始训练,以期与自己的数据更符合。

force_retraining = True

max_training_epochs = 5 if isFast else 20

learning_params = {
    'max_epochs': max_training_epochs,
    'mb_size': 50,
    'lr_per_mb': [0.2]*10 + [0.1],
    'momentum_per_mb': 0.9,
    'l2_reg_weight': 0.0005,
    'freeze_weights': True
}

flowers_model = {
    'model_file': os.path.join(output_path, 'FlowersTransferLearning.model'),
    'results_file': os.path.join(output_path, 'FlowersPredictions.txt'),
    'num_classes': 102
}

# Train only if no model exists yet or if force_retraining is set to True
if os.path.exists(flowers_model['model_file']) and not force_retraining:
    print("Loading existing model from %s" % flowers_model['model_file'])
    trained_model = C.load_model(flowers_model['model_file'])
else:
    trained_model = train_model(base_model,
                                flowers_model['num_classes'], flowers_data['training_map'],
                                learning_params)
    trained_model.save(flowers_model['model_file'])
    print("Stored trained model at %s" % flowers_model['model_file'])

# Evaluate the test set
predict_correct, predict_total, predict_accuracy = \
   eval_test_images(trained_model, flowers_model['results_file'], flowers_data['testing_map'], base_model['image_dims'])
print("Done. Wrote output to %s" % flowers_model['results_file'])


# Test: Accuracy on flower data
print ("Prediction accuracy: {0:.2%}".format(float(predict_correct) / predict_total))

动物数据集

在花朵数据集中,几千张图片由一百多类。如果我们只有更少的类别和更少的图片,迁移学习是否还奏效呢?让我们使用动物数据集来试试看。我们下载的动物数据集只包含羊和狼的图片,数据量也少很多,我们先看看其中一些图片。

sheep = ['738519_d0394de9.jpg', 'Pair_of_Icelandic_Sheep.jpg']
wolves = ['European_grey_wolf_in_Prague_zoo.jpg', 'Wolf_je1-3.jpg']
for image in [os.path.join('Sheep', f) for f in sheep] + [os.path.join('Wolf', f) for f in wolves]:
    D.display(D.Image(os.path.join(animals_data['training_folder'], image), width=100, height=100))

image
image
image
image

这些图片被分为训练和测试文件夹,下面的文件夹给出了图片的分类(Sheep,Wolf)。这是常用的做法,所以下面我们就需要知道如何将其转换成CNTK可以接受的格式。下面的create_class_mapping_from_folder函数查看根目录下的所有文件夹,并将其名字转换成标签,然后返回一个可以被create_map_file_from_folder函数用的数组。create_map_file_from_folder函数浏览整个文件夹,将其路径和标签以索引的方式写入根目录中的map.txt文件。注意使用abspath可以让你使用相对路径,以后移动到其他文件夹也可以使用。

# Set python version variable 
python_version = sys.version_info.major

def create_map_file_from_folder(root_folder, class_mapping, include_unknown=False, valid_extensions=['.jpg', '.jpeg', '.png']):
    map_file_name = os.path.join(root_folder, "map.txt")

    map_file = None

    if python_version == 3: 
        map_file = open(map_file_name , 'w', encoding='utf-8')
    else:
        map_file = open(map_file_name , 'w')

    for class_id in range(0, len(class_mapping)):
        folder = os.path.join(root_folder, class_mapping[class_id])
        if os.path.exists(folder):
            for entry in os.listdir(folder):
                filename = os.path.abspath(os.path.join(folder, entry))
                if os.path.isfile(filename) and os.path.splitext(filename)[1].lower() in valid_extensions:
                    try:
                        map_file.write("{0}\t{1}\n".format(filename, class_id))
                    except UnicodeEncodeError:
                        continue

    if include_unknown:
        for entry in os.listdir(root_folder):
            filename = os.path.abspath(os.path.join(root_folder, entry))
            if os.path.isfile(filename) and os.path.splitext(filename)[1].lower() in valid_extensions:
                try:
                    map_file.write("{0}\t-1\n".format(filename))
                except UnicodeEncodeError:
                    continue

    map_file.close()  

    return map_file_name


def create_class_mapping_from_folder(root_folder):
    classes = []
    for _, directories, _ in os.walk(root_folder):
        for directory in directories:
            classes.append(directory)
    return np.asarray(classes)

animals_data['class_mapping'] = create_class_mapping_from_folder(animals_data['training_folder'])
animals_data['training_map'] = create_map_file_from_folder(animals_data['training_folder'], animals_data['class_mapping'])
# Since the test data includes some birds, set include_unknown
animals_data['testing_map'] = create_map_file_from_folder(animals_data['testing_folder'], animals_data['class_mapping'],include_unknown=True)

现在我们可以训练和评估我们的模型了。

animals_model = {
    'model_file': os.path.join(output_path, 'AnimalsTransferLearning.model'),
    'results_file': os.path.join(output_path, 'AnimalsPredictions.txt'),
    'num_classes': len(animals_data['class_mapping'])
}

if os.path.exists(animals_model['model_file']) and not force_retraining:
    print("Loading existing model from %s" % animals_model['model_file'])
    trained_model = C.load_model(animals_model['model_file'])
else:
    trained_model = train_model(base_model, 
                                animals_model['num_classes'], animals_data['training_map'],
                                learning_params)
    trained_model.save(animals_model['model_file'])
    print("Stored trained model at %s" % animals_model['model_file'])

# evaluate test images
with open(animals_data['testing_map'], 'r') as input_file:
    for line in input_file:
        tokens = line.rstrip().split('\t')
        img_file = tokens[0]
        true_label = int(tokens[1])
        probs = eval_single_image(trained_model, img_file, base_model['image_dims'])

        if probs[0]=='None':
            continue
        class_probs = np.column_stack((probs, animals_data['class_mapping'])).tolist()
        class_probs.sort(key=lambda x: float(x[0]), reverse=True)
        predictions = ' '.join(['%s:%.3f' % (class_probs[i][1], float(class_probs[i][0])) \
                                for i in range(0, animals_model['num_classes'])])
        true_class_name = animals_data['class_mapping'][true_label] if true_label >= 0 else 'unknown'
        print('Class: %s, predictions: %s, image: %s' % (true_class_name, predictions, img_file))

已知和未知

注意create_map_file_from_folder函数中的include_unknown=True,这是因为文件夹中有少部分没有标记的图片。这只是在展示,如果我们只训练一个可以分别羊和狼的判别器,那他就只能用来判别羊和狼。如果使用其他照片比如鸟的,就会预测失败。

images = ['Bird_in_flight_wings_spread.jpg', 'quetzal-bird.jpg', 'Weaver_bird.jpg']
for image in images:
    D.display(D.Image(os.path.join(animals_data['testing_folder'], image), width=100, height=100))

image
image
image

最后的思考和说明

迁移学习有其局限性,如果你留意,我们重新训练一个已经使用ImageNet图片训练过的模型,这表示模型已经知道了什么是图片,而且对从低级别的要素(纹理、形状)概念到高级别的概念(狗鼻子、猫耳朵)已经有了一定的套路。重新训练这样的模型来监测狼或者羊是可行的,但是如果用来从航测影像中检测车辆就会非常困难。当然你还是可以用迁移学习来实现,只不过你就需要重新训练更高级别的网络层,也就需要更多的训练数据。

添加一个其他类别或许是个好主意,不过只有当该列别的训练数据包含着与评估时使用的数据相似时才有用。比如在上面的例子中,如果我们使用羊和狼的图片训练出来的分类器去识别鸟的图片,分类器最终还是会给其贴一个羊或者狼的标签,因为他不知道羊和狼之外的其他类别。如果我们添加一个其他类别,然后训练鸟的图片进入其他类别,这个类别可能以后遇到鸟的图片都能预测成功,但是,如果我们又拿来一张比如汽车的图片,他又会遇到之前我们说的问题,我们的模型只知道羊,狼,鸟(被我们叫成了其他)。所以,你的训练数据需要覆盖之后你在使用时希望被分入那类的所有的数据,即使你使用了其他。


欢迎扫码关注我的微信公众号获取最新文章
image

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值