I am trying to do a sample project on Melanoma(the one from kaggle).
Using tf.keras.utils.image_dataset_from_directory I got the train_ds but I would like to get print out how many images belong to each class.
Example:
actinic keratosis : x images
basal cell carcinoma : y images
The code I used to load the data is
data_dir = pathlib.Path("Train\\")
batch_size = 32
img_height = 180
img_width = 180
train_ds = tf.keras.utils.image_dataset_from_directory(
data_dir,
validation_split=0.2,
subset="training",
seed=123,
image_size=(img_height, img_width),
batch_size=batch_size)
This gives me this output
Found 6739 files belonging to 9 classes.
Using 5392 files for training.
and this:
class_names = train_ds.class_names
print(class_names)
gives me the name of the classes
How do I see how many images belong to which class?
One way of doing this is realized is by counting the files under directory using the code below(taken from github)
def class_distribution_count(directory):
count= []
for path in pathlib.Path(directory).iterdir():
if path.is_dir():
count.append(len([name for name in os.listdir(path)
if os.path.isfile(os.path.join(path, name))]))
sub_directory = [name for name in os.listdir(directory)
if os.path.isdir(os.path.join(directory, name))]
return pd.DataFrame(list(zip(sub_directory,count)),columns =['Class', 'No. of Image'])
df = class_distribution_count(data_dir)
df
But I am trying to see if there is a way to get this without having to read the files in the directory but directly from the keras dataset.
Thanks In Advance
I also tried this
import pandas as pd
dataset_unbatched = tuple(train_ds.unbatch())
labels = []
for (image,label) in dataset_unbatched:
labels.append(label.numpy())
labels = pd.Series(labels)
count = labels.value_counts()
print(count)
But I got a list of values not label names
Just replace the index of count with the train_ds.class_names like this:
import pandas as pd
dataset_unbatched = tuple(train_ds.unbatch())
labels = []
for (image,label) in dataset_unbatched:
labels.append(label.numpy())
labels = pd.Series(labels)
# adjustments
count = labels.value_counts().sort_index()
count.index = ds.class_names
Make sure to sort the index beforehand because it's sorted either by frequency or first occurrence.
Related
I am trying to implement a custom layer in keras.layers where I want to do a custom image augmentation. My idea is to cut out a part of an image from a random location and paste it to a different random location in the same image. The code below I have written works well for PIL Image but when I integrate it into my final code (which is a tensorflow model), I get as error saying that tensor doesn't support item assignment.
Below is the class that I have implemented:
class Cut_Paste(layers.Layer):
def __init__(self, x_scale = 10, y_scale = 10, IMG_SIZE = (224,224), **kwargs):
super().__init__(**kwargs)
"""
defining the x span and the y span of the box to cutout
x_scale and y_scale are taken as inputs as % of the width and height of the image
size
"""
self.size_x, self.size_y = IMG_SIZE
self.span_x = int(x_scale*self.size_x*0.01)
self.span_y = int(y_scale*self.size_y*0.01)
#getting the vertices for cut and paste
def get_vertices(self):
#determining random points for cut and paste
""" since the images in the dataset have the object of interest in the center of
the Image, the cutout will be taken from the central 25% of the image"""
fraction = 0.25
vert_x = random.randint(int(self.size_x*0.5*(1-fraction)),
int(self.size_x*0.5*(1+fraction)))
vert_y = random.randint(int(self.size_y*0.5*(1-fraction)),
int(self.size_y*0.5*(1+fraction)))
start_x = int(vert_x-self.span_x/2)
start_y = int(vert_y-self.span_y/2)
end_x = int(vert_x+self.span_x/2)
end_y = int(vert_y+self.span_y/2)
return start_x, start_y, end_x, end_y
def call(self, image):
#getting random vertices for cutting
cut_start_x, cut_start_y, cut_end_x, cut_end_y = self.get_vertices()
#getting the image as a sub-image
#image = tf.Variable(image)
sub_image = image[cut_start_x:cut_end_x,cut_start_y:cut_end_y,:]
#getting random vertices for pasting
paste_start_x, paste_start_y, paste_end_x, paste_end_y = self.get_vertices()
#replacing a part of the image at random location with sub_image
image[paste_start_x:paste_end_x,
paste_start_y:paste_end_y,:] = sub_image
return image
I am calling it from my model class this way:
class Contrastive_learning_model(keras.Model):
def __init__(self):
super().__init__()
self.cut_paste = Cut_Paste(**cut_paste_augmentation)
def train_step(self, data):
augmented_images_2 = self.cut_paste.call(images)
I have removed the part of the code which is irrelevant. But upon executing this is the error I get:
TypeError: 'tensorflow.python.framework.ops.EagerTensor' object does not support item assignment
I understood from other sources that it is not possible to do item assignment in tensor. So here I am seeking help to do this in an easier way. I need to use tensors for this. Any help will be much appreciated.
Tensorflow does not support item assignment unlike PyTorch.
A workaround you can implement is to convert the tensor to tf.Variable and then a numpy array like the following:
image = tf.Variable(image).numpy()
I found [this][1] and created this running POC code:
import tensorflow as tf
from tensorflow import keras
import numpy as np
def get_embedding_size(cat_data):
no_of_unique_cat = len(np.unique(cat_data))
return int(min(np.ceil((no_of_unique_cat)/2), 50))
# 3 numerical variables
num_data = np.random.random(size=(10,3))
# 2 categorical variables
cat_data_1 = np.random.randint(0,4,10)
cat_data_2 = np.random.randint(0,5,10)
target = np.random.random(size=(10,1))
no_unique_categories_category_1 = len(np.unique(cat_data_1))
embedding_size_category_1 = get_embedding_size(cat_data_1)
inp_cat_data = keras.layers.Input(shape=(no_unique_categories_category_1,))
# 3 columns
inp_num_data = keras.layers.Input(shape=(num_data.shape[1],))
emb = keras.layers.Embedding(input_dim=no_unique_categories_category_1, output_dim=embedding_size_category_1)(inp_cat_data)
flatten = keras.layers.Flatten()(emb)
# Concatenate two layers
conc = keras.layers.Concatenate()([flatten, inp_num_data])
dense1 = keras.layers.Dense(3, activation=tf.nn.relu,)(conc)
# Creating output layer
out = keras.layers.Dense(1, activation=None)(dense1)
model = keras.Model(inputs=[inp_cat_data, inp_num_data], outputs=out)
model.compile(optimizer='adam',
loss=keras.losses.mean_squared_error,
metrics=[keras.metrics.mean_squared_error])
one_hot_encoded_cat_data_1 = np.eye(cat_data_1.max()+1)[cat_data_1]
model.fit([one_hot_encoded_cat_data_1, num_data], target)
I wonder how could one add the additional categorical variable cat_data_2? I am also wondering, why is one hot encoding still used. Is the whole point of embedding not to make this necessary? Thanks!
model.layers[1].get_weights()[0]
[1]: https://mmuratarat.github.io/2019-06-12/embeddings-with-numeric-variables-Keras
I found a similar question but it doesn't quite fit this scenario.
I have some images of multiple classes in the same directory that I want to label according to their filename. I also have other directories with more images that I want to include in the same dataset.
Here's an example of the folder structure:
train/directory_1/cat_1.png
train/directory_1/cat_2.png
train/directory_1/dog_1.png
train/directory_1/dog_2.png
...
train/directory_2/cat_1.png
train/directory_2/cat_2.png
train/directory_2/dog_1.png
train/directory_2/dog_2.png
Is there any efficient way to create a dataset from this structure? Since I usually use image_dataset_from_directory() but it doesn't work in this case without moving images around.
You can store all the file paths in a list and create a tensorflow dataset with it.
A simple way to get all the file paths is
import glob
paths = glob.glob("train/*/*")
#If you want only the .png files
paths = glob.glob("train/*/*.png")
Full example
I created some sample images here on Colab
paths = glob.glob("/content/*/*.jpeg")
paths
>>>
['/content/dir_1/cat_1.jpeg',
'/content/dir_1/dog_1.jpeg',
'/content/dir_2/dog_2.jpeg',
'/content/dir_2/cat_2.jpeg']
paths = glob.glob("/content/*/*.jpeg")
label_dict = {'cat':0, 'dog':1}
def get_label(x):
label = x.split('.')[0].rsplit('_')[-2].split('/')[-1]
return label_dict[label]
labels = [get_label(i) for i in paths]
labels
>>>
[0, 1, 1, 0]
Creating dataset
def preprocess(x,y):
img = tf.io.read_file(x)
img = tf.io.decode_png(img, channels=3)
img = tf.cast(img,dtype=tf.float32)
img = img / 255.
return img,y
dataset = tf.data.Dataset.from_tensor_slices((paths,labels))
dataset = dataset.map(preprocess)
dataset = dataset.batch(2)
for x,y in dataset.take(1):
print(x.shape, y)
>>>
(2, 192, 262, 3) tf.Tensor([0 1], shape=(2,), dtype=int32)
There is no method that lets you do this automatically like image_dataset_from_directory (that I know of at least). However creating the dataset on your own is pretty easy.
You first have to iterate through your directory searching for the images. You save the path of each image you encounter in a list paths. For each image you check the class, either dog or cat in this case, and you create a dictionary where you map each class name to a different int, the label of that class. For each image you save aside the label of it in a list labels.
import glob
paths = []
labels = []
labels_dict = {}
index = 0
for filepath in glob.iglob('train/*/*.jpg'):
paths.append(filepath)
class_name = filepath.split('_')[1].split("/")[-1]
if class_name not in labels_dict:
labels_dict[class_name] = index
index += 1
labels.append(labels_dict[class_name])
print(labels)
print(paths)
print(labels_dict)
Outputs:
# labels:
[0, 1, 1, 1, 1, 0]
# paths
['train/dir_1/dog_1.jpg', 'train/dir_1/cat_2.jpg', 'train/dir_1/cat_1.jpg', 'train/dir_2/cat_1.jpg', 'train/dir_2/cat_2.jpg', 'train/dir_2/dog_1.jpg']
# labels_dict
{'dog': 0, 'cat': 1}
Now that you have prepared your paths and labels, you can proceed as from this answer:
import tensorflow as tf
paths = tf.constant(paths)
labels = tf.constant(labels)
# create a dataset returning slices of `paths`
dataset = tf.data.Dataset.from_tensor_slices((paths, labels))
# parse every image in the dataset using `map`
def _parse_function(filename, label):
image_string = tf.io.read_file(filename)
image_decoded = tf.image.decode_jpeg(image_string, channels=3)
image = tf.cast(image_decoded, tf.float32)
return image, label
dataset = dataset.map(_parse_function)
I have my data in multiple pickle files stored on disk. I want to use tensorflow's tf.data.Dataset to load my data into training pipeline. My code goes:
def _parse_file(path):
image, label = *load pickle file*
return image, label
paths = glob.glob('*.pkl')
print(len(paths))
dataset = tf.data.Dataset.from_tensor_slices(paths)
dataset = dataset.map(_parse_file)
iterator = dataset.make_one_shot_iterator()
Problem is I don't know how to implement the _parse_file fuction. The argument to this function, path, is of tensor type. I tried
def _parse_file(path):
with tf.Session() as s:
p = s.run(path)
image, label = pickle.load(open(p, 'rb'))
return image, label
and got error message:
InvalidArgumentError (see above for traceback): You must feed a value for placeholder tensor 'arg0' with dtype string
[[Node: arg0 = Placeholder[dtype=DT_STRING, shape=<unknown>, _device="/job:localhost/replica:0/task:0/device:CPU:0"]()]]
After some search on the Internet I still have no idea how to do it. I will be grateful to anyone providing me a hint.
I have solved this myself. I should use tf.py_func as in this doc.
This is how I solved this issue. I didn't use the tf.py_func; check out function "load_encoding()" below, which is what's doing the pickle reading. The FACELIB_DIR contains directories of pickled vggface2 encodings, each directory named for the person of those face encodings.
import tensorflow as tf
import pickle
import os
FACELIB_DIR='/var/noggin/FaceEncodings'
# Get list of all classes & build a quick int-lookup dictionary
labelNames = sorted([x for x in os.listdir(FACELIB_DIR) if os.path.isdir(os.path.join(FACELIB_DIR,x)) and not x.startswith('.')])
labelStrToInt = dict([(x,i) for i,x in enumerate(labelNames)])
# Function load_encoding - Loads Encoding data from enc2048 file in filepath
# This reads an encoding from disk, and through the file path gets the label oneHot value, returns both
def load_encoding(file_path):
with open(os.path.join(FACELIB_DIR,file_path),'rb') as fin:
A,_ = pickle.loads(fin.read()) # encodings, source_image_name
label_str = tf.strings.split(file_path, os.path.sep)[-2]
return (A, labelStrToInt[label_str])
# Build the dataset of every enc2048 file in our data library
encpaths = []
for D in sorted([x for x in os.listdir(FACELIB_DIR) if os.path.isdir(os.path.join(FACELIB_DIR,x)) and not x.startswith('.')]):
# All the encoding files
encfiles = sorted(filter((lambda x: x.endswith('.enc2048')), os.listdir(os.path.join(FACELIB_DIR, D))))
encpaths += [os.path.join(D,x) for x in encfiles]
dataset = tf.data.Dataset.from_tensor_slices(encpaths)
# Shuffle and speed improvements on the dataset
BATCH_SIZE = 64
from tensorflow.data import AUTOTUNE
dataset = (dataset
.shuffle(1024)
.cache()
.repeat()
.batch(BATCH_SIZE)
.prefetch(AUTOTUNE)
)
# Benchmark our tf.data pipeline
import time
datasetGen = iter(dataset)
NUM_STEPS = 10000
start_time = time.time()
for i in range(0, NUM_STEPS):
X = next(datasetGen)
totalTime = time.time() - start_time
print('==> tf.data generated {} tensors in {:.2f} seconds'.format(BATCH_SIZE * NUM_STEPS, totalTime))
tf.py_func
This function is used to solved that problem and also as menstion in doc.
Can someone give a example on how to use tensorboard visualize numpy array value?
There is a related question here, I don't really get it.
Tensorboard logging non-tensor (numpy) information (AUC)
For example,
If I have
for i in range(100):
foo = np.random.rand(3,2)
How can I keep tracking the distribution of foo using tensorboard for 100 iterations? Can someone give a code example?
Thanks.
For simple values (scalar), you can use this recipe
summary_writer = tf.train.SummaryWriter(FLAGS.logdir)
summary = tf.Summary()
summary.value.add(tag=tagname, simple_value=value)
summary_writer.add_summary(summary, global_step)
summary_writer.flush()
As far as using array, perhaps you can add 6 values in a sequence, ie
for value in foo:
summary.value.add(tag=tagname, simple_value=value)
Another (simplest) way is just using placeholders. First, you can make a placeholder for your numpy array shape.
# Some place holders for summary
summary_reward = tf.placeholder(tf.float32, shape=(), name="reward")
tf.summary.scalar("reward", summary_reward)
Then, just call session.run the merged summary with the feed_dict.
# Summary
summ = tf.summary.merge_all()
...
s = sess.run(summ, feed_dict={summary_reward: reward})
writer.add_summary(s, i)
if you install this package via pip install tensorboard-pytorch it becomes as straightforward as it can get:
import numpy as np
from tensorboardX import SummaryWriter
writer = SummaryWriter()
for i in range(50):
writer.add_histogram("moving_gauss", np.random.normal(i, i, 1000), i, bins="auto")
writer.close()
Will generate the corresponding histogram data in the runs directory:
Found a way to work around, create a variable and assign the value of numpy array to the variable, use tensorboard to track the variable
mysummary_writer = tf.train.SummaryWriter("./tmp/test/")
a = tf.Variable(tf.zeros([3,2]), name="a")
sum1 = tf.histogram_summary("nparray1", a)
summary_op = tf.merge_all_summaries()
sess = tf.Session()
sess.run(tf.initialize_all_variables())
for ii in range(10):
foo = np.random.rand(3, 2)
assign_op = a.assign(foo)
summary, _ = sess.run([summary_op, assign_op])
mysummary_writer.add_summary(tf.Summary.FromString(summary), global_step=ii)
mysummary_writer.flush()
sess = tf.Session()
writer = tf.summary.FileWriter('tensorboard_test')
var = tf.Variable(0.0,trainable=False,name='loss')
sess.run(var.initializer)
summary_op = tf.summary.scalar('scalar1',var)
for value in array:
sess.run(var.assign(value))
summary = sess.run(summary_op)
writer.add_summary(summary,i)
It works, but slow.
You could define a function like this (taken from gyglim's gist):
def add_histogram(writer, tag, values, step, bins=1000):
"""
Logs the histogram of a list/vector of values.
From: https://gist.github.com/gyglim/1f8dfb1b5c82627ae3efcfbbadb9f514
"""
# Create histogram using numpy
counts, bin_edges = np.histogram(values, bins=bins)
# Fill fields of histogram proto
hist = tf.HistogramProto()
hist.min = float(np.min(values))
hist.max = float(np.max(values))
hist.num = int(np.prod(values.shape))
hist.sum = float(np.sum(values))
hist.sum_squares = float(np.sum(values ** 2))
# Requires equal number as bins, where the first goes from -DBL_MAX to bin_edges[1]
# See https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/framework/summary.proto#L30
# Therefore we drop the start of the first bin
bin_edges = bin_edges[1:]
# Add bin edges and counts
for edge in bin_edges:
hist.bucket_limit.append(edge)
for c in counts:
hist.bucket.append(c)
# Create and write Summary
summary = tf.Summary(value=[tf.Summary.Value(tag=tag, histo=hist)])
writer.add_summary(summary, step)
And then add to the summary writer like this:
add_histogram(summary_writer, "Histogram_Name", your_numpy_array, step)
You can plot the vector with matplotlib, convert the plot to numpy array along the lines of
https://stackoverflow.com/a/35362787/10873169, and then add it to Tensorboard as image
import numpy as np
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.figure import Figure
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("runs/example")
for step in range(1, 10):
# Time-dependent vector we want to plot
example_vector = np.sin(np.arange(100) / step)
# Plot it in matplotlib first. Default DPI doesn't look good in Tensorboard
fig = Figure(figsize=(5, 2), dpi=200)
canvas = FigureCanvasAgg(fig)
fig.gca().plot(example_vector)
canvas.draw()
# Get the image as a string of bytes
image_as_string = np.frombuffer(canvas.tostring_rgb(), dtype='uint8')
# Need to reshape to (height, width, channels)
target_shape = canvas.get_width_height()[::-1] + (3,)
reshaped_image = image_as_string.reshape(target_shape)
# Write to Tensorboard logs
writer.add_image("example_vector", reshaped_image,
dataformats="HWC", global_step=step)
writer.close()