Deep Learning dla każdego 2: Deep Dream

in polish •  7 years ago  (edited)

Wstęp

DeapDream to termin, który początkowo odnosił się do programu bazującego na sieciach neuronowych stworzonego przez Google, który służył do wykrywania wzorców, jednak jako "efektem uboczny" powstawały zdeformowane obrazy, które sprawiały wrażenie, że są wytworem halucynacji. Z czasem tym terminem zaczęto nazywać ogólną technikę związaną z powstawaniem takich obrazów, a nie tylko ten pierwszy program. W sieci pojawiło się wiele stron służących do generowania tego typu dzieł np: https://dreamscopeapp.com/deep-dream-generator.

Opisując to w inny sposób - załóżmy, że mamy sieć neuronową, która potrafi wykrywać zwierzęta na zdjęciach. Czyli jeśli na wejściu pojawi się zdjęcie z kotem, to sieć zwróci wartość kot, jeśli na wejściu pojawi się zdjęcie z psem to sieć zwróci wartość pies. A teraz pomyślmy co się stanie jeśli mamy odpowiednio wytrenowaną sieć, ale spróbujemy odwrócić jej działanie - tak, żeby na wejściu móc wprowadzić np tekst kot i oczekiwać od sieci obrazu, który sieć zaklasyfikowałaby jako właśnie kot. W ten sposób powstawały twory takie jak przedstawione poniżej. De facto dowiedzieliśmy się co tak naprawdę potrafi skrywać w sobie sieć neuronowa.

Implementacja

Okazuje się, że taka sieć neuronowa nie jest specjalnie trudna w implementacji (oczywiście w podstawowej formie; bez żadnych zaawansowanych ulepszeń / optymalizacji). Poniżej kompletny kod.

Wymagany jest python3.x oraz biblioteki:

  • numpy
  • scipy
  • matplotlib
  • tensorflow
  • keras


Opis

Na początku importujemy potrzebne biblioteki.

import numpy as np
import scipy
from keras.applications import inception_v3
from keras import backend as K
from keras.preprocessing.image import load_img, img_to_array
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

Następnie określamy nazwę pliku wejściowego.

image_path = 'cats.jpg'

Wejściowy obraz musimy odpowiednio przetworzyć, tak aby był w formacie jakiego oczekuje sieć neuronowa. Służy do tego poniższa funkcja.

def preprocess(path):
    image = np.expand_dims(img_to_array(load_img(path)), axis=0)
    return inception_v3.preprocess_input(image)

Kolejna funkcja realizuje odwrotną operację do tej przedstawionej powyżej - czyli przekształca obraz z reprezentacji wymaganej przez sieć neuronową na reprezentację umożliwiające wyświetlenie obrazu bądź jego zapisanie.

def deprocess(image):
    image = image.reshape((image.shape[1], image.shape[2], 3))
    image /= 2.
    image += 0.5
    image *= 255.
    return np.clip(image, 0, 255).astype('uint8')

Korzystamy tutaj z (gotowej) konwolucyjnej sieci neuronowej InceptionV3, która była trenowana na zbiorze imagenet. Dodajemy kolejne warstwy. Wartości w zmiennej layer_names możemy zmieniać i obserwować jak nasza zmiana wpływa na końcowe wyniki.

layer_names = {'mixed2': 0.2, 'mixed3': 0.5, 'mixed4': 1.5, 'mixed5': 2}
K.set_learning_phase(0)
model = inception_v3.InceptionV3(weights='imagenet', include_top=False)
model_input = model.input
layers = dict([(layer.name, layer) for layer in model.layers])

loss = K.variable(0.)
for layer_name in layer_names:
    coefficients = layer_names[layer_name]
    image = layers[layer_name].output
    loss += coefficients * K.sum(K.square(image[:, 2: -2, 2: -2, :])) / K.prod(K.cast(K.shape(image), 'float32'))

Wyznaczamy gradienty oraz "stratę" (choć tutaj lepszym określeniem będzie "zysk" gdyż zależy nam na maksymalizacji).

gradients = K.gradients(loss, model_input)[0]
gradients /= K.maximum(K.mean(K.abs(gradients)), K.epsilon())
outputs = [loss, gradients]
loss_and_gradients = K.function([model_input], outputs)

Pomocnicza funkcja zwracająca stratę oraz gradienty.

def eval_loss_and_gradients(x):
    return loss_and_gradients([x])

Funkcja skalująca zdjęcie do odpowiedniego rozmiaru.

