Panduan Lanjutan Inception v3

Dokumen ini membahas aspek model Inception dan bagaimana model tersebut bekerja sama untuk membuat model berjalan secara efisien di Cloud TPU. Ini adalah tampilan lanjutan dari panduan untuk menjalankan Inception v3 di Cloud TPU. Perubahan khusus pada model yang menghasilkan peningkatan signifikan akan dibahas secara lebih mendetail. Dokumen ini melengkapi tutorial Inception v3.

Pelatihan TPU Inception v3 menjalankan kurva akurasi pencocokan yang dihasilkan oleh tugas GPU dengan konfigurasi yang serupa. Model ini telah berhasil dilatih pada konfigurasi v2-8, v2-128, dan v2-512. Model ini telah mencapai akurasi lebih dari 78,1% dalam waktu sekitar 170 epoch.

Contoh kode yang ditampilkan dalam dokumen ini dimaksudkan sebagai ilustrasi, gambaran umum tentang apa yang terjadi dalam penerapan sebenarnya. Kode yang berfungsi dapat ditemukan di GitHub.

Pengantar

Inception v3 adalah model pengenalan citra yang telah terbukti mencapai akurasi lebih dari 78,1% pada set data ImageNet. Model ini adalah puncak dari banyak ide yang dikembangkan oleh beberapa peneliti selama bertahun-tahun. Sistem ini didasarkan pada makalah asli: "Rethinking the Inception Architecture for Computer Vision" oleh Szegedy, et. al.

Model itu sendiri terdiri dari elemen penyusun simetris dan asimetris, termasuk konvolusi, penggabungan rata-rata, penggabungan maksimum, penggabungan, dropout, dan lapisan yang terhubung sepenuhnya. Normalisasi batch digunakan secara luas di seluruh model dan diterapkan ke input aktivasi. Kerugian dihitung menggunakan Softmax.

Diagram tingkat tinggi model ditampilkan dalam screenshot berikut:

gambar

API Validator

Versi TPU Inception v3 ditulis menggunakan TPUEstimator, API yang dirancang untuk memfasilitasi pengembangan, sehingga Anda dapat berfokus pada model itu sendiri, bukan pada detail hardware yang mendasarinya. API melakukan sebagian besar pekerjaan grunge tingkat rendah yang diperlukan untuk menjalankan model di TPU di balik layar, sekaligus mengotomatiskan fungsi umum, seperti menyimpan dan memulihkan checkpoint.

Mengotomatiskan API menerapkan pemisahan bagian model dan input dari kode. Anda menentukan fungsi model_fn dan input_fn, yang sesuai dengan definisi model dan pipeline input. Kode berikut menunjukkan deklarasi fungsi ini:

def model_fn(features, labels, mode, params):
     …
  return tpu_estimator.TPUEstimatorSpec(mode=mode, loss=loss, train_op=train_op)

def input_fn(params):
    def parser(serialized_example):
          …
        return image, label

          …
   images, labels = dataset.make_one_shot_iterator().get_next()
   return images, labels

Dua fungsi utama yang disediakan oleh API ini adalah train() dan evaluate() yang digunakan untuk melatih dan mengevaluasi seperti yang ditunjukkan dalam kode berikut:

def main(unused_argv):
  …
  run_config = tpu_config.RunConfig(
      master=FLAGS.master,
      model_dir=FLAGS.model_dir,
      session_config=tf.ConfigProto(
          allow_soft_placement=True, log_device_placement=True),
      tpu_config=tpu_config.TPUConfig(FLAGS.iterations, FLAGS.num_shards),)

  estimator = tpu_estimator.TPUEstimator(
      model_fn=model_fn,
      use_tpu=FLAGS.use_tpu,
      train_batch_size=FLAGS.batch_size,
      eval_batch_size=FLAGS.batch_size,
      config=run_config)

  estimator.train(input_fn=input_fn, max_steps=FLAGS.train_steps)

  eval_results = inception_classifier.evaluate(
      input_fn=imagenet_eval.input_fn, steps=eval_steps)

Set data ImageNet

Sebelum dapat digunakan untuk mengenali gambar, model harus dilatih menggunakan sekumpulan besar gambar berlabel. ImageNet adalah set data umum untuk digunakan.

ImageNet memiliki lebih dari sepuluh juta URL gambar berlabel. Satu juta gambar juga memiliki kotak pembatas yang menentukan lokasi yang lebih akurat untuk objek berlabel.

Untuk model ini, set data ImageNet terdiri dari 1.331.167 gambar yang dibagi menjadi set data pelatihan dan evaluasi yang masing-masing berisi 1.281.167 dan 50.000 gambar.

Set data pelatihan dan evaluasi sengaja disimpan secara terpisah. Hanya gambar dari set data pelatihan yang digunakan untuk melatih model, dan hanya gambar dari set data evaluasi yang digunakan untuk mengevaluasi akurasi model.

Model ini mengharapkan gambar disimpan sebagai TFRecords. Untuk mengetahui informasi selengkapnya tentang cara mengonversi gambar dari file JPEG mentah ke TFRecords, lihat download_and_preprocess_imagenet.sh.

