tensorflow學(xué)習(xí)筆記-cifar10圖像分類示例

這篇筆記主要記錄一下學(xué)習(xí)tensorflow cifar-10圖像分類的示例代碼。

數(shù)據(jù)介紹

Cifar-10是由 Hinton 的兩個(gè)大弟子 Alex Krizhevsky、Ilya Sutskever 收集的一個(gè)用于普適物體識(shí)別的數(shù)據(jù)集。Cifar 是加拿大政府牽頭投資的一個(gè)先進(jìn)科學(xué)項(xiàng)目研究所。Hinton、Bengio和他的學(xué)生在2004年拿到了 Cifar 投資的少量資金,建立了神經(jīng)計(jì)算和自適應(yīng)感知項(xiàng)目。這個(gè)項(xiàng)目結(jié)集了不少計(jì)算機(jī)科學(xué)家、生物學(xué)家、電氣工程師、神經(jīng)科學(xué)家、物理學(xué)家、心理學(xué)家,加速推動(dòng)了 Deep Learning 的進(jìn)程。從這個(gè)陣容來看,DL 已經(jīng)和 ML 系的數(shù)據(jù)挖掘分的很遠(yuǎn)了。Deep Learning 強(qiáng)調(diào)的是自適應(yīng)感知和人工智能,是計(jì)算機(jī)與神經(jīng)科學(xué)交叉;Data Mining 強(qiáng)調(diào)的是高速、大數(shù)據(jù)、統(tǒng)計(jì)數(shù)學(xué)分析,是計(jì)算機(jī)和數(shù)學(xué)的交叉。

cifar-10分類數(shù)據(jù)集為60000張32 * 32的彩色圖片,總共有10個(gè)類別,其中50000張訓(xùn)練集,10000張測試集,官網(wǎng)地址http://www.cs.toronto.edu/~kriz/cifar.html,提供數(shù)據(jù)集下載 數(shù)據(jù)集中圖片諸如以下

20171108170500783.png

這里重點(diǎn)說明數(shù)據(jù)集的存儲(chǔ)格式。下載解壓后為5個(gè)batch(圖中data_batch_1,2,3,4,5)的訓(xùn)練集,1個(gè)batch(圖中test_batch)的測試集。每個(gè)batch中10000張圖片。
20171108171349118.png

每個(gè)文件中數(shù)據(jù)存儲(chǔ)格式為dict字典,鍵值為b’data’的為圖片數(shù)據(jù),是一個(gè)10000 * 3072(32 * 32 * 3)的numpy向量,10000表示圖片張數(shù),3072中前1024個(gè)表示Red通道數(shù)據(jù),中間1024個(gè)表示Green通道數(shù)據(jù),最后1024個(gè)表示Blue通道數(shù)據(jù),數(shù)據(jù)范圍是0-255,表示像素點(diǎn)灰度。鍵值為b’labels’表示對(duì)應(yīng)的標(biāo)簽,是一個(gè)長度為10000的list,數(shù)據(jù)范圍是0-9,分別表示10個(gè)類別。
另外要說明的是卷積濾波器卷積的是32 * 32 * 3格式的數(shù)據(jù),32 * 32代表圖片一個(gè)通道格式,3表示RGB 3個(gè)通道,然而依據(jù)其數(shù)據(jù)表示格式,在 reshape 3072維度的向量的時(shí)候必須首先reshape成3 * 32 * 32格式的向量,否則會(huì)破壞圖片原本格式,怎么辦呢,轉(zhuǎn)置!類似于矩陣的轉(zhuǎn)置,三維向量也有轉(zhuǎn)置,tensorflow提供transpose方法對(duì)三維向量作轉(zhuǎn)置。

網(wǎng)絡(luò)結(jié)構(gòu)

先介紹一下示例代碼中的文件構(gòu)成:
本文將使用的代碼結(jié)果和網(wǎng)絡(luò)結(jié)構(gòu):