def resize(image, size):
    image = np.copy(image)
    factors = (1, float(size[0]) / image.shape[1], float(size[1]) / image.shape[2], 1)
    return scipy.ndimage.zoom(image, factors, order=1)

Gradient ascent to metoda optymalizacji analogiczna jak gradient descent z tą różnicą, że tutaj zależy nam na maksymalizacji, a nie minimalizacji.

def gradient_ascent(image, iterations, step, max_loss=None):
    for _ in range(iterations):
        loss_value, grad_values = eval_loss_and_gradients(image)
        if max_loss is not None and loss_value > max_loss:
            break
        image += step * grad_values
    return image

Poniższe parametry (hyperparameters) możemy dowolnie zmieniać.
step - krok dla gradient ascent
num_octave - ile razy rozmiar zdjęcia będzie zmieniany podczas działania algorytmu
scale - skala dla zmian rozmiaru zdjęć
max_iterations - maksymalna liczba iteracji
max_loss - maksymalna strata

Idea działania algorytmu wygląda następująco:

  • wczytujemy zdjęcie wejściowe
  • na podstawie num_octave oraz scale definiujemy wszystkie pośrednie wymiary, które będą używane w trakcie działania algorytmu
  • skalujemy zdjęcie do najmniejszego rozmiaru
  • dla każdej skali:
    a. wykonujemy gradient_ascent
    b. skalujemy do wyższego rozmiaru
    c. dodajemy detale, które zostały utracone podczas skalowania (można je otrzymać porównując z oryginalnym zdjęciem)

Powyższy opis może być nieco zawiły; najlepszym sposobem na dokładne zrozumienie jest po prostu uruchomienie programu i sprawdzenie co dokładnie się dzieje w danym kroku.

def main():
    step = 0.01
    num_octave = 4
    scale = 1.4
    max_iterations = 5
    max_loss = 10.

    image = preprocess(image_path)
    input_shape = image.shape[1:3]
    all_shapes = [input_shape]
    for i in range(1, num_octave):
        shape = tuple([int(dim / (scale ** i)) for dim in input_shape])
        all_shapes.append(shape)
    all_shapes = all_shapes[::-1]
    original_image = np.copy(image)
    shrunk_image = resize(image, all_shapes[0])

    for shape in all_shapes:
        image = resize(image, shape)
        image = gradient_ascent(image,
                                iterations=max_iterations,
                                step=step,
                                max_loss=max_loss)
        upscaled_image = resize(shrunk_image, shape)
        resized_image = resize(original_image, shape)
        lost_detail_image = resized_image - upscaled_image
        image += lost_detail_image
        shrunk_image = resize(original_image, shape)

    image = deprocess(np.copy(image))
    mpimg.imsave('_' + image_path, image)


if __name__ == '__main__':
    main()

Rezultaty

Zdjęcie 1

Wynik 1

Zdjęcie 2

Wynik 2

Zdjęcie 3

Wynik 3

Podsumowanie

Przedstawiona tutaj technika ma dwa główne zastosowania. Po pierwsze umożliwia lepsze zrozumienie działania sieci neuronowych "od środka". Co z kolei ułatwia wprowadzanie różnego rodzaju ulepszeń. Po drugie jako sposób tworzenia "sztuki" :)

Była to druga część z serii Deep Learning dla każdego. Następne części będą prawdopodobnie traktować o:

  • kolorowaniu czarno-białych obrazów
  • przekształcaniu grafik będących designem strony na kod HTML (frontendowcy strzeżcie się ;))
  • wykrywaniu spamu na Steem
  • graniu w proste gry

Załącznik

Poniżej jeszcze raz cały kod, tym razem jako tekst (po prostu możliwy do skopiowania).

import numpy as np
import scipy
from keras.applications import inception_v3
from keras import backend as K
from keras.preprocessing.image import load_img, img_to_array
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

image_path = 'cats.jpg'
layer_names = {'mixed2': 0.2, 'mixed3': 0.5, 'mixed4': 1.5, 'mixed5': 2}


def preprocess(path):
    image = np.expand_dims(img_to_array(load_img(path)), axis=0)
    return inception_v3.preprocess_input(image)


def deprocess(image):
    image = image.reshape((image.shape[1], image.shape[2], 3))
    image /= 2.
    image += 0.5
    image *= 255.
    return np.clip(image, 0, 255).astype('uint8')


K.set_learning_phase(0)
model = inception_v3.InceptionV3(weights='imagenet', include_top=False)
model_input = model.input
layers = dict([(layer.name, layer) for layer in model.layers])

