在TensorFlow上训练MNIST数据集的特定标签分类器。

huangapple go评论120阅读模式
英文:

Train a classifier on specific labels of MNIST dataset with TensorFlow

问题

I would like to train a classifier on MNIST dataset but with limited labels. For eg. I would like to train a classifier only on labels [1, 4, 5, 6, 8, 9] from all the labels [0-9]. I am getting the following error:

res = tf.nn.sparse_softmax_cross_entropy_with_logits( Node: 'sparse_categorical_crossentropy/SparseSoftmaxCrossEntropyWithLogits/SparseSoftmaxCrossEntropyWithLogits' Received a label value of 9 which is outside the valid range of [0, 6). Label values: 9 5 9 9 6 1 4 4 6 6 9 4 9 1 8 5 9 5 4 8 9 9 1 8 6 4 4 9 9 4 4 8 8 6 6 5 9 4 1 5 5 6 4 1 1 8 9 6 8 5 6 1 6 6 4 6 1 4 4 4 1 1 1 6 9 8 8 8 5 1 8 8 6 6 5 1 1 5 1 6 9 8 1 8 4 6 4 9 8 1 6 5 5 9 1 6 8 1 5 5 6 9 1 9 9 6 4 6 6 4 8 6 6 4 5 4 4 5 8 1 8 6 1 5 4 5 8 1

Here is the approach I have used:

  1. import tensorflow_datasets as tfds
  2. import tensorflow as tf
  3. val_split = 20 # percent of training data
  4. (ds_test, ds_valid, ds_train), ds_info = tfds.load(
  5. 'mnist',
  6. split=['test', f'train[0%:{val_split}%]', f'train[{val_split}%:]'],
  7. as_supervised=True,
  8. with_info=True
  9. )

The ds_train dataset object has the following samples per label:
{0: 4705, 1: 5433, 2: 4772, 3: 4936, 4: 4681, 5: 4333, 6: 4728, 7: 4966, 8: 4703, 9: 4743}.

After this, I filter the dataset using filter() as follows:

  1. known_classes = [1, 4, 5, 6, 8, 9]
  2. kc = tf.constant(known_classes, dtype=tf.int64)
  3. def predicate(image, label):
  4. isallowed = tf.equal(kc, label)
  5. reduced = tf.reduce_sum(tf.cast(isallowed, tf.int64))
  6. return tf.greater(reduced, tf.constant(0, dtype=tf.int64))
  7. ds_test = ds_test.filter(predicate)
  8. ds_valid = ds_valid.filter(predicate)
  9. ds_train = ds_train.filter(predicate)

The updated samples per label post-filter for ds_train is:
{0: 0, 1: 5433, 2: 0, 3: 0, 4: 4681, 5: 4333, 6: 4728, 7: 0, 8: 4703, 9: 4743}.

Next steps are normalizing the image and preparing the dataset objects for training.

  1. def normalize_img(image, label):
  2. """Normalizes images: `uint8` -> `float32`."""
  3. return tf.cast(image, tf.float32) / 255., label
  4. ds_train = ds_train.map(normalize_img, num_parallel_calls=tf.data.AUTOTUNE)
  5. ds_train = ds_train.shuffle(ds_info.splits['train'].num_examples)
  6. ds_train = ds_train.batch(128, drop_remainder=True)
  7. ds_train = ds_train.prefetch(tf.data.AUTOTUNE)
  8. ds_test = ds_test.map(normalize_img, num_parallel_calls=tf.data.AUTOTUNE)
  9. ds_test = ds_test.batch(128, drop_remainder=True)
  10. ds_test = ds_test.prefetch(tf.data.AUTOTUNE)
  11. ds_valid = ds_valid.map(normalize_img, num_parallel_calls=tf.data.AUTOTUNE)
  12. ds_valid = ds_valid.batch(128, drop_remainder=True)
  13. ds_valid = ds_valid.prefetch(tf.data.AUTOTUNE)

Thereafter, I create a simple model as follows and then proceed with training:

  1. model = tf.keras.models.Sequential([
  2. tf.keras.layers.Flatten(input_shape=(28, 28)),
  3. tf.keras.layers.Dense(128, activation='relu'),
  4. tf.keras.layers.Dense(len(known_classes)) # known_classes from above
  5. ])
  6. model.compile(
  7. optimizer=tf.keras.optimizers.Adam(0.001),
  8. loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
  9. metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],
  10. )
  11. history = model.fit(
  12. ds_train,
  13. epochs=5,
  14. validation_data=ds_valid,
  15. )