文件 說明
cifar10_input.py 讀取本地CIFAR-10的二進(jìn)制文件格式的內(nèi)容
cifar10.py 建立CIFAR-10的模型
cifar10_train.py 在CPU或GPU上訓(xùn)練CIFAR-10的模型
cifar10_multi_gpu_train.py 在多GPU上訓(xùn)練CIFAR-10的模型。
cifar10_eval.py 評(píng)估CIFAR-10模型的預(yù)測性能

示例中的網(wǎng)絡(luò)結(jié)構(gòu)比較簡單,


20170322111837119.png

模型輸入

輸入模型是通過 cifar10_input.inputs() 和 cifar10_input.distorted_inputs() 函數(shù)建立起來的,這2個(gè)函數(shù)會(huì)從 CIFAR-10 二進(jìn)制文件中讀取圖片文件,具體實(shí)現(xiàn)定義在 cifar10_input.py 中,使用的數(shù)據(jù)為 CIFAR-10 page 下的162M 的二進(jìn)制文件,由于每個(gè)圖片的存儲(chǔ)字節(jié)數(shù)是固定的,因此可以使用 tf.FixedLengthRecordReader 函數(shù)。

載入圖像數(shù)據(jù)后,通過以下流程進(jìn)行數(shù)據(jù)增廣:

  1. 統(tǒng)一裁剪到24x24像素大小,裁剪中央?yún)^(qū)域用于評(píng)估或隨機(jī)裁剪用于訓(xùn)練;
  2. 對(duì)圖像進(jìn)行隨機(jī)的左右翻轉(zhuǎn);
  3. 隨機(jī)變換圖像的亮度;
  4. 隨機(jī)變換圖像的對(duì)比度;
  5. 圖片會(huì)進(jìn)行近似的白化處理。
    其中,白化(whitening)處理或者叫標(biāo)準(zhǔn)化(standardization)處理,是對(duì)圖片數(shù)據(jù)減去均值,除以方差,保證數(shù)據(jù)零均值,方差為1,如此降低輸入圖像的冗余性,盡量去除輸入特征間的相關(guān)性,使得網(wǎng)絡(luò)對(duì)圖片的動(dòng)態(tài)范圍變化不敏感。
    # Image processing for training the network. Note the many random
    # distortions applied to the image.

    # Randomly crop a [height, width] section of the image.
    distorted_image = tf.random_crop(reshaped_image, [height, width, 3])

    # Randomly flip the image horizontally.
    distorted_image = tf.image.random_flip_left_right(distorted_image)

    # Because these operations are not commutative, consider randomizing
    # the order their operation.
    # NOTE: since per_image_standardization zeros the mean and makes
    # the stddev unit, this likely has no effect see tensorflow#1458.
    distorted_image = tf.image.random_brightness(distorted_image,
                                                 max_delta=63)
    distorted_image = tf.image.random_contrast(distorted_image,
                                               lower=0.2, upper=1.8)

    # Subtract off the mean and divide by the variance of the pixels.
    float_image = tf.image.per_image_standardization(distorted_image)

從磁盤上加載圖像并進(jìn)行變換需要花費(fèi)不少的處理時(shí)間。為了避免這些操作減慢訓(xùn)練過程,使用16個(gè)獨(dú)立的線程中并行進(jìn)行這些操作,這16個(gè)線程被連續(xù)的安排在一個(gè) TensorFlow 隊(duì)列中,最后返回預(yù)處理后封裝好的tensor,每次執(zhí)行都會(huì)生成一個(gè) batch_size 數(shù)量的樣本 [images,labels]。測試數(shù)據(jù)使用cifar10_input.inputs() 函數(shù)生成,測試數(shù)據(jù)不需要對(duì)圖片進(jìn)行翻轉(zhuǎn)或修改亮度、對(duì)比度,需要裁剪圖片正中間的24*24大小的區(qū)塊,并進(jìn)行數(shù)據(jù)標(biāo)準(zhǔn)化操作。

