迁移学习就是用别人已经训练好的模型,如:Inception Model,Resnet Model等,把它当作Pre-trained Model,帮助咱们提取特征。经常使用方法是去除Pre-trained Model的最后一层,按照本身的需求从新更改,而后用训练集训练。
由于Pre-trained Model可能已经使用过大量数据集,通过了长时间的训练,因此咱们经过迁移学习可使用较少的数据集训练就能够得到相对不错的结果。git
因为项目中使用到Estimator,因此咱们再简单介绍下Estimator。github
这里引用下官网 Estimator的介绍。编程
咱们可使用“tf.keras.estimator.model_to_estimator”将keras转换Estimator。这里使用的数据集是Fashion-MNIST。安全
Fashion-MNIST数据标签:服务器
数据导入:网络
import os import time import tensorflow as tf import numpy as np import tensorflow.contrib as tcon (train_image,train_lables),(test_image,test_labels)=tf.keras.datasets.fashion_mnist.load_data() TRAINING_SIZE=len(train_image) TEST_SIZE=len(test_image) # 将像素值由0-255 转为0-1 之间 train_image=np.asarray(train_image,dtype=np.float32)/255 # 4维张量[batch_size,height,width,channels] train_image=train_image.reshape(shape=(TRAINING_SIZE,28,28,1)) test_image=np.asarray(test_image,dtype=np.float32)/255 test_image=test_image.reshape(shape=(TEST_SIZE,28,28,1))
使用tf.keras.utils.to_categorical将标签转为独热编码表示:app
# lables 转为 one_hot表示 # 类别数量 LABEL_DIMENSIONS=10 train_lables_onehot=tf.keras.utils.to_categorical( y=train_lables,num_classes=LABEL_DIMENSIONS ) test_labels_onehot=tf.keras.utils.to_categorical( y=test_labels,num_classes=LABEL_DIMENSIONS ) train_lables_onehot=train_lables_onehot.astype(np.float32) test_labels_onehot=test_labels_onehot.astype(np.float32)
建立Keras模型:机器学习
“”“ 3层卷积层,2层池化层,最后展平添加全链接层使用softmax分类 ”“” inputs=tf.keras.Input(shape=(28,28,1)) conv_1=tf.keras.layers.Conv2D( filters=32, kernel_size=3, # relu激活函数在输入值为负值时,激活值为0,此时可使用LeakyReLU activation=tf.nn.relu )(inputs) pool_1=tf.keras.layers.MaxPooling2D( pool_size=2, strides=2 )(conv_1) conv_2=tf.keras.layers.Conv2D( filters=64, kernel_size=3, activation=tf.nn.relu )(pool_1) pool_2=tf.keras.layers.MaxPooling2D( pool_size=2, strides=2 )(conv_2) conv_3=tf.keras.layers.Conv2D( filters=64, kernel_size=3, activation=tf.nn.relu )(pool_2) conv_flat=tf.keras.layers.Flatten()(conv_3) dense_64=tf.keras.layers.Dense( units=64, activation=tf.nn.relu )(conv_flat) predictions=tf.keras.layers.Dense( units=LABEL_DIMENSIONS, activation=tf.nn.softmax )(dense_64)
模型配置:分布式
model=tf.keras.Model( inputs=inputs, outputs=predictions ) model.compile( loss='categorical_crossentropy', optimizer=tf.train.AdamOptimizer(learning_rate=0.001), metrics=['accuracy'] )
指定GPU数量,而后将keras转为Estimator,代码以下:ide
NUM_GPUS=2 strategy=tcon.distribute.MirroredStrategy(num_gpus=NUM_GPUS) config=tf.estimator.RunConfig(train_distribute=strategy) estimator=tf.keras.estimator.model_to_estimator( keras_model=model,config=config )
前面说到过使用 Estimator 编写应用时,您必须将数据输入管道从模型中分离出来,因此,咱们先建立input function。使用prefetch将data预置缓冲区能够加快数据读取。由于下面的迁移训练使用的数据集较大,因此在这里有必要介绍下优化数据输入管道的相关内容。
TensorFlow数据输入管道是如下三个过程:
数据读取:
一般,当CPU为计算准备数据时,GPU/TPU处于闲置状态;当GPU/TPU运行时,CPU处于闲置,显然设备没有被合理利用。
tf.data.Dataset.prefetch能够将上述行为并行实现,当GPU/TPU执行第N次训练,此时让CPU准备N+1次训练使两个操做重叠,从而利用设备空闲时间。
经过使用tf.contrib.data.parallel_interleave能够并行从多个文件读取数据,并行文件数有cycle_length指定。
数据转换:
使用tf.data.Dataset.map对数据集中的数据进行处理,因为数据独立,因此能够并行处理。此函数读取的文件是含有肯定性顺序,若是顺序对训练没有影响,也能够取消肯定性顺序加快训练。
def input_fn(images,labels,epochs,batch_size): ds=tf.data.Dataset.from_tensor_slices((images,labels)) # repeat值为None或者-1时将无限制迭代 ds=ds.shuffle(500).repeat(epochs).batch(batch_size).prefetch(batch_size) return ds
# 用于计算迭代时间 class TimeHistory(tf.train.SessionRunHook): def begin(self): self.times = [] def before_run(self, run_context): self.iter_time_start = time.time() def after_run(self, run_context, run_values): self.times.append(time.time() - self.iter_time_start) time_hist = TimeHistory() BATCH_SIZE = 512 EPOCHS = 5 # lambda为了填写参数 estimator.train(lambda:input_fn(train_images, train_labels, epochs=EPOCHS, batch_size=BATCH_SIZE), hooks=[time_hist]) # 训练时间 total_time = sum(time_hist.times) print(f"total time with {NUM_GPUS} GPU(s): {total_time} seconds") # 训练数据量 avg_time_per_batch = np.mean(time_hist.times) print(f"{BATCH_SIZE*NUM_GPUS/avg_time_per_batch} images/second with {NUM_GPUS} GPU(s)")
结果如图:
得益于Estimator数据输入和模型的分离,评估方法很简单。
estimator.evaluate(lambda:input_fn(test_images, test_labels, epochs=1, batch_size=BATCH_SIZE))
咱们使用Retinal OCT images数据集进行迁移训练,数据标签为:NORMAL, CNV, DME DRUSEN,包含分辨率为512*296,84495张照片。
数据读取,设置input_fn:
labels = ['CNV', 'DME', 'DRUSEN', 'NORMAL'] train_folder = os.path.join('OCT2017', 'train', '**', '*.jpeg') test_folder = os.path.join('OCT2017', 'test', '**', '*.jpeg')
def input_fn(file_pattern, labels, image_size=(224,224), shuffle=False, batch_size=64, num_epochs=None, buffer_size=4096, prefetch_buffer_size=None): # 建立查找表,将string 转为 int 64ID table = tcon.lookup.index_table_from_tensor(mapping=tf.constant(labels)) num_classes = len(labels) def _map_func(filename): # sep = '/' label = tf.string_split([filename], delimiter=os.sep).values[-2] image = tf.image.decode_jpeg(tf.read_file(filename), channels=3) image = tf.image.convert_image_dtype(image, dtype=tf.float32) image = tf.image.resize_images(image, size=image_size) # tf.one_hot:根据输入的depth返回one_hot张量 # indices = [0, 1, 2] # depth = 3 # tf.one_hot(indices, depth) return: # [[1., 0., 0.], # [0., 1., 0.], # [0., 0., 1.]] return (image, tf.one_hot(table.lookup(label), num_classes)) dataset = tf.data.Dataset.list_files(file_pattern, shuffle=shuffle) if num_epochs is not None and shuffle: dataset = dataset.apply( tcon.data.shuffle_and_repeat(buffer_size, num_epochs)) elif shuffle: dataset = dataset.shuffle(buffer_size) elif num_epochs is not None: dataset = dataset.repeat(num_epochs) dataset = dataset.apply( tcon.data.map_and_batch(map_func=_map_func, batch_size=batch_size, num_parallel_calls=os.cpu_count())) dataset = dataset.prefetch(buffer_size=prefetch_buffer_size) return dataset
经过keras使用预训练的VGG16网络,咱们重训练最后5层:
# include_top:不包含最后3个全链接层 keras_vgg16 = tf.keras.applications.VGG16(input_shape=(224,224,3), include_top=False) output = keras_vgg16.output output = tf.keras.layers.Flatten()(output) prediction = tf.keras.layers.Dense(len(labels), activation=tf.nn.softmax)(output) model = tf.keras.Model(inputs=keras_vgg16.input, outputs=prediction) # 后5层不训练 for layer in keras_vgg16.layers[:-4]: layer.trainable = False
从新训练模型:
# 经过迁移学习获得模型 model.compile(loss='categorical_crossentropy', # 使用默认学习率 optimizer=tf.train.AdamOptimizer(), metrics=['accuracy']) NUM_GPUS = 2 strategy = tf.contrib.distribute.MirroredStrategy(num_gpus=NUM_GPUS) config = tf.estimator.RunConfig(train_distribute=strategy) # 转至estimator estimator = tf.keras.estimator.model_to_estimator(model, config=config) BATCH_SIZE = 64 EPOCHS = 1 estimator.train(input_fn=lambda:input_fn(train_folder, labels, shuffle=True, batch_size=BATCH_SIZE, buffer_size=2048, num_epochs=EPOCHS, prefetch_buffer_size=4), hooks=[time_hist]) # 模型评估: estimator.evaluate(input_fn=lambda:input_fn(test_folder, labels, shuffle=False, batch_size=BATCH_SIZE, buffer_size=1024, num_epochs=1))
如图所示,VGG16有13个卷积层和3个全链接层。VGG16输入为[224,224,3],卷积核大小为(3,3),池化大小为(2,2)步长为2。各层的详细参数能够查看VGG ILSVRC 16 layers由于图片较大,这里只给出部分截图,详情请点击连接查看。
VGG16模型结构规整,简单,经过几个小卷积核(3,3)卷积层组合比大卷积核如(7,7)更好,由于多个(3,3)卷积比一个大的卷积拥有更多的非线性,更少的参数。此外,验证了不断加深的网络结构能够提高性能(卷积+卷积+卷积+池化,代替卷积+池化,这样减小W的同时有能够拟合更复杂的数据),不过VGG16参数量不少,占用内存较大。
经过迁移学习咱们可使用较少的数据训练出来一个相对不错的模型,Estimator简化了机器学习编程特别是在分布式环境下。对于输入数据较多的状况咱们要从Extract,Transform,Load三方面考虑进行优化处理。固然,除了VGG16咱们还有不少选择,如:Inception Model,Resnet Model。
代码实现部分参考Kashif Rasul,在此表示感谢。