I am new to Tensorflow, and any help is appreciated!

英文:

I would like to train a classifier on MNIST dataset but with limited labels. For eg. I would like to train a classifier only on labels [1, 4, 5, 6, 8, 9] from all the labels [0-9]. I am getting the following error:

res = tf.nn.sparse_softmax_cross_entropy_with_logits(
Node: 'sparse_categorical_crossentropy/SparseSoftmaxCrossEntropyWithLogits/SparseSoftmaxCrossEntropyWithLogits'
Received a label value of 9 which is outside the valid range of [0, 6). Label values: 9 5 9 9 6 1 4 4 6 6 9 4 9 1 8 5 9 5 4 8 9 9 1 8 6 4 4 9 9 4 4 8 8 6 6 5 9 4 1 5 5 6 4 1 1 8 9 6 8 5 6 1 6 6 4 6 1 4 4 4 1 1 1 6 9 8 8 8 5 1 8 8 6 6 5 1 1 5 1 6 9 8 1 8 4 6 4 9 8 1 6 5 5 9 1 6 8 1 5 5 6 9 1 9 9 6 4 6 6 4 8 6 6 4 5 4 4 5 8 1 8 6 1 5 4 5 8 1

Here is approach I have used:

  1. import tensorflow_datasets as tfds
  2. import tensorflow as tf
  3. val_split = 20 # percent of training data
  4. (ds_test, ds_valid, ds_train), ds_info = tfds.load(
  5. 'mnist,
  6. split=['test', f'train[0%:{val_split}%]', f'train[{val_split}%:]'],
  7. as_supervised=True,
  8. with_info=True
  9. )

The ds_train dataset object has the following samples per label <br>
{0: 4705, 1: 5433, 2: 4772, 3: 4936, 4: 4681, 5: 4333, 6: 4728, 7: 4966, 8: 4703, 9: 4743}.

After this I filter the dataset using filter() as follows:

  1. known_classes = [1, 4, 5, 6, 8, 9]
  2. kc = tf.constant(known_classes, dtype=tf.int64)
  3. def predicate(image, label):
  4. isallowed = tf.equal(kc, label)
  5. reduced = tf.reduce_sum(tf.cast(isallowed, tf.int64))
  6. return tf.greater(reduced, tf.constant(0, dtype=tf.int64))
  7. ds_test = ds_test.filter(predicate)
  8. ds_valid = ds_valid.filter(predicate)
  9. ds_train = ds_train.filter(predicate)

The updated samples per label post filter fo ds_train is <br>
{0: 0, 1: 5433, 2: 0, 3: 0, 4: 4681, 5: 4333, 6: 4728, 7: 0, 8: 4703, 9: 4743}.

Next steps are normalizing the image and preparing the dataset objects for training.

  1. def normalize_img(image, label):
  2. &quot;&quot;&quot;Normalizes images: `uint8` -&gt; `float32`.&quot;&quot;&quot;
  3. return tf.cast(image, tf.float32) / 255., label
  4. ds_train = ds_train.map(normalize_img, num_parallel_calls=tf.data.AUTOTUNE)
  5. ds_train = ds_train.shuffle(ds_info.splits[&#39;train&#39;].num_examples)
  6. ds_train = ds_train.batch(128, drop_remainder=True)
  7. ds_train = ds_train.prefetch(tf.data.AUTOTUNE)
  8. ds_test = ds_test.map(normalize_img, num_parallel_calls=tf.data.AUTOTUNE)
  9. ds_test = ds_test.batch(128, drop_remainder=True)
  10. ds_test = ds_test.prefetch(tf.data.AUTOTUNE)
  11. ds_valid = ds_valid.map(normalize_img, num_parallel_calls=tf.data.AUTOTUNE)
  12. ds_valid = ds_valid.batch(128, drop_remainder=True)
  13. ds_valid = ds_valid.prefetch(tf.data.AUTOTUNE)

Thereafter, I create a simple model as follows and then proceed with training

  1. model = tf.keras.models.Sequential([
  2. tf.keras.layers.Flatten(input_shape=(28, 28)),
  3. tf.keras.layers.Dense(128, activation=&#39;relu&#39;),
  4. tf.keras.layers.Dense(len(known_classes)) # known_classes from above
  5. ])
  6. model.compile(
  7. optimizer=tf.keras.optimizers.Adam(0.001),
  8. loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
  9. metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],
  10. )
  11. history = model.fit(
  12. ds_train,
  13. epochs=5,
  14. validation_data=ds_valid,
  15. )

I am new to Tensorflow and any help is appreciated!!

答案1

得分: 1

你需要更改训练标签,将它们映射到0-5的范围,而不是[1, 4, 5, 6, 8, 9],因为sparse_categorical_crossentropy期望它们在0-5范围内,因为你删除了其中一些标签。

你可以在归一化图像时执行此操作,例如:

  1. def make_fn(y):
  2. def fn(z):
  3. return tf.equal(z, tf.constant(y, dtype=tf.int64))
  4. return fn
  5. def make_case(i):
  6. def case():
  7. return tf.constant(i, dtype=tf.int64)
  8. return case
  9. label_cases = [(make_fn(y), make_case(i)) for i, y in enumerate(known_classes)]
  10. def normalize_img_and_label(image, label):
  11. label = tf.case([(fn(label), case) for fn, case in label_cases],
  12. exclusive=False, default=lambda: label)
  13. return tf.cast(image, tf.float32) / 255., label

你也需要在ds_validds_test上执行相同的操作。这个代码段的目的是将剩余的类标签映射为[1 -> 0, 4 -> 1, 5 -> 2, 6 -> 3, 8 -> 4, 9 -> 5],因为损失函数期望它们在0-5范围内。这段代码使用了TensorFlow的tf.case来实现对标签的条件分支操作,根据标签值的不同返回不同的映射值。

英文:

You have to change the training labels too, mapping them to 0-5 range instead of [1, 4, 5, 6, 8, 9] because the sparse_categorical_crossentropy expects them to be in 0-5 since you removed some of them.

You can do this when normalizing the images, for example:

  1. def make_fn(y):
  2. def fn(z):
  3. return tf.equal(z, tf.constant(y, dtype=tf.int64))
  4. return fn
  5. def make_case(i):
  6. def case():
  7. return tf.constant(i, dtype=tf.int64)
  8. return case
  9. label_cases = [(make_fn(y), make_case(i)) for i, y in enumerate(known_classes)]
  10. def normalize_img_and_label(image, label):
  11. &quot;&quot;&quot;Normalizes images and labels: `uint8` -&gt; `float32`.&quot;&quot;&quot;
  12. label = tf.case([(fn(label), case) for fn, case in label_cases],
  13. exclusive=False, default=lambda: label)
  14. return tf.cast(image, tf.float32) / 255., label
  15. ds_train = ds_train.map(normalize_img_and_label, num_parallel_calls=tf.data.AUTOTUNE)
  16. ...

Indeed, you do this also on ds_valid and ds_test

Explanation: we want to implement the following mapping of the remaining class labels, i.e. [1 -&gt; 0, 4 -&gt; 1, 5 -&gt; 2, 6 -&gt; 3, 8 -&gt; 4, 9 -&gt; 5], because the loss function expects them to be in 0-5 range. Because of functions that operates on tf.data.Dataset objects (e.g. normalize_img()) work on symbolic tensors we can't implement the mapping directly using a dict with integers key-value pairs (as my previous solution.)

Within the normalize_img_and_label function both image and label are symbolic, meaning that they are like placeholders with no numpy value associated to them: such value will be available when actually consuming the dataset object. So we must use tf primitives able to handle symbolic tensors. Here I make use of a tf.case to implement a switch-case statement over symbolic tensors, of the kind:

  1. if label == 1:
  2. return 0
  3. elif label == 4:
  4. return 1:
  5. ...
  6. elif label == 9:
  7. return 5

To do this you define a list of function pairs (i.e. label_cases): one pair for each label.

  • The make_fn creates a closure that when called, e.g. fn(label), return the value of the boolean conditional tf.equal(z, y) - where y is one value of known_classes and z is the provided label.
  • The make_case yields another closure that returns a constant tensor, corresponding to the return value in the switch-case, i.e. the new mapped label.

Now, inside normalize_img_and_label we have to apply the tf.case and so we loop over the pairs of functions, yielding a new pair (Boolean, tensor) since we have to pass the label tensor to the tf.equal closure (i.e. fn.) For example assuming label = 4, the list [(fn(label), case) for fn, case in label_cases] is actually: [(False, 0), (True, 1), (False, 2), ..., (False, 5)]. Therefore the tf.case would yield a 1 because of the true condition.

huangapple
  • 本文由 发表于 2023年6月6日 17:30:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76413246.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定