TensorFlow.org'da görüntüleyin | Google Colab'da çalıştırın | Kaynağı GitHub'da görüntüleyin | Not defterini indir |
Bu öğreticide, önceden eğitilmiş bir ağdan aktarım öğrenimini kullanarak kedi ve köpek görüntülerini nasıl sınıflandıracağınızı öğreneceksiniz.
Önceden eğitilmiş bir model, daha önce büyük bir veri kümesinde, genellikle büyük ölçekli bir görüntü sınıflandırma görevinde eğitilmiş kayıtlı bir ağdır. Ya önceden eğitilmiş modeli olduğu gibi kullanırsınız ya da bu modeli belirli bir göreve göre özelleştirmek için transfer öğrenimini kullanırsınız.
Görüntü sınıflandırması için transfer öğreniminin arkasındaki sezgi, bir modelin yeterince geniş ve genel bir veri kümesi üzerinde eğitilmesi durumunda, bu modelin görsel dünyanın genel bir modeli olarak etkin bir şekilde hizmet edeceğidir. Daha sonra, büyük bir modeli büyük bir veri kümesi üzerinde eğiterek sıfırdan başlamak zorunda kalmadan bu öğrenilen özellik haritalarından yararlanabilirsiniz.
Bu not defterinde, önceden eğitilmiş bir modeli özelleştirmek için iki yol deneyeceksiniz:
Özellik Çıkarma: Yeni örneklerden anlamlı özellikler çıkarmak için önceki bir ağ tarafından öğrenilen temsilleri kullanın. Önceden eğitilmiş modelin üzerine sıfırdan eğitilecek yeni bir sınıflandırıcı eklemeniz yeterlidir, böylece daha önce öğrenilen özellik haritalarını veri kümesi için yeniden kullanabilirsiniz.
Tüm modeli (yeniden) eğitmeniz gerekmez. Temel evrişim ağı, resimleri sınıflandırmak için genel olarak yararlı olan özellikleri zaten içerir. Bununla birlikte, önceden eğitilmiş modelin son sınıflandırma kısmı, orijinal sınıflandırma görevine ve daha sonra modelin üzerinde eğitildiği sınıflar kümesine özgüdür.
İnce Ayar: Donmuş bir model tabanının birkaç üst katmanını çözün ve hem yeni eklenen sınıflandırıcı katmanlarını hem de temel modelin son katmanlarını birlikte eğitin. Bu, belirli görev için onları daha alakalı hale getirmek için temel modeldeki yüksek mertebeden özellik temsillerine "ince ayar yapmamızı" sağlar.
Genel makine öğrenimi iş akışını takip edeceksiniz.
- Verileri inceleyin ve anlayın
- Bu durumda Keras ImageDataGenerator kullanarak bir girdi ardışık düzeni oluşturun
- Modeli oluşturun
- Önceden eğitilmiş temel modelde (ve önceden eğitilmiş ağırlıklarda) yük
- Sınıflandırma katmanlarını üstte yığın
- Modeli eğit
- Modeli değerlendir
import matplotlib.pyplot as plt
import numpy as np
import os
import tensorflow as tf
Veri ön işleme
Veri indirme
Bu öğreticide, kedi ve köpeklerin birkaç bin görüntüsünü içeren bir veri kümesi kullanacaksınız. Görüntüleri içeren bir zip dosyasını indirin ve ayıklayın, ardından tf.keras.utils.image_dataset_from_directory
yardımcı programını kullanarak eğitim ve doğrulama için bir tf.data.Dataset
oluşturun. Bu eğitimde resim yükleme hakkında daha fazla bilgi edinebilirsiniz.
_URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'
path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=_URL, extract=True)
PATH = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')
train_dir = os.path.join(PATH, 'train')
validation_dir = os.path.join(PATH, 'validation')
BATCH_SIZE = 32
IMG_SIZE = (160, 160)
train_dataset = tf.keras.utils.image_dataset_from_directory(train_dir,
shuffle=True,
batch_size=BATCH_SIZE,
image_size=IMG_SIZE)
Downloading data from https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip 68608000/68606236 [==============================] - 1s 0us/step 68616192/68606236 [==============================] - 1s 0us/step Found 2000 files belonging to 2 classes.yer tutucu3 l10n-yer
validation_dataset = tf.keras.utils.image_dataset_from_directory(validation_dir,
shuffle=True,
batch_size=BATCH_SIZE,
image_size=IMG_SIZE)
Found 1000 files belonging to 2 classes.
Eğitim setindeki ilk dokuz resmi ve etiketi gösterin:
class_names = train_dataset.class_names
plt.figure(figsize=(10, 10))
for images, labels in train_dataset.take(1):
for i in range(9):
ax = plt.subplot(3, 3, i + 1)
plt.imshow(images[i].numpy().astype("uint8"))
plt.title(class_names[labels[i]])
plt.axis("off")
Orijinal veri kümesi bir test kümesi içermediğinden, bir tane oluşturacaksınız. Bunu yapmak için, tf.data.experimental.cardinality
kullanarak doğrulama kümesinde kaç tane veri grubu bulunduğunu belirleyin, ardından bunların %20'sini bir test kümesine taşıyın.
val_batches = tf.data.experimental.cardinality(validation_dataset)
test_dataset = validation_dataset.take(val_batches // 5)
validation_dataset = validation_dataset.skip(val_batches // 5)
print('Number of validation batches: %d' % tf.data.experimental.cardinality(validation_dataset))
print('Number of test batches: %d' % tf.data.experimental.cardinality(test_dataset))
-yer tutucu8 l10n-yerNumber of validation batches: 26 Number of test batches: 6
Performans için veri kümesini yapılandırın
G/Ç'yi engellemeden görüntüleri diskten yüklemek için arabelleğe alınmış önceden getirmeyi kullanın. Bu yöntem hakkında daha fazla bilgi edinmek için veri performansı kılavuzuna bakın.
AUTOTUNE = tf.data.AUTOTUNE
train_dataset = train_dataset.prefetch(buffer_size=AUTOTUNE)
validation_dataset = validation_dataset.prefetch(buffer_size=AUTOTUNE)
test_dataset = test_dataset.prefetch(buffer_size=AUTOTUNE)
Veri büyütmeyi kullan
Büyük bir görüntü veri kümeniz olmadığında, eğitim görüntülerine döndürme ve yatay çevirme gibi rastgele ancak gerçekçi dönüşümler uygulayarak örnek çeşitliliğini yapay olarak tanıtmak iyi bir uygulamadır. Bu, modeli eğitim verilerinin farklı yönlerine maruz bırakmaya ve fazla uydurmayı azaltmaya yardımcı olur. Bu eğitimde veri büyütme hakkında daha fazla bilgi edinebilirsiniz.
data_augmentation = tf.keras.Sequential([
tf.keras.layers.RandomFlip('horizontal'),
tf.keras.layers.RandomRotation(0.2),
])
Bu katmanları tekrar tekrar aynı görüntüye uygulayalım ve sonucu görelim.
for image, _ in train_dataset.take(1):
plt.figure(figsize=(10, 10))
first_image = image[0]
for i in range(9):
ax = plt.subplot(3, 3, i + 1)
augmented_image = data_augmentation(tf.expand_dims(first_image, 0))
plt.imshow(augmented_image[0] / 255)
plt.axis('off')
Piksel değerlerini yeniden ölçeklendir
Birazdan, temel modeliniz olarak kullanmak için tf.keras.applications.MobileNetV2
. Bu model [-1, 1]
içinde piksel değerleri bekler ancak bu noktada resimlerinizdeki piksel değerleri [0, 255]
. Bunları yeniden ölçeklendirmek için modele dahil olan ön işleme yöntemini kullanın.
preprocess_input = tf.keras.applications.mobilenet_v2.preprocess_input
rescale = tf.keras.layers.Rescaling(1./127.5, offset=-1)
Önceden eğitilmiş convnet'lerden temel modeli oluşturun
Google'da geliştirilen MobileNet V2 modelinden temel modeli oluşturacaksınız. Bu, 1,4M görüntü ve 1000 sınıftan oluşan büyük bir veri kümesi olan ImageNet veri kümesinde önceden eğitilmiştir. ImageNet, jackfruit
ve syringe
gibi çok çeşitli kategorilere sahip bir araştırma eğitimi veri setidir. Bu bilgi temeli, kedileri ve köpekleri belirli veri kümemizden sınıflandırmamıza yardımcı olacaktır.
Öncelikle, özellik çıkarımı için hangi MobileNet V2 katmanını kullanacağınızı seçmeniz gerekir. En son sınıflandırma katmanı (makine öğrenimi modellerinin çoğu diyagramı aşağıdan yukarıya gittiği için "üstte") çok kullanışlı değildir. Bunun yerine, düzleştirme işleminden önceki en son katmana bağlı olmak için yaygın uygulamayı izleyeceksiniz. Bu katmana "darboğaz katmanı" denir. Darboğaz katmanı özellikleri, son/üst katmana kıyasla daha fazla genellik sağlar.
İlk olarak, ImageNet üzerinde eğitilmiş ağırlıklarla önceden yüklenmiş bir MobileNet V2 modelini somutlaştırın. include_top=False bağımsız değişkenini belirterek, en üstte sınıflandırma katmanlarını içermeyen bir ağ yüklersiniz, bu özellik çıkarma için idealdir.
# Create the base model from the pre-trained model MobileNet V2
IMG_SHAPE = IMG_SIZE + (3,)
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE,
include_top=False,
weights='imagenet')
tutucu15 l10n-yerDownloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet_v2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_160_no_top.h5 9412608/9406464 [==============================] - 0s 0us/step 9420800/9406464 [==============================] - 0s 0us/step
Bu özellik çıkarıcı, her 160x160x3
görüntüyü 5x5x1280
özellik bloğuna dönüştürür. Örnek bir toplu resim için ne yaptığını görelim:
image_batch, label_batch = next(iter(train_dataset))
feature_batch = base_model(image_batch)
print(feature_batch.shape)
tutucu17 l10n-yer(32, 5, 5, 1280)
Özellik çıkarma
Bu adımda, bir önceki adımdan oluşturulan konvolüsyonel tabanı donduracak ve bir özellik çıkarıcı olarak kullanacaksınız. Ek olarak, üstüne bir sınıflandırıcı eklersiniz ve üst düzey sınıflandırıcıyı eğitirsiniz.
Evrişimli tabanı dondur
Modeli derlemeden ve eğitmeden önce evrişim tabanını dondurmak önemlidir. Dondurma (layer.trainable = False ayarlanarak), belirli bir katmandaki ağırlıkların eğitim sırasında güncellenmesini engeller. MobileNet V2'nin birçok katmanı vardır, bu nedenle tüm modelin trainable
bayrağını False olarak ayarlamak hepsini donduracaktır.
base_model.trainable = False
BatchNormalization katmanları hakkında önemli not
Birçok model tf.keras.layers.BatchNormalization
katmanlarını içerir. Bu katman özel bir durumdur ve bu öğreticide daha sonra gösterildiği gibi ince ayar bağlamında önlemler alınmalıdır.
layer.trainable = False
ayarladığınızda, BatchNormalization
katmanı çıkarım modunda çalışacak ve ortalama ve varyans istatistiklerini güncellemeyecektir.
İnce ayar yapmak için BatchNormalization katmanları içeren bir modeli çözdüğünüzde, temel modeli çağırırken training = False
False'ı geçerek BatchNormalization katmanlarını çıkarım modunda tutmalısınız. Aksi takdirde, eğitilebilir olmayan ağırlıklara uygulanan güncellemeler, modelin öğrendiklerini yok edecektir.
Daha fazla ayrıntı için Transfer öğrenme kılavuzuna bakın.
# Let's take a look at the base model architecture
base_model.summary()
tutucu20 l10n-yerModel: "mobilenetv2_1.00_160" __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== input_1 (InputLayer) [(None, 160, 160, 3 0 [] )] Conv1 (Conv2D) (None, 80, 80, 32) 864 ['input_1[0][0]'] bn_Conv1 (BatchNormalization) (None, 80, 80, 32) 128 ['Conv1[0][0]'] Conv1_relu (ReLU) (None, 80, 80, 32) 0 ['bn_Conv1[0][0]'] expanded_conv_depthwise (Depth (None, 80, 80, 32) 288 ['Conv1_relu[0][0]'] wiseConv2D) expanded_conv_depthwise_BN (Ba (None, 80, 80, 32) 128 ['expanded_conv_depthwise[0][0]'] tchNormalization) expanded_conv_depthwise_relu ( (None, 80, 80, 32) 0 ['expanded_conv_depthwise_BN[0][0 ReLU) ]'] expanded_conv_project (Conv2D) (None, 80, 80, 16) 512 ['expanded_conv_depthwise_relu[0] [0]'] expanded_conv_project_BN (Batc (None, 80, 80, 16) 64 ['expanded_conv_project[0][0]'] hNormalization) block_1_expand (Conv2D) (None, 80, 80, 96) 1536 ['expanded_conv_project_BN[0][0]' ] block_1_expand_BN (BatchNormal (None, 80, 80, 96) 384 ['block_1_expand[0][0]'] ization) block_1_expand_relu (ReLU) (None, 80, 80, 96) 0 ['block_1_expand_BN[0][0]'] block_1_pad (ZeroPadding2D) (None, 81, 81, 96) 0 ['block_1_expand_relu[0][0]'] block_1_depthwise (DepthwiseCo (None, 40, 40, 96) 864 ['block_1_pad[0][0]'] nv2D) block_1_depthwise_BN (BatchNor (None, 40, 40, 96) 384 ['block_1_depthwise[0][0]'] malization) block_1_depthwise_relu (ReLU) (None, 40, 40, 96) 0 ['block_1_depthwise_BN[0][0]'] block_1_project (Conv2D) (None, 40, 40, 24) 2304 ['block_1_depthwise_relu[0][0]'] block_1_project_BN (BatchNorma (None, 40, 40, 24) 96 ['block_1_project[0][0]'] lization) block_2_expand (Conv2D) (None, 40, 40, 144) 3456 ['block_1_project_BN[0][0]'] block_2_expand_BN (BatchNormal (None, 40, 40, 144) 576 ['block_2_expand[0][0]'] ization) block_2_expand_relu (ReLU) (None, 40, 40, 144) 0 ['block_2_expand_BN[0][0]'] block_2_depthwise (DepthwiseCo (None, 40, 40, 144) 1296 ['block_2_expand_relu[0][0]'] nv2D) block_2_depthwise_BN (BatchNor (None, 40, 40, 144) 576 ['block_2_depthwise[0][0]'] malization) block_2_depthwise_relu (ReLU) (None, 40, 40, 144) 0 ['block_2_depthwise_BN[0][0]'] block_2_project (Conv2D) (None, 40, 40, 24) 3456 ['block_2_depthwise_relu[0][0]'] block_2_project_BN (BatchNorma (None, 40, 40, 24) 96 ['block_2_project[0][0]'] lization) block_2_add (Add) (None, 40, 40, 24) 0 ['block_1_project_BN[0][0]', 'block_2_project_BN[0][0]'] block_3_expand (Conv2D) (None, 40, 40, 144) 3456 ['block_2_add[0][0]'] block_3_expand_BN (BatchNormal (None, 40, 40, 144) 576 ['block_3_expand[0][0]'] ization) block_3_expand_relu (ReLU) (None, 40, 40, 144) 0 ['block_3_expand_BN[0][0]'] block_3_pad (ZeroPadding2D) (None, 41, 41, 144) 0 ['block_3_expand_relu[0][0]'] block_3_depthwise (DepthwiseCo (None, 20, 20, 144) 1296 ['block_3_pad[0][0]'] nv2D) block_3_depthwise_BN (BatchNor (None, 20, 20, 144) 576 ['block_3_depthwise[0][0]'] malization) block_3_depthwise_relu (ReLU) (None, 20, 20, 144) 0 ['block_3_depthwise_BN[0][0]'] block_3_project (Conv2D) (None, 20, 20, 32) 4608 ['block_3_depthwise_relu[0][0]'] block_3_project_BN (BatchNorma (None, 20, 20, 32) 128 ['block_3_project[0][0]'] lization) block_4_expand (Conv2D) (None, 20, 20, 192) 6144 ['block_3_project_BN[0][0]'] block_4_expand_BN (BatchNormal (None, 20, 20, 192) 768 ['block_4_expand[0][0]'] ization) block_4_expand_relu (ReLU) (None, 20, 20, 192) 0 ['block_4_expand_BN[0][0]'] block_4_depthwise (DepthwiseCo (None, 20, 20, 192) 1728 ['block_4_expand_relu[0][0]'] nv2D) block_4_depthwise_BN (BatchNor (None, 20, 20, 192) 768 ['block_4_depthwise[0][0]'] malization) block_4_depthwise_relu (ReLU) (None, 20, 20, 192) 0 ['block_4_depthwise_BN[0][0]'] block_4_project (Conv2D) (None, 20, 20, 32) 6144 ['block_4_depthwise_relu[0][0]'] block_4_project_BN (BatchNorma (None, 20, 20, 32) 128 ['block_4_project[0][0]'] lization) block_4_add (Add) (None, 20, 20, 32) 0 ['block_3_project_BN[0][0]', 'block_4_project_BN[0][0]'] block_5_expand (Conv2D) (None, 20, 20, 192) 6144 ['block_4_add[0][0]'] block_5_expand_BN (BatchNormal (None, 20, 20, 192) 768 ['block_5_expand[0][0]'] ization) block_5_expand_relu (ReLU) (None, 20, 20, 192) 0 ['block_5_expand_BN[0][0]'] block_5_depthwise (DepthwiseCo (None, 20, 20, 192) 1728 ['block_5_expand_relu[0][0]'] nv2D) block_5_depthwise_BN (BatchNor (None, 20, 20, 192) 768 ['block_5_depthwise[0][0]'] malization) block_5_depthwise_relu (ReLU) (None, 20, 20, 192) 0 ['block_5_depthwise_BN[0][0]'] block_5_project (Conv2D) (None, 20, 20, 32) 6144 ['block_5_depthwise_relu[0][0]'] block_5_project_BN (BatchNorma (None, 20, 20, 32) 128 ['block_5_project[0][0]'] lization) block_5_add (Add) (None, 20, 20, 32) 0 ['block_4_add[0][0]', 'block_5_project_BN[0][0]'] block_6_expand (Conv2D) (None, 20, 20, 192) 6144 ['block_5_add[0][0]'] block_6_expand_BN (BatchNormal (None, 20, 20, 192) 768 ['block_6_expand[0][0]'] ization) block_6_expand_relu (ReLU) (None, 20, 20, 192) 0 ['block_6_expand_BN[0][0]'] block_6_pad (ZeroPadding2D) (None, 21, 21, 192) 0 ['block_6_expand_relu[0][0]'] block_6_depthwise (DepthwiseCo (None, 10, 10, 192) 1728 ['block_6_pad[0][0]'] nv2D) block_6_depthwise_BN (BatchNor (None, 10, 10, 192) 768 ['block_6_depthwise[0][0]'] malization) block_6_depthwise_relu (ReLU) (None, 10, 10, 192) 0 ['block_6_depthwise_BN[0][0]'] block_6_project (Conv2D) (None, 10, 10, 64) 12288 ['block_6_depthwise_relu[0][0]'] block_6_project_BN (BatchNorma (None, 10, 10, 64) 256 ['block_6_project[0][0]'] lization) block_7_expand (Conv2D) (None, 10, 10, 384) 24576 ['block_6_project_BN[0][0]'] block_7_expand_BN (BatchNormal (None, 10, 10, 384) 1536 ['block_7_expand[0][0]'] ization) block_7_expand_relu (ReLU) (None, 10, 10, 384) 0 ['block_7_expand_BN[0][0]'] block_7_depthwise (DepthwiseCo (None, 10, 10, 384) 3456 ['block_7_expand_relu[0][0]'] nv2D) block_7_depthwise_BN (BatchNor (None, 10, 10, 384) 1536 ['block_7_depthwise[0][0]'] malization) block_7_depthwise_relu (ReLU) (None, 10, 10, 384) 0 ['block_7_depthwise_BN[0][0]'] block_7_project (Conv2D) (None, 10, 10, 64) 24576 ['block_7_depthwise_relu[0][0]'] block_7_project_BN (BatchNorma (None, 10, 10, 64) 256 ['block_7_project[0][0]'] lization) block_7_add (Add) (None, 10, 10, 64) 0 ['block_6_project_BN[0][0]', 'block_7_project_BN[0][0]'] block_8_expand (Conv2D) (None, 10, 10, 384) 24576 ['block_7_add[0][0]'] block_8_expand_BN (BatchNormal (None, 10, 10, 384) 1536 ['block_8_expand[0][0]'] ization) block_8_expand_relu (ReLU) (None, 10, 10, 384) 0 ['block_8_expand_BN[0][0]'] block_8_depthwise (DepthwiseCo (None, 10, 10, 384) 3456 ['block_8_expand_relu[0][0]'] nv2D) block_8_depthwise_BN (BatchNor (None, 10, 10, 384) 1536 ['block_8_depthwise[0][0]'] malization) block_8_depthwise_relu (ReLU) (None, 10, 10, 384) 0 ['block_8_depthwise_BN[0][0]'] block_8_project (Conv2D) (None, 10, 10, 64) 24576 ['block_8_depthwise_relu[0][0]'] block_8_project_BN (BatchNorma (None, 10, 10, 64) 256 ['block_8_project[0][0]'] lization) block_8_add (Add) (None, 10, 10, 64) 0 ['block_7_add[0][0]', 'block_8_project_BN[0][0]'] block_9_expand (Conv2D) (None, 10, 10, 384) 24576 ['block_8_add[0][0]'] block_9_expand_BN (BatchNormal (None, 10, 10, 384) 1536 ['block_9_expand[0][0]'] ization) block_9_expand_relu (ReLU) (None, 10, 10, 384) 0 ['block_9_expand_BN[0][0]'] block_9_depthwise (DepthwiseCo (None, 10, 10, 384) 3456 ['block_9_expand_relu[0][0]'] nv2D) block_9_depthwise_BN (BatchNor (None, 10, 10, 384) 1536 ['block_9_depthwise[0][0]'] malization) block_9_depthwise_relu (ReLU) (None, 10, 10, 384) 0 ['block_9_depthwise_BN[0][0]'] block_9_project (Conv2D) (None, 10, 10, 64) 24576 ['block_9_depthwise_relu[0][0]'] block_9_project_BN (BatchNorma (None, 10, 10, 64) 256 ['block_9_project[0][0]'] lization) block_9_add (Add) (None, 10, 10, 64) 0 ['block_8_add[0][0]', 'block_9_project_BN[0][0]'] block_10_expand (Conv2D) (None, 10, 10, 384) 24576 ['block_9_add[0][0]'] block_10_expand_BN (BatchNorma (None, 10, 10, 384) 1536 ['block_10_expand[0][0]'] lization) block_10_expand_relu (ReLU) (None, 10, 10, 384) 0 ['block_10_expand_BN[0][0]'] block_10_depthwise (DepthwiseC (None, 10, 10, 384) 3456 ['block_10_expand_relu[0][0]'] onv2D) block_10_depthwise_BN (BatchNo (None, 10, 10, 384) 1536 ['block_10_depthwise[0][0]'] rmalization) block_10_depthwise_relu (ReLU) (None, 10, 10, 384) 0 ['block_10_depthwise_BN[0][0]'] block_10_project (Conv2D) (None, 10, 10, 96) 36864 ['block_10_depthwise_relu[0][0]'] block_10_project_BN (BatchNorm (None, 10, 10, 96) 384 ['block_10_project[0][0]'] alization) block_11_expand (Conv2D) (None, 10, 10, 576) 55296 ['block_10_project_BN[0][0]'] block_11_expand_BN (BatchNorma (None, 10, 10, 576) 2304 ['block_11_expand[0][0]'] lization) block_11_expand_relu (ReLU) (None, 10, 10, 576) 0 ['block_11_expand_BN[0][0]'] block_11_depthwise (DepthwiseC (None, 10, 10, 576) 5184 ['block_11_expand_relu[0][0]'] onv2D) block_11_depthwise_BN (BatchNo (None, 10, 10, 576) 2304 ['block_11_depthwise[0][0]'] rmalization) block_11_depthwise_relu (ReLU) (None, 10, 10, 576) 0 ['block_11_depthwise_BN[0][0]'] block_11_project (Conv2D) (None, 10, 10, 96) 55296 ['block_11_depthwise_relu[0][0]'] block_11_project_BN (BatchNorm (None, 10, 10, 96) 384 ['block_11_project[0][0]'] alization) block_11_add (Add) (None, 10, 10, 96) 0 ['block_10_project_BN[0][0]', 'block_11_project_BN[0][0]'] block_12_expand (Conv2D) (None, 10, 10, 576) 55296 ['block_11_add[0][0]'] block_12_expand_BN (BatchNorma (None, 10, 10, 576) 2304 ['block_12_expand[0][0]'] lization) block_12_expand_relu (ReLU) (None, 10, 10, 576) 0 ['block_12_expand_BN[0][0]'] block_12_depthwise (DepthwiseC (None, 10, 10, 576) 5184 ['block_12_expand_relu[0][0]'] onv2D) block_12_depthwise_BN (BatchNo (None, 10, 10, 576) 2304 ['block_12_depthwise[0][0]'] rmalization) block_12_depthwise_relu (ReLU) (None, 10, 10, 576) 0 ['block_12_depthwise_BN[0][0]'] block_12_project (Conv2D) (None, 10, 10, 96) 55296 ['block_12_depthwise_relu[0][0]'] block_12_project_BN (BatchNorm (None, 10, 10, 96) 384 ['block_12_project[0][0]'] alization) block_12_add (Add) (None, 10, 10, 96) 0 ['block_11_add[0][0]', 'block_12_project_BN[0][0]'] block_13_expand (Conv2D) (None, 10, 10, 576) 55296 ['block_12_add[0][0]'] block_13_expand_BN (BatchNorma (None, 10, 10, 576) 2304 ['block_13_expand[0][0]'] lization) block_13_expand_relu (ReLU) (None, 10, 10, 576) 0 ['block_13_expand_BN[0][0]'] block_13_pad (ZeroPadding2D) (None, 11, 11, 576) 0 ['block_13_expand_relu[0][0]'] block_13_depthwise (DepthwiseC (None, 5, 5, 576) 5184 ['block_13_pad[0][0]'] onv2D) block_13_depthwise_BN (BatchNo (None, 5, 5, 576) 2304 ['block_13_depthwise[0][0]'] rmalization) block_13_depthwise_relu (ReLU) (None, 5, 5, 576) 0 ['block_13_depthwise_BN[0][0]'] block_13_project (Conv2D) (None, 5, 5, 160) 92160 ['block_13_depthwise_relu[0][0]'] block_13_project_BN (BatchNorm (None, 5, 5, 160) 640 ['block_13_project[0][0]'] alization) block_14_expand (Conv2D) (None, 5, 5, 960) 153600 ['block_13_project_BN[0][0]'] block_14_expand_BN (BatchNorma (None, 5, 5, 960) 3840 ['block_14_expand[0][0]'] lization) block_14_expand_relu (ReLU) (None, 5, 5, 960) 0 ['block_14_expand_BN[0][0]'] block_14_depthwise (DepthwiseC (None, 5, 5, 960) 8640 ['block_14_expand_relu[0][0]'] onv2D) block_14_depthwise_BN (BatchNo (None, 5, 5, 960) 3840 ['block_14_depthwise[0][0]'] rmalization) block_14_depthwise_relu (ReLU) (None, 5, 5, 960) 0 ['block_14_depthwise_BN[0][0]'] block_14_project (Conv2D) (None, 5, 5, 160) 153600 ['block_14_depthwise_relu[0][0]'] block_14_project_BN (BatchNorm (None, 5, 5, 160) 640 ['block_14_project[0][0]'] alization) block_14_add (Add) (None, 5, 5, 160) 0 ['block_13_project_BN[0][0]', 'block_14_project_BN[0][0]'] block_15_expand (Conv2D) (None, 5, 5, 960) 153600 ['block_14_add[0][0]'] block_15_expand_BN (BatchNorma (None, 5, 5, 960) 3840 ['block_15_expand[0][0]'] lization) block_15_expand_relu (ReLU) (None, 5, 5, 960) 0 ['block_15_expand_BN[0][0]'] block_15_depthwise (DepthwiseC (None, 5, 5, 960) 8640 ['block_15_expand_relu[0][0]'] onv2D) block_15_depthwise_BN (BatchNo (None, 5, 5, 960) 3840 ['block_15_depthwise[0][0]'] rmalization) block_15_depthwise_relu (ReLU) (None, 5, 5, 960) 0 ['block_15_depthwise_BN[0][0]'] block_15_project (Conv2D) (None, 5, 5, 160) 153600 ['block_15_depthwise_relu[0][0]'] block_15_project_BN (BatchNorm (None, 5, 5, 160) 640 ['block_15_project[0][0]'] alization) block_15_add (Add) (None, 5, 5, 160) 0 ['block_14_add[0][0]', 'block_15_project_BN[0][0]'] block_16_expand (Conv2D) (None, 5, 5, 960) 153600 ['block_15_add[0][0]'] block_16_expand_BN (BatchNorma (None, 5, 5, 960) 3840 ['block_16_expand[0][0]'] lization) block_16_expand_relu (ReLU) (None, 5, 5, 960) 0 ['block_16_expand_BN[0][0]'] block_16_depthwise (DepthwiseC (None, 5, 5, 960) 8640 ['block_16_expand_relu[0][0]'] onv2D) block_16_depthwise_BN (BatchNo (None, 5, 5, 960) 3840 ['block_16_depthwise[0][0]'] rmalization) block_16_depthwise_relu (ReLU) (None, 5, 5, 960) 0 ['block_16_depthwise_BN[0][0]'] block_16_project (Conv2D) (None, 5, 5, 320) 307200 ['block_16_depthwise_relu[0][0]'] block_16_project_BN (BatchNorm (None, 5, 5, 320) 1280 ['block_16_project[0][0]'] alization) Conv_1 (Conv2D) (None, 5, 5, 1280) 409600 ['block_16_project_BN[0][0]'] Conv_1_bn (BatchNormalization) (None, 5, 5, 1280) 5120 ['Conv_1[0][0]'] out_relu (ReLU) (None, 5, 5, 1280) 0 ['Conv_1_bn[0][0]'] ================================================================================================== Total params: 2,257,984 Trainable params: 0 Non-trainable params: 2,257,984 __________________________________________________________________________________________________
Bir sınıflandırma başlığı ekleyin
Özellikler bloğundan tahminler oluşturmak için, özellikleri görüntü başına tek bir 1280 elemanlı vektöre dönüştürmek için bir tf.keras.layers.GlobalAveragePooling2D
katmanını kullanarak uzamsal 5x5
uzamsal konumların ortalamasını alın.
global_average_layer = tf.keras.layers.GlobalAveragePooling2D()
feature_batch_average = global_average_layer(feature_batch)
print(feature_batch_average.shape)
tutucu22 l10n-yer(32, 1280)
Bu özellikleri görüntü başına tek bir tahmine dönüştürmek için bir tf.keras.layers.Dense
katmanı uygulayın. Burada bir etkinleştirme işlevine ihtiyacınız yoktur, çünkü bu tahmin bir logit
veya ham tahmin değeri olarak değerlendirilecektir. Pozitif sayılar sınıf 1'i, negatif sayılar sınıf 0'ı öngörür.
prediction_layer = tf.keras.layers.Dense(1)
prediction_batch = prediction_layer(feature_batch_average)
print(prediction_batch.shape)
tutucu24 l10n-yer(32, 1)
Keras İşlevsel API'sini kullanarak veri büyütme, yeniden ölçeklendirme, base_model
ve özellik çıkarıcı katmanlarını zincirleyerek bir model oluşturun. Daha önce belirtildiği gibi, modelimiz bir BatchNormalization
katmanı içerdiğinden training=False
kullanın.
inputs = tf.keras.Input(shape=(160, 160, 3))
x = data_augmentation(inputs)
x = preprocess_input(x)
x = base_model(x, training=False)
x = global_average_layer(x)
x = tf.keras.layers.Dropout(0.2)(x)
outputs = prediction_layer(x)
model = tf.keras.Model(inputs, outputs)
Modeli derleyin
Modeli eğitmeden önce derleyin. İki sınıf olduğundan, model doğrusal bir çıktı sağladığından, from_logits=True
ile tf.keras.losses.BinaryCrossentropy
kaybını kullanın.
base_learning_rate = 0.0001
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=base_learning_rate),
loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
metrics=['accuracy'])
model.summary()
-yer tutucu28 l10n-yerModel: "model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_2 (InputLayer) [(None, 160, 160, 3)] 0 sequential (Sequential) (None, 160, 160, 3) 0 tf.math.truediv (TFOpLambda (None, 160, 160, 3) 0 ) tf.math.subtract (TFOpLambd (None, 160, 160, 3) 0 a) mobilenetv2_1.00_160 (Funct (None, 5, 5, 1280) 2257984 ional) global_average_pooling2d (G (None, 1280) 0 lobalAveragePooling2D) dropout (Dropout) (None, 1280) 0 dense (Dense) (None, 1) 1281 ================================================================= Total params: 2,259,265 Trainable params: 1,281 Non-trainable params: 2,257,984 _________________________________________________________________
MobileNet'teki 2,5 milyon parametre donduruldu, ancak Yoğun katmanda 1,2 bin eğitilebilir parametre var. Bunlar iki tf.Variable
nesnesi, ağırlıklar ve önyargılar arasında bölünür.
len(model.trainable_variables)
tutucu30 l10n-yer2
Modeli eğit
10 dönemlik eğitimden sonra, doğrulama setinde ~%94 doğruluk görmelisiniz.
initial_epochs = 10
loss0, accuracy0 = model.evaluate(validation_dataset)
26/26 [==============================] - 2s 16ms/step - loss: 0.7428 - accuracy: 0.5186
print("initial loss: {:.2f}".format(loss0))
print("initial accuracy: {:.2f}".format(accuracy0))
initial loss: 0.74 initial accuracy: 0.52
history = model.fit(train_dataset,
epochs=initial_epochs,
validation_data=validation_dataset)
Epoch 1/10 63/63 [==============================] - 4s 23ms/step - loss: 0.6804 - accuracy: 0.5680 - val_loss: 0.4981 - val_accuracy: 0.7054 Epoch 2/10 63/63 [==============================] - 1s 22ms/step - loss: 0.5044 - accuracy: 0.7170 - val_loss: 0.3598 - val_accuracy: 0.8144 Epoch 3/10 63/63 [==============================] - 1s 21ms/step - loss: 0.4109 - accuracy: 0.7845 - val_loss: 0.2810 - val_accuracy: 0.8861 Epoch 4/10 63/63 [==============================] - 1s 21ms/step - loss: 0.3285 - accuracy: 0.8445 - val_loss: 0.2256 - val_accuracy: 0.9208 Epoch 5/10 63/63 [==============================] - 1s 21ms/step - loss: 0.3108 - accuracy: 0.8555 - val_loss: 0.1986 - val_accuracy: 0.9307 Epoch 6/10 63/63 [==============================] - 1s 21ms/step - loss: 0.2659 - accuracy: 0.8855 - val_loss: 0.1703 - val_accuracy: 0.9418 Epoch 7/10 63/63 [==============================] - 1s 21ms/step - loss: 0.2459 - accuracy: 0.8935 - val_loss: 0.1495 - val_accuracy: 0.9517 Epoch 8/10 63/63 [==============================] - 1s 21ms/step - loss: 0.2315 - accuracy: 0.8950 - val_loss: 0.1454 - val_accuracy: 0.9542 Epoch 9/10 63/63 [==============================] - 1s 21ms/step - loss: 0.2204 - accuracy: 0.9030 - val_loss: 0.1326 - val_accuracy: 0.9592 Epoch 10/10 63/63 [==============================] - 1s 21ms/step - loss: 0.2180 - accuracy: 0.9115 - val_loss: 0.1215 - val_accuracy: 0.9604
öğrenme eğrileri
MobileNetV2 temel modelini sabit bir özellik çıkarıcı olarak kullanırken eğitim ve doğrulama doğruluğu/kaybının öğrenme eğrilerine bir göz atalım.
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.ylabel('Accuracy')
plt.ylim([min(plt.ylim()),1])
plt.title('Training and Validation Accuracy')
plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.ylabel('Cross Entropy')
plt.ylim([0,1.0])
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()
Daha az ölçüde, bunun nedeni aynı zamanda eğitim metriklerinin bir dönemin ortalamasını raporlaması ve doğrulama metriklerinin çağdan sonra değerlendirilmesidir, dolayısıyla doğrulama metriklerinin biraz daha uzun eğitilmiş bir model görmesidir.
İnce ayar
Özellik çıkarma deneyinde, bir MobileNetV2 temel modelinin üzerinde yalnızca birkaç katmanı eğitiyordunuz. Önceden eğitilmiş ağın ağırlıkları eğitim sırasında güncellenmedi .
Performansı daha da artırmanın bir yolu, eklediğiniz sınıflandırıcının eğitimiyle birlikte önceden eğitilmiş modelin üst katmanlarının ağırlıklarını eğitmek (veya "ince ayar yapmak"). Eğitim süreci, ağırlıkların genel özellik haritalarından özellikle veri kümesiyle ilişkilendirilen özelliklere ayarlanmasını zorlayacaktır.
Ayrıca, tüm MobileNet modeli yerine az sayıda üst katmanda ince ayar yapmaya çalışmalısınız. Çoğu evrişimsel ağda, bir katman ne kadar yüksekse, o kadar özelleşir. İlk birkaç katman, hemen hemen tüm görüntü türlerini genelleştiren çok basit ve genel özellikleri öğrenir. Siz yukarı çıktıkça, özellikler modelin üzerinde eğitildiği veri kümesine giderek daha özel hale gelir. İnce ayarın amacı, genel öğrenmenin üzerine yazmak yerine bu özelleştirilmiş özellikleri yeni veri kümesiyle çalışacak şekilde uyarlamaktır.
Modelin üst katmanlarını çözdürün
Tek yapmanız gereken base_model
ve alt katmanları eğitilemez hale getirmek. Ardından, modeli yeniden derlemelisiniz (bu değişikliklerin etkili olması için gerekli) ve eğitime devam etmelisiniz.
base_model.trainable = True
# Let's take a look to see how many layers are in the base model
print("Number of layers in the base model: ", len(base_model.layers))
# Fine-tune from this layer onwards
fine_tune_at = 100
# Freeze all the layers before the `fine_tune_at` layer
for layer in base_model.layers[:fine_tune_at]:
layer.trainable = False
-yer tutucu40 l10n-yerNumber of layers in the base model: 154
Modeli derleyin
Çok daha büyük bir modeli eğittiğiniz ve önceden eğitilmiş ağırlıkları yeniden uyarlamak istediğiniz için bu aşamada daha düşük bir öğrenme oranı kullanmak önemlidir. Aksi takdirde, modeliniz çok hızlı bir şekilde fazla sığabilir.
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
optimizer = tf.keras.optimizers.RMSprop(learning_rate=base_learning_rate/10),
metrics=['accuracy'])
model.summary()
Model: "model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_2 (InputLayer) [(None, 160, 160, 3)] 0 sequential (Sequential) (None, 160, 160, 3) 0 tf.math.truediv (TFOpLambda (None, 160, 160, 3) 0 ) tf.math.subtract (TFOpLambd (None, 160, 160, 3) 0 a) mobilenetv2_1.00_160 (Funct (None, 5, 5, 1280) 2257984 ional) global_average_pooling2d (G (None, 1280) 0 lobalAveragePooling2D) dropout (Dropout) (None, 1280) 0 dense (Dense) (None, 1) 1281 ================================================================= Total params: 2,259,265 Trainable params: 1,862,721 Non-trainable params: 396,544 _________________________________________________________________
len(model.trainable_variables)
56
Modeli eğitmeye devam edin
Daha önce yakınsama eğitimi aldıysanız, bu adım doğruluğunuzu birkaç yüzde puanı artıracaktır.
fine_tune_epochs = 10
total_epochs = initial_epochs + fine_tune_epochs
history_fine = model.fit(train_dataset,
epochs=total_epochs,
initial_epoch=history.epoch[-1],
validation_data=validation_dataset)
tutucu47 l10n-yerEpoch 10/20 63/63 [==============================] - 7s 40ms/step - loss: 0.1545 - accuracy: 0.9335 - val_loss: 0.0531 - val_accuracy: 0.9864 Epoch 11/20 63/63 [==============================] - 2s 28ms/step - loss: 0.1161 - accuracy: 0.9540 - val_loss: 0.0500 - val_accuracy: 0.9814 Epoch 12/20 63/63 [==============================] - 2s 28ms/step - loss: 0.1125 - accuracy: 0.9525 - val_loss: 0.0379 - val_accuracy: 0.9876 Epoch 13/20 63/63 [==============================] - 2s 28ms/step - loss: 0.0891 - accuracy: 0.9625 - val_loss: 0.0472 - val_accuracy: 0.9889 Epoch 14/20 63/63 [==============================] - 2s 28ms/step - loss: 0.0844 - accuracy: 0.9680 - val_loss: 0.0478 - val_accuracy: 0.9889 Epoch 15/20 63/63 [==============================] - 2s 28ms/step - loss: 0.0857 - accuracy: 0.9645 - val_loss: 0.0354 - val_accuracy: 0.9839 Epoch 16/20 63/63 [==============================] - 2s 28ms/step - loss: 0.0785 - accuracy: 0.9690 - val_loss: 0.0449 - val_accuracy: 0.9864 Epoch 17/20 63/63 [==============================] - 2s 28ms/step - loss: 0.0669 - accuracy: 0.9740 - val_loss: 0.0375 - val_accuracy: 0.9839 Epoch 18/20 63/63 [==============================] - 2s 28ms/step - loss: 0.0701 - accuracy: 0.9695 - val_loss: 0.0324 - val_accuracy: 0.9864 Epoch 19/20 63/63 [==============================] - 2s 28ms/step - loss: 0.0636 - accuracy: 0.9760 - val_loss: 0.0465 - val_accuracy: 0.9790 Epoch 20/20 63/63 [==============================] - 2s 29ms/step - loss: 0.0585 - accuracy: 0.9765 - val_loss: 0.0392 - val_accuracy: 0.9851
Şimdi MobileNetV2 temel modelinin son birkaç katmanına ince ayar yaparken ve bunun üzerine sınıflandırıcıyı eğitirken eğitim ve doğrulama doğruluğu/kaybının öğrenme eğrilerine bir göz atalım. Doğrulama kaybı, eğitim kaybından çok daha yüksektir, bu nedenle biraz fazla uyum sağlayabilirsiniz.
Yeni eğitim seti nispeten küçük olduğundan ve orijinal MobileNetV2 veri setlerine benzer olduğundan, biraz fazla uyum sağlayabilirsiniz.
İnce ayardan sonra model, doğrulama setinde neredeyse %98 doğruluğa ulaşır.
acc += history_fine.history['accuracy']
val_acc += history_fine.history['val_accuracy']
loss += history_fine.history['loss']
val_loss += history_fine.history['val_loss']
tutucu49 l10n-yerplt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.ylim([0.8, 1])
plt.plot([initial_epochs-1,initial_epochs-1],
plt.ylim(), label='Start Fine Tuning')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.ylim([0, 1.0])
plt.plot([initial_epochs-1,initial_epochs-1],
plt.ylim(), label='Start Fine Tuning')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()
Değerlendirme ve tahmin
Son olarak, test setini kullanarak modelin performansını yeni veriler üzerinde doğrulayabilirsiniz.
loss, accuracy = model.evaluate(test_dataset)
print('Test accuracy :', accuracy)
tutucu51 l10n-yer6/6 [==============================] - 0s 13ms/step - loss: 0.0281 - accuracy: 0.9948 Test accuracy : 0.9947916865348816
Artık evcil hayvanınızın kedi mi yoksa köpek mi olduğunu tahmin etmek için bu modeli kullanmaya hazırsınız.
# Retrieve a batch of images from the test set
image_batch, label_batch = test_dataset.as_numpy_iterator().next()
predictions = model.predict_on_batch(image_batch).flatten()
# Apply a sigmoid since our model returns logits
predictions = tf.nn.sigmoid(predictions)
predictions = tf.where(predictions < 0.5, 0, 1)
print('Predictions:\n', predictions.numpy())
print('Labels:\n', label_batch)
plt.figure(figsize=(10, 10))
for i in range(9):
ax = plt.subplot(3, 3, i + 1)
plt.imshow(image_batch[i].astype("uint8"))
plt.title(class_names[predictions[i]])
plt.axis("off")
tutucu53 l10n-yerPredictions: [0 1 1 1 1 0 1 1 1 0 1 1 0 1 1 1 0 0 0 1 0 1 0 0 1 1 1 0 0 0 1 0] Labels: [0 1 1 1 1 0 1 1 1 0 1 1 0 1 1 1 0 0 0 1 0 1 0 0 1 1 1 0 0 0 1 0]
Özet
Özellik çıkarma için önceden eğitilmiş bir model kullanma : Küçük bir veri kümesiyle çalışırken, aynı etki alanındaki daha büyük bir veri kümesi üzerinde eğitilmiş bir model tarafından öğrenilen özelliklerden yararlanmak yaygın bir uygulamadır. Bu, önceden eğitilmiş modeli somutlaştırarak ve üstüne tam bağlantılı bir sınıflandırıcı ekleyerek yapılır. Önceden eğitilmiş model "dondurulur" ve eğitim sırasında yalnızca sınıflandırıcının ağırlıkları güncellenir. Bu durumda, evrişimli taban, her bir görüntüyle ilişkili tüm öznitelikleri çıkardı ve siz az önce, çıkarılan öznitelikler kümesi verilen görüntü sınıfını belirleyen bir sınıflandırıcıyı eğittiniz.
Önceden eğitilmiş bir modelde ince ayar yapma : Performansı daha da artırmak için, önceden eğitilmiş modellerin üst düzey katmanlarını ince ayar yoluyla yeni veri kümesine yeniden kullanmak isteyebilirsiniz. Bu durumda, ağırlıklarınızı, modelinizin veri kümesine özgü üst düzey özellikleri öğreneceği şekilde ayarladınız. Bu teknik genellikle eğitim veri kümesi büyük olduğunda ve önceden eğitilmiş modelin üzerinde eğitildiği orijinal veri kümesine çok benzer olduğunda önerilir.
Daha fazla bilgi edinmek için Transfer öğrenim kılavuzunu ziyaret edin.
# MIT License
#
# Copyright (c) 2017 François Chollet # IGNORE_COPYRIGHT: cleared by OSS licensing
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.