885 lines
45 KiB
HTML
885 lines
45 KiB
HTML
<!DOCTYPE html>
|
|
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<meta name="generator" content="DocOnce: https://github.com/doconce/doconce/" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
|
|
<title></title>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- reveal.js: https://lab.hakim.se/reveal-js/ -->
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
|
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui">
|
|
|
|
<link rel="stylesheet" href="reveal.js/css/reveal.css">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/beige.css" id="theme">
|
|
<!--
|
|
<link rel="stylesheet" href="reveal.js/css/reveal.css">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/beige.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/beigesmall.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/solarized.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/serif.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/night.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/moon.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/simple.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/sky.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/darkgray.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/default.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/cbc.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/simula.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/black.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/white.css" id="theme">
|
|
<link rel="stylesheet" href="reveal.js/css/theme/league.css" id="theme">
|
|
-->
|
|
|
|
<!-- For syntax highlighting -->
|
|
<link rel="stylesheet" href="reveal.js/lib/css/zenburn.css">
|
|
|
|
<!-- Printing and PDF exports -->
|
|
<script>
|
|
var link = document.createElement( 'link' );
|
|
link.rel = 'stylesheet';
|
|
link.type = 'text/css';
|
|
link.href = window.location.search.match( /print-pdf/gi ) ? 'css/print/pdf.css' : 'css/print/paper.css';
|
|
document.getElementsByTagName( 'head' )[0].appendChild( link );
|
|
</script>
|
|
|
|
<style type="text/css">
|
|
hr { border: 0; width: 80%; border-bottom: 1px solid #aaa}
|
|
p.caption { width: 80%; font-size: 60%; font-style: italic; text-align: left; }
|
|
hr.figure { border: 0; width: 80%; border-bottom: 1px solid #aaa}
|
|
.reveal .alert-text-small { font-size: 80%; }
|
|
.reveal .alert-text-large { font-size: 130%; }
|
|
.reveal .alert-text-normal { font-size: 90%; }
|
|
.reveal .alert {
|
|
padding:8px 35px 8px 14px; margin-bottom:18px;
|
|
text-shadow:0 1px 0 rgba(255,255,255,0.5);
|
|
border:5px solid #bababa;
|
|
-webkit-border-radius: 14px; -moz-border-radius: 14px;
|
|
border-radius:14px;
|
|
background-position: 10px 10px;
|
|
background-repeat: no-repeat;
|
|
background-size: 38px;
|
|
padding-left: 30px; /* 55px; if icon */
|
|
}
|
|
.reveal .alert-block {padding-top:14px; padding-bottom:14px}
|
|
.reveal .alert-block > p, .alert-block > ul {margin-bottom:1em}
|
|
/*.reveal .alert li {margin-top: 1em}*/
|
|
.reveal .alert-block p+p {margin-top:5px}
|
|
/*.reveal .alert-notice { background-image: url(https://hplgit.github.io/doconce/bundled/html_images/small_gray_notice.png); }
|
|
.reveal .alert-summary { background-image:url(https://hplgit.github.io/doconce/bundled/html_images/small_gray_summary.png); }
|
|
.reveal .alert-warning { background-image: url(https://hplgit.github.io/doconce/bundled/html_images/small_gray_warning.png); }
|
|
.reveal .alert-question {background-image:url(https://hplgit.github.io/doconce/bundled/html_images/small_gray_question.png); } */
|
|
|
|
</style>
|
|
|
|
|
|
|
|
<!-- Styles for table layout of slides -->
|
|
<style type="text/css">
|
|
td.padding {
|
|
padding-top:20px;
|
|
padding-bottom:20px;
|
|
padding-right:50px;
|
|
padding-left:50px;
|
|
}
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
<div class="reveal">
|
|
|
|
<!-- Any section element inside the <div class="slides"> container
|
|
is displayed as a slide -->
|
|
|
|
<div class="slides">
|
|
|
|
|
|
|
|
|
|
|
|
<script type="text/x-mathjax-config">
|
|
MathJax.Hub.Config({
|
|
TeX: {
|
|
equationNumbers: { autoNumber: "none" },
|
|
extensions: ["AMSmath.js", "AMSsymbols.js", "autobold.js", "color.js"]
|
|
}
|
|
});
|
|
</script>
|
|
<script type="text/javascript" async
|
|
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<section>
|
|
<!-- ------------------- main content ---------------------- -->
|
|
|
|
<h1 id="generative-models">Generative Models </h1>
|
|
<b>Generative models</b> describe a class of statistical models that are a contrast
|
|
to <b>discriminative models</b>. Informally we say that generative models can
|
|
generate new data instances while discriminative models discriminate between
|
|
different kinds of data instances. A generative model could generate new photos
|
|
of animals that look like 'real' animals while a discriminative model could tell
|
|
a dog from a cat. More formally, given a data set \( x \) and a set of labels /
|
|
targets \( y \). Generative models capture the joint probability \( p(x, y) \), or
|
|
just \( p(x) \) if there are no labels, while discriminative models capture the
|
|
conditional probability \( p(y | x) \). Discriminative models generally try to draw
|
|
boundaries in the data space (often high dimensional), while generative models
|
|
try to model how data is placed throughout the space.
|
|
|
|
<h2 id="generative-adversarial-networks">Generative Adversarial Networks </h2>
|
|
<b>Generative Adversarial Networks</b> are a type of unsupervised machine learning
|
|
algorithm proposed by <a href="https://arxiv.org/pdf/1406.2661.pdf" target="_blank">Goodfellow et. al</a>
|
|
in 2014 (Read the paper first it's only 6 pages). The simplest formulation of
|
|
the model is based on a game theoretic approach, <em>zero sum game</em>, where we pit
|
|
two neural networks against one another. We define two rival networks, one
|
|
generator \( g \), and one discriminator \( d \). The generator directly produces
|
|
samples
|
|
<p> <br>
|
|
$$
|
|
\begin{equation}
|
|
x = g(z; \theta^{(g)})
|
|
\tag{1}
|
|
\end{equation}
|
|
$$
|
|
<p> <br>
|
|
|
|
The discriminator attempts to distinguish between samples drawn from the
|
|
training data and samples drawn from the generator. In other words, it tries to
|
|
tell the difference between the fake data produced by \( g \) and the actual data
|
|
samples we want to do prediction on. The discriminator outputs a probability
|
|
value given by
|
|
|
|
<p> <br>
|
|
$$
|
|
\begin{equation}
|
|
d(x; \theta^{(d)})
|
|
\tag{2}
|
|
\end{equation}
|
|
$$
|
|
<p> <br>
|
|
|
|
<p>
|
|
indicating the probability that \( x \) is a real training example rather than a
|
|
fake sample the generator has generated. The simplest way to formulate the
|
|
learning process in a generative adversarial network is a zero-sum game, in
|
|
which a function
|
|
|
|
<p> <br>
|
|
$$
|
|
\begin{equation}
|
|
v(\theta^{(g)}, \theta^{(d)})
|
|
\tag{3}
|
|
\end{equation}
|
|
$$
|
|
<p> <br>
|
|
|
|
<p>
|
|
determines the reward for the discriminator, while the generator gets the
|
|
conjugate reward
|
|
|
|
<p> <br>
|
|
$$
|
|
\begin{equation}
|
|
-v(\theta^{(g)}, \theta^{(d)})
|
|
\tag{4}
|
|
\end{equation}
|
|
$$
|
|
<p> <br>
|
|
|
|
<p>
|
|
During learning both of the networks maximize their own reward function, so that
|
|
the generator gets better and better at tricking the discriminator, while the
|
|
discriminator gets better and better at telling the difference between the fake
|
|
and real data. The generator and discriminator alternate on which one trains at
|
|
one time (i.e. for one epoch). In other words, we keep the generator constant
|
|
and train the discriminator, then we keep the discriminator constant to train
|
|
the generator and repeat. It is this back and forth dynamic which lets GANs
|
|
tackle otherwise intractable generative problems. As the generator improves with
|
|
training, the discriminator's performance gets worse because it cannot easily
|
|
tell the difference between real and fake. If the generator ends up succeeding
|
|
perfectly, the the discriminator will do no better than random guessing i.e.
|
|
50\%. This progression in the training poses a problem for the convergence
|
|
criteria for GANs. The discriminator feedback gets less meaningful over time,
|
|
if we continue training after this point then the generator is effectively
|
|
training on junk data which can undo the learning up to that point. Therefore,
|
|
we stop training when the discriminator starts outputting \( 1/2 \) everywhere.
|
|
At convergence we have
|
|
|
|
<p> <br>
|
|
$$
|
|
\begin{equation}
|
|
g^* = \underset{g}{\mathrm{argmin}}\hspace{2pt}
|
|
\underset{d}{\mathrm{max}}v(\theta^{(g)}, \theta^{(d)})
|
|
\tag{5}
|
|
\end{equation}
|
|
$$
|
|
<p> <br>
|
|
|
|
The default choice for \( v \) is
|
|
<p> <br>
|
|
$$
|
|
\begin{equation}
|
|
v(\theta^{(g)}, \theta^{(d)}) = \mathbb{E}_{x\sim p_\mathrm{data}}\log d(x)
|
|
+ \mathbb{E}_{x\sim p_\mathrm{model}}
|
|
\log (1 - d(x))
|
|
\tag{6}
|
|
\end{equation}
|
|
$$
|
|
<p> <br>
|
|
|
|
The main motivation for the design of GANs is that the learning process requires
|
|
neither approximate inference (variational autoencoders for example) nor
|
|
approximation of a partition function. In the case where
|
|
<p> <br>
|
|
$$
|
|
\begin{equation}
|
|
\underset{d}{\mathrm{max}}v(\theta^{(g)}, \theta^{(d)})
|
|
\tag{7}
|
|
\end{equation}
|
|
$$
|
|
<p> <br>
|
|
|
|
is convex in $\theta^{(g)} then the procedure is guaranteed to converge and is
|
|
asymptotically consistent
|
|
( <a href="https://arxiv.org/pdf/1804.09139.pdf" target="_blank">Seth Lloyd on QuGANs</a> ). This is in
|
|
general not the case and it is possible to get situations where the training
|
|
process never converges because the generator and discriminator chase one
|
|
another around in the parameter space indefinitely. A much deeper discussion on
|
|
the currently open research problem of GAN convergence is available
|
|
<a href="https://www.deeplearningbook.org/contents/generative_models.html" target="_blank">here</a>. To
|
|
anyone interested in learning more about GANs it is a highly recommended read.
|
|
Direct quote: "In this best-performing formulation, the generator aims to
|
|
increase the log probability that the discriminator makes a mistake, rather than
|
|
aiming to decrease the log probability that the discriminator makes the correct
|
|
prediction." <a href="https://arxiv.org/abs/1701.00160" target="_blank">Another interesting read</a>
|
|
|
|
<h2 id="writing-our-first-generative-adversarial-network">Writing Our First Generative Adversarial Network </h2>
|
|
Let us now move on to actually implementing a GAN in tensorflow. We will study
|
|
the performance of our GAN on the MNIST dataset. This code is based on and
|
|
adapted from the
|
|
<a href="https://www.tensorflow.org/tutorials/generative/dcgan" target="_blank">google tutorial</a>
|
|
|
|
<p>
|
|
First we import our libraries
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span><span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">os</span>
|
|
<span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">time</span>
|
|
<span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">numpy</span> <span style="color: #8B008B; font-weight: bold">as</span> <span style="color: #008b45; text-decoration: underline">np</span>
|
|
<span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">tensorflow</span> <span style="color: #8B008B; font-weight: bold">as</span> <span style="color: #008b45; text-decoration: underline">tf</span>
|
|
<span style="color: #8B008B; font-weight: bold">import</span> <span style="color: #008b45; text-decoration: underline">matplotlib.pyplot</span> <span style="color: #8B008B; font-weight: bold">as</span> <span style="color: #008b45; text-decoration: underline">plt</span>
|
|
<span style="color: #8B008B; font-weight: bold">from</span> <span style="color: #008b45; text-decoration: underline">tensorflow.keras</span> <span style="color: #8B008B; font-weight: bold">import</span> layers
|
|
<span style="color: #8B008B; font-weight: bold">from</span> <span style="color: #008b45; text-decoration: underline">tensorflow.keras.utils</span> <span style="color: #8B008B; font-weight: bold">import</span> plot_model
|
|
</pre></div>
|
|
<p>
|
|
Next we define our hyperparameters and import our data the usual way
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span>BUFFER_SIZE = <span style="color: #B452CD">60000</span>
|
|
BATCH_SIZE = <span style="color: #B452CD">256</span>
|
|
EPOCHS = <span style="color: #B452CD">30</span>
|
|
|
|
data = tf.keras.datasets.mnist.load_data()
|
|
(train_images, train_labels), (test_images, test_labels) = data
|
|
train_images = np.reshape(train_images, (train_images.shape[<span style="color: #B452CD">0</span>],
|
|
<span style="color: #B452CD">28</span>,
|
|
<span style="color: #B452CD">28</span>,
|
|
<span style="color: #B452CD">1</span>)).astype(<span style="color: #CD5555">'float32'</span>)
|
|
|
|
<span style="color: #228B22"># we normalize between -1 and 1</span>
|
|
train_images = (train_images - <span style="color: #B452CD">127.5</span>) / <span style="color: #B452CD">127.5</span>
|
|
training_dataset = tf.data.Dataset.from_tensor_slices(
|
|
train_images).shuffle(BUFFER_SIZE).batch(BATCH_SIZE)
|
|
</pre></div>
|
|
<p>
|
|
Let's have a quick look
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span>plt.imshow(train_images[<span style="color: #B452CD">0</span>], cmap=<span style="color: #CD5555">'Greys'</span>)
|
|
plt.show()
|
|
</pre></div>
|
|
<p>
|
|
Now we define our two models. This is where the 'magic' happens. There are a
|
|
huge amount of possible formulations for both models. A lot of engineering and
|
|
trial and error can be done here to try to produce better performing models. For
|
|
more advanced GANs this is by far the step where you can 'make or break' a
|
|
model.
|
|
|
|
<p>
|
|
We start with the generator. As stated in the introductory text the generator
|
|
\( g \) upsamples from a random sample to the shape of what we want to predict. In
|
|
our case we are trying to predict MNIST images (\( 28\times 28 \) pixels).
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span><span style="color: #8B008B; font-weight: bold">def</span> <span style="color: #008b45">generator_model</span>():
|
|
<span style="color: #CD5555">"""</span>
|
|
<span style="color: #CD5555"> The generator uses upsampling layers tf.keras.layers.Conv2DTranspose() to</span>
|
|
<span style="color: #CD5555"> produce an image from a random seed. We start with a Dense layer taking this</span>
|
|
<span style="color: #CD5555"> random sample as an input and subsequently upsample through multiple</span>
|
|
<span style="color: #CD5555"> convolutional layers.</span>
|
|
<span style="color: #CD5555"> """</span>
|
|
|
|
<span style="color: #228B22"># we define our model</span>
|
|
model = tf.keras.Sequential()
|
|
|
|
|
|
<span style="color: #228B22"># adding our input layer. Dense means that every neuron is connected and</span>
|
|
<span style="color: #228B22"># the input shape is the shape of our random noise. The units need to match</span>
|
|
<span style="color: #228B22"># in some sense the upsampling strides to reach our desired output shape.</span>
|
|
<span style="color: #228B22"># we are using 100 random numbers as our seed</span>
|
|
model.add(layers.Dense(units=<span style="color: #B452CD">7</span>*<span style="color: #B452CD">7</span>*BATCH_SIZE,
|
|
use_bias=<span style="color: #8B008B; font-weight: bold">False</span>,
|
|
input_shape=(<span style="color: #B452CD">100</span>, )))
|
|
<span style="color: #228B22"># we normalize the output form the Dense layer</span>
|
|
model.add(layers.BatchNormalization())
|
|
<span style="color: #228B22"># and add an activation function to our 'layer'. LeakyReLU avoids vanishing</span>
|
|
<span style="color: #228B22"># gradient problem</span>
|
|
model.add(layers.LeakyReLU())
|
|
model.add(layers.Reshape((<span style="color: #B452CD">7</span>, <span style="color: #B452CD">7</span>, BATCH_SIZE)))
|
|
<span style="color: #8B008B; font-weight: bold">assert</span> model.output_shape == (<span style="color: #8B008B; font-weight: bold">None</span>, <span style="color: #B452CD">7</span>, <span style="color: #B452CD">7</span>, BATCH_SIZE)
|
|
<span style="color: #228B22"># even though we just added four keras layers we think of everything above</span>
|
|
<span style="color: #228B22"># as 'one' layer</span>
|
|
|
|
<span style="color: #228B22"># next we add our upscaling convolutional layers</span>
|
|
model.add(layers.Conv2DTranspose(filters=<span style="color: #B452CD">128</span>,
|
|
kernel_size=(<span style="color: #B452CD">5</span>, <span style="color: #B452CD">5</span>),
|
|
strides=(<span style="color: #B452CD">1</span>, <span style="color: #B452CD">1</span>),
|
|
padding=<span style="color: #CD5555">'same'</span>,
|
|
use_bias=<span style="color: #8B008B; font-weight: bold">False</span>))
|
|
model.add(layers.BatchNormalization())
|
|
model.add(layers.LeakyReLU())
|
|
<span style="color: #8B008B; font-weight: bold">assert</span> model.output_shape == (<span style="color: #8B008B; font-weight: bold">None</span>, <span style="color: #B452CD">7</span>, <span style="color: #B452CD">7</span>, <span style="color: #B452CD">128</span>)
|
|
|
|
model.add(layers.Conv2DTranspose(filters=<span style="color: #B452CD">64</span>,
|
|
kernel_size=(<span style="color: #B452CD">5</span>, <span style="color: #B452CD">5</span>),
|
|
strides=(<span style="color: #B452CD">2</span>, <span style="color: #B452CD">2</span>),
|
|
padding=<span style="color: #CD5555">'same'</span>,
|
|
use_bias=<span style="color: #8B008B; font-weight: bold">False</span>))
|
|
model.add(layers.BatchNormalization())
|
|
model.add(layers.LeakyReLU())
|
|
<span style="color: #8B008B; font-weight: bold">assert</span> model.output_shape == (<span style="color: #8B008B; font-weight: bold">None</span>, <span style="color: #B452CD">14</span>, <span style="color: #B452CD">14</span>, <span style="color: #B452CD">64</span>)
|
|
|
|
model.add(layers.Conv2DTranspose(filters=<span style="color: #B452CD">1</span>,
|
|
kernel_size=(<span style="color: #B452CD">5</span>, <span style="color: #B452CD">5</span>),
|
|
strides=(<span style="color: #B452CD">2</span>, <span style="color: #B452CD">2</span>),
|
|
padding=<span style="color: #CD5555">'same'</span>,
|
|
use_bias=<span style="color: #8B008B; font-weight: bold">False</span>,
|
|
activation=<span style="color: #CD5555">'tanh'</span>))
|
|
<span style="color: #8B008B; font-weight: bold">assert</span> model.output_shape == (<span style="color: #8B008B; font-weight: bold">None</span>, <span style="color: #B452CD">28</span>, <span style="color: #B452CD">28</span>, <span style="color: #B452CD">1</span>)
|
|
|
|
<span style="color: #8B008B; font-weight: bold">return</span> model
|
|
</pre></div>
|
|
<p>
|
|
And there we have our 'simple' generator model. Now we move on to defining our
|
|
discriminator model \( d \), which is a convolutional neural network based image
|
|
classifier.
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span><span style="color: #8B008B; font-weight: bold">def</span> <span style="color: #008b45">discriminator_model</span>():
|
|
<span style="color: #CD5555">"""</span>
|
|
<span style="color: #CD5555"> The discriminator is a convolutional neural network based image classifier</span>
|
|
<span style="color: #CD5555"> """</span>
|
|
|
|
<span style="color: #228B22"># we define our model</span>
|
|
model = tf.keras.Sequential()
|
|
model.add(layers.Conv2D(filters=<span style="color: #B452CD">64</span>,
|
|
kernel_size=(<span style="color: #B452CD">5</span>, <span style="color: #B452CD">5</span>),
|
|
strides=(<span style="color: #B452CD">2</span>, <span style="color: #B452CD">2</span>),
|
|
padding=<span style="color: #CD5555">'same'</span>,
|
|
input_shape=[<span style="color: #B452CD">28</span>, <span style="color: #B452CD">28</span>, <span style="color: #B452CD">1</span>]))
|
|
model.add(layers.LeakyReLU())
|
|
<span style="color: #228B22"># adding a dropout layer as you do in conv-nets</span>
|
|
model.add(layers.Dropout(<span style="color: #B452CD">0.3</span>))
|
|
|
|
|
|
model.add(layers.Conv2D(filters=<span style="color: #B452CD">128</span>,
|
|
kernel_size=(<span style="color: #B452CD">5</span>, <span style="color: #B452CD">5</span>),
|
|
strides=(<span style="color: #B452CD">2</span>, <span style="color: #B452CD">2</span>),
|
|
padding=<span style="color: #CD5555">'same'</span>))
|
|
model.add(layers.LeakyReLU())
|
|
<span style="color: #228B22"># adding a dropout layer as you do in conv-nets</span>
|
|
model.add(layers.Dropout(<span style="color: #B452CD">0.3</span>))
|
|
|
|
model.add(layers.Flatten())
|
|
model.add(layers.Dense(<span style="color: #B452CD">1</span>))
|
|
|
|
<span style="color: #8B008B; font-weight: bold">return</span> model
|
|
</pre></div>
|
|
<p>
|
|
Let us take a look at our models. <b>Note</b>: double click images for bigger view.
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span>generator = generator_model()
|
|
plot_model(generator, show_shapes=<span style="color: #8B008B; font-weight: bold">True</span>, rankdir=<span style="color: #CD5555">'LR'</span>)
|
|
</pre></div>
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span>discriminator = discriminator_model()
|
|
plot_model(discriminator, show_shapes=<span style="color: #8B008B; font-weight: bold">True</span>, rankdir=<span style="color: #CD5555">'LR'</span>)
|
|
</pre></div>
|
|
<p>
|
|
Next we need a few helper objects we will use in training
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span>cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=<span style="color: #8B008B; font-weight: bold">True</span>)
|
|
generator_optimizer = tf.keras.optimizers.Adam(<span style="color: #B452CD">1e-4</span>)
|
|
discriminator_optimizer = tf.keras.optimizers.Adam(<span style="color: #B452CD">1e-4</span>)
|
|
</pre></div>
|
|
<p>
|
|
The first object, <em>cross_entropy</em> is our loss function and the two others are
|
|
our optimizers. Notice we use the same learning rate for both \( g \) and \( d \). This
|
|
is because they need to improve their accuracy at approximately equal speeds to
|
|
get convergence (not necessarily exactly equal). Now we define our loss
|
|
functions
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span><span style="color: #8B008B; font-weight: bold">def</span> <span style="color: #008b45">generator_loss</span>(fake_output):
|
|
loss = cross_entropy(tf.ones_like(fake_output), fake_output)
|
|
|
|
<span style="color: #8B008B; font-weight: bold">return</span> loss
|
|
</pre></div>
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span><span style="color: #8B008B; font-weight: bold">def</span> <span style="color: #008b45">discriminator_loss</span>(real_output, fake_output):
|
|
real_loss = cross_entropy(tf.ones_like(real_output), real_output)
|
|
fake_loss = cross_entropy(tf.zeros_liks(fake_output), fake_output)
|
|
total_loss = real_loss + fake_loss
|
|
|
|
<span style="color: #8B008B; font-weight: bold">return</span> total_loss
|
|
</pre></div>
|
|
<p>
|
|
Next we define a kind of seed to help us compare the learning process over
|
|
multiple training epochs.
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span>noise_dimension = <span style="color: #B452CD">100</span>
|
|
n_examples_to_generate = <span style="color: #B452CD">16</span>
|
|
seed_images = tf.random.normal([n_examples_to_generate, noise_dimension])
|
|
</pre></div>
|
|
<p>
|
|
Now we have everything we need to define our training step, which we will apply
|
|
for every step in our training loop. Notice the @tf.function flag signifying
|
|
that the function is tensorflow 'compiled'. Removing this flag doubles the
|
|
computation time.
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span><span style="color: #707a7c">@tf</span>.function
|
|
<span style="color: #8B008B; font-weight: bold">def</span> <span style="color: #008b45">train_step</span>(images):
|
|
noise = tf.random.normal([BATCH_SIZE, noise_dimension])
|
|
|
|
<span style="color: #8B008B; font-weight: bold">with</span> tf.GradientTape() <span style="color: #8B008B; font-weight: bold">as</span> gen_tape, tf.GradientTape() <span style="color: #8B008B; font-weight: bold">as</span> disc_tape:
|
|
generated_images = generator(noise, training=<span style="color: #8B008B; font-weight: bold">True</span>)
|
|
|
|
real_output = discriminator(images, training=<span style="color: #8B008B; font-weight: bold">True</span>)
|
|
fake_output = discriminator(generated_images, training=<span style="color: #8B008B; font-weight: bold">True</span>)
|
|
|
|
gen_loss = generator_loss(fake_output)
|
|
disc_loss = discriminator_loss(real_output, fake_output)
|
|
|
|
gradients_of_generator = gen_tape.gradient(gen_loss,
|
|
generator.trainable_variables)
|
|
gradients_of_discriminator = disc_tape.gradient(disc_loss,
|
|
discriminator.trainable_variables)
|
|
generator_optimizer.apply_gradients(<span style="color: #658b00">zip</span>(gradients_of_generator,
|
|
generator.trainable_variables))
|
|
discriminator_optimizer.apply_gradients(<span style="color: #658b00">zip</span>(gradients_of_discriminator,
|
|
discriminator.trainable_variables))
|
|
|
|
<span style="color: #8B008B; font-weight: bold">return</span> gen_loss, disc_loss
|
|
</pre></div>
|
|
<p>
|
|
Next we define a helper function to produce an output over our training epochs
|
|
to see the predictive progression of our generator model. <b>Note</b>: I am including
|
|
this code here, but comment it out in the training loop.
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span><span style="color: #8B008B; font-weight: bold">def</span> <span style="color: #008b45">generate_and_save_images</span>(model, epoch, test_input):
|
|
<span style="color: #228B22"># we're making inferences here</span>
|
|
predictions = model(test_input, training=<span style="color: #8B008B; font-weight: bold">False</span>)
|
|
|
|
fig = plt.figure(figsize=(<span style="color: #B452CD">4</span>, <span style="color: #B452CD">4</span>))
|
|
|
|
<span style="color: #8B008B; font-weight: bold">for</span> i <span style="color: #8B008B">in</span> <span style="color: #658b00">range</span>(predictions.shape[<span style="color: #B452CD">0</span>]):
|
|
plt.subplot(<span style="color: #B452CD">4</span>, <span style="color: #B452CD">4</span>, i+<span style="color: #B452CD">1</span>)
|
|
plt.imshow(predictions[i, :, :, <span style="color: #B452CD">0</span>] * <span style="color: #B452CD">127.5</span> + <span style="color: #B452CD">127.5</span>, cmap=<span style="color: #CD5555">'gray'</span>)
|
|
plt.axis(<span style="color: #CD5555">'off'</span>)
|
|
|
|
plt.savefig(<span style="color: #CD5555">f'./images_from_seed_images/image_at_epoch_{</span><span style="color: #658b00">str</span>(epoch).zfill(<span style="color: #B452CD">3</span>)<span style="color: #CD5555">}.png'</span>)
|
|
plt.close()
|
|
<span style="color: #228B22">#plt.show()</span>
|
|
</pre></div>
|
|
<p>
|
|
Setting up checkpoints to periodically save our model during training so that
|
|
everything is not lost even if the program were to somehow terminate while
|
|
training.
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span><span style="color: #228B22"># Setting up checkpoints to save model during training</span>
|
|
checkpoint_dir = <span style="color: #CD5555">'./training_checkpoints'</span>
|
|
checkpoint_prefix = os.path.join(checkpoint_dir, <span style="color: #CD5555">'ckpt'</span>)
|
|
checkpoint = tf.train.Checkpoint(generator_optimizer=generator_optimizer,
|
|
discriminator_optimizer=discriminator_optimizer,
|
|
generator=generator,
|
|
discriminator=discriminator)
|
|
</pre></div>
|
|
<p>
|
|
Now we define our training loop
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span><span style="color: #8B008B; font-weight: bold">def</span> <span style="color: #008b45">train</span>(dataset, epochs):
|
|
generator_loss_list = []
|
|
discriminator_loss_list = []
|
|
|
|
<span style="color: #8B008B; font-weight: bold">for</span> epoch <span style="color: #8B008B">in</span> <span style="color: #658b00">range</span>(epochs):
|
|
start = time.time()
|
|
|
|
<span style="color: #8B008B; font-weight: bold">for</span> image_batch <span style="color: #8B008B">in</span> dataset:
|
|
gen_loss, disc_loss = train_step(image_batch)
|
|
generator_loss_list.append(gen_loss.numpy())
|
|
discriminator_loss_list.append(disc_loss.numpy())
|
|
|
|
<span style="color: #228B22">#generate_and_save_images(generator, epoch + 1, seed_images)</span>
|
|
|
|
<span style="color: #8B008B; font-weight: bold">if</span> (epoch + <span style="color: #B452CD">1</span>) % <span style="color: #B452CD">15</span> == <span style="color: #B452CD">0</span>:
|
|
checkpoint.save(file_prefix=checkpoint_prefix)
|
|
|
|
<span style="color: #658b00">print</span>(<span style="color: #CD5555">f'Time for epoch {</span>epoch<span style="color: #CD5555">} is {</span>time.time() - start<span style="color: #CD5555">}'</span>)
|
|
|
|
<span style="color: #228B22">#generate_and_save_images(generator, epochs, seed_images)</span>
|
|
|
|
loss_file = <span style="color: #CD5555">'./data/lossfile.txt'</span>
|
|
<span style="color: #8B008B; font-weight: bold">with</span> <span style="color: #658b00">open</span>(loss_file, <span style="color: #CD5555">'w'</span>) <span style="color: #8B008B; font-weight: bold">as</span> outfile:
|
|
outfile.write(<span style="color: #658b00">str</span>(generator_loss_list))
|
|
outfile.write(<span style="color: #CD5555">'\n'</span>)
|
|
outfile.write(<span style="color: #CD5555">'\n'</span>)
|
|
outfile.write(<span style="color: #658b00">str</span>(discriminator_loss_list))
|
|
outfile.write(<span style="color: #CD5555">'\n'</span>)
|
|
outfile.write(<span style="color: #CD5555">'\n'</span>)
|
|
</pre></div>
|
|
<p>
|
|
To train simply call this function. <b>Warning</b>: this might take a long time so
|
|
there is a folder of a pretrained network already included in the repository.
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span>train(train_dataset, EPOCHS)
|
|
</pre></div>
|
|
<p>
|
|
And here is the result of training our model for 100 epochs
|
|
|
|
<p>
|
|
|
|
<embed src="images_from_seed_images/generation.gif" autoplay="false" loop="true"></embed>
|
|
<p><em></em></p>
|
|
|
|
<p>
|
|
Now to avoid having to train and everything, which will take a while depending
|
|
on your computer setup we now load in the model which produced the above gif.
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span>checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))
|
|
restored_generator = checkpoint.generator
|
|
restored_discriminator = checkpoint.discriminator
|
|
|
|
<span style="color: #658b00">print</span>(restored_generator)
|
|
<span style="color: #658b00">print</span>(restored_discriminator)
|
|
</pre></div>
|
|
|
|
<h2 id="exploring-the-latent-space">Exploring the Latent Space </h2>
|
|
|
|
<p>
|
|
So we have successfully loaded in our latest model. Let us now play around a bit
|
|
and see what kind of things we can learn about this model. Our generator takes
|
|
an array of 100 numbers. One idea can be to try to systematically change our
|
|
input. Let us try and see what we get
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span><span style="color: #8B008B; font-weight: bold">def</span> <span style="color: #008b45">generate_latent_points</span>(number=<span style="color: #B452CD">100</span>, scale_means=<span style="color: #B452CD">1</span>, scale_stds=<span style="color: #B452CD">1</span>):
|
|
latent_dim = <span style="color: #B452CD">100</span>
|
|
means = scale_means * tf.linspace(-<span style="color: #B452CD">1</span>, <span style="color: #B452CD">1</span>, num=latent_dim)
|
|
stds = scale_stds * tf.linspace(-<span style="color: #B452CD">1</span>, <span style="color: #B452CD">1</span>, num=latent_dim)
|
|
latent_space_value_range = tf.random.normal([number, number],
|
|
means,
|
|
stds,
|
|
dtype=tf.float64)
|
|
|
|
<span style="color: #8B008B; font-weight: bold">return</span> latent_space_value_range
|
|
|
|
<span style="color: #8B008B; font-weight: bold">def</span> <span style="color: #008b45">generate_images</span>(latent_points):
|
|
<span style="color: #228B22"># notice we set training to false because we are making inferences</span>
|
|
generated_images = restored_generator(latent_space_value_range,
|
|
training=<span style="color: #8B008B; font-weight: bold">False</span>)
|
|
|
|
<span style="color: #8B008B; font-weight: bold">return</span> generated_images
|
|
</pre></div>
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span><span style="color: #8B008B; font-weight: bold">def</span> <span style="color: #008b45">plot_result</span>(generated_images, number):
|
|
<span style="color: #228B22"># obviously this assumes sqrt number is an int</span>
|
|
fig, axs = plt.subplots(<span style="color: #658b00">int</span>(np.sqrt(number)), <span style="color: #658b00">int</span>(np.sqrt(number)),
|
|
figsize=(<span style="color: #B452CD">10</span>, <span style="color: #B452CD">10</span>))
|
|
|
|
<span style="color: #8B008B; font-weight: bold">for</span> i <span style="color: #8B008B">in</span> <span style="color: #658b00">range</span>(<span style="color: #658b00">int</span>(np.sqrt(number))):
|
|
<span style="color: #8B008B; font-weight: bold">for</span> j <span style="color: #8B008B">in</span> <span style="color: #658b00">range</span>(<span style="color: #658b00">int</span>(np.sqrt(number))):
|
|
axs[i, j].imshow(generated_images[i*j], cmap=<span style="color: #CD5555">'Greys'</span>)
|
|
axs[i, j].axis(<span style="color: #CD5555">'off'</span>)
|
|
|
|
plt.show()
|
|
</pre></div>
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span>generated_images = generate_images(generate_latent_points())
|
|
plot_result(generated_images, number)
|
|
</pre></div>
|
|
<p>
|
|
Interesting! We see that the generator generates images that look like MNIST
|
|
numbers: \( 1, 4, 7, 9 \). Let's try to tweak it a bit more to see if we are able
|
|
to generate a similar plot where we generate every MNIST number. Let us now try
|
|
to 'move' a bit around in the latent space. <b>Note</b>: decrease the plot number if
|
|
these following cells take too long to run on your computer.
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span>plot_number = <span style="color: #B452CD">225</span>
|
|
|
|
generated_images = generate_images(generate_latent_points(number=plot_number,
|
|
scale_means=<span style="color: #B452CD">5</span>,
|
|
scale_stds=<span style="color: #B452CD">1</span>))
|
|
plot_result(generated_images, plot_number)
|
|
|
|
generated_images = generate_images(generate_latent_points(number=plot_number,
|
|
scale_means=-<span style="color: #B452CD">5</span>,
|
|
scale_stds=<span style="color: #B452CD">1</span>))
|
|
plot_result(generated_images, plot_number)
|
|
|
|
generated_images = generate_images(generate_latent_points(number=plot_number,
|
|
scale_means=<span style="color: #B452CD">1</span>,
|
|
scale_stds=<span style="color: #B452CD">5</span>))
|
|
plot_result(generated_images, plot_number)
|
|
</pre></div>
|
|
<p>
|
|
Again, we have found something interesting. <em>Moving</em> around using our means
|
|
takes us from digit to digit, while <em>moving</em> around using our standard
|
|
deviations seem to increase the number of different digits! In the last image
|
|
above, we can barely make out every MNIST digit. Let us make on last plot using
|
|
this information by upping the standard deviation of our Gaussian noises.
|
|
|
|
<p>
|
|
|
|
<!-- code=python (!bc pycod) typeset with pygments style "perldoc" -->
|
|
<div class="highlight" style="background: #eeeedd"><pre style="font-size: 80%; line-height: 125%;"><span></span>plot_number = <span style="color: #B452CD">400</span>
|
|
generated_images = generate_images(generate_latent_points(number=plot_number,
|
|
scale_means=<span style="color: #B452CD">1</span>,
|
|
scale_stds=<span style="color: #B452CD">10</span>))
|
|
</pre></div>
|
|
<p>
|
|
A pretty cool result! We see that our generator indeed has learned a
|
|
distribution which qualitatively looks a whole lot like the MNIST dataset.
|
|
|
|
<h2 id="interpolating-between-mnist-digits">Interpolating Between MNIST Digits </h2>
|
|
Another interesting way to explore the latent space of our generator model is by
|
|
interpolating between the MNIST digits. This section is largely based on
|
|
"this excellent blogpost": <a href="https://machinelearningmastery.com/how-to-interpolate-and-perform-vector-arithmetic-with-faces-using-a-generative-adversarial-network/" target="_blank"><tt>https://machinelearningmastery.com/how-to-interpolate-and-perform-vector-arithmetic-with-faces-using-a-generative-adversarial-network/</tt></a>
|
|
by Jason Brownlee.
|
|
|
|
<p>
|
|
So let us start
|
|
</section>
|
|
|
|
|
|
|
|
</div> <!-- class="slides" -->
|
|
</div> <!-- class="reveal" -->
|
|
|
|
<script src="reveal.js/lib/js/head.min.js"></script>
|
|
<script src="reveal.js/js/reveal.js"></script>
|
|
|
|
<script>
|
|
// Full list of configuration options available here:
|
|
// https://github.com/hakimel/reveal.js#configuration
|
|
Reveal.initialize({
|
|
|
|
// Display navigation controls in the bottom right corner
|
|
controls: true,
|
|
|
|
// Display progress bar (below the horiz. slider)
|
|
progress: true,
|
|
|
|
// Display the page number of the current slide
|
|
slideNumber: true,
|
|
|
|
// Push each slide change to the browser history
|
|
history: false,
|
|
|
|
// Enable keyboard shortcuts for navigation
|
|
keyboard: true,
|
|
|
|
// Enable the slide overview mode
|
|
overview: true,
|
|
|
|
// Vertical centering of slides
|
|
//center: true,
|
|
center: false,
|
|
|
|
// Enables touch navigation on devices with touch input
|
|
touch: true,
|
|
|
|
// Loop the presentation
|
|
loop: false,
|
|
|
|
// Change the presentation direction to be RTL
|
|
rtl: false,
|
|
|
|
// Turns fragments on and off globally
|
|
fragments: true,
|
|
|
|
// Flags if the presentation is running in an embedded mode,
|
|
// i.e. contained within a limited portion of the screen
|
|
embedded: false,
|
|
|
|
// Number of milliseconds between automatically proceeding to the
|
|
// next slide, disabled when set to 0, this value can be overwritten
|
|
// by using a data-autoslide attribute on your slides
|
|
autoSlide: 0,
|
|
|
|
// Stop auto-sliding after user input
|
|
autoSlideStoppable: true,
|
|
|
|
// Enable slide navigation via mouse wheel
|
|
mouseWheel: false,
|
|
|
|
// Hides the address bar on mobile devices
|
|
hideAddressBar: true,
|
|
|
|
// Opens links in an iframe preview overlay
|
|
previewLinks: false,
|
|
|
|
// Transition style
|
|
transition: 'default', // default/cube/page/concave/zoom/linear/fade/none
|
|
|
|
// Transition speed
|
|
transitionSpeed: 'default', // default/fast/slow
|
|
|
|
// Transition style for full page slide backgrounds
|
|
backgroundTransition: 'default', // default/none/slide/concave/convex/zoom
|
|
|
|
// Number of slides away from the current that are visible
|
|
viewDistance: 3,
|
|
|
|
// Parallax background image
|
|
//parallaxBackgroundImage: '', // e.g. "'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg'"
|
|
|
|
// Parallax background size
|
|
//parallaxBackgroundSize: '' // CSS syntax, e.g. "2100px 900px"
|
|
|
|
theme: Reveal.getQueryHash().theme, // available themes are in reveal.js/css/theme
|
|
transition: Reveal.getQueryHash().transition || 'default', // default/cube/page/concave/zoom/linear/none
|
|
|
|
});
|
|
|
|
Reveal.initialize({
|
|
dependencies: [
|
|
// Cross-browser shim that fully implements classList - https://github.com/eligrey/classList.js/
|
|
{ src: 'reveal.js/lib/js/classList.js', condition: function() { return !document.body.classList; } },
|
|
|
|
// Interpret Markdown in <section> elements
|
|
{ src: 'reveal.js/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
|
|
{ src: 'reveal.js/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
|
|
|
|
// Syntax highlight for <code> elements
|
|
{ src: 'reveal.js/plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
|
|
|
|
// Zoom in and out with Alt+click
|
|
{ src: 'reveal.js/plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
|
|
|
|
// Speaker notes
|
|
{ src: 'reveal.js/plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } },
|
|
|
|
// Remote control your reveal.js presentation using a touch device
|
|
//{ src: 'reveal.js/plugin/remotes/remotes.js', async: true, condition: function() { return !!document.body.classList; } },
|
|
|
|
// MathJax
|
|
//{ src: 'reveal.js/plugin/math/math.js', async: true }
|
|
]
|
|
});
|
|
|
|
Reveal.initialize({
|
|
|
|
// The "normal" size of the presentation, aspect ratio will be preserved
|
|
// when the presentation is scaled to fit different resolutions. Can be
|
|
// specified using percentage units.
|
|
width: 1170, // original: 960,
|
|
height: 700,
|
|
|
|
// Factor of the display size that should remain empty around the content
|
|
margin: 0.1,
|
|
|
|
// Bounds for smallest/largest possible scale to apply to content
|
|
minScale: 0.2,
|
|
maxScale: 1.0
|
|
|
|
});
|
|
</script>
|
|
|
|
<!-- begin footer logo
|
|
<div style="position: absolute; bottom: 0px; left: 0; margin-left: 0px">
|
|
<img src="somelogo.png">
|
|
</div>
|
|
end footer logo -->
|
|
|
|
|
|
|
|
</body>
|
|
</html>
|