def _generate_image_and_label_batch(image, label, min_queue_examples,
                                    batch_size, shuffle):
  # Create a queue that shuffles the examples, and then
  # read 'batch_size' images + labels from the example queue.
  num_preprocess_threads = 16
  if shuffle:
    images, label_batch = tf.train.shuffle_batch(
        [image, label],
        batch_size=batch_size,
        num_threads=num_preprocess_threads,
        capacity=min_queue_examples + 3 * batch_size,
        min_after_dequeue=min_queue_examples)
  ...... 
  # Display the training images in the visualizer.
  tf.summary.image('images', images)
  return images, tf.reshape(label_batch, [batch_size])

模型構(gòu)建

在建立模型之前,我們構(gòu)造 weight 的構(gòu)造函數(shù) _variable_with_weight_decay(name, shape, stddev, wd),其中 wd 用于向 losses 添加L2正則化,可以防止過擬合,提高泛化能力:

def _variable_with_weight_decay(name, shape, stddev, wd):
  dtype = tf.float16 if FLAGS.use_fp16 else tf.float32
  var = _variable_on_cpu(
      name,
      shape,
      tf.truncated_normal_initializer(stddev=stddev, dtype=dtype))
  if wd is not None:
    weight_decay = tf.multiply(tf.nn.l2_loss(var), wd, name='weight_loss')
    tf.add_to_collection('losses', weight_decay)
  return var

然后我們開始建立網(wǎng)絡(luò),第一層卷積層的 weight 不進(jìn)行 L2正則,因此 kernel(wd) 這一項(xiàng)設(shè)為0,建立值為0的 biases,conv1的結(jié)果由 ReLu 激活,由 _activation_summary() 進(jìn)行匯總;然后建立第一層池化層,最大池化尺寸和步長不一致可以增加數(shù)據(jù)的豐富性;最后建立 LRN 層,LRN層模仿了生物神經(jīng)系統(tǒng)的"側(cè)抑制"機(jī)制,對(duì)局部神經(jīng)元的活動(dòng)創(chuàng)建競爭環(huán)境,使得其中響應(yīng)比較大的值變得相對(duì)更大,并抑制其他反饋較小的神經(jīng)元,增強(qiáng)了模型的泛化能力,LRN 對(duì) Relu 這種沒有上限邊界的激活函數(shù)會(huì)比較有用,因?yàn)樗鼤?huì)從附近的多個(gè)卷積核的響應(yīng)中挑選比較大的反饋,但不適合 sigmoid 這種有固定邊界并且能抑制過大的激活函數(shù)。

  # conv1
  with tf.variable_scope('conv1') as scope:
    kernel = _variable_with_weight_decay('weights',
                                         shape=[5, 5, 3, 64],
                                         stddev=5e-2,
                                         wd=None)
    conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME')
    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0))
    pre_activation = tf.nn.bias_add(conv, biases)
    conv1 = tf.nn.relu(pre_activation, name=scope.name)
    _activation_summary(conv1)

  # pool1
  pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                         padding='SAME', name='pool1')
  # norm1
  norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
                    name='norm1')

第二層卷積層與第一層,除了輸入?yún)?shù)的改變之外,將 biases 值全部初始化為0.1,調(diào)換最大池化和 LRN 層的順序,先進(jìn)行LRN,再使用最大池化層。


  # conv2
  with tf.variable_scope('conv2') as scope:
    kernel = _variable_with_weight_decay('weights',
                                         shape=[5, 5, 64, 64],
                                         stddev=5e-2,
                                         wd=None)
    conv = tf.nn.conv2d(norm1, kernel, [1, 1, 1, 1], padding='SAME')
    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.1))
    pre_activation = tf.nn.bias_add(conv, biases)
    conv2 = tf.nn.relu(pre_activation, name=scope.name)
    _activation_summary(conv2)

  # norm2
  norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,
                    name='norm2')
  # pool2
  pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1],
                         strides=[1, 2, 2, 1], padding='SAME', name='pool2')

