Фон
У меня есть следующий код, который работает как шарм и используется для создания пар для сиамской сети:
def make_pairs(images, labels):
# initialize two empty lists to hold the (image, image) pairs and
# labels to indicate if a pair is positive or negative
pairImages = []
pairLabels = []
# calculate the total number of classes present in the dataset
# and then build a list of indexes for each class label that
# provides the indexes for all examples with a given label
#np.unique function finds all unique class labels in our labels list.
#Taking the len of the np.unique output yields the total number of unique class labels in the dataset.
#In the case of the MNIST dataset, there are 10 unique class labels, corresponding to the digits 0-9.
numClasses = len(np.unique(labels))
#idxs have a list of indexes that belong to each class
idx = [np.where(labels == i)[0] for i in range(0, numClasses)]
#let’s now start generating our positive and negative pairs
for idxA in range(len(images)):
# grab the current image and label belonging to the current
# iteration
currentImage = images[idxA]
label = labels[idxA]
# randomly pick an image that belongs to the *same* class
# label
idxB = np.random.choice(idx[label])
posImage = images[idxB]
# prepare a positive pair and update the images and labels
# lists, respectively
pairImages.append([currentImage, posImage])
pairLabels.append([1])
#grab the indices for each of the class labels *not* equal to
#the current label and randomly pick an image corresponding
#to a label *not* equal to the current label
negIdx = np.where(labels != label)[0]
negImage = images[np.random.choice(negIdx)]
# prepare a negative pair of images and update our lists
pairImages.append([currentImage, negImage])
pairLabels.append([0])
#return a 2-tuple of our image pairs and labels
return (np.array(pairImages), np.array(pairLabels))
Хорошо, этот код работает, выбирая пары для каждого изображения в наборе данных MNIST. Он строит одну пару для этого изображения, случайным образом выбирая другое изображение того же класса (метки) и другой фрагмент другого класса (метки), чтобы создать другую пару. При запуске кода окончательные формы возвращенных двух матриц:
# load MNIST dataset and scale the pixel values to the range of [0, 1]
print("[INFO] loading MNIST dataset...")
(trainX, trainY), (testX, testY) = mnist.load_data()
# build the positive and negative image pairs
print("[INFO] preparing positive and negative pairs...")
(pairTrain, labelTrain) = make_pairs(trainX, trainY)
(pairTest, labelTest) = make_pairs(testX, testY)
>> print(pairTrain.shape)
(120000, 2, 28, 28)
>> print(labelTrain.shape)
(120000, 1)
Мой набор данных
Я хочу сделать что-то немного другое с другим набором данных. Предположим, у меня есть еще один набор данных из 5600 изображений RGB с размерами 28x28x3, как показано ниже:
>>> images2.shape
(5600, 28, 28, 3)
У меня есть еще один массив, назовем его labels2, он имеет 8 меток для всех 5600 изображений, по 700 изображений на метку, как показано ниже:
>>> labels2.shape
(5600,)
>>> len(np.unique(labels2))
8
>>> (labels2==0).sum()
700
>>> (labels2==1).sum()
700
>>> (labels2==2).sum()
700
...
Что я хочу делать
Мой набор данных не является набором данных MNIST, поэтому изображения из одного класса не так похожи. Я хотел бы построить пары, которые примерно одинаковы, следующим образом:
Для каждого изображения в моем наборе данных я хочу сделать следующее:
1.1. Рассчитайте сходство с помощью MSE между этим изображением и всеми остальными в наборе данных.
1.2 Для набора изображений MSE с той же меткой, что и у этого изображения, выберите изображения с 7 наименьшими значениями MSE и постройте 7 пар, содержащих это изображение и 7 ближайших изображений MSE. Эти пары представляют изображения одного класса для моей сиамской сети.
1.3 Для набора СКО изображений с разными метками из этого изображения выберите для каждой отдельной метки только одно изображение с наименьшими СКО. Следовательно, поскольку имеется 7 меток, отличных от метки этого изображения, для этого изображения есть еще 7 пар.
Поскольку для моего набора данных имеется 5600 изображений 28x28x3, и для каждого изображения я создаю 14 пар (7 одного класса и 7 для разных классов), я ожидаю, что у меня будет матрица pairTrain размера (78400, 2, 28, 28, 3)
Что я сделал
У меня есть следующий код, который делает именно то, что я хочу:
def make_pairs(images, labels):
# initialize two empty lists to hold the (image, image) pairs and
# labels to indicate if a pair is positive or negative
pairImages = []
pairLabels = []
#In my dataset, there are 8 unique class labels, corresponding to the classes 0-7.
numClasses = len(np.unique(labels))
#Initial lists
pairLabels=[]
pairImages=[]
#let’s now start generating our positive and negative pairs for each image in the dataset
for idxA in range(len(images)):
print("Image "+str(k)+ " out of " +str(len(images)))
k=k+1
#For each image, I need to store the MSE between it and all the others
mse_all=[]
#Get each image and its label
currentImage = images[idxA]
label = labels[idxA]
#Now we need to iterate through all the other images
for idxB in range(len(images)):
candidateImage = images[idxB]
#Calculate the mse and store all mses
mse=np.mean(candidateImage - currentImage)**2
mse_all.append(mse)
mse_all=np.array(mse_all)
#When we finished calculating mse between the currentImage ad all the others,
#let's add 7 pairs that have the smallest mse in the case of images from the
#same class and 1 pair for each different class
#For all classes, do
for i in range(0,numClasses):
#get indices of images for that class
idxs=[np.where(labels == i)[0]]
#Get images of that class
imgs=images[np.array(idxs)]
imgs=np.squeeze(imgs, axis=0)
#get MSEs between the currentImage and all the others of that class
mse_that_class=mse_all[np.array(idxs)]
mse_that_class=np.squeeze(mse_that_class, axis=0)
#if the class is the same class of that image
if i==label:
#Get indices of that class that have the 7 smallest MSEs
indices_sorted = np.argpartition(mse_that_class, numClasses-1)
else:
#Otherwise, get only the smallest MSE
indices_sorted = np.argpartition(mse_that_class, 1)
# Now, lets pair them
for j in range(0,indices_sorted.shape[0]):
image_to_pair=imgs[indices_sorted[j], :, :, :]
pairImages.append([currentImage, image_to_pair])
if i==label:
pairLabels.append([1])
else:
pairLabels.append([0])
del image_to_pair, currentImage, label, mse_that_class, imgs, indices_sorted, idxs, mse_all
return (np.array(pairImages), np.array(pairLabels))
Моя проблема
Проблема с моим кодом в том, что он просто зависает мой компьютер, когда я запускаю построение пар для изображения номер 2200, я пытался очистить переменные после каждого цикла, как вы можете видеть в приведенном выше коде (del image_to_pair, currentImage, label, mse_that_class , imgs, index_sorted, idxs, mse_all). Вопрос в том, что матрицу парных изображений (120000, 2, 28, 28) построить было несложно, а матрицу (78400,2,28,28,3) — сложно. Так:
Вы можете найти функциональный код и входные матрицы ЗДЕСЬ
Я думаю, вы можете решить это, вычислив сходство (например: kneighbors (евклидово расстояние)) по вектору признаков из последнего слоя предварительно сохраненной сети изображений.
Вы можете попробовать запускать gc.collect() в начале каждого цикла, чтобы активно запускать сборщик мусора. Память в Python не освобождается до тех пор, пока не запустится сборка мусора. Мне не ясно, делает ли ваш текущий оператор del то, что вы хотите. (del уменьшает счетчик ссылок, но не обязательно освобождает память, и ваш код фактически передает ему только что созданный кортеж вместо переменных).
78400 * 2 * 28 * 28 * 3 = 368 793 600, что умножается на размер каждого фрагмента данных в байтах, что указывает мне на то, что это должна быть проблема с памятью. Я предполагаю, что зависание - это компьютер, пытающийся переключиться с использования ОЗУ на использование файла подкачки на диске, и интенсивное использование файла подкачки, подобное этому, приведет к тому, что любой компьютер сделает дамп.
Ваши изображения также должны загружаться по одному через генератор, а не упаковываться в массив.
import gc
gc.collect()
filenames = ["a.jpg", "b.jpg"]
labels = ["a", "b"]
def image_loader(filenames): # this is a generator, not a function
# code to load image
for f in filenames:
gc.collect() # make super sure we're freeing the memory from the last image
image = load_or_something(filename)
yield image
make_pairs(image_loader(filenames), labels)
Генераторы работают точно так же, как списки с учетом циклов for и подобных вещей, с той разницей, что каждый элемент в списке генерируется на месте, а не загружается в память. (Это немного технически, но тл;д-р это средство для создания списков, которое загружает изображения только на лету).
Я считаю, что вы можете упростить эту часть своего кода, что также должно помочь во время выполнения.
#Now we need to iterate through all the other images
for idxB in range(len(images)):
candidateImage = images[idxB]
#Calculate the mse and store all mses
mse=np.mean(candidateImage - currentImage)**2
mse_all.append(mse)
Вместо того, чтобы перебирать ваши данные с помощью цикла for
, вы можете просто сделать это и позволить NumPy выполнять трансляцию.
# assuming images is a numpy array with shape 5600,28,28,3
mse_all = np.mean( ((images - currentImage)**2).reshape(images.shape[0],-1), axis=1 )
# mse_all.shape 5600,
Помимо попытки заставить сборщик мусора освободить неиспользуемую память (это, кажется, не решает вашу проблему, пытаясь это сделать), я думаю, что в вашем коде есть другие проблемы, если только я не понял, что происходит.
Глядя на следующий фрагмент:
#Agora adiciono os 7 mais parecidos com aquele bloco, sendo 7 da mesma e 1 de cada das outras classes. 1 bloco
for j in range(0,indices_sorted.shape[0]):
Кажется, вы выполняете итерацию для j <- (0..indices_sorted.shape[0]), где index_sorted.shape[0] всегда равно 700, и я не уверен, что это то, что вы хотите. Я думаю, вам нужно просто j <- (0..6). Кроме того, значения на изображениях всегда ниже 255, если я правильно понял. Если да, то можно добавить вторую оптимизацию: вы можете форсировать тип uint8. Короче говоря, я думаю, вы могли бы реорганизовать что-то похожее на это:
for i in range(0,numClasses):
# ...
# ...
#print(indices_sorted.shape) # -> (700,)
#print(indices_sorted.shape[0]) # --> always 700!so why range(0, 700?)
#Agora adiciono os 7 mais parecidos com aquele bloco, sendo 7 da mesma e 1 de cada das outras classes. 1 bloco
for j in range(0, 7):
image_to_pair=np.array(imgs[indices_sorted[j], :, :, :], dtype=np.uint8)
pairImages.append([currentImage, image_to_pair])
if i==label:
pairLabels.append([1])
else:
pairLabels.append([0])
del imgs, idxs
#When finished to find the pairs for that image, lets clean the trash
del image_to_pair, currentImage, label, mse_that_class, indices_sorted, mse_all
gc.collect()
Проведя некоторые тесты, я увидел это, комментируя:
pairImages.append([currentImage, image_to_pair])
у вас почти 0 памяти.
В качестве дополнительного примечания я переместил операцию del imgs
и idxs
внутрь i для
и основное улучшение здесь, по-видимому, достигается путем принудительного ввода правильного типа:
image_to_pair=np.array(imgs[indices_sorted[j], :, :, :], dtype=np.uint8)
Согласно моему тесту, с исходным кодом использование памяти для k = 100, 200 составляет соответственно 642351104 байта и 783355904 байта. Таким образом, увеличение использования памяти для 100 итераций составляет 134,5 МБ. После применения вышеуказанной модификации имеем 345239552 B и 362336256 B с увеличением всего на 16,5 МБ.
Вы написали
mse=np.mean(candidateImage - currentImage)**2
; Я предполагаю, чтоmse
означает среднеквадратичную ошибку, и в этом случае это должно бытьmse=np.mean((candidateImage - currentImage)**2)
. Или вы можете использоватьfrom sklearn.metrics import mean_squared_error; mse = mean_squared_error(candidateImage,currentImage)
.