Pipeline input

Setiap perangkat Cloud TPU memiliki 8 core dan terhubung ke host (CPU). Slice yang lebih besar memiliki beberapa host. Konfigurasi yang lebih besar lainnya berinteraksi dengan beberapa {i>host<i}. Misalnya v2-256 berkomunikasi dengan 16 {i>host<i}.

Host mengambil data dari sistem file atau memori lokal, melakukan pra-pemrosesan data apa pun yang diperlukan, lalu mentransfer data yang telah diproses sebelumnya ke inti TPU. Kami mempertimbangkan tiga fase penanganan data ini yang dilakukan oleh host satu per satu dan merujuk pada fase-fase tersebut sebagai: 1) Penyimpanan, 2) Pra-pemrosesan, 3) Transfer. Gambar tingkat tinggi diagram ditampilkan dalam gambar berikut:

gambar

Untuk menghasilkan performa yang baik, sistem harus seimbang. Jika CPU host memerlukan waktu lebih lama daripada TPU untuk menyelesaikan tiga fase penanganan data, eksekusi akan terikat pada host. Kedua kasus tersebut ditampilkan dalam diagram berikut:

gambar

Implementasi Inception v3 saat ini berada di tepi batas input. Gambar diambil dari sistem file, didekode, lalu diproses sebelumnya. Berbagai jenis tahap pra-pemrosesan tersedia, mulai dari yang sedang hingga kompleks. Jika kita menggunakan tahap pra-pemrosesan yang paling kompleks, pipeline pelatihan akan terikat pada pra-pemrosesan. Anda dapat mencapai akurasi lebih dari 78,1% menggunakan tahap pra-pemrosesan yang cukup kompleks yang membuat model tetap terikat TPU.

Model ini menggunakan tf.data.Dataset untuk menangani pemrosesan pipeline input. Untuk informasi selengkapnya tentang cara mengoptimalkan pipeline input, lihat panduan performa set data.

Meskipun Anda dapat menentukan fungsi dan meneruskannya ke Validator API, class InputPipeline mengenkapsulasi semua fitur yang diperlukan.

Mengotomatiskan API memudahkan penggunaan class ini. Anda meneruskannya ke parameter input_fn fungsi train() dan evaluate(), seperti yang ditunjukkan dalam cuplikan kode berikut:

def main(unused_argv):

          …

  inception_classifier = tpu_estimator.TPUEstimator(
      model_fn=inception_model_fn,
      use_tpu=FLAGS.use_tpu,
      config=run_config,
      params=params,
      train_batch_size=FLAGS.train_batch_size,
      eval_batch_size=eval_batch_size,
      batch_axis=(batch_axis, 0))

          …

  for cycle in range(FLAGS.train_steps // FLAGS.train_steps_per_eval):
    tf.logging.info('Starting training cycle %d.' % cycle)
    inception_classifier.train(
        input_fn=InputPipeline(True), steps=FLAGS.train_steps_per_eval)

    tf.logging.info('Starting evaluation cycle %d .' % cycle)
    eval_results = inception_classifier.evaluate(
        input_fn=InputPipeline(False), steps=eval_steps, hooks=eval_hooks)
    tf.logging.info('Evaluation results: %s' % eval_results)

Elemen utama InputPipeline ditampilkan dalam cuplikan kode berikut.

class InputPipeline(object):

  def __init__(self, is_training):
    self.is_training = is_training

  def __call__(self, params):
    # Storage
    file_pattern = os.path.join(
        FLAGS.data_dir, 'train-*' if self.is_training else 'validation-*')
    dataset = tf.data.Dataset.list_files(file_pattern)
    if self.is_training and FLAGS.initial_shuffle_buffer_size > 0:
      dataset = dataset.shuffle(
          buffer_size=FLAGS.initial_shuffle_buffer_size)
    if self.is_training:
      dataset = dataset.repeat()

    def prefetch_dataset(filename):
      dataset = tf.data.TFRecordDataset(
          filename, buffer_size=FLAGS.prefetch_dataset_buffer_size)
      return dataset

    dataset = dataset.apply(
        tf.contrib.data.parallel_interleave(
            prefetch_dataset,
            cycle_length=FLAGS.num_files_infeed,
            sloppy=True))
    if FLAGS.followup_shuffle_buffer_size > 0:
      dataset = dataset.shuffle(
          buffer_size=FLAGS.followup_shuffle_buffer_size)

    # Preprocessing
    dataset = dataset.map(
        self.dataset_parser,
        num_parallel_calls=FLAGS.num_parallel_calls)

    dataset = dataset.prefetch(batch_size)
    dataset = dataset.apply(
        tf.contrib.data.batch_and_drop_remainder(batch_size))
    dataset = dataset.prefetch(2)  # Prefetch overlaps in-feed with training
    images, labels = dataset.make_one_shot_iterator().get_next()

    # Transfer
    return images, labels

Bagian penyimpanan dimulai dengan pembuatan set data dan mencakup pembacaan TFRecords dari penyimpanan (menggunakan tf.data.TFRecordDataset). Fungsi tujuan khusus repeat() dan shuffle() digunakan sesuai kebutuhan. Fungsi tf.contrib.data.parallel_interleave() memetakan fungsi prefetch_dataset() di seluruh inputnya untuk menghasilkan set data bertingkat, dan menghasilkan elemen-elemennya yang berselang-seling. Class ini mendapatkan elemen dari cycle_length set data bertingkat secara paralel, yang meningkatkan throughput. Argumen sloppy melonggarkan persyaratan bahwa output dihasilkan dalam urutan deterministik, dan memungkinkan implementasi melewati set data bertingkat yang elemennya tidak tersedia saat diminta.

Bagian pra-pemrosesan memanggil dataset.map(parser) yang kemudian memanggil fungsi parser tempat gambar telah diproses sebelumnya. Detail tahap pra-pemrosesan dibahas di bagian selanjutnya.

Bagian transfer (di akhir fungsi) menyertakan baris return images, labels. TPUAssessment mengambil nilai yang dikembalikan dan secara otomatis mentransfernya ke perangkat.

Gambar berikut menunjukkan contoh trace performa Cloud TPU dari Inception v3. Waktu komputasi TPU, mengabaikan kios yang memberi makan, adalah sekitar 815 milidetik.

gambar

Storage host ditulis ke rekaman aktivitas dan ditampilkan dalam screenshot berikut:

gambar

Pra-pemrosesan host, yang mencakup decoding gambar dan serangkaian fungsi distorsi gambar ditunjukkan dalam screenshot berikut:

gambar

Transfer Host/TPU ditampilkan dalam screenshot berikut:

gambar

Tahap Prapemrosesan

Prapemrosesan gambar adalah bagian penting dari sistem dan dapat memengaruhi akurasi maksimum yang dicapai model selama pelatihan. Setidaknya, gambar harus didekode dan diubah ukurannya agar sesuai dengan model. Untuk Inception, gambar harus berukuran 299x299x3 piksel.

Namun, melakukan dekode dan pengubahan ukuran saja tidaklah cukup untuk mendapatkan akurasi yang baik. {i>Dataset<i} pelatihan ImageNet berisi 1.281.167 gambar. Satu penerusan pada kumpulan gambar pelatihan disebut sebagai epoch. Selama pelatihan, model memerlukan beberapa penerusan set data pelatihan untuk meningkatkan kemampuan pengenalan gambarnya. Untuk melatih Inception v3 ke akurasi yang memadai, gunakan antara 140 dan 200 epoch, bergantung pada ukuran batch global.

Sebaiknya ubah gambar secara terus-menerus sebelum memasukkannya ke model, sehingga gambar tertentu sedikit berbeda di setiap epoch. Cara terbaik untuk melakukan pra-pemrosesan gambar ini sama pentingnya dengan seni seperti halnya sains. Tahap pra-pemrosesan yang dirancang dengan baik dapat meningkatkan kemampuan pengenalan model secara signifikan. Tahap pra-pemrosesan yang terlalu sederhana dapat menghasilkan batas buatan pada akurasi yang dapat dicapai oleh model yang sama selama pelatihan.

Inception v3 menawarkan opsi untuk tahap pra-pemrosesan, mulai dari yang relatif sederhana dan murah secara komputasi hingga yang cukup kompleks dan mahal secara komputasi. Dua ragam berbeda dapat ditemukan di file vgg_preprocessing.py dan inception_preprocessing.py.

File vgg_preprocessing.py menentukan tahap pra-pemrosesan yang telah berhasil digunakan untuk melatih resnet hingga akurasi 75%, tetapi memberikan hasil yang kurang optimal saat diterapkan pada Inception v3.

File inception_preprocessing.py berisi tahap pra-pemrosesan yang telah digunakan untuk melatih Inception v3 dengan akurasi antara 78,1 dan 78,5% saat dijalankan di TPU.

Prapemrosesan berbeda-beda bergantung pada apakah model sedang menjalani pelatihan atau digunakan untuk inferensi/evaluasi.

Pada waktu evaluasi, prapemrosesannya mudah: pangkas area pusat gambar, lalu ubah ukurannya ke ukuran default 299x299. Cuplikan kode berikut menunjukkan implementasi pra-pemrosesan:

def preprocess_for_eval(image, height, width, central_fraction=0.875):
  with tf.name_scope(scope, 'eval_image', [image, height, width]):
    if image.dtype != tf.float32:
      image = tf.image.convert_image_dtype(image, dtype=tf.float32)
    image = tf.image.central_crop(image, central_fraction=central_fraction)
    image = tf.expand_dims(image, 0)
    image = tf.image.resize_bilinear(image, [height, width], align_corners=False)
    image = tf.squeeze(image, [0])
    image = tf.subtract(image, 0.5)
    image = tf.multiply(image, 2.0)
    image.set_shape([height, width, 3])
    return image

Selama pelatihan, pemangkasan akan diacak: kotak pembatas dipilih secara acak untuk memilih area gambar yang kemudian diubah ukurannya. Gambar yang diubah ukurannya kemudian dibalik secara opsional dan warnanya terdistorsi. Cuplikan kode berikut menunjukkan implementasi operasi ini:

def preprocess_for_train(image, height, width, bbox, fast_mode=True, scope=None):
  with tf.name_scope(scope, 'distort_image', [image, height, width, bbox]):
    if bbox is None:
      bbox = tf.constant([0.0, 0.0, 1.0, 1.0], dtype=tf.float32, shape=[1, 1, 4])
    if image.dtype != tf.float32:
      image = tf.image.convert_image_dtype(image, dtype=tf.float32)

    distorted_image, distorted_bbox = distorted_bounding_box_crop(image, bbox)
    distorted_image.set_shape([None, None, 3])

    num_resize_cases = 1 if fast_mode else 4
    distorted_image = apply_with_random_selector(
        distorted_image,
        lambda x, method: tf.image.resize_images(x, [height, width], method),
        num_cases=num_resize_cases)

    distorted_image = tf.image.random_flip_left_right(distorted_image)

    if FLAGS.use_fast_color_distort:
      distorted_image = distort_color_fast(distorted_image)
    else:
      num_distort_cases = 1 if fast_mode else 4
      distorted_image = apply_with_random_selector(
          distorted_image,
          lambda x, ordering: distort_color(x, ordering, fast_mode),
          num_cases=num_distort_cases)

    distorted_image = tf.subtract(distorted_image, 0.5)
    distorted_image = tf.multiply(distorted_image, 2.0)
    return distorted_image

Fungsi distort_color bertanggung jawab atas perubahan warna. Fitur ini menawarkan mode cepat dengan hanya kecerahan dan saturasi yang diubah. Mode lengkap mengubah kecerahan, saturasi, dan hue, dalam urutan acak.

def distort_color(image, color_ordering=0, fast_mode=True, scope=None):
  with tf.name_scope(scope, 'distort_color', [image]):
    if fast_mode:
      if color_ordering == 0:
        image = tf.image.random_brightness(image, max_delta=32. / 255.)
        image = tf.image.random_saturation(image, lower=0.5, upper=1.5)
      else:
        image = tf.image.random_saturation(image, lower=0.5, upper=1.5)
        image = tf.image.random_brightness(image, max_delta=32. / 255.)
    else:
      if color_ordering == 0:
        image = tf.image.random_brightness(image, max_delta=32. / 255.)
        image = tf.image.random_saturation(image, lower=0.5, upper=1.5)
        image = tf.image.random_hue(image, max_delta=0.2)
        image = tf.image.random_contrast(image, lower=0.5, upper=1.5)
      elif color_ordering == 1:
        image = tf.image.random_saturation(image, lower=0.5, upper=1.5)
        image = tf.image.random_brightness(image, max_delta=32. / 255.)
        image = tf.image.random_contrast(image, lower=0.5, upper=1.5)
        image = tf.image.random_hue(image, max_delta=0.2)
      elif color_ordering == 2:
        image = tf.image.random_contrast(image, lower=0.5, upper=1.5)
        image = tf.image.random_hue(image, max_delta=0.2)
        image = tf.image.random_brightness(image, max_delta=32. / 255.)
        image = tf.image.random_saturation(image, lower=0.5, upper=1.5)
      elif color_ordering == 3:
        image = tf.image.random_hue(image, max_delta=0.2)
        image = tf.image.random_saturation(image, lower=0.5, upper=1.5)
        image = tf.image.random_contrast(image, lower=0.5, upper=1.5)
        image = tf.image.random_brightness(image, max_delta=32. / 255.)

    return tf.clip_by_value(image, 0.0, 1.0)

Fungsi distort_color mahal secara komputasi, sebagian karena RGB nonlinear ke HSV dan HSV ke RGB yang diperlukan untuk mengakses hue dan saturasi. Mode cepat dan penuh memerlukan konversi ini dan meskipun mode cepat lebih murah secara komputasi, mode ini tetap mendorong model ke region yang terikat komputasi CPU, jika diaktifkan.

Sebagai alternatif, fungsi baru distort_color_fast telah ditambahkan ke daftar opsi. Fungsi ini memetakan gambar dari RGB ke YCrCb menggunakan skema konversi JPEG dan secara acak mengubah kecerahan dan kroma Cr/Cb sebelum memetakan kembali ke RGB. Cuplikan kode berikut menunjukkan implementasi fungsi ini:

def distort_color_fast(image, scope=None):
  with tf.name_scope(scope, 'distort_color', [image]):
    br_delta = random_ops.random_uniform([], -32./255., 32./255., seed=None)
    cb_factor = random_ops.random_uniform(
        [], -FLAGS.cb_distortion_range, FLAGS.cb_distortion_range, seed=None)
    cr_factor = random_ops.random_uniform(
        [], -FLAGS.cr_distortion_range, FLAGS.cr_distortion_range, seed=None)

    channels = tf.split(axis=2, num_or_size_splits=3, value=image)
    red_offset = 1.402 * cr_factor + br_delta
    green_offset = -0.344136 * cb_factor - 0.714136 * cr_factor + br_delta
    blue_offset = 1.772 * cb_factor + br_delta
    channels[0] += red_offset
    channels[1] += green_offset
    channels[2] += blue_offset
    image = tf.concat(axis=2, values=channels)
    image = tf.clip_by_value(image, 0., 1.)
    return image

Berikut gambar contoh yang telah melalui pra-pemrosesan. Region gambar yang dipilih secara acak telah dipilih dan warna diubah menggunakan fungsi distort_color_fast.

gambar

Fungsi distort_color_fast efisien secara komputasi dan masih memungkinkan pelatihan terikat waktu eksekusi TPU. Selain itu, model ini telah digunakan untuk melatih model Inception v3 hingga akurasi lebih dari 78,1% menggunakan ukuran tumpukan dalam rentang 1.024-16.384.

Taktis

Model saat ini menampilkan tiga ragam pengoptimal: SGD, momentum, dan RMSProp.

Stochastic gradient descent (SGD) adalah pembaruan yang paling sederhana: bobot didorong ke arah gradien negatif. Meskipun sederhana, hasil yang baik masih dapat diperoleh pada beberapa model. Dinamika update dapat ditulis sebagai:

$$w_{k+1}=w_k-\alpha∇f(w_k)$$

Momentum adalah pengoptimal populer yang sering menyebabkan konvergensi lebih cepat daripada SGD. Pengoptimal ini memperbarui bobot seperti halnya SGD, tetapi juga menambahkan komponen ke arah pembaruan sebelumnya. Persamaan berikut menjelaskan pembaruan yang dilakukan oleh pengoptimal momentum:

$$z_{k+1}=\beta z_k+∇f(w_k)$$
$$w_{k+1}=w_k-\alpha z_{k+1}$$

yang dapat ditulis sebagai:

$$w_{k+1}=w_k-\alpha ∇f(w_k)+\beta \left(w_k-w_{k-1}\right)$$

Istilah terakhir adalah komponen ke arah pembaruan sebelumnya.

gambar

Untuk momentum \({\beta}\), kita gunakan nilai 0,9.

RMSprop adalah pengoptimal populer yang pertama kali diusulkan oleh Geoff Hinton dalam salah satu presentasinya. Persamaan berikut menjelaskan cara kerja pengoptimal:

$$g_{k+1}^{-2} = \alpha g_{k}^{-2} + (1-\alpha) g_{k}^2$$ $$w_{k+1}=\beta w_k + \frac{\eta}{\sqrt {g_{k+1^{\math{+{\ep-bf)}}

Untuk Inception v3, pengujian menunjukkan bahwa RMSProp memberikan hasil terbaik dalam hal akurasi dan waktu maksimum untuk mencapainya, dengan momentum mendekati satu detik. Dengan demikian, RMSprop ditetapkan sebagai pengoptimal default. Parameter yang digunakan adalah: peluruhan \({\alpha}\) = 0,9, momentum \({\beta}\) = 0,9, dan \({\epsilon}\) = 1,0.

Cuplikan kode berikut menunjukkan cara menetapkan parameter ini:

if FLAGS.optimizer == 'sgd':
  tf.logging.info('Using SGD optimizer')
  optimizer = tf.train.GradientDescentOptimizer(
      learning_rate=learning_rate)
elif FLAGS.optimizer == 'momentum':
  tf.logging.info('Using Momentum optimizer')
  optimizer = tf.train.MomentumOptimizer(
      learning_rate=learning_rate, momentum=0.9)
elif FLAGS.optimizer == 'RMS':
  tf.logging.info('Using RMS optimizer')
  optimizer = tf.train.RMSPropOptimizer(
      learning_rate,
      RMSPROP_DECAY,
      momentum=RMSPROP_MOMENTUM,
      epsilon=RMSPROP_EPSILON)
else:
  tf.logging.fatal('Unknown optimizer:', FLAGS.optimizer)

Saat berjalan di TPU dan menggunakan Validator API, pengoptimal harus digabungkan dalam fungsi CrossShardOptimizer untuk memastikan sinkronisasi di antara replika (beserta komunikasi silang yang diperlukan). Cuplikan kode berikut menunjukkan cara model Inception v3 menggabungkan pengoptimal:

if FLAGS.use_tpu:
    optimizer = tpu_optimizer.CrossShardOptimizer(optimizer)

update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
  train_op = optimizer.minimize(loss, global_step=global_step)

Rata-rata pergerakan eksponensial

Saat pelatihan, parameter yang dapat dilatih akan diupdate selama propagasi mundur sesuai dengan aturan update pengoptimal. Persamaan yang menjelaskan aturan ini telah dibahas di bagian sebelumnya dan diulang di sini untuk memudahkan:

$${\theta_{k+1}} = {\theta_k}-{\alpha ∇f(\theta_k)} \qquad(SGD)$$
$${\theta_{k+1}}={\theta_k}-{\alpha z_{k+1}} \qquad(Momentum)$$
$${\theta_{k+1}}= {\beta \theta_k}+\frac{\eta}{\sqrt {g_{k+1^{\mathbf+{\epsilon}}}}^{-2}} ∇f(\theta_k) \qquad(RMSprop)$$

Rata-rata pergerakan eksponensial (juga dikenal sebagai smoothing eksponensial) adalah langkah pascapemrosesan opsional yang diterapkan pada bobot yang diperbarui dan terkadang dapat menyebabkan peningkatan performa yang signifikan. TensorFlow menyediakan fungsi tf.train.ExponentialMovingAverage yang menghitung ema \({\hat{\theta}}\) berat \({\theta}\) menggunakan rumus:

$${\hat{\theta_t}}={\alpha {\topi{\theta}{_{t-1}}}}+{(1-\alpha)}{\theta_t}$$

dengan \({\alpha}\) adalah faktor peluruhan (mendekati 1,0). Pada model Inception v3,\({\alpha}\) disetel ke 0,995.

Meskipun penghitungan ini adalah filter Infinite Impulse Response (IIR), faktor peluruhan menetapkan periode efektif tempat sebagian besar energi (atau sampel yang relevan) berada, seperti yang ditunjukkan dalam diagram berikut:

gambar

Kita dapat menulis ulang persamaan filter, seperti berikut:

$${\hat{\theta}_{t+T+1}}={\alpha(1-\alpha)}({\theta_{t+T}}+{\alpha \theta_{t+T-1}}+...+{\alpha^{t+T}}{\theta_0})$$

di mana kita menggunakan \({\hat\theta_{-1}}=0\).

Nilai \({\alpha}^k\) melemah dengan meningkatnya k, jadi hanya sebagian sampel yang akan memiliki pengaruh yang cukup besar pada \(\hat{\theta}_{t+T+1}\). Aturan praktis untuk nilai faktor peluruhan adalah: \(\frac {1} {1-\alpha}\), yang sesuai dengan \({\alpha}\) = 200 untuk =0,995.

Pertama-tama, kita akan mendapatkan kumpulan variabel yang dapat dilatih, lalu menggunakan metode apply() untuk membuat variabel bayangan untuk setiap variabel yang dilatih. Cuplikan kode berikut menunjukkan implementasi model Inception v3:

update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
  train_op = optimizer.minimize(loss, global_step=global_step)

if FLAGS.moving_average:
  ema = tf.train.ExponentialMovingAverage(
      decay=MOVING_AVERAGE_DECAY, num_updates=global_step)
  variables_to_average = (tf.trainable_variables() +
                          tf.moving_average_variables())
  with tf.control_dependencies([train_op]), tf.name_scope('moving_average'):
    train_op = ema.apply(variables_to_average)

Kita ingin menggunakan variabel ema selama evaluasi. Kami menentukan class LoadEMAHook yang menerapkan metode variables_to_restore() ke file checkpoint untuk mengevaluasi menggunakan nama variabel bayangan:

class LoadEMAHook(tf.train.SessionRunHook):
  def __init__(self, model_dir):
    super(LoadEMAHook, self).__init__()
    self._model_dir = model_dir

  def begin(self):
    ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY)
    variables_to_restore = ema.variables_to_restore()
    self._load_ema = tf.contrib.framework.assign_from_checkpoint_fn(
        tf.train.latest_checkpoint(self._model_dir), variables_to_restore)

  def after_create_session(self, sess, coord):
    tf.logging.info('Reloading EMA...')
    self._load_ema(sess)

Fungsi hooks diteruskan ke evaluate() seperti ditunjukkan dalam cuplikan kode berikut:

if FLAGS.moving_average:
    eval_hooks = [LoadEMAHook(FLAGS.model_dir)]
else:
    eval_hooks = []

    …

eval_results = inception_classifier.evaluate(
    input_fn=InputPipeline(False), steps=eval_steps, hooks=eval_hooks)

Normalisasi batch

Normalisasi batch adalah teknik yang banyak digunakan untuk menormalisasi fitur input pada model yang dapat menyebabkan pengurangan besar dalam waktu konvergensi. Ini adalah salah satu peningkatan algoritma yang lebih populer dan berguna dalam machine learning dalam beberapa tahun terakhir dan digunakan di berbagai model, termasuk Inception v3.

Input aktivasi dinormalisasi dengan mengurangi rata-rata dan membaginya dengan simpangan baku. Untuk menjaga keseimbangan dengan adanya propagasi kembali, dua parameter yang dapat dilatih diperkenalkan di setiap lapisan. Output yang dinormalkan \({\hat{x}}\) mengalami operasi berikutnya \({\gamma\hat{x}}+\beta\), dengan \({\gamma}\) dan\({\beta}\) adalah semacam simpangan baku dan rata-rata yang dipelajari oleh model itu sendiri.

Kumpulan persamaan lengkap ada dalam makalah dan diulang di sini untuk memudahkan:

Masukan: Nilai x pada tumpukan mini: \(\Phi=\) { \({x_{1..m}\\} \) } Parameter yang akan dipelajari: \({\gamma}\),\({\beta}\)

Output: { \({y_i}=BN_{\gamma,\beta}{(x_i)}\) }

\[{\mu_\phi} \leftarrow {\frac{1}{m}}{\sum_{i=1}^m}x_i \qquad \mathsf(mini-batch\ mean)\]

\[{\sigma_\phi}^2 \leftarrow {\frac{1}{m}}{\sum_{i=1}^m} {(x_i - {\mu_\phi})^2} \qquad \mathbf(mini-batch\ variance)\]

\[{\hat{x_i}} \leftarrow {\frac{x_i-{\mu_\phi}}{\sqrt {\sigma^2_\phi}+{\epsilon}}}\qquad \mathbf(normalisasi)\]

\[{y_i}\leftarrow {\gamma \hat{x_i}} + \beta \equiv BN_{\gamma,\beta}{(x_i)}\qquad \mathbf(skala \ dan \ shift)\]

Normalisasi terjadi selama pelatihan, tetapi pada waktu evaluasi, kita ingin model berperilaku dengan cara deterministik: hasil klasifikasi gambar harus hanya bergantung pada gambar input, bukan kumpulan gambar yang dimasukkan ke model. Dengan demikian, kita perlu memperbaiki \({\mu}\) dan \({\sigma}^2\) dan menggunakan nilai yang mewakili statistik populasi gambar.

Model ini menghitung rata-rata pergerakan dari mean dan varians pada minibatch:

\[{\hat\mu_i} = {\alpha \hat\mu_{t-1}}+{(1-\alpha)\mu_t}\]

\[{\hat\sigma_t}^2 = {\alpha{\hat\sigma^2_{t-1}}} + {(1-\alpha) {\sigma_t}^2}\]

Dalam kasus spesifik Inception v3, faktor peluruhan yang logis telah diperoleh (menggunakan penyesuaian hyperparameter) untuk digunakan dalam GPU. Kita ingin menggunakan nilai ini di TPU juga, tetapi untuk melakukannya, kita perlu melakukan beberapa penyesuaian.

Rata-rata dan varians pergerakan normalisasi batch dihitung menggunakan filter lulus kehilangan, seperti yang ditunjukkan dalam persamaan berikut (di sini, \({y_t}\) mewakili mean bergerak atau varians):

\[{y_t}={\alpha y_{t-1}}+{(1-\alpha)}{x_t} \]

(1)

Dalam tugas GPU 8x1 (sinkron), setiap replika membaca rata-rata pergerakan saat ini dan mengupdatenya. Replika saat ini harus menulis variabel bergerak baru sebelum replika berikutnya dapat membacanya.

Jika ada 8 replika, kumpulan operasi untuk update ensemble adalah sebagai berikut:

\[{y_t}={\alpha y_{t-1}}+{(1-\alpha)}{x_t} \]

\[{y_{t+1}}={\alpha y_{t}}+{(1-\alpha)}{x_{t+1}} \]

\[{y_{t+2}}={\alpha y_{t+1}}+{(1-\alpha)}{x_{t+2}} \]

\[{y_{t+3}}={\alpha y_{t+2}}+{(1-\alpha)}{x_{t+3}} \]

\[{y_{t+4}}={\alpha y_{t+3}}+{(1-\alpha)}{x_{t+4}} \]

\[{y_{t+5}}={\alpha y_{t+4}}+{(1-\alpha)}{x_{t+5}} \]

\[{y_{t+6}}={\alpha y_{t+5}}+{(1-\alpha)}{x_{t+6}} \]

\[{y_{t+7}}={\alpha y_{t+6}}+{(1-\alpha)}{x_{t+7}} \]

Kumpulan 8 pembaruan berurutan ini dapat ditulis sebagai:

\[{y_{t+7}}={\alpha^8y_{t-1}}+(1-\alpha){\sum_{k=0}^7} {\alpha^{7-k}}{x_{t+k}}\]

(2)

Dalam implementasi penghitungan momen bergerak saat ini pada TPU, setiap shard melakukan penghitungan secara independen dan tidak ada komunikasi lintas-shard. Batch didistribusikan ke setiap shard dan masing-masing memproses 1/8 dari total batch (jika ada 8 shard).

Meskipun setiap shard menghitung momen bergerak (yaitu, rata-rata dan varians), hanya hasil dari shard 0 yang dikomunikasikan kembali ke CPU host. Jadi, secara efektif, hanya satu replika yang melakukan update rata-rata/varian bergerak:

\[{z_t}={\beta {z_{t-1}}}+{(1-\beta)u_t}\]

(3)

dan pembaruan ini terjadi pada 1/8 tingkat tingkat dari pasangan berurutannya. Untuk membandingkan persamaan update GPU dan TPU, kita perlu menyelaraskan skala waktu masing-masing. Secara khusus, satu set operasi yang membentuk 8 update berurutan pada GPU harus dibandingkan dengan satu update pada TPU seperti yang diilustrasikan dalam diagram berikut:

gambar

Mari kita tampilkan persamaan dengan indeks waktu yang diubah:

\[{y_t}={\alpha^8y_{t-1}}+(1-\alpha){\sum_{k=0}^7} {\alpha^{7-k}}{x_{t-k/8}} \qquad \mathsf(GPU)\]

\[{z_t}={\beta {z_{t-1}}}+{(1-\beta)u_t}\qquad \mathsf(TPU) \]

Jika kita membuat asumsi bahwa 8 batch mini (dinormalisasi di seluruh dimensi yang relevan) menghasilkan nilai yang serupa dalam update berurutan GPU 8-minibatch, kita dapat memperkirakan persamaan ini sebagai berikut:

\[{y_t}={\alpha^8y_{t-1}}+(1-\alpha){\sum_{k=0}^7} {\alpha^{7-k}}{\hat{x_t}}={\alpha^8y_{t-1}+(1-\alpha^8){\hat{x_t}}} \qquad \math]sf(GPU)

\[{z_t}={\beta {z_{t-1}}}+{(1-\beta)u_t}\qquad \mathsf(TPU) \]

Untuk mencocokkan efek faktor peluruhan tertentu pada GPU, kita akan memodifikasi faktor peluruhan pada TPU. Secara khusus, kita menetapkan \({\beta}\)=\({\alpha}^8\).

Untuk Inception v3, nilai peluruhan yang digunakan dalam GPU adalah \({\alpha}\)=0,9997, yang diterjemahkan menjadi nilai peluruhan TPU \({\beta}\)=0,9976.

Adaptasi kecepatan pembelajaran

Seiring bertambahnya ukuran tumpukan, pelatihan menjadi lebih sulit. Berbagai teknik terus diusulkan guna memungkinkan pelatihan yang efisien untuk ukuran batch besar (misalnya lihat di sini, di sini, dan di sini).

Salah satu teknik ini adalah meningkatkan kecepatan pembelajaran secara bertahap (juga disebut peningkatan). Peningkatan digunakan untuk melatih model agar memiliki akurasi lebih dari 78,1% untuk ukuran batch yang berkisar antara 4.096 hingga 16.384. Untuk Inception v3, kecepatan pemelajaran pertama-tama ditetapkan menjadi sekitar 10% dari kecepatan pemelajaran awal yang biasanya. Kecepatan pembelajaran tetap konstan pada nilai rendah ini untuk jumlah 'epoch dingin' yang ditentukan (kecil), lalu memulai peningkatan linear untuk jumlah 'epoch pemanasan' yang ditentukan. Di akhir 'epoch pemanasan', kecepatan pemelajaran berpotongan dengan pemelajaran peluruhan eksponensial normal. Hal ini diilustrasikan dalam diagram berikut.

gambar

Cuplikan kode berikut menunjukkan cara melakukannya:

initial_learning_rate = FLAGS.learning_rate * FLAGS.train_batch_size / 256
if FLAGS.use_learning_rate_warmup:
  warmup_decay = FLAGS.learning_rate_decay**(
    (FLAGS.warmup_epochs + FLAGS.cold_epochs) /
    FLAGS.learning_rate_decay_epochs)
  adj_initial_learning_rate = initial_learning_rate * warmup_decay

final_learning_rate = 0.0001 * initial_learning_rate

train_op = None
if training_active:
  batches_per_epoch = _NUM_TRAIN_IMAGES / FLAGS.train_batch_size
  global_step = tf.train.get_or_create_global_step()
  current_epoch = tf.cast(
    (tf.cast(global_step, tf.float32) / batches_per_epoch), tf.int32)

  learning_rate = tf.train.exponential_decay(
    learning_rate=initial_learning_rate,
    global_step=global_step,
    decay_steps=int(FLAGS.learning_rate_decay_epochs * batches_per_epoch),
    decay_rate=FLAGS.learning_rate_decay,
    staircase=True)

  if FLAGS.use_learning_rate_warmup:
    wlr = 0.1 * adj_initial_learning_rate
    wlr_height = tf.cast(
      0.9 * adj_initial_learning_rate /
      (FLAGS.warmup_epochs + FLAGS.learning_rate_decay_epochs - 1),
      tf.float32)
    epoch_offset = tf.cast(FLAGS.cold_epochs - 1, tf.int32)
    exp_decay_start = (FLAGS.warmup_epochs + FLAGS.cold_epochs +
                   FLAGS.learning_rate_decay_epochs)
    lin_inc_lr = tf.add(
      wlr, tf.multiply(
        tf.cast(tf.subtract(current_epoch, epoch_offset), tf.float32),
        wlr_height))
    learning_rate = tf.where(
      tf.greater_equal(current_epoch, FLAGS.cold_epochs),
      (tf.where(tf.greater_equal(current_epoch, exp_decay_start),
              learning_rate, lin_inc_lr)),
       wlr)

  # Set a minimum boundary for the learning rate.
  learning_rate = tf.maximum(
      learning_rate, final_learning_rate, name='learning_rate')