第三層全連接層 ,需要先把前面的卷積層的輸出結(jié)果全部 flatten,使用 tf.reshape 函數(shù)將每個(gè)樣本都變?yōu)橐痪S向量,使用 get_shape 函數(shù)獲取數(shù)據(jù)扁平化之后的長度;然后對(duì)全連接層的 weights 和 biases 進(jìn)行初始化,為了防止全連接層過擬合,設(shè)置一個(gè)非零的 wd 值0.004,讓這一層的所有參數(shù)都被 L2正則所約束,最后依然使用 Relu 激活函數(shù)進(jìn)行非線性化。同理,可以建立第四層全連接層。

  # local3
  with tf.variable_scope('local3') as scope:
    # Move everything into depth so we can perform a single matrix multiply.
    reshape = tf.reshape(pool2, [images.get_shape().as_list()[0], -1])
    dim = reshape.get_shape()[1].value
    weights = _variable_with_weight_decay('weights', shape=[dim, 384],
                                          stddev=0.04, wd=0.004)
    biases = _variable_on_cpu('biases', [384], tf.constant_initializer(0.1))
    local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name)
    _activation_summary(local3)

  # local4
  with tf.variable_scope('local4') as scope:
    weights = _variable_with_weight_decay('weights', shape=[384, 192],
                                          stddev=0.04, wd=0.004)
    biases = _variable_on_cpu('biases', [192], tf.constant_initializer(0.1))
    local4 = tf.nn.relu(tf.matmul(local3, weights) + biases, name=scope.name)
    _activation_summary(local4)

最后的 softmax_linear 層,先創(chuàng)建這一層的 weights 和 biases,不添加L2正則化。在這個(gè)模型中,不像之前的例子使用 sotfmax 輸出最后的結(jié)果,因?yàn)閷?softmax 的操作放在來計(jì)算 loss 的部分,將 softmax_linear 的線性返回值 logits 與 labels 計(jì)算 loss

  # linear layer(WX + b),
  # We don't apply softmax here because
  # tf.nn.sparse_softmax_cross_entropy_with_logits accepts the unscaled logits
  # and performs the softmax internally for efficiency.
  with tf.variable_scope('softmax_linear') as scope:
    weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES],
                                          stddev=1/192.0, wd=None)
    biases = _variable_on_cpu('biases', [NUM_CLASSES],
                              tf.constant_initializer(0.0))
    softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name)
    _activation_summary(softmax_linear)

然后計(jì)算loss:先計(jì)算交叉熵,然后在加上L2 loss

def loss(logits, labels):
  # Calculate the average cross entropy loss across the batch.
  labels = tf.cast(labels, tf.int64)
  cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
      labels=labels, logits=logits, name='cross_entropy_per_example')
  cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
  tf.add_to_collection('losses', cross_entropy_mean)

  # The total loss is defined as the cross entropy loss plus all of the weight
  # decay terms (L2 loss).
  return tf.add_n(tf.get_collection('losses'), name='total_loss')

模型訓(xùn)練

在定義 loss 之后,我們需要定義接受 loss 并返回 train op 的 train()。
首先定義學(xué)習(xí)率(learning rate),并設(shè)置隨迭代次數(shù)衰減,并進(jìn)行 summary:

  # Decay the learning rate exponentially based on the number of steps.
  lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE,
                                  global_step,
                                  decay_steps,
                                  LEARNING_RATE_DECAY_FACTOR,
                                  staircase=True)
  tf.summary.scalar('learning_rate', lr)

  # Generate moving averages of all losses and associated summaries.
  loss_averages_op = _add_loss_summaries(total_loss)

此外,我們對(duì) loss 生成滑動(dòng)均值和匯總,通過使用指數(shù)衰減,來維護(hù)變量的滑動(dòng)均值(Moving Average)。當(dāng)訓(xùn)練模型時(shí),維護(hù)訓(xùn)練參數(shù)的滑動(dòng)均值是有好處的,在測試過程中使用滑動(dòng)參數(shù)比最終訓(xùn)練的參數(shù)值本身,會(huì)提高模型的實(shí)際性能即準(zhǔn)確率。apply() 方法會(huì)添加 trained variables 的 shadow copies,并添加操作來維護(hù)變量的滑動(dòng)均值到 shadow copies。 average() 方法可以訪問 shadow variables,在創(chuàng)建 evaluation model 時(shí)非常有用。滑動(dòng)均值是通過指數(shù)衰減計(jì)算得到的,shadow variable 的初始化值和 trained variables 相同,其更新公式為 shadow_variable = decay * shadow_variable + (1 - decay) * variable。(關(guān)于ema,可參考之前的這篇tensorflow學(xué)習(xí)筆記-ExponentialMovingAverage)