loss = K.variable(0.)
for layer_name in layer_names:
    coefficients = layer_names[layer_name]
    image = layers[layer_name].output
    loss += coefficients * K.sum(K.square(image[:, 2: -2, 2: -2, :])) / K.prod(K.cast(K.shape(image), 'float32'))

gradients = K.gradients(loss, model_input)[0]
gradients /= K.maximum(K.mean(K.abs(gradients)), K.epsilon())
outputs = [loss, gradients]
loss_and_gradients = K.function([model_input], outputs)


def eval_loss_and_gradients(x):
    return loss_and_gradients([x])


def resize(image, size):
    image = np.copy(image)
    factors = (1, float(size[0]) / image.shape[1], float(size[1]) / image.shape[2], 1)
    return scipy.ndimage.zoom(image, factors, order=1)


def gradient_ascent(image, iterations, step, max_loss=None):
    for _ in range(iterations):
        loss_value, grad_values = eval_loss_and_gradients(image)
        if max_loss is not None and loss_value > max_loss:
            break
        image += step * grad_values
    return image


def main():
    step = 0.01
    num_octave = 4
    scale = 1.4
    max_iterations = 5
    max_loss = 10.

    image = preprocess(image_path)
    input_shape = image.shape[1:3]
    all_shapes = [input_shape]
    for i in range(1, num_octave):
        shape = tuple([int(dim / (scale ** i)) for dim in input_shape])
        all_shapes.append(shape)
    all_shapes = all_shapes[::-1]
    original_image = np.copy(image)
    shrunk_image = resize(image, all_shapes[0])

    for shape in all_shapes:
        image = resize(image, shape)
        image = gradient_ascent(image,
                                iterations=max_iterations,
                                step=step,
                                max_loss=max_loss)
        upscaled_image = resize(shrunk_image, shape)
        resized_image = resize(original_image, shape)
        lost_detail_image = resized_image - upscaled_image
        image += lost_detail_image
        shrunk_image = resize(original_image, shape)

    image = deprocess(np.copy(image))
    mpimg.imsave('_' + image_path, image)


if __name__ == '__main__':
    main()

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Chociaż chyba nigdy nie zrozumiem technikaliów, mniej więcej rozumiem jak to działa tak logicznie. Nie mogę wyjść z podziwu i używam tego algorytmu jako jednego z głównych narzędzi w mojej przygodzie ze sztuką. Za jakiś czas wrzucę posta z efektami deepdreama w połączeniu z innymi narzędziami, które osiągnąłem:) Najciekawsza jest część, że wiele osób porównuje stylistykę tych obrazów do wizuali po psychodelikach. Czyżby to była jakaś uniwersalna cecha sieci neuronowych? :)

Nie znam się na psychodelikach ;) ale może chodzi o to, że w obu przypadkach niejako wyciągamy na zewnątrz to co jest w środku mózgu / sieci neuronowej i w normalnych warunkach nie mamy do tego dostępu :)

Możliwe, chociaż ciężko cokolwiek stwierdzić na tym etapie, wrzucam jeszcze artykuł gdzie się trochę rozpisali o tym zjawisku

https://motherboard.vice.com/en_us/article/53985k/why-googles-neural-networks-look-like-theyre-on-acid

Kurcze, piękne. Czekam na dalsze części, szczególnie drugi z zapowiadanej serii.

(Test) Upvoted and followed!

Please stop spamming in comments or else people may flag you!
Spam probability: 86.09%

Dobry bot!

wiem, że to bardzo "oklepany" pomysł ale czy próbowałeś stworzyć sieć, która rozpozna setupy na wykresie danego instrumentu finansowego? jednak bazując na danych liczbowych a nie graficznych. choć przyznam, że próba nauczenia sieci rozpoznawania setupu graficznie też jest ciekawa.

Trochę bawiłem się w przewidywanie wykresów, ale przyznam, że skuteczne nauczenie sieci neuronowej (LSTM) jest w tym przypadku bardzo trudne.

Dziękuję za informację. Tak się spodziewałem, że nie będzie to łatwe bo gdyby było to zapewne już parę milionów sieć dała by zarobić:) A tak na poważnie, czy próbowałeś albo czy masz możliwość spróbowania zbudowania sieci, która uczyła by się korzystając z wiedzy VSA (Volume Spread Analysis)? Zajmuję się tym tematem od paru lat (w sensie bez pomocy sieci neuronowych) i tak sobie myślę, że kilka powtarzających się setupów sieć powinna dać radę się nauczyć. Napisz, proszę czy byłbyś zainteresowany podjęciem takiego wyzwania? Dziękuję.