def _add_loss_summaries(total_loss):
  # Compute the moving average of all individual losses and the total loss.
  loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg')
  losses = tf.get_collection('losses')
  loss_averages_op = loss_averages.apply(losses + [total_loss])

  # Attach a scalar summary to all individual losses and the total loss; do the
  # same for the averaged version of the losses.
  for l in losses + [total_loss]:
    # Name each loss as '(raw)' and name the moving average version of the loss
    # as the original loss name.
    tf.summary.scalar(l.op.name + ' (raw)', l)
    tf.summary.scalar(l.op.name, loss_averages.average(l))

  return loss_averages_op

然后,我們定義訓(xùn)練方法與目標(biāo),tf.control_dependencies 是一個(gè) context manager,控制節(jié)點(diǎn)執(zhí)行順序,先執(zhí)行[ ]中的操作,再執(zhí)行 context 中的操作。計(jì)算并應(yīng)用梯度,最后,動(dòng)態(tài)調(diào)整衰減率,返回模型參數(shù)變量的滑動(dòng)更新操作即 train op

 # Compute gradients.
  with tf.control_dependencies([loss_averages_op]):
    opt = tf.train.GradientDescentOptimizer(lr)
    grads = opt.compute_gradients(total_loss)

  # Apply gradients.
  apply_gradient_op = opt.apply_gradients(grads, global_step=global_step)

# Track the moving averages of all trainable variables.
  variable_averages = tf.train.ExponentialMovingAverage(
      MOVING_AVERAGE_DECAY, global_step)
  with tf.control_dependencies([apply_gradient_op]):
    variables_averages_op = variable_averages.apply(tf.trainable_variables())

  return variables_averages_op

訓(xùn)練過程

def train():
  """Train CIFAR-10 for a number of steps."""
  with tf.Graph().as_default():
    global_step = tf.train.get_or_create_global_step()

    # Get images and labels for CIFAR-10.
    # Force input pipeline to CPU:0 to avoid operations sometimes ending up on
    # GPU and resulting in a slow down.
    with tf.device('/cpu:0'):
      images, labels = cifar10.distorted_inputs()

    # Build a Graph that computes the logits predictions from the
    # inference model.
    logits = cifar10.inference(images)

    # Calculate loss.
    loss = cifar10.loss(logits, labels)

    # Build a Graph that trains the model with one batch of examples and
    # updates the model parameters.
    train_op = cifar10.train(loss, global_step)

    class _LoggerHook(tf.train.SessionRunHook):
      """Logs loss and runtime."""

      def begin(self):
        self._step = -1
        self._start_time = time.time()

      def before_run(self, run_context):
        self._step += 1
        return tf.train.SessionRunArgs(loss)  # Asks for loss value.

      def after_run(self, run_context, run_values):
        if self._step % FLAGS.log_frequency == 0:
          current_time = time.time()
          duration = current_time - self._start_time
          self._start_time = current_time

          loss_value = run_values.results
          examples_per_sec = FLAGS.log_frequency * FLAGS.batch_size / duration
          sec_per_batch = float(duration / FLAGS.log_frequency)

          format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f '
                        'sec/batch)')
          print (format_str % (datetime.now(), self._step, loss_value,
                               examples_per_sec, sec_per_batch))

    with tf.train.MonitoredTrainingSession(
        checkpoint_dir=FLAGS.train_dir,  #這里會(huì)自動(dòng)保存checkpoint
        hooks=[tf.train.StopAtStepHook(last_step=FLAGS.max_steps),
               tf.train.NanTensorHook(loss),
               _LoggerHook()],
        config=tf.ConfigProto(
            log_device_placement=FLAGS.log_device_placement)) as mon_sess:
      while not mon_sess.should_stop():
        mon_sess.run(train_op)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容