From 4eb1a14de44dae36b358c749b9a6fe05911db0e3 Mon Sep 17 00:00:00 2001 From: Morten Hjorth-Jensen Date: Sun, 27 Oct 2024 22:07:05 +0100 Subject: [PATCH] update week 44 --- doc/pub/week44/html/._week44-bs000.html | 228 +- doc/pub/week44/html/._week44-bs001.html | 228 +- doc/pub/week44/html/._week44-bs002.html | 228 +- doc/pub/week44/html/._week44-bs003.html | 228 +- doc/pub/week44/html/._week44-bs004.html | 228 +- doc/pub/week44/html/._week44-bs005.html | 228 +- doc/pub/week44/html/._week44-bs006.html | 228 +- doc/pub/week44/html/._week44-bs007.html | 228 +- doc/pub/week44/html/._week44-bs008.html | 228 +- doc/pub/week44/html/._week44-bs009.html | 228 +- doc/pub/week44/html/._week44-bs010.html | 228 +- doc/pub/week44/html/._week44-bs011.html | 228 +- doc/pub/week44/html/._week44-bs012.html | 228 +- doc/pub/week44/html/._week44-bs013.html | 228 +- doc/pub/week44/html/._week44-bs014.html | 228 +- doc/pub/week44/html/._week44-bs015.html | 228 +- doc/pub/week44/html/._week44-bs016.html | 228 +- doc/pub/week44/html/._week44-bs017.html | 271 +- doc/pub/week44/html/._week44-bs018.html | 325 +- doc/pub/week44/html/._week44-bs019.html | 266 +- doc/pub/week44/html/._week44-bs020.html | 263 +- doc/pub/week44/html/._week44-bs021.html | 290 +- doc/pub/week44/html/._week44-bs022.html | 263 +- doc/pub/week44/html/._week44-bs023.html | 271 +- doc/pub/week44/html/._week44-bs024.html | 291 +- doc/pub/week44/html/._week44-bs025.html | 256 +- doc/pub/week44/html/._week44-bs026.html | 261 +- doc/pub/week44/html/._week44-bs027.html | 266 +- doc/pub/week44/html/._week44-bs028.html | 278 +- doc/pub/week44/html/._week44-bs029.html | 248 +- doc/pub/week44/html/._week44-bs030.html | 247 +- doc/pub/week44/html/._week44-bs031.html | 265 +- doc/pub/week44/html/._week44-bs032.html | 257 +- doc/pub/week44/html/._week44-bs033.html | 290 +- doc/pub/week44/html/._week44-bs034.html | 293 +- doc/pub/week44/html/._week44-bs035.html | 3486 +---------------- doc/pub/week44/html/._week44-bs036.html | 252 +- doc/pub/week44/html/._week44-bs037.html | 246 +- doc/pub/week44/html/._week44-bs038.html | 263 +- doc/pub/week44/html/._week44-bs039.html | 263 +- doc/pub/week44/html/._week44-bs040.html | 260 +- doc/pub/week44/html/._week44-bs041.html | 251 +- doc/pub/week44/html/._week44-bs042.html | 303 +- doc/pub/week44/html/._week44-bs043.html | 286 +- doc/pub/week44/html/._week44-bs044.html | 295 +- doc/pub/week44/html/._week44-bs045.html | 281 +- doc/pub/week44/html/._week44-bs046.html | 301 +- doc/pub/week44/html/._week44-bs047.html | 299 +- doc/pub/week44/html/._week44-bs048.html | 3522 +---------------- doc/pub/week44/html/._week44-bs049.html | 434 ++- doc/pub/week44/html/._week44-bs050.html | 497 +-- doc/pub/week44/html/._week44-bs051.html | 492 ++- doc/pub/week44/html/._week44-bs052.html | 495 +-- doc/pub/week44/html/._week44-bs053.html | 470 ++- doc/pub/week44/html/._week44-bs054.html | 504 ++- doc/pub/week44/html/._week44-bs055.html | 464 +-- doc/pub/week44/html/._week44-bs056.html | 463 ++- doc/pub/week44/html/._week44-bs057.html | 486 +-- doc/pub/week44/html/._week44-bs058.html | 469 ++- doc/pub/week44/html/._week44-bs059.html | 478 +-- doc/pub/week44/html/._week44-bs060.html | 469 +-- doc/pub/week44/html/._week44-bs061.html | 3687 +++++++++++++++++- doc/pub/week44/html/week44-bs.html | 228 +- doc/pub/week44/html/week44-reveal.html | 1627 +++++--- doc/pub/week44/html/week44-solarized.html | 1675 +++++--- doc/pub/week44/html/week44.html | 1675 +++++--- doc/pub/week44/ipynb/ipynb-week44-src.tar.gz | Bin 498917 -> 473525 bytes doc/pub/week44/ipynb/week44.ipynb | 2248 +++++++---- doc/src/week44/figslides/photo.jpg | Bin 0 -> 197326 bytes doc/src/week44/week44.do.txt | 1189 ++++-- 70 files changed, 19262 insertions(+), 17873 deletions(-) create mode 100755 doc/src/week44/figslides/photo.jpg diff --git a/doc/pub/week44/html/._week44-bs000.html b/doc/pub/week44/html/._week44-bs000.html index c696978c2..e73a75127 100644 --- a/doc/pub/week44/html/._week44-bs000.html +++ b/doc/pub/week44/html/._week44-bs000.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -351,7 +393,7 @@ MathJax.Hub.Config({
  • 9
  • 10
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs001.html b/doc/pub/week44/html/._week44-bs001.html index c47f506e6..5555f64c1 100644 --- a/doc/pub/week44/html/._week44-bs001.html +++ b/doc/pub/week44/html/._week44-bs001.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -345,7 +387,7 @@ MathJax.Hub.Config({
  • 10
  • 11
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs002.html b/doc/pub/week44/html/._week44-bs002.html index 8fed3c827..6c07203a5 100644 --- a/doc/pub/week44/html/._week44-bs002.html +++ b/doc/pub/week44/html/._week44-bs002.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -340,7 +382,7 @@ MathJax.Hub.Config({
  • 11
  • 12
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs003.html b/doc/pub/week44/html/._week44-bs003.html index fbe640a9f..c283b9feb 100644 --- a/doc/pub/week44/html/._week44-bs003.html +++ b/doc/pub/week44/html/._week44-bs003.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -328,7 +370,7 @@ MathJax.Hub.Config({
  • 12
  • 13
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs004.html b/doc/pub/week44/html/._week44-bs004.html index f67228f65..e726d59b6 100644 --- a/doc/pub/week44/html/._week44-bs004.html +++ b/doc/pub/week44/html/._week44-bs004.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -348,7 +390,7 @@ Networks still apply (back propagation, gradient descent etc etc).
  • 13
  • 14
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs005.html b/doc/pub/week44/html/._week44-bs005.html index ea2689f94..8d49d3e88 100644 --- a/doc/pub/week44/html/._week44-bs005.html +++ b/doc/pub/week44/html/._week44-bs005.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -337,7 +379,7 @@ the network.
  • 14
  • 15
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs006.html b/doc/pub/week44/html/._week44-bs006.html index 21a6e67c9..edf65f635 100644 --- a/doc/pub/week44/html/._week44-bs006.html +++ b/doc/pub/week44/html/._week44-bs006.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -340,7 +382,7 @@ before the transformation.
  • 15
  • 16
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs007.html b/doc/pub/week44/html/._week44-bs007.html index 4c8ec3794..c3be604da 100644 --- a/doc/pub/week44/html/._week44-bs007.html +++ b/doc/pub/week44/html/._week44-bs007.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -354,7 +396,7 @@ in the input).
  • 16
  • 17
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs008.html b/doc/pub/week44/html/._week44-bs008.html index f94bd9e9c..68b5b92e8 100644 --- a/doc/pub/week44/html/._week44-bs008.html +++ b/doc/pub/week44/html/._week44-bs008.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -357,7 +399,7 @@ would quickly lead to possible overfitting.
  • 17
  • 18
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs009.html b/doc/pub/week44/html/._week44-bs009.html index 694d67abc..4800dc14e 100644 --- a/doc/pub/week44/html/._week44-bs009.html +++ b/doc/pub/week44/html/._week44-bs009.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -370,7 +412,7 @@ dimension.
  • 18
  • 19
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs010.html b/doc/pub/week44/html/._week44-bs010.html index 5e741911d..b36dfe10a 100644 --- a/doc/pub/week44/html/._week44-bs010.html +++ b/doc/pub/week44/html/._week44-bs010.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -361,7 +403,7 @@ $$
  • 19
  • 20
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs011.html b/doc/pub/week44/html/._week44-bs011.html index ea1ee1fd0..f5d4ca77f 100644 --- a/doc/pub/week44/html/._week44-bs011.html +++ b/doc/pub/week44/html/._week44-bs011.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -344,7 +386,7 @@ principles then apply to images and other similar data.
  • 20
  • 21
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs012.html b/doc/pub/week44/html/._week44-bs012.html index 4ec9c0617..470056884 100644 --- a/doc/pub/week44/html/._week44-bs012.html +++ b/doc/pub/week44/html/._week44-bs012.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -352,7 +394,7 @@ will stack these layers to form a full CNN architecture.
  • 21
  • 22
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs013.html b/doc/pub/week44/html/._week44-bs013.html index a5f7b2701..e1101b015 100644 --- a/doc/pub/week44/html/._week44-bs013.html +++ b/doc/pub/week44/html/._week44-bs013.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -349,7 +391,7 @@ are consistent with the labels in the training set for each image.
  • 22
  • 23
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs014.html b/doc/pub/week44/html/._week44-bs014.html index 8b3484795..a1eaa151e 100644 --- a/doc/pub/week44/html/._week44-bs014.html +++ b/doc/pub/week44/html/._week44-bs014.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -344,7 +386,7 @@ MathJax.Hub.Config({
  • 23
  • 24
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs015.html b/doc/pub/week44/html/._week44-bs015.html index ac6d05f64..9bee34d42 100644 --- a/doc/pub/week44/html/._week44-bs015.html +++ b/doc/pub/week44/html/._week44-bs015.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -343,7 +385,7 @@ MathJax.Hub.Config({
  • 24
  • 25
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs016.html b/doc/pub/week44/html/._week44-bs016.html index 98df55d62..658ac8bb3 100644 --- a/doc/pub/week44/html/._week44-bs016.html +++ b/doc/pub/week44/html/._week44-bs016.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -343,7 +385,7 @@ only neighboring neurons in the input instead of connecting all with the first h
  • 25
  • 26
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs017.html b/doc/pub/week44/html/._week44-bs017.html index a24fbd0c1..bf405105c 100644 --- a/doc/pub/week44/html/._week44-bs017.html +++ b/doc/pub/week44/html/._week44-bs017.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,41 +350,24 @@ MathJax.Hub.Config({

     

     

     

    -

    Mathematics of CNNs

    +

    How to do image compression before the era of deep learning

    -

    The mathematics of CNNs is based on the mathematical operation of -convolution. In mathematics (in particular in functional analysis), -convolution is represented by mathematical operations (integration, -summation etc) on two functions in order to produce a third function -that expresses how the shape of one gets modified by the other. -Convolution has a plethora of applications in a variety of -disciplines, spanning from statistics to signal processing, computer -vision, solutions of differential equations,linear algebra, -engineering, and yes, machine learning. +

    The singular-value decomposition (SVD) algorithm has been for decades one of the standard ways of compressing images. +The lectures on the SVD give many of the essential details concerning the SVD.

    -

    Mathematically, convolution is defined as follows (one-dimensional example): -Let us define a continuous function \( y(t) \) given by +

    The orthogonal vectors which are obtained from the SVD, can be used to +project down the dimensionality of a given image. In the example here +we gray-scale an image and downsize it.

    -$$ -y(t) = \int x(a) w(t-a) da, -$$ -

    where \( x(a) \) represents a so-called input and \( w(t-a) \) is normally called the weight function or kernel.

    - -

    The above integral is written in a more compact form as

    -$$ -y(t) = \left(x * w\right)(t). -$$ - -

    The discretized version reads

    -$$ -y(t) = \sum_{a=-\infty}^{a=\infty}x(a)w(t-a). -$$ - -

    Computing the inverse of the above convolution operations is known as deconvolution and the process is commutative.

    - -

    How can we use this? And what does it mean? Let us study some familiar examples first.

    +

    This recipe relies on us being able to actually perform the SVD. For +large images, and in particular with many images to reconstruct, using the SVD +may quickly become an overwhelming task. With the advent of efficient deep +learning methods like CNNs and later generative methods, these methods +have become in the last years the premier way of performing image +analysis. In particular for classification problems with labelled images. +

    @@ -369,7 +394,7 @@ $$

  • 26
  • 27
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs018.html b/doc/pub/week44/html/._week44-bs018.html index a65c89667..755dc9894 100644 --- a/doc/pub/week44/html/._week44-bs018.html +++ b/doc/pub/week44/html/._week44-bs018.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,30 +350,87 @@ MathJax.Hub.Config({

     

     

     

    -

    Convolution Examples: Polynomial multiplication

    +

    The SVD example

    -

    Our first example is that of a multiplication between two polynomials, -which we will rewrite in terms of the mathematics of convolution. In -the final stage, since the problem here is a discrete one, we will -recast the final expression in terms of a matrix-vector -multiplication, where the matrix is a so-called Toeplitz matrix -. -

    -

    Let us look a the following polynomials to second and third order, respectively:

    -$$ -p(t) = \alpha_0+\alpha_1 t+\alpha_2 t^2, -$$ + +
    +
    +
    +
    +
    +
    from matplotlib.image import imread
    +import matplotlib.pyplot as plt
    +import scipy.linalg as ln
    +import numpy as np
    +import os
    +from PIL import Image
    +from math import log10, sqrt 
    +plt.rcParams['figure.figsize'] = [16, 8]
    +# Import image
    +A = imread(os.path.join("figslides/photo1.jpg"))
    +X = A.dot([0.299, 0.5870, 0.114]) # Convert RGB to grayscale
    +img = plt.imshow(X)
    +# convert to gray
    +img.set_cmap('gray')
    +plt.axis('off')
    +plt.show()
    +# Call image size
    +print(': %s'%str(X.shape))
     
    -

    and

    -$$ -s(t) = \beta_0+\beta_1 t+\beta_2 t^2+\beta_3 t^3. -$$ -

    The polynomial multiplication gives us a new polynomial of degree \( 5 \)

    -$$ -z(t) = \delta_0+\delta_1 t+\delta_2 t^2+\delta_3 t^3+\delta_4 t^4+\delta_5 t^5. -$$ +# split the matrix into U, S, VT +U, S, VT = np.linalg.svd(X,full_matrices=False) +S = np.diag(S) +m = 800 # Image's width +n = 1200 # Image's height +j = 0 +# Try compression with different k vectors (these represent projections): +for k in (5,10, 20, 100,200,400,500): + # Original size of the image + originalSize = m * n + # Size after compressed + compressedSize = k * (1 + m + n) + # The projection of the original image + Xapprox = U[:,:k] @ S[0:k,:k] @ VT[:k,:] + plt.figure(j+1) + j += 1 + img = plt.imshow(Xapprox) + img.set_cmap('gray') + + plt.axis('off') + plt.title('k = ' + str(k)) + plt.show() + print('Original size of image:') + print(originalSize) + print('Compression rate as Compressed image / Original size:') + ratio = compressedSize * 1.0 / originalSize + print(ratio) + print('Compression rate is ' + str( round(ratio * 100 ,2)) + '%' ) + # Estimate MQA + x= X.astype("float") + y=Xapprox.astype("float") + err = np.sum((x - y) ** 2) + err /= float(X.shape[0] * Xapprox.shape[1]) + print('The mean-square deviation '+ str(round( err))) + max_pixel = 255.0 + # Estimate Signal Noise Ratio + srv = 20 * (log10(max_pixel / sqrt(err))) + print('Signa to noise ratio '+ str(round(srv)) +'dB') +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    @@ -359,7 +458,7 @@ $$

  • 27
  • 28
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs019.html b/doc/pub/week44/html/._week44-bs019.html index 7868d9229..9f4418b19 100644 --- a/doc/pub/week44/html/._week44-bs019.html +++ b/doc/pub/week44/html/._week44-bs019.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,35 +350,41 @@ MathJax.Hub.Config({

     

     

     

    -

    Efficient Polynomial Multiplication

    +

    Mathematics of CNNs

    -

    Computing polynomial products can be implemented efficiently if we rewrite the more brute force multiplications using convolution. -We note first that the new coefficients are given as +

    The mathematics of CNNs is based on the mathematical operation of +convolution. In mathematics (in particular in functional analysis), +convolution is represented by mathematical operations (integration, +summation etc) on two functions in order to produce a third function +that expresses how the shape of one gets modified by the other. +Convolution has a plethora of applications in a variety of +disciplines, spanning from statistics to signal processing, computer +vision, solutions of differential equations,linear algebra, +engineering, and yes, machine learning.

    +

    Mathematically, convolution is defined as follows (one-dimensional example): +Let us define a continuous function \( y(t) \) given by +

    $$ -\begin{split} -\delta_0=&\alpha_0\beta_0\\ -\delta_1=&\alpha_1\beta_0+\alpha_0\beta_1\\ -\delta_2=&\alpha_0\beta_2+\alpha_1\beta_1+\alpha_2\beta_0\\ -\delta_3=&\alpha_1\beta_2+\alpha_2\beta_1+\alpha_0\beta_3\\ -\delta_4=&\alpha_2\beta_2+\alpha_1\beta_3\\ -\delta_5=&\alpha_2\beta_3.\\ -\end{split} +y(t) = \int x(a) w(t-a) da, $$ -

    We note that \( \alpha_i=0 \) except for \( i\in \left\{0,1,2\right\} \) and \( \beta_i=0 \) except for \( i\in\left\{0,1,2,3\right\} \).

    +

    where \( x(a) \) represents a so-called input and \( w(t-a) \) is normally called the weight function or kernel.

    -

    We can then rewrite the coefficients \( \delta_j \) using a discrete convolution as

    +

    The above integral is written in a more compact form as

    $$ -\delta_j = \sum_{i=-\infty}^{i=\infty}\alpha_i\beta_{j-i}=(\alpha * \beta)_j, +y(t) = \left(x * w\right)(t). $$ -

    or as a double sum with restriction \( l=i+j \)

    +

    The discretized version reads

    $$ -\delta_l = \sum_{ij}\alpha_i\beta_{j}. +y(t) = \sum_{a=-\infty}^{a=\infty}x(a)w(t-a). $$ +

    Computing the inverse of the above convolution operations is known as deconvolution and the process is commutative.

    + +

    How can we use this? And what does it mean? Let us study some familiar examples first.

    @@ -363,7 +411,7 @@ $$

  • 28
  • 29
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs020.html b/doc/pub/week44/html/._week44-bs020.html index a9649fb5d..75e91e754 100644 --- a/doc/pub/week44/html/._week44-bs020.html +++ b/doc/pub/week44/html/._week44-bs020.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,14 +350,41 @@ MathJax.Hub.Config({

     

     

     

    -

    Further simplification

    +

    Mathematics of CNNs

    -

    Although we may have redundant operations with some few zeros for \( \beta_i \), we can rewrite the above sum in a more compact way as

    +

    The mathematics of CNNs is based on the mathematical operation of +convolution. In mathematics (in particular in functional analysis), +convolution is represented by mathematical operations (integration, +summation etc) on two functions in order to produce a third function +that expresses how the shape of one gets modified by the other. +Convolution has a plethora of applications in a variety of +disciplines, spanning from statistics to signal processing, computer +vision, solutions of differential equations,linear algebra, +engineering, and yes, machine learning. +

    + +

    Mathematically, convolution is defined as follows (one-dimensional example): +Let us define a continuous function \( y(t) \) given by +

    $$ -\delta_i = \sum_{k=0}^{k=m-1}\alpha_k\beta_{i-k}, +y(t) = \int x(a) w(t-a) da, $$ -

    where \( m=3 \) in our case, the maximum length of the vector \( \alpha \). Note that the vector \( \boldsymbol{\beta} \) has length \( n=4 \).

    +

    where \( x(a) \) represents a so-called input and \( w(t-a) \) is normally called the weight function or kernel.

    + +

    The above integral is written in a more compact form as

    +$$ +y(t) = \left(x * w\right)(t). +$$ + +

    The discretized version reads

    +$$ +y(t) = \sum_{a=-\infty}^{a=\infty}x(a)w(t-a). +$$ + +

    Computing the inverse of the above convolution operations is known as deconvolution and the process is commutative.

    + +

    How can we use this? And what does it mean? Let us study some familiar examples first.

    @@ -342,7 +411,7 @@ $$

  • 29
  • 30
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs021.html b/doc/pub/week44/html/._week44-bs021.html index 40543bfb3..4dcc8e13f 100644 --- a/doc/pub/week44/html/._week44-bs021.html +++ b/doc/pub/week44/html/._week44-bs021.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,67 +350,31 @@ MathJax.Hub.Config({

     

     

     

    -

    A more efficient way of coding the above Convolution

    +

    Convolution Examples: Polynomial multiplication

    -

    Since we only have a finite number of \( \alpha \) and \( \beta \) values -which are non-zero, we can rewrite the above convolution expressions -as a matrix-vector multiplication +

    Our first example is that of a multiplication between two polynomials, +which we will rewrite in terms of the mathematics of convolution. In +the final stage, since the problem here is a discrete one, we will +recast the final expression in terms of a matrix-vector +multiplication, where the matrix is a so-called Toeplitz matrix +.

    +

    Let us look a the following polynomials to second and third order, respectively:

    $$ -\boldsymbol{\delta}=\begin{bmatrix}\alpha_0 & 0 & 0 & 0 \\ - \alpha_1 & \alpha_0 & 0 & 0 \\ - \alpha_2 & \alpha_1 & \alpha_0 & 0 \\ - 0 & \alpha_2 & \alpha_1 & \alpha_0 \\ - 0 & 0 & \alpha_2 & \alpha_1 \\ - 0 & 0 & 0 & \alpha_2 - \end{bmatrix}\begin{bmatrix} \beta_0 \\ \beta_1 \\ \beta_2 \\ \beta_3\end{bmatrix}. +p(t) = \alpha_0+\alpha_1 t+\alpha_2 t^2, $$ -

    The process is commutative and we can easily see that we can rewrite the multiplication in terms of a matrix holding \( \beta \) and a vector holding \( \alpha \). -In this case we have -

    +

    and

    $$ -\boldsymbol{\delta}=\begin{bmatrix}\beta_0 & 0 & 0 \\ - \beta_1 & \beta_0 & 0 \\ - \beta_2 & \beta_1 & \beta_0 \\ - \beta_3 & \beta_2 & \beta_1 \\ - 0 & \beta_3 & \beta_2 \\ - 0 & 0 & \beta_3 - \end{bmatrix}\begin{bmatrix} \alpha_0 \\ \alpha_1 \\ \alpha_2\end{bmatrix}. +s(t) = \beta_0+\beta_1 t+\beta_2 t^2+\beta_3 t^3. $$ -

    Note that the use of these matrices is for mathematical purposes only -and not implementation purposes. When implementing the above equation -we do not encode (and allocate memory) the matrices explicitely. We -rather code the convolutions in the minimal memory footprint that they -require. -

    - -

    The above matrices are examples of so-called Toeplitz -matrices. A -Toeplitz matrix is a matrix in which each descending diagonal from -left to right is constant. For instance the last matrix, which we -rewrite as -

    +

    The polynomial multiplication gives us a new polynomial of degree \( 5 \)

    $$ -\boldsymbol{A}=\begin{bmatrix}a_0 & 0 & 0 \\ - a_1 & a_0 & 0 \\ - a_2 & a_1 & a_0 \\ - a_3 & a_2 & a_1 \\ - 0 & a_3 & a_2 \\ - 0 & 0 & a_3 - \end{bmatrix}, +z(t) = \delta_0+\delta_1 t+\delta_2 t^2+\delta_3 t^3+\delta_4 t^4+\delta_5 t^5. $$ -

    with elements \( a_{ii}=a_{i+1,j+1}=a_{i-j} \) is an example of a Toeplitz -matrix. Such a matrix does not need to be a square matrix. Toeplitz -matrices are also closely connected with Fourier series, because the multiplication operator by a trigonometric -polynomial, compressed to a finite-dimensional space, can be -represented by such a matrix. The example above shows that we can -represent linear convolution as multiplication of a Toeplitz matrix by -a vector. -

    @@ -395,7 +401,7 @@ a vector.

  • 30
  • 31
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs022.html b/doc/pub/week44/html/._week44-bs022.html index 2b1c06f38..9e20c0f4c 100644 --- a/doc/pub/week44/html/._week44-bs022.html +++ b/doc/pub/week44/html/._week44-bs022.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,13 +350,36 @@ MathJax.Hub.Config({

     

     

     

    -

    Fourier series and Toeplitz matrices

    +

    Efficient Polynomial Multiplication

    + +

    Computing polynomial products can be implemented efficiently if we rewrite the more brute force multiplications using convolution. +We note first that the new coefficients are given as +

    + +$$ +\begin{split} +\delta_0=&\alpha_0\beta_0\\ +\delta_1=&\alpha_1\beta_0+\alpha_0\beta_1\\ +\delta_2=&\alpha_0\beta_2+\alpha_1\beta_1+\alpha_2\beta_0\\ +\delta_3=&\alpha_1\beta_2+\alpha_2\beta_1+\alpha_0\beta_3\\ +\delta_4=&\alpha_2\beta_2+\alpha_1\beta_3\\ +\delta_5=&\alpha_2\beta_3.\\ +\end{split} +$$ + +

    We note that \( \alpha_i=0 \) except for \( i\in \left\{0,1,2\right\} \) and \( \beta_i=0 \) except for \( i\in\left\{0,1,2,3\right\} \).

    + +

    We can then rewrite the coefficients \( \delta_j \) using a discrete convolution as

    +$$ +\delta_j = \sum_{i=-\infty}^{i=\infty}\alpha_i\beta_{j-i}=(\alpha * \beta)_j, +$$ + +

    or as a double sum with restriction \( l=i+j \)

    +$$ +\delta_l = \sum_{ij}\alpha_i\beta_{j}. +$$ + -

    This is an active and ogoing research area concerning CNNs. The following articles may be of interest

    -
      -
    1. Read more about the convolution theorem and Fouriers series
    2. -
    3. Fourier Transform Layer
    4. -

    diff --git a/doc/pub/week44/html/._week44-bs023.html b/doc/pub/week44/html/._week44-bs023.html index a982663d6..7b54e91ce 100644 --- a/doc/pub/week44/html/._week44-bs023.html +++ b/doc/pub/week44/html/._week44-bs023.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,43 +350,16 @@ MathJax.Hub.Config({

     

     

     

    -

    Generalizing the above one-dimensional case

    +

    Further simplification

    -

    In order to align the above simple case with the more general convolution cases, we rename \( \boldsymbol{\alpha} \), whose length is \( m=3 \), with \( \boldsymbol{w} \). -We will interpret \( \boldsymbol{w} \) as a weight/filter function with which we want to perform the convolution with an input varibale \( \boldsymbol{x} \). -We replace thus \( \boldsymbol{\beta} \) with \( \boldsymbol{x} \) and \( \boldsymbol{\delta} \) with \( \boldsymbol{s} \) and have +

    Although we may have redundant operations with some few zeros for \( \beta_i \), we can rewrite the above sum in a more compact way as

    +$$ +\delta_i = \sum_{k=0}^{k=m-1}\alpha_k\beta_{i-k}, +$$ + +

    where \( m=3 \) in our case, the maximum length of +the vector \( \alpha \). Note that the vector \( \boldsymbol{\beta} \) has length \( n=4 \). Below we will find an even more efficient representation.

    -$$ -s(i) \left(x*w\right)(i)= \sum_{k=0}^{k=m-1}w(k)x(i-k), -$$ - -

    where \( m=3 \) in our case, the maximum length of the vector \( \boldsymbol{w} \). -Here the symbol \( * \) represents the mathematical operation of convolution. -

    -

    Two-dimensional Objects

    - -

    We are now ready to start studying the discrete convolutions relevant for convolutional neural networks. -We often use convolutions over more than one dimension at a time. If -we have a two-dimensional image \( X \) as input, we can have a filter -defined by a two-dimensional kernel/weight/filter \( W \). This leads to an output \( Y \) -

    - -$$ -Y(i,j)=(X * W)(i,j) = \sum_m\sum_n X(m,n)W(i-m,j-n). -$$ - -

    Convolution is a commutative process, which means we can rewrite this equation as

    -$$ -Y(i,j)=(X * W)(i,j) = \sum_m\sum_n X(i-m,j-n)W(m,n). -$$ - -

    Normally the latter is more straightforward to implement in a machine larning library since there is less variation in the range of values of \( m \) and \( n \).

    - -

    Many deep learning libraries implement cross-correlation instead of convolution (although it is referred to s convolution)

    -$$ -Y(i,j)=(X * W)(i,j) = \sum_m\sum_n X(i+m,j+n)W(m,n). -$$ -

    @@ -371,7 +386,7 @@ $$

  • 32
  • 33
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs024.html b/doc/pub/week44/html/._week44-bs024.html index 87ec16d92..5c0d35788 100644 --- a/doc/pub/week44/html/._week44-bs024.html +++ b/doc/pub/week44/html/._week44-bs024.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,64 +350,23 @@ MathJax.Hub.Config({

     

     

     

    -

    CNNs in more detail

    +

    A more efficient way of coding the above Convolution

    -

    Let assume we have an input matrix \( X \) of dimensionality \( 3\times 3 \) -and a \( 2\times 2 \) filter \( W \) given by the following matrices +

    Since we only have a finite number of \( \alpha \) and \( \beta \) values +which are non-zero, we can rewrite the above convolution expressions +as a matrix-vector multiplication

    $$ -\boldsymbol{X}=\begin{bmatrix}x_{00} & x_{01} & x_{02} \\ - x_{10} & x_{11} & x_{12} \\ - x_{20} & x_{21} & x_{22} \end{bmatrix}, +\boldsymbol{\delta}=\begin{bmatrix}\alpha_0 & 0 & 0 & 0 \\ + \alpha_1 & \alpha_0 & 0 & 0 \\ + \alpha_2 & \alpha_1 & \alpha_0 & 0 \\ + 0 & \alpha_2 & \alpha_1 & \alpha_0 \\ + 0 & 0 & \alpha_2 & \alpha_1 \\ + 0 & 0 & 0 & \alpha_2 + \end{bmatrix}\begin{bmatrix} \beta_0 \\ \beta_1 \\ \beta_2 \\ \beta_3\end{bmatrix}. $$ -

    and

    -$$ -\boldsymbol{W}=\begin{bmatrix}w_{00} & w_{01} \\ - w_{10} & w_{11}\end{bmatrix}. -$$ - -

    We introduce now the hyperparameter \( S \) stride. Stride represents how the filter \( W \) moves the convolution process on the matrix \( X \). -We strongly recommend the repository on Arithmetic of deep learning by Dumoulin and Visin -

    - -

    Here we set the stride equal to \( S=1 \), which means that, starting with the element \( x_{00} \), the filter will act on \( 2\times 2 \) submatrices each time, starting with the upper corner and moving according to the stride value column by column.

    - -

    Here we perform the operation

    -$$ -Y_(i,j)=(X * W)(i,j) = \sum_m\sum_n X(i-m,j-n)W(m,n), -$$ - -

    and obtain

    -$$ -\boldsymbol{Y}=\begin{bmatrix}x_{00}w_{00}+x_{01}w_{01}+x_{10}w_{10}+x_{11}w_{11} & x_{01}w_{00}+x_{02}w_{01}+x_{11}w_{10}+x_{12}w_{11} \\ - x_{10}w_{00}+x_{11}w_{01}+x_{20}w_{10}+x_{21}w_{11} & x_{11}w_{00}+x_{12}w_{01}+x_{21}w_{10}+x_{22}w_{11}\end{bmatrix}. -$$ - -

    We can rewrite this operation in terms of a matrix-vector multiplication by defining a new vector where we flatten out the inputs as a vector \( \boldsymbol{X}' \) of length \( 9 \) and -a matrix \( \boldsymbol{W}' \) with dimension \( 4\times 9 \) as -

    - -$$ -\boldsymbol{X}'=\begin{bmatrix}x_{00} \\ x_{01} \\ x_{02} \\ x_{10} \\ x_{11} \\ x_{12} \\ x_{20} \\ x_{21} \\ x_{22} \end{bmatrix}, -$$ - -

    and the new matrix

    -$$ -\boldsymbol{W}'=\begin{bmatrix} w_{00} & w_{01} & 0 & w_{10} & w_{11} & 0 & 0 & 0 & 0 \\ - 0 & w_{00} & w_{01} & 0 & w_{10} & w_{11} & 0 & 0 & 0 \\ - 0 & 0 & 0 & w_{00} & w_{01} & 0 & w_{10} & w_{11} & 0 \\ - 0 & 0 & 0 & 0 & w_{00} & w_{01} & 0 & w_{10} & w_{11}\end{bmatrix}. -$$ - -

    We see easily that performing the matrix-vector multiplication \( \boldsymbol{W}'\boldsymbol{X}' \) is the same as the above convolution with stride \( S=1 \), that is

    - -$$ -Y=(\boldsymbol{W}*\boldsymbol{X}), -$$ - -

    is now given by \( \boldsymbol{W}'\boldsymbol{X}' \) which is a vector of length \( 4 \) instead of the originally resulting \( 2\times 2 \) output matrix.

    @@ -392,7 +393,7 @@ $$

  • 33
  • 34
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs025.html b/doc/pub/week44/html/._week44-bs025.html index 15f2c31e1..f73cb55c2 100644 --- a/doc/pub/week44/html/._week44-bs025.html +++ b/doc/pub/week44/html/._week44-bs025.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,15 +350,27 @@ MathJax.Hub.Config({

     

     

     

    -

    Performing a discrete convolution with padding (From Raschka et al)

    +

    Commutative process

    -
    -
    -
    -

    Figure 4: A deep CNN

    -
    -

    -
    +

    The process is commutative and we can easily see that we can rewrite the multiplication in terms of a matrix holding \( \beta \) and a vector holding \( \alpha \). +In this case we have +

    +$$ +\boldsymbol{\delta}=\begin{bmatrix}\beta_0 & 0 & 0 \\ + \beta_1 & \beta_0 & 0 \\ + \beta_2 & \beta_1 & \beta_0 \\ + \beta_3 & \beta_2 & \beta_1 \\ + 0 & \beta_3 & \beta_2 \\ + 0 & 0 & \beta_3 + \end{bmatrix}\begin{bmatrix} \alpha_0 \\ \alpha_1 \\ \alpha_2\end{bmatrix}. +$$ + +

    Note that the use of these matrices is for mathematical purposes only +and not implementation purposes. When implementing the above equation +we do not encode (and allocate memory) the matrices explicitely. We +rather code the convolutions in the minimal memory footprint that they +require. +

    @@ -343,7 +397,7 @@ MathJax.Hub.Config({

  • 34
  • 35
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs026.html b/doc/pub/week44/html/._week44-bs026.html index 3bae34559..e5ac726f9 100644 --- a/doc/pub/week44/html/._week44-bs026.html +++ b/doc/pub/week44/html/._week44-bs026.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,15 +350,32 @@ MathJax.Hub.Config({

     

     

     

    -

    Performing a general discrete convolution (From Raschka et al)

    +

    Toeplitz matrices

    -
    -
    -
    -

    Figure 5: A deep CNN

    -
    -

    -
    +

    The above matrices are examples of so-called Toeplitz +matrices. A +Toeplitz matrix is a matrix in which each descending diagonal from +left to right is constant. For instance the last matrix, which we +rewrite as +

    +$$ +\boldsymbol{A}=\begin{bmatrix}a_0 & 0 & 0 \\ + a_1 & a_0 & 0 \\ + a_2 & a_1 & a_0 \\ + a_3 & a_2 & a_1 \\ + 0 & a_3 & a_2 \\ + 0 & 0 & a_3 + \end{bmatrix}, +$$ + +

    with elements \( a_{ii}=a_{i+1,j+1}=a_{i-j} \) is an example of a Toeplitz +matrix. Such a matrix does not need to be a square matrix. Toeplitz +matrices are also closely connected with Fourier series, because the multiplication operator by a trigonometric +polynomial, compressed to a finite-dimensional space, can be +represented by such a matrix. The example above shows that we can +represent linear convolution as multiplication of a Toeplitz matrix by +a vector. +

    @@ -343,7 +402,7 @@ MathJax.Hub.Config({

  • 35
  • 36
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs027.html b/doc/pub/week44/html/._week44-bs027.html index ae07a062f..75c51ee78 100644 --- a/doc/pub/week44/html/._week44-bs027.html +++ b/doc/pub/week44/html/._week44-bs027.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,43 +350,13 @@ MathJax.Hub.Config({

     

     

     

    -

    Set of filters

    - -

    The collection of kernels/filters \( W \) defining a discrete convolution has a shape -corresponding to some permutation of \( (n, m, k_1, \ldots, k_N) \), where -

    - -$$ -\begin{equation*} -\begin{split} - n &\equiv \text{number of output feature maps},\\ - m &\equiv \text{number of input feature maps},\\ - k_j &\equiv \text{kernel size along axis $j$}. -\end{split} -\end{equation*} -$$ - -

    The following properties affect the output size \( o_j \) of a convolutional layer -along axis \( j \): -

    +

    Fourier series and Toeplitz matrices

    +

    This is an active and ogoing research area concerning CNNs. The following articles may be of interest

      -
    1. \( i_j \): input size along axis \( j \),
    2. -
    3. \( k_j \): kernel/filter size along axis \( j \),
    4. -
    5. stride (distance between two consecutive positions of the kernel/filter) along axis \( j \),
    6. -
    7. zero padding (number of zeros concatenated at the beginning and at the end of an axis) along axis \( j \).
    8. +
    9. Read more about the convolution theorem and Fouriers series
    10. +
    11. Fourier Transform Layer
    -

    For instance, the above examples shows a \( 2\times 2 \) kernel/filter \( \boldsymbol{W} \) applied to a \( 3 \times 3 \) input padded with a \( 0 \times 0 \) -border of zeros using \( 1 \times 1 \) strides. -

    - -

    Note that strides constitute a form of subsampling. As an alternative to -being interpreted as a measure of how much the kernel/filter is translated, strides -can also be viewed as how much of the output is retained. For instance, moving -the kernel by hops of two is equivalent to moving the kernel by hops of one but -retaining only odd output elements. -

    -

    diff --git a/doc/pub/week44/html/._week44-bs028.html b/doc/pub/week44/html/._week44-bs028.html index a0fdecb22..b097c3f11 100644 --- a/doc/pub/week44/html/._week44-bs028.html +++ b/doc/pub/week44/html/._week44-bs028.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,47 +350,23 @@ MathJax.Hub.Config({

     

     

     

    -

    Pooling

    +

    Generalizing the above one-dimensional case

    -

    In addition to discrete convolutions themselves, {\em pooling\/} operations -make up another important building block in CNNs. Pooling operations reduce -the size of feature maps by using some function to summarize subregions, such -as taking the average or the maximum value. +

    In order to align the above simple case with the more general +convolution cases, we rename \( \boldsymbol{\alpha} \), whose length is \( m=3 \), +with \( \boldsymbol{w} \). We will interpret \( \boldsymbol{w} \) as a weight/filter function +with which we want to perform the convolution with an input variable +\( \boldsymbol{x} \) of length \( n \). We will assume always that the filter +\( \boldsymbol{w} \) has dimensionality \( m \le n \).

    -

    Pooling works by sliding a window across the input and feeding the content of -the window to a {\em pooling function}. In some sense, pooling works very much -like a discrete convolution, but replaces the linear combination described by -the kernel with some other function. Poolin -provides an example for average pooling, and -does the same for max pooling. -

    +

    We replace thus \( \boldsymbol{\beta} \) with \( \boldsymbol{x} \) and \( \boldsymbol{\delta} \) with \( \boldsymbol{y} \) and have

    +$$ +y(i)= \left(x*w\right)(i)= \sum_{k=0}^{k=m-1}w(k)x(i-k), +$$ -

    The following properties affect the output size \( o_j \) of a pooling layer -along axis \( j \): -

    - -
      -
    1. \( i_j \): input size along axis \( j \),
    2. -
    3. \( k_j \): pooling window size along axis \( j \),
    4. -
    5. \( s_j \): stride (distance between two consecutive positions of the pooling window) along axis \( j \).
    6. -
    -

    The analysis of the relationship between convolutional layer properties is eased -by the fact that they don't interact across axes, i.e., the choice of kernel -size, stride and zero padding along axis \( j \) only affects the output size of -axis \( j \). Because of that, we will focus on the following simplified -setting: -

    - -
      -
    1. 2-D discrete convolutions (\( N = 2 \)),
    2. -
    3. square inputs (\( i_1 = i_2 = i \)),
    4. -
    5. square kernel size (\( k_1 = k_2 = k \)),
    6. -
    7. same strides along both axes (\( s_1 = s_2 = s \)),
    8. -
    9. same zero padding along both axes (\( p_1 = p_2 = p \)).
    10. -
    -

    This facilitates the analysis and the visualization, but keep in mind that the -results outlined here also generalize to the N-D and non-square cases. +

    where \( m=3 \) in our case, the maximum length of the vector \( \boldsymbol{w} \). +Here the symbol \( * \) represents the mathematical operation of convolution.

    @@ -376,7 +394,7 @@ results outlined here also generalize to the N-D and non-square cases.

  • 37
  • 38
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs029.html b/doc/pub/week44/html/._week44-bs029.html index 6424f3df5..0cb0751b9 100644 --- a/doc/pub/week44/html/._week44-bs029.html +++ b/doc/pub/week44/html/._week44-bs029.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,19 +350,21 @@ MathJax.Hub.Config({

     

     

     

    -

    No zero padding, unit strides

    +

    Memory considerations

    -

    The simplest case to analyze is when the kernel just slides across every -position of the input (i.e., \( s = 1 \) and \( p = 0 \)). +

    This expression leaves us however with some terms with negative +indices, for example \( x(-1) \) and \( x(-2) \) which may not be defined. Our +vector \( \boldsymbol{x} \) has components \( x(0) \), \( x(1) \), \( x(2) \) and \( x(3) \).

    -

    For any \( i \) and \( k \), and for \( s = 1 \) and \( p = 0 \),

    -$$ -\begin{equation*} - o = (i - k) + 1. -\end{equation*} -$$ +

    The index \( j \) for \( \boldsymbol{x} \) runs from \( j=0 \) to \( j=3 \) since \( \boldsymbol{x} \) is meant to +represent a third-order polynomial. +

    +

    Furthermore, the index \( i \) runs from \( i=0 \) to \( i=5 \) since \( \boldsymbol{y} \) +contains the coefficients of a fifth-order polynomial. When \( i=5 \) we +may also have values of \( x(4) \) and \( x(5) \) which are not defined. +

    @@ -347,7 +391,7 @@ $$

  • 38
  • 39
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs030.html b/doc/pub/week44/html/._week44-bs030.html index cf9c7fa09..7abb72049 100644 --- a/doc/pub/week44/html/._week44-bs030.html +++ b/doc/pub/week44/html/._week44-bs030.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,22 +350,15 @@ MathJax.Hub.Config({

     

     

     

    -

    Zero padding, unit strides

    +

    Padding

    -

    To factor in zero padding (i.e., only restricting to \( s = 1 \)), let's consider -its effect on the effective input size: padding with \( p \) zeros changes the -effective input size from \( i \) to \( i + 2p \). In the general case, we can infer the following -relationship +

    The solution to this is what is called padding! We simply define a +new vector \( x \) with two added elements set to zero before \( x(0) \) and +two new elements after \( x(3) \) set to zero. That is, we augment the +length of \( \boldsymbol{x} \) from \( n=4 \) to \( n+2P=8 \), where \( P=2 \) is the padding +constant (a new hyperparameter), see discussions below as well.

    -

    For any \( i \), \( k \) and \( p \), and for \( s = 1 \),

    -$$ -\begin{equation*} - o = (i - k) + 2p + 1. -\end{equation*} -$$ - -

    diff --git a/doc/pub/week44/html/._week44-bs031.html b/doc/pub/week44/html/._week44-bs031.html index 523411cb7..f2375e2f8 100644 --- a/doc/pub/week44/html/._week44-bs031.html +++ b/doc/pub/week44/html/._week44-bs031.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,23 +350,36 @@ MathJax.Hub.Config({

     

     

     

    -

    Half (same) padding

    +

    New vector

    -

    Having the output size be the same as the input size (i.e., \( o = i \)) can be a -desirable property: +

    We have a new vector defined as \( x(0)=0 \), \( x(1)=0 \), +\( x(2)=\beta_0 \), \( x(3)=\beta_1 \), \( x(4)=\beta_2 \), \( x(5)=\beta_3 \), +\( x(6)=0 \), and \( x(7)=0 \).

    -

    For any \( i \) and for \( k \) odd (\( k = 2n + 1, \quad n \in \mathbb{N} \)), \( s = 1 \) and -\( p = \lfloor k / 2 \rfloor = n \), +

    We have added four new elements, which +are all zero. The benefit is that we can rewrite the equation for +\( \boldsymbol{y} \), with \( i=0,1,\dots,5 \),

    $$ -\begin{equation*} -\begin{split} - o &= i + 2 \lfloor k / 2 \rfloor - (k - 1) \\ - &= i + 2n - 2n \\ - &= i. -\end{split} -\end{equation*} +y(i) = \sum_{k=0}^{k=m-1}w(k)x(i+(m-1)-k). +$$ + +

    As an example, we have

    +$$ +y(4)=x(6)w(0)+x(5)w(1)+x(4)w(2)=0\times \alpha_0+\beta_3\alpha_1+\beta_2\alpha_2, +$$ + +

    as before except that we have an additional term \( x(6)w(0) \), which is zero.

    + +

    Similarly, for the fifth-order term we have

    +$$ +y(5)=x(7)w(0)+x(6)w(1)+x(5)w(2)=0\times \alpha_0+0\times\alpha_1+\beta_3\alpha_2. +$$ + +

    The zeroth-order term is

    +$$ +y(0)=x(2)w(0)+x(1)w(1)+x(0)w(2)=\beta_0 \alpha_0+0\times\alpha_1+0\times\alpha_2=\alpha_0\beta_0. $$ @@ -353,7 +408,7 @@ $$
  • 40
  • 41
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs032.html b/doc/pub/week44/html/._week44-bs032.html index 66913f320..9f6bcb472 100644 --- a/doc/pub/week44/html/._week44-bs032.html +++ b/doc/pub/week44/html/._week44-bs032.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,26 +350,21 @@ MathJax.Hub.Config({

     

     

     

    -

    Full padding

    +

    Rewriting as dot products

    -

    While convolving a kernel generally decreases the output size with -respect to the input size, sometimes the opposite is required. This can be -achieved with proper zero padding: +

    If we now flip the filter/weight vector, with the following term as a typical example

    +$$ +y(0)=x(2)w(0)+x(1)w(1)+x(0)w(2)=x(2)\tilde{w}(2)+x(1)\tilde{w}(1)+x(0)\tilde{w}(0), +$$ + +

    with \( \tilde{w}(0)=w(2) \), \( \tilde{w}(1)=w(1) \), and \( \tilde{w}(2)=w(0) \), we can then rewrite the above sum as a dot product of +\( x(i:i+(m-1))\tilde{w} \) for element \( y(i) \), where \( x(i:i+(m-1)) \) is simply a patch of \( \boldsymbol{x} \) of size \( m-1 \).

    -

    For any \( i \) and \( k \), and for \( p = k - 1 \) and \( s = 1 \),

    -$$ -\begin{equation*} -\begin{split} - o &= i + 2(k - 1) - (k - 1) \\ - &= i + (k - 1). -\end{split} -\end{equation*} -$$ - -

    This is sometimes referred to as full padding, because in this -setting every possible partial or complete superimposition of the kernel on the -input feature map is taken into account. +

    The padding \( P \) we have introduced for the convolution stage is just +another hyperparameter which is introduced as part of the +architecture. Similarly, below we will also introduce another +hyperparameter called Stride \( S \).

    @@ -355,7 +392,7 @@ input feature map is taken into account.

  • 41
  • 42
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs033.html b/doc/pub/week44/html/._week44-bs033.html index e7d939c78..3adce6b7e 100644 --- a/doc/pub/week44/html/._week44-bs033.html +++ b/doc/pub/week44/html/._week44-bs033.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,25 +350,55 @@ MathJax.Hub.Config({

     

     

     

    -

    Pooling arithmetic

    +

    Cross correlation

    -

    In a neural network, pooling layers provide invariance to small translations of -the input. The most common kind of pooling is max pooling, which -consists in splitting the input in (usually non-overlapping) patches and -outputting the maximum value of each patch. Other kinds of pooling exist, e.g., -mean or average pooling, which all share the same idea of aggregating the input -locally by applying a non-linearity to the content of some patches. +

    In essentially all applications one uses what is called cross correlation instead of the standard convolution described above. +This means that multiplication is performed in the same direction and instead of the general expression we have discussed above (with infinite sums)

    - -

    Since pooling does not involve -zero padding, the relationship describing the general case is as follows: -

    - -

    For any \( i \), \( k \) and \( s \),

    $$ -\begin{equation*} - o = \left\lfloor \frac{i - k}{s} \right\rfloor + 1. -\end{equation*} +y(i) = \sum_{k=-\infty}^{k=\infty}w(k)x(i-k), +$$ + +

    we have now

    +$$ +y(i) = \sum_{k=-\infty}^{k=\infty}w(k)x(i+k). +$$ + +

    Both TensorFlow and PyTorch (as well as our own code example below), +implement the last equation, although it is normally referred to as +convolution. The same padding rules and stride rules discussed below +apply to this expression as well. +

    + +

    We leave it as an exercise for you to convince yourself that the example we have discussed till now, gives the same final expression using the last expression.

    +

    Two-dimensional objects

    + +

    We are now ready to start studying the discrete convolutions relevant for convolutional neural networks. +We often use convolutions over more than one dimension at a time. If +we have a two-dimensional image \( X \) as input, we can have a filter +defined by a two-dimensional kernel/weight/filter \( W \). This leads to an output \( Y \) +

    + +$$ +Y(i,j)=(X * W)(i,j) = \sum_m\sum_n X(m,n)W(i-m,j-n). +$$ + +

    Convolution is a commutative process, which means we can rewrite this equation as

    +$$ +Y(i,j)=(X * W)(i,j) = \sum_m\sum_n X(i-m,j-n)W(m,n). +$$ + +

    Normally the latter is more straightforward to implement in a machine +larning library since there is less variation in the range of values +of \( m \) and \( n \). +

    + +

    As mentioned above, most deep learning libraries implement +cross-correlation instead of convolution (although it is referred to as +convolution) +

    +$$ +Y(i,j)=(X * W)(i,j) = \sum_m\sum_n X(i+m,j+n)W(m,n). $$ @@ -355,7 +427,7 @@ $$
  • 42
  • 43
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs034.html b/doc/pub/week44/html/._week44-bs034.html index d4f55423e..46214b787 100644 --- a/doc/pub/week44/html/._week44-bs034.html +++ b/doc/pub/week44/html/._week44-bs034.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,15 +350,64 @@ MathJax.Hub.Config({

     

     

     

    -

    Pooling types (From Raschka et al)

    +

    CNNs in more detail, simple example

    -
    -
    -
    -

    Figure 6: A deep CNN

    -
    -

    -
    +

    Let assume we have an input matrix \( X \) of dimensionality \( 3\times 3 \) +and a \( 2\times 2 \) filter \( W \) given by the following matrices +

    + +$$ +\boldsymbol{X}=\begin{bmatrix}x_{00} & x_{01} & x_{02} \\ + x_{10} & x_{11} & x_{12} \\ + x_{20} & x_{21} & x_{22} \end{bmatrix}, +$$ + +

    and

    +$$ +\boldsymbol{W}=\begin{bmatrix}w_{00} & w_{01} \\ + w_{10} & w_{11}\end{bmatrix}. +$$ + +

    We introduce now the hyperparameter \( S \) stride. Stride represents how the filter \( W \) moves the convolution process on the matrix \( X \). +We strongly recommend the repository on Arithmetic of deep learning by Dumoulin and Visin +

    + +

    Here we set the stride equal to \( S=1 \), which means that, starting with the element \( x_{00} \), the filter will act on \( 2\times 2 \) submatrices each time, starting with the upper corner and moving according to the stride value column by column.

    + +

    Here we perform the operation

    +$$ +Y_(i,j)=(X * W)(i,j) = \sum_m\sum_n X(i-m,j-n)W(m,n), +$$ + +

    and obtain

    +$$ +\boldsymbol{Y}=\begin{bmatrix}x_{00}w_{00}+x_{01}w_{01}+x_{10}w_{10}+x_{11}w_{11} & x_{01}w_{00}+x_{02}w_{01}+x_{11}w_{10}+x_{12}w_{11} \\ + x_{10}w_{00}+x_{11}w_{01}+x_{20}w_{10}+x_{21}w_{11} & x_{11}w_{00}+x_{12}w_{01}+x_{21}w_{10}+x_{22}w_{11}\end{bmatrix}. +$$ + +

    We can rewrite this operation in terms of a matrix-vector multiplication by defining a new vector where we flatten out the inputs as a vector \( \boldsymbol{X}' \) of length \( 9 \) and +a matrix \( \boldsymbol{W}' \) with dimension \( 4\times 9 \) as +

    + +$$ +\boldsymbol{X}'=\begin{bmatrix}x_{00} \\ x_{01} \\ x_{02} \\ x_{10} \\ x_{11} \\ x_{12} \\ x_{20} \\ x_{21} \\ x_{22} \end{bmatrix}, +$$ + +

    and the new matrix

    +$$ +\boldsymbol{W}'=\begin{bmatrix} w_{00} & w_{01} & 0 & w_{10} & w_{11} & 0 & 0 & 0 & 0 \\ + 0 & w_{00} & w_{01} & 0 & w_{10} & w_{11} & 0 & 0 & 0 \\ + 0 & 0 & 0 & w_{00} & w_{01} & 0 & w_{10} & w_{11} & 0 \\ + 0 & 0 & 0 & 0 & w_{00} & w_{01} & 0 & w_{10} & w_{11}\end{bmatrix}. +$$ + +

    We see easily that performing the matrix-vector multiplication \( \boldsymbol{W}'\boldsymbol{X}' \) is the same as the above convolution with stride \( S=1 \), that is

    + +$$ +Y=(\boldsymbol{W}*\boldsymbol{X}), +$$ + +

    is now given by \( \boldsymbol{W}'\boldsymbol{X}' \) which is a vector of length \( 4 \) instead of the originally resulting \( 2\times 2 \) output matrix.

    @@ -343,7 +434,7 @@ MathJax.Hub.Config({

  • 43
  • 44
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs035.html b/doc/pub/week44/html/._week44-bs035.html index 34af1ccd0..02e0ed674 100644 --- a/doc/pub/week44/html/._week44-bs035.html +++ b/doc/pub/week44/html/._week44-bs035.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,3259 +350,13 @@ MathJax.Hub.Config({

     

     

     

    -

    Building our own CNN code

    +

    The convolution stage

    -

    Here we present a flexible and readable python code for a CNN -implemented with NumPy. We will present the code, showcase how to use -the codebase and fit a CNN that yields a 99% accuracy on the 28x28 -MNIST dataset within reasonable time. -

    - -

    The CNN is compatible with all schedulers, cost functions and -activation functions discussed in constructing our neural network -codes. -

    - -

    The CNN code consists of different types of Layer classes, including -Convolution2DLayer, Pooling2DLayer, FlattenLayer, FullyConnectedLayer -and OutputLayer, which can be added to the CNN object using the -interface of the CNN class. This allows you to easily construct your -own CNN, as well as allowing you to get used to an interface similar -to that of TensorFlow which is used for real world applications. -

    - -

    Another important feature of this code is that it throws errors if -unreasonable decisions are made (for example using a kernel that is -larger than the image, not using a FlattenLayer, etc), and provides -the user with an informative error message. -

    -

    List of contents:

    -
      -
    1. Schedulers
    2. -
    3. Activation Functions
    4. -
    5. Cost Functions
    6. -
    7. Convolution
    8. -
    9. Layers
    10. -
    11. CNN
    12. -
    13. Some final remarks
    14. -
    -

    Schedulers

    - -

    The code below shows object oriented implementations of the Constant, -Momentum, Adagrad, AdagradMomentum, RMS prop and Adam schedulers. All -of the classes belong to the shared abstract Scheduler class, and -share the update_change() and reset() methods allowing for any of the -schedulers to be seamlessly used during the training stage, as will -later be shown in the fit() method of the neural -network. Update_change() only has one parameter, the gradient -(\( \delta^{l}_{j}a^{l-1}_k \)), and returns the change which will be -subtracted from the weights. The reset() function takes no parameters, -and resets the desired variables. For Constant and Momentum, reset -does nothing. -

    - - - -
    -
    -
    -
    -
    -
    import autograd.numpy as np
    -
    -class Scheduler:
    -    """
    -    Abstract class for Schedulers
    -    """
    -
    -    def __init__(self, eta):
    -        self.eta = eta
    -
    -    # should be overwritten
    -    def update_change(self, gradient):
    -        raise NotImplementedError
    -
    -    # overwritten if needed
    -    def reset(self):
    -        pass
    -
    -
    -class Constant(Scheduler):
    -    def __init__(self, eta):
    -        super().__init__(eta)
    -
    -    def update_change(self, gradient):
    -        return self.eta * gradient
    -    
    -    def reset(self):
    -        pass
    -
    -
    -class Momentum(Scheduler):
    -    def __init__(self, eta: float, momentum: float):
    -        super().__init__(eta)
    -        self.momentum = momentum
    -        self.change = 0
    -
    -    def update_change(self, gradient):
    -        self.change = self.momentum * self.change + self.eta * gradient
    -        return self.change
    -
    -    def reset(self):
    -        pass
    -
    -
    -class Adagrad(Scheduler):
    -    def __init__(self, eta):
    -        super().__init__(eta)
    -        self.G_t = None
    -
    -    def update_change(self, gradient):
    -        delta = 1e-8  # avoid division ny zero
    -
    -        if self.G_t is None:
    -            self.G_t = np.zeros((gradient.shape[0], gradient.shape[0]))
    -
    -        self.G_t += gradient @ gradient.T
    -
    -        G_t_inverse = 1 / (
    -            delta + np.sqrt(np.reshape(np.diagonal(self.G_t), (self.G_t.shape[0], 1)))
    -        )
    -        return self.eta * gradient * G_t_inverse
    -
    -    def reset(self):
    -        self.G_t = None
    -
    -
    -class AdagradMomentum(Scheduler):
    -    def __init__(self, eta, momentum):
    -        super().__init__(eta)
    -        self.G_t = None
    -        self.momentum = momentum
    -        self.change = 0
    -
    -    def update_change(self, gradient):
    -        delta = 1e-8  # avoid division ny zero
    -
    -        if self.G_t is None:
    -            self.G_t = np.zeros((gradient.shape[0], gradient.shape[0]))
    -
    -        self.G_t += gradient @ gradient.T
    -
    -        G_t_inverse = 1 / (
    -            delta + np.sqrt(np.reshape(np.diagonal(self.G_t), (self.G_t.shape[0], 1)))
    -        )
    -        self.change = self.change * self.momentum + self.eta * gradient * G_t_inverse
    -        return self.change
    -
    -    def reset(self):
    -        self.G_t = None
    -
    -
    -class RMS_prop(Scheduler):
    -    def __init__(self, eta, rho):
    -        super().__init__(eta)
    -        self.rho = rho
    -        self.second = 0.0
    -
    -    def update_change(self, gradient):
    -        delta = 1e-8  # avoid division ny zero
    -        self.second = self.rho * self.second + (1 - self.rho) * gradient * gradient
    -        return self.eta * gradient / (np.sqrt(self.second + delta))
    -
    -    def reset(self):
    -        self.second = 0.0
    -
    -
    -class Adam(Scheduler):
    -    def __init__(self, eta, rho, rho2):
    -        super().__init__(eta)
    -        self.rho = rho
    -        self.rho2 = rho2
    -        self.moment = 0
    -        self.second = 0
    -        self.n_epochs = 1
    -
    -    def update_change(self, gradient):
    -        delta = 1e-8  # avoid division ny zero
    -
    -        self.moment = self.rho * self.moment + (1 - self.rho) * gradient
    -        self.second = self.rho2 * self.second + (1 - self.rho2) * gradient * gradient
    -
    -        moment_corrected = self.moment / (1 - self.rho**self.n_epochs)
    -        second_corrected = self.second / (1 - self.rho2**self.n_epochs)
    -
    -        return self.eta * moment_corrected / (np.sqrt(second_corrected + delta))
    -
    -    def reset(self):
    -        self.n_epochs += 1
    -        self.moment = 0
    -        self.second = 0
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Usage of schedulers

    - -

    To initalize a scheduler, simply create the object and pass in the necessary parameters such as the learning rate and the momentum as shown below. As the Scheduler class is an abstract class it should not called directly, and will raise an error upon usage.

    - - - -
    -
    -
    -
    -
    -
    momentum_scheduler = Momentum(eta=1e-3, momentum=0.9)
    -adam_scheduler = Adam(eta=1e-3, rho=0.9, rho2=0.999)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Here is a small example for how a segment of code using schedulers could look. Switching out the schedulers is simple.

    - - - -
    -
    -
    -
    -
    -
    weights = np.ones((3,3))
    -print(f"Before scheduler:\n{weights=}")
    -
    -epochs = 10
    -for e in range(epochs):
    -    gradient = np.random.rand(3, 3)
    -    change = adam_scheduler.update_change(gradient)
    -    weights = weights - change
    -    adam_scheduler.reset()
    -
    -print(f"\nAfter scheduler:\n{weights=}")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Cost functions

    - -

    In this section we will quickly look at cost functions that can be -used when creating the neural network. Every cost function takes the -target vector as its parameter, and returns a function valued only at -X such that it may easily be differentiated. -

    - - - -
    -
    -
    -
    -
    -
    def CostOLS(target):
    -    """
    -    Return OLS function valued only at X, so
    -    that it may be easily differentiated
    -    """
    -
    -    def func(X):
    -        return (1.0 / target.shape[0]) * np.sum((target - X) ** 2)
    -
    -    return func
    -
    -
    -def CostLogReg(target):
    -    """
    -    Return Logistic Regression cost function
    -    valued only at X, so that it may be easily differentiated
    -    """
    -
    -    def func(X):
    -        return -(1.0 / target.shape[0]) * np.sum(
    -            (target * np.log(X + 10e-10)) + ((1 - target) * np.log(1 - X + 10e-10))
    -        )
    -
    -    return func
    -
    -
    -def CostCrossEntropy(target):
    -    """
    -    Return cross entropy cost function valued only at X, so
    -    that it may be easily differentiated
    -    """
    -    
    -    def func(X):
    -        return -(1.0 / target.size) * np.sum(target * np.log(X + 10e-10))
    -
    -    return func
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Usage of cost functions

    - -

    Below we will provide a short example of how these cost function may -be used to obtain results if you wish to test them out on your own -using AutoGrad's automatic differentiation. -

    - - - -
    -
    -
    -
    -
    -
    from autograd import grad
    -
    -target = np.array([[1, 2, 3]]).T
    -a = np.array([[4, 5, 6]]).T
    -
    -cost_func = CostCrossEntropy
    -cost_func_derivative = grad(cost_func(target))
    -
    -valued_at_a = cost_func_derivative(a)
    -print(f"Derivative of cost function {cost_func.__name__} valued at a:\n{valued_at_a}")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Activation functions

    - -

    Finally, before we look at the layers that make up the neural network, -we will look at the activation functions which can be specified -between the hidden layers and as the output function. Each function -can be valued for any given vector or matrix X, and can be -differentiated via derivate(). -

    - - - -
    -
    -
    -
    -
    -
    import autograd.numpy as np
    -from autograd import elementwise_grad
    -
    -def identity(X):
    -    return X
    -
    -
    -def sigmoid(X):
    -    try:
    -        return 1.0 / (1 + np.exp(-X))
    -    except FloatingPointError:
    -        return np.where(X > np.zeros(X.shape), np.ones(X.shape), np.zeros(X.shape))
    -
    -
    -def softmax(X):
    -    X = X - np.max(X, axis=-1, keepdims=True)
    -    delta = 10e-10
    -    return np.exp(X) / (np.sum(np.exp(X), axis=-1, keepdims=True) + delta)
    -
    -
    -def RELU(X):
    -    return np.where(X > np.zeros(X.shape), X, np.zeros(X.shape))
    -
    -
    -def LRELU(X):
    -    delta = 10e-4
    -    return np.where(X > np.zeros(X.shape), X, delta * X)
    -
    -
    -def derivate(func):
    -    if func.__name__ == "RELU":
    -
    -        def func(X):
    -            return np.where(X > 0, 1, 0)
    -
    -        return func
    -
    -    elif func.__name__ == "LRELU":
    -
    -        def func(X):
    -            delta = 10e-4
    -            return np.where(X > 0, 1, delta)
    -
    -        return func
    -
    -    else:
    -        return elementwise_grad(func)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Usage of activation functions

    - -

    Below we present a short demonstration of how to use an activation -function. The derivative of the activation function will be important -when calculating the output delta term during backpropagation. Note -that derivate() can also be used for cost functions for a more -generalized approach. -

    - - - -
    -
    -
    -
    -
    -
    z = np.array([[4, 5, 6]]).T
    -print(f"Input to activation function:\n{z}")
    -
    -act_func = sigmoid
    -a = act_func(z)
    -print(f"\nOutput from {act_func.__name__} activation function:\n{a}")
    -
    -act_func_derivative = derivate(act_func)
    -valued_at_z = act_func_derivative(a)
    -print(f"\nDerivative of {act_func.__name__} activation function valued at z:\n{valued_at_z}")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Convolution

    - -

    In order to construct a convolutional neural network (CNN), it is -crucial to comprehend the fundamental principles of convolution and -how it aids in extracting information from images. Convolution, at its -core, is merely a mathematical operation between two functions that -yields another function. It is represented by an integral between two -functions, which is typically expressed as: -

    - -$$ -(f \ast g)(t):=\int_{-\infty}^{\infty} f(\tau) g(t-\tau) d \tau. -$$ - -

    Here, f and g are the two functions on which we want to perform an -operation. The outcome of the convolution operation is represented by -\( (f \ast g) \), and it is derived by sliding the function g over f and -computing the integral of their product at each position. If both -functions are continuous, convolution takes the form shown -above. However, if we discretize both f and g, the convolution -operation will take the form of a sum between the elements of f and g: -

    -$$ -(f \ast g)[n]=\sum_{m=0}^{n-1} f[m] g[n-m]. -$$ - -

    The key idea we utilize to extract the information contained in an -image is to slide an \( m \times n \) matrix g over an \( m \times n \) -matrix f. In our case, f represents the image, while g -represents the kernel, oftentimes called a filter. However, since our -convolution will be a two-dimensional variant, we need to extend our -mathematical formula with an additional summation: -

    - -$$ -(f \ast g)[i, j]\sum_{m=0}^{M-1}\sum_{n=0}^{N-1} f[m,n] g[i-m, j-n]. -$$ - -

    It is imperative to note that the size of the kernel g is -significantly smaller than the size of the input image f, thereby -reducing the amount of computation necessary for feature -extraction. Furthermore, the kernel is usually a trainable parameter -in a convolutional neural network, allowing the network to learn -appropriate kernels for specific tasks. -

    - -

    To give you an example of how 2D convolution works in practice, -suppose we have an image f of dimension \( 6 \times 6 \) -

    - -$$ -f = \begin{bmatrix} -4 & 1 & 2 & 9 & 8 & 6 \\ -9 & 5 & 9 & 5 & 8 & 5 \\ -1 & 5 & 9 & 7 & 6 & 4 \\ -2 & 9 & 8 & 3 & 7 & 1 \\ -8 & 1 & 6 & 4 & 2 & 2 \\ -1 & 0 & 5 & 7 & 8 & 2 \\ -\end{bmatrix} -$$ - -

    and a \( 3 \times 3 \) kernel g called a low-pass filter. Note that the -kernel is usually rotated by 180 degrees during convolution, however -this has no effect on this kernel. -

    - -$$ -g = \frac{1}{9} -\begin{bmatrix} -1 & 1 & 1 \\ -1 & 1 & 1 \\ -1 & 1 & 1 \\ -\end{bmatrix} -$$ - -

    In order to filter the image, we have to extract a \( 3 \times 3 \) -element from the upper left corner of f, and perform element-wise -multiplication of the extracted image pixels with the elements of the -kernel g: -

    - -$$ -\begin{bmatrix} -4 & 1 & 2 \\ -9 & 5 & 9 \\ -1 & 5 & 9 \\ -\end{bmatrix} -\cdot -\begin{bmatrix} -\frac{1}{9} & \frac{1}{9} & \frac{1}{9} \\ -\frac{1}{9} & \frac{1}{9} & \frac{1}{9} \\ -\frac{1}{9} & \frac{1}{9} & \frac{1}{9} \\ -\end{bmatrix} -= -\begin{bmatrix} -\frac{4}{9} & \frac{1}{9} & \frac{2}{9} \\ -\frac{9}{9} & \frac{5}{9} & \frac{9}{9} \\ -\frac{1}{9} & \frac{5}{9} & \frac{9}{9} \\ -\end {bmatrix} -= \textbf{A} -$$ - -

    Then, following the multiplication, we summarize all the elements of the resulting matrix A:

    - -$$ -(f \ast g)[0, 0]= \sum_{i=0}^{2} \sum_{j=0}^{2} a_{i,j} = 5 -$$ - -

    Which corresponds to the first element of the filtered image \( (f \ast g) \).

    - -

    Here we use a stride of 1, a parameter denoted s which describes how -many indexes we move the kernel g to the right before repeating the -calculations above for the next \( 3 \times 3 \) element of the image -f. It is usually presumed that *s*=1, however, larger values for s -can be used to reduce the dimentionality of the filtered image such -that the convolution operation is more computationally efficient. In -the context of a convolutional neural network, this will become very -useful. -

    - -

    The full result of the convolution is:

    - -$$ -(f \ast g) = -\begin{bmatrix} -5 & 5.78 & 7 & 6.44 \\ -6.33 & 6.67 & 6.89 & 5.11 \\ -5.44 & 5.78 & 5.78 & 4 \\ -4.44 & 4.78 & 5.56 & 4 \\ -\end{bmatrix} -$$ - -

    The result is markedly smaller in shape than the original image. This occurs when using convolution without first padding the image with additional columns and rows, allowing us to keep the original image shape after sliding the kernel over the image. -How many rows and columns we wish to pad the image with depends strictly on the shape of the kernel, as we wish to pad the image with r additional rows and c additional columns. -

    - -$$ -r =\lfloor \frac{kernel\ height}{2} \rfloor \cdot 2 \\ -c =\lfloor \frac{kernel\ width}{2} \rfloor \cdot 2 -$$ - -

    Note the notation \( \lfloor \frac{kernel width}{2} \rfloor \) means that -we floor the result of the division, meaning we round down to a whole -number in case \( \frac{kernel width}{2} \) results in a floating point -number. -

    - -

    Using those simple equations, we find out by how much we have to -extend the dimensions of the original image. Before proceeding, -however, we might ask what we shall fill the additional rows and -columns with? One of the most common approaches to padding is -zero-padding, which as the name suggest, involves filling the rows and -columns with zeros. This is the approach that we will be using for -this demonstration. If we apply this padding to out original \( 6 \times 6 \) -image, the result will be an \( 8 \times 8 \) image as the kernel has a width and -height of 3. Note that the original image is encapsuled by the -zero-padded rows and columns: -

    - -$$ -\begin{bmatrix} -0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ -0 & 4 & 1 & 2 & 9 & 8 & 6 & 0 \\ -0 & 9 & 5 & 9 & 5 & 8 & 5 & 0 \\ -0 & 1 & 5 & 9 & 7 & 6 & 4 & 0 \\ -0 & 2 & 9 & 8 & 3 & 7 & 1 & 0 \\ -0 & 8 & 1 & 6 & 4 & 2 & 2 & 0 \\ -0 & 1 & 0 & 5 & 7 & 8 & 2 & 0 \\ -0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ -\] -\end{bmatrix} -$$ - -

    Below we have provided code that demonstrates padding and convolution. As you will see when we run the code, the size of the image will remain unchanged when using padding. 

    - - - -
    -
    -
    -
    -
    -
    import numpy as np
    -
    -def padding(image, kernel):
    -    # calculate r and c
    -    r = (kernel.shape[0] // 2) * 2
    -    c = (kernel.shape[1] // 2) * 2
    -    
    -    # padded image dimensions
    -    padded_height = image.shape[0] + r
    -    padded_width = image.shape[1] + c
    -    
    -    # for more readable code
    -    k_half_height = kernel.shape[0] // 2
    -    k_half_width = kernel.shape[1] // 2
    -
    -    # zero matrix with padded dimensions
    -    padded_img = np.zeros((padded_height, padded_width))
    -
    -    # place image into zero matrix
    -    padded_img[k_half_height : padded_height - k_half_height,
    -               k_half_width : padded_width - k_half_width] = image[:, :]
    -
    -    return padded_img
    -
    -def convolve(original_image, padded_image, kernel, stride=1):
    -    # rotate kernel by 180 degrees
    -    kernel = np.rot90(np.rot90(kernel))
    -
    -    # note that kernel height // 2 is written as 'm'
    -    # and kernel width // 2 as 'n' in the mathematical notation
    -    m = kernel.shape[0] // 2
    -    n = kernel.shape[1] // 2
    -    
    -    r = (kernel.shape[0] // 2) * 2
    -    c = (kernel.shape[1] // 2) * 2
    -    
    -    # initialize output array
    -    convolved_image = np.zeros(original_image.shape)
    -    image_height = original_image.shape[0]
    -    image_width = original_image.shape[1]
    -
    -    # the convolution
    -    for i in range(m, image_height + m, stride):
    -        for j in range(n, image_width + n, stride):
    -            convolved_image[i-m, j-n] = np.sum(
    -                padded_image[i : i + m, j : j + n]
    -                * kernel
    -            )
    -            
    -    return convolved_image
    -
    -def convolve(image, kernel, stride=1):
    -    for i in range(2):
    -        kernel = np.rot90(kernel)
    -
    -    k_half_height = kernel.shape[0] // 2
    -    k_half_width = kernel.shape[0] // 2
    -
    -    conv_image = np.zeros(image.shape)
    -    pad_image = padding(image, kernel)
    -
    -    for i in range(k_half_height, conv_image.shape[0] + k_half_height, stride):
    -        for j in range(k_half_width, conv_image.shape[1] + k_half_width, stride):
    -            conv_image[i - k_half_height, j - k_half_width] = np.sum(
    -                pad_image[
    -                    i - k_half_height : i + k_half_height + 1, j - k_half_width : j + k_half_width + 1
    -                ]
    -                * kernel
    -            )
    -
    -    return conv_image
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Fun fact: When filtering images, you will see that convolution involves rotating the kernel by 180 degrees. -However, this is not the case when applying convolution in a CNN, where the same operation not rotated by 180 degrees is called -cross-correlation. -

    - - - -
    -
    -
    -
    -
    -
    original_image = np.array([[4, 1, 2, 9, 8, 6],
    -                 [9, 5, 9, 5, 8, 5],
    -                 [1, 5, 9, 7, 6, 4],
    -                 [2, 9, 8, 3, 7, 1],
    -                 [8, 1, 6, 4, 2, 2],
    -                 [1, 0, 5, 7, 8, 2]])
    -
    -kernel = (1/9)*np.ones((3,3))
    -
    -print(f"{original_image.shape=}")
    -
    -# note that convolve() performs padding
    -convolved_image = convolve(original_image, kernel, stride=1)
    -
    -print(f"{convolved_image.shape=}")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    As you can see, the resulting image is of the same size as the -original image. To round of our demonstration of convolution, we will -present the results of convolution using commonly used kernels. In a -CNN, the values of the kernels are randomly initialized, and then -learned during training. These kernels will extract information -regarding the picture, such as for example the edge detection filter -demonstrated below extracts the edges present in the picture. Of -course, there is no guarantee that the CNN will learn an edge -detection filter, but this should provide some intuiton as to how the -CNN is able to use kernels to make better predictions than a regular -feed forward neural network. -

    - - - -
    -
    -
    -
    -
    -
    # Now an example using a real image and first a gaussian low-pass filter and then a sobel filter
    -import numpy as np
    -import imageio.v3 as imageio
    -import matplotlib.pyplot as plt
    -import time
    -
    -def generate_gauss_mask(sigma, K=1):
    -    side = np.ceil(1 + 8 * sigma)
    -    y, x = np.mgrid[-side // 2 + 1 : (side // 2) + 1, -side // 2 + 1 : (side // 2) + 1]
    -    ker_coef = K / (2 * np.pi * sigma**2)
    -    g = np.exp(-((x**2 + y**2) / (2.0 * sigma**2)))
    -
    -    return g, ker_coef
    -
    -
    -img_path = "data/IMG-2167.JPG"
    -image_of_cute_dog = imageio.imread(img_path, mode='L')
    -
    -plt.imshow(image_of_cute_dog, cmap="gray", vmin=0, vmax=255, aspect="auto")
    -plt.title("Original image")
    -plt.show()
    -
    -gauss, kernel = generate_gauss_mask(sigma=6)
    -gauss_kernel = gauss*kernel
    -
    -filtered_image = convolve(image_of_cute_dog, gauss_kernel)
    -plt.imshow(filtered_image, cmap="gray", vmin=0, vmax=255, aspect="auto")
    -plt.title("Result of convolution with gauss kernel (blurring filter)")
    -plt.show()
    -
    -sobel_kernel = np.array([[1, 2, 1],
    -                    [0, 0, 0], 
    -                    [-1, -2, -1]])
    -
    -filtered_image = convolve(image_of_cute_dog, sobel_kernel)
    -
    -plt.imshow(filtered_image, cmap="gray", vmin=0, vmax=255, aspect="auto")
    -plt.title("Result of convolution with sobel kernel (edge detection filter)")
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Layers

    - -

    The code below initialises global variables for readability and -describes the abstract class Layers. This is not important in order to -understand the CNN, but is benefitial for organizing the code neatly. -

    - - - -
    -
    -
    -
    -
    -
    import math
    -import autograd.numpy as np
    -from copy import deepcopy, copy
    -from autograd import grad
    -from typing import Callable
    -
    -# global variables for index readability
    -input_index = 0
    -node_index = 1
    -bias_index = 1
    -input_channel_index = 1
    -feature_maps_index = 1
    -height_index = 2
    -width_index = 3
    -kernel_feature_maps_index = 1
    -kernel_input_channels_index = 0
    -
    -
    -class Layer:
    -    def __init__(self, seed):
    -        self.seed = seed
    -
    -    def _feedforward(self):
    -        raise NotImplementedError
    -
    -    def _backpropagate(self):
    -        raise NotImplementedError
    -
    -    def _reset_weights(self, previous_nodes):
    -        raise NotImplementedError
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Convolution2DLayer: convolution in a hidden layer

    - -

    After establishing the foundational understanding of applying -convolution to spatial data, let us delve into the intricate workings -of a convolutional layer in a Convolutional Neural Network (CNN). The -primary function of convolution, as previously discussed, is to -extract pertinent information from images while simultaneously -decreasing the scale of our data. To initiate the image processing, we -shall begin by partitioning the images into color channels (unless the -image is grayscale), comprising three primary colors: red, green, and -blue. We will subsequently utilize trainable kernels to construct a -higher-dimensional encoding of each channel called feature -maps. Successive layers will receive these feature maps as inputs, -generating further encodings, albeit with reduced dimensions. The term -trainable kernels denotes the initialization of pre-defined -kernel-shaped weights, which we will then train via backpropagation, -similar to how weights are trained in a Feedforward Neural Network. -

    - -

    To ensure seamless integration between our implementation of the -convolutional layer and popular machine learning frameworks like -Tensorflow (Keras) and PyTorch, we have adopted a design pattern that -mirrors the construction of models using these APIs. This involves -implementing our convolutional layer as a Python class or object, -which allows for a more modular and flexible approach to building -neural networks. By structuring our code in this way, users can easily -incorporate our implementation into their existing machine learning -pipelines without having to make significant changes to their -codebase. Additionally, this design pattern promotes code reusability -and makes it easier to maintain and update our convolutional layer -implementation over time. -

    - -

    Note that the Convolution2DLayer takes in an activation function as a parameter, as it also performs non-linearity.

    - - - -
    -
    -
    -
    -
    -
    class Convolution2DLayer(Layer):
    -    def __init__(
    -        self,
    -        input_channels,
    -        feature_maps,
    -        kernel_height,
    -        kernel_width,
    -        v_stride,
    -        h_stride,
    -        pad,
    -        act_func: Callable,
    -        seed=None,
    -        reset_weights_independently=True,
    -    ):
    -        super().__init__(seed)
    -        self.input_channels = input_channels
    -        self.feature_maps = feature_maps
    -        self.kernel_height = kernel_height
    -        self.kernel_width = kernel_width
    -        self.v_stride = v_stride
    -        self.h_stride = h_stride
    -        self.pad = pad
    -        self.act_func = act_func
    -
    -        # such that the layer can be used on its own
    -        # outside of the CNN module
    -        if reset_weights_independently == True:
    -            self._reset_weights_independently()
    -
    -    def _feedforward(self, X_batch):
    -        # note that the shape of X_batch = [inputs, input_maps, img_height, img_width]
    -
    -        # pad the input batch
    -        X_batch_padded = self._padding(X_batch)
    -
    -        # calculate height_index and width_index after stride
    -        strided_height = int(np.ceil(X_batch.shape[height_index] / self.v_stride))
    -        strided_width = int(np.ceil(X_batch.shape[width_index] / self.h_stride))
    -
    -        # create output array
    -        output = np.ndarray(
    -            (
    -                X_batch.shape[input_index],
    -                self.feature_maps,
    -                strided_height,
    -                strided_width,
    -            )
    -        )
    -
    -        # save input and output for backpropagation
    -        self.X_batch_feedforward = X_batch
    -        self.output_shape = output.shape
    -
    -        # checking for errors, no need to look here :)
    -        self._check_for_errors()
    -
    -        # convolve input with kernel
    -        for img in range(X_batch.shape[input_index]):
    -            for chin in range(self.input_channels):
    -                for fmap in range(self.feature_maps):
    -                    out_h = 0
    -                    for h in range(0, X_batch.shape[height_index], self.v_stride):
    -                        out_w = 0
    -                        for w in range(0, X_batch.shape[width_index], self.h_stride):
    -                            output[img, fmap, out_h, out_w] = np.sum(
    -                                X_batch_padded[
    -                                    img,
    -                                    chin,
    -                                    h : h + self.kernel_height,
    -                                    w : w + self.kernel_width,
    -                                ]
    -                                * self.kernel[chin, fmap, :, :]
    -                            )
    -                            out_w += 1
    -                        out_h += 1
    -
    -        # Pay attention to the fact that we're not rotating the kernel by 180 degrees when filtering the image in
    -        # the convolutional layer, as convolution in terms of Machine Learning is a procedure known as cross-correlation
    -        # in image processing and signal processing
    -
    -        # return a
    -        return self.act_func(output / (self.kernel_height))
    -
    -    def _backpropagate(self, delta_term_next):
    -        # intiate matrices
    -        delta_term = np.zeros((self.X_batch_feedforward.shape))
    -        gradient_kernel = np.zeros((self.kernel.shape))
    -
    -        # pad input for convolution
    -        X_batch_padded = self._padding(self.X_batch_feedforward)
    -
    -        # Since an activation function is used at the output of the convolution layer, its derivative
    -        # has to be accounted for in the backpropagation -> as if ReLU was a layer on its own.
    -        act_derivative = derivate(self.act_func)
    -        delta_term_next = act_derivative(delta_term_next)
    -
    -        # fill in 0's for values removed by vertical stride in feedforward
    -        if self.v_stride > 1:
    -            v_ind = 1
    -            for i in range(delta_term_next.shape[height_index]):
    -                for j in range(self.v_stride - 1):
    -                    delta_term_next = np.insert(
    -                        delta_term_next, v_ind, 0, axis=height_index
    -                    )
    -                v_ind += self.v_stride
    -
    -        # fill in 0's for values removed by horizontal stride in feedforward
    -        if self.h_stride > 1:
    -            h_ind = 1
    -            for i in range(delta_term_next.shape[width_index]):
    -                for k in range(self.h_stride - 1):
    -                    delta_term_next = np.insert(
    -                        delta_term_next, h_ind, 0, axis=width_index
    -                    )
    -                h_ind += self.h_stride
    -
    -        # crops out 0-rows and 0-columns
    -        delta_term_next = delta_term_next[
    -            :,
    -            :,
    -            : self.X_batch_feedforward.shape[height_index],
    -            : self.X_batch_feedforward.shape[width_index],
    -        ]
    -
    -        # the gradient received from the next layer also needs to be padded
    -        delta_term_next = self._padding(delta_term_next)
    -
    -        # calculate delta term by convolving next delta term with kernel
    -        for img in range(self.X_batch_feedforward.shape[input_index]):
    -            for chin in range(self.input_channels):
    -                for fmap in range(self.feature_maps):
    -                    for h in range(self.X_batch_feedforward.shape[height_index]):
    -                        for w in range(self.X_batch_feedforward.shape[width_index]):
    -                            delta_term[img, chin, h, w] = np.sum(
    -                                delta_term_next[
    -                                    img,
    -                                    fmap,
    -                                    h : h + self.kernel_height,
    -                                    w : w + self.kernel_width,
    -                                ]
    -                                * np.rot90(np.rot90(self.kernel[chin, fmap, :, :]))
    -                            )
    -
    -        # calculate gradient for kernel for weight update
    -        # also via convolution
    -        for chin in range(self.input_channels):
    -            for fmap in range(self.feature_maps):
    -                for k_x in range(self.kernel_height):
    -                    for k_y in range(self.kernel_width):
    -                        gradient_kernel[chin, fmap, k_x, k_y] = np.sum(
    -                            X_batch_padded[
    -                                img,
    -                                chin,
    -                                h : h + self.kernel_height,
    -                                w : w + self.kernel_width,
    -                            ]
    -                            * delta_term_next[
    -                                img,
    -                                fmap,
    -                                h : h + self.kernel_height,
    -                                w : w + self.kernel_width,
    -                            ]
    -                        )
    -        # all kernels are updated with weight gradient of kernel
    -        self.kernel -= gradient_kernel
    -
    -        # return delta term
    -        return delta_term
    -
    -    def _padding(self, X_batch, batch_type="image"):
    -
    -        # same padding for images
    -        if self.pad == "same" and batch_type == "image":
    -            padded_height = X_batch.shape[height_index] + (self.kernel_height // 2) * 2
    -            padded_width = X_batch.shape[width_index] + (self.kernel_width // 2) * 2
    -            half_kernel_height = self.kernel_height // 2
    -            half_kernel_width = self.kernel_width // 2
    -
    -            # initialize padded array
    -            X_batch_padded = np.ndarray(
    -                (
    -                    X_batch.shape[input_index],
    -                    X_batch.shape[feature_maps_index],
    -                    padded_height,
    -                    padded_width,
    -                )
    -            )
    -
    -            # zero pad all images in X_batch
    -            for img in range(X_batch.shape[input_index]):
    -                padded_img = np.zeros(
    -                    (X_batch.shape[feature_maps_index], padded_height, padded_width)
    -                )
    -                padded_img[
    -                    :,
    -                    half_kernel_height : padded_height - half_kernel_height,
    -                    half_kernel_width : padded_width - half_kernel_width,
    -                ] = X_batch[img, :, :, :]
    -                X_batch_padded[img, :, :, :] = padded_img[:, :, :]
    -
    -            return X_batch_padded
    -
    -        # same padding for gradients
    -        elif self.pad == "same" and batch_type == "grad":
    -            padded_height = X_batch.shape[height_index] + (self.kernel_height // 2) * 2
    -            padded_width = X_batch.shape[width_index] + (self.kernel_width // 2) * 2
    -            half_kernel_height = self.kernel_height // 2
    -            half_kernel_width = self.kernel_width // 2
    -
    -            # initialize padded array
    -            delta_term_padded = np.zeros(
    -                (
    -                    X_batch.shape[input_index],
    -                    X_batch.shape[feature_maps_index],
    -                    padded_height,
    -                    padded_width,
    -                )
    -            )
    -
    -            # zero pad delta term
    -            delta_term_padded[
    -                :, :, : X_batch.shape[height_index], : X_batch.shape[width_index]
    -            ] = X_batch[:, :, :, :]
    -
    -            return delta_term_padded
    -
    -        else:
    -            return X_batch
    -
    -    def _reset_weights_independently(self):
    -        # sets seed to remove randomness inbetween runs
    -        if self.seed is not None:
    -            np.random.seed(self.seed)
    -
    -        # initializes kernel matrix
    -        self.kernel = np.ndarray(
    -            (
    -                self.input_channels,
    -                self.feature_maps,
    -                self.kernel_height,
    -                self.kernel_width,
    -            )
    -        )
    -
    -        # randomly initializes weights
    -        for chin in range(self.kernel.shape[kernel_input_channels_index]):
    -            for fmap in range(self.kernel.shape[kernel_feature_maps_index]):
    -                self.kernel[chin, fmap, :, :] = np.random.rand(
    -                    self.kernel_height, self.kernel_width
    -                )
    -
    -    def _reset_weights(self, previous_nodes):
    -        # sets weights
    -        self._reset_weights_independently()
    -
    -        # returns shape of output used for subsequent layer's weight initiation
    -        strided_height = int(
    -            np.ceil(previous_nodes.shape[height_index] / self.v_stride)
    -        )
    -        strided_width = int(np.ceil(previous_nodes.shape[width_index] / self.h_stride))
    -        next_nodes = np.ones(
    -            (
    -                previous_nodes.shape[input_index],
    -                self.feature_maps,
    -                strided_height,
    -                strided_width,
    -            )
    -        )
    -        return next_nodes / self.kernel_height
    -
    -    def _check_for_errors(self):
    -        if self.X_batch_feedforward.shape[input_channel_index] != self.input_channels:
    -            raise AssertionError(
    -                f"ERROR: Number of input channels in data ({self.X_batch_feedforward.shape[input_channel_index]}) is not equal to input channels in Convolution2DLayerOPT ({self.input_channels})! Please change the number of input channels of the Convolution2DLayer such that they are equal"
    -            )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Backpropagation in the convolutional layer

    - -

    As you may have noticed, we have not yet explained how the -backpropagation algorithm works in a convolutional layer. However, -having covered all other major details about convolutional layers, we -are now prepared to do so. It should come as no surprise that the -calculation of delta terms at each convolutional layer takes the form -of convolution. After the gradient has been propagated backwards -through the flattening layer, where it was reshaped into an -appropriate form, calculating the update value for the kernel is -simply a matter of convolving the output gradient with the input of -the layer for which we are updating the weights. For more detail, this -article serves as an excellent resource, see -https://pavisj.medium.com/convolutions-and-backpropagations-46026a8f5d2c -

    -

    Demonstration

    - -

    We can use the convolutional layer above to perform a simple convolution on an image of the now familiar cute dog.

    - - - -
    -
    -
    -
    -
    -
    import numpy as np
    -import imageio.v3 as imageio
    -import matplotlib.pyplot as plt
    -
    -def plot_convolution_result(X, layer):
    -    plt.imshow(X[0, 0, :, :], vmin=0, vmax=255, cmap="gray")
    -    plt.title("Original image")
    -    plt.colorbar()
    -    plt.show()
    -    conv_result = layer._feedforward(X)
    -    plt.title("Result of convolutional layer")
    -    plt.imshow(conv_result[0, 0, :, :], vmin=0, vmax=255, cmap="gray")
    -    plt.colorbar()
    -    plt.show()
    -
    -# create layer
    -layer = Convolution2DLayer(
    -    input_channels=3,
    -    feature_maps=1,
    -    kernel_height=4,
    -    kernel_width=4,
    -    v_stride=2,
    -    h_stride=2,
    -    pad="same",
    -    act_func=identity,
    -    seed=2023,
    -    )
    -
    -# read in image path, make data correct format
    -img_path = img_path = "data/IMG-2167.JPG"
    -image_of_cute_dog = imageio.imread(img_path)
    -image_shape = image_of_cute_dog.shape
    -image_of_cute_dog = image_of_cute_dog.reshape(1, image_shape[0], image_shape[1], image_shape[2])
    -image_of_cute_dog = image_of_cute_dog.transpose(0, 3, 1, 2)
    -
    -# plot the result of the convolution
    -plot_convolution_result(image_of_cute_dog, layer)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    We cobserve that the result has half the pixels on each axis due to -the fact that we've used a horizontal and vertical stride of 2. The -result of this convolution is not very insightfull, as the kernel has -completely random values for the first feedforward pass. However, as -we perform multiple forward and backward passes, the results of the -convolution should provide identifying features of the image it uses -for classification. -

    - -

    Note that image data usually comes in many different shapes and sizes, -but for our CNN we require the input data be formatted as \[Number of -inputs, input channels, input height, input width\]. Occasionally, the -data you come accross use will be formatted like this, but on many -occasions reshaping and transposing the dimensions is sadly necessary. -

    -

    Pooling Layer

    - -

    The pooling layer is another widely used type of layer in -convolutional neural networks that enables data downsampling to a more -manageable size. Despite recent technological advancements that allow -for convolution without excessive size reduction of the data, the -pooling layer still remains a fundamental component of convolutional -neural networks. It can be used before, after, or in between -convolutional layers, although finding the optimal placement of layers -and network depth requires experimentation to achieve the best -performance for a given problem. The code we provide allows you to -perform two types of pooling known as max pooling and average pooling. -

    - - - -
    -
    -
    -
    -
    -
    class Pooling2DLayer(Layer):
    -    def __init__(
    -        self,
    -        kernel_height,
    -        kernel_width,
    -        v_stride,
    -        h_stride,
    -        pooling="max",
    -        seed=None,
    -    ):
    -        super().__init__(seed)
    -        self.kernel_height = kernel_height
    -        self.kernel_width = kernel_width
    -        self.v_stride = v_stride
    -        self.h_stride = h_stride
    -        self.pooling = pooling
    -
    -    def _feedforward(self, X_batch):
    -        # Saving the input for use in the backwardpass
    -        self.X_batch_feedforward = X_batch
    -
    -        # check if user is silly
    -        self._check_for_errors()
    -
    -        # Computing the size of the feature maps based on kernel size and the stride parameter
    -        strided_height = (
    -            X_batch.shape[height_index] - self.kernel_height
    -        ) // self.v_stride + 1
    -        if X_batch.shape[height_index] == X_batch.shape[width_index]:
    -            strided_width = strided_height
    -        else:
    -            strided_width = (
    -                X_batch.shape[width_index] - self.kernel_width
    -            ) // self.h_stride + 1
    -
    -        # initialize output array
    -        output = np.ndarray(
    -            (
    -                X_batch.shape[input_index],
    -                X_batch.shape[feature_maps_index],
    -                strided_height,
    -                strided_width,
    -            )
    -        )
    -
    -        # select pooling action, either max or average pooling
    -        if self.pooling == "max":
    -            self.pooling_action = np.max
    -        elif self.pooling == "average":
    -            self.pooling_action = np.mean
    -
    -        # pool based on kernel size and stride
    -        for img in range(output.shape[input_index]):
    -            for fmap in range(output.shape[feature_maps_index]):
    -                for h in range(strided_height):
    -                    for w in range(strided_width):
    -                        output[img, fmap, h, w] = self.pooling_action(
    -                            X_batch[
    -                                img,
    -                                fmap,
    -                                (h * self.v_stride) : (h * self.v_stride)
    -                                + self.kernel_height,
    -                                (w * self.h_stride) : (w * self.h_stride)
    -                                + self.kernel_width,
    -                            ]
    -                        )
    -
    -        # output for feedforward in next layer
    -        return output
    -
    -    def _backpropagate(self, delta_term_next):
    -        # initiate delta term array
    -        delta_term = np.zeros((self.X_batch_feedforward.shape))
    -
    -        for img in range(delta_term_next.shape[input_index]):
    -            for fmap in range(delta_term_next.shape[feature_maps_index]):
    -                for h in range(0, delta_term_next.shape[height_index], self.v_stride):
    -                    for w in range(
    -                        0, delta_term_next.shape[width_index], self.h_stride
    -                    ):
    -                        # max pooling
    -                        if self.pooling == "max":
    -                            # get window
    -                            window = self.X_batch_feedforward[
    -                                img,
    -                                fmap,
    -                                h : h + self.kernel_height,
    -                                w : w + self.kernel_width,
    -                            ]
    -
    -                            # find max values indices in window
    -                            max_h, max_w = np.unravel_index(
    -                                window.argmax(), window.shape
    -                            )
    -
    -                            # set values in new, upsampled delta term
    -                            delta_term[
    -                                img,
    -                                fmap,
    -                                (h + max_h),
    -                                (w + max_w),
    -                            ] += delta_term_next[img, fmap, h, w]
    -
    -                        # average pooling
    -                        if self.pooling == "average":
    -                            delta_term[
    -                                img,
    -                                fmap,
    -                                h : h + self.kernel_height,
    -                                w : w + self.kernel_width,
    -                            ] = (
    -                                delta_term_next[img, fmap, h, w]
    -                                / self.kernel_height
    -                                / self.kernel_width
    -                            )
    -        # returns input to backpropagation in previous layer
    -        return delta_term
    -
    -    def _reset_weights(self, previous_nodes):
    -        # calculate strided height, strided width
    -        strided_height = (
    -            previous_nodes.shape[height_index] - self.kernel_height
    -        ) // self.v_stride + 1
    -        if previous_nodes.shape[height_index] == previous_nodes.shape[width_index]:
    -            strided_width = strided_height
    -        else:
    -            strided_width = (
    -                previous_nodes.shape[width_index] - self.kernel_width
    -            ) // self.h_stride + 1
    -
    -        # initiate output array
    -        output = np.ones(
    -            (
    -                previous_nodes.shape[input_index],
    -                previous_nodes.shape[feature_maps_index],
    -                strided_height,
    -                strided_width,
    -            )
    -        )
    -
    -        # returns output with shape used for reset weights in next layer
    -        return output
    -
    -    def _check_for_errors(self):
    -        # check if input is smaller than kernel size -> error
    -        assert (
    -            self.X_batch_feedforward.shape[width_index] >= self.kernel_width
    -        ), f"ERROR: Pooling kernel width_index ({self.kernel_width}) larger than data width_index ({self.X_batch_feedforward.input.shape[2]}), please lower the kernel width_index of the Pooling2DLayer"
    -        assert (
    -            self.X_batch_feedforward.shape[height_index] >= self.kernel_height
    -        ), f"ERROR: Pooling kernel height_index ({self.kernel_height}) larger than data height_index ({self.X_batch_feedforward.input.shape[3]}), please lower the kernel height_index of the Pooling2DLayer"
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Flattening Layer

    - -

    Before we can begin building our first CNN model, we need to introduce -the flattening layer. As its name suggests, the flattening layer -transforms the data into a one-dimensional vector that can be fed into -the feedforward layers of our network. This layer plays a crucial role -in preparing the data for further processing in the -network. Additionally, the flattening layer is responsible for -reshaping the gradient to the proper shape during -backpropagation. This ensures that the kernels are correctly updated, -allowing for effective learning in the network. -

    - - - -
    -
    -
    -
    -
    -
    class FlattenLayer(Layer):
    -    def __init__(self, act_func=LRELU, seed=None):
    -        super().__init__(seed)
    -        self.act_func = act_func
    -
    -    def _feedforward(self, X_batch):
    -        # save input for backpropagation
    -        self.X_batch_feedforward_shape = X_batch.shape
    -        # Remember, the data has the following shape: (I, FM, H, W, ) in the convolutional layers
    -        # whilst the data has the shape (I, FM * H * W) in the fully connected layers
    -        # I = Inputs, FM = Feature Maps, H = Height and W = Width.
    -        X_batch = X_batch.reshape(
    -            X_batch.shape[input_index],
    -            X_batch.shape[feature_maps_index]
    -            * X_batch.shape[height_index]
    -            * X_batch.shape[width_index],
    -        )
    -
    -        # add bias to a
    -        self.z_matrix = X_batch
    -        bias = np.ones((X_batch.shape[input_index], 1)) * 0.01
    -        self.a_matrix = np.hstack([bias, X_batch])
    -
    -        # return a, the input to feedforward in next layer
    -        return self.a_matrix
    -
    -    def _backpropagate(self, weights_next, delta_term_next):
    -        activation_derivative = derivate(self.act_func)
    -
    -        # calculate delta term
    -        delta_term = (
    -            weights_next[bias_index:, :] @ delta_term_next.T
    -        ).T * activation_derivative(self.z_matrix)
    -
    -        # FlattenLayer does not update weights
    -        # reshapes delta layer to convolutional layer data format [Input, Feature_Maps, Height, Width]
    -        return delta_term.reshape(self.X_batch_feedforward_shape)
    -
    -    def _reset_weights(self, previous_nodes):
    -        # note that the previous nodes to the FlattenLayer are from the convolutional layers
    -        previous_nodes = previous_nodes.reshape(
    -            previous_nodes.shape[input_index],
    -            previous_nodes.shape[feature_maps_index]
    -            * previous_nodes.shape[height_index]
    -            * previous_nodes.shape[width_index],
    -        )
    -
    -        # return shape used in reset_weights in next layer
    -        return previous_nodes.shape[node_index]
    -
    -    def get_prev_a(self):
    -        return self.a_matrix
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Fully Connected Layers

    - -

    Finally, the result from the flatten layer will pass to a series of -fully connected layers, which function as a normal feed forward neural -network. The fully connected layers are split into two classes; -FullyConnectedLayer which acts as a hidden layer, and OutputLayer, -which acts as the single output layer at the end of the CNN. If one -wishes to use this codebase to construct a normal feed forward neural -network, it must start with a FlattenLayer due to techincal details -regarding weight intitialization. However many FullyConnectedLayers -can be added to the CNN, and in each layer the amount of nodes, which -activation function and scheduler to use can be specified. In -practice, the scheduler will be specified in the CNN object -initialization, and inherited if no other scheduler is specified. -

    - - - -
    -
    -
    -
    -
    -
    class FullyConnectedLayer(Layer):
    -    # FullyConnectedLayer per default uses LRELU and Adam scheduler
    -    # with an eta of 0.0001, rho of 0.9 and rho2 of 0.999
    -    def __init__(
    -        self,
    -        nodes: int,
    -        act_func: Callable = LRELU,
    -        scheduler: Scheduler = Adam(eta=1e-4, rho=0.9, rho2=0.999),
    -        seed: int = None,
    -    ):
    -        super().__init__(seed)
    -        self.nodes = nodes
    -        self.act_func = act_func
    -        self.scheduler_weight = copy(scheduler)
    -        self.scheduler_bias = copy(scheduler)
    -
    -        # initiate matrices for later
    -        self.weights = None
    -        self.a_matrix = None
    -        self.z_matrix = None
    -
    -    def _feedforward(self, X_batch):
    -        # calculate z
    -        self.z_matrix = X_batch @ self.weights
    -
    -        # calculate a, add bias
    -        bias = np.ones((X_batch.shape[input_index], 1)) * 0.01
    -        self.a_matrix = self.act_func(self.z_matrix)
    -        self.a_matrix = np.hstack([bias, self.a_matrix])
    -
    -        # return a, the input for feedforward in next layer
    -        return self.a_matrix
    -
    -    def _backpropagate(self, weights_next, delta_term_next, a_previous, lam):
    -        # take the derivative of the activation function
    -        activation_derivative = derivate(self.act_func)
    -
    -        # calculate the delta term
    -        delta_term = (
    -            weights_next[bias_index:, :] @ delta_term_next.T
    -        ).T * activation_derivative(self.z_matrix)
    -
    -        # intitiate matrix to store gradient
    -        # note that we exclude the bias term, which we will calculate later
    -        gradient_weights = np.zeros(
    -            (
    -                a_previous.shape[input_index],
    -                a_previous.shape[node_index] - bias_index,
    -                delta_term.shape[node_index],
    -            )
    -        )
    -
    -        # calculate gradient = delta term * previous a
    -        for i in range(len(delta_term)):
    -            gradient_weights[i, :, :] = np.outer(
    -                a_previous[i, bias_index:], delta_term[i, :]
    -            )
    -
    -        # sum the gradient, divide by input_index
    -        gradient_weights = np.mean(gradient_weights, axis=input_index)
    -        # for the bias gradient we do not multiply by previous a
    -        gradient_bias = np.mean(delta_term, axis=input_index).reshape(
    -            1, delta_term.shape[node_index]
    -        )
    -
    -        # regularization term
    -        gradient_weights += self.weights[bias_index:, :] * lam
    -
    -        # send gradients into scheduler
    -        # returns update matrix which will be used to update the weights and bias
    -        update_matrix = np.vstack(
    -            [
    -                self.scheduler_bias.update_change(gradient_bias),
    -                self.scheduler_weight.update_change(gradient_weights),
    -            ]
    -        )
    -
    -        # update weights
    -        self.weights -= update_matrix
    -
    -        # return weights and delta term, input for backpropagation in previous layer
    -        return self.weights, delta_term
    -
    -    def _reset_weights(self, previous_nodes):
    -        # sets seed to remove randomness inbetween runs
    -        if self.seed is not None:
    -            np.random.seed(self.seed)
    -
    -        # add bias, initiate random weights
    -        bias = 1
    -        self.weights = np.random.randn(previous_nodes + bias, self.nodes)
    -
    -        # returns number of nodes, used for reset_weights in next layer
    -        return self.nodes
    -
    -    def _reset_scheduler(self):
    -        # resets scheduler per epoch
    -        self.scheduler_weight.reset()
    -        self.scheduler_bias.reset()
    -
    -    def get_prev_a(self):
    -        # returns a matrix, used in backpropagation
    -        return self.a_matrix
    -
    -
    -class OutputLayer(FullyConnectedLayer):
    -    def __init__(
    -        self,
    -        nodes: int,
    -        output_func: Callable = LRELU,
    -        cost_func: Callable = CostCrossEntropy,
    -        scheduler: Scheduler = Adam(eta=1e-4, rho=0.9, rho2=0.999),
    -        seed: int = None,
    -    ):
    -        super().__init__(nodes, output_func, copy(scheduler), seed)
    -        self.cost_func = cost_func
    -
    -        # initiate matrices for later
    -        self.weights = None
    -        self.a_matrix = None
    -        self.z_matrix = None
    -
    -        # decides if the output layer performs binary or multi-class classification
    -        self._set_pred_format()
    -
    -    def _feedforward(self, X_batch: np.ndarray):
    -        # calculate a, z
    -        # note that bias is not added as this would create an extra output class
    -        self.z_matrix = X_batch @ self.weights
    -        self.a_matrix = self.act_func(self.z_matrix)
    -
    -        # returns prediction
    -        return self.a_matrix
    -
    -    def _backpropagate(self, target, a_previous, lam):
    -        # note that in the OutputLayer the activation function is the output function
    -        activation_derivative = derivate(self.act_func)
    -
    -        # calculate output delta terms
    -        # for multi-class or binary classification
    -        if self.pred_format == "Multi-class":
    -            delta_term = self.a_matrix - target
    -        else:
    -            cost_func_derivative = grad(self.cost_func(target))
    -            delta_term = activation_derivative(self.z_matrix) * cost_func_derivative(
    -                self.a_matrix
    -            )
    -
    -        # intiate matrix that stores gradient
    -        gradient_weights = np.zeros(
    -            (
    -                a_previous.shape[input_index],
    -                a_previous.shape[node_index] - bias_index,
    -                delta_term.shape[node_index],
    -            )
    -        )
    -
    -        # calculate gradient = delta term * previous a
    -        for i in range(len(delta_term)):
    -            gradient_weights[i, :, :] = np.outer(
    -                a_previous[i, bias_index:], delta_term[i, :]
    -            )
    -
    -        # sum the gradient, divide by input_index
    -        gradient_weights = np.mean(gradient_weights, axis=input_index)
    -        # for the bias gradient we do not multiply by previous a
    -        gradient_bias = np.mean(delta_term, axis=input_index).reshape(
    -            1, delta_term.shape[node_index]
    -        )
    -
    -        # regularization term
    -        gradient_weights += self.weights[bias_index:, :] * lam
    -
    -        # send gradients into scheduler
    -        # returns update matrix which will be used to update the weights and bias
    -        update_matrix = np.vstack(
    -            [
    -                self.scheduler_bias.update_change(gradient_bias),
    -                self.scheduler_weight.update_change(gradient_weights),
    -            ]
    -        )
    -
    -        # update weights
    -        self.weights -= update_matrix
    -
    -        # return weights and delta term, input for backpropagation in previous layer
    -        return self.weights, delta_term
    -
    -    def _reset_weights(self, previous_nodes):
    -        # sets seed to remove randomness inbetween runs
    -        if self.seed is not None:
    -            np.random.seed(self.seed)
    -
    -        # add bias, initiate random weights
    -        bias = 1
    -        self.weights = np.random.rand(previous_nodes + bias, self.nodes)
    -
    -        # returns number of nodes, used for reset_weights in next layer
    -        return self.nodes
    -
    -    def _reset_scheduler(self):
    -        # resets scheduler per epoch
    -        self.scheduler_weight.reset()
    -        self.scheduler_bias.reset()
    -
    -    def _set_pred_format(self):
    -        # sets prediction format to either regression, binary or multi-class classification
    -        if self.act_func.__name__ is None or self.act_func.__name__ == "identity":
    -            self.pred_format = "Regression"
    -        elif self.act_func.__name__ == "sigmoid" or self.act_func.__name__ == "tanh":
    -            self.pred_format = "Binary"
    -        else:
    -            self.pred_format = "Multi-class"
    -
    -    def get_pred_format(self):
    -        # returns format of prediction
    -        return self.pred_format
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Optimized Convolution2DLayer

    - -

    For our CNN, we have also implemented an optimized version of the -Convolution2DLayer, Convolution2DLayerOPT, which runs much faster. See -VII. Remarks for discussion. This layer will per default be used by -the CNN due to its computational advantages, but is much less -readable. We've documented it such that specially interested students -can understand the principles behind it, but it is not recommended to -read. In short, we reshape and transpose parts of the image such that -the convolutional operation can be swapped out for a simple matrix -multiplication. -

    - - - -
    -
    -
    -
    -
    -
    class Convolution2DLayerOPT(Convolution2DLayer):
    -    """
    -    Am optimized version of the convolution layer above which
    -    utilizes an approach of extracting windows of size equivalent
    -    in size to the filter. The convoution is then performed on those
    -    windows instead of a full feature map.
    -    """
    -
    -    def __init__(
    -        self,
    -        input_channels,
    -        feature_maps,
    -        kernel_height,
    -        kernel_width,
    -        v_stride,
    -        h_stride,
    -        pad,
    -        act_func: Callable,
    -        seed=None,
    -        reset_weights_independently=True,
    -    ):
    -        super().__init__(
    -            input_channels,
    -            feature_maps,
    -            kernel_height,
    -            kernel_width,
    -            v_stride,
    -            h_stride,
    -            pad,
    -            act_func,
    -            seed,
    -        )
    -        # true if layer is used outside of CNN
    -        if reset_weights_independently == True:
    -            self._reset_weights_independently()
    -
    -    def _feedforward(self, X_batch):
    -        # The optimized _feedforward method is difficult to understand but computationally more efficient
    -        # for a more "by the book" approach, please look at the _feedforward method of Convolution2DLayer
    -
    -        # save the input for backpropagation
    -        self.X_batch_feedforward = X_batch
    -
    -        # check that there are the correct amount of input channels
    -        self._check_for_errors()
    -
    -        # calculate new shape after stride
    -        strided_height = int(np.ceil(X_batch.shape[height_index] / self.v_stride))
    -        strided_width = int(np.ceil(X_batch.shape[width_index] / self.h_stride))
    -
    -        # get windows of the image for more computationally efficient convolution
    -        # the idea is that we want to align the dimensions that we wish to matrix
    -        # multiply, then use a simple matrix multiplication instead of convolution.
    -        # then, we reshape the size back to its intended shape
    -        windows = self._extract_windows(X_batch)
    -        windows = windows.transpose(1, 0, 2, 3, 4).reshape(
    -            X_batch.shape[input_index],
    -            strided_height * strided_width,
    -            -1,
    -        )
    -
    -        # reshape the kernel for more computationally efficient convolution
    -        kernel = self.kernel
    -        kernel = kernel.transpose(0, 2, 3, 1).reshape(
    -            kernel.shape[kernel_input_channels_index]
    -            * kernel.shape[height_index]
    -            * kernel.shape[width_index],
    -            -1,
    -        )
    -
    -        # use simple matrix calculation to obtain output
    -        output = (
    -            (windows @ kernel)
    -            .reshape(
    -                X_batch.shape[input_index],
    -                strided_height,
    -                strided_width,
    -                -1,
    -            )
    -            .transpose(0, 3, 1, 2)
    -        )
    -
    -        # The output is reshaped and rearranged to appropriate shape
    -        return self.act_func(
    -            output / (self.kernel_height * X_batch.shape[feature_maps_index])
    -        )
    -
    -    def _backpropagate(self, delta_term_next):
    -        # The optimized _backpropagate method is difficult to understand but computationally more efficient
    -        # for a more "by the book" approach, please look at the _backpropagate method of Convolution2DLayer
    -        act_derivative = derivate(self.act_func)
    -        delta_term_next = act_derivative(delta_term_next)
    -
    -        # calculate strided dimensions
    -        strided_height = int(
    -            np.ceil(self.X_batch_feedforward.shape[height_index] / self.v_stride)
    -        )
    -        strided_width = int(
    -            np.ceil(self.X_batch_feedforward.shape[width_index] / self.h_stride)
    -        )
    -
    -        # copy kernel
    -        kernel = self.kernel
    -
    -        # get windows, reshape for matrix multiplication
    -        windows = self._extract_windows(self.X_batch_feedforward, "image").reshape(
    -            self.X_batch_feedforward.shape[input_index]
    -            * strided_height
    -            * strided_width,
    -            -1,
    -        )
    -
    -        # initialize output gradient, reshape and transpose into correct shape
    -        # for matrix multiplication
    -        output_grad_tr = delta_term_next.transpose(0, 2, 3, 1).reshape(
    -            self.X_batch_feedforward.shape[input_index]
    -            * strided_height
    -            * strided_width,
    -            -1,
    -        )
    -
    -        # calculate gradient kernel via simple matrix multiplication and reshaping
    -        gradient_kernel = (
    -            (windows.T @ output_grad_tr)
    -            .reshape(
    -                kernel.shape[kernel_input_channels_index],
    -                kernel.shape[height_index],
    -                kernel.shape[width_index],
    -                kernel.shape[kernel_feature_maps_index],
    -            )
    -            .transpose(0, 3, 1, 2)
    -        )
    -
    -        # for computing the input gradient
    -        windows_out, upsampled_height, upsampled_width = self._extract_windows(
    -            delta_term_next, "grad"
    -        )
    -
    -        # calculate new window dimensions
    -        new_windows_first_dim = (
    -            self.X_batch_feedforward.shape[input_index]
    -            * upsampled_height
    -            * upsampled_width
    -        )
    -        # ceil allows for various asymmetric kernels
    -        new_windows_sec_dim = int(np.ceil(windows_out.size / new_windows_first_dim))
    -
    -        # reshape for matrix multiplication
    -        windows_out = windows_out.transpose(1, 0, 2, 3, 4).reshape(
    -            new_windows_first_dim, new_windows_sec_dim
    -        )
    -
    -        # reshape for matrix multiplication
    -        kernel_reshaped = kernel.reshape(self.input_channels, -1)
    -
    -        # calculating input gradient for next convolutional layer
    -        input_grad = (windows_out @ kernel_reshaped.T).reshape(
    -            self.X_batch_feedforward.shape[input_index],
    -            upsampled_height,
    -            upsampled_width,
    -            kernel.shape[kernel_input_channels_index],
    -        )
    -        input_grad = input_grad.transpose(0, 3, 1, 2)
    -
    -        # Update the weights in the kernel
    -        self.kernel -= gradient_kernel
    -
    -        # Output the gradient to propagate backwards
    -        return input_grad
    -
    -    def _extract_windows(self, X_batch, batch_type="image"):
    -        """
    -        Receives as input the X_batch with shape (inputs, feature_maps, image_height, image_width)
    -        and extract windows of size kernel_height * kernel_width for every image and every feature_map.
    -        It then returns an np.ndarray of shape (image_height * image_width, inputs, feature_maps, kernel_height, kernel_width)
    -        which will be used either to filter the images in feedforward or to calculate the gradient.
    -        """
    -
    -        # initialize list of windows
    -        windows = []
    -
    -        if batch_type == "image":
    -            # pad the images
    -            X_batch_padded = self._padding(X_batch, batch_type="image")
    -            img_height, img_width = X_batch_padded.shape[2:]
    -            # For each location in the image...
    -            for h in range(
    -                0,
    -                X_batch.shape[height_index],
    -                self.v_stride,
    -            ):
    -                for w in range(
    -                    0,
    -                    X_batch.shape[width_index],
    -                    self.h_stride,
    -                ):
    -                    # ...obtain an image patch of the original size (strided)
    -
    -                    # get window
    -                    window = X_batch_padded[
    -                        :,
    -                        :,
    -                        h : h + self.kernel_height,
    -                        w : w + self.kernel_width,
    -                    ]
    -
    -                    # append to list of windows
    -                    windows.append(window)
    -
    -            # return numpy array instead of list
    -            return np.stack(windows)
    -
    -        # In order to be able to perform backprogagation by the method of window extraction,
    -        # here is a modified approach to extracting the windows which allow for the necessary
    -        # upsampling of the gradient in case the on of the stride parameters is larger than one.
    -
    -        if batch_type == "grad":
    -
    -            # In the case of one of the stride parameters being odd, we have to take some
    -            # extra care in calculating the upsampled size of X_batch. We solve this
    -            # by simply flooring the result of dividing stride by 2.
    -            if self.v_stride < 2 or self.v_stride % 2 == 0:
    -                v_stride = 0
    -            else:
    -                v_stride = int(np.floor(self.v_stride / 2))
    -
    -            if self.h_stride < 2 or self.h_stride % 2 == 0:
    -                h_stride = 0
    -            else:
    -                h_stride = int(np.floor(self.h_stride / 2))
    -
    -            upsampled_height = (X_batch.shape[height_index] * self.v_stride) - v_stride
    -            upsampled_width = (X_batch.shape[width_index] * self.h_stride) - h_stride
    -
    -            # When upsampling, we need to insert rows and columns filled with zeros
    -            # into each feature map. How many of those we have to insert is purely
    -            # dependant on the value of stride parameter in the vertical and horizontal
    -            # direction.
    -            if self.v_stride > 1:
    -                v_ind = 1
    -                for i in range(X_batch.shape[height_index]):
    -                    for j in range(self.v_stride - 1):
    -                        X_batch = np.insert(X_batch, v_ind, 0, axis=height_index)
    -                    v_ind += self.v_stride
    -
    -            if self.h_stride > 1:
    -                h_ind = 1
    -                for i in range(X_batch.shape[width_index]):
    -                    for k in range(self.h_stride - 1):
    -                        X_batch = np.insert(X_batch, h_ind, 0, axis=width_index)
    -                    h_ind += self.h_stride
    -
    -            # Since the insertion of zero-filled rows and columns isn't perfect, we have
    -            # to assure that the resulting feature maps will have the expected upsampled height
    -            # and width by cutting them og at desired dimensions.
    -
    -            X_batch = X_batch[:, :, :upsampled_height, :upsampled_width]
    -
    -            X_batch_padded = self._padding(X_batch, batch_type="grad")
    -
    -            # initialize list of windows
    -            windows = []
    -
    -            # For each location in the image...
    -            for h in range(
    -                0,
    -                X_batch.shape[height_index],
    -                self.v_stride,
    -            ):
    -                for w in range(
    -                    0,
    -                    X_batch.shape[width_index],
    -                    self.h_stride,
    -                ):
    -                    # ...obtain an image patch of the original size (strided)
    -
    -                    # get window
    -                    window = X_batch_padded[
    -                        :, :, h : h + self.kernel_height, w : w + self.kernel_width
    -                    ]
    -
    -                    # append window to list
    -                    windows.append(window)
    -
    -            # return numpy array, unsampled dimensions
    -            return np.stack(windows), upsampled_height, upsampled_width
    -
    -    def _check_for_errors(self):
    -        # compares input channels of data to input channels of Convolution2DLayer
    -        if self.X_batch_feedforward.shape[input_channel_index] != self.input_channels:
    -            raise AssertionError(
    -                f"ERROR: Number of input channels in data ({self.X_batch_feedforward.shape[input_channel_index]}) is not equal to input channels in Convolution2DLayerOPT ({self.input_channels})! Please change the number of input channels of the Convolution2DLayer such that they are equal"
    -            )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    The Convolutional Neural Network (CNN)

    - -

    Finally, we present the code for the CNN. The CNN class organizes all the layers, and allows for training on image data.

    - - - -
    -
    -
    -
    -
    -
    import math
    -import autograd.numpy as np
    -import sys
    -import warnings
    -from autograd import grad, elementwise_grad
    -from random import random, seed
    -from copy import deepcopy
    -from typing import Tuple, Callable
    -from sklearn.utils import resample
    -
    -warnings.simplefilter("error")
    -
    -
    -class CNN:
    -    def __init__(
    -        self,
    -        cost_func: Callable = CostCrossEntropy,
    -        scheduler: Scheduler = Adam(eta=1e-4, rho=0.9, rho2=0.999),
    -        seed: int = None,
    -    ):
    -        """
    -        Description:
    -        ------------
    -            Instantiates CNN object
    -
    -        Parameters:
    -        ------------
    -            I   output_func (costFunctions) cost function for feed forward neural network part of CNN,
    -                such as "CostLogReg", "CostOLS" or "CostCrossEntropy"
    -
    -            II  scheduler (Scheduler) optional parameter, default set to Adam. Can also be set to other
    -                schedulers such as AdaGrad, Momentum, RMS_prop and Constant. Note that schedulers have
    -                to be instantiated first with proper parameters (for example eta, rho and rho2 for Adam)
    -
    -            III seed (int) used for seeding all random operations
    -        """
    -        self.layers = list()
    -        self.cost_func = cost_func
    -        self.scheduler = scheduler
    -        self.schedulers_weight = list()
    -        self.schedulers_bias = list()
    -        self.seed = seed
    -        self.pred_format = None
    -
    -    def add_FullyConnectedLayer(
    -        self, nodes: int, act_func=LRELU, scheduler=None
    -    ) -> None:
    -        """
    -        Description:
    -        ------------
    -            Add a FullyConnectedLayer to the CNN, i.e. a hidden layer in the feed forward neural
    -            network part of the CNN. Often called a Dense layer in literature
    -
    -        Parameters:
    -        ------------
    -            I   nodes (int) number of nodes in FullyConnectedLayer
    -            II  act_func (activationFunctions) activation function of FullyConnectedLayer,
    -                such as "sigmoid", "RELU", "LRELU", "softmax" or "identity"
    -            III scheduler (Scheduler) optional parameter, default set to Adam. Can also be set to other
    -                schedulers such as AdaGrad, Momentum, RMS_prop and Constant
    -        """
    -        assert self.layers, "FullyConnectedLayer should follow FlattenLayer in CNN"
    -
    -        if scheduler is None:
    -            scheduler = self.scheduler
    -
    -        layer = FullyConnectedLayer(nodes, act_func, scheduler, self.seed)
    -        self.layers.append(layer)
    -
    -    def add_OutputLayer(self, nodes: int, output_func=sigmoid, scheduler=None) -> None:
    -        """
    -        Description:
    -        ------------
    -            Add an OutputLayer to the CNN, i.e. a the final layer in the feed forward neural
    -            network part of the CNN
    -
    -        Parameters:
    -        ------------
    -            I   nodes (int) number of nodes in OutputLayer. Set nodes=1 for binary classification and
    -                nodes = number of classes for multi-class classification
    -            II  output_func (activationFunctions) activation function for the output layer, such as
    -                "identity" for regression, "sigmoid" for binary classification and "softmax" for multi-class
    -                classification
    -            III scheduler (Scheduler) optional parameter, default set to Adam. Can also be set to other
    -                schedulers such as AdaGrad, Momentum, RMS_prop and Constant
    -        """
    -        assert self.layers, "OutputLayer should follow FullyConnectedLayer in CNN"
    -
    -        if scheduler is None:
    -            scheduler = self.scheduler
    -
    -        output_layer = OutputLayer(
    -            nodes, output_func, self.cost_func, scheduler, self.seed
    -        )
    -        self.layers.append(output_layer)
    -        self.pred_format = output_layer.get_pred_format()
    -
    -    def add_FlattenLayer(self, act_func=LRELU) -> None:
    -        """
    -        Description:
    -        ------------
    -            Add a FlattenLayer to the CNN, which flattens the image data such that it is formatted to
    -            be used in the feed forward neural network part of the CNN
    -        """
    -        self.layers.append(FlattenLayer(act_func=act_func, seed=self.seed))
    -
    -    def add_Convolution2DLayer(
    -        self,
    -        input_channels=1,
    -        feature_maps=1,
    -        kernel_height=3,
    -        kernel_width=3,
    -        v_stride=1,
    -        h_stride=1,
    -        pad="same",
    -        act_func=LRELU,
    -        optimized=True,
    -    ) -> None:
    -        """
    -        Description:
    -        ------------
    -            Add a Convolution2DLayer to the CNN, i.e. a convolutional layer with a 2 dimensional kernel. Should be
    -            the first layer added to the CNN
    -
    -        Parameters:
    -        ------------
    -            I   input_channels (int) specifies amount of input channels. For monochrome images, use input_channels
    -                = 1, and input_channels = 3 for colored images, where each channel represents one of R, G and B
    -            II  feature_maps (int) amount of feature maps in CNN
    -            III kernel_height (int) height of the kernel, also called 'convolutional filter' in literature
    -            IV  kernel_width (int) width of the kernel, also called 'convolutional filter' in literature
    -            V   v_stride (int) value of vertical stride for dimentionality reduction
    -            VI  h_stride (int) value of horizontal stride for dimentionality reduction
    -            VII pad (str) default = "same" ensures output size is the same as input size (given stride=1)
    -           VIII act_func (activationFunctions) default = "LRELU", nonlinear activation function
    -             IX optimized (bool) default = True, uses Convolution2DLayerOPT if True which is much faster when
    -                compared to Convolution2DLayer, which is a more straightforward, understandable implementation
    -        """
    -        if optimized:
    -            conv_layer = Convolution2DLayerOPT(
    -                input_channels,
    -                feature_maps,
    -                kernel_height,
    -                kernel_width,
    -                v_stride,
    -                h_stride,
    -                pad,
    -                act_func,
    -                self.seed,
    -                reset_weights_independently=False,
    -            )
    -        else:
    -            conv_layer = Convolution2DLayer(
    -                input_channels,
    -                feature_maps,
    -                kernel_height,
    -                kernel_width,
    -                v_stride,
    -                h_stride,
    -                pad,
    -                act_func,
    -                self.seed,
    -                reset_weights_independently=False,
    -            )
    -        self.layers.append(conv_layer)
    -
    -    def add_PoolingLayer(
    -        self, kernel_height=2, kernel_width=2, v_stride=1, h_stride=1, pooling="max"
    -    ) -> None:
    -        """
    -        Description:
    -        ------------
    -            Add a Pooling2DLayer to the CNN, i.e. a pooling layer that reduces the dimentionality of
    -            the image data. It is not necessary to use a Pooling2DLayer when creating a CNN, but it
    -            can be used to speed up the training
    -
    -        Parameters:
    -        ------------
    -            I   kernel_height (int) height of the kernel used for pooling
    -            II  kernel_width (int) width of the kernel used for pooling
    -            III v_stride (int) value of vertical stride for dimentionality reduction
    -            IV  h_stride (int) value of horizontal stride for dimentionality reduction
    -            V   pooling (str) either "max" or "average", describes type of pooling performed
    -        """
    -        pooling_layer = Pooling2DLayer(
    -            kernel_height, kernel_width, v_stride, h_stride, pooling, self.seed
    -        )
    -        self.layers.append(pooling_layer)
    -
    -    def fit(
    -        self,
    -        X: np.ndarray,
    -        t: np.ndarray,
    -        epochs: int = 100,
    -        lam: float = 0,
    -        batches: int = 1,
    -        X_val: np.ndarray = None,
    -        t_val: np.ndarray = None,
    -    ) -> dict:
    -        """
    -        Description:
    -        ------------
    -            Fits the CNN to input X for a given amount of epochs. Performs feedforward and backpropagation passes,
    -            can utilize batches, regulariziation and validation if desired.
    -
    -        Parameters:
    -        ------------
    -            X (numpy array) with input data in format [images, input channels,
    -            image height, image_width]
    -            t (numpy array) target labels for input data
    -            epochs (int) amount of epochs
    -            lam (float) regulariziation term lambda
    -            batches (int) amount of batches input data splits into
    -            X_val (numpy array) validation data
    -            t_val (numpy array) target labels for validation data
    -
    -        Returns:
    -        ------------
    -            scores (dict) a dictionary with "train_error", "train_acc", "val_error", val_acc" keys
    -            that contain numpy arrays with float values of all accuracies/errors over all epochs.
    -            Can be used to create plots. Also used to update the progress bar during training
    -        """
    -
    -        # setup
    -        if self.seed is not None:
    -            np.random.seed(self.seed)
    -
    -        # initialize weights
    -        self._initialize_weights(X)
    -
    -        # create arrays for score metrics
    -        scores = self._initialize_scores(epochs)
    -
    -        assert batches <= t.shape[0]
    -        batch_size = X.shape[0] // batches
    -
    -        try:
    -            for epoch in range(epochs):
    -                for batch in range(batches):
    -                    # minibatch gradient descent
    -                    # If the for loop has reached the last batch, take all thats left
    -                    if batch == batches - 1:
    -                        X_batch = X[batch * batch_size :, :, :, :]
    -                        t_batch = t[batch * batch_size :, :]
    -                    else:
    -                        X_batch = X[
    -                            batch * batch_size : (batch + 1) * batch_size, :, :, :
    -                        ]
    -                        t_batch = t[batch * batch_size : (batch + 1) * batch_size, :]
    -
    -                    self._feedforward(X_batch)
    -                    self._backpropagate(t_batch, lam)
    -
    -                # reset schedulers for each epoch (some schedulers pass in this call)
    -                for layer in self.layers:
    -                    if isinstance(layer, FullyConnectedLayer):
    -                        layer._reset_scheduler()
    -
    -                # computing performance metrics
    -                scores = self._compute_scores(scores, epoch, X, t, X_val, t_val)
    -
    -                # printing progress bar
    -                print_length = self._progress_bar(
    -                    epoch,
    -                    epochs,
    -                    scores,
    -                )
    -        # allows for stopping training at any point and seeing the result
    -        except KeyboardInterrupt:
    -            pass
    -
    -        # visualization of training progression (similiar to tensorflow progression bar)
    -        sys.stdout.write("\r" + " " * print_length)
    -        sys.stdout.flush()
    -        self._progress_bar(
    -            epochs,
    -            epochs,
    -            scores,
    -        )
    -        sys.stdout.write("")
    -
    -        return scores
    -
    -    def _feedforward(self, X_batch) -> np.ndarray:
    -        """
    -        Description:
    -        ------------
    -            Performs the feedforward pass for all layers in the CNN. Called from fit()
    -        """
    -        a = X_batch
    -        for layer in self.layers:
    -            a = layer._feedforward(a)
    -
    -        return a
    -
    -    def _backpropagate(self, t_batch, lam) -> None:
    -        """
    -        Description:
    -        ------------
    -            Performs backpropagation for all layers in the CNN. Called from fit()
    -        """
    -        assert len(self.layers) >= 2
    -        reversed_layers = self.layers[::-1]
    -
    -        # for every layer, backwards
    -        for i in range(len(reversed_layers) - 1):
    -            layer = reversed_layers[i]
    -            prev_layer = reversed_layers[i + 1]
    -
    -            # OutputLayer
    -            if isinstance(layer, OutputLayer):
    -                prev_a = prev_layer.get_prev_a()
    -                weights_next, delta_next = layer._backpropagate(t_batch, prev_a, lam)
    -
    -            # FullyConnectedLayer
    -            elif isinstance(layer, FullyConnectedLayer):
    -                assert (
    -                    delta_next is not None
    -                ), "No OutputLayer to follow FullyConnectedLayer"
    -                assert (
    -                    weights_next is not None
    -                ), "No OutputLayer to follow FullyConnectedLayer"
    -                prev_a = prev_layer.get_prev_a()
    -                weights_next, delta_next = layer._backpropagate(
    -                    weights_next, delta_next, prev_a, lam
    -                )
    -
    -            # FlattenLayer
    -            elif isinstance(layer, FlattenLayer):
    -                assert (
    -                    delta_next is not None
    -                ), "No FullyConnectedLayer to follow FlattenLayer"
    -                assert (
    -                    weights_next is not None
    -                ), "No FullyConnectedLayer to follow FlattenLayer"
    -                delta_next = layer._backpropagate(weights_next, delta_next)
    -
    -            # Convolution2DLayer and Convolution2DLayerOPT
    -            elif isinstance(layer, Convolution2DLayer):
    -                assert (
    -                    delta_next is not None
    -                ), "No FlattenLayer to follow Convolution2DLayer"
    -                delta_next = layer._backpropagate(delta_next)
    -
    -            # Pooling2DLayer
    -            elif isinstance(layer, Pooling2DLayer):
    -                assert delta_next is not None, "No Layer to follow Pooling2DLayer"
    -                delta_next = layer._backpropagate(delta_next)
    -
    -            # Catch error
    -            else:
    -                raise NotImplementedError
    -
    -    def _compute_scores(
    -        self,
    -        scores: dict,
    -        epoch: int,
    -        X: np.ndarray,
    -        t: np.ndarray,
    -        X_val: np.ndarray,
    -        t_val: np.ndarray,
    -    ) -> dict:
    -        """
    -        Description:
    -        ------------
    -            Computes scores such as training error, training accuracy, validation error
    -            and validation accuracy for the CNN depending on if a validation set is used
    -            and if the CNN performs classification or regression
    -
    -        Returns:
    -        ------------
    -            scores (dict) a dictionary with "train_error", "train_acc", "val_error", val_acc" keys
    -            that contain numpy arrays with float values of all accuracies/errors over all epochs.
    -            Can be used to create plots. Also used to update the progress bar during training
    -        """
    -
    -        pred_train = self.predict(X)
    -        cost_function_train = self.cost_func(t)
    -        train_error = cost_function_train(pred_train)
    -        scores["train_error"][epoch] = train_error
    -
    -        if X_val is not None and t_val is not None:
    -            cost_function_val = self.cost_func(t_val)
    -            pred_val = self.predict(X_val)
    -            val_error = cost_function_val(pred_val)
    -            scores["val_error"][epoch] = val_error
    -
    -        if self.pred_format != "Regression":
    -            train_acc = self._accuracy(pred_train, t)
    -            scores["train_acc"][epoch] = train_acc
    -            if X_val is not None and t_val is not None:
    -                val_acc = self._accuracy(pred_val, t_val)
    -                scores["val_acc"][epoch] = val_acc
    -
    -        return scores
    -
    -    def _initialize_scores(self, epochs) -> dict:
    -        """
    -        Description:
    -        ------------
    -            Initializes scores such as training error, training accuracy, validation error
    -            and validation accuracy for the CNN
    -
    -        Returns:
    -        ------------
    -            A dictionary with "train_error", "train_acc", "val_error", val_acc" keys that
    -            will contain numpy arrays with float values of all accuracies/errors over all epochs
    -            when passed through the _compute_scores() function during fit()
    -        """
    -        scores = dict()
    -
    -        train_errors = np.empty(epochs)
    -        train_errors.fill(np.nan)
    -        val_errors = np.empty(epochs)
    -        val_errors.fill(np.nan)
    -
    -        train_accs = np.empty(epochs)
    -        train_accs.fill(np.nan)
    -        val_accs = np.empty(epochs)
    -        val_accs.fill(np.nan)
    -
    -        scores["train_error"] = train_errors
    -        scores["val_error"] = val_errors
    -        scores["train_acc"] = train_accs
    -        scores["val_acc"] = val_accs
    -
    -        return scores
    -
    -    def _initialize_weights(self, X: np.ndarray) -> None:
    -        """
    -        Description:
    -        ------------
    -            Initializes weights for all layers in CNN
    -
    -        Parameters:
    -        ------------
    -            I   X (np.ndarray) input of format [img, feature_maps, height, width]
    -        """
    -        prev_nodes = X
    -        for layer in self.layers:
    -            prev_nodes = layer._reset_weights(prev_nodes)
    -
    -    def predict(self, X: np.ndarray, *, threshold=0.5) -> np.ndarray:
    -        """
    -        Description:
    -        ------------
    -            Predicts output of input X
    -
    -        Parameters:
    -        ------------
    -            I   X (np.ndarray) input [img, feature_maps, height, width]
    -        """
    -
    -        prediction = self._feedforward(X)
    -
    -        if self.pred_format == "Binary":
    -            return np.where(prediction > threshold, 1, 0)
    -        elif self.pred_format == "Multi-class":
    -            class_prediction = np.zeros(prediction.shape)
    -            for i in range(prediction.shape[0]):
    -                class_prediction[i, np.argmax(prediction[i, :])] = 1
    -            return class_prediction
    -        else:
    -            return prediction
    -
    -    def _accuracy(self, prediction: np.ndarray, target: np.ndarray) -> float:
    -        """
    -        Description:
    -        ------------
    -            Calculates accuracy of given prediction to target
    -
    -        Parameters:
    -        ------------
    -            I   prediction (np.ndarray): output of predict() fuction
    -            (1s and 0s in case of classification, and real numbers in case of regression)
    -            II  target (np.ndarray): vector of true values (What the network should predict)
    -
    -        Returns:
    -        ------------
    -            A floating point number representing the percentage of correctly classified instances.
    -        """
    -        assert prediction.size == target.size
    -        return np.average((target == prediction))
    -
    -    def _progress_bar(self, epoch: int, epochs: int, scores: dict) -> int:
    -        """
    -        Description:
    -        ------------
    -            Displays progress of training
    -        """
    -        progression = epoch / epochs
    -        epoch -= 1
    -        print_length = 40
    -        num_equals = int(progression * print_length)
    -        num_not = print_length - num_equals
    -        arrow = ">" if num_equals > 0 else ""
    -        bar = "[" + "=" * (num_equals - 1) + arrow + "-" * num_not + "]"
    -        perc_print = self._fmt(progression * 100, N=5)
    -        line = f"  {bar} {perc_print}% "
    -
    -        for key, score in scores.items():
    -            if np.isnan(score[epoch]) == False:
    -                value = self._fmt(score[epoch], N=4)
    -                line += f"| {key}: {value} "
    -        print(line, end="\r")
    -        return len(line)
    -
    -    def _fmt(self, value: int, N=4) -> str:
    -        """
    -        Description:
    -        ------------
    -            Formats decimal numbers for progress bar
    -        """
    -        if value > 0:
    -            v = value
    -        elif value < 0:
    -            v = -10 * value
    -        else:
    -            v = 1
    -        n = 1 + math.floor(math.log10(v))
    -        if n >= N - 1:
    -            return str(round(value))
    -            # or overflow
    -        return f"{value:.{N-n-1}f}"
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Usage of CNN code

    - -

    Using the CNN codebase is very simple. We begin by initiating a CNN -object, which takes a cost function, a scheduler and a seed as its -arguments. If a scheduler is not provided, it will per default -initiate an Adam scheduler with eta=1e-4, and if a seed is not -provided, the CNN will not be seeded, meaning it will run with a -different random seed every run. Below we demonstrate an initiation of -our CNN. -

    - - - -
    -
    -
    -
    -
    -
    adam_scheduler = Adam(eta=1e-3, rho=0.9, rho2=0.999)
    -cnn = CNN(cost_func=CostCrossEntropy, scheduler=adam_scheduler, seed=2023)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Now that we have our CNN object, we can begin to add layers to it! -Many of the add_layer functions have default values, for example -add_Convolution2DLayer() has a default v_stride and h_stride of -1. However, these can of course be set to any value you please. Note -that the input channels of a subsequent convolutional layer must equal -the previous convolutional layer's feature maps. -

    - - - -
    -
    -
    -
    -
    -
    cnn.add_Convolution2DLayer(
    -    input_channels=1,
    -    feature_maps=1,
    -    kernel_height=3,
    -    kernel_width=3,
    -    act_func=LRELU,
    -)
    -
    -cnn.add_FlattenLayer()
    -
    -cnn.add_FullyConnectedLayer(30, LRELU)
    -
    -cnn.add_FullyConnectedLayer(20, LRELU)
    -
    -cnn.add_OutputLayer(10, softmax)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Here we have created a CNN with the following architecture:

    - -
      -
    1. A convolutional layer with 1 input channel, with a kernel height of 2 and a width of 2, which uses LRELU as its non-linearity function. This layer outputs 1 feature map, which feed into the subsequent layer.
    2. -
    3. A flatten layer
    4. -
    5. A hidden layer with 30 nodes, with LRELU as its activation function
    6. -
    7. Another hidden layer but with 20 nodes
    8. -
    9. The output layer, with softmax as its activation function and 10 nodes. We use 10 nodes because we will be using a dataset with 10 classes.
    10. -
    -

    Now, before we can train the model, we need to load in our data. We -will use the MNIST dataset and use 10000 \( 28 \times 28 images \). -

    - - - -
    -
    -
    -
    -
    -
    from sklearn.datasets import fetch_openml
    -from sklearn.model_selection import train_test_split
    -
    -def onehot(target: np.ndarray):
    -    onehot = np.zeros((target.size, target.max() + 1))
    -    onehot[np.arange(target.size), target] = 1
    -    return onehot
    -
    -# get dataset
    -dataset = fetch_openml("mnist_784", parser="auto")
    -mnist = dataset.data.to_numpy(dtype="float")[:10000, :]
    -
    -# scale data
    -for i in range(mnist.shape[1]):
    -    mnist[:, i] /= 255
    -    
    -# reshape to add single input channel to data shape [inputs, input_channels, height, width]
    -mnist = mnist.reshape(mnist.shape[0], 1, 28, 28)
    -
    -# one hot encode target as we are doing multi-class classification
    -target = onehot(np.array([int(i) for i in dataset.target.to_numpy()[:10000]]))
    -
    -# split into training and validation data
    -x_train, x_val, y_train, y_val = train_test_split(mnist, target)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Now we may train our model. Note that we can utilize regularization in -the CNN by using the lam (lambda) parameter in fit(), and utilize -different types of gradient descent by specifying the amount of -batches via the batches parameter as shown below. -

    - -

    The functionfit() returns a score dictionary of the training error and -accuracy (and validation error and accuracy if a validation set is -provided) which can be used to plot the error and accuracy of the -model over epochs. -

    - - - -
    -
    -
    -
    -
    -
    scores = cnn.fit(
    -    x_train,
    -    y_train,
    -    lam=1e-5,
    -    batches=10,
    -    epochs=100,
    -    X_val=x_val,
    -    t_val=y_val,
    -)
    -
    -plt.plot(scores["train_acc"], label="Training")
    -plt.plot(scores["val_acc"], label="Validation")
    -plt.ylim([0.8,1])
    -plt.xlabel("Epochs")
    -plt.ylabel("Accuracy")
    -plt.legend()
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Considering we only trained the model for 100 epochs without any tuning of the hyperparameters, this result is pretty good.

    - -

    The codebase allows for great flexibility in CNN -architectures. Pooling layers can be added before, inbetween or after -convolutional layers, but due to the great optimizations made within -Convolution2DLayerOPT, we recommend using the v_stride and h_stride -parameters in add_Convolution2DLayer() to reduce the dimentionality of -the problem as the pooling layer is slow in comparison. To use the -unoptimized version of Convolution2DLayer, simply pass optimized=False -as an argument in add_Convolution2DLayer(). -

    - -

    If one wishes to perform binary classification using the CNN, simply -use the cost function 'CostLogReg' when initializing the CNN and use 1 -node at the OutputLayer. -

    - -

    Below we have created another, more untraditional architecture using -our code to demonstrate its flexibility and different attributes such -as asymmetric stride that might become useful when constructing your -own CNN. -

    - - - -
    -
    -
    -
    -
    -
    adam_scheduler = Adam(eta=1e-3, rho=0.9, rho2=0.999)
    -cnn = CNN(cost_func=CostCrossEntropy, scheduler=adam_scheduler, seed=2023)
    -
    -cnn.add_Convolution2DLayer(
    -    input_channels=1,
    -    feature_maps=7,
    -    kernel_height=7,
    -    kernel_width=1,
    -    act_func=LRELU,
    -)
    -
    -cnn.add_PoolingLayer(
    -    kernel_height=2,
    -    kernel_width=2,
    -    pooling="average",
    -)
    -
    -cnn.add_PoolingLayer(
    -    kernel_height=2,
    -    kernel_width=2,
    -    pooling="max",
    -)
    -
    -cnn.add_Convolution2DLayer(
    -    input_channels=7,
    -    feature_maps=1,
    -    kernel_height=4,
    -    kernel_width=4,
    -    v_stride=2,
    -    h_stride=3,
    -    act_func=LRELU,
    -    optimized=False,
    -)
    -
    -cnn.add_Convolution2DLayer(
    -    input_channels=1,
    -    feature_maps=1,
    -    kernel_height=2,
    -    kernel_width=2,
    -    act_func=sigmoid,
    -    optimized=True,
    -)
    -
    -cnn.add_PoolingLayer(
    -    kernel_height=2,
    -    kernel_width=2,
    -    pooling="max"
    -)
    -
    -cnn.add_FlattenLayer()
    -
    -cnn.add_FullyConnectedLayer(100, LRELU)
    -
    -cnn.add_FullyConnectedLayer(10, sigmoid)
    -
    -cnn.add_FullyConnectedLayer(101, identity)
    -
    -cnn.add_OutputLayer(10, softmax)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Here we see the use of asymmetrical 1D kernels such as the \( 7 \times -1 \) kernel in the first convolutional layer, both max and average -pooling, asymmetric stride in the unoptimized convolutional layer, -more pooling, a flatten layer, a hidden layer with 100 nodes using -LRELU, another hidden layer with 10 hidden nodes that uses the sigmoid -activation function, and another hidden layer with 101 nodes which -utilizes no activation function (identity). Finally, we arrive at the -output layer with 10 nodes, which uses softmax as its activation -function. -

    -

    Additional Remarks

    - -

    The stride parameter controls the distance between each convolution -and the kernel/filter. If our image is padded, stride is the only -parameter that determines the size of the output from a convolutional -layer. However, if we decide not to perform any padding, the size of -the output feature map depends on both the stride and kernel size. It -is important to note that neither the stride nor the kernel has to be -symmetrical. This means that we can use a rectangular filter if we -choose, and the stride in the vertical direction (axis=0 in Python) -does not need to be the same as the stride in the horizontal direction -(axis=1 in Python). It may even be the case that asymmetric -combinations of stride or kernel dimensions, or both, yield better -results than symmetric values for these parameters. -

    - - - -
    -
    -
    -
    -
    -
    def convolve(image, kernel, stride=1):
    -    for i in range(2):
    -        kernel = np.rot90(kernel)
    -
    -    k_half_height = kernel.shape[0] // 2
    -    k_half_width = kernel.shape[0] // 2
    -
    -    conv_image = np.zeros(image.shape)
    -    pad_image = padding(image, kernel)
    -
    -    for i in range(k_half_height, conv_image.shape[0] + k_half_height, stride):
    -        for j in range(k_half_width, conv_image.shape[1] + k_half_width, stride):
    -            conv_image[i - k_half_height, j - k_half_width] = np.sum(
    -                pad_image[
    -                    i - k_half_height : i + k_half_height + 1, j - k_half_width : j + k_half_width + 1
    -                ]
    -                * kernel
    -            )
    -
    -    return conv_image
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Remarks on the speed

    - -

    Despite the naive convolution algorithm shown above working finely, it -is extremely slow, requiring approximately 20-30 seconds to process a -single image. The time complexity of 2D convolution, which is O(NMnm), -rapidly becomes a constraint and may, at worst, make computations -infeasible. Consequently, optimizing the naive 2D convolution -algorithm is a necessity, as the execution time of the algorithm -significantly increases as the input data size expands. This can pose -a bottleneck in applications that necessitate real-time processing of -large data volumes, such as image and video processing, deep learning, -and scientific simulations. -

    - -

    To address this issue, we shall present two widely used optimization -techniques: the separable kernel approach and Fast Fourier Transform -(FFT). Both of these methods can drastically reduce the computational -complexity of convolution and enhance the overall efficiency of -processing substantial data quantities. While we shall refrain from -delving into the intricacies of these algorithms, we strongly -encourage you to examine at least the application of FFT to optimize -computations. -

    -

    Convolution using separable kernels

    - - - -
    -
    -
    -
    -
    -
    def conv2DSep(image, kernel, coef, stride=1, pad="zero"):
    -    for i in range(2):
    -        kernel = np.rot90(kernel)
    -
    -    # The kernel is quadratic, thus we only need one of its dimensions
    -    half_dim = kernel.shape[0] // 2
    -
    -    ker1 = np.array(kernel[0, :])
    -    ker2 = np.array(kernel[:, 0])
    -
    -    if pad == "zero":
    -        conv_image = np.zeros(image.shape)
    -        pad_image = padding(image, kernel)
    -    else:
    -        conv_image = np.zeros(
    -            (image.shape[0] - kernel.shape[0], image.shape[1] - kernel.shape[1])
    -        )
    -        pad_image = image[:, :]
    -
    -    for i in range(half_dim, conv_image.shape[0] + half_dim, stride):
    -        for j in range(half_dim, conv_image.shape[1] + half_dim, stride):
    -            conv_image[i - half_dim, j - half_dim] = (
    -                pad_image[
    -                    i - half_dim : i + half_dim + 1, j - half_dim : j + half_dim + 1
    -                ]
    -                @ ker1
    -                @ ker2.T
    -                * coef
    -            )
    -
    -    return conv_image
    -
    -img_path = img_path = "data/IMG-2167.JPG"
    -image_of_cute_dog = imageio.imread(img_path, mode="L")
    -start_time = time.time()
    -filtered_image = conv2DSep(image_of_cute_dog, kernel=sobel_kernel, coef=1)
    -print(f'Time taken for convolution with seperated kernel on 128x128 image {time.time() - start_time}')
    -plt.imshow(filtered_image, cmap="gray", vmin=0, vmax=255, aspect="auto")
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    By taking advantage of the capabilities of separable kernels, we can -effectively cut the computational expense of filtering an image in -half. Yet, if we seek even more rapid processing, we can turn to the -Fast Fourier Transform (FFT) algorithm provided by the numpy -library. By utilizing FFT to transform the input image and filter into -the frequency domain, we can perform convolution in this domain. This -approach significantly reduces the number of operations needed and -results in a marked speedup relative to other convolution -techniques. In addition, it is worth noting that the FFT is widely -regarded as one of the most critical algorithms developed to date, -with applications ranging from digital signal processing to scientific -computing. -

    -

    Convolution in the Fourier domain

    - - -
    -
    -
    -
    -
    -
    start_time = time.time()
    -img_fft = np.fft.fft2(image_of_cute_dog)
    -kernel_fft = np.fft.fft2(sobel_kernel, s=image_of_cute_dog.shape)
    -
    -conv_image = img_fft * kernel_fft
    -
    -filtered_image = np.fft.ifft2(conv_image)
    -print(f'Time take for convolution in the fourier domain: {time.time() - start_time}')
    -plt.imshow(filtered_image.real, cmap="gray", vmin=0, vmax=255, aspect="auto")
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    It is evident that executing convolution in the Fourier domain yields -the quickest computation time. Nonetheless, one should exercise -caution, particularly when dealing with images of relatively small -dimensions, as one of the other methods may prove to be more -expeditious than FFT-enhanced convolution. The overhead involved in -transferring both the image and filter into the Fourier domain, -followed by their subsequent transformation back into the spatial -domain, results in a minor inconvenience. Therefore, it is imperative -to remain cognizant of this fact when utilizing FFT as the primary -optimization technique. +

    The convolution stage, where we apply different filters \( \boldsymbol{W} \) in +order to reduce the dimensionality of an image, adds, in addition to +the weights and biases (to be trained by the back propagation +algorithm) that define the filters, two new hyperparameters, the so-called +padding \( P \) and the stride \( S \).

    @@ -3588,7 +384,7 @@ optimization technique.

  • 44
  • 45
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs036.html b/doc/pub/week44/html/._week44-bs036.html index 1201e6182..91901cd36 100644 --- a/doc/pub/week44/html/._week44-bs036.html +++ b/doc/pub/week44/html/._week44-bs036.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,18 +350,24 @@ MathJax.Hub.Config({

     

     

     

    -

    Building convolutional neural networks in Tensorflow and Keras

    +

    Finding the number of parameters

    -

    As discussed above, CNNs are neural networks built from the assumption that the inputs -to the network are 2D images. This is important because the number of features or pixels in images -grows very fast with the image size, and an enormous number of weights and biases are needed in order to build an accurate network. +

    In the above example we have an input matrix of dimension \( 3\times +3 \). In general we call the input for an input volume and it is defined +by its width \( H_1 \), height \( H_1 \) and depth \( D_1 \). If we have the +standard three color channels \( D_1=3 \).

    -

    As before, we still have our input, a hidden layer and an output. What's novel about convolutional networks -are the convolutional and pooling layers stacked in pairs between the input and the hidden layer. -In addition, the data is no longer represented as a 2D feature matrix, instead each input is a number of 2D -matrices, typically 1 for each color dimension (Red, Green, Blue). -

    +

    The above example has \( W_1=H_1=3 \) and \( D_1=1 \).

    + +

    When we introduce the filter we have the following additional hyperparameters

    +
      +
    1. \( K \) the number of filters. It is common to perform the convolution of the input several times since by experience shrinking the input too fast does not work well
    2. +
    3. \( F \) as the filter's spatial extent
    4. +
    5. \( S \) as the stride parameter
    6. +
    7. \( P \) as the padding parameter
    8. +
    +

    These parameters are defined by the architecture of the network and are not included in the training.

    @@ -346,7 +394,7 @@ matrices, typically 1 for each color dimension (Red, Green, Blue).

  • 45
  • 46
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs037.html b/doc/pub/week44/html/._week44-bs037.html index 394e728ac..a3a441870 100644 --- a/doc/pub/week44/html/._week44-bs037.html +++ b/doc/pub/week44/html/._week44-bs037.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,15 +350,23 @@ MathJax.Hub.Config({

     

     

     

    -

    Setting it up

    +

    New image (or volume)

    -

    It means that to represent the entire -dataset of images, we require a 4D matrix or tensor. This tensor has the dimensions: +

    Acting with the filter on the input volume produces an output volume +which is defined by its width \( W_2 \), its height \( H_2 \) and its depth +\( D_2 \).

    -$$ -(n_{inputs},\, n_{pixels, width},\, n_{pixels, height},\, depth) . + +

    These are defined by the following relations

    +$$ +W_2 = \frac{(W_1-F+2P)}{S}+1, $$ +$$ +H_2 = \frac{(H_1-F+2P)}{S}+1, +$$ + +

    and \( D_2=K \).

    @@ -342,6 +392,8 @@ $$

  • 45
  • 46
  • 47
  • +
  • ...
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs038.html b/doc/pub/week44/html/._week44-bs038.html index 503f24246..ce55b0d63 100644 --- a/doc/pub/week44/html/._week44-bs038.html +++ b/doc/pub/week44/html/._week44-bs038.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,20 +350,30 @@ MathJax.Hub.Config({

     

     

     

    -

    The MNIST dataset again

    +

    Parameters to train, common settings

    -

    The MNIST dataset consists of grayscale images with a pixel size of -\( 28\times 28 \), meaning we require \( 28 \times 28 = 724 \) weights to each -neuron in the first hidden layer. -

    +

    With parameter sharing, the convolution involves thus for each filter \( F\times F\times D_1 \) weights plus one bias parameter.

    -

    If we were to analyze images of size \( 128\times 128 \) we would require -\( 128 \times 128 = 16384 \) weights to each neuron. Even worse if we were -dealing with color images, as most images are, we have an image matrix -of size \( 128\times 128 \) for each color dimension (Red, Green, Blue), -meaning 3 times the number of weights \( = 49152 \) are required for every -single neuron in the first hidden layer. -

    +

    In total we have

    +$$ +\left(F\times F\times D_1)\right) \times K+(K\mathrm{--biases}), +$$ + +

    parameters to train by back propagation.

    + +

    It is common to let \( K \) come in powers of \( 2 \), that is \( 32 \), \( 64 \), \( 128 \) etc.

    + +
    +
    + +
      +
    1. \( \begin{array}{c} F=3 & S=1 & P=1 \end{array} \)
    2. +
    3. \( \begin{array}{c} F=5 & S=1 & P=2 \end{array} \)
    4. +
    5. \( \begin{array}{c} F=5 & S=2 & P=\mathrm{open} \end{array} \)
    6. +
    7. \( \begin{array}{c} F=1 & S=1 & P=0 \end{array} \)
    8. +
    +
    +

    @@ -347,6 +399,9 @@ single neuron in the first hidden layer.

  • 45
  • 46
  • 47
  • +
  • 48
  • +
  • ...
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs039.html b/doc/pub/week44/html/._week44-bs039.html index 0c84d4e92..c97e2a6e9 100644 --- a/doc/pub/week44/html/._week44-bs039.html +++ b/doc/pub/week44/html/._week44-bs039.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,21 +350,34 @@ MathJax.Hub.Config({

     

     

     

    -

    Strong correlations

    +

    Examples of CNN setups

    -

    Images typically have strong local correlations, meaning that a small -part of the image varies little from its neighboring regions. If for -example we have an image of a blue car, we can roughly assume that a -small blue part of the image is surrounded by other blue regions. +

    Let us assume we have an input volume \( V \) given by an image of dimensionality +\( 32\times 32 \times 3 \), that is three color channels and \( 32\times 32 \) pixels.

    -

    Therefore, instead of connecting every single pixel to a neuron in the -first hidden layer, as we have previously done with deep neural -networks, we can instead connect each neuron to a small part of the -image (in all 3 RGB depth dimensions). The size of each small area is -fixed, and known as a receptive. +

    We apply a filter of dimension \( 5\times 5 \) ten times with stride \( S=1 \) and padding \( P=0 \).

    + +

    The output volume is given by \( (32-5)/1+1=28 \), resulting in ten images +of dimensionality \( 28\times 28\times 3 \).

    +

    The total number of parameters to train for each filter is then +\( 5\times 5\times 3+1 \), where the last parameter is the bias. This +gives us \( 76 \) parameters for each filter, leading to a total of \( 760 \) +parameters for the ten filters. +

    + +

    How many parameters will a filter of dimensionality \( 3\times 3 \) +(adding color channels) result in if we produce \( 32 \) new images? Use \( S=1 \) and \( P=0 \). +

    + +

    Note that strides constitute a form of subsampling. As an alternative to +being interpreted as a measure of how much the kernel/filter is translated, strides +can also be viewed as how much of the output is retained. For instance, moving +the kernel by hops of two is equivalent to moving the kernel by hops of one but +retaining only odd output elements. +

    @@ -346,6 +401,10 @@ fixed, and known as a 45

  • 46
  • 47
  • +
  • 48
  • +
  • 49
  • +
  • ...
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs040.html b/doc/pub/week44/html/._week44-bs040.html index 6adb8bbf6..1a539a527 100644 --- a/doc/pub/week44/html/._week44-bs040.html +++ b/doc/pub/week44/html/._week44-bs040.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -307,27 +349,16 @@ MathJax.Hub.Config({

     

     

     

    - -

    Layers of a CNN

    + +

    Summarizing: Performing a general discrete convolution (From Raschka et al)

    -

    The layers of a convolutional neural network arrange neurons in 3D: width, height and depth. -The input image is typically a square matrix of depth 3. -

    - -

    A convolution is performed on the image which outputs -a 3D volume of neurons. The weights to the input are arranged in a number of 2D matrices, known as filters. -

    - -

    Each filter slides along the input image, taking the dot product -between each small part of the image and the filter, in all depth -dimensions. This is then passed through a non-linear function, -typically the Rectified Linear (ReLu) function, which serves as the -activation of the neurons in the first convolutional layer. This is -further passed through a pooling layer, which reduces the size of the -convolutional layer, e.g. by taking the maximum or average across some -small regions, and this serves as input to the next convolutional -layer. -

    +
    +
    +
    +

    Figure 4: A deep CNN

    +
    +

    +

    @@ -350,6 +381,11 @@ layer.

  • 45
  • 46
  • 47
  • +
  • 48
  • +
  • 49
  • +
  • 50
  • +
  • ...
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs041.html b/doc/pub/week44/html/._week44-bs041.html index f3006d333..bfda4cc53 100644 --- a/doc/pub/week44/html/._week44-bs041.html +++ b/doc/pub/week44/html/._week44-bs041.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,18 +350,19 @@ MathJax.Hub.Config({

     

     

     

    -

    Systematic reduction

    +

    Pooling

    -

    By systematically reducing the size of the input volume, through -convolution and pooling, the network should create representations of -small parts of the input, and then from them assemble representations -of larger areas. The final pooling layer is flattened to serve as -input to a hidden layer, such that each neuron in the final pooling -layer is connected to every single neuron in the hidden layer. This -then serves as input to the output layer, e.g. a softmax output for -classification. +

    In addition to discrete convolutions themselves, pooling operations +make up another important building block in CNNs. Pooling operations reduce +the size of feature maps by using some function to summarize subregions, such +as taking the average or the maximum value.

    +

    Pooling works by sliding a window across the input and feeding the content of +the window to a pooling function. In some sense, pooling works very much +like a discrete convolution, but replaces the linear combination described by +the kernel with some other function. +

    @@ -341,6 +384,12 @@ classification.

  • 45
  • 46
  • 47
  • +
  • 48
  • +
  • 49
  • +
  • 50
  • +
  • 51
  • +
  • ...
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs042.html b/doc/pub/week44/html/._week44-bs042.html index 728dab2b0..19e47880a 100644 --- a/doc/pub/week44/html/._week44-bs042.html +++ b/doc/pub/week44/html/._week44-bs042.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,69 +350,15 @@ MathJax.Hub.Config({

     

     

     

    -

    Prerequisites: Collect and pre-process data

    - - -
    -
    -
    -
    -
    -
    # import necessary packages
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from sklearn import datasets
    -
    -
    -# ensure the same random numbers appear every time
    -np.random.seed(0)
    -
    -# display images in notebook
    -%matplotlib inline
    -plt.rcParams['figure.figsize'] = (12,12)
    -
    -
    -# download MNIST dataset
    -digits = datasets.load_digits()
    -
    -# define inputs and labels
    -inputs = digits.images
    -labels = digits.target
    -
    -# RGB images have a depth of 3
    -# our images are grayscale so they should have a depth of 1
    -inputs = inputs[:,:,:,np.newaxis]
    -
    -print("inputs = (n_inputs, pixel_width, pixel_height, depth) = " + str(inputs.shape))
    -print("labels = (n_inputs) = " + str(labels.shape))
    -
    -
    -# choose some random images to display
    -n_inputs = len(inputs)
    -indices = np.arange(n_inputs)
    -random_indices = np.random.choice(indices, size=5)
    -
    -for i, image in enumerate(digits.images[random_indices]):
    -    plt.subplot(1, 5, i+1)
    -    plt.axis('off')
    -    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    -    plt.title("Label: %d" % digits.target[random_indices[i]])
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +

    Pooling arithmetic

    +

    In a neural network, pooling layers provide invariance to small translations of +the input. The most common kind of pooling is max pooling, which +consists in splitting the input in (usually non-overlapping) patches and +outputting the maximum value of each patch. Other kinds of pooling exist, e.g., +mean or average pooling, which all share the same idea of aggregating the input +locally by applying a non-linearity to the content of some patches. +

    @@ -391,6 +379,13 @@ plt.show()

  • 45
  • 46
  • 47
  • +
  • 48
  • +
  • 49
  • +
  • 50
  • +
  • 51
  • +
  • 52
  • +
  • ...
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs043.html b/doc/pub/week44/html/._week44-bs043.html index 57914b217..eeff4c046 100644 --- a/doc/pub/week44/html/._week44-bs043.html +++ b/doc/pub/week44/html/._week44-bs043.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,51 +350,15 @@ MathJax.Hub.Config({

     

     

     

    -

    Importing Keras and Tensorflow

    - - -
    -
    -
    -
    -
    -
    from tensorflow.keras import datasets, layers, models
    -from tensorflow.keras.layers import Input
    -from tensorflow.keras.models import Sequential      #This allows appending layers to existing models
    -from tensorflow.keras.layers import Dense           #This allows defining the characteristics of a particular layer
    -from tensorflow.keras import optimizers             #This allows using whichever optimiser we want (sgd,adam,RMSprop)
    -from tensorflow.keras import regularizers           #This allows using whichever regularizer we want (l1,l2,l1_l2)
    -from tensorflow.keras.utils import to_categorical   #This allows using categorical cross entropy as the cost function
    -#from tensorflow.keras import Conv2D
    -#from tensorflow.keras import MaxPooling2D
    -#from tensorflow.keras import Flatten
    -
    -from sklearn.model_selection import train_test_split
    -
    -# representation of labels
    -labels = to_categorical(labels)
    -
    -# split into train and test data
    -# one-liner from scikit-learn library
    -train_size = 0.8
    -test_size = 1 - train_size
    -X_train, X_test, Y_train, Y_test = train_test_split(inputs, labels, train_size=train_size,
    -                                                    test_size=test_size)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +

    Pooling types (From Raschka et al)

    +
    +
    +
    +

    Figure 5: A deep CNN

    +
    +

    +

    @@ -372,6 +378,14 @@ X_train, X_test, Y_train, Y_test = train_tes

  • 45
  • 46
  • 47
  • +
  • 48
  • +
  • 49
  • +
  • 50
  • +
  • 51
  • +
  • 52
  • +
  • 53
  • +
  • ...
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs044.html b/doc/pub/week44/html/._week44-bs044.html index 823e99d4b..8aea60144 100644 --- a/doc/pub/week44/html/._week44-bs044.html +++ b/doc/pub/week44/html/._week44-bs044.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -307,57 +349,19 @@ MathJax.Hub.Config({

     

     

     

    - -

    Running with Keras

    + +

    Building convolutional neural networks in Tensorflow and Keras

    +

    As discussed above, CNNs are neural networks built from the assumption that the inputs +to the network are 2D images. This is important because the number of features or pixels in images +grows very fast with the image size, and an enormous number of weights and biases are needed in order to build an accurate network. +

    - -
    -
    -
    -
    -
    -
    def create_convolutional_neural_network_keras(input_shape, receptive_field,
    -                                              n_filters, n_neurons_connected, n_categories,
    -                                              eta, lmbd):
    -    model = Sequential()
    -    model.add(layers.Conv2D(n_filters, (receptive_field, receptive_field), input_shape=input_shape, padding='same',
    -              activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    -    model.add(layers.MaxPooling2D(pool_size=(2, 2)))
    -    model.add(layers.Flatten())
    -    model.add(layers.Dense(n_neurons_connected, activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    -    model.add(layers.Dense(n_categories, activation='softmax', kernel_regularizer=regularizers.l2(lmbd)))
    -    
    -    sgd = optimizers.SGD(learning_rate=eta)
    -    model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
    -    
    -    return model
    -
    -epochs = 100
    -batch_size = 100
    -input_shape = X_train.shape[1:4]
    -receptive_field = 3
    -n_filters = 10
    -n_neurons_connected = 50
    -n_categories = 10
    -
    -eta_vals = np.logspace(-5, 1, 7)
    -lmbd_vals = np.logspace(-5, 1, 7)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - +

    As before, we still have our input, a hidden layer and an output. What's novel about convolutional networks +are the convolutional and pooling layers stacked in pairs between the input and the hidden layer. +In addition, the data is no longer represented as a 2D feature matrix, instead each input is a number of 2D +matrices, typically 1 for each color dimension (Red, Green, Blue). +

    @@ -376,6 +380,15 @@ lmbd_vals = np.

  • 45
  • 46
  • 47
  • +
  • 48
  • +
  • 49
  • +
  • 50
  • +
  • 51
  • +
  • 52
  • +
  • 53
  • +
  • 54
  • +
  • ...
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs045.html b/doc/pub/week44/html/._week44-bs045.html index 29c333ad4..7b05ade0e 100644 --- a/doc/pub/week44/html/._week44-bs045.html +++ b/doc/pub/week44/html/._week44-bs045.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,45 +350,14 @@ MathJax.Hub.Config({

     

     

     

    -

    Final part

    +

    Setting it up

    - - -
    -
    -
    -
    -
    -
    CNN_keras = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)
    -        
    -for i, eta in enumerate(eta_vals):
    -    for j, lmbd in enumerate(lmbd_vals):
    -        CNN = create_convolutional_neural_network_keras(input_shape, receptive_field,
    -                                              n_filters, n_neurons_connected, n_categories,
    -                                              eta, lmbd)
    -        CNN.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=0)
    -        scores = CNN.evaluate(X_test, Y_test)
    -        
    -        CNN_keras[i][j] = CNN
    -        
    -        print("Learning rate = ", eta)
    -        print("Lambda = ", lmbd)
    -        print("Test accuracy: %.3f" % scores[1])
    -        print()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +

    It means that to represent the entire +dataset of images, we require a 4D matrix or tensor. This tensor has the dimensions: +

    +$$ +(n_{inputs},\, n_{pixels, width},\, n_{pixels, height},\, depth) . +$$

    @@ -365,6 +376,16 @@ MathJax.Hub.Config({

  • 45
  • 46
  • 47
  • +
  • 48
  • +
  • 49
  • +
  • 50
  • +
  • 51
  • +
  • 52
  • +
  • 53
  • +
  • 54
  • +
  • 55
  • +
  • ...
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs046.html b/doc/pub/week44/html/._week44-bs046.html index 1b6c525cd..1efc4cf20 100644 --- a/doc/pub/week44/html/._week44-bs046.html +++ b/doc/pub/week44/html/._week44-bs046.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -308,59 +350,20 @@ MathJax.Hub.Config({

     

     

     

    -

    Final visualization

    +

    The MNIST dataset again

    +

    The MNIST dataset consists of grayscale images with a pixel size of +\( 28\times 28 \), meaning we require \( 28 \times 28 = 724 \) weights to each +neuron in the first hidden layer. +

    - -
    -
    -
    -
    -
    -
    # visual representation of grid search
    -# uses seaborn heatmap, could probably do this in matplotlib
    -import seaborn as sns
    -
    -sns.set()
    -
    -train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    -test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    -
    -for i in range(len(eta_vals)):
    -    for j in range(len(lmbd_vals)):
    -        CNN = CNN_keras[i][j]
    -
    -        train_accuracy[i][j] = CNN.evaluate(X_train, Y_train)[1]
    -        test_accuracy[i][j] = CNN.evaluate(X_test, Y_test)[1]
    -
    -        
    -fig, ax = plt.subplots(figsize = (10, 10))
    -sns.heatmap(train_accuracy, annot=True, ax=ax, cmap="viridis")
    -ax.set_title("Training Accuracy")
    -ax.set_ylabel("$\eta$")
    -ax.set_xlabel("$\lambda$")
    -plt.show()
    -
    -fig, ax = plt.subplots(figsize = (10, 10))
    -sns.heatmap(test_accuracy, annot=True, ax=ax, cmap="viridis")
    -ax.set_title("Test Accuracy")
    -ax.set_ylabel("$\eta$")
    -ax.set_xlabel("$\lambda$")
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +

    If we were to analyze images of size \( 128\times 128 \) we would require +\( 128 \times 128 = 16384 \) weights to each neuron. Even worse if we were +dealing with color images, as most images are, we have an image matrix +of size \( 128\times 128 \) for each color dimension (Red, Green, Blue), +meaning 3 times the number of weights \( = 49152 \) are required for every +single neuron in the first hidden layer. +

    @@ -378,6 +381,18 @@ plt.show()

  • 45
  • 46
  • 47
  • +
  • 48
  • +
  • 49
  • +
  • 50
  • +
  • 51
  • +
  • 52
  • +
  • 53
  • +
  • 54
  • +
  • 55
  • +
  • 56
  • +
  • ...
  • +
  • 62
  • +
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs047.html b/doc/pub/week44/html/._week44-bs047.html index 0f3fceb51..4efe23450 100644 --- a/doc/pub/week44/html/._week44-bs047.html +++ b/doc/pub/week44/html/._week44-bs047.html @@ -37,10 +37,14 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d
  • Plan for week 44
  • -
  • Material for Lecture Thursday November 2
  • -
  • Convolutional Neural Networks (recognizing images)
  • -
  • What is the Difference
  • -
  • Neural Networks vs CNNs
  • -
  • Why CNNS for images, sound files, medical images from CT scans etc?
  • -
  • Regular NNs don’t scale well to full images
  • -
  • 3D volumes of neurons
  • -
  • Layers used to build CNNs
  • -
  • Transforming images
  • -
  • CNNs in brief
  • -
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Convolution Examples: Principle of Superposition and Periodic Forces (Fourier Transforms)
  • -
  • Simple Code Example
  • -
  • Wrapping up Fourier transforms
  • -
  • Finding the Coefficients
  • -
  • Final words on Fourier Transforms
  • -
  •    Fourier transforms and convolution
  • -
  • Two-dimensional Objects
  • -
  • More on Dimensionalities
  • -
  • Further Dimensionality Remarks
  • -
  • CNNs in more detail
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • CNNs in more detail, building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • -
  • The CIFAR01 data set
  • -
  • Verifying the data set
  • -
  • Set up the model
  • -
  • Add Dense layers on top
  • -
  • Compile and train the model
  • -
  • Finally, evaluate the model
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • +
  • Lab sessions on Tuesday and Wednesday
  • +
  • Material for Lecture Monday October 28
  • +
  • Convolutional Neural Networks (recognizing images)
  • +
  • What is the Difference
  • +
  • Neural Networks vs CNNs
  • +
  • Why CNNS for images, sound files, medical images from CT scans etc?
  • +
  • Regular NNs don’t scale well to full images
  • +
  • 3D volumes of neurons
  • +
  • More on Dimensionalities
  • +
  • Further remarks
  • +
  • Layers used to build CNNs
  • +
  • Transforming images
  • +
  • CNNs in brief
  • +
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Key Idea
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -312,39 +350,20 @@ MathJax.Hub.Config({

     

     

     

    -

    Finally, evaluate the model

    +

    Strong correlations

    +

    Images typically have strong local correlations, meaning that a small +part of the image varies little from its neighboring regions. If for +example we have an image of a blue car, we can roughly assume that a +small blue part of the image is surrounded by other blue regions. +

    - -
    -
    -
    -
    -
    -
    plt.plot(history.history['accuracy'], label='accuracy')
    -plt.plot(history.history['val_accuracy'], label = 'val_accuracy')
    -plt.xlabel('Epoch')
    -plt.ylabel('Accuracy')
    -plt.ylim([0.5, 1])
    -plt.legend(loc='lower right')
    -
    -test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)
    -
    -print(test_acc)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +

    Therefore, instead of connecting every single pixel to a neuron in the +first hidden layer, as we have previously done with deep neural +networks, we can instead connect each neuron to a small part of the +image (in all 3 RGB depth dimensions). The size of each small area is +fixed, and known as a receptive. +

    @@ -363,6 +382,16 @@ test_loss, test_acc = model47

  • 48
  • 49
  • +
  • 50
  • +
  • 51
  • +
  • 52
  • +
  • 53
  • +
  • 54
  • +
  • 55
  • +
  • 56
  • +
  • 57
  • +
  • ...
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs048.html b/doc/pub/week44/html/._week44-bs048.html index db4de1f2c..17bc78175 100644 --- a/doc/pub/week44/html/._week44-bs048.html +++ b/doc/pub/week44/html/._week44-bs048.html @@ -37,10 +37,14 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d
  • Plan for week 44
  • -
  • Material for Lecture Thursday November 2
  • -
  • Convolutional Neural Networks (recognizing images)
  • -
  • What is the Difference
  • -
  • Neural Networks vs CNNs
  • -
  • Why CNNS for images, sound files, medical images from CT scans etc?
  • -
  • Regular NNs don’t scale well to full images
  • -
  • 3D volumes of neurons
  • -
  • Layers used to build CNNs
  • -
  • Transforming images
  • -
  • CNNs in brief
  • -
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Convolution Examples: Principle of Superposition and Periodic Forces (Fourier Transforms)
  • -
  • Simple Code Example
  • -
  • Wrapping up Fourier transforms
  • -
  • Finding the Coefficients
  • -
  • Final words on Fourier Transforms
  • -
  •    Fourier transforms and convolution
  • -
  • Two-dimensional Objects
  • -
  • More on Dimensionalities
  • -
  • Further Dimensionality Remarks
  • -
  • CNNs in more detail
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • CNNs in more detail, building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • -
  • The CIFAR01 data set
  • -
  • Verifying the data set
  • -
  • Set up the model
  • -
  • Add Dense layers on top
  • -
  • Compile and train the model
  • -
  • Finally, evaluate the model
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • +
  • Lab sessions on Tuesday and Wednesday
  • +
  • Material for Lecture Monday October 28
  • +
  • Convolutional Neural Networks (recognizing images)
  • +
  • What is the Difference
  • +
  • Neural Networks vs CNNs
  • +
  • Why CNNS for images, sound files, medical images from CT scans etc?
  • +
  • Regular NNs don’t scale well to full images
  • +
  • 3D volumes of neurons
  • +
  • More on Dimensionalities
  • +
  • Further remarks
  • +
  • Layers used to build CNNs
  • +
  • Transforming images
  • +
  • CNNs in brief
  • +
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Key Idea
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -311,3260 +349,26 @@ MathJax.Hub.Config({

     

     

     

    - -

    Building our own CNN code

    + +

    Layers of a CNN

    -

    Here we present a flexible and readable python code for a CNN -implemented with NumPy. We will present the code, showcase how to use -the codebase and fit a CNN that yields a 99% accuracy on the 28x28 -MNIST dataset within reasonable time. +

    The layers of a convolutional neural network arrange neurons in 3D: width, height and depth. +The input image is typically a square matrix of depth 3.

    -

    The CNN is compatible with all schedulers, cost functions and -activation functions discussed in constructing our neural network -codes. +

    A convolution is performed on the image which outputs +a 3D volume of neurons. The weights to the input are arranged in a number of 2D matrices, known as filters.

    -

    The CNN code consists of different types of Layer classes, including -Convolution2DLayer, Pooling2DLayer, FlattenLayer, FullyConnectedLayer -and OutputLayer, which can be added to the CNN object using the -interface of the CNN class. This allows you to easily construct your -own CNN, as well as allowing you to get used to an interface similar -to that of TensorFlow which is used for real world applications. -

    - -

    Another important feature of this code is that it throws errors if -unreasonable decisions are made (for example using a kernel that is -larger than the image, not using a FlattenLayer, etc), and provides -the user with an informative error message. -

    -

    List of contents:

    -
      -
    1. Schedulers
    2. -
    3. Activation Functions
    4. -
    5. Cost Functions
    6. -
    7. Convolution
    8. -
    9. Layers
    10. -
    11. CNN
    12. -
    13. Some final remarks
    14. -
    -

    Schedulers

    - -

    The code below shows object oriented implementations of the Constant, -Momentum, Adagrad, AdagradMomentum, RMS prop and Adam schedulers. All -of the classes belong to the shared abstract Scheduler class, and -share the update_change() and reset() methods allowing for any of the -schedulers to be seamlessly used during the training stage, as will -later be shown in the fit() method of the neural -network. Update_change() only has one parameter, the gradient -(\( \delta^{l}_{j}a^{l-1}_k \)), and returns the change which will be -subtracted from the weights. The reset() function takes no parameters, -and resets the desired variables. For Constant and Momentum, reset -does nothing. -

    - - - -
    -
    -
    -
    -
    -
    import autograd.numpy as np
    -
    -class Scheduler:
    -    """
    -    Abstract class for Schedulers
    -    """
    -
    -    def __init__(self, eta):
    -        self.eta = eta
    -
    -    # should be overwritten
    -    def update_change(self, gradient):
    -        raise NotImplementedError
    -
    -    # overwritten if needed
    -    def reset(self):
    -        pass
    -
    -
    -class Constant(Scheduler):
    -    def __init__(self, eta):
    -        super().__init__(eta)
    -
    -    def update_change(self, gradient):
    -        return self.eta * gradient
    -    
    -    def reset(self):
    -        pass
    -
    -
    -class Momentum(Scheduler):
    -    def __init__(self, eta: float, momentum: float):
    -        super().__init__(eta)
    -        self.momentum = momentum
    -        self.change = 0
    -
    -    def update_change(self, gradient):
    -        self.change = self.momentum * self.change + self.eta * gradient
    -        return self.change
    -
    -    def reset(self):
    -        pass
    -
    -
    -class Adagrad(Scheduler):
    -    def __init__(self, eta):
    -        super().__init__(eta)
    -        self.G_t = None
    -
    -    def update_change(self, gradient):
    -        delta = 1e-8  # avoid division ny zero
    -
    -        if self.G_t is None:
    -            self.G_t = np.zeros((gradient.shape[0], gradient.shape[0]))
    -
    -        self.G_t += gradient @ gradient.T
    -
    -        G_t_inverse = 1 / (
    -            delta + np.sqrt(np.reshape(np.diagonal(self.G_t), (self.G_t.shape[0], 1)))
    -        )
    -        return self.eta * gradient * G_t_inverse
    -
    -    def reset(self):
    -        self.G_t = None
    -
    -
    -class AdagradMomentum(Scheduler):
    -    def __init__(self, eta, momentum):
    -        super().__init__(eta)
    -        self.G_t = None
    -        self.momentum = momentum
    -        self.change = 0
    -
    -    def update_change(self, gradient):
    -        delta = 1e-8  # avoid division ny zero
    -
    -        if self.G_t is None:
    -            self.G_t = np.zeros((gradient.shape[0], gradient.shape[0]))
    -
    -        self.G_t += gradient @ gradient.T
    -
    -        G_t_inverse = 1 / (
    -            delta + np.sqrt(np.reshape(np.diagonal(self.G_t), (self.G_t.shape[0], 1)))
    -        )
    -        self.change = self.change * self.momentum + self.eta * gradient * G_t_inverse
    -        return self.change
    -
    -    def reset(self):
    -        self.G_t = None
    -
    -
    -class RMS_prop(Scheduler):
    -    def __init__(self, eta, rho):
    -        super().__init__(eta)
    -        self.rho = rho
    -        self.second = 0.0
    -
    -    def update_change(self, gradient):
    -        delta = 1e-8  # avoid division ny zero
    -        self.second = self.rho * self.second + (1 - self.rho) * gradient * gradient
    -        return self.eta * gradient / (np.sqrt(self.second + delta))
    -
    -    def reset(self):
    -        self.second = 0.0
    -
    -
    -class Adam(Scheduler):
    -    def __init__(self, eta, rho, rho2):
    -        super().__init__(eta)
    -        self.rho = rho
    -        self.rho2 = rho2
    -        self.moment = 0
    -        self.second = 0
    -        self.n_epochs = 1
    -
    -    def update_change(self, gradient):
    -        delta = 1e-8  # avoid division ny zero
    -
    -        self.moment = self.rho * self.moment + (1 - self.rho) * gradient
    -        self.second = self.rho2 * self.second + (1 - self.rho2) * gradient * gradient
    -
    -        moment_corrected = self.moment / (1 - self.rho**self.n_epochs)
    -        second_corrected = self.second / (1 - self.rho2**self.n_epochs)
    -
    -        return self.eta * moment_corrected / (np.sqrt(second_corrected + delta))
    -
    -    def reset(self):
    -        self.n_epochs += 1
    -        self.moment = 0
    -        self.second = 0
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Usage of schedulers

    - -

    To initalize a scheduler, simply create the object and pass in the necessary parameters such as the learning rate and the momentum as shown below. As the Scheduler class is an abstract class it should not called directly, and will raise an error upon usage.

    - - - -
    -
    -
    -
    -
    -
    momentum_scheduler = Momentum(eta=1e-3, momentum=0.9)
    -adam_scheduler = Adam(eta=1e-3, rho=0.9, rho2=0.999)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Here is a small example for how a segment of code using schedulers could look. Switching out the schedulers is simple.

    - - - -
    -
    -
    -
    -
    -
    weights = np.ones((3,3))
    -print(f"Before scheduler:\n{weights=}")
    -
    -epochs = 10
    -for e in range(epochs):
    -    gradient = np.random.rand(3, 3)
    -    change = adam_scheduler.update_change(gradient)
    -    weights = weights - change
    -    adam_scheduler.reset()
    -
    -print(f"\nAfter scheduler:\n{weights=}")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Cost functions

    - -

    In this section we will quickly look at cost functions that can be -used when creating the neural network. Every cost function takes the -target vector as its parameter, and returns a function valued only at -X such that it may easily be differentiated. -

    - - - -
    -
    -
    -
    -
    -
    def CostOLS(target):
    -    """
    -    Return OLS function valued only at X, so
    -    that it may be easily differentiated
    -    """
    -
    -    def func(X):
    -        return (1.0 / target.shape[0]) * np.sum((target - X) ** 2)
    -
    -    return func
    -
    -
    -def CostLogReg(target):
    -    """
    -    Return Logistic Regression cost function
    -    valued only at X, so that it may be easily differentiated
    -    """
    -
    -    def func(X):
    -        return -(1.0 / target.shape[0]) * np.sum(
    -            (target * np.log(X + 10e-10)) + ((1 - target) * np.log(1 - X + 10e-10))
    -        )
    -
    -    return func
    -
    -
    -def CostCrossEntropy(target):
    -    """
    -    Return cross entropy cost function valued only at X, so
    -    that it may be easily differentiated
    -    """
    -    
    -    def func(X):
    -        return -(1.0 / target.size) * np.sum(target * np.log(X + 10e-10))
    -
    -    return func
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Usage of cost functions

    - -

    Below we will provide a short example of how these cost function may -be used to obtain results if you wish to test them out on your own -using AutoGrad's automatic differentiation. -

    - - - -
    -
    -
    -
    -
    -
    from autograd import grad
    -
    -target = np.array([[1, 2, 3]]).T
    -a = np.array([[4, 5, 6]]).T
    -
    -cost_func = CostCrossEntropy
    -cost_func_derivative = grad(cost_func(target))
    -
    -valued_at_a = cost_func_derivative(a)
    -print(f"Derivative of cost function {cost_func.__name__} valued at a:\n{valued_at_a}")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Activation functions

    - -

    Finally, before we look at the layers that make up the neural network, -we will look at the activation functions which can be specified -between the hidden layers and as the output function. Each function -can be valued for any given vector or matrix X, and can be -differentiated via derivate(). -

    - - - -
    -
    -
    -
    -
    -
    import autograd.numpy as np
    -from autograd import elementwise_grad
    -
    -def identity(X):
    -    return X
    -
    -
    -def sigmoid(X):
    -    try:
    -        return 1.0 / (1 + np.exp(-X))
    -    except FloatingPointError:
    -        return np.where(X > np.zeros(X.shape), np.ones(X.shape), np.zeros(X.shape))
    -
    -
    -def softmax(X):
    -    X = X - np.max(X, axis=-1, keepdims=True)
    -    delta = 10e-10
    -    return np.exp(X) / (np.sum(np.exp(X), axis=-1, keepdims=True) + delta)
    -
    -
    -def RELU(X):
    -    return np.where(X > np.zeros(X.shape), X, np.zeros(X.shape))
    -
    -
    -def LRELU(X):
    -    delta = 10e-4
    -    return np.where(X > np.zeros(X.shape), X, delta * X)
    -
    -
    -def derivate(func):
    -    if func.__name__ == "RELU":
    -
    -        def func(X):
    -            return np.where(X > 0, 1, 0)
    -
    -        return func
    -
    -    elif func.__name__ == "LRELU":
    -
    -        def func(X):
    -            delta = 10e-4
    -            return np.where(X > 0, 1, delta)
    -
    -        return func
    -
    -    else:
    -        return elementwise_grad(func)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Usage of activation functions

    - -

    Below we present a short demonstration of how to use an activation -function. The derivative of the activation function will be important -when calculating the output delta term during backpropagation. Note -that derivate() can also be used for cost functions for a more -generalized approach. -

    - - - -
    -
    -
    -
    -
    -
    z = np.array([[4, 5, 6]]).T
    -print(f"Input to activation function:\n{z}")
    -
    -act_func = sigmoid
    -a = act_func(z)
    -print(f"\nOutput from {act_func.__name__} activation function:\n{a}")
    -
    -act_func_derivative = derivate(act_func)
    -valued_at_z = act_func_derivative(a)
    -print(f"\nDerivative of {act_func.__name__} activation function valued at z:\n{valued_at_z}")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Convolution

    - -

    In order to construct a convolutional neural network (CNN), it is -crucial to comprehend the fundamental principles of convolution and -how it aids in extracting information from images. Convolution, at its -core, is merely a mathematical operation between two functions that -yields another function. It is represented by an integral between two -functions, which is typically expressed as: -

    - -$$ -(f \ast g)(t):=\int_{-\infty}^{\infty} f(\tau) g(t-\tau) d \tau. -$$ - -

    Here, f and g are the two functions on which we want to perform an -operation. The outcome of the convolution operation is represented by -\( (f \ast g) \), and it is derived by sliding the function g over f and -computing the integral of their product at each position. If both -functions are continuous, convolution takes the form shown -above. However, if we discretize both f and g, the convolution -operation will take the form of a sum between the elements of f and g: -

    -$$ -(f \ast g)[n]=\sum_{m=0}^{n-1} f[m] g[n-m]. -$$ - -

    The key idea we utilize to extract the information contained in an -image is to slide an \( m \times n \) matrix g over an \( m \times n \) -matrix f. In our case, f represents the image, while g -represents the kernel, oftentimes called a filter. However, since our -convolution will be a two-dimensional variant, we need to extend our -mathematical formula with an additional summation: -

    - -$$ -(f \ast g)[i, j]\sum_{m=0}^{M-1}\sum_{n=0}^{N-1} f[m,n] g[i-m, j-n]. -$$ - -

    It is imperative to note that the size of the kernel g is -significantly smaller than the size of the input image f, thereby -reducing the amount of computation necessary for feature -extraction. Furthermore, the kernel is usually a trainable parameter -in a convolutional neural network, allowing the network to learn -appropriate kernels for specific tasks. -

    - -

    To give you an example of how 2D convolution works in practice, -suppose we have an image f of dimension \( 6 \times 6 \) -

    - -$$ -f = \begin{bmatrix} -4 & 1 & 2 & 9 & 8 & 6 \\ -9 & 5 & 9 & 5 & 8 & 5 \\ -1 & 5 & 9 & 7 & 6 & 4 \\ -2 & 9 & 8 & 3 & 7 & 1 \\ -8 & 1 & 6 & 4 & 2 & 2 \\ -1 & 0 & 5 & 7 & 8 & 2 \\ -\end{bmatrix} -$$ - -

    and a \( 3 \times 3 \) kernel g called a low-pass filter. Note that the -kernel is usually rotated by 180 degrees during convolution, however -this has no effect on this kernel. -

    - -$$ -g = \frac{1}{9} -\begin{bmatrix} -1 & 1 & 1 \\ -1 & 1 & 1 \\ -1 & 1 & 1 \\ -\end{bmatrix} -$$ - -

    In order to filter the image, we have to extract a \( 3 \times 3 \) -element from the upper left corner of f, and perform element-wise -multiplication of the extracted image pixels with the elements of the -kernel g: -

    - -$$ -\begin{bmatrix} -4 & 1 & 2 \\ -9 & 5 & 9 \\ -1 & 5 & 9 \\ -\end{bmatrix} -\cdot -\begin{bmatrix} -\frac{1}{9} & \frac{1}{9} & \frac{1}{9} \\ -\frac{1}{9} & \frac{1}{9} & \frac{1}{9} \\ -\frac{1}{9} & \frac{1}{9} & \frac{1}{9} \\ -\end{bmatrix} -= -\begin{bmatrix} -\frac{4}{9} & \frac{1}{9} & \frac{2}{9} \\ -\frac{9}{9} & \frac{5}{9} & \frac{9}{9} \\ -\frac{1}{9} & \frac{5}{9} & \frac{9}{9} \\ -\end {bmatrix} -= \textbf{A} -$$ - -

    Then, following the multiplication, we summarize all the elements of the resulting matrix A:

    - -$$ -(f \ast g)[0, 0]= \sum_{i=0}^{2} \sum_{j=0}^{2} a_{i,j} = 5 -$$ - -

    Which corresponds to the first element of the filtered image \( (f \ast g) \).

    - -

    Here we use a stride of 1, a parameter denoted s which describes how -many indexes we move the kernel g to the right before repeating the -calculations above for the next \( 3 \times 3 \) element of the image -f. It is usually presumed that *s*=1, however, larger values for s -can be used to reduce the dimentionality of the filtered image such -that the convolution operation is more computationally efficient. In -the context of a convolutional neural network, this will become very -useful. -

    - -

    The full result of the convolution is:

    - -$$ -(f \ast g) = -\begin{bmatrix} -5 & 5.78 & 7 & 6.44 \\ -6.33 & 6.67 & 6.89 & 5.11 \\ -5.44 & 5.78 & 5.78 & 4 \\ -4.44 & 4.78 & 5.56 & 4 \\ -\end{bmatrix} -$$ - -

    The result is markedly smaller in shape than the original image. This occurs when using convolution without first padding the image with additional columns and rows, allowing us to keep the original image shape after sliding the kernel over the image. -How many rows and columns we wish to pad the image with depends strictly on the shape of the kernel, as we wish to pad the image with r additional rows and c additional columns. -

    - -$$ -r =\lfloor \frac{kernel\ height}{2} \rfloor \cdot 2 \\ -c =\lfloor \frac{kernel\ width}{2} \rfloor \cdot 2 -$$ - -

    Note the notation \( \lfloor \frac{kernel width}{2} \rfloor \) means that -we floor the result of the division, meaning we round down to a whole -number in case \( \frac{kernel width}{2} \) results in a floating point -number. -

    - -

    Using those simple equations, we find out by how much we have to -extend the dimensions of the original image. Before proceeding, -however, we might ask what we shall fill the additional rows and -columns with? One of the most common approaches to padding is -zero-padding, which as the name suggest, involves filling the rows and -columns with zeros. This is the approach that we will be using for -this demonstration. If we apply this padding to out original \( 6 \times 6 \) -image, the result will be an \( 8 \times 8 \) image as the kernel has a width and -height of 3. Note that the original image is encapsuled by the -zero-padded rows and columns: -

    - -$$ -\begin{bmatrix} -0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ -0 & 4 & 1 & 2 & 9 & 8 & 6 & 0 \\ -0 & 9 & 5 & 9 & 5 & 8 & 5 & 0 \\ -0 & 1 & 5 & 9 & 7 & 6 & 4 & 0 \\ -0 & 2 & 9 & 8 & 3 & 7 & 1 & 0 \\ -0 & 8 & 1 & 6 & 4 & 2 & 2 & 0 \\ -0 & 1 & 0 & 5 & 7 & 8 & 2 & 0 \\ -0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ -\] -\end{bmatrix} -$$ - -

    Below we have provided code that demonstrates padding and convolution. As you will see when we run the code, the size of the image will remain unchanged when using padding. 

    - - - -
    -
    -
    -
    -
    -
    import numpy as np
    -
    -def padding(image, kernel):
    -    # calculate r and c
    -    r = (kernel.shape[0] // 2) * 2
    -    c = (kernel.shape[1] // 2) * 2
    -    
    -    # padded image dimensions
    -    padded_height = image.shape[0] + r
    -    padded_width = image.shape[1] + c
    -    
    -    # for more readable code
    -    k_half_height = kernel.shape[0] // 2
    -    k_half_width = kernel.shape[1] // 2
    -
    -    # zero matrix with padded dimensions
    -    padded_img = np.zeros((padded_height, padded_width))
    -
    -    # place image into zero matrix
    -    padded_img[k_half_height : padded_height - k_half_height,
    -               k_half_width : padded_width - k_half_width] = image[:, :]
    -
    -    return padded_img
    -
    -def convolve(original_image, padded_image, kernel, stride=1):
    -    # rotate kernel by 180 degrees
    -    kernel = np.rot90(np.rot90(kernel))
    -
    -    # note that kernel height // 2 is written as 'm'
    -    # and kernel width // 2 as 'n' in the mathematical notation
    -    m = kernel.shape[0] // 2
    -    n = kernel.shape[1] // 2
    -    
    -    r = (kernel.shape[0] // 2) * 2
    -    c = (kernel.shape[1] // 2) * 2
    -    
    -    # initialize output array
    -    convolved_image = np.zeros(original_image.shape)
    -    image_height = original_image.shape[0]
    -    image_width = original_image.shape[1]
    -
    -    # the convolution
    -    for i in range(m, image_height + m, stride):
    -        for j in range(n, image_width + n, stride):
    -            convolved_image[i-m, j-n] = np.sum(
    -                padded_image[i : i + m, j : j + n]
    -                * kernel
    -            )
    -            
    -    return convolved_image
    -
    -def convolve(image, kernel, stride=1):
    -    for i in range(2):
    -        kernel = np.rot90(kernel)
    -
    -    k_half_height = kernel.shape[0] // 2
    -    k_half_width = kernel.shape[0] // 2
    -
    -    conv_image = np.zeros(image.shape)
    -    pad_image = padding(image, kernel)
    -
    -    for i in range(k_half_height, conv_image.shape[0] + k_half_height, stride):
    -        for j in range(k_half_width, conv_image.shape[1] + k_half_width, stride):
    -            conv_image[i - k_half_height, j - k_half_width] = np.sum(
    -                pad_image[
    -                    i - k_half_height : i + k_half_height + 1, j - k_half_width : j + k_half_width + 1
    -                ]
    -                * kernel
    -            )
    -
    -    return conv_image
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Fun fact: When filtering images, you will see that convolution involves rotating the kernel by 180 degrees. -However, this is not the case when applying convolution in a CNN, where the same operation not rotated by 180 degrees is called -cross-correlation. -

    - - - -
    -
    -
    -
    -
    -
    original_image = np.array([[4, 1, 2, 9, 8, 6],
    -                 [9, 5, 9, 5, 8, 5],
    -                 [1, 5, 9, 7, 6, 4],
    -                 [2, 9, 8, 3, 7, 1],
    -                 [8, 1, 6, 4, 2, 2],
    -                 [1, 0, 5, 7, 8, 2]])
    -
    -kernel = (1/9)*np.ones((3,3))
    -
    -print(f"{original_image.shape=}")
    -
    -# note that convolve() performs padding
    -convolved_image = convolve(original_image, kernel, stride=1)
    -
    -print(f"{convolved_image.shape=}")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    As you can see, the resulting image is of the same size as the -original image. To round of our demonstration of convolution, we will -present the results of convolution using commonly used kernels. In a -CNN, the values of the kernels are randomly initialized, and then -learned during training. These kernels will extract information -regarding the picture, such as for example the edge detection filter -demonstrated below extracts the edges present in the picture. Of -course, there is no guarantee that the CNN will learn an edge -detection filter, but this should provide some intuiton as to how the -CNN is able to use kernels to make better predictions than a regular -feed forward neural network. -

    - - - -
    -
    -
    -
    -
    -
    # Now an example using a real image and first a gaussian low-pass filter and then a sobel filter
    -import numpy as np
    -import imageio.v3 as imageio
    -import matplotlib.pyplot as plt
    -import time
    -
    -def generate_gauss_mask(sigma, K=1):
    -    side = np.ceil(1 + 8 * sigma)
    -    y, x = np.mgrid[-side // 2 + 1 : (side // 2) + 1, -side // 2 + 1 : (side // 2) + 1]
    -    ker_coef = K / (2 * np.pi * sigma**2)
    -    g = np.exp(-((x**2 + y**2) / (2.0 * sigma**2)))
    -
    -    return g, ker_coef
    -
    -
    -img_path = "data/IMG-2167.JPG"
    -image_of_cute_dog = imageio.imread(img_path, mode='L')
    -
    -plt.imshow(image_of_cute_dog, cmap="gray", vmin=0, vmax=255, aspect="auto")
    -plt.title("Original image")
    -plt.show()
    -
    -gauss, kernel = generate_gauss_mask(sigma=6)
    -gauss_kernel = gauss*kernel
    -
    -filtered_image = convolve(image_of_cute_dog, gauss_kernel)
    -plt.imshow(filtered_image, cmap="gray", vmin=0, vmax=255, aspect="auto")
    -plt.title("Result of convolution with gauss kernel (blurring filter)")
    -plt.show()
    -
    -sobel_kernel = np.array([[1, 2, 1],
    -                    [0, 0, 0], 
    -                    [-1, -2, -1]])
    -
    -filtered_image = convolve(image_of_cute_dog, sobel_kernel)
    -
    -plt.imshow(filtered_image, cmap="gray", vmin=0, vmax=255, aspect="auto")
    -plt.title("Result of convolution with sobel kernel (edge detection filter)")
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Layers

    - -

    The code below initialises global variables for readability and -describes the abstract class Layers. This is not important in order to -understand the CNN, but is benefitial for organizing the code neatly. -

    - - - -
    -
    -
    -
    -
    -
    import math
    -import autograd.numpy as np
    -from copy import deepcopy, copy
    -from autograd import grad
    -from typing import Callable
    -
    -# global variables for index readability
    -input_index = 0
    -node_index = 1
    -bias_index = 1
    -input_channel_index = 1
    -feature_maps_index = 1
    -height_index = 2
    -width_index = 3
    -kernel_feature_maps_index = 1
    -kernel_input_channels_index = 0
    -
    -
    -class Layer:
    -    def __init__(self, seed):
    -        self.seed = seed
    -
    -    def _feedforward(self):
    -        raise NotImplementedError
    -
    -    def _backpropagate(self):
    -        raise NotImplementedError
    -
    -    def _reset_weights(self, previous_nodes):
    -        raise NotImplementedError
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Convolution2DLayer: convolution in a hidden layer

    - -

    After establishing the foundational understanding of applying -convolution to spatial data, let us delve into the intricate workings -of a convolutional layer in a Convolutional Neural Network (CNN). The -primary function of convolution, as previously discussed, is to -extract pertinent information from images while simultaneously -decreasing the scale of our data. To initiate the image processing, we -shall begin by partitioning the images into color channels (unless the -image is grayscale), comprising three primary colors: red, green, and -blue. We will subsequently utilize trainable kernels to construct a -higher-dimensional encoding of each channel called feature -maps. Successive layers will receive these feature maps as inputs, -generating further encodings, albeit with reduced dimensions. The term -trainable kernels denotes the initialization of pre-defined -kernel-shaped weights, which we will then train via backpropagation, -similar to how weights are trained in a Feedforward Neural Network. -

    - -

    To ensure seamless integration between our implementation of the -convolutional layer and popular machine learning frameworks like -Tensorflow (Keras) and PyTorch, we have adopted a design pattern that -mirrors the construction of models using these APIs. This involves -implementing our convolutional layer as a Python class or object, -which allows for a more modular and flexible approach to building -neural networks. By structuring our code in this way, users can easily -incorporate our implementation into their existing machine learning -pipelines without having to make significant changes to their -codebase. Additionally, this design pattern promotes code reusability -and makes it easier to maintain and update our convolutional layer -implementation over time. -

    - -

    Note that the Convolution2DLayer takes in an activation function as a parameter, as it also performs non-linearity.

    - - - -
    -
    -
    -
    -
    -
    class Convolution2DLayer(Layer):
    -    def __init__(
    -        self,
    -        input_channels,
    -        feature_maps,
    -        kernel_height,
    -        kernel_width,
    -        v_stride,
    -        h_stride,
    -        pad,
    -        act_func: Callable,
    -        seed=None,
    -        reset_weights_independently=True,
    -    ):
    -        super().__init__(seed)
    -        self.input_channels = input_channels
    -        self.feature_maps = feature_maps
    -        self.kernel_height = kernel_height
    -        self.kernel_width = kernel_width
    -        self.v_stride = v_stride
    -        self.h_stride = h_stride
    -        self.pad = pad
    -        self.act_func = act_func
    -
    -        # such that the layer can be used on its own
    -        # outside of the CNN module
    -        if reset_weights_independently == True:
    -            self._reset_weights_independently()
    -
    -    def _feedforward(self, X_batch):
    -        # note that the shape of X_batch = [inputs, input_maps, img_height, img_width]
    -
    -        # pad the input batch
    -        X_batch_padded = self._padding(X_batch)
    -
    -        # calculate height_index and width_index after stride
    -        strided_height = int(np.ceil(X_batch.shape[height_index] / self.v_stride))
    -        strided_width = int(np.ceil(X_batch.shape[width_index] / self.h_stride))
    -
    -        # create output array
    -        output = np.ndarray(
    -            (
    -                X_batch.shape[input_index],
    -                self.feature_maps,
    -                strided_height,
    -                strided_width,
    -            )
    -        )
    -
    -        # save input and output for backpropagation
    -        self.X_batch_feedforward = X_batch
    -        self.output_shape = output.shape
    -
    -        # checking for errors, no need to look here :)
    -        self._check_for_errors()
    -
    -        # convolve input with kernel
    -        for img in range(X_batch.shape[input_index]):
    -            for chin in range(self.input_channels):
    -                for fmap in range(self.feature_maps):
    -                    out_h = 0
    -                    for h in range(0, X_batch.shape[height_index], self.v_stride):
    -                        out_w = 0
    -                        for w in range(0, X_batch.shape[width_index], self.h_stride):
    -                            output[img, fmap, out_h, out_w] = np.sum(
    -                                X_batch_padded[
    -                                    img,
    -                                    chin,
    -                                    h : h + self.kernel_height,
    -                                    w : w + self.kernel_width,
    -                                ]
    -                                * self.kernel[chin, fmap, :, :]
    -                            )
    -                            out_w += 1
    -                        out_h += 1
    -
    -        # Pay attention to the fact that we're not rotating the kernel by 180 degrees when filtering the image in
    -        # the convolutional layer, as convolution in terms of Machine Learning is a procedure known as cross-correlation
    -        # in image processing and signal processing
    -
    -        # return a
    -        return self.act_func(output / (self.kernel_height))
    -
    -    def _backpropagate(self, delta_term_next):
    -        # intiate matrices
    -        delta_term = np.zeros((self.X_batch_feedforward.shape))
    -        gradient_kernel = np.zeros((self.kernel.shape))
    -
    -        # pad input for convolution
    -        X_batch_padded = self._padding(self.X_batch_feedforward)
    -
    -        # Since an activation function is used at the output of the convolution layer, its derivative
    -        # has to be accounted for in the backpropagation -> as if ReLU was a layer on its own.
    -        act_derivative = derivate(self.act_func)
    -        delta_term_next = act_derivative(delta_term_next)
    -
    -        # fill in 0's for values removed by vertical stride in feedforward
    -        if self.v_stride > 1:
    -            v_ind = 1
    -            for i in range(delta_term_next.shape[height_index]):
    -                for j in range(self.v_stride - 1):
    -                    delta_term_next = np.insert(
    -                        delta_term_next, v_ind, 0, axis=height_index
    -                    )
    -                v_ind += self.v_stride
    -
    -        # fill in 0's for values removed by horizontal stride in feedforward
    -        if self.h_stride > 1:
    -            h_ind = 1
    -            for i in range(delta_term_next.shape[width_index]):
    -                for k in range(self.h_stride - 1):
    -                    delta_term_next = np.insert(
    -                        delta_term_next, h_ind, 0, axis=width_index
    -                    )
    -                h_ind += self.h_stride
    -
    -        # crops out 0-rows and 0-columns
    -        delta_term_next = delta_term_next[
    -            :,
    -            :,
    -            : self.X_batch_feedforward.shape[height_index],
    -            : self.X_batch_feedforward.shape[width_index],
    -        ]
    -
    -        # the gradient received from the next layer also needs to be padded
    -        delta_term_next = self._padding(delta_term_next)
    -
    -        # calculate delta term by convolving next delta term with kernel
    -        for img in range(self.X_batch_feedforward.shape[input_index]):
    -            for chin in range(self.input_channels):
    -                for fmap in range(self.feature_maps):
    -                    for h in range(self.X_batch_feedforward.shape[height_index]):
    -                        for w in range(self.X_batch_feedforward.shape[width_index]):
    -                            delta_term[img, chin, h, w] = np.sum(
    -                                delta_term_next[
    -                                    img,
    -                                    fmap,
    -                                    h : h + self.kernel_height,
    -                                    w : w + self.kernel_width,
    -                                ]
    -                                * np.rot90(np.rot90(self.kernel[chin, fmap, :, :]))
    -                            )
    -
    -        # calculate gradient for kernel for weight update
    -        # also via convolution
    -        for chin in range(self.input_channels):
    -            for fmap in range(self.feature_maps):
    -                for k_x in range(self.kernel_height):
    -                    for k_y in range(self.kernel_width):
    -                        gradient_kernel[chin, fmap, k_x, k_y] = np.sum(
    -                            X_batch_padded[
    -                                img,
    -                                chin,
    -                                h : h + self.kernel_height,
    -                                w : w + self.kernel_width,
    -                            ]
    -                            * delta_term_next[
    -                                img,
    -                                fmap,
    -                                h : h + self.kernel_height,
    -                                w : w + self.kernel_width,
    -                            ]
    -                        )
    -        # all kernels are updated with weight gradient of kernel
    -        self.kernel -= gradient_kernel
    -
    -        # return delta term
    -        return delta_term
    -
    -    def _padding(self, X_batch, batch_type="image"):
    -
    -        # same padding for images
    -        if self.pad == "same" and batch_type == "image":
    -            padded_height = X_batch.shape[height_index] + (self.kernel_height // 2) * 2
    -            padded_width = X_batch.shape[width_index] + (self.kernel_width // 2) * 2
    -            half_kernel_height = self.kernel_height // 2
    -            half_kernel_width = self.kernel_width // 2
    -
    -            # initialize padded array
    -            X_batch_padded = np.ndarray(
    -                (
    -                    X_batch.shape[input_index],
    -                    X_batch.shape[feature_maps_index],
    -                    padded_height,
    -                    padded_width,
    -                )
    -            )
    -
    -            # zero pad all images in X_batch
    -            for img in range(X_batch.shape[input_index]):
    -                padded_img = np.zeros(
    -                    (X_batch.shape[feature_maps_index], padded_height, padded_width)
    -                )
    -                padded_img[
    -                    :,
    -                    half_kernel_height : padded_height - half_kernel_height,
    -                    half_kernel_width : padded_width - half_kernel_width,
    -                ] = X_batch[img, :, :, :]
    -                X_batch_padded[img, :, :, :] = padded_img[:, :, :]
    -
    -            return X_batch_padded
    -
    -        # same padding for gradients
    -        elif self.pad == "same" and batch_type == "grad":
    -            padded_height = X_batch.shape[height_index] + (self.kernel_height // 2) * 2
    -            padded_width = X_batch.shape[width_index] + (self.kernel_width // 2) * 2
    -            half_kernel_height = self.kernel_height // 2
    -            half_kernel_width = self.kernel_width // 2
    -
    -            # initialize padded array
    -            delta_term_padded = np.zeros(
    -                (
    -                    X_batch.shape[input_index],
    -                    X_batch.shape[feature_maps_index],
    -                    padded_height,
    -                    padded_width,
    -                )
    -            )
    -
    -            # zero pad delta term
    -            delta_term_padded[
    -                :, :, : X_batch.shape[height_index], : X_batch.shape[width_index]
    -            ] = X_batch[:, :, :, :]
    -
    -            return delta_term_padded
    -
    -        else:
    -            return X_batch
    -
    -    def _reset_weights_independently(self):
    -        # sets seed to remove randomness inbetween runs
    -        if self.seed is not None:
    -            np.random.seed(self.seed)
    -
    -        # initializes kernel matrix
    -        self.kernel = np.ndarray(
    -            (
    -                self.input_channels,
    -                self.feature_maps,
    -                self.kernel_height,
    -                self.kernel_width,
    -            )
    -        )
    -
    -        # randomly initializes weights
    -        for chin in range(self.kernel.shape[kernel_input_channels_index]):
    -            for fmap in range(self.kernel.shape[kernel_feature_maps_index]):
    -                self.kernel[chin, fmap, :, :] = np.random.rand(
    -                    self.kernel_height, self.kernel_width
    -                )
    -
    -    def _reset_weights(self, previous_nodes):
    -        # sets weights
    -        self._reset_weights_independently()
    -
    -        # returns shape of output used for subsequent layer's weight initiation
    -        strided_height = int(
    -            np.ceil(previous_nodes.shape[height_index] / self.v_stride)
    -        )
    -        strided_width = int(np.ceil(previous_nodes.shape[width_index] / self.h_stride))
    -        next_nodes = np.ones(
    -            (
    -                previous_nodes.shape[input_index],
    -                self.feature_maps,
    -                strided_height,
    -                strided_width,
    -            )
    -        )
    -        return next_nodes / self.kernel_height
    -
    -    def _check_for_errors(self):
    -        if self.X_batch_feedforward.shape[input_channel_index] != self.input_channels:
    -            raise AssertionError(
    -                f"ERROR: Number of input channels in data ({self.X_batch_feedforward.shape[input_channel_index]}) is not equal to input channels in Convolution2DLayerOPT ({self.input_channels})! Please change the number of input channels of the Convolution2DLayer such that they are equal"
    -            )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Backpropagation in the convolutional layer

    - -

    As you may have noticed, we have not yet explained how the -backpropagation algorithm works in a convolutional layer. However, -having covered all other major details about convolutional layers, we -are now prepared to do so. It should come as no surprise that the -calculation of delta terms at each convolutional layer takes the form -of convolution. After the gradient has been propagated backwards -through the flattening layer, where it was reshaped into an -appropriate form, calculating the update value for the kernel is -simply a matter of convolving the output gradient with the input of -the layer for which we are updating the weights. For more detail, this -article serves as an excellent resource, see -https://pavisj.medium.com/convolutions-and-backpropagations-46026a8f5d2c -

    -

    Demonstration

    - -

    We can use the convolutional layer above to perform a simple convolution on an image of the now familiar cute dog.

    - - - -
    -
    -
    -
    -
    -
    import numpy as np
    -import imageio.v3 as imageio
    -import matplotlib.pyplot as plt
    -
    -def plot_convolution_result(X, layer):
    -    plt.imshow(X[0, 0, :, :], vmin=0, vmax=255, cmap="gray")
    -    plt.title("Original image")
    -    plt.colorbar()
    -    plt.show()
    -    conv_result = layer._feedforward(X)
    -    plt.title("Result of convolutional layer")
    -    plt.imshow(conv_result[0, 0, :, :], vmin=0, vmax=255, cmap="gray")
    -    plt.colorbar()
    -    plt.show()
    -
    -# create layer
    -layer = Convolution2DLayer(
    -    input_channels=3,
    -    feature_maps=1,
    -    kernel_height=4,
    -    kernel_width=4,
    -    v_stride=2,
    -    h_stride=2,
    -    pad="same",
    -    act_func=identity,
    -    seed=2023,
    -    )
    -
    -# read in image path, make data correct format
    -img_path = img_path = "data/IMG-2167.JPG"
    -image_of_cute_dog = imageio.imread(img_path)
    -image_shape = image_of_cute_dog.shape
    -image_of_cute_dog = image_of_cute_dog.reshape(1, image_shape[0], image_shape[1], image_shape[2])
    -image_of_cute_dog = image_of_cute_dog.transpose(0, 3, 1, 2)
    -
    -# plot the result of the convolution
    -plot_convolution_result(image_of_cute_dog, layer)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    We cobserve that the result has half the pixels on each axis due to -the fact that we've used a horizontal and vertical stride of 2. The -result of this convolution is not very insightfull, as the kernel has -completely random values for the first feedforward pass. However, as -we perform multiple forward and backward passes, the results of the -convolution should provide identifying features of the image it uses -for classification. -

    - -

    Note that image data usually comes in many different shapes and sizes, -but for our CNN we require the input data be formatted as \[Number of -inputs, input channels, input height, input width\]. Occasionally, the -data you come accross use will be formatted like this, but on many -occasions reshaping and transposing the dimensions is sadly necessary. -

    -

    Pooling Layer

    - -

    The pooling layer is another widely used type of layer in -convolutional neural networks that enables data downsampling to a more -manageable size. Despite recent technological advancements that allow -for convolution without excessive size reduction of the data, the -pooling layer still remains a fundamental component of convolutional -neural networks. It can be used before, after, or in between -convolutional layers, although finding the optimal placement of layers -and network depth requires experimentation to achieve the best -performance for a given problem. The code we provide allows you to -perform two types of pooling known as max pooling and average pooling. -

    - - - -
    -
    -
    -
    -
    -
    class Pooling2DLayer(Layer):
    -    def __init__(
    -        self,
    -        kernel_height,
    -        kernel_width,
    -        v_stride,
    -        h_stride,
    -        pooling="max",
    -        seed=None,
    -    ):
    -        super().__init__(seed)
    -        self.kernel_height = kernel_height
    -        self.kernel_width = kernel_width
    -        self.v_stride = v_stride
    -        self.h_stride = h_stride
    -        self.pooling = pooling
    -
    -    def _feedforward(self, X_batch):
    -        # Saving the input for use in the backwardpass
    -        self.X_batch_feedforward = X_batch
    -
    -        # check if user is silly
    -        self._check_for_errors()
    -
    -        # Computing the size of the feature maps based on kernel size and the stride parameter
    -        strided_height = (
    -            X_batch.shape[height_index] - self.kernel_height
    -        ) // self.v_stride + 1
    -        if X_batch.shape[height_index] == X_batch.shape[width_index]:
    -            strided_width = strided_height
    -        else:
    -            strided_width = (
    -                X_batch.shape[width_index] - self.kernel_width
    -            ) // self.h_stride + 1
    -
    -        # initialize output array
    -        output = np.ndarray(
    -            (
    -                X_batch.shape[input_index],
    -                X_batch.shape[feature_maps_index],
    -                strided_height,
    -                strided_width,
    -            )
    -        )
    -
    -        # select pooling action, either max or average pooling
    -        if self.pooling == "max":
    -            self.pooling_action = np.max
    -        elif self.pooling == "average":
    -            self.pooling_action = np.mean
    -
    -        # pool based on kernel size and stride
    -        for img in range(output.shape[input_index]):
    -            for fmap in range(output.shape[feature_maps_index]):
    -                for h in range(strided_height):
    -                    for w in range(strided_width):
    -                        output[img, fmap, h, w] = self.pooling_action(
    -                            X_batch[
    -                                img,
    -                                fmap,
    -                                (h * self.v_stride) : (h * self.v_stride)
    -                                + self.kernel_height,
    -                                (w * self.h_stride) : (w * self.h_stride)
    -                                + self.kernel_width,
    -                            ]
    -                        )
    -
    -        # output for feedforward in next layer
    -        return output
    -
    -    def _backpropagate(self, delta_term_next):
    -        # initiate delta term array
    -        delta_term = np.zeros((self.X_batch_feedforward.shape))
    -
    -        for img in range(delta_term_next.shape[input_index]):
    -            for fmap in range(delta_term_next.shape[feature_maps_index]):
    -                for h in range(0, delta_term_next.shape[height_index], self.v_stride):
    -                    for w in range(
    -                        0, delta_term_next.shape[width_index], self.h_stride
    -                    ):
    -                        # max pooling
    -                        if self.pooling == "max":
    -                            # get window
    -                            window = self.X_batch_feedforward[
    -                                img,
    -                                fmap,
    -                                h : h + self.kernel_height,
    -                                w : w + self.kernel_width,
    -                            ]
    -
    -                            # find max values indices in window
    -                            max_h, max_w = np.unravel_index(
    -                                window.argmax(), window.shape
    -                            )
    -
    -                            # set values in new, upsampled delta term
    -                            delta_term[
    -                                img,
    -                                fmap,
    -                                (h + max_h),
    -                                (w + max_w),
    -                            ] += delta_term_next[img, fmap, h, w]
    -
    -                        # average pooling
    -                        if self.pooling == "average":
    -                            delta_term[
    -                                img,
    -                                fmap,
    -                                h : h + self.kernel_height,
    -                                w : w + self.kernel_width,
    -                            ] = (
    -                                delta_term_next[img, fmap, h, w]
    -                                / self.kernel_height
    -                                / self.kernel_width
    -                            )
    -        # returns input to backpropagation in previous layer
    -        return delta_term
    -
    -    def _reset_weights(self, previous_nodes):
    -        # calculate strided height, strided width
    -        strided_height = (
    -            previous_nodes.shape[height_index] - self.kernel_height
    -        ) // self.v_stride + 1
    -        if previous_nodes.shape[height_index] == previous_nodes.shape[width_index]:
    -            strided_width = strided_height
    -        else:
    -            strided_width = (
    -                previous_nodes.shape[width_index] - self.kernel_width
    -            ) // self.h_stride + 1
    -
    -        # initiate output array
    -        output = np.ones(
    -            (
    -                previous_nodes.shape[input_index],
    -                previous_nodes.shape[feature_maps_index],
    -                strided_height,
    -                strided_width,
    -            )
    -        )
    -
    -        # returns output with shape used for reset weights in next layer
    -        return output
    -
    -    def _check_for_errors(self):
    -        # check if input is smaller than kernel size -> error
    -        assert (
    -            self.X_batch_feedforward.shape[width_index] >= self.kernel_width
    -        ), f"ERROR: Pooling kernel width_index ({self.kernel_width}) larger than data width_index ({self.X_batch_feedforward.input.shape[2]}), please lower the kernel width_index of the Pooling2DLayer"
    -        assert (
    -            self.X_batch_feedforward.shape[height_index] >= self.kernel_height
    -        ), f"ERROR: Pooling kernel height_index ({self.kernel_height}) larger than data height_index ({self.X_batch_feedforward.input.shape[3]}), please lower the kernel height_index of the Pooling2DLayer"
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Flattening Layer

    - -

    Before we can begin building our first CNN model, we need to introduce -the flattening layer. As its name suggests, the flattening layer -transforms the data into a one-dimensional vector that can be fed into -the feedforward layers of our network. This layer plays a crucial role -in preparing the data for further processing in the -network. Additionally, the flattening layer is responsible for -reshaping the gradient to the proper shape during -backpropagation. This ensures that the kernels are correctly updated, -allowing for effective learning in the network. -

    - - - -
    -
    -
    -
    -
    -
    class FlattenLayer(Layer):
    -    def __init__(self, act_func=LRELU, seed=None):
    -        super().__init__(seed)
    -        self.act_func = act_func
    -
    -    def _feedforward(self, X_batch):
    -        # save input for backpropagation
    -        self.X_batch_feedforward_shape = X_batch.shape
    -        # Remember, the data has the following shape: (I, FM, H, W, ) in the convolutional layers
    -        # whilst the data has the shape (I, FM * H * W) in the fully connected layers
    -        # I = Inputs, FM = Feature Maps, H = Height and W = Width.
    -        X_batch = X_batch.reshape(
    -            X_batch.shape[input_index],
    -            X_batch.shape[feature_maps_index]
    -            * X_batch.shape[height_index]
    -            * X_batch.shape[width_index],
    -        )
    -
    -        # add bias to a
    -        self.z_matrix = X_batch
    -        bias = np.ones((X_batch.shape[input_index], 1)) * 0.01
    -        self.a_matrix = np.hstack([bias, X_batch])
    -
    -        # return a, the input to feedforward in next layer
    -        return self.a_matrix
    -
    -    def _backpropagate(self, weights_next, delta_term_next):
    -        activation_derivative = derivate(self.act_func)
    -
    -        # calculate delta term
    -        delta_term = (
    -            weights_next[bias_index:, :] @ delta_term_next.T
    -        ).T * activation_derivative(self.z_matrix)
    -
    -        # FlattenLayer does not update weights
    -        # reshapes delta layer to convolutional layer data format [Input, Feature_Maps, Height, Width]
    -        return delta_term.reshape(self.X_batch_feedforward_shape)
    -
    -    def _reset_weights(self, previous_nodes):
    -        # note that the previous nodes to the FlattenLayer are from the convolutional layers
    -        previous_nodes = previous_nodes.reshape(
    -            previous_nodes.shape[input_index],
    -            previous_nodes.shape[feature_maps_index]
    -            * previous_nodes.shape[height_index]
    -            * previous_nodes.shape[width_index],
    -        )
    -
    -        # return shape used in reset_weights in next layer
    -        return previous_nodes.shape[node_index]
    -
    -    def get_prev_a(self):
    -        return self.a_matrix
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Fully Connected Layers

    - -

    Finally, the result from the flatten layer will pass to a series of -fully connected layers, which function as a normal feed forward neural -network. The fully connected layers are split into two classes; -FullyConnectedLayer which acts as a hidden layer, and OutputLayer, -which acts as the single output layer at the end of the CNN. If one -wishes to use this codebase to construct a normal feed forward neural -network, it must start with a FlattenLayer due to techincal details -regarding weight intitialization. However many FullyConnectedLayers -can be added to the CNN, and in each layer the amount of nodes, which -activation function and scheduler to use can be specified. In -practice, the scheduler will be specified in the CNN object -initialization, and inherited if no other scheduler is specified. -

    - - - -
    -
    -
    -
    -
    -
    class FullyConnectedLayer(Layer):
    -    # FullyConnectedLayer per default uses LRELU and Adam scheduler
    -    # with an eta of 0.0001, rho of 0.9 and rho2 of 0.999
    -    def __init__(
    -        self,
    -        nodes: int,
    -        act_func: Callable = LRELU,
    -        scheduler: Scheduler = Adam(eta=1e-4, rho=0.9, rho2=0.999),
    -        seed: int = None,
    -    ):
    -        super().__init__(seed)
    -        self.nodes = nodes
    -        self.act_func = act_func
    -        self.scheduler_weight = copy(scheduler)
    -        self.scheduler_bias = copy(scheduler)
    -
    -        # initiate matrices for later
    -        self.weights = None
    -        self.a_matrix = None
    -        self.z_matrix = None
    -
    -    def _feedforward(self, X_batch):
    -        # calculate z
    -        self.z_matrix = X_batch @ self.weights
    -
    -        # calculate a, add bias
    -        bias = np.ones((X_batch.shape[input_index], 1)) * 0.01
    -        self.a_matrix = self.act_func(self.z_matrix)
    -        self.a_matrix = np.hstack([bias, self.a_matrix])
    -
    -        # return a, the input for feedforward in next layer
    -        return self.a_matrix
    -
    -    def _backpropagate(self, weights_next, delta_term_next, a_previous, lam):
    -        # take the derivative of the activation function
    -        activation_derivative = derivate(self.act_func)
    -
    -        # calculate the delta term
    -        delta_term = (
    -            weights_next[bias_index:, :] @ delta_term_next.T
    -        ).T * activation_derivative(self.z_matrix)
    -
    -        # intitiate matrix to store gradient
    -        # note that we exclude the bias term, which we will calculate later
    -        gradient_weights = np.zeros(
    -            (
    -                a_previous.shape[input_index],
    -                a_previous.shape[node_index] - bias_index,
    -                delta_term.shape[node_index],
    -            )
    -        )
    -
    -        # calculate gradient = delta term * previous a
    -        for i in range(len(delta_term)):
    -            gradient_weights[i, :, :] = np.outer(
    -                a_previous[i, bias_index:], delta_term[i, :]
    -            )
    -
    -        # sum the gradient, divide by input_index
    -        gradient_weights = np.mean(gradient_weights, axis=input_index)
    -        # for the bias gradient we do not multiply by previous a
    -        gradient_bias = np.mean(delta_term, axis=input_index).reshape(
    -            1, delta_term.shape[node_index]
    -        )
    -
    -        # regularization term
    -        gradient_weights += self.weights[bias_index:, :] * lam
    -
    -        # send gradients into scheduler
    -        # returns update matrix which will be used to update the weights and bias
    -        update_matrix = np.vstack(
    -            [
    -                self.scheduler_bias.update_change(gradient_bias),
    -                self.scheduler_weight.update_change(gradient_weights),
    -            ]
    -        )
    -
    -        # update weights
    -        self.weights -= update_matrix
    -
    -        # return weights and delta term, input for backpropagation in previous layer
    -        return self.weights, delta_term
    -
    -    def _reset_weights(self, previous_nodes):
    -        # sets seed to remove randomness inbetween runs
    -        if self.seed is not None:
    -            np.random.seed(self.seed)
    -
    -        # add bias, initiate random weights
    -        bias = 1
    -        self.weights = np.random.randn(previous_nodes + bias, self.nodes)
    -
    -        # returns number of nodes, used for reset_weights in next layer
    -        return self.nodes
    -
    -    def _reset_scheduler(self):
    -        # resets scheduler per epoch
    -        self.scheduler_weight.reset()
    -        self.scheduler_bias.reset()
    -
    -    def get_prev_a(self):
    -        # returns a matrix, used in backpropagation
    -        return self.a_matrix
    -
    -
    -class OutputLayer(FullyConnectedLayer):
    -    def __init__(
    -        self,
    -        nodes: int,
    -        output_func: Callable = LRELU,
    -        cost_func: Callable = CostCrossEntropy,
    -        scheduler: Scheduler = Adam(eta=1e-4, rho=0.9, rho2=0.999),
    -        seed: int = None,
    -    ):
    -        super().__init__(nodes, output_func, copy(scheduler), seed)
    -        self.cost_func = cost_func
    -
    -        # initiate matrices for later
    -        self.weights = None
    -        self.a_matrix = None
    -        self.z_matrix = None
    -
    -        # decides if the output layer performs binary or multi-class classification
    -        self._set_pred_format()
    -
    -    def _feedforward(self, X_batch: np.ndarray):
    -        # calculate a, z
    -        # note that bias is not added as this would create an extra output class
    -        self.z_matrix = X_batch @ self.weights
    -        self.a_matrix = self.act_func(self.z_matrix)
    -
    -        # returns prediction
    -        return self.a_matrix
    -
    -    def _backpropagate(self, target, a_previous, lam):
    -        # note that in the OutputLayer the activation function is the output function
    -        activation_derivative = derivate(self.act_func)
    -
    -        # calculate output delta terms
    -        # for multi-class or binary classification
    -        if self.pred_format == "Multi-class":
    -            delta_term = self.a_matrix - target
    -        else:
    -            cost_func_derivative = grad(self.cost_func(target))
    -            delta_term = activation_derivative(self.z_matrix) * cost_func_derivative(
    -                self.a_matrix
    -            )
    -
    -        # intiate matrix that stores gradient
    -        gradient_weights = np.zeros(
    -            (
    -                a_previous.shape[input_index],
    -                a_previous.shape[node_index] - bias_index,
    -                delta_term.shape[node_index],
    -            )
    -        )
    -
    -        # calculate gradient = delta term * previous a
    -        for i in range(len(delta_term)):
    -            gradient_weights[i, :, :] = np.outer(
    -                a_previous[i, bias_index:], delta_term[i, :]
    -            )
    -
    -        # sum the gradient, divide by input_index
    -        gradient_weights = np.mean(gradient_weights, axis=input_index)
    -        # for the bias gradient we do not multiply by previous a
    -        gradient_bias = np.mean(delta_term, axis=input_index).reshape(
    -            1, delta_term.shape[node_index]
    -        )
    -
    -        # regularization term
    -        gradient_weights += self.weights[bias_index:, :] * lam
    -
    -        # send gradients into scheduler
    -        # returns update matrix which will be used to update the weights and bias
    -        update_matrix = np.vstack(
    -            [
    -                self.scheduler_bias.update_change(gradient_bias),
    -                self.scheduler_weight.update_change(gradient_weights),
    -            ]
    -        )
    -
    -        # update weights
    -        self.weights -= update_matrix
    -
    -        # return weights and delta term, input for backpropagation in previous layer
    -        return self.weights, delta_term
    -
    -    def _reset_weights(self, previous_nodes):
    -        # sets seed to remove randomness inbetween runs
    -        if self.seed is not None:
    -            np.random.seed(self.seed)
    -
    -        # add bias, initiate random weights
    -        bias = 1
    -        self.weights = np.random.rand(previous_nodes + bias, self.nodes)
    -
    -        # returns number of nodes, used for reset_weights in next layer
    -        return self.nodes
    -
    -    def _reset_scheduler(self):
    -        # resets scheduler per epoch
    -        self.scheduler_weight.reset()
    -        self.scheduler_bias.reset()
    -
    -    def _set_pred_format(self):
    -        # sets prediction format to either regression, binary or multi-class classification
    -        if self.act_func.__name__ is None or self.act_func.__name__ == "identity":
    -            self.pred_format = "Regression"
    -        elif self.act_func.__name__ == "sigmoid" or self.act_func.__name__ == "tanh":
    -            self.pred_format = "Binary"
    -        else:
    -            self.pred_format = "Multi-class"
    -
    -    def get_pred_format(self):
    -        # returns format of prediction
    -        return self.pred_format
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Optimized Convolution2DLayer

    - -

    For our CNN, we have also implemented an optimized version of the -Convolution2DLayer, Convolution2DLayerOPT, which runs much faster. See -VII. Remarks for discussion. This layer will per default be used by -the CNN due to its computational advantages, but is much less -readable. We've documented it such that specially interested students -can understand the principles behind it, but it is not recommended to -read. In short, we reshape and transpose parts of the image such that -the convolutional operation can be swapped out for a simple matrix -multiplication. -

    - - - -
    -
    -
    -
    -
    -
    class Convolution2DLayerOPT(Convolution2DLayer):
    -    """
    -    Am optimized version of the convolution layer above which
    -    utilizes an approach of extracting windows of size equivalent
    -    in size to the filter. The convoution is then performed on those
    -    windows instead of a full feature map.
    -    """
    -
    -    def __init__(
    -        self,
    -        input_channels,
    -        feature_maps,
    -        kernel_height,
    -        kernel_width,
    -        v_stride,
    -        h_stride,
    -        pad,
    -        act_func: Callable,
    -        seed=None,
    -        reset_weights_independently=True,
    -    ):
    -        super().__init__(
    -            input_channels,
    -            feature_maps,
    -            kernel_height,
    -            kernel_width,
    -            v_stride,
    -            h_stride,
    -            pad,
    -            act_func,
    -            seed,
    -        )
    -        # true if layer is used outside of CNN
    -        if reset_weights_independently == True:
    -            self._reset_weights_independently()
    -
    -    def _feedforward(self, X_batch):
    -        # The optimized _feedforward method is difficult to understand but computationally more efficient
    -        # for a more "by the book" approach, please look at the _feedforward method of Convolution2DLayer
    -
    -        # save the input for backpropagation
    -        self.X_batch_feedforward = X_batch
    -
    -        # check that there are the correct amount of input channels
    -        self._check_for_errors()
    -
    -        # calculate new shape after stride
    -        strided_height = int(np.ceil(X_batch.shape[height_index] / self.v_stride))
    -        strided_width = int(np.ceil(X_batch.shape[width_index] / self.h_stride))
    -
    -        # get windows of the image for more computationally efficient convolution
    -        # the idea is that we want to align the dimensions that we wish to matrix
    -        # multiply, then use a simple matrix multiplication instead of convolution.
    -        # then, we reshape the size back to its intended shape
    -        windows = self._extract_windows(X_batch)
    -        windows = windows.transpose(1, 0, 2, 3, 4).reshape(
    -            X_batch.shape[input_index],
    -            strided_height * strided_width,
    -            -1,
    -        )
    -
    -        # reshape the kernel for more computationally efficient convolution
    -        kernel = self.kernel
    -        kernel = kernel.transpose(0, 2, 3, 1).reshape(
    -            kernel.shape[kernel_input_channels_index]
    -            * kernel.shape[height_index]
    -            * kernel.shape[width_index],
    -            -1,
    -        )
    -
    -        # use simple matrix calculation to obtain output
    -        output = (
    -            (windows @ kernel)
    -            .reshape(
    -                X_batch.shape[input_index],
    -                strided_height,
    -                strided_width,
    -                -1,
    -            )
    -            .transpose(0, 3, 1, 2)
    -        )
    -
    -        # The output is reshaped and rearranged to appropriate shape
    -        return self.act_func(
    -            output / (self.kernel_height * X_batch.shape[feature_maps_index])
    -        )
    -
    -    def _backpropagate(self, delta_term_next):
    -        # The optimized _backpropagate method is difficult to understand but computationally more efficient
    -        # for a more "by the book" approach, please look at the _backpropagate method of Convolution2DLayer
    -        act_derivative = derivate(self.act_func)
    -        delta_term_next = act_derivative(delta_term_next)
    -
    -        # calculate strided dimensions
    -        strided_height = int(
    -            np.ceil(self.X_batch_feedforward.shape[height_index] / self.v_stride)
    -        )
    -        strided_width = int(
    -            np.ceil(self.X_batch_feedforward.shape[width_index] / self.h_stride)
    -        )
    -
    -        # copy kernel
    -        kernel = self.kernel
    -
    -        # get windows, reshape for matrix multiplication
    -        windows = self._extract_windows(self.X_batch_feedforward, "image").reshape(
    -            self.X_batch_feedforward.shape[input_index]
    -            * strided_height
    -            * strided_width,
    -            -1,
    -        )
    -
    -        # initialize output gradient, reshape and transpose into correct shape
    -        # for matrix multiplication
    -        output_grad_tr = delta_term_next.transpose(0, 2, 3, 1).reshape(
    -            self.X_batch_feedforward.shape[input_index]
    -            * strided_height
    -            * strided_width,
    -            -1,
    -        )
    -
    -        # calculate gradient kernel via simple matrix multiplication and reshaping
    -        gradient_kernel = (
    -            (windows.T @ output_grad_tr)
    -            .reshape(
    -                kernel.shape[kernel_input_channels_index],
    -                kernel.shape[height_index],
    -                kernel.shape[width_index],
    -                kernel.shape[kernel_feature_maps_index],
    -            )
    -            .transpose(0, 3, 1, 2)
    -        )
    -
    -        # for computing the input gradient
    -        windows_out, upsampled_height, upsampled_width = self._extract_windows(
    -            delta_term_next, "grad"
    -        )
    -
    -        # calculate new window dimensions
    -        new_windows_first_dim = (
    -            self.X_batch_feedforward.shape[input_index]
    -            * upsampled_height
    -            * upsampled_width
    -        )
    -        # ceil allows for various asymmetric kernels
    -        new_windows_sec_dim = int(np.ceil(windows_out.size / new_windows_first_dim))
    -
    -        # reshape for matrix multiplication
    -        windows_out = windows_out.transpose(1, 0, 2, 3, 4).reshape(
    -            new_windows_first_dim, new_windows_sec_dim
    -        )
    -
    -        # reshape for matrix multiplication
    -        kernel_reshaped = kernel.reshape(self.input_channels, -1)
    -
    -        # calculating input gradient for next convolutional layer
    -        input_grad = (windows_out @ kernel_reshaped.T).reshape(
    -            self.X_batch_feedforward.shape[input_index],
    -            upsampled_height,
    -            upsampled_width,
    -            kernel.shape[kernel_input_channels_index],
    -        )
    -        input_grad = input_grad.transpose(0, 3, 1, 2)
    -
    -        # Update the weights in the kernel
    -        self.kernel -= gradient_kernel
    -
    -        # Output the gradient to propagate backwards
    -        return input_grad
    -
    -    def _extract_windows(self, X_batch, batch_type="image"):
    -        """
    -        Receives as input the X_batch with shape (inputs, feature_maps, image_height, image_width)
    -        and extract windows of size kernel_height * kernel_width for every image and every feature_map.
    -        It then returns an np.ndarray of shape (image_height * image_width, inputs, feature_maps, kernel_height, kernel_width)
    -        which will be used either to filter the images in feedforward or to calculate the gradient.
    -        """
    -
    -        # initialize list of windows
    -        windows = []
    -
    -        if batch_type == "image":
    -            # pad the images
    -            X_batch_padded = self._padding(X_batch, batch_type="image")
    -            img_height, img_width = X_batch_padded.shape[2:]
    -            # For each location in the image...
    -            for h in range(
    -                0,
    -                X_batch.shape[height_index],
    -                self.v_stride,
    -            ):
    -                for w in range(
    -                    0,
    -                    X_batch.shape[width_index],
    -                    self.h_stride,
    -                ):
    -                    # ...obtain an image patch of the original size (strided)
    -
    -                    # get window
    -                    window = X_batch_padded[
    -                        :,
    -                        :,
    -                        h : h + self.kernel_height,
    -                        w : w + self.kernel_width,
    -                    ]
    -
    -                    # append to list of windows
    -                    windows.append(window)
    -
    -            # return numpy array instead of list
    -            return np.stack(windows)
    -
    -        # In order to be able to perform backprogagation by the method of window extraction,
    -        # here is a modified approach to extracting the windows which allow for the necessary
    -        # upsampling of the gradient in case the on of the stride parameters is larger than one.
    -
    -        if batch_type == "grad":
    -
    -            # In the case of one of the stride parameters being odd, we have to take some
    -            # extra care in calculating the upsampled size of X_batch. We solve this
    -            # by simply flooring the result of dividing stride by 2.
    -            if self.v_stride < 2 or self.v_stride % 2 == 0:
    -                v_stride = 0
    -            else:
    -                v_stride = int(np.floor(self.v_stride / 2))
    -
    -            if self.h_stride < 2 or self.h_stride % 2 == 0:
    -                h_stride = 0
    -            else:
    -                h_stride = int(np.floor(self.h_stride / 2))
    -
    -            upsampled_height = (X_batch.shape[height_index] * self.v_stride) - v_stride
    -            upsampled_width = (X_batch.shape[width_index] * self.h_stride) - h_stride
    -
    -            # When upsampling, we need to insert rows and columns filled with zeros
    -            # into each feature map. How many of those we have to insert is purely
    -            # dependant on the value of stride parameter in the vertical and horizontal
    -            # direction.
    -            if self.v_stride > 1:
    -                v_ind = 1
    -                for i in range(X_batch.shape[height_index]):
    -                    for j in range(self.v_stride - 1):
    -                        X_batch = np.insert(X_batch, v_ind, 0, axis=height_index)
    -                    v_ind += self.v_stride
    -
    -            if self.h_stride > 1:
    -                h_ind = 1
    -                for i in range(X_batch.shape[width_index]):
    -                    for k in range(self.h_stride - 1):
    -                        X_batch = np.insert(X_batch, h_ind, 0, axis=width_index)
    -                    h_ind += self.h_stride
    -
    -            # Since the insertion of zero-filled rows and columns isn't perfect, we have
    -            # to assure that the resulting feature maps will have the expected upsampled height
    -            # and width by cutting them og at desired dimensions.
    -
    -            X_batch = X_batch[:, :, :upsampled_height, :upsampled_width]
    -
    -            X_batch_padded = self._padding(X_batch, batch_type="grad")
    -
    -            # initialize list of windows
    -            windows = []
    -
    -            # For each location in the image...
    -            for h in range(
    -                0,
    -                X_batch.shape[height_index],
    -                self.v_stride,
    -            ):
    -                for w in range(
    -                    0,
    -                    X_batch.shape[width_index],
    -                    self.h_stride,
    -                ):
    -                    # ...obtain an image patch of the original size (strided)
    -
    -                    # get window
    -                    window = X_batch_padded[
    -                        :, :, h : h + self.kernel_height, w : w + self.kernel_width
    -                    ]
    -
    -                    # append window to list
    -                    windows.append(window)
    -
    -            # return numpy array, unsampled dimensions
    -            return np.stack(windows), upsampled_height, upsampled_width
    -
    -    def _check_for_errors(self):
    -        # compares input channels of data to input channels of Convolution2DLayer
    -        if self.X_batch_feedforward.shape[input_channel_index] != self.input_channels:
    -            raise AssertionError(
    -                f"ERROR: Number of input channels in data ({self.X_batch_feedforward.shape[input_channel_index]}) is not equal to input channels in Convolution2DLayerOPT ({self.input_channels})! Please change the number of input channels of the Convolution2DLayer such that they are equal"
    -            )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    The Convolutional Neural Network (CNN)

    - -

    Finally, we present the code for the CNN. The CNN class organizes all the layers, and allows for training on image data.

    - - - -
    -
    -
    -
    -
    -
    import math
    -import autograd.numpy as np
    -import sys
    -import warnings
    -from autograd import grad, elementwise_grad
    -from random import random, seed
    -from copy import deepcopy
    -from typing import Tuple, Callable
    -from sklearn.utils import resample
    -
    -warnings.simplefilter("error")
    -
    -
    -class CNN:
    -    def __init__(
    -        self,
    -        cost_func: Callable = CostCrossEntropy,
    -        scheduler: Scheduler = Adam(eta=1e-4, rho=0.9, rho2=0.999),
    -        seed: int = None,
    -    ):
    -        """
    -        Description:
    -        ------------
    -            Instantiates CNN object
    -
    -        Parameters:
    -        ------------
    -            I   output_func (costFunctions) cost function for feed forward neural network part of CNN,
    -                such as "CostLogReg", "CostOLS" or "CostCrossEntropy"
    -
    -            II  scheduler (Scheduler) optional parameter, default set to Adam. Can also be set to other
    -                schedulers such as AdaGrad, Momentum, RMS_prop and Constant. Note that schedulers have
    -                to be instantiated first with proper parameters (for example eta, rho and rho2 for Adam)
    -
    -            III seed (int) used for seeding all random operations
    -        """
    -        self.layers = list()
    -        self.cost_func = cost_func
    -        self.scheduler = scheduler
    -        self.schedulers_weight = list()
    -        self.schedulers_bias = list()
    -        self.seed = seed
    -        self.pred_format = None
    -
    -    def add_FullyConnectedLayer(
    -        self, nodes: int, act_func=LRELU, scheduler=None
    -    ) -> None:
    -        """
    -        Description:
    -        ------------
    -            Add a FullyConnectedLayer to the CNN, i.e. a hidden layer in the feed forward neural
    -            network part of the CNN. Often called a Dense layer in literature
    -
    -        Parameters:
    -        ------------
    -            I   nodes (int) number of nodes in FullyConnectedLayer
    -            II  act_func (activationFunctions) activation function of FullyConnectedLayer,
    -                such as "sigmoid", "RELU", "LRELU", "softmax" or "identity"
    -            III scheduler (Scheduler) optional parameter, default set to Adam. Can also be set to other
    -                schedulers such as AdaGrad, Momentum, RMS_prop and Constant
    -        """
    -        assert self.layers, "FullyConnectedLayer should follow FlattenLayer in CNN"
    -
    -        if scheduler is None:
    -            scheduler = self.scheduler
    -
    -        layer = FullyConnectedLayer(nodes, act_func, scheduler, self.seed)
    -        self.layers.append(layer)
    -
    -    def add_OutputLayer(self, nodes: int, output_func=sigmoid, scheduler=None) -> None:
    -        """
    -        Description:
    -        ------------
    -            Add an OutputLayer to the CNN, i.e. a the final layer in the feed forward neural
    -            network part of the CNN
    -
    -        Parameters:
    -        ------------
    -            I   nodes (int) number of nodes in OutputLayer. Set nodes=1 for binary classification and
    -                nodes = number of classes for multi-class classification
    -            II  output_func (activationFunctions) activation function for the output layer, such as
    -                "identity" for regression, "sigmoid" for binary classification and "softmax" for multi-class
    -                classification
    -            III scheduler (Scheduler) optional parameter, default set to Adam. Can also be set to other
    -                schedulers such as AdaGrad, Momentum, RMS_prop and Constant
    -        """
    -        assert self.layers, "OutputLayer should follow FullyConnectedLayer in CNN"
    -
    -        if scheduler is None:
    -            scheduler = self.scheduler
    -
    -        output_layer = OutputLayer(
    -            nodes, output_func, self.cost_func, scheduler, self.seed
    -        )
    -        self.layers.append(output_layer)
    -        self.pred_format = output_layer.get_pred_format()
    -
    -    def add_FlattenLayer(self, act_func=LRELU) -> None:
    -        """
    -        Description:
    -        ------------
    -            Add a FlattenLayer to the CNN, which flattens the image data such that it is formatted to
    -            be used in the feed forward neural network part of the CNN
    -        """
    -        self.layers.append(FlattenLayer(act_func=act_func, seed=self.seed))
    -
    -    def add_Convolution2DLayer(
    -        self,
    -        input_channels=1,
    -        feature_maps=1,
    -        kernel_height=3,
    -        kernel_width=3,
    -        v_stride=1,
    -        h_stride=1,
    -        pad="same",
    -        act_func=LRELU,
    -        optimized=True,
    -    ) -> None:
    -        """
    -        Description:
    -        ------------
    -            Add a Convolution2DLayer to the CNN, i.e. a convolutional layer with a 2 dimensional kernel. Should be
    -            the first layer added to the CNN
    -
    -        Parameters:
    -        ------------
    -            I   input_channels (int) specifies amount of input channels. For monochrome images, use input_channels
    -                = 1, and input_channels = 3 for colored images, where each channel represents one of R, G and B
    -            II  feature_maps (int) amount of feature maps in CNN
    -            III kernel_height (int) height of the kernel, also called 'convolutional filter' in literature
    -            IV  kernel_width (int) width of the kernel, also called 'convolutional filter' in literature
    -            V   v_stride (int) value of vertical stride for dimentionality reduction
    -            VI  h_stride (int) value of horizontal stride for dimentionality reduction
    -            VII pad (str) default = "same" ensures output size is the same as input size (given stride=1)
    -           VIII act_func (activationFunctions) default = "LRELU", nonlinear activation function
    -             IX optimized (bool) default = True, uses Convolution2DLayerOPT if True which is much faster when
    -                compared to Convolution2DLayer, which is a more straightforward, understandable implementation
    -        """
    -        if optimized:
    -            conv_layer = Convolution2DLayerOPT(
    -                input_channels,
    -                feature_maps,
    -                kernel_height,
    -                kernel_width,
    -                v_stride,
    -                h_stride,
    -                pad,
    -                act_func,
    -                self.seed,
    -                reset_weights_independently=False,
    -            )
    -        else:
    -            conv_layer = Convolution2DLayer(
    -                input_channels,
    -                feature_maps,
    -                kernel_height,
    -                kernel_width,
    -                v_stride,
    -                h_stride,
    -                pad,
    -                act_func,
    -                self.seed,
    -                reset_weights_independently=False,
    -            )
    -        self.layers.append(conv_layer)
    -
    -    def add_PoolingLayer(
    -        self, kernel_height=2, kernel_width=2, v_stride=1, h_stride=1, pooling="max"
    -    ) -> None:
    -        """
    -        Description:
    -        ------------
    -            Add a Pooling2DLayer to the CNN, i.e. a pooling layer that reduces the dimentionality of
    -            the image data. It is not necessary to use a Pooling2DLayer when creating a CNN, but it
    -            can be used to speed up the training
    -
    -        Parameters:
    -        ------------
    -            I   kernel_height (int) height of the kernel used for pooling
    -            II  kernel_width (int) width of the kernel used for pooling
    -            III v_stride (int) value of vertical stride for dimentionality reduction
    -            IV  h_stride (int) value of horizontal stride for dimentionality reduction
    -            V   pooling (str) either "max" or "average", describes type of pooling performed
    -        """
    -        pooling_layer = Pooling2DLayer(
    -            kernel_height, kernel_width, v_stride, h_stride, pooling, self.seed
    -        )
    -        self.layers.append(pooling_layer)
    -
    -    def fit(
    -        self,
    -        X: np.ndarray,
    -        t: np.ndarray,
    -        epochs: int = 100,
    -        lam: float = 0,
    -        batches: int = 1,
    -        X_val: np.ndarray = None,
    -        t_val: np.ndarray = None,
    -    ) -> dict:
    -        """
    -        Description:
    -        ------------
    -            Fits the CNN to input X for a given amount of epochs. Performs feedforward and backpropagation passes,
    -            can utilize batches, regulariziation and validation if desired.
    -
    -        Parameters:
    -        ------------
    -            X (numpy array) with input data in format [images, input channels,
    -            image height, image_width]
    -            t (numpy array) target labels for input data
    -            epochs (int) amount of epochs
    -            lam (float) regulariziation term lambda
    -            batches (int) amount of batches input data splits into
    -            X_val (numpy array) validation data
    -            t_val (numpy array) target labels for validation data
    -
    -        Returns:
    -        ------------
    -            scores (dict) a dictionary with "train_error", "train_acc", "val_error", val_acc" keys
    -            that contain numpy arrays with float values of all accuracies/errors over all epochs.
    -            Can be used to create plots. Also used to update the progress bar during training
    -        """
    -
    -        # setup
    -        if self.seed is not None:
    -            np.random.seed(self.seed)
    -
    -        # initialize weights
    -        self._initialize_weights(X)
    -
    -        # create arrays for score metrics
    -        scores = self._initialize_scores(epochs)
    -
    -        assert batches <= t.shape[0]
    -        batch_size = X.shape[0] // batches
    -
    -        try:
    -            for epoch in range(epochs):
    -                for batch in range(batches):
    -                    # minibatch gradient descent
    -                    # If the for loop has reached the last batch, take all thats left
    -                    if batch == batches - 1:
    -                        X_batch = X[batch * batch_size :, :, :, :]
    -                        t_batch = t[batch * batch_size :, :]
    -                    else:
    -                        X_batch = X[
    -                            batch * batch_size : (batch + 1) * batch_size, :, :, :
    -                        ]
    -                        t_batch = t[batch * batch_size : (batch + 1) * batch_size, :]
    -
    -                    self._feedforward(X_batch)
    -                    self._backpropagate(t_batch, lam)
    -
    -                # reset schedulers for each epoch (some schedulers pass in this call)
    -                for layer in self.layers:
    -                    if isinstance(layer, FullyConnectedLayer):
    -                        layer._reset_scheduler()
    -
    -                # computing performance metrics
    -                scores = self._compute_scores(scores, epoch, X, t, X_val, t_val)
    -
    -                # printing progress bar
    -                print_length = self._progress_bar(
    -                    epoch,
    -                    epochs,
    -                    scores,
    -                )
    -        # allows for stopping training at any point and seeing the result
    -        except KeyboardInterrupt:
    -            pass
    -
    -        # visualization of training progression (similiar to tensorflow progression bar)
    -        sys.stdout.write("\r" + " " * print_length)
    -        sys.stdout.flush()
    -        self._progress_bar(
    -            epochs,
    -            epochs,
    -            scores,
    -        )
    -        sys.stdout.write("")
    -
    -        return scores
    -
    -    def _feedforward(self, X_batch) -> np.ndarray:
    -        """
    -        Description:
    -        ------------
    -            Performs the feedforward pass for all layers in the CNN. Called from fit()
    -        """
    -        a = X_batch
    -        for layer in self.layers:
    -            a = layer._feedforward(a)
    -
    -        return a
    -
    -    def _backpropagate(self, t_batch, lam) -> None:
    -        """
    -        Description:
    -        ------------
    -            Performs backpropagation for all layers in the CNN. Called from fit()
    -        """
    -        assert len(self.layers) >= 2
    -        reversed_layers = self.layers[::-1]
    -
    -        # for every layer, backwards
    -        for i in range(len(reversed_layers) - 1):
    -            layer = reversed_layers[i]
    -            prev_layer = reversed_layers[i + 1]
    -
    -            # OutputLayer
    -            if isinstance(layer, OutputLayer):
    -                prev_a = prev_layer.get_prev_a()
    -                weights_next, delta_next = layer._backpropagate(t_batch, prev_a, lam)
    -
    -            # FullyConnectedLayer
    -            elif isinstance(layer, FullyConnectedLayer):
    -                assert (
    -                    delta_next is not None
    -                ), "No OutputLayer to follow FullyConnectedLayer"
    -                assert (
    -                    weights_next is not None
    -                ), "No OutputLayer to follow FullyConnectedLayer"
    -                prev_a = prev_layer.get_prev_a()
    -                weights_next, delta_next = layer._backpropagate(
    -                    weights_next, delta_next, prev_a, lam
    -                )
    -
    -            # FlattenLayer
    -            elif isinstance(layer, FlattenLayer):
    -                assert (
    -                    delta_next is not None
    -                ), "No FullyConnectedLayer to follow FlattenLayer"
    -                assert (
    -                    weights_next is not None
    -                ), "No FullyConnectedLayer to follow FlattenLayer"
    -                delta_next = layer._backpropagate(weights_next, delta_next)
    -
    -            # Convolution2DLayer and Convolution2DLayerOPT
    -            elif isinstance(layer, Convolution2DLayer):
    -                assert (
    -                    delta_next is not None
    -                ), "No FlattenLayer to follow Convolution2DLayer"
    -                delta_next = layer._backpropagate(delta_next)
    -
    -            # Pooling2DLayer
    -            elif isinstance(layer, Pooling2DLayer):
    -                assert delta_next is not None, "No Layer to follow Pooling2DLayer"
    -                delta_next = layer._backpropagate(delta_next)
    -
    -            # Catch error
    -            else:
    -                raise NotImplementedError
    -
    -    def _compute_scores(
    -        self,
    -        scores: dict,
    -        epoch: int,
    -        X: np.ndarray,
    -        t: np.ndarray,
    -        X_val: np.ndarray,
    -        t_val: np.ndarray,
    -    ) -> dict:
    -        """
    -        Description:
    -        ------------
    -            Computes scores such as training error, training accuracy, validation error
    -            and validation accuracy for the CNN depending on if a validation set is used
    -            and if the CNN performs classification or regression
    -
    -        Returns:
    -        ------------
    -            scores (dict) a dictionary with "train_error", "train_acc", "val_error", val_acc" keys
    -            that contain numpy arrays with float values of all accuracies/errors over all epochs.
    -            Can be used to create plots. Also used to update the progress bar during training
    -        """
    -
    -        pred_train = self.predict(X)
    -        cost_function_train = self.cost_func(t)
    -        train_error = cost_function_train(pred_train)
    -        scores["train_error"][epoch] = train_error
    -
    -        if X_val is not None and t_val is not None:
    -            cost_function_val = self.cost_func(t_val)
    -            pred_val = self.predict(X_val)
    -            val_error = cost_function_val(pred_val)
    -            scores["val_error"][epoch] = val_error
    -
    -        if self.pred_format != "Regression":
    -            train_acc = self._accuracy(pred_train, t)
    -            scores["train_acc"][epoch] = train_acc
    -            if X_val is not None and t_val is not None:
    -                val_acc = self._accuracy(pred_val, t_val)
    -                scores["val_acc"][epoch] = val_acc
    -
    -        return scores
    -
    -    def _initialize_scores(self, epochs) -> dict:
    -        """
    -        Description:
    -        ------------
    -            Initializes scores such as training error, training accuracy, validation error
    -            and validation accuracy for the CNN
    -
    -        Returns:
    -        ------------
    -            A dictionary with "train_error", "train_acc", "val_error", val_acc" keys that
    -            will contain numpy arrays with float values of all accuracies/errors over all epochs
    -            when passed through the _compute_scores() function during fit()
    -        """
    -        scores = dict()
    -
    -        train_errors = np.empty(epochs)
    -        train_errors.fill(np.nan)
    -        val_errors = np.empty(epochs)
    -        val_errors.fill(np.nan)
    -
    -        train_accs = np.empty(epochs)
    -        train_accs.fill(np.nan)
    -        val_accs = np.empty(epochs)
    -        val_accs.fill(np.nan)
    -
    -        scores["train_error"] = train_errors
    -        scores["val_error"] = val_errors
    -        scores["train_acc"] = train_accs
    -        scores["val_acc"] = val_accs
    -
    -        return scores
    -
    -    def _initialize_weights(self, X: np.ndarray) -> None:
    -        """
    -        Description:
    -        ------------
    -            Initializes weights for all layers in CNN
    -
    -        Parameters:
    -        ------------
    -            I   X (np.ndarray) input of format [img, feature_maps, height, width]
    -        """
    -        prev_nodes = X
    -        for layer in self.layers:
    -            prev_nodes = layer._reset_weights(prev_nodes)
    -
    -    def predict(self, X: np.ndarray, *, threshold=0.5) -> np.ndarray:
    -        """
    -        Description:
    -        ------------
    -            Predicts output of input X
    -
    -        Parameters:
    -        ------------
    -            I   X (np.ndarray) input [img, feature_maps, height, width]
    -        """
    -
    -        prediction = self._feedforward(X)
    -
    -        if self.pred_format == "Binary":
    -            return np.where(prediction > threshold, 1, 0)
    -        elif self.pred_format == "Multi-class":
    -            class_prediction = np.zeros(prediction.shape)
    -            for i in range(prediction.shape[0]):
    -                class_prediction[i, np.argmax(prediction[i, :])] = 1
    -            return class_prediction
    -        else:
    -            return prediction
    -
    -    def _accuracy(self, prediction: np.ndarray, target: np.ndarray) -> float:
    -        """
    -        Description:
    -        ------------
    -            Calculates accuracy of given prediction to target
    -
    -        Parameters:
    -        ------------
    -            I   prediction (np.ndarray): output of predict() fuction
    -            (1s and 0s in case of classification, and real numbers in case of regression)
    -            II  target (np.ndarray): vector of true values (What the network should predict)
    -
    -        Returns:
    -        ------------
    -            A floating point number representing the percentage of correctly classified instances.
    -        """
    -        assert prediction.size == target.size
    -        return np.average((target == prediction))
    -
    -    def _progress_bar(self, epoch: int, epochs: int, scores: dict) -> int:
    -        """
    -        Description:
    -        ------------
    -            Displays progress of training
    -        """
    -        progression = epoch / epochs
    -        epoch -= 1
    -        print_length = 40
    -        num_equals = int(progression * print_length)
    -        num_not = print_length - num_equals
    -        arrow = ">" if num_equals > 0 else ""
    -        bar = "[" + "=" * (num_equals - 1) + arrow + "-" * num_not + "]"
    -        perc_print = self._fmt(progression * 100, N=5)
    -        line = f"  {bar} {perc_print}% "
    -
    -        for key, score in scores.items():
    -            if np.isnan(score[epoch]) == False:
    -                value = self._fmt(score[epoch], N=4)
    -                line += f"| {key}: {value} "
    -        print(line, end="\r")
    -        return len(line)
    -
    -    def _fmt(self, value: int, N=4) -> str:
    -        """
    -        Description:
    -        ------------
    -            Formats decimal numbers for progress bar
    -        """
    -        if value > 0:
    -            v = value
    -        elif value < 0:
    -            v = -10 * value
    -        else:
    -            v = 1
    -        n = 1 + math.floor(math.log10(v))
    -        if n >= N - 1:
    -            return str(round(value))
    -            # or overflow
    -        return f"{value:.{N-n-1}f}"
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Usage of CNN code

    - -

    Using the CNN codebase is very simple. We begin by initiating a CNN -object, which takes a cost function, a scheduler and a seed as its -arguments. If a scheduler is not provided, it will per default -initiate an Adam scheduler with eta=1e-4, and if a seed is not -provided, the CNN will not be seeded, meaning it will run with a -different random seed every run. Below we demonstrate an initiation of -our CNN. -

    - - - -
    -
    -
    -
    -
    -
    adam_scheduler = Adam(eta=1e-3, rho=0.9, rho2=0.999)
    -cnn = CNN(cost_func=CostCrossEntropy, scheduler=adam_scheduler, seed=2023)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Now that we have our CNN object, we can begin to add layers to it! -Many of the add_layer functions have default values, for example -add_Convolution2DLayer() has a default v_stride and h_stride of -1. However, these can of course be set to any value you please. Note -that the input channels of a subsequent convolutional layer must equal -the previous convolutional layer's feature maps. -

    - - - -
    -
    -
    -
    -
    -
    cnn.add_Convolution2DLayer(
    -    input_channels=1,
    -    feature_maps=1,
    -    kernel_height=3,
    -    kernel_width=3,
    -    act_func=LRELU,
    -)
    -
    -cnn.add_FlattenLayer()
    -
    -cnn.add_FullyConnectedLayer(30, LRELU)
    -
    -cnn.add_FullyConnectedLayer(20, LRELU)
    -
    -cnn.add_OutputLayer(10, softmax)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Here we have created a CNN with the following architecture:

    - -
      -
    1. A convolutional layer with 1 input channel, with a kernel height of 2 and a width of 2, which uses LRELU as its non-linearity function. This layer outputs 1 feature map, which feed into the subsequent layer.
    2. -
    3. A flatten layer
    4. -
    5. A hidden layer with 30 nodes, with LRELU as its activation function
    6. -
    7. Another hidden layer but with 20 nodes
    8. -
    9. The output layer, with softmax as its activation function and 10 nodes. We use 10 nodes because we will be using a dataset with 10 classes.
    10. -
    -

    Now, before we can train the model, we need to load in our data. We -will use the MNIST dataset and use 10000 \( 28 \times 28 images \). -

    - - - -
    -
    -
    -
    -
    -
    from sklearn.datasets import fetch_openml
    -from sklearn.model_selection import train_test_split
    -
    -def onehot(target: np.ndarray):
    -    onehot = np.zeros((target.size, target.max() + 1))
    -    onehot[np.arange(target.size), target] = 1
    -    return onehot
    -
    -# get dataset
    -dataset = fetch_openml("mnist_784", parser="auto")
    -mnist = dataset.data.to_numpy(dtype="float")[:10000, :]
    -
    -# scale data
    -for i in range(mnist.shape[1]):
    -    mnist[:, i] /= 255
    -    
    -# reshape to add single input channel to data shape [inputs, input_channels, height, width]
    -mnist = mnist.reshape(mnist.shape[0], 1, 28, 28)
    -
    -# one hot encode target as we are doing multi-class classification
    -target = onehot(np.array([int(i) for i in dataset.target.to_numpy()[:10000]]))
    -
    -# split into training and validation data
    -x_train, x_val, y_train, y_val = train_test_split(mnist, target)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Now we may train our model. Note that we can utilize regularization in -the CNN by using the lam (lambda) parameter in fit(), and utilize -different types of gradient descent by specifying the amount of -batches via the batches parameter as shown below. -

    - -

    The functionfit() returns a score dictionary of the training error and -accuracy (and validation error and accuracy if a validation set is -provided) which can be used to plot the error and accuracy of the -model over epochs. -

    - - - -
    -
    -
    -
    -
    -
    scores = cnn.fit(
    -    x_train,
    -    y_train,
    -    lam=1e-5,
    -    batches=10,
    -    epochs=100,
    -    X_val=x_val,
    -    t_val=y_val,
    -)
    -
    -plt.plot(scores["train_acc"], label="Training")
    -plt.plot(scores["val_acc"], label="Validation")
    -plt.ylim([0.8,1])
    -plt.xlabel("Epochs")
    -plt.ylabel("Accuracy")
    -plt.legend()
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Considering we only trained the model for 100 epochs without any tuning of the hyperparameters, this result is pretty good.

    - -

    The codebase allows for great flexibility in CNN -architectures. Pooling layers can be added before, inbetween or after -convolutional layers, but due to the great optimizations made within -Convolution2DLayerOPT, we recommend using the v_stride and h_stride -parameters in add_Convolution2DLayer() to reduce the dimentionality of -the problem as the pooling layer is slow in comparison. To use the -unoptimized version of Convolution2DLayer, simply pass optimized=False -as an argument in add_Convolution2DLayer(). -

    - -

    If one wishes to perform binary classification using the CNN, simply -use the cost function 'CostLogReg' when initializing the CNN and use 1 -node at the OutputLayer. -

    - -

    Below we have created another, more untraditional architecture using -our code to demonstrate its flexibility and different attributes such -as asymmetric stride that might become useful when constructing your -own CNN. -

    - - - -
    -
    -
    -
    -
    -
    adam_scheduler = Adam(eta=1e-3, rho=0.9, rho2=0.999)
    -cnn = CNN(cost_func=CostCrossEntropy, scheduler=adam_scheduler, seed=2023)
    -
    -cnn.add_Convolution2DLayer(
    -    input_channels=1,
    -    feature_maps=7,
    -    kernel_height=7,
    -    kernel_width=1,
    -    act_func=LRELU,
    -)
    -
    -cnn.add_PoolingLayer(
    -    kernel_height=2,
    -    kernel_width=2,
    -    pooling="average",
    -)
    -
    -cnn.add_PoolingLayer(
    -    kernel_height=2,
    -    kernel_width=2,
    -    pooling="max",
    -)
    -
    -cnn.add_Convolution2DLayer(
    -    input_channels=7,
    -    feature_maps=1,
    -    kernel_height=4,
    -    kernel_width=4,
    -    v_stride=2,
    -    h_stride=3,
    -    act_func=LRELU,
    -    optimized=False,
    -)
    -
    -cnn.add_Convolution2DLayer(
    -    input_channels=1,
    -    feature_maps=1,
    -    kernel_height=2,
    -    kernel_width=2,
    -    act_func=sigmoid,
    -    optimized=True,
    -)
    -
    -cnn.add_PoolingLayer(
    -    kernel_height=2,
    -    kernel_width=2,
    -    pooling="max"
    -)
    -
    -cnn.add_FlattenLayer()
    -
    -cnn.add_FullyConnectedLayer(100, LRELU)
    -
    -cnn.add_FullyConnectedLayer(10, sigmoid)
    -
    -cnn.add_FullyConnectedLayer(101, identity)
    -
    -cnn.add_OutputLayer(10, softmax)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    Here we see the use of asymmetrical 1D kernels such as the \( 7 \times -1 \) kernel in the first convolutional layer, both max and average -pooling, asymmetric stride in the unoptimized convolutional layer, -more pooling, a flatten layer, a hidden layer with 100 nodes using -LRELU, another hidden layer with 10 hidden nodes that uses the sigmoid -activation function, and another hidden layer with 101 nodes which -utilizes no activation function (identity). Finally, we arrive at the -output layer with 10 nodes, which uses softmax as its activation -function. -

    -

    Additional Remarks

    - -

    The stride parameter controls the distance between each convolution -and the kernel/filter. If our image is padded, stride is the only -parameter that determines the size of the output from a convolutional -layer. However, if we decide not to perform any padding, the size of -the output feature map depends on both the stride and kernel size. It -is important to note that neither the stride nor the kernel has to be -symmetrical. This means that we can use a rectangular filter if we -choose, and the stride in the vertical direction (axis=0 in Python) -does not need to be the same as the stride in the horizontal direction -(axis=1 in Python). It may even be the case that asymmetric -combinations of stride or kernel dimensions, or both, yield better -results than symmetric values for these parameters. -

    - - - -
    -
    -
    -
    -
    -
    def convolve(image, kernel, stride=1):
    -    for i in range(2):
    -        kernel = np.rot90(kernel)
    -
    -    k_half_height = kernel.shape[0] // 2
    -    k_half_width = kernel.shape[0] // 2
    -
    -    conv_image = np.zeros(image.shape)
    -    pad_image = padding(image, kernel)
    -
    -    for i in range(k_half_height, conv_image.shape[0] + k_half_height, stride):
    -        for j in range(k_half_width, conv_image.shape[1] + k_half_width, stride):
    -            conv_image[i - k_half_height, j - k_half_width] = np.sum(
    -                pad_image[
    -                    i - k_half_height : i + k_half_height + 1, j - k_half_width : j + k_half_width + 1
    -                ]
    -                * kernel
    -            )
    -
    -    return conv_image
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Remarks on the speed

    - -

    Despite the naive convolution algorithm shown above working finely, it -is extremely slow, requiring approximately 20-30 seconds to process a -single image. The time complexity of 2D convolution, which is O(NMnm), -rapidly becomes a constraint and may, at worst, make computations -infeasible. Consequently, optimizing the naive 2D convolution -algorithm is a necessity, as the execution time of the algorithm -significantly increases as the input data size expands. This can pose -a bottleneck in applications that necessitate real-time processing of -large data volumes, such as image and video processing, deep learning, -and scientific simulations. -

    - -

    To address this issue, we shall present two widely used optimization -techniques: the separable kernel approach and Fast Fourier Transform -(FFT). Both of these methods can drastically reduce the computational -complexity of convolution and enhance the overall efficiency of -processing substantial data quantities. While we shall refrain from -delving into the intricacies of these algorithms, we strongly -encourage you to examine at least the application of FFT to optimize -computations. -

    -

    Convolution using separable kernels

    - - - -
    -
    -
    -
    -
    -
    def conv2DSep(image, kernel, coef, stride=1, pad="zero"):
    -    for i in range(2):
    -        kernel = np.rot90(kernel)
    -
    -    # The kernel is quadratic, thus we only need one of its dimensions
    -    half_dim = kernel.shape[0] // 2
    -
    -    ker1 = np.array(kernel[0, :])
    -    ker2 = np.array(kernel[:, 0])
    -
    -    if pad == "zero":
    -        conv_image = np.zeros(image.shape)
    -        pad_image = padding(image, kernel)
    -    else:
    -        conv_image = np.zeros(
    -            (image.shape[0] - kernel.shape[0], image.shape[1] - kernel.shape[1])
    -        )
    -        pad_image = image[:, :]
    -
    -    for i in range(half_dim, conv_image.shape[0] + half_dim, stride):
    -        for j in range(half_dim, conv_image.shape[1] + half_dim, stride):
    -            conv_image[i - half_dim, j - half_dim] = (
    -                pad_image[
    -                    i - half_dim : i + half_dim + 1, j - half_dim : j + half_dim + 1
    -                ]
    -                @ ker1
    -                @ ker2.T
    -                * coef
    -            )
    -
    -    return conv_image
    -
    -img_path = img_path = "data/IMG-2167.JPG"
    -image_of_cute_dog = imageio.imread(img_path, mode="L")
    -start_time = time.time()
    -filtered_image = conv2DSep(image_of_cute_dog, kernel=sobel_kernel, coef=1)
    -print(f'Time taken for convolution with seperated kernel on 128x128 image {time.time() - start_time}')
    -plt.imshow(filtered_image, cmap="gray", vmin=0, vmax=255, aspect="auto")
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    By taking advantage of the capabilities of separable kernels, we can -effectively cut the computational expense of filtering an image in -half. Yet, if we seek even more rapid processing, we can turn to the -Fast Fourier Transform (FFT) algorithm provided by the numpy -library. By utilizing FFT to transform the input image and filter into -the frequency domain, we can perform convolution in this domain. This -approach significantly reduces the number of operations needed and -results in a marked speedup relative to other convolution -techniques. In addition, it is worth noting that the FFT is widely -regarded as one of the most critical algorithms developed to date, -with applications ranging from digital signal processing to scientific -computing. -

    -

    Convolution in the Fourier domain

    - - -
    -
    -
    -
    -
    -
    start_time = time.time()
    -img_fft = np.fft.fft2(image_of_cute_dog)
    -kernel_fft = np.fft.fft2(sobel_kernel, s=image_of_cute_dog.shape)
    -
    -conv_image = img_fft * kernel_fft
    -
    -filtered_image = np.fft.ifft2(conv_image)
    -print(f'Time take for convolution in the fourier domain: {time.time() - start_time}')
    -plt.imshow(filtered_image.real, cmap="gray", vmin=0, vmax=255, aspect="auto")
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -

    It is evident that executing convolution in the Fourier domain yields -the quickest computation time. Nonetheless, one should exercise -caution, particularly when dealing with images of relatively small -dimensions, as one of the other methods may prove to be more -expeditious than FFT-enhanced convolution. The overhead involved in -transferring both the image and filter into the Fourier domain, -followed by their subsequent transformation back into the spatial -domain, results in a minor inconvenience. Therefore, it is imperative -to remain cognizant of this fact when utilizing FFT as the primary -optimization technique. +

    Each filter slides along the input image, taking the dot product +between each small part of the image and the filter, in all depth +dimensions. This is then passed through a non-linear function, +typically the Rectified Linear (ReLu) function, which serves as the +activation of the neurons in the first convolutional layer. This is +further passed through a pooling layer, which reduces the size of the +convolutional layer, e.g. by taking the maximum or average across some +small regions, and this serves as input to the next convolutional +layer.

    @@ -3582,6 +386,18 @@ optimization technique.

  • 47
  • 48
  • 49
  • +
  • 50
  • +
  • 51
  • +
  • 52
  • +
  • 53
  • +
  • 54
  • +
  • 55
  • +
  • 56
  • +
  • 57
  • +
  • 58
  • +
  • ...
  • +
  • 62
  • +
  • »
  • diff --git a/doc/pub/week44/html/._week44-bs049.html b/doc/pub/week44/html/._week44-bs049.html index 243d34579..c667b312d 100644 --- a/doc/pub/week44/html/._week44-bs049.html +++ b/doc/pub/week44/html/._week44-bs049.html @@ -8,8 +8,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d - -Week 44: Decision Trees, Ensemble methods and Random Forests + +Week 44, Convolutional Neural Networks (CNN) @@ -36,191 +36,191 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d @@ -248,78 +248,97 @@ MathJax.Hub.Config({ - Week 44: Decision Trees, Ensemble methods and Random Forests + Week 44, Convolutional Neural Networks (CNN)
    diff --git a/doc/pub/week44/html/week44-bs.html b/doc/pub/week44/html/week44-bs.html index c696978c2..e73a75127 100644 --- a/doc/pub/week44/html/week44-bs.html +++ b/doc/pub/week44/html/week44-bs.html @@ -75,6 +75,12 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -89,6 +95,8 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -97,36 +105,75 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -173,27 +220,7 @@ doconce format html week44.do.txt --html_style=bootstrap --pygments_html_style=d ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -244,59 +271,74 @@ MathJax.Hub.Config({
  • CNNs in brief
  • A deep CNN model ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • Key Idea
  • -
  • Mathematics of CNNs
  • -
  • Convolution Examples: Polynomial multiplication
  • -
  • Efficient Polynomial Multiplication
  • -
  • Further simplification
  • -
  • A more efficient way of coding the above Convolution
  • -
  • Fourier series and Toeplitz matrices
  • -
  • Generalizing the above one-dimensional case
  • -
  • Two-dimensional Objects
  • -
  • CNNs in more detail
  • -
  • Performing a discrete convolution with padding ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Set of filters
  • -
  • Pooling
  • -
  • No zero padding, unit strides
  • -
  • Zero padding, unit strides
  • -
  • Half (same) padding
  • -
  • Full padding
  • -
  • Pooling arithmetic
  • -
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • -
  • Building our own CNN code
  • -
  •    List of contents:
  • -
  •    Schedulers
  • -
  •    Usage of schedulers
  • -
  •    Cost functions
  • -
  •    Usage of cost functions
  • -
  •    Activation functions
  • -
  •    Usage of activation functions
  • -
  •    Convolution
  • -
  •    Layers
  • -
  •    Convolution2DLayer: convolution in a hidden layer
  • -
  •    Backpropagation in the convolutional layer
  • -
  •    Demonstration
  • -
  •    Pooling Layer
  • -
  •    Flattening Layer
  • -
  •    Fully Connected Layers
  • -
  •    Optimized Convolution2DLayer
  • -
  •    The Convolutional Neural Network (CNN)
  • -
  •    Usage of CNN code
  • -
  •    Additional Remarks
  • -
  •    Remarks on the speed
  • -
  •    Convolution using separable kernels
  • -
  •    Convolution in the Fourier domain
  • -
  • Building convolutional neural networks in Tensorflow and Keras
  • -
  • Setting it up
  • -
  • The MNIST dataset again
  • -
  • Strong correlations
  • -
  • Layers of a CNN
  • -
  • Systematic reduction
  • -
  • Prerequisites: Collect and pre-process data
  • -
  • Importing Keras and Tensorflow
  • -
  • Running with Keras
  • -
  • Final part
  • -
  • Final visualization
  • +
  • How to do image compression before the era of deep learning
  • +
  • The SVD example
  • +
  • Mathematics of CNNs
  • +
  • Mathematics of CNNs
  • +
  • Convolution Examples: Polynomial multiplication
  • +
  • Efficient Polynomial Multiplication
  • +
  • Further simplification
  • +
  • A more efficient way of coding the above Convolution
  • +
  • Commutative process
  • +
  • Toeplitz matrices
  • +
  • Fourier series and Toeplitz matrices
  • +
  • Generalizing the above one-dimensional case
  • +
  • Memory considerations
  • +
  • Padding
  • +
  • New vector
  • +
  • Rewriting as dot products
  • +
  • Cross correlation
  • +
  • Two-dimensional objects
  • +
  • CNNs in more detail, simple example
  • +
  • The convolution stage
  • +
  • Finding the number of parameters
  • +
  • New image (or volume)
  • +
  • Parameters to train, common settings
  • +
  • Examples of CNN setups
  • +
  • Summarizing: Performing a general discrete convolution ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Pooling
  • +
  • Pooling arithmetic
  • +
  • Pooling types ("From Raschka et al":"https://github.com/rasbt/machine-learning-book")
  • +
  • Building convolutional neural networks in Tensorflow and Keras
  • +
  • Setting it up
  • +
  • The MNIST dataset again
  • +
  • Strong correlations
  • +
  • Layers of a CNN
  • +
  • Systematic reduction
  • +
  • Prerequisites: Collect and pre-process data
  • +
  • Importing Keras and Tensorflow
  • +
  • Running with Keras
  • +
  • Final part
  • +
  • Final visualization
  • +
  • The CIFAR01 data set
  • +
  • Verifying the data set
  • +
  • Set up the model
  • +
  • Add Dense layers on top
  • +
  • Compile and train the model
  • +
  • Finally, evaluate the model
  • +
  • Building our own CNN code
  • +
  •    List of contents:
  • +
  •    Schedulers
  • +
  •    Usage of schedulers
  • +
  •    Cost functions
  • +
  •    Usage of cost functions
  • +
  •    Activation functions
  • +
  •    Usage of activation functions
  • +
  •    Convolution
  • +
  •    Layers
  • +
  •    Convolution2DLayer: convolution in a hidden layer
  • +
  •    Backpropagation in the convolutional layer
  • +
  •    Demonstration
  • +
  •    Pooling Layer
  • +
  •    Flattening Layer
  • +
  •    Fully Connected Layers
  • +
  •    Optimized Convolution2DLayer
  • +
  •    The Convolutional Neural Network (CNN)
  • +
  •    Usage of CNN code
  • +
  •    Additional Remarks
  • +
  •    Remarks on the speed
  • +
  •    Convolution using separable kernels
  • +
  •    Convolution in the Fourier domain
  • @@ -351,7 +393,7 @@ MathJax.Hub.Config({
  • 9
  • 10
  • ...
  • -
  • 47
  • +
  • 62
  • »
  • diff --git a/doc/pub/week44/html/week44-reveal.html b/doc/pub/week44/html/week44-reveal.html index 8f9fadb28..6f2942bdf 100644 --- a/doc/pub/week44/html/week44-reveal.html +++ b/doc/pub/week44/html/week44-reveal.html @@ -506,6 +506,155 @@ only neighboring neurons in the input instead of connecting all with the first h

    We say we perform a filtering (convolution is the mathematical operation).

    +
    +

    How to do image compression before the era of deep learning

    + +

    The singular-value decomposition (SVD) algorithm has been for decades one of the standard ways of compressing images. +The lectures on the SVD give many of the essential details concerning the SVD. +

    + +

    The orthogonal vectors which are obtained from the SVD, can be used to +project down the dimensionality of a given image. In the example here +we gray-scale an image and downsize it. +

    + +

    This recipe relies on us being able to actually perform the SVD. For +large images, and in particular with many images to reconstruct, using the SVD +may quickly become an overwhelming task. With the advent of efficient deep +learning methods like CNNs and later generative methods, these methods +have become in the last years the premier way of performing image +analysis. In particular for classification problems with labelled images. +

    +
    + +
    +

    The SVD example

    + + + +
    +
    +
    +
    +
    +
    from matplotlib.image import imread
    +import matplotlib.pyplot as plt
    +import scipy.linalg as ln
    +import numpy as np
    +import os
    +from PIL import Image
    +from math import log10, sqrt 
    +plt.rcParams['figure.figsize'] = [16, 8]
    +# Import image
    +A = imread(os.path.join("figslides/photo1.jpg"))
    +X = A.dot([0.299, 0.5870, 0.114]) # Convert RGB to grayscale
    +img = plt.imshow(X)
    +# convert to gray
    +img.set_cmap('gray')
    +plt.axis('off')
    +plt.show()
    +# Call image size
    +print(': %s'%str(X.shape))
    +
    +
    +# split the matrix into U, S, VT
    +U, S, VT = np.linalg.svd(X,full_matrices=False)
    +S = np.diag(S)
    +m = 800 # Image's width
    +n = 1200 # Image's height
    +j = 0
    +# Try compression with different k vectors (these represent projections):
    +for k in (5,10, 20, 100,200,400,500):
    +    # Original size of the image
    +    originalSize = m * n 
    +    # Size after compressed
    +    compressedSize = k * (1 + m + n) 
    +    # The projection of the original image
    +    Xapprox = U[:,:k] @ S[0:k,:k] @ VT[:k,:]
    +    plt.figure(j+1)
    +    j += 1
    +    img = plt.imshow(Xapprox)
    +    img.set_cmap('gray')
    +    
    +    plt.axis('off')
    +    plt.title('k = ' + str(k))
    +    plt.show() 
    +    print('Original size of image:')
    +    print(originalSize)
    +    print('Compression rate as Compressed image / Original size:')
    +    ratio = compressedSize * 1.0 / originalSize
    +    print(ratio)
    +    print('Compression rate is ' + str( round(ratio * 100 ,2)) + '%' )  
    +    # Estimate MQA
    +    x= X.astype("float")
    +    y=Xapprox.astype("float")
    +    err = np.sum((x - y) ** 2)
    +    err /= float(X.shape[0] * Xapprox.shape[1])
    +    print('The mean-square deviation '+ str(round( err)))
    +    max_pixel = 255.0
    +    # Estimate Signal Noise Ratio
    +    srv = 20 * (log10(max_pixel / sqrt(err)))
    +    print('Signa to noise ratio '+ str(round(srv)) +'dB')
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    Mathematics of CNNs

    + +

    The mathematics of CNNs is based on the mathematical operation of +convolution. In mathematics (in particular in functional analysis), +convolution is represented by mathematical operations (integration, +summation etc) on two functions in order to produce a third function +that expresses how the shape of one gets modified by the other. +Convolution has a plethora of applications in a variety of +disciplines, spanning from statistics to signal processing, computer +vision, solutions of differential equations,linear algebra, +engineering, and yes, machine learning. +

    + +

    Mathematically, convolution is defined as follows (one-dimensional example): +Let us define a continuous function \( y(t) \) given by +

    +

     
    +$$ +y(t) = \int x(a) w(t-a) da, +$$ +

     
    + +

    where \( x(a) \) represents a so-called input and \( w(t-a) \) is normally called the weight function or kernel.

    + +

    The above integral is written in a more compact form as

    +

     
    +$$ +y(t) = \left(x * w\right)(t). +$$ +

     
    + +

    The discretized version reads

    +

     
    +$$ +y(t) = \sum_{a=-\infty}^{a=\infty}x(a)w(t-a). +$$ +

     
    + +

    Computing the inverse of the above convolution operations is known as deconvolution and the process is commutative.

    + +

    How can we use this? And what does it mean? Let us study some familiar examples first.

    +
    +

    Mathematics of CNNs

    @@ -630,7 +779,9 @@ $$ $$

     
    -

    where \( m=3 \) in our case, the maximum length of the vector \( \alpha \). Note that the vector \( \boldsymbol{\beta} \) has length \( n=4 \).

    +

    where \( m=3 \) in our case, the maximum length of +the vector \( \alpha \). Note that the vector \( \boldsymbol{\beta} \) has length \( n=4 \). Below we will find an even more efficient representation. +

    @@ -652,6 +803,10 @@ $$ \end{bmatrix}\begin{bmatrix} \beta_0 \\ \beta_1 \\ \beta_2 \\ \beta_3\end{bmatrix}. $$

     
    +

    + +
    +

    Commutative process

    The process is commutative and we can easily see that we can rewrite the multiplication in terms of a matrix holding \( \beta \) and a vector holding \( \alpha \). In this case we have @@ -674,6 +829,10 @@ we do not encode (and allocate memory) the matrices explicitely. We rather code the convolutions in the minimal memory footprint that they require.

    +
    + +
    +

    Toeplitz matrices

    The above matrices are examples of so-called Toeplitz matrices. A @@ -716,20 +875,145 @@ a vector.

    Generalizing the above one-dimensional case

    -

    In order to align the above simple case with the more general convolution cases, we rename \( \boldsymbol{\alpha} \), whose length is \( m=3 \), with \( \boldsymbol{w} \). -We will interpret \( \boldsymbol{w} \) as a weight/filter function with which we want to perform the convolution with an input varibale \( \boldsymbol{x} \). -We replace thus \( \boldsymbol{\beta} \) with \( \boldsymbol{x} \) and \( \boldsymbol{\delta} \) with \( \boldsymbol{s} \) and have +

    In order to align the above simple case with the more general +convolution cases, we rename \( \boldsymbol{\alpha} \), whose length is \( m=3 \), +with \( \boldsymbol{w} \). We will interpret \( \boldsymbol{w} \) as a weight/filter function +with which we want to perform the convolution with an input variable +\( \boldsymbol{x} \) of length \( n \). We will assume always that the filter +\( \boldsymbol{w} \) has dimensionality \( m \le n \).

    + +

    We replace thus \( \boldsymbol{\beta} \) with \( \boldsymbol{x} \) and \( \boldsymbol{\delta} \) with \( \boldsymbol{y} \) and have

     
    $$ -s(i) \left(x*w\right)(i)= \sum_{k=0}^{k=m-1}w(k)x(i-k), +y(i)= \left(x*w\right)(i)= \sum_{k=0}^{k=m-1}w(k)x(i-k), $$

     

    where \( m=3 \) in our case, the maximum length of the vector \( \boldsymbol{w} \). Here the symbol \( * \) represents the mathematical operation of convolution.

    -

    Two-dimensional Objects

    +
    + +
    +

    Memory considerations

    + +

    This expression leaves us however with some terms with negative +indices, for example \( x(-1) \) and \( x(-2) \) which may not be defined. Our +vector \( \boldsymbol{x} \) has components \( x(0) \), \( x(1) \), \( x(2) \) and \( x(3) \). +

    + +

    The index \( j \) for \( \boldsymbol{x} \) runs from \( j=0 \) to \( j=3 \) since \( \boldsymbol{x} \) is meant to +represent a third-order polynomial. +

    + +

    Furthermore, the index \( i \) runs from \( i=0 \) to \( i=5 \) since \( \boldsymbol{y} \) +contains the coefficients of a fifth-order polynomial. When \( i=5 \) we +may also have values of \( x(4) \) and \( x(5) \) which are not defined. +

    +
    + +
    +

    Padding

    + +

    The solution to this is what is called padding! We simply define a +new vector \( x \) with two added elements set to zero before \( x(0) \) and +two new elements after \( x(3) \) set to zero. That is, we augment the +length of \( \boldsymbol{x} \) from \( n=4 \) to \( n+2P=8 \), where \( P=2 \) is the padding +constant (a new hyperparameter), see discussions below as well. +

    +
    + +
    +

    New vector

    + +

    We have a new vector defined as \( x(0)=0 \), \( x(1)=0 \), +\( x(2)=\beta_0 \), \( x(3)=\beta_1 \), \( x(4)=\beta_2 \), \( x(5)=\beta_3 \), +\( x(6)=0 \), and \( x(7)=0 \). +

    + +

    We have added four new elements, which +are all zero. The benefit is that we can rewrite the equation for +\( \boldsymbol{y} \), with \( i=0,1,\dots,5 \), +

    +

     
    +$$ +y(i) = \sum_{k=0}^{k=m-1}w(k)x(i+(m-1)-k). +$$ +

     
    + +

    As an example, we have

    +

     
    +$$ +y(4)=x(6)w(0)+x(5)w(1)+x(4)w(2)=0\times \alpha_0+\beta_3\alpha_1+\beta_2\alpha_2, +$$ +

     
    + +

    as before except that we have an additional term \( x(6)w(0) \), which is zero.

    + +

    Similarly, for the fifth-order term we have

    +

     
    +$$ +y(5)=x(7)w(0)+x(6)w(1)+x(5)w(2)=0\times \alpha_0+0\times\alpha_1+\beta_3\alpha_2. +$$ +

     
    + +

    The zeroth-order term is

    +

     
    +$$ +y(0)=x(2)w(0)+x(1)w(1)+x(0)w(2)=\beta_0 \alpha_0+0\times\alpha_1+0\times\alpha_2=\alpha_0\beta_0. +$$ +

     
    +

    + +
    +

    Rewriting as dot products

    + +

    If we now flip the filter/weight vector, with the following term as a typical example

    +

     
    +$$ +y(0)=x(2)w(0)+x(1)w(1)+x(0)w(2)=x(2)\tilde{w}(2)+x(1)\tilde{w}(1)+x(0)\tilde{w}(0), +$$ +

     
    + +

    with \( \tilde{w}(0)=w(2) \), \( \tilde{w}(1)=w(1) \), and \( \tilde{w}(2)=w(0) \), we can then rewrite the above sum as a dot product of +\( x(i:i+(m-1))\tilde{w} \) for element \( y(i) \), where \( x(i:i+(m-1)) \) is simply a patch of \( \boldsymbol{x} \) of size \( m-1 \). +

    + +

    The padding \( P \) we have introduced for the convolution stage is just +another hyperparameter which is introduced as part of the +architecture. Similarly, below we will also introduce another +hyperparameter called Stride \( S \). +

    +
    + +
    +

    Cross correlation

    + +

    In essentially all applications one uses what is called cross correlation instead of the standard convolution described above. +This means that multiplication is performed in the same direction and instead of the general expression we have discussed above (with infinite sums) +

    +

     
    +$$ +y(i) = \sum_{k=-\infty}^{k=\infty}w(k)x(i-k), +$$ +

     
    + +

    we have now

    +

     
    +$$ +y(i) = \sum_{k=-\infty}^{k=\infty}w(k)x(i+k). +$$ +

     
    + +

    Both TensorFlow and PyTorch (as well as our own code example below), +implement the last equation, although it is normally referred to as +convolution. The same padding rules and stride rules discussed below +apply to this expression as well. +

    + +

    We leave it as an exercise for you to convince yourself that the example we have discussed till now, gives the same final expression using the last expression.

    +

    Two-dimensional objects

    We are now ready to start studying the discrete convolutions relevant for convolutional neural networks. We often use convolutions over more than one dimension at a time. If @@ -750,9 +1034,15 @@ Y(i,j)=(X * W)(i,j) = \sum_m\sum_n X(i-m,j-n)W(m,n). $$

     
    -

    Normally the latter is more straightforward to implement in a machine larning library since there is less variation in the range of values of \( m \) and \( n \).

    +

    Normally the latter is more straightforward to implement in a machine +larning library since there is less variation in the range of values +of \( m \) and \( n \). +

    -

    Many deep learning libraries implement cross-correlation instead of convolution (although it is referred to s convolution)

    +

    As mentioned above, most deep learning libraries implement +cross-correlation instead of convolution (although it is referred to as +convolution) +

     
    $$ Y(i,j)=(X * W)(i,j) = \sum_m\sum_n X(i+m,j+n)W(m,n). @@ -761,7 +1051,7 @@ $$

    -

    CNNs in more detail

    +

    CNNs in more detail, simple example

    Let assume we have an input matrix \( X \) of dimensionality \( 3\times 3 \) and a \( 2\times 2 \) filter \( W \) given by the following matrices @@ -836,59 +1126,111 @@ $$

    -

    Performing a discrete convolution with padding (From Raschka et al)

    +

    The convolution stage

    -
    -
    -
    -

    Figure 4: A deep CNN

    -
    -

    -
    +

    The convolution stage, where we apply different filters \( \boldsymbol{W} \) in +order to reduce the dimensionality of an image, adds, in addition to +the weights and biases (to be trained by the back propagation +algorithm) that define the filters, two new hyperparameters, the so-called +padding \( P \) and the stride \( S \). +

    -

    Performing a general discrete convolution (From Raschka et al)

    +

    Finding the number of parameters

    -
    -
    -
    -

    Figure 5: A deep CNN

    -
    -

    -
    -
    - -
    -

    Set of filters

    - -

    The collection of kernels/filters \( W \) defining a discrete convolution has a shape -corresponding to some permutation of \( (n, m, k_1, \ldots, k_N) \), where +

    In the above example we have an input matrix of dimension \( 3\times +3 \). In general we call the input for an input volume and it is defined +by its width \( H_1 \), height \( H_1 \) and depth \( D_1 \). If we have the +standard three color channels \( D_1=3 \).

    -$$ -\begin{equation*} -\begin{split} - n &\equiv \text{number of output feature maps},\\ - m &\equiv \text{number of input feature maps},\\ - k_j &\equiv \text{kernel size along axis $j$}. -\end{split} -\end{equation*} -$$ - -

    The following properties affect the output size \( o_j \) of a convolutional layer -along axis \( j \): -

    +

    The above example has \( W_1=H_1=3 \) and \( D_1=1 \).

    +

    When we introduce the filter we have the following additional hyperparameters

      -

    1. \( i_j \): input size along axis \( j \),
    2. -

    3. \( k_j \): kernel/filter size along axis \( j \),
    4. -

    5. stride (distance between two consecutive positions of the kernel/filter) along axis \( j \),
    6. -

    7. zero padding (number of zeros concatenated at the beginning and at the end of an axis) along axis \( j \).
    8. +

    9. \( K \) the number of filters. It is common to perform the convolution of the input several times since by experience shrinking the input too fast does not work well
    10. +

    11. \( F \) as the filter's spatial extent
    12. +

    13. \( S \) as the stride parameter
    14. +

    15. \( P \) as the padding parameter

    -

    For instance, the above examples shows a \( 2\times 2 \) kernel/filter \( \boldsymbol{W} \) applied to a \( 3 \times 3 \) input padded with a \( 0 \times 0 \) -border of zeros using \( 1 \times 1 \) strides. +

    These parameters are defined by the architecture of the network and are not included in the training.

    +
    + +
    +

    New image (or volume)

    + +

    Acting with the filter on the input volume produces an output volume +which is defined by its width \( W_2 \), its height \( H_2 \) and its depth +\( D_2 \). +

    + +

    These are defined by the following relations

    +

     
    +$$ +W_2 = \frac{(W_1-F+2P)}{S}+1, +$$ +

     
    + +

     
    +$$ +H_2 = \frac{(H_1-F+2P)}{S}+1, +$$ +

     
    + +

    and \( D_2=K \).

    +
    + +
    +

    Parameters to train, common settings

    + +

    With parameter sharing, the convolution involves thus for each filter \( F\times F\times D_1 \) weights plus one bias parameter.

    + +

    In total we have

    +

     
    +$$ +\left(F\times F\times D_1)\right) \times K+(K\mathrm{--biases}), +$$ +

     
    + +

    parameters to train by back propagation.

    + +

    It is common to let \( K \) come in powers of \( 2 \), that is \( 32 \), \( 64 \), \( 128 \) etc.

    + +
    +Common settings +

    +

      +

    1. \( \begin{array}{c} F=3 & S=1 & P=1 \end{array} \)
    2. +

    3. \( \begin{array}{c} F=5 & S=1 & P=2 \end{array} \)
    4. +

    5. \( \begin{array}{c} F=5 & S=2 & P=\mathrm{open} \end{array} \)
    6. +

    7. \( \begin{array}{c} F=1 & S=1 & P=0 \end{array} \)
    8. +
    +
    +
    + +
    +

    Examples of CNN setups

    + +

    Let us assume we have an input volume \( V \) given by an image of dimensionality +\( 32\times 32 \times 3 \), that is three color channels and \( 32\times 32 \) pixels. +

    + +

    We apply a filter of dimension \( 5\times 5 \) ten times with stride \( S=1 \) and padding \( P=0 \).

    + +

    The output volume is given by \( (32-5)/1+1=28 \), resulting in ten images +of dimensionality \( 28\times 28\times 3 \). +

    + +

    The total number of parameters to train for each filter is then +\( 5\times 5\times 3+1 \), where the last parameter is the bias. This +gives us \( 76 \) parameters for each filter, leading to a total of \( 760 \) +parameters for the ten filters. +

    + +

    How many parameters will a filter of dimensionality \( 3\times 3 \) +(adding color channels) result in if we produce \( 32 \) new images? Use \( S=1 \) and \( P=0 \).

    Note that strides constitute a form of subsampling. As an alternative to @@ -899,135 +1241,31 @@ retaining only odd output elements.

    +
    +

    Summarizing: Performing a general discrete convolution (From Raschka et al)

    + +
    +
    +
    +

    Figure 4: A deep CNN

    +
    +

    +
    +
    +

    Pooling

    -

    In addition to discrete convolutions themselves, {\em pooling\/} operations +

    In addition to discrete convolutions themselves, pooling operations make up another important building block in CNNs. Pooling operations reduce the size of feature maps by using some function to summarize subregions, such as taking the average or the maximum value.

    Pooling works by sliding a window across the input and feeding the content of -the window to a {\em pooling function}. In some sense, pooling works very much +the window to a pooling function. In some sense, pooling works very much like a discrete convolution, but replaces the linear combination described by -the kernel with some other function. Poolin -provides an example for average pooling, and -does the same for max pooling. -

    - -

    The following properties affect the output size \( o_j \) of a pooling layer -along axis \( j \): -

    - -
      -

    1. \( i_j \): input size along axis \( j \),
    2. -

    3. \( k_j \): pooling window size along axis \( j \),
    4. -

    5. \( s_j \): stride (distance between two consecutive positions of the pooling window) along axis \( j \).
    6. -
    -

    -

    The analysis of the relationship between convolutional layer properties is eased -by the fact that they don't interact across axes, i.e., the choice of kernel -size, stride and zero padding along axis \( j \) only affects the output size of -axis \( j \). Because of that, we will focus on the following simplified -setting: -

    - -
      -

    1. 2-D discrete convolutions (\( N = 2 \)),
    2. -

    3. square inputs (\( i_1 = i_2 = i \)),
    4. -

    5. square kernel size (\( k_1 = k_2 = k \)),
    6. -

    7. same strides along both axes (\( s_1 = s_2 = s \)),
    8. -

    9. same zero padding along both axes (\( p_1 = p_2 = p \)).
    10. -
    -

    -

    This facilitates the analysis and the visualization, but keep in mind that the -results outlined here also generalize to the N-D and non-square cases. -

    -
    - -
    -

    No zero padding, unit strides

    - -

    The simplest case to analyze is when the kernel just slides across every -position of the input (i.e., \( s = 1 \) and \( p = 0 \)). -

    - -

    For any \( i \) and \( k \), and for \( s = 1 \) and \( p = 0 \),

    -

     
    -$$ -\begin{equation*} - o = (i - k) + 1. -\end{equation*} -$$ -

     
    -

    - -
    -

    Zero padding, unit strides

    - -

    To factor in zero padding (i.e., only restricting to \( s = 1 \)), let's consider -its effect on the effective input size: padding with \( p \) zeros changes the -effective input size from \( i \) to \( i + 2p \). In the general case, we can infer the following -relationship -

    - -

    For any \( i \), \( k \) and \( p \), and for \( s = 1 \),

    -

     
    -$$ -\begin{equation*} - o = (i - k) + 2p + 1. -\end{equation*} -$$ -

     
    -

    - -
    -

    Half (same) padding

    - -

    Having the output size be the same as the input size (i.e., \( o = i \)) can be a -desirable property: -

    - -

    For any \( i \) and for \( k \) odd (\( k = 2n + 1, \quad n \in \mathbb{N} \)), \( s = 1 \) and -\( p = \lfloor k / 2 \rfloor = n \), -

    -

     
    -$$ -\begin{equation*} -\begin{split} - o &= i + 2 \lfloor k / 2 \rfloor - (k - 1) \\ - &= i + 2n - 2n \\ - &= i. -\end{split} -\end{equation*} -$$ -

     
    -

    - -
    -

    Full padding

    - -

    While convolving a kernel generally decreases the output size with -respect to the input size, sometimes the opposite is required. This can be -achieved with proper zero padding: -

    - -

    For any \( i \) and \( k \), and for \( p = k - 1 \) and \( s = 1 \),

    -

     
    -$$ -\begin{equation*} -\begin{split} - o &= i + 2(k - 1) - (k - 1) \\ - &= i + (k - 1). -\end{split} -\end{equation*} -$$ -

     
    - -

    This is sometimes referred to as full padding, because in this -setting every possible partial or complete superimposition of the kernel on the -input feature map is taken into account. +the kernel with some other function.

    @@ -1041,19 +1279,6 @@ outputting the maximum value of each patch. Other kinds of pooling exist, e.g., mean or average pooling, which all share the same idea of aggregating the input locally by applying a non-linearity to the content of some patches.

    - -

    Since pooling does not involve -zero padding, the relationship describing the general case is as follows: -

    - -

    For any \( i \), \( k \) and \( s \),

    -

     
    -$$ -\begin{equation*} - o = \left\lfloor \frac{i - k}{s} \right\rfloor + 1. -\end{equation*} -$$ -

     

    @@ -1062,12 +1287,609 @@ $$

    -

    Figure 6: A deep CNN

    +

    Figure 5: A deep CNN

    +
    +

    Building convolutional neural networks in Tensorflow and Keras

    + +

    As discussed above, CNNs are neural networks built from the assumption that the inputs +to the network are 2D images. This is important because the number of features or pixels in images +grows very fast with the image size, and an enormous number of weights and biases are needed in order to build an accurate network. +

    + +

    As before, we still have our input, a hidden layer and an output. What's novel about convolutional networks +are the convolutional and pooling layers stacked in pairs between the input and the hidden layer. +In addition, the data is no longer represented as a 2D feature matrix, instead each input is a number of 2D +matrices, typically 1 for each color dimension (Red, Green, Blue). +

    +
    + +
    +

    Setting it up

    + +

    It means that to represent the entire +dataset of images, we require a 4D matrix or tensor. This tensor has the dimensions: +

    +

     
    +$$ +(n_{inputs},\, n_{pixels, width},\, n_{pixels, height},\, depth) . +$$ +

     
    +

    + +
    +

    The MNIST dataset again

    + +

    The MNIST dataset consists of grayscale images with a pixel size of +\( 28\times 28 \), meaning we require \( 28 \times 28 = 724 \) weights to each +neuron in the first hidden layer. +

    + +

    If we were to analyze images of size \( 128\times 128 \) we would require +\( 128 \times 128 = 16384 \) weights to each neuron. Even worse if we were +dealing with color images, as most images are, we have an image matrix +of size \( 128\times 128 \) for each color dimension (Red, Green, Blue), +meaning 3 times the number of weights \( = 49152 \) are required for every +single neuron in the first hidden layer. +

    +
    + +
    +

    Strong correlations

    + +

    Images typically have strong local correlations, meaning that a small +part of the image varies little from its neighboring regions. If for +example we have an image of a blue car, we can roughly assume that a +small blue part of the image is surrounded by other blue regions. +

    + +

    Therefore, instead of connecting every single pixel to a neuron in the +first hidden layer, as we have previously done with deep neural +networks, we can instead connect each neuron to a small part of the +image (in all 3 RGB depth dimensions). The size of each small area is +fixed, and known as a receptive. +

    +
    + +
    +

    Layers of a CNN

    + +

    The layers of a convolutional neural network arrange neurons in 3D: width, height and depth. +The input image is typically a square matrix of depth 3. +

    + +

    A convolution is performed on the image which outputs +a 3D volume of neurons. The weights to the input are arranged in a number of 2D matrices, known as filters. +

    + +

    Each filter slides along the input image, taking the dot product +between each small part of the image and the filter, in all depth +dimensions. This is then passed through a non-linear function, +typically the Rectified Linear (ReLu) function, which serves as the +activation of the neurons in the first convolutional layer. This is +further passed through a pooling layer, which reduces the size of the +convolutional layer, e.g. by taking the maximum or average across some +small regions, and this serves as input to the next convolutional +layer. +

    +
    + +
    +

    Systematic reduction

    + +

    By systematically reducing the size of the input volume, through +convolution and pooling, the network should create representations of +small parts of the input, and then from them assemble representations +of larger areas. The final pooling layer is flattened to serve as +input to a hidden layer, such that each neuron in the final pooling +layer is connected to every single neuron in the hidden layer. This +then serves as input to the output layer, e.g. a softmax output for +classification. +

    +
    + +
    +

    Prerequisites: Collect and pre-process data

    + + +
    +
    +
    +
    +
    +
    # import necessary packages
    +import numpy as np
    +import matplotlib.pyplot as plt
    +from sklearn import datasets
    +
    +
    +# ensure the same random numbers appear every time
    +np.random.seed(0)
    +
    +# display images in notebook
    +%matplotlib inline
    +plt.rcParams['figure.figsize'] = (12,12)
    +
    +
    +# download MNIST dataset
    +digits = datasets.load_digits()
    +
    +# define inputs and labels
    +inputs = digits.images
    +labels = digits.target
    +
    +# RGB images have a depth of 3
    +# our images are grayscale so they should have a depth of 1
    +inputs = inputs[:,:,:,np.newaxis]
    +
    +print("inputs = (n_inputs, pixel_width, pixel_height, depth) = " + str(inputs.shape))
    +print("labels = (n_inputs) = " + str(labels.shape))
    +
    +
    +# choose some random images to display
    +n_inputs = len(inputs)
    +indices = np.arange(n_inputs)
    +random_indices = np.random.choice(indices, size=5)
    +
    +for i, image in enumerate(digits.images[random_indices]):
    +    plt.subplot(1, 5, i+1)
    +    plt.axis('off')
    +    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    +    plt.title("Label: %d" % digits.target[random_indices[i]])
    +plt.show()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    Importing Keras and Tensorflow

    + + +
    +
    +
    +
    +
    +
    from tensorflow.keras import datasets, layers, models
    +from tensorflow.keras.layers import Input
    +from tensorflow.keras.models import Sequential      #This allows appending layers to existing models
    +from tensorflow.keras.layers import Dense           #This allows defining the characteristics of a particular layer
    +from tensorflow.keras import optimizers             #This allows using whichever optimiser we want (sgd,adam,RMSprop)
    +from tensorflow.keras import regularizers           #This allows using whichever regularizer we want (l1,l2,l1_l2)
    +from tensorflow.keras.utils import to_categorical   #This allows using categorical cross entropy as the cost function
    +#from tensorflow.keras import Conv2D
    +#from tensorflow.keras import MaxPooling2D
    +#from tensorflow.keras import Flatten
    +
    +from sklearn.model_selection import train_test_split
    +
    +# representation of labels
    +labels = to_categorical(labels)
    +
    +# split into train and test data
    +# one-liner from scikit-learn library
    +train_size = 0.8
    +test_size = 1 - train_size
    +X_train, X_test, Y_train, Y_test = train_test_split(inputs, labels, train_size=train_size,
    +                                                    test_size=test_size)
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    Running with Keras

    + + + +
    +
    +
    +
    +
    +
    def create_convolutional_neural_network_keras(input_shape, receptive_field,
    +                                              n_filters, n_neurons_connected, n_categories,
    +                                              eta, lmbd):
    +    model = Sequential()
    +    model.add(layers.Conv2D(n_filters, (receptive_field, receptive_field), input_shape=input_shape, padding='same',
    +              activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    +    model.add(layers.MaxPooling2D(pool_size=(2, 2)))
    +    model.add(layers.Flatten())
    +    model.add(layers.Dense(n_neurons_connected, activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    +    model.add(layers.Dense(n_categories, activation='softmax', kernel_regularizer=regularizers.l2(lmbd)))
    +    
    +    sgd = optimizers.SGD(lr=eta)
    +    model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
    +    
    +    return model
    +
    +epochs = 100
    +batch_size = 100
    +input_shape = X_train.shape[1:4]
    +receptive_field = 3
    +n_filters = 10
    +n_neurons_connected = 50
    +n_categories = 10
    +
    +eta_vals = np.logspace(-5, 1, 7)
    +lmbd_vals = np.logspace(-5, 1, 7)
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    Final part

    + + + +
    +
    +
    +
    +
    +
    CNN_keras = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)
    +        
    +for i, eta in enumerate(eta_vals):
    +    for j, lmbd in enumerate(lmbd_vals):
    +        CNN = create_convolutional_neural_network_keras(input_shape, receptive_field,
    +                                              n_filters, n_neurons_connected, n_categories,
    +                                              eta, lmbd)
    +        CNN.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=0)
    +        scores = CNN.evaluate(X_test, Y_test)
    +        
    +        CNN_keras[i][j] = CNN
    +        
    +        print("Learning rate = ", eta)
    +        print("Lambda = ", lmbd)
    +        print("Test accuracy: %.3f" % scores[1])
    +        print()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    Final visualization

    + + + +
    +
    +
    +
    +
    +
    # visual representation of grid search
    +# uses seaborn heatmap, could probably do this in matplotlib
    +import seaborn as sns
    +
    +sns.set()
    +
    +train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    +test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    +
    +for i in range(len(eta_vals)):
    +    for j in range(len(lmbd_vals)):
    +        CNN = CNN_keras[i][j]
    +
    +        train_accuracy[i][j] = CNN.evaluate(X_train, Y_train)[1]
    +        test_accuracy[i][j] = CNN.evaluate(X_test, Y_test)[1]
    +
    +        
    +fig, ax = plt.subplots(figsize = (10, 10))
    +sns.heatmap(train_accuracy, annot=True, ax=ax, cmap="viridis")
    +ax.set_title("Training Accuracy")
    +ax.set_ylabel("$\eta$")
    +ax.set_xlabel("$\lambda$")
    +plt.show()
    +
    +fig, ax = plt.subplots(figsize = (10, 10))
    +sns.heatmap(test_accuracy, annot=True, ax=ax, cmap="viridis")
    +ax.set_title("Test Accuracy")
    +ax.set_ylabel("$\eta$")
    +ax.set_xlabel("$\lambda$")
    +plt.show()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    The CIFAR01 data set

    + +

    The CIFAR10 dataset contains 60,000 color images in 10 classes, with +6,000 images in each class. The dataset is divided into 50,000 +training images and 10,000 testing images. The classes are mutually +exclusive and there is no overlap between them. +

    + + + +
    +
    +
    +
    +
    +
    import tensorflow as tf
    +
    +from tensorflow.keras import datasets, layers, models
    +import matplotlib.pyplot as plt
    +
    +# We import the data set
    +(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()
    +
    +# Normalize pixel values to be between 0 and 1 by dividing by 255. 
    +train_images, test_images = train_images / 255.0, test_images / 255.0
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    Verifying the data set

    + +

    To verify that the dataset looks correct, let's plot the first 25 images from the training set and display the class name below each image.

    + + + +
    +
    +
    +
    +
    +
    class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
    +               'dog', 'frog', 'horse', 'ship', 'truck']
    +plt.figure(figsize=(10,10))
    +for i in range(25):
    +    plt.subplot(5,5,i+1)
    +    plt.xticks([])
    +    plt.yticks([])
    +    plt.grid(False)
    +    plt.imshow(train_images[i], cmap=plt.cm.binary)
    +    # The CIFAR labels happen to be arrays, 
    +    # which is why you need the extra index
    +    plt.xlabel(class_names[train_labels[i][0]])
    +plt.show()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    Set up the model

    + +

    The 6 lines of code below define the convolutional base using a common pattern: a stack of Conv2D and MaxPooling2D layers.

    + +

    As input, a CNN takes tensors of shape (image_height, image_width, color_channels), ignoring the batch size. If you are new to these dimensions, color_channels refers to (R,G,B). In this example, you will configure our CNN to process inputs of shape (32, 32, 3), which is the format of CIFAR images. You can do this by passing the argument input_shape to our first layer.

    + + + +
    +
    +
    +
    +
    +
    model = models.Sequential()
    +model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
    +model.add(layers.MaxPooling2D((2, 2)))
    +model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    +model.add(layers.MaxPooling2D((2, 2)))
    +model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    +
    +# Let's display the architecture of our model so far.
    +
    +model.summary()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +

    You can see that the output of every Conv2D and MaxPooling2D layer is a 3D tensor of shape (height, width, channels). The width and height dimensions tend to shrink as you go deeper in the network. The number of output channels for each Conv2D layer is controlled by the first argument (e.g., 32 or 64). Typically, as the width and height shrink, you can afford (computationally) to add more output channels in each Conv2D layer.

    +
    + +
    +

    Add Dense layers on top

    + +

    To complete our model, you will feed the last output tensor from the +convolutional base (of shape (4, 4, 64)) into one or more Dense layers +to perform classification. Dense layers take vectors as input (which +are 1D), while the current output is a 3D tensor. First, you will +flatten (or unroll) the 3D output to 1D, then add one or more Dense +layers on top. CIFAR has 10 output classes, so you use a final Dense +layer with 10 outputs and a softmax activation. +

    + + + +
    +
    +
    +
    +
    +
    model.add(layers.Flatten())
    +model.add(layers.Dense(64, activation='relu'))
    +model.add(layers.Dense(10))
    +Here's the complete architecture of our model.
    +
    +model.summary()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +

    As you can see, our (4, 4, 64) outputs were flattened into vectors of shape (1024) before going through two Dense layers.

    +
    + +
    +

    Compile and train the model

    + + + +
    +
    +
    +
    +
    +
    model.compile(optimizer='adam',
    +              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    +              metrics=['accuracy'])
    +
    +history = model.fit(train_images, train_labels, epochs=10, 
    +                    validation_data=(test_images, test_labels))
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +

    Finally, evaluate the model

    + + + +
    +
    +
    +
    +
    +
    plt.plot(history.history['accuracy'], label='accuracy')
    +plt.plot(history.history['val_accuracy'], label = 'val_accuracy')
    +plt.xlabel('Epoch')
    +plt.ylabel('Accuracy')
    +plt.ylim([0.5, 1])
    +plt.legend(loc='lower right')
    +
    +test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)
    +
    +print(test_acc)
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Building our own CNN code

    @@ -1077,6 +1899,8 @@ the codebase and fit a CNN that yields a 99% accuracy on the 28x28 MNIST dataset within reasonable time.

    +The codes here were developed by Eric Reber and Gregor Kajda during spring 2023. +

    The CNN is compatible with all schedulers, cost functions and activation functions discussed in constructing our neural network codes. @@ -1570,23 +2394,23 @@ $$ $$

     
    -

    Here, f and g are the two functions on which we want to perform an +

    Here, \( f \) and \( g \) are the two functions on which we want to perform an operation. The outcome of the convolution operation is represented by -\( (f \ast g) \), and it is derived by sliding the function g over f and +\( (f \ast g) \), and it is derived by sliding the function \( g \) over \( f \) and computing the integral of their product at each position. If both functions are continuous, convolution takes the form shown -above. However, if we discretize both f and g, the convolution -operation will take the form of a sum between the elements of f and g: +above. However, if we discretize both \( f \) and \( g \), the convolution +operation will take the form of a sum between the elements of \( f \) and \( g \):

     
    $$ -(f \ast g)[n]=\sum_{m=0}^{n-1} f[m] g[n-m]. +(f \ast g)[n]=\sum_{m=0}^{n-1} f(m) g(n-m). $$

     

    The key idea we utilize to extract the information contained in an -image is to slide an \( m \times n \) matrix g over an \( m \times n \) -matrix f. In our case, f represents the image, while g +image is to slide an \( m \times n \) matrix \( g \) over an \( m \times n \) +matrix \( f \). In our case, \( f \) represents the image, while \( g \) represents the kernel, oftentimes called a filter. However, since our convolution will be a two-dimensional variant, we need to extend our mathematical formula with an additional summation: @@ -1594,7 +2418,7 @@ mathematical formula with an additional summation:

     
    $$ -(f \ast g)[i, j]\sum_{m=0}^{M-1}\sum_{n=0}^{N-1} f[m,n] g[i-m, j-n]. +(f \ast g)(i, j)\sum_{m=0}^{M-1}\sum_{n=0}^{N-1} f(m,n) g(i-m, j-n). $$

     
    @@ -1607,7 +2431,7 @@ appropriate kernels for specific tasks.

    To give you an example of how 2D convolution works in practice, -suppose we have an image f of dimension \( 6 \times 6 \) +suppose we have an image \( f \) of dimension \( 6 \times 6 \)

     
    @@ -1623,7 +2447,7 @@ f = \begin{bmatrix} $$

     
    -

    and a \( 3 \times 3 \) kernel g called a low-pass filter. Note that the +

    and a \( 3 \times 3 \) kernel \( g \) called a low-pass filter. Note that the kernel is usually rotated by 180 degrees during convolution, however this has no effect on this kernel.

    @@ -1640,9 +2464,9 @@ $$

     

    In order to filter the image, we have to extract a \( 3 \times 3 \) -element from the upper left corner of f, and perform element-wise +element from the upper left corner of \( f \), and perform element-wise multiplication of the extracted image pixels with the elements of the -kernel g: +kernel \( g \):

     
    @@ -1664,24 +2488,24 @@ $$ \frac{9}{9} & \frac{5}{9} & \frac{9}{9} \\ \frac{1}{9} & \frac{5}{9} & \frac{9}{9} \\ \end {bmatrix} -= \textbf{A} += \boldsymbol{A} $$

     
    -

    Then, following the multiplication, we summarize all the elements of the resulting matrix A:

    +

    Then, following the multiplication, we summarize all the elements of the resulting matrix \( \boldsymbol{A} \):

     
    $$ -(f \ast g)[0, 0]= \sum_{i=0}^{2} \sum_{j=0}^{2} a_{i,j} = 5 +(f \ast g)(0, 0)= \sum_{i=0}^{2} \sum_{j=0}^{2} a_{i,j} = 5, $$

     
    -

    Which corresponds to the first element of the filtered image \( (f \ast g) \).

    +

    which corresponds to the first element of the filtered image \( (f \ast g) \).

    -

    Here we use a stride of 1, a parameter denoted s which describes how -many indexes we move the kernel g to the right before repeating the +

    Here we use a stride of \( S=1 \), a parameter denoted \( S \) which describes how +many indexes we move the kernel \( g \) to the right before repeating the calculations above for the next \( 3 \times 3 \) element of the image -f. It is usually presumed that *s*=1, however, larger values for s +\( f \). It is usually presumed that \( S=1 \), however, larger values for \( S \) can be used to reduce the dimentionality of the filtered image such that the convolution operation is more computationally efficient. In the context of a convolutional neural network, this will become very @@ -1702,20 +2526,25 @@ $$ $$

     
    -

    The result is markedly smaller in shape than the original image. This occurs when using convolution without first padding the image with additional columns and rows, allowing us to keep the original image shape after sliding the kernel over the image. -How many rows and columns we wish to pad the image with depends strictly on the shape of the kernel, as we wish to pad the image with r additional rows and c additional columns. +

    The result is markedly smaller in shape than the original image. This +occurs when using convolution without first padding the image with +additional columns and rows, allowing us to keep the original image +shape after sliding the kernel over the image. How many rows and +columns we wish to pad the image with depends strictly on the shape of +the kernel, as we wish to pad the image with \( r \) additional rows and +\( c \) additional columns.

     
    $$ -r =\lfloor \frac{kernel\ height}{2} \rfloor \cdot 2 \\ -c =\lfloor \frac{kernel\ width}{2} \rfloor \cdot 2 +r =\lfloor \frac{\mathrm{kernel height}}{2} \rfloor \cdot 2 \\ +c =\lfloor \frac{\mathrm{kernel width}}{2} \rfloor \cdot 2 $$

     
    -

    Note the notation \( \lfloor \frac{kernel width}{2} \rfloor \) means that +

    Note the notation \( \lfloor \frac{\mathrm{kernel width}}{2} \rfloor \) means that we floor the result of the division, meaning we round down to a whole -number in case \( \frac{kernel width}{2} \) results in a floating point +number in case \( \frac{\mathrm{kernel width}}{2} \) results in a floating point number.

    @@ -1742,12 +2571,14 @@ $$ 0 & 8 & 1 & 6 & 4 & 2 & 2 & 0 \\ 0 & 1 & 0 & 5 & 7 & 8 & 2 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ -\] -\end{bmatrix} +\end{bmatrix}. $$

     
    -

    Below we have provided code that demonstrates padding and convolution. As you will see when we run the code, the size of the image will remain unchanged when using padding. 

    +

    Below we have provided code that demonstrates padding and +convolution. As you will see when we run the code, the size of the +image will remain unchanged when using padding.~ +

    @@ -1842,9 +2673,10 @@ $$ -

    Fun fact: When filtering images, you will see that convolution involves rotating the kernel by 180 degrees. -However, this is not the case when applying convolution in a CNN, where the same operation not rotated by 180 degrees is called -cross-correlation. +

    Fun fact: When filtering images, you will see that convolution +involves rotating the kernel by 180 degrees. However, this is not the +case when applying convolution in a CNN, where the same operation that is not +rotated by 180 degrees is called cross-correlation, which is normally implemented in most libraries.

    @@ -1904,7 +2736,7 @@ feed forward neural network.
    -
    # Now an example using a real image and first a gaussian low-pass filter and then a sobel filter
    +  
    # Now an example using a real image and first a gaussian low-pass filter and then a Sobel filter
     import numpy as np
     import imageio.v3 as imageio
     import matplotlib.pyplot as plt
    @@ -3932,7 +4764,7 @@ cnn.add_OutputLayer(10, softmax)
     
     

    Now, before we can train the model, we need to load in our data. We -will use the MNIST dataset and use 10000 \( 28 \times 28 images \). +will use the MNIST dataset and use 10000 \( 28 \times 28 \) images.

    @@ -4347,367 +5179,6 @@ optimization technique.

    -
    -

    Building convolutional neural networks in Tensorflow and Keras

    - -

    As discussed above, CNNs are neural networks built from the assumption that the inputs -to the network are 2D images. This is important because the number of features or pixels in images -grows very fast with the image size, and an enormous number of weights and biases are needed in order to build an accurate network. -

    - -

    As before, we still have our input, a hidden layer and an output. What's novel about convolutional networks -are the convolutional and pooling layers stacked in pairs between the input and the hidden layer. -In addition, the data is no longer represented as a 2D feature matrix, instead each input is a number of 2D -matrices, typically 1 for each color dimension (Red, Green, Blue). -

    -
    - -
    -

    Setting it up

    - -

    It means that to represent the entire -dataset of images, we require a 4D matrix or tensor. This tensor has the dimensions: -

    -

     
    -$$ -(n_{inputs},\, n_{pixels, width},\, n_{pixels, height},\, depth) . -$$ -

     
    -

    - -
    -

    The MNIST dataset again

    - -

    The MNIST dataset consists of grayscale images with a pixel size of -\( 28\times 28 \), meaning we require \( 28 \times 28 = 724 \) weights to each -neuron in the first hidden layer. -

    - -

    If we were to analyze images of size \( 128\times 128 \) we would require -\( 128 \times 128 = 16384 \) weights to each neuron. Even worse if we were -dealing with color images, as most images are, we have an image matrix -of size \( 128\times 128 \) for each color dimension (Red, Green, Blue), -meaning 3 times the number of weights \( = 49152 \) are required for every -single neuron in the first hidden layer. -

    -
    - -
    -

    Strong correlations

    - -

    Images typically have strong local correlations, meaning that a small -part of the image varies little from its neighboring regions. If for -example we have an image of a blue car, we can roughly assume that a -small blue part of the image is surrounded by other blue regions. -

    - -

    Therefore, instead of connecting every single pixel to a neuron in the -first hidden layer, as we have previously done with deep neural -networks, we can instead connect each neuron to a small part of the -image (in all 3 RGB depth dimensions). The size of each small area is -fixed, and known as a receptive. -

    -
    - -
    -

    Layers of a CNN

    - -

    The layers of a convolutional neural network arrange neurons in 3D: width, height and depth. -The input image is typically a square matrix of depth 3. -

    - -

    A convolution is performed on the image which outputs -a 3D volume of neurons. The weights to the input are arranged in a number of 2D matrices, known as filters. -

    - -

    Each filter slides along the input image, taking the dot product -between each small part of the image and the filter, in all depth -dimensions. This is then passed through a non-linear function, -typically the Rectified Linear (ReLu) function, which serves as the -activation of the neurons in the first convolutional layer. This is -further passed through a pooling layer, which reduces the size of the -convolutional layer, e.g. by taking the maximum or average across some -small regions, and this serves as input to the next convolutional -layer. -

    -
    - -
    -

    Systematic reduction

    - -

    By systematically reducing the size of the input volume, through -convolution and pooling, the network should create representations of -small parts of the input, and then from them assemble representations -of larger areas. The final pooling layer is flattened to serve as -input to a hidden layer, such that each neuron in the final pooling -layer is connected to every single neuron in the hidden layer. This -then serves as input to the output layer, e.g. a softmax output for -classification. -

    -
    - -
    -

    Prerequisites: Collect and pre-process data

    - - -
    -
    -
    -
    -
    -
    # import necessary packages
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from sklearn import datasets
    -
    -
    -# ensure the same random numbers appear every time
    -np.random.seed(0)
    -
    -# display images in notebook
    -%matplotlib inline
    -plt.rcParams['figure.figsize'] = (12,12)
    -
    -
    -# download MNIST dataset
    -digits = datasets.load_digits()
    -
    -# define inputs and labels
    -inputs = digits.images
    -labels = digits.target
    -
    -# RGB images have a depth of 3
    -# our images are grayscale so they should have a depth of 1
    -inputs = inputs[:,:,:,np.newaxis]
    -
    -print("inputs = (n_inputs, pixel_width, pixel_height, depth) = " + str(inputs.shape))
    -print("labels = (n_inputs) = " + str(labels.shape))
    -
    -
    -# choose some random images to display
    -n_inputs = len(inputs)
    -indices = np.arange(n_inputs)
    -random_indices = np.random.choice(indices, size=5)
    -
    -for i, image in enumerate(digits.images[random_indices]):
    -    plt.subplot(1, 5, i+1)
    -    plt.axis('off')
    -    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    -    plt.title("Label: %d" % digits.target[random_indices[i]])
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -

    Importing Keras and Tensorflow

    - - -
    -
    -
    -
    -
    -
    from tensorflow.keras import datasets, layers, models
    -from tensorflow.keras.layers import Input
    -from tensorflow.keras.models import Sequential      #This allows appending layers to existing models
    -from tensorflow.keras.layers import Dense           #This allows defining the characteristics of a particular layer
    -from tensorflow.keras import optimizers             #This allows using whichever optimiser we want (sgd,adam,RMSprop)
    -from tensorflow.keras import regularizers           #This allows using whichever regularizer we want (l1,l2,l1_l2)
    -from tensorflow.keras.utils import to_categorical   #This allows using categorical cross entropy as the cost function
    -#from tensorflow.keras import Conv2D
    -#from tensorflow.keras import MaxPooling2D
    -#from tensorflow.keras import Flatten
    -
    -from sklearn.model_selection import train_test_split
    -
    -# representation of labels
    -labels = to_categorical(labels)
    -
    -# split into train and test data
    -# one-liner from scikit-learn library
    -train_size = 0.8
    -test_size = 1 - train_size
    -X_train, X_test, Y_train, Y_test = train_test_split(inputs, labels, train_size=train_size,
    -                                                    test_size=test_size)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -

    Running with Keras

    - - - -
    -
    -
    -
    -
    -
    def create_convolutional_neural_network_keras(input_shape, receptive_field,
    -                                              n_filters, n_neurons_connected, n_categories,
    -                                              eta, lmbd):
    -    model = Sequential()
    -    model.add(layers.Conv2D(n_filters, (receptive_field, receptive_field), input_shape=input_shape, padding='same',
    -              activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    -    model.add(layers.MaxPooling2D(pool_size=(2, 2)))
    -    model.add(layers.Flatten())
    -    model.add(layers.Dense(n_neurons_connected, activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    -    model.add(layers.Dense(n_categories, activation='softmax', kernel_regularizer=regularizers.l2(lmbd)))
    -    
    -    sgd = optimizers.SGD(learning_rate=eta)
    -    model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
    -    
    -    return model
    -
    -epochs = 100
    -batch_size = 100
    -input_shape = X_train.shape[1:4]
    -receptive_field = 3
    -n_filters = 10
    -n_neurons_connected = 50
    -n_categories = 10
    -
    -eta_vals = np.logspace(-5, 1, 7)
    -lmbd_vals = np.logspace(-5, 1, 7)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -

    Final part

    - - - -
    -
    -
    -
    -
    -
    CNN_keras = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)
    -        
    -for i, eta in enumerate(eta_vals):
    -    for j, lmbd in enumerate(lmbd_vals):
    -        CNN = create_convolutional_neural_network_keras(input_shape, receptive_field,
    -                                              n_filters, n_neurons_connected, n_categories,
    -                                              eta, lmbd)
    -        CNN.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=0)
    -        scores = CNN.evaluate(X_test, Y_test)
    -        
    -        CNN_keras[i][j] = CNN
    -        
    -        print("Learning rate = ", eta)
    -        print("Lambda = ", lmbd)
    -        print("Test accuracy: %.3f" % scores[1])
    -        print()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -

    Final visualization

    - - - -
    -
    -
    -
    -
    -
    # visual representation of grid search
    -# uses seaborn heatmap, could probably do this in matplotlib
    -import seaborn as sns
    -
    -sns.set()
    -
    -train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    -test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    -
    -for i in range(len(eta_vals)):
    -    for j in range(len(lmbd_vals)):
    -        CNN = CNN_keras[i][j]
    -
    -        train_accuracy[i][j] = CNN.evaluate(X_train, Y_train)[1]
    -        test_accuracy[i][j] = CNN.evaluate(X_test, Y_test)[1]
    -
    -        
    -fig, ax = plt.subplots(figsize = (10, 10))
    -sns.heatmap(train_accuracy, annot=True, ax=ax, cmap="viridis")
    -ax.set_title("Training Accuracy")
    -ax.set_ylabel("$\eta$")
    -ax.set_xlabel("$\lambda$")
    -plt.show()
    -
    -fig, ax = plt.subplots(figsize = (10, 10))
    -sns.heatmap(test_accuracy, annot=True, ax=ax, cmap="viridis")
    -ax.set_title("Test Accuracy")
    -ax.set_ylabel("$\eta$")
    -ax.set_xlabel("$\lambda$")
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - diff --git a/doc/pub/week44/html/week44-solarized.html b/doc/pub/week44/html/week44-solarized.html index 0cf7b26c0..ee5abe447 100644 --- a/doc/pub/week44/html/week44-solarized.html +++ b/doc/pub/week44/html/week44-solarized.html @@ -102,6 +102,12 @@ div.toc p,a { None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -116,6 +122,8 @@ div.toc p,a { 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -124,36 +132,75 @@ div.toc p,a { 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -200,27 +247,7 @@ div.toc p,a { ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -548,6 +575,147 @@ only neighboring neurons in the input instead of connecting all with the first h

    We say we perform a filtering (convolution is the mathematical operation).

    +









    +

    How to do image compression before the era of deep learning

    + +

    The singular-value decomposition (SVD) algorithm has been for decades one of the standard ways of compressing images. +The lectures on the SVD give many of the essential details concerning the SVD. +

    + +

    The orthogonal vectors which are obtained from the SVD, can be used to +project down the dimensionality of a given image. In the example here +we gray-scale an image and downsize it. +

    + +

    This recipe relies on us being able to actually perform the SVD. For +large images, and in particular with many images to reconstruct, using the SVD +may quickly become an overwhelming task. With the advent of efficient deep +learning methods like CNNs and later generative methods, these methods +have become in the last years the premier way of performing image +analysis. In particular for classification problems with labelled images. +

    + +









    +

    The SVD example

    + + + +
    +
    +
    +
    +
    +
    from matplotlib.image import imread
    +import matplotlib.pyplot as plt
    +import scipy.linalg as ln
    +import numpy as np
    +import os
    +from PIL import Image
    +from math import log10, sqrt 
    +plt.rcParams['figure.figsize'] = [16, 8]
    +# Import image
    +A = imread(os.path.join("figslides/photo1.jpg"))
    +X = A.dot([0.299, 0.5870, 0.114]) # Convert RGB to grayscale
    +img = plt.imshow(X)
    +# convert to gray
    +img.set_cmap('gray')
    +plt.axis('off')
    +plt.show()
    +# Call image size
    +print(': %s'%str(X.shape))
    +
    +
    +# split the matrix into U, S, VT
    +U, S, VT = np.linalg.svd(X,full_matrices=False)
    +S = np.diag(S)
    +m = 800 # Image's width
    +n = 1200 # Image's height
    +j = 0
    +# Try compression with different k vectors (these represent projections):
    +for k in (5,10, 20, 100,200,400,500):
    +    # Original size of the image
    +    originalSize = m * n 
    +    # Size after compressed
    +    compressedSize = k * (1 + m + n) 
    +    # The projection of the original image
    +    Xapprox = U[:,:k] @ S[0:k,:k] @ VT[:k,:]
    +    plt.figure(j+1)
    +    j += 1
    +    img = plt.imshow(Xapprox)
    +    img.set_cmap('gray')
    +    
    +    plt.axis('off')
    +    plt.title('k = ' + str(k))
    +    plt.show() 
    +    print('Original size of image:')
    +    print(originalSize)
    +    print('Compression rate as Compressed image / Original size:')
    +    ratio = compressedSize * 1.0 / originalSize
    +    print(ratio)
    +    print('Compression rate is ' + str( round(ratio * 100 ,2)) + '%' )  
    +    # Estimate MQA
    +    x= X.astype("float")
    +    y=Xapprox.astype("float")
    +    err = np.sum((x - y) ** 2)
    +    err /= float(X.shape[0] * Xapprox.shape[1])
    +    print('The mean-square deviation '+ str(round( err)))
    +    max_pixel = 255.0
    +    # Estimate Signal Noise Ratio
    +    srv = 20 * (log10(max_pixel / sqrt(err)))
    +    print('Signa to noise ratio '+ str(round(srv)) +'dB')
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Mathematics of CNNs

    + +

    The mathematics of CNNs is based on the mathematical operation of +convolution. In mathematics (in particular in functional analysis), +convolution is represented by mathematical operations (integration, +summation etc) on two functions in order to produce a third function +that expresses how the shape of one gets modified by the other. +Convolution has a plethora of applications in a variety of +disciplines, spanning from statistics to signal processing, computer +vision, solutions of differential equations,linear algebra, +engineering, and yes, machine learning. +

    + +

    Mathematically, convolution is defined as follows (one-dimensional example): +Let us define a continuous function \( y(t) \) given by +

    +$$ +y(t) = \int x(a) w(t-a) da, +$$ + +

    where \( x(a) \) represents a so-called input and \( w(t-a) \) is normally called the weight function or kernel.

    + +

    The above integral is written in a more compact form as

    +$$ +y(t) = \left(x * w\right)(t). +$$ + +

    The discretized version reads

    +$$ +y(t) = \sum_{a=-\infty}^{a=\infty}x(a)w(t-a). +$$ + +

    Computing the inverse of the above convolution operations is known as deconvolution and the process is commutative.

    + +

    How can we use this? And what does it mean? Let us study some familiar examples first.

    +









    Mathematics of CNNs

    @@ -651,7 +819,9 @@ $$ \delta_i = \sum_{k=0}^{k=m-1}\alpha_k\beta_{i-k}, $$ -

    where \( m=3 \) in our case, the maximum length of the vector \( \alpha \). Note that the vector \( \boldsymbol{\beta} \) has length \( n=4 \).

    +

    where \( m=3 \) in our case, the maximum length of +the vector \( \alpha \). Note that the vector \( \boldsymbol{\beta} \) has length \( n=4 \). Below we will find an even more efficient representation. +











    A more efficient way of coding the above Convolution

    @@ -671,6 +841,10 @@ $$ \end{bmatrix}\begin{bmatrix} \beta_0 \\ \beta_1 \\ \beta_2 \\ \beta_3\end{bmatrix}. $$ + +









    +

    Commutative process

    +

    The process is commutative and we can easily see that we can rewrite the multiplication in terms of a matrix holding \( \beta \) and a vector holding \( \alpha \). In this case we have

    @@ -691,6 +865,9 @@ rather code the convolutions in the minimal memory footprint that they require.

    +









    +

    Toeplitz matrices

    +

    The above matrices are examples of so-called Toeplitz matrices. A Toeplitz matrix is a matrix in which each descending diagonal from @@ -727,18 +904,125 @@ a vector.









    Generalizing the above one-dimensional case

    -

    In order to align the above simple case with the more general convolution cases, we rename \( \boldsymbol{\alpha} \), whose length is \( m=3 \), with \( \boldsymbol{w} \). -We will interpret \( \boldsymbol{w} \) as a weight/filter function with which we want to perform the convolution with an input varibale \( \boldsymbol{x} \). -We replace thus \( \boldsymbol{\beta} \) with \( \boldsymbol{x} \) and \( \boldsymbol{\delta} \) with \( \boldsymbol{s} \) and have +

    In order to align the above simple case with the more general +convolution cases, we rename \( \boldsymbol{\alpha} \), whose length is \( m=3 \), +with \( \boldsymbol{w} \). We will interpret \( \boldsymbol{w} \) as a weight/filter function +with which we want to perform the convolution with an input variable +\( \boldsymbol{x} \) of length \( n \). We will assume always that the filter +\( \boldsymbol{w} \) has dimensionality \( m \le n \).

    + +

    We replace thus \( \boldsymbol{\beta} \) with \( \boldsymbol{x} \) and \( \boldsymbol{\delta} \) with \( \boldsymbol{y} \) and have

    $$ -s(i) \left(x*w\right)(i)= \sum_{k=0}^{k=m-1}w(k)x(i-k), +y(i)= \left(x*w\right)(i)= \sum_{k=0}^{k=m-1}w(k)x(i-k), $$

    where \( m=3 \) in our case, the maximum length of the vector \( \boldsymbol{w} \). Here the symbol \( * \) represents the mathematical operation of convolution.

    -

    Two-dimensional Objects

    + +









    +

    Memory considerations

    + +

    This expression leaves us however with some terms with negative +indices, for example \( x(-1) \) and \( x(-2) \) which may not be defined. Our +vector \( \boldsymbol{x} \) has components \( x(0) \), \( x(1) \), \( x(2) \) and \( x(3) \). +

    + +

    The index \( j \) for \( \boldsymbol{x} \) runs from \( j=0 \) to \( j=3 \) since \( \boldsymbol{x} \) is meant to +represent a third-order polynomial. +

    + +

    Furthermore, the index \( i \) runs from \( i=0 \) to \( i=5 \) since \( \boldsymbol{y} \) +contains the coefficients of a fifth-order polynomial. When \( i=5 \) we +may also have values of \( x(4) \) and \( x(5) \) which are not defined. +

    + +









    +

    Padding

    + +

    The solution to this is what is called padding! We simply define a +new vector \( x \) with two added elements set to zero before \( x(0) \) and +two new elements after \( x(3) \) set to zero. That is, we augment the +length of \( \boldsymbol{x} \) from \( n=4 \) to \( n+2P=8 \), where \( P=2 \) is the padding +constant (a new hyperparameter), see discussions below as well. +

    + +









    +

    New vector

    + +

    We have a new vector defined as \( x(0)=0 \), \( x(1)=0 \), +\( x(2)=\beta_0 \), \( x(3)=\beta_1 \), \( x(4)=\beta_2 \), \( x(5)=\beta_3 \), +\( x(6)=0 \), and \( x(7)=0 \). +

    + +

    We have added four new elements, which +are all zero. The benefit is that we can rewrite the equation for +\( \boldsymbol{y} \), with \( i=0,1,\dots,5 \), +

    +$$ +y(i) = \sum_{k=0}^{k=m-1}w(k)x(i+(m-1)-k). +$$ + +

    As an example, we have

    +$$ +y(4)=x(6)w(0)+x(5)w(1)+x(4)w(2)=0\times \alpha_0+\beta_3\alpha_1+\beta_2\alpha_2, +$$ + +

    as before except that we have an additional term \( x(6)w(0) \), which is zero.

    + +

    Similarly, for the fifth-order term we have

    +$$ +y(5)=x(7)w(0)+x(6)w(1)+x(5)w(2)=0\times \alpha_0+0\times\alpha_1+\beta_3\alpha_2. +$$ + +

    The zeroth-order term is

    +$$ +y(0)=x(2)w(0)+x(1)w(1)+x(0)w(2)=\beta_0 \alpha_0+0\times\alpha_1+0\times\alpha_2=\alpha_0\beta_0. +$$ + + +









    +

    Rewriting as dot products

    + +

    If we now flip the filter/weight vector, with the following term as a typical example

    +$$ +y(0)=x(2)w(0)+x(1)w(1)+x(0)w(2)=x(2)\tilde{w}(2)+x(1)\tilde{w}(1)+x(0)\tilde{w}(0), +$$ + +

    with \( \tilde{w}(0)=w(2) \), \( \tilde{w}(1)=w(1) \), and \( \tilde{w}(2)=w(0) \), we can then rewrite the above sum as a dot product of +\( x(i:i+(m-1))\tilde{w} \) for element \( y(i) \), where \( x(i:i+(m-1)) \) is simply a patch of \( \boldsymbol{x} \) of size \( m-1 \). +

    + +

    The padding \( P \) we have introduced for the convolution stage is just +another hyperparameter which is introduced as part of the +architecture. Similarly, below we will also introduce another +hyperparameter called Stride \( S \). +

    + +









    +

    Cross correlation

    + +

    In essentially all applications one uses what is called cross correlation instead of the standard convolution described above. +This means that multiplication is performed in the same direction and instead of the general expression we have discussed above (with infinite sums) +

    +$$ +y(i) = \sum_{k=-\infty}^{k=\infty}w(k)x(i-k), +$$ + +

    we have now

    +$$ +y(i) = \sum_{k=-\infty}^{k=\infty}w(k)x(i+k). +$$ + +

    Both TensorFlow and PyTorch (as well as our own code example below), +implement the last equation, although it is normally referred to as +convolution. The same padding rules and stride rules discussed below +apply to this expression as well. +

    + +

    We leave it as an exercise for you to convince yourself that the example we have discussed till now, gives the same final expression using the last expression.

    +

    Two-dimensional objects

    We are now ready to start studying the discrete convolutions relevant for convolutional neural networks. We often use convolutions over more than one dimension at a time. If @@ -755,16 +1039,22 @@ $$ Y(i,j)=(X * W)(i,j) = \sum_m\sum_n X(i-m,j-n)W(m,n). $$ -

    Normally the latter is more straightforward to implement in a machine larning library since there is less variation in the range of values of \( m \) and \( n \).

    +

    Normally the latter is more straightforward to implement in a machine +larning library since there is less variation in the range of values +of \( m \) and \( n \). +

    -

    Many deep learning libraries implement cross-correlation instead of convolution (although it is referred to s convolution)

    +

    As mentioned above, most deep learning libraries implement +cross-correlation instead of convolution (although it is referred to as +convolution) +

    $$ Y(i,j)=(X * W)(i,j) = \sum_m\sum_n X(i+m,j+n)W(m,n). $$









    -

    CNNs in more detail

    +

    CNNs in more detail, simple example

    Let assume we have an input matrix \( X \) of dimensionality \( 3\times 3 \) and a \( 2\times 2 \) filter \( W \) given by the following matrices @@ -824,56 +1114,101 @@ $$

    is now given by \( \boldsymbol{W}'\boldsymbol{X}' \) which is a vector of length \( 4 \) instead of the originally resulting \( 2\times 2 \) output matrix.











    -

    Performing a discrete convolution with padding (From Raschka et al)

    +

    The convolution stage

    -
    -
    -
    -

    Figure 4: A deep CNN

    -
    -

    -
    - -









    -

    Performing a general discrete convolution (From Raschka et al)

    - -
    -
    -
    -

    Figure 5: A deep CNN

    -
    -

    -
    - -









    -

    Set of filters

    - -

    The collection of kernels/filters \( W \) defining a discrete convolution has a shape -corresponding to some permutation of \( (n, m, k_1, \ldots, k_N) \), where +

    The convolution stage, where we apply different filters \( \boldsymbol{W} \) in +order to reduce the dimensionality of an image, adds, in addition to +the weights and biases (to be trained by the back propagation +algorithm) that define the filters, two new hyperparameters, the so-called +padding \( P \) and the stride \( S \).

    -$$ -\begin{equation*} -\begin{split} - n &\equiv \text{number of output feature maps},\\ - m &\equiv \text{number of input feature maps},\\ - k_j &\equiv \text{kernel size along axis $j$}. -\end{split} -\end{equation*} -$$ +









    +

    Finding the number of parameters

    -

    The following properties affect the output size \( o_j \) of a convolutional layer -along axis \( j \): +

    In the above example we have an input matrix of dimension \( 3\times +3 \). In general we call the input for an input volume and it is defined +by its width \( H_1 \), height \( H_1 \) and depth \( D_1 \). If we have the +standard three color channels \( D_1=3 \).

    +

    The above example has \( W_1=H_1=3 \) and \( D_1=1 \).

    + +

    When we introduce the filter we have the following additional hyperparameters

      -
    1. \( i_j \): input size along axis \( j \),
    2. -
    3. \( k_j \): kernel/filter size along axis \( j \),
    4. -
    5. stride (distance between two consecutive positions of the kernel/filter) along axis \( j \),
    6. -
    7. zero padding (number of zeros concatenated at the beginning and at the end of an axis) along axis \( j \).
    8. +
    9. \( K \) the number of filters. It is common to perform the convolution of the input several times since by experience shrinking the input too fast does not work well
    10. +
    11. \( F \) as the filter's spatial extent
    12. +
    13. \( S \) as the stride parameter
    14. +
    15. \( P \) as the padding parameter
    -

    For instance, the above examples shows a \( 2\times 2 \) kernel/filter \( \boldsymbol{W} \) applied to a \( 3 \times 3 \) input padded with a \( 0 \times 0 \) -border of zeros using \( 1 \times 1 \) strides. +

    These parameters are defined by the architecture of the network and are not included in the training.

    + +









    +

    New image (or volume)

    + +

    Acting with the filter on the input volume produces an output volume +which is defined by its width \( W_2 \), its height \( H_2 \) and its depth +\( D_2 \). +

    + +

    These are defined by the following relations

    +$$ +W_2 = \frac{(W_1-F+2P)}{S}+1, +$$ + +$$ +H_2 = \frac{(H_1-F+2P)}{S}+1, +$$ + +

    and \( D_2=K \).

    + +









    +

    Parameters to train, common settings

    + +

    With parameter sharing, the convolution involves thus for each filter \( F\times F\times D_1 \) weights plus one bias parameter.

    + +

    In total we have

    +$$ +\left(F\times F\times D_1)\right) \times K+(K\mathrm{--biases}), +$$ + +

    parameters to train by back propagation.

    + +

    It is common to let \( K \) come in powers of \( 2 \), that is \( 32 \), \( 64 \), \( 128 \) etc.

    + +
    +Common settings +

    +

      +
    1. \( \begin{array}{c} F=3 & S=1 & P=1 \end{array} \)
    2. +
    3. \( \begin{array}{c} F=5 & S=1 & P=2 \end{array} \)
    4. +
    5. \( \begin{array}{c} F=5 & S=2 & P=\mathrm{open} \end{array} \)
    6. +
    7. \( \begin{array}{c} F=1 & S=1 & P=0 \end{array} \)
    8. +
    +
    + + +









    +

    Examples of CNN setups

    + +

    Let us assume we have an input volume \( V \) given by an image of dimensionality +\( 32\times 32 \times 3 \), that is three color channels and \( 32\times 32 \) pixels. +

    + +

    We apply a filter of dimension \( 5\times 5 \) ten times with stride \( S=1 \) and padding \( P=0 \).

    + +

    The output volume is given by \( (32-5)/1+1=28 \), resulting in ten images +of dimensionality \( 28\times 28\times 3 \). +

    + +

    The total number of parameters to train for each filter is then +\( 5\times 5\times 3+1 \), where the last parameter is the bias. This +gives us \( 76 \) parameters for each filter, leading to a total of \( 760 \) +parameters for the ten filters. +

    + +

    How many parameters will a filter of dimensionality \( 3\times 3 \) +(adding color channels) result in if we produce \( 32 \) new images? Use \( S=1 \) and \( P=0 \).

    Note that strides constitute a form of subsampling. As an alternative to @@ -883,124 +1218,30 @@ the kernel by hops of two is equivalent to moving the kernel by hops of one but retaining only odd output elements.

    +









    +

    Summarizing: Performing a general discrete convolution (From Raschka et al)

    + +
    +
    +
    +

    Figure 4: A deep CNN

    +
    +

    +
    +









    Pooling

    -

    In addition to discrete convolutions themselves, {\em pooling\/} operations +

    In addition to discrete convolutions themselves, pooling operations make up another important building block in CNNs. Pooling operations reduce the size of feature maps by using some function to summarize subregions, such as taking the average or the maximum value.

    Pooling works by sliding a window across the input and feeding the content of -the window to a {\em pooling function}. In some sense, pooling works very much +the window to a pooling function. In some sense, pooling works very much like a discrete convolution, but replaces the linear combination described by -the kernel with some other function. Poolin -provides an example for average pooling, and -does the same for max pooling. -

    - -

    The following properties affect the output size \( o_j \) of a pooling layer -along axis \( j \): -

    - -
      -
    1. \( i_j \): input size along axis \( j \),
    2. -
    3. \( k_j \): pooling window size along axis \( j \),
    4. -
    5. \( s_j \): stride (distance between two consecutive positions of the pooling window) along axis \( j \).
    6. -
    -

    The analysis of the relationship between convolutional layer properties is eased -by the fact that they don't interact across axes, i.e., the choice of kernel -size, stride and zero padding along axis \( j \) only affects the output size of -axis \( j \). Because of that, we will focus on the following simplified -setting: -

    - -
      -
    1. 2-D discrete convolutions (\( N = 2 \)),
    2. -
    3. square inputs (\( i_1 = i_2 = i \)),
    4. -
    5. square kernel size (\( k_1 = k_2 = k \)),
    6. -
    7. same strides along both axes (\( s_1 = s_2 = s \)),
    8. -
    9. same zero padding along both axes (\( p_1 = p_2 = p \)).
    10. -
    -

    This facilitates the analysis and the visualization, but keep in mind that the -results outlined here also generalize to the N-D and non-square cases. -

    - -









    -

    No zero padding, unit strides

    - -

    The simplest case to analyze is when the kernel just slides across every -position of the input (i.e., \( s = 1 \) and \( p = 0 \)). -

    - -

    For any \( i \) and \( k \), and for \( s = 1 \) and \( p = 0 \),

    -$$ -\begin{equation*} - o = (i - k) + 1. -\end{equation*} -$$ - - -









    -

    Zero padding, unit strides

    - -

    To factor in zero padding (i.e., only restricting to \( s = 1 \)), let's consider -its effect on the effective input size: padding with \( p \) zeros changes the -effective input size from \( i \) to \( i + 2p \). In the general case, we can infer the following -relationship -

    - -

    For any \( i \), \( k \) and \( p \), and for \( s = 1 \),

    -$$ -\begin{equation*} - o = (i - k) + 2p + 1. -\end{equation*} -$$ - - -









    -

    Half (same) padding

    - -

    Having the output size be the same as the input size (i.e., \( o = i \)) can be a -desirable property: -

    - -

    For any \( i \) and for \( k \) odd (\( k = 2n + 1, \quad n \in \mathbb{N} \)), \( s = 1 \) and -\( p = \lfloor k / 2 \rfloor = n \), -

    -$$ -\begin{equation*} -\begin{split} - o &= i + 2 \lfloor k / 2 \rfloor - (k - 1) \\ - &= i + 2n - 2n \\ - &= i. -\end{split} -\end{equation*} -$$ - - -









    -

    Full padding

    - -

    While convolving a kernel generally decreases the output size with -respect to the input size, sometimes the opposite is required. This can be -achieved with proper zero padding: -

    - -

    For any \( i \) and \( k \), and for \( p = k - 1 \) and \( s = 1 \),

    -$$ -\begin{equation*} -\begin{split} - o &= i + 2(k - 1) - (k - 1) \\ - &= i + (k - 1). -\end{split} -\end{equation*} -$$ - -

    This is sometimes referred to as full padding, because in this -setting every possible partial or complete superimposition of the kernel on the -input feature map is taken into account. +the kernel with some other function.











    @@ -1014,29 +1255,608 @@ mean or average pooling, which all share the same idea of aggregating the input locally by applying a non-linearity to the content of some patches.

    -

    Since pooling does not involve -zero padding, the relationship describing the general case is as follows: -

    - -

    For any \( i \), \( k \) and \( s \),

    -$$ -\begin{equation*} - o = \left\lfloor \frac{i - k}{s} \right\rfloor + 1. -\end{equation*} -$$ - -









    Pooling types (From Raschka et al)


    -

    Figure 6: A deep CNN

    +

    Figure 5: A deep CNN

    +









    +

    Building convolutional neural networks in Tensorflow and Keras

    + +

    As discussed above, CNNs are neural networks built from the assumption that the inputs +to the network are 2D images. This is important because the number of features or pixels in images +grows very fast with the image size, and an enormous number of weights and biases are needed in order to build an accurate network. +

    + +

    As before, we still have our input, a hidden layer and an output. What's novel about convolutional networks +are the convolutional and pooling layers stacked in pairs between the input and the hidden layer. +In addition, the data is no longer represented as a 2D feature matrix, instead each input is a number of 2D +matrices, typically 1 for each color dimension (Red, Green, Blue). +

    + +









    +

    Setting it up

    + +

    It means that to represent the entire +dataset of images, we require a 4D matrix or tensor. This tensor has the dimensions: +

    +$$ +(n_{inputs},\, n_{pixels, width},\, n_{pixels, height},\, depth) . +$$ + + +









    +

    The MNIST dataset again

    + +

    The MNIST dataset consists of grayscale images with a pixel size of +\( 28\times 28 \), meaning we require \( 28 \times 28 = 724 \) weights to each +neuron in the first hidden layer. +

    + +

    If we were to analyze images of size \( 128\times 128 \) we would require +\( 128 \times 128 = 16384 \) weights to each neuron. Even worse if we were +dealing with color images, as most images are, we have an image matrix +of size \( 128\times 128 \) for each color dimension (Red, Green, Blue), +meaning 3 times the number of weights \( = 49152 \) are required for every +single neuron in the first hidden layer. +

    + + +









    +

    Strong correlations

    + +

    Images typically have strong local correlations, meaning that a small +part of the image varies little from its neighboring regions. If for +example we have an image of a blue car, we can roughly assume that a +small blue part of the image is surrounded by other blue regions. +

    + +

    Therefore, instead of connecting every single pixel to a neuron in the +first hidden layer, as we have previously done with deep neural +networks, we can instead connect each neuron to a small part of the +image (in all 3 RGB depth dimensions). The size of each small area is +fixed, and known as a receptive. +

    + + + +

    Layers of a CNN

    + +

    The layers of a convolutional neural network arrange neurons in 3D: width, height and depth. +The input image is typically a square matrix of depth 3. +

    + +

    A convolution is performed on the image which outputs +a 3D volume of neurons. The weights to the input are arranged in a number of 2D matrices, known as filters. +

    + +

    Each filter slides along the input image, taking the dot product +between each small part of the image and the filter, in all depth +dimensions. This is then passed through a non-linear function, +typically the Rectified Linear (ReLu) function, which serves as the +activation of the neurons in the first convolutional layer. This is +further passed through a pooling layer, which reduces the size of the +convolutional layer, e.g. by taking the maximum or average across some +small regions, and this serves as input to the next convolutional +layer. +

    + +









    +

    Systematic reduction

    + +

    By systematically reducing the size of the input volume, through +convolution and pooling, the network should create representations of +small parts of the input, and then from them assemble representations +of larger areas. The final pooling layer is flattened to serve as +input to a hidden layer, such that each neuron in the final pooling +layer is connected to every single neuron in the hidden layer. This +then serves as input to the output layer, e.g. a softmax output for +classification. +

    + + +









    +

    Prerequisites: Collect and pre-process data

    + + +
    +
    +
    +
    +
    +
    # import necessary packages
    +import numpy as np
    +import matplotlib.pyplot as plt
    +from sklearn import datasets
    +
    +
    +# ensure the same random numbers appear every time
    +np.random.seed(0)
    +
    +# display images in notebook
    +%matplotlib inline
    +plt.rcParams['figure.figsize'] = (12,12)
    +
    +
    +# download MNIST dataset
    +digits = datasets.load_digits()
    +
    +# define inputs and labels
    +inputs = digits.images
    +labels = digits.target
    +
    +# RGB images have a depth of 3
    +# our images are grayscale so they should have a depth of 1
    +inputs = inputs[:,:,:,np.newaxis]
    +
    +print("inputs = (n_inputs, pixel_width, pixel_height, depth) = " + str(inputs.shape))
    +print("labels = (n_inputs) = " + str(labels.shape))
    +
    +
    +# choose some random images to display
    +n_inputs = len(inputs)
    +indices = np.arange(n_inputs)
    +random_indices = np.random.choice(indices, size=5)
    +
    +for i, image in enumerate(digits.images[random_indices]):
    +    plt.subplot(1, 5, i+1)
    +    plt.axis('off')
    +    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    +    plt.title("Label: %d" % digits.target[random_indices[i]])
    +plt.show()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Importing Keras and Tensorflow

    + + +
    +
    +
    +
    +
    +
    from tensorflow.keras import datasets, layers, models
    +from tensorflow.keras.layers import Input
    +from tensorflow.keras.models import Sequential      #This allows appending layers to existing models
    +from tensorflow.keras.layers import Dense           #This allows defining the characteristics of a particular layer
    +from tensorflow.keras import optimizers             #This allows using whichever optimiser we want (sgd,adam,RMSprop)
    +from tensorflow.keras import regularizers           #This allows using whichever regularizer we want (l1,l2,l1_l2)
    +from tensorflow.keras.utils import to_categorical   #This allows using categorical cross entropy as the cost function
    +#from tensorflow.keras import Conv2D
    +#from tensorflow.keras import MaxPooling2D
    +#from tensorflow.keras import Flatten
    +
    +from sklearn.model_selection import train_test_split
    +
    +# representation of labels
    +labels = to_categorical(labels)
    +
    +# split into train and test data
    +# one-liner from scikit-learn library
    +train_size = 0.8
    +test_size = 1 - train_size
    +X_train, X_test, Y_train, Y_test = train_test_split(inputs, labels, train_size=train_size,
    +                                                    test_size=test_size)
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + +

    Running with Keras

    + + + +
    +
    +
    +
    +
    +
    def create_convolutional_neural_network_keras(input_shape, receptive_field,
    +                                              n_filters, n_neurons_connected, n_categories,
    +                                              eta, lmbd):
    +    model = Sequential()
    +    model.add(layers.Conv2D(n_filters, (receptive_field, receptive_field), input_shape=input_shape, padding='same',
    +              activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    +    model.add(layers.MaxPooling2D(pool_size=(2, 2)))
    +    model.add(layers.Flatten())
    +    model.add(layers.Dense(n_neurons_connected, activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    +    model.add(layers.Dense(n_categories, activation='softmax', kernel_regularizer=regularizers.l2(lmbd)))
    +    
    +    sgd = optimizers.SGD(lr=eta)
    +    model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
    +    
    +    return model
    +
    +epochs = 100
    +batch_size = 100
    +input_shape = X_train.shape[1:4]
    +receptive_field = 3
    +n_filters = 10
    +n_neurons_connected = 50
    +n_categories = 10
    +
    +eta_vals = np.logspace(-5, 1, 7)
    +lmbd_vals = np.logspace(-5, 1, 7)
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Final part

    + + + +
    +
    +
    +
    +
    +
    CNN_keras = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)
    +        
    +for i, eta in enumerate(eta_vals):
    +    for j, lmbd in enumerate(lmbd_vals):
    +        CNN = create_convolutional_neural_network_keras(input_shape, receptive_field,
    +                                              n_filters, n_neurons_connected, n_categories,
    +                                              eta, lmbd)
    +        CNN.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=0)
    +        scores = CNN.evaluate(X_test, Y_test)
    +        
    +        CNN_keras[i][j] = CNN
    +        
    +        print("Learning rate = ", eta)
    +        print("Lambda = ", lmbd)
    +        print("Test accuracy: %.3f" % scores[1])
    +        print()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Final visualization

    + + + +
    +
    +
    +
    +
    +
    # visual representation of grid search
    +# uses seaborn heatmap, could probably do this in matplotlib
    +import seaborn as sns
    +
    +sns.set()
    +
    +train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    +test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    +
    +for i in range(len(eta_vals)):
    +    for j in range(len(lmbd_vals)):
    +        CNN = CNN_keras[i][j]
    +
    +        train_accuracy[i][j] = CNN.evaluate(X_train, Y_train)[1]
    +        test_accuracy[i][j] = CNN.evaluate(X_test, Y_test)[1]
    +
    +        
    +fig, ax = plt.subplots(figsize = (10, 10))
    +sns.heatmap(train_accuracy, annot=True, ax=ax, cmap="viridis")
    +ax.set_title("Training Accuracy")
    +ax.set_ylabel("$\eta$")
    +ax.set_xlabel("$\lambda$")
    +plt.show()
    +
    +fig, ax = plt.subplots(figsize = (10, 10))
    +sns.heatmap(test_accuracy, annot=True, ax=ax, cmap="viridis")
    +ax.set_title("Test Accuracy")
    +ax.set_ylabel("$\eta$")
    +ax.set_xlabel("$\lambda$")
    +plt.show()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    The CIFAR01 data set

    + +

    The CIFAR10 dataset contains 60,000 color images in 10 classes, with +6,000 images in each class. The dataset is divided into 50,000 +training images and 10,000 testing images. The classes are mutually +exclusive and there is no overlap between them. +

    + + + +
    +
    +
    +
    +
    +
    import tensorflow as tf
    +
    +from tensorflow.keras import datasets, layers, models
    +import matplotlib.pyplot as plt
    +
    +# We import the data set
    +(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()
    +
    +# Normalize pixel values to be between 0 and 1 by dividing by 255. 
    +train_images, test_images = train_images / 255.0, test_images / 255.0
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Verifying the data set

    + +

    To verify that the dataset looks correct, let's plot the first 25 images from the training set and display the class name below each image.

    + + + +
    +
    +
    +
    +
    +
    class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
    +               'dog', 'frog', 'horse', 'ship', 'truck']
    +plt.figure(figsize=(10,10))
    +for i in range(25):
    +    plt.subplot(5,5,i+1)
    +    plt.xticks([])
    +    plt.yticks([])
    +    plt.grid(False)
    +    plt.imshow(train_images[i], cmap=plt.cm.binary)
    +    # The CIFAR labels happen to be arrays, 
    +    # which is why you need the extra index
    +    plt.xlabel(class_names[train_labels[i][0]])
    +plt.show()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Set up the model

    + +

    The 6 lines of code below define the convolutional base using a common pattern: a stack of Conv2D and MaxPooling2D layers.

    + +

    As input, a CNN takes tensors of shape (image_height, image_width, color_channels), ignoring the batch size. If you are new to these dimensions, color_channels refers to (R,G,B). In this example, you will configure our CNN to process inputs of shape (32, 32, 3), which is the format of CIFAR images. You can do this by passing the argument input_shape to our first layer.

    + + + +
    +
    +
    +
    +
    +
    model = models.Sequential()
    +model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
    +model.add(layers.MaxPooling2D((2, 2)))
    +model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    +model.add(layers.MaxPooling2D((2, 2)))
    +model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    +
    +# Let's display the architecture of our model so far.
    +
    +model.summary()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +

    You can see that the output of every Conv2D and MaxPooling2D layer is a 3D tensor of shape (height, width, channels). The width and height dimensions tend to shrink as you go deeper in the network. The number of output channels for each Conv2D layer is controlled by the first argument (e.g., 32 or 64). Typically, as the width and height shrink, you can afford (computationally) to add more output channels in each Conv2D layer.

    + +









    +

    Add Dense layers on top

    + +

    To complete our model, you will feed the last output tensor from the +convolutional base (of shape (4, 4, 64)) into one or more Dense layers +to perform classification. Dense layers take vectors as input (which +are 1D), while the current output is a 3D tensor. First, you will +flatten (or unroll) the 3D output to 1D, then add one or more Dense +layers on top. CIFAR has 10 output classes, so you use a final Dense +layer with 10 outputs and a softmax activation. +

    + + + +
    +
    +
    +
    +
    +
    model.add(layers.Flatten())
    +model.add(layers.Dense(64, activation='relu'))
    +model.add(layers.Dense(10))
    +Here's the complete architecture of our model.
    +
    +model.summary()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +

    As you can see, our (4, 4, 64) outputs were flattened into vectors of shape (1024) before going through two Dense layers.

    + +









    +

    Compile and train the model

    + + + +
    +
    +
    +
    +
    +
    model.compile(optimizer='adam',
    +              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    +              metrics=['accuracy'])
    +
    +history = model.fit(train_images, train_labels, epochs=10, 
    +                    validation_data=(test_images, test_labels))
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Finally, evaluate the model

    + + + +
    +
    +
    +
    +
    +
    plt.plot(history.history['accuracy'], label='accuracy')
    +plt.plot(history.history['val_accuracy'], label = 'val_accuracy')
    +plt.xlabel('Epoch')
    +plt.ylabel('Accuracy')
    +plt.ylim([0.5, 1])
    +plt.legend(loc='lower right')
    +
    +test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)
    +
    +print(test_acc)
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +









    Building our own CNN code

    @@ -1046,6 +1866,8 @@ the codebase and fit a CNN that yields a 99% accuracy on the 28x28 MNIST dataset within reasonable time.

    +The codes here were developed by Eric Reber and Gregor Kajda during spring 2023. +

    The CNN is compatible with all schedulers, cost functions and activation functions discussed in constructing our neural network codes. @@ -1536,28 +2358,28 @@ $$ (f \ast g)(t):=\int_{-\infty}^{\infty} f(\tau) g(t-\tau) d \tau. $$ -

    Here, f and g are the two functions on which we want to perform an +

    Here, \( f \) and \( g \) are the two functions on which we want to perform an operation. The outcome of the convolution operation is represented by -\( (f \ast g) \), and it is derived by sliding the function g over f and +\( (f \ast g) \), and it is derived by sliding the function \( g \) over \( f \) and computing the integral of their product at each position. If both functions are continuous, convolution takes the form shown -above. However, if we discretize both f and g, the convolution -operation will take the form of a sum between the elements of f and g: +above. However, if we discretize both \( f \) and \( g \), the convolution +operation will take the form of a sum between the elements of \( f \) and \( g \):

    $$ -(f \ast g)[n]=\sum_{m=0}^{n-1} f[m] g[n-m]. +(f \ast g)[n]=\sum_{m=0}^{n-1} f(m) g(n-m). $$

    The key idea we utilize to extract the information contained in an -image is to slide an \( m \times n \) matrix g over an \( m \times n \) -matrix f. In our case, f represents the image, while g +image is to slide an \( m \times n \) matrix \( g \) over an \( m \times n \) +matrix \( f \). In our case, \( f \) represents the image, while \( g \) represents the kernel, oftentimes called a filter. However, since our convolution will be a two-dimensional variant, we need to extend our mathematical formula with an additional summation:

    $$ -(f \ast g)[i, j]\sum_{m=0}^{M-1}\sum_{n=0}^{N-1} f[m,n] g[i-m, j-n]. +(f \ast g)(i, j)\sum_{m=0}^{M-1}\sum_{n=0}^{N-1} f(m,n) g(i-m, j-n). $$

    It is imperative to note that the size of the kernel g is @@ -1569,7 +2391,7 @@ appropriate kernels for specific tasks.

    To give you an example of how 2D convolution works in practice, -suppose we have an image f of dimension \( 6 \times 6 \) +suppose we have an image \( f \) of dimension \( 6 \times 6 \)

    $$ @@ -1583,7 +2405,7 @@ f = \begin{bmatrix} \end{bmatrix} $$ -

    and a \( 3 \times 3 \) kernel g called a low-pass filter. Note that the +

    and a \( 3 \times 3 \) kernel \( g \) called a low-pass filter. Note that the kernel is usually rotated by 180 degrees during convolution, however this has no effect on this kernel.

    @@ -1598,9 +2420,9 @@ g = \frac{1}{9} $$

    In order to filter the image, we have to extract a \( 3 \times 3 \) -element from the upper left corner of f, and perform element-wise +element from the upper left corner of \( f \), and perform element-wise multiplication of the extracted image pixels with the elements of the -kernel g: +kernel \( g \):

    $$ @@ -1621,21 +2443,21 @@ $$ \frac{9}{9} & \frac{5}{9} & \frac{9}{9} \\ \frac{1}{9} & \frac{5}{9} & \frac{9}{9} \\ \end {bmatrix} -= \textbf{A} += \boldsymbol{A} $$ -

    Then, following the multiplication, we summarize all the elements of the resulting matrix A:

    +

    Then, following the multiplication, we summarize all the elements of the resulting matrix \( \boldsymbol{A} \):

    $$ -(f \ast g)[0, 0]= \sum_{i=0}^{2} \sum_{j=0}^{2} a_{i,j} = 5 +(f \ast g)(0, 0)= \sum_{i=0}^{2} \sum_{j=0}^{2} a_{i,j} = 5, $$ -

    Which corresponds to the first element of the filtered image \( (f \ast g) \).

    +

    which corresponds to the first element of the filtered image \( (f \ast g) \).

    -

    Here we use a stride of 1, a parameter denoted s which describes how -many indexes we move the kernel g to the right before repeating the +

    Here we use a stride of \( S=1 \), a parameter denoted \( S \) which describes how +many indexes we move the kernel \( g \) to the right before repeating the calculations above for the next \( 3 \times 3 \) element of the image -f. It is usually presumed that *s*=1, however, larger values for s +\( f \). It is usually presumed that \( S=1 \), however, larger values for \( S \) can be used to reduce the dimentionality of the filtered image such that the convolution operation is more computationally efficient. In the context of a convolutional neural network, this will become very @@ -1654,18 +2476,23 @@ $$ \end{bmatrix} $$ -

    The result is markedly smaller in shape than the original image. This occurs when using convolution without first padding the image with additional columns and rows, allowing us to keep the original image shape after sliding the kernel over the image. -How many rows and columns we wish to pad the image with depends strictly on the shape of the kernel, as we wish to pad the image with r additional rows and c additional columns. +

    The result is markedly smaller in shape than the original image. This +occurs when using convolution without first padding the image with +additional columns and rows, allowing us to keep the original image +shape after sliding the kernel over the image. How many rows and +columns we wish to pad the image with depends strictly on the shape of +the kernel, as we wish to pad the image with \( r \) additional rows and +\( c \) additional columns.

    $$ -r =\lfloor \frac{kernel\ height}{2} \rfloor \cdot 2 \\ -c =\lfloor \frac{kernel\ width}{2} \rfloor \cdot 2 +r =\lfloor \frac{\mathrm{kernel height}}{2} \rfloor \cdot 2 \\ +c =\lfloor \frac{\mathrm{kernel width}}{2} \rfloor \cdot 2 $$ -

    Note the notation \( \lfloor \frac{kernel width}{2} \rfloor \) means that +

    Note the notation \( \lfloor \frac{\mathrm{kernel width}}{2} \rfloor \) means that we floor the result of the division, meaning we round down to a whole -number in case \( \frac{kernel width}{2} \) results in a floating point +number in case \( \frac{\mathrm{kernel width}}{2} \) results in a floating point number.

    @@ -1691,11 +2518,13 @@ $$ 0 & 8 & 1 & 6 & 4 & 2 & 2 & 0 \\ 0 & 1 & 0 & 5 & 7 & 8 & 2 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ -\] -\end{bmatrix} +\end{bmatrix}. $$ -

    Below we have provided code that demonstrates padding and convolution. As you will see when we run the code, the size of the image will remain unchanged when using padding. 

    +

    Below we have provided code that demonstrates padding and +convolution. As you will see when we run the code, the size of the +image will remain unchanged when using padding.~ +

    @@ -1790,9 +2619,10 @@ $$ -

    Fun fact: When filtering images, you will see that convolution involves rotating the kernel by 180 degrees. -However, this is not the case when applying convolution in a CNN, where the same operation not rotated by 180 degrees is called -cross-correlation. +

    Fun fact: When filtering images, you will see that convolution +involves rotating the kernel by 180 degrees. However, this is not the +case when applying convolution in a CNN, where the same operation that is not +rotated by 180 degrees is called cross-correlation, which is normally implemented in most libraries.

    @@ -1852,7 +2682,7 @@ feed forward neural network.
    -
    # Now an example using a real image and first a gaussian low-pass filter and then a sobel filter
    +  
    # Now an example using a real image and first a gaussian low-pass filter and then a Sobel filter
     import numpy as np
     import imageio.v3 as imageio
     import matplotlib.pyplot as plt
    @@ -3879,7 +4709,7 @@ cnn.add_OutputLayer(10, softmax)
     
  • The output layer, with softmax as its activation function and 10 nodes. We use 10 nodes because we will be using a dataset with 10 classes.
  • Now, before we can train the model, we need to load in our data. We -will use the MNIST dataset and use 10000 \( 28 \times 28 images \). +will use the MNIST dataset and use 10000 \( 28 \times 28 \) images.

    @@ -4293,363 +5123,6 @@ to remain cognizant of this fact when utilizing FFT as the primary optimization technique.

    -









    -

    Building convolutional neural networks in Tensorflow and Keras

    - -

    As discussed above, CNNs are neural networks built from the assumption that the inputs -to the network are 2D images. This is important because the number of features or pixels in images -grows very fast with the image size, and an enormous number of weights and biases are needed in order to build an accurate network. -

    - -

    As before, we still have our input, a hidden layer and an output. What's novel about convolutional networks -are the convolutional and pooling layers stacked in pairs between the input and the hidden layer. -In addition, the data is no longer represented as a 2D feature matrix, instead each input is a number of 2D -matrices, typically 1 for each color dimension (Red, Green, Blue). -

    - -









    -

    Setting it up

    - -

    It means that to represent the entire -dataset of images, we require a 4D matrix or tensor. This tensor has the dimensions: -

    -$$ -(n_{inputs},\, n_{pixels, width},\, n_{pixels, height},\, depth) . -$$ - - -









    -

    The MNIST dataset again

    - -

    The MNIST dataset consists of grayscale images with a pixel size of -\( 28\times 28 \), meaning we require \( 28 \times 28 = 724 \) weights to each -neuron in the first hidden layer. -

    - -

    If we were to analyze images of size \( 128\times 128 \) we would require -\( 128 \times 128 = 16384 \) weights to each neuron. Even worse if we were -dealing with color images, as most images are, we have an image matrix -of size \( 128\times 128 \) for each color dimension (Red, Green, Blue), -meaning 3 times the number of weights \( = 49152 \) are required for every -single neuron in the first hidden layer. -

    - - -









    -

    Strong correlations

    - -

    Images typically have strong local correlations, meaning that a small -part of the image varies little from its neighboring regions. If for -example we have an image of a blue car, we can roughly assume that a -small blue part of the image is surrounded by other blue regions. -

    - -

    Therefore, instead of connecting every single pixel to a neuron in the -first hidden layer, as we have previously done with deep neural -networks, we can instead connect each neuron to a small part of the -image (in all 3 RGB depth dimensions). The size of each small area is -fixed, and known as a receptive. -

    - - - -

    Layers of a CNN

    - -

    The layers of a convolutional neural network arrange neurons in 3D: width, height and depth. -The input image is typically a square matrix of depth 3. -

    - -

    A convolution is performed on the image which outputs -a 3D volume of neurons. The weights to the input are arranged in a number of 2D matrices, known as filters. -

    - -

    Each filter slides along the input image, taking the dot product -between each small part of the image and the filter, in all depth -dimensions. This is then passed through a non-linear function, -typically the Rectified Linear (ReLu) function, which serves as the -activation of the neurons in the first convolutional layer. This is -further passed through a pooling layer, which reduces the size of the -convolutional layer, e.g. by taking the maximum or average across some -small regions, and this serves as input to the next convolutional -layer. -

    - -









    -

    Systematic reduction

    - -

    By systematically reducing the size of the input volume, through -convolution and pooling, the network should create representations of -small parts of the input, and then from them assemble representations -of larger areas. The final pooling layer is flattened to serve as -input to a hidden layer, such that each neuron in the final pooling -layer is connected to every single neuron in the hidden layer. This -then serves as input to the output layer, e.g. a softmax output for -classification. -

    - - -









    -

    Prerequisites: Collect and pre-process data

    - - -
    -
    -
    -
    -
    -
    # import necessary packages
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from sklearn import datasets
    -
    -
    -# ensure the same random numbers appear every time
    -np.random.seed(0)
    -
    -# display images in notebook
    -%matplotlib inline
    -plt.rcParams['figure.figsize'] = (12,12)
    -
    -
    -# download MNIST dataset
    -digits = datasets.load_digits()
    -
    -# define inputs and labels
    -inputs = digits.images
    -labels = digits.target
    -
    -# RGB images have a depth of 3
    -# our images are grayscale so they should have a depth of 1
    -inputs = inputs[:,:,:,np.newaxis]
    -
    -print("inputs = (n_inputs, pixel_width, pixel_height, depth) = " + str(inputs.shape))
    -print("labels = (n_inputs) = " + str(labels.shape))
    -
    -
    -# choose some random images to display
    -n_inputs = len(inputs)
    -indices = np.arange(n_inputs)
    -random_indices = np.random.choice(indices, size=5)
    -
    -for i, image in enumerate(digits.images[random_indices]):
    -    plt.subplot(1, 5, i+1)
    -    plt.axis('off')
    -    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    -    plt.title("Label: %d" % digits.target[random_indices[i]])
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -









    -

    Importing Keras and Tensorflow

    - - -
    -
    -
    -
    -
    -
    from tensorflow.keras import datasets, layers, models
    -from tensorflow.keras.layers import Input
    -from tensorflow.keras.models import Sequential      #This allows appending layers to existing models
    -from tensorflow.keras.layers import Dense           #This allows defining the characteristics of a particular layer
    -from tensorflow.keras import optimizers             #This allows using whichever optimiser we want (sgd,adam,RMSprop)
    -from tensorflow.keras import regularizers           #This allows using whichever regularizer we want (l1,l2,l1_l2)
    -from tensorflow.keras.utils import to_categorical   #This allows using categorical cross entropy as the cost function
    -#from tensorflow.keras import Conv2D
    -#from tensorflow.keras import MaxPooling2D
    -#from tensorflow.keras import Flatten
    -
    -from sklearn.model_selection import train_test_split
    -
    -# representation of labels
    -labels = to_categorical(labels)
    -
    -# split into train and test data
    -# one-liner from scikit-learn library
    -train_size = 0.8
    -test_size = 1 - train_size
    -X_train, X_test, Y_train, Y_test = train_test_split(inputs, labels, train_size=train_size,
    -                                                    test_size=test_size)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - -

    Running with Keras

    - - - -
    -
    -
    -
    -
    -
    def create_convolutional_neural_network_keras(input_shape, receptive_field,
    -                                              n_filters, n_neurons_connected, n_categories,
    -                                              eta, lmbd):
    -    model = Sequential()
    -    model.add(layers.Conv2D(n_filters, (receptive_field, receptive_field), input_shape=input_shape, padding='same',
    -              activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    -    model.add(layers.MaxPooling2D(pool_size=(2, 2)))
    -    model.add(layers.Flatten())
    -    model.add(layers.Dense(n_neurons_connected, activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    -    model.add(layers.Dense(n_categories, activation='softmax', kernel_regularizer=regularizers.l2(lmbd)))
    -    
    -    sgd = optimizers.SGD(learning_rate=eta)
    -    model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
    -    
    -    return model
    -
    -epochs = 100
    -batch_size = 100
    -input_shape = X_train.shape[1:4]
    -receptive_field = 3
    -n_filters = 10
    -n_neurons_connected = 50
    -n_categories = 10
    -
    -eta_vals = np.logspace(-5, 1, 7)
    -lmbd_vals = np.logspace(-5, 1, 7)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -









    -

    Final part

    - - - -
    -
    -
    -
    -
    -
    CNN_keras = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)
    -        
    -for i, eta in enumerate(eta_vals):
    -    for j, lmbd in enumerate(lmbd_vals):
    -        CNN = create_convolutional_neural_network_keras(input_shape, receptive_field,
    -                                              n_filters, n_neurons_connected, n_categories,
    -                                              eta, lmbd)
    -        CNN.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=0)
    -        scores = CNN.evaluate(X_test, Y_test)
    -        
    -        CNN_keras[i][j] = CNN
    -        
    -        print("Learning rate = ", eta)
    -        print("Lambda = ", lmbd)
    -        print("Test accuracy: %.3f" % scores[1])
    -        print()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -









    -

    Final visualization

    - - - -
    -
    -
    -
    -
    -
    # visual representation of grid search
    -# uses seaborn heatmap, could probably do this in matplotlib
    -import seaborn as sns
    -
    -sns.set()
    -
    -train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    -test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    -
    -for i in range(len(eta_vals)):
    -    for j in range(len(lmbd_vals)):
    -        CNN = CNN_keras[i][j]
    -
    -        train_accuracy[i][j] = CNN.evaluate(X_train, Y_train)[1]
    -        test_accuracy[i][j] = CNN.evaluate(X_test, Y_test)[1]
    -
    -        
    -fig, ax = plt.subplots(figsize = (10, 10))
    -sns.heatmap(train_accuracy, annot=True, ax=ax, cmap="viridis")
    -ax.set_title("Training Accuracy")
    -ax.set_ylabel("$\eta$")
    -ax.set_xlabel("$\lambda$")
    -plt.show()
    -
    -fig, ax = plt.subplots(figsize = (10, 10))
    -sns.heatmap(test_accuracy, annot=True, ax=ax, cmap="viridis")
    -ax.set_title("Test Accuracy")
    -ax.set_ylabel("$\eta$")
    -ax.set_xlabel("$\lambda$")
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    © 1999-2024, Morten Hjorth-Jensen. Released under CC Attribution-NonCommercial 4.0 license diff --git a/doc/pub/week44/html/week44.html b/doc/pub/week44/html/week44.html index 47f4477f0..088207579 100644 --- a/doc/pub/week44/html/week44.html +++ b/doc/pub/week44/html/week44.html @@ -179,6 +179,12 @@ div.toc p,a { None, 'a-deep-cnn-model-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Key Idea', 2, None, 'key-idea'), + ('How to do image compression before the era of deep learning', + 2, + None, + 'how-to-do-image-compression-before-the-era-of-deep-learning'), + ('The SVD example', 2, None, 'the-svd-example'), + ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Mathematics of CNNs', 2, None, 'mathematics-of-cnns'), ('Convolution Examples: Polynomial multiplication', 2, @@ -193,6 +199,8 @@ div.toc p,a { 2, None, 'a-more-efficient-way-of-coding-the-above-convolution'), + ('Commutative process', 2, None, 'commutative-process'), + ('Toeplitz matrices', 2, None, 'toeplitz-matrices'), ('Fourier series and Toeplitz matrices', 2, None, @@ -201,36 +209,75 @@ div.toc p,a { 2, None, 'generalizing-the-above-one-dimensional-case'), - ('Two-dimensional Objects', 2, None, 'two-dimensional-objects'), - ('CNNs in more detail', 2, None, 'cnns-in-more-detail'), - ('Performing a discrete convolution with padding ("From Raschka ' - 'et al":"https://github.com/rasbt/machine-learning-book")', + ('Memory considerations', 2, None, 'memory-considerations'), + ('Padding', 2, None, 'padding'), + ('New vector', 2, None, 'new-vector'), + ('Rewriting as dot products', 2, None, - 'performing-a-discrete-convolution-with-padding-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Performing a general discrete convolution ("From Raschka et ' + 'rewriting-as-dot-products'), + ('Cross correlation', 2, None, 'cross-correlation'), + ('Two-dimensional objects', 2, None, 'two-dimensional-objects'), + ('CNNs in more detail, simple example', + 2, + None, + 'cnns-in-more-detail-simple-example'), + ('The convolution stage', 2, None, 'the-convolution-stage'), + ('Finding the number of parameters', + 2, + None, + 'finding-the-number-of-parameters'), + ('New image (or volume)', 2, None, 'new-image-or-volume'), + ('Parameters to train, common settings', + 2, + None, + 'parameters-to-train-common-settings'), + ('Examples of CNN setups', 2, None, 'examples-of-cnn-setups'), + ('Summarizing: Performing a general discrete convolution ("From ' + 'Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, - 'performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), - ('Set of filters', 2, None, 'set-of-filters'), + 'summarizing-performing-a-general-discrete-convolution-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), ('Pooling', 2, None, 'pooling'), - ('No zero padding, unit strides', - 2, - None, - 'no-zero-padding-unit-strides'), - ('Zero padding, unit strides', - 2, - None, - 'zero-padding-unit-strides'), - ('Half (same) padding', 2, None, 'half-same-padding'), - ('Full padding', 2, None, 'full-padding'), ('Pooling arithmetic', 2, None, 'pooling-arithmetic'), ('Pooling types ("From Raschka et ' 'al":"https://github.com/rasbt/machine-learning-book")', 2, None, 'pooling-types-from-raschka-et-al-https-github-com-rasbt-machine-learning-book'), + ('Building convolutional neural networks in Tensorflow and Keras', + 2, + None, + 'building-convolutional-neural-networks-in-tensorflow-and-keras'), + ('Setting it up', 2, None, 'setting-it-up'), + ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), + ('Strong correlations', 2, None, 'strong-correlations'), + ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), + ('Systematic reduction', 2, None, 'systematic-reduction'), + ('Prerequisites: Collect and pre-process data', + 2, + None, + 'prerequisites-collect-and-pre-process-data'), + ('Importing Keras and Tensorflow', + 2, + None, + 'importing-keras-and-tensorflow'), + ('Running with Keras', 2, None, 'running-with-keras'), + ('Final part', 2, None, 'final-part'), + ('Final visualization', 2, None, 'final-visualization'), + ('The CIFAR01 data set', 2, None, 'the-cifar01-data-set'), + ('Verifying the data set', 2, None, 'verifying-the-data-set'), + ('Set up the model', 2, None, 'set-up-the-model'), + ('Add Dense layers on top', 2, None, 'add-dense-layers-on-top'), + ('Compile and train the model', + 2, + None, + 'compile-and-train-the-model'), + ('Finally, evaluate the model', + 2, + None, + 'finally-evaluate-the-model'), ('Building our own CNN code', 2, None, @@ -277,27 +324,7 @@ div.toc p,a { ('Convolution in the Fourier domain', 3, None, - 'convolution-in-the-fourier-domain'), - ('Building convolutional neural networks in Tensorflow and Keras', - 2, - None, - 'building-convolutional-neural-networks-in-tensorflow-and-keras'), - ('Setting it up', 2, None, 'setting-it-up'), - ('The MNIST dataset again', 2, None, 'the-mnist-dataset-again'), - ('Strong correlations', 2, None, 'strong-correlations'), - ('Layers of a CNN', 2, None, 'layers-of-a-cnn'), - ('Systematic reduction', 2, None, 'systematic-reduction'), - ('Prerequisites: Collect and pre-process data', - 2, - None, - 'prerequisites-collect-and-pre-process-data'), - ('Importing Keras and Tensorflow', - 2, - None, - 'importing-keras-and-tensorflow'), - ('Running with Keras', 2, None, 'running-with-keras'), - ('Final part', 2, None, 'final-part'), - ('Final visualization', 2, None, 'final-visualization')]} + 'convolution-in-the-fourier-domain')]} end of tocinfo --> @@ -625,6 +652,147 @@ only neighboring neurons in the input instead of connecting all with the first h

    We say we perform a filtering (convolution is the mathematical operation).

    +









    +

    How to do image compression before the era of deep learning

    + +

    The singular-value decomposition (SVD) algorithm has been for decades one of the standard ways of compressing images. +The lectures on the SVD give many of the essential details concerning the SVD. +

    + +

    The orthogonal vectors which are obtained from the SVD, can be used to +project down the dimensionality of a given image. In the example here +we gray-scale an image and downsize it. +

    + +

    This recipe relies on us being able to actually perform the SVD. For +large images, and in particular with many images to reconstruct, using the SVD +may quickly become an overwhelming task. With the advent of efficient deep +learning methods like CNNs and later generative methods, these methods +have become in the last years the premier way of performing image +analysis. In particular for classification problems with labelled images. +

    + +









    +

    The SVD example

    + + + +
    +
    +
    +
    +
    +
    from matplotlib.image import imread
    +import matplotlib.pyplot as plt
    +import scipy.linalg as ln
    +import numpy as np
    +import os
    +from PIL import Image
    +from math import log10, sqrt 
    +plt.rcParams['figure.figsize'] = [16, 8]
    +# Import image
    +A = imread(os.path.join("figslides/photo1.jpg"))
    +X = A.dot([0.299, 0.5870, 0.114]) # Convert RGB to grayscale
    +img = plt.imshow(X)
    +# convert to gray
    +img.set_cmap('gray')
    +plt.axis('off')
    +plt.show()
    +# Call image size
    +print(': %s'%str(X.shape))
    +
    +
    +# split the matrix into U, S, VT
    +U, S, VT = np.linalg.svd(X,full_matrices=False)
    +S = np.diag(S)
    +m = 800 # Image's width
    +n = 1200 # Image's height
    +j = 0
    +# Try compression with different k vectors (these represent projections):
    +for k in (5,10, 20, 100,200,400,500):
    +    # Original size of the image
    +    originalSize = m * n 
    +    # Size after compressed
    +    compressedSize = k * (1 + m + n) 
    +    # The projection of the original image
    +    Xapprox = U[:,:k] @ S[0:k,:k] @ VT[:k,:]
    +    plt.figure(j+1)
    +    j += 1
    +    img = plt.imshow(Xapprox)
    +    img.set_cmap('gray')
    +    
    +    plt.axis('off')
    +    plt.title('k = ' + str(k))
    +    plt.show() 
    +    print('Original size of image:')
    +    print(originalSize)
    +    print('Compression rate as Compressed image / Original size:')
    +    ratio = compressedSize * 1.0 / originalSize
    +    print(ratio)
    +    print('Compression rate is ' + str( round(ratio * 100 ,2)) + '%' )  
    +    # Estimate MQA
    +    x= X.astype("float")
    +    y=Xapprox.astype("float")
    +    err = np.sum((x - y) ** 2)
    +    err /= float(X.shape[0] * Xapprox.shape[1])
    +    print('The mean-square deviation '+ str(round( err)))
    +    max_pixel = 255.0
    +    # Estimate Signal Noise Ratio
    +    srv = 20 * (log10(max_pixel / sqrt(err)))
    +    print('Signa to noise ratio '+ str(round(srv)) +'dB')
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Mathematics of CNNs

    + +

    The mathematics of CNNs is based on the mathematical operation of +convolution. In mathematics (in particular in functional analysis), +convolution is represented by mathematical operations (integration, +summation etc) on two functions in order to produce a third function +that expresses how the shape of one gets modified by the other. +Convolution has a plethora of applications in a variety of +disciplines, spanning from statistics to signal processing, computer +vision, solutions of differential equations,linear algebra, +engineering, and yes, machine learning. +

    + +

    Mathematically, convolution is defined as follows (one-dimensional example): +Let us define a continuous function \( y(t) \) given by +

    +$$ +y(t) = \int x(a) w(t-a) da, +$$ + +

    where \( x(a) \) represents a so-called input and \( w(t-a) \) is normally called the weight function or kernel.

    + +

    The above integral is written in a more compact form as

    +$$ +y(t) = \left(x * w\right)(t). +$$ + +

    The discretized version reads

    +$$ +y(t) = \sum_{a=-\infty}^{a=\infty}x(a)w(t-a). +$$ + +

    Computing the inverse of the above convolution operations is known as deconvolution and the process is commutative.

    + +

    How can we use this? And what does it mean? Let us study some familiar examples first.

    +









    Mathematics of CNNs

    @@ -728,7 +896,9 @@ $$ \delta_i = \sum_{k=0}^{k=m-1}\alpha_k\beta_{i-k}, $$ -

    where \( m=3 \) in our case, the maximum length of the vector \( \alpha \). Note that the vector \( \boldsymbol{\beta} \) has length \( n=4 \).

    +

    where \( m=3 \) in our case, the maximum length of +the vector \( \alpha \). Note that the vector \( \boldsymbol{\beta} \) has length \( n=4 \). Below we will find an even more efficient representation. +











    A more efficient way of coding the above Convolution

    @@ -748,6 +918,10 @@ $$ \end{bmatrix}\begin{bmatrix} \beta_0 \\ \beta_1 \\ \beta_2 \\ \beta_3\end{bmatrix}. $$ + +









    +

    Commutative process

    +

    The process is commutative and we can easily see that we can rewrite the multiplication in terms of a matrix holding \( \beta \) and a vector holding \( \alpha \). In this case we have

    @@ -768,6 +942,9 @@ rather code the convolutions in the minimal memory footprint that they require.

    +









    +

    Toeplitz matrices

    +

    The above matrices are examples of so-called Toeplitz matrices. A Toeplitz matrix is a matrix in which each descending diagonal from @@ -804,18 +981,125 @@ a vector.









    Generalizing the above one-dimensional case

    -

    In order to align the above simple case with the more general convolution cases, we rename \( \boldsymbol{\alpha} \), whose length is \( m=3 \), with \( \boldsymbol{w} \). -We will interpret \( \boldsymbol{w} \) as a weight/filter function with which we want to perform the convolution with an input varibale \( \boldsymbol{x} \). -We replace thus \( \boldsymbol{\beta} \) with \( \boldsymbol{x} \) and \( \boldsymbol{\delta} \) with \( \boldsymbol{s} \) and have +

    In order to align the above simple case with the more general +convolution cases, we rename \( \boldsymbol{\alpha} \), whose length is \( m=3 \), +with \( \boldsymbol{w} \). We will interpret \( \boldsymbol{w} \) as a weight/filter function +with which we want to perform the convolution with an input variable +\( \boldsymbol{x} \) of length \( n \). We will assume always that the filter +\( \boldsymbol{w} \) has dimensionality \( m \le n \).

    + +

    We replace thus \( \boldsymbol{\beta} \) with \( \boldsymbol{x} \) and \( \boldsymbol{\delta} \) with \( \boldsymbol{y} \) and have

    $$ -s(i) \left(x*w\right)(i)= \sum_{k=0}^{k=m-1}w(k)x(i-k), +y(i)= \left(x*w\right)(i)= \sum_{k=0}^{k=m-1}w(k)x(i-k), $$

    where \( m=3 \) in our case, the maximum length of the vector \( \boldsymbol{w} \). Here the symbol \( * \) represents the mathematical operation of convolution.

    -

    Two-dimensional Objects

    + +









    +

    Memory considerations

    + +

    This expression leaves us however with some terms with negative +indices, for example \( x(-1) \) and \( x(-2) \) which may not be defined. Our +vector \( \boldsymbol{x} \) has components \( x(0) \), \( x(1) \), \( x(2) \) and \( x(3) \). +

    + +

    The index \( j \) for \( \boldsymbol{x} \) runs from \( j=0 \) to \( j=3 \) since \( \boldsymbol{x} \) is meant to +represent a third-order polynomial. +

    + +

    Furthermore, the index \( i \) runs from \( i=0 \) to \( i=5 \) since \( \boldsymbol{y} \) +contains the coefficients of a fifth-order polynomial. When \( i=5 \) we +may also have values of \( x(4) \) and \( x(5) \) which are not defined. +

    + +









    +

    Padding

    + +

    The solution to this is what is called padding! We simply define a +new vector \( x \) with two added elements set to zero before \( x(0) \) and +two new elements after \( x(3) \) set to zero. That is, we augment the +length of \( \boldsymbol{x} \) from \( n=4 \) to \( n+2P=8 \), where \( P=2 \) is the padding +constant (a new hyperparameter), see discussions below as well. +

    + +









    +

    New vector

    + +

    We have a new vector defined as \( x(0)=0 \), \( x(1)=0 \), +\( x(2)=\beta_0 \), \( x(3)=\beta_1 \), \( x(4)=\beta_2 \), \( x(5)=\beta_3 \), +\( x(6)=0 \), and \( x(7)=0 \). +

    + +

    We have added four new elements, which +are all zero. The benefit is that we can rewrite the equation for +\( \boldsymbol{y} \), with \( i=0,1,\dots,5 \), +

    +$$ +y(i) = \sum_{k=0}^{k=m-1}w(k)x(i+(m-1)-k). +$$ + +

    As an example, we have

    +$$ +y(4)=x(6)w(0)+x(5)w(1)+x(4)w(2)=0\times \alpha_0+\beta_3\alpha_1+\beta_2\alpha_2, +$$ + +

    as before except that we have an additional term \( x(6)w(0) \), which is zero.

    + +

    Similarly, for the fifth-order term we have

    +$$ +y(5)=x(7)w(0)+x(6)w(1)+x(5)w(2)=0\times \alpha_0+0\times\alpha_1+\beta_3\alpha_2. +$$ + +

    The zeroth-order term is

    +$$ +y(0)=x(2)w(0)+x(1)w(1)+x(0)w(2)=\beta_0 \alpha_0+0\times\alpha_1+0\times\alpha_2=\alpha_0\beta_0. +$$ + + +









    +

    Rewriting as dot products

    + +

    If we now flip the filter/weight vector, with the following term as a typical example

    +$$ +y(0)=x(2)w(0)+x(1)w(1)+x(0)w(2)=x(2)\tilde{w}(2)+x(1)\tilde{w}(1)+x(0)\tilde{w}(0), +$$ + +

    with \( \tilde{w}(0)=w(2) \), \( \tilde{w}(1)=w(1) \), and \( \tilde{w}(2)=w(0) \), we can then rewrite the above sum as a dot product of +\( x(i:i+(m-1))\tilde{w} \) for element \( y(i) \), where \( x(i:i+(m-1)) \) is simply a patch of \( \boldsymbol{x} \) of size \( m-1 \). +

    + +

    The padding \( P \) we have introduced for the convolution stage is just +another hyperparameter which is introduced as part of the +architecture. Similarly, below we will also introduce another +hyperparameter called Stride \( S \). +

    + +









    +

    Cross correlation

    + +

    In essentially all applications one uses what is called cross correlation instead of the standard convolution described above. +This means that multiplication is performed in the same direction and instead of the general expression we have discussed above (with infinite sums) +

    +$$ +y(i) = \sum_{k=-\infty}^{k=\infty}w(k)x(i-k), +$$ + +

    we have now

    +$$ +y(i) = \sum_{k=-\infty}^{k=\infty}w(k)x(i+k). +$$ + +

    Both TensorFlow and PyTorch (as well as our own code example below), +implement the last equation, although it is normally referred to as +convolution. The same padding rules and stride rules discussed below +apply to this expression as well. +

    + +

    We leave it as an exercise for you to convince yourself that the example we have discussed till now, gives the same final expression using the last expression.

    +

    Two-dimensional objects

    We are now ready to start studying the discrete convolutions relevant for convolutional neural networks. We often use convolutions over more than one dimension at a time. If @@ -832,16 +1116,22 @@ $$ Y(i,j)=(X * W)(i,j) = \sum_m\sum_n X(i-m,j-n)W(m,n). $$ -

    Normally the latter is more straightforward to implement in a machine larning library since there is less variation in the range of values of \( m \) and \( n \).

    +

    Normally the latter is more straightforward to implement in a machine +larning library since there is less variation in the range of values +of \( m \) and \( n \). +

    -

    Many deep learning libraries implement cross-correlation instead of convolution (although it is referred to s convolution)

    +

    As mentioned above, most deep learning libraries implement +cross-correlation instead of convolution (although it is referred to as +convolution) +

    $$ Y(i,j)=(X * W)(i,j) = \sum_m\sum_n X(i+m,j+n)W(m,n). $$









    -

    CNNs in more detail

    +

    CNNs in more detail, simple example

    Let assume we have an input matrix \( X \) of dimensionality \( 3\times 3 \) and a \( 2\times 2 \) filter \( W \) given by the following matrices @@ -901,56 +1191,101 @@ $$

    is now given by \( \boldsymbol{W}'\boldsymbol{X}' \) which is a vector of length \( 4 \) instead of the originally resulting \( 2\times 2 \) output matrix.











    -

    Performing a discrete convolution with padding (From Raschka et al)

    +

    The convolution stage

    -
    -
    -
    -

    Figure 4: A deep CNN

    -
    -

    -
    - -









    -

    Performing a general discrete convolution (From Raschka et al)

    - -
    -
    -
    -

    Figure 5: A deep CNN

    -
    -

    -
    - -









    -

    Set of filters

    - -

    The collection of kernels/filters \( W \) defining a discrete convolution has a shape -corresponding to some permutation of \( (n, m, k_1, \ldots, k_N) \), where +

    The convolution stage, where we apply different filters \( \boldsymbol{W} \) in +order to reduce the dimensionality of an image, adds, in addition to +the weights and biases (to be trained by the back propagation +algorithm) that define the filters, two new hyperparameters, the so-called +padding \( P \) and the stride \( S \).

    -$$ -\begin{equation*} -\begin{split} - n &\equiv \text{number of output feature maps},\\ - m &\equiv \text{number of input feature maps},\\ - k_j &\equiv \text{kernel size along axis $j$}. -\end{split} -\end{equation*} -$$ +









    +

    Finding the number of parameters

    -

    The following properties affect the output size \( o_j \) of a convolutional layer -along axis \( j \): +

    In the above example we have an input matrix of dimension \( 3\times +3 \). In general we call the input for an input volume and it is defined +by its width \( H_1 \), height \( H_1 \) and depth \( D_1 \). If we have the +standard three color channels \( D_1=3 \).

    +

    The above example has \( W_1=H_1=3 \) and \( D_1=1 \).

    + +

    When we introduce the filter we have the following additional hyperparameters

      -
    1. \( i_j \): input size along axis \( j \),
    2. -
    3. \( k_j \): kernel/filter size along axis \( j \),
    4. -
    5. stride (distance between two consecutive positions of the kernel/filter) along axis \( j \),
    6. -
    7. zero padding (number of zeros concatenated at the beginning and at the end of an axis) along axis \( j \).
    8. +
    9. \( K \) the number of filters. It is common to perform the convolution of the input several times since by experience shrinking the input too fast does not work well
    10. +
    11. \( F \) as the filter's spatial extent
    12. +
    13. \( S \) as the stride parameter
    14. +
    15. \( P \) as the padding parameter
    -

    For instance, the above examples shows a \( 2\times 2 \) kernel/filter \( \boldsymbol{W} \) applied to a \( 3 \times 3 \) input padded with a \( 0 \times 0 \) -border of zeros using \( 1 \times 1 \) strides. +

    These parameters are defined by the architecture of the network and are not included in the training.

    + +









    +

    New image (or volume)

    + +

    Acting with the filter on the input volume produces an output volume +which is defined by its width \( W_2 \), its height \( H_2 \) and its depth +\( D_2 \). +

    + +

    These are defined by the following relations

    +$$ +W_2 = \frac{(W_1-F+2P)}{S}+1, +$$ + +$$ +H_2 = \frac{(H_1-F+2P)}{S}+1, +$$ + +

    and \( D_2=K \).

    + +









    +

    Parameters to train, common settings

    + +

    With parameter sharing, the convolution involves thus for each filter \( F\times F\times D_1 \) weights plus one bias parameter.

    + +

    In total we have

    +$$ +\left(F\times F\times D_1)\right) \times K+(K\mathrm{--biases}), +$$ + +

    parameters to train by back propagation.

    + +

    It is common to let \( K \) come in powers of \( 2 \), that is \( 32 \), \( 64 \), \( 128 \) etc.

    + +
    +Common settings +

    +

      +
    1. \( \begin{array}{c} F=3 & S=1 & P=1 \end{array} \)
    2. +
    3. \( \begin{array}{c} F=5 & S=1 & P=2 \end{array} \)
    4. +
    5. \( \begin{array}{c} F=5 & S=2 & P=\mathrm{open} \end{array} \)
    6. +
    7. \( \begin{array}{c} F=1 & S=1 & P=0 \end{array} \)
    8. +
    +
    + + +









    +

    Examples of CNN setups

    + +

    Let us assume we have an input volume \( V \) given by an image of dimensionality +\( 32\times 32 \times 3 \), that is three color channels and \( 32\times 32 \) pixels. +

    + +

    We apply a filter of dimension \( 5\times 5 \) ten times with stride \( S=1 \) and padding \( P=0 \).

    + +

    The output volume is given by \( (32-5)/1+1=28 \), resulting in ten images +of dimensionality \( 28\times 28\times 3 \). +

    + +

    The total number of parameters to train for each filter is then +\( 5\times 5\times 3+1 \), where the last parameter is the bias. This +gives us \( 76 \) parameters for each filter, leading to a total of \( 760 \) +parameters for the ten filters. +

    + +

    How many parameters will a filter of dimensionality \( 3\times 3 \) +(adding color channels) result in if we produce \( 32 \) new images? Use \( S=1 \) and \( P=0 \).

    Note that strides constitute a form of subsampling. As an alternative to @@ -960,124 +1295,30 @@ the kernel by hops of two is equivalent to moving the kernel by hops of one but retaining only odd output elements.

    +









    +

    Summarizing: Performing a general discrete convolution (From Raschka et al)

    + +
    +
    +
    +

    Figure 4: A deep CNN

    +
    +

    +
    +









    Pooling

    -

    In addition to discrete convolutions themselves, {\em pooling\/} operations +

    In addition to discrete convolutions themselves, pooling operations make up another important building block in CNNs. Pooling operations reduce the size of feature maps by using some function to summarize subregions, such as taking the average or the maximum value.

    Pooling works by sliding a window across the input and feeding the content of -the window to a {\em pooling function}. In some sense, pooling works very much +the window to a pooling function. In some sense, pooling works very much like a discrete convolution, but replaces the linear combination described by -the kernel with some other function. Poolin -provides an example for average pooling, and -does the same for max pooling. -

    - -

    The following properties affect the output size \( o_j \) of a pooling layer -along axis \( j \): -

    - -
      -
    1. \( i_j \): input size along axis \( j \),
    2. -
    3. \( k_j \): pooling window size along axis \( j \),
    4. -
    5. \( s_j \): stride (distance between two consecutive positions of the pooling window) along axis \( j \).
    6. -
    -

    The analysis of the relationship between convolutional layer properties is eased -by the fact that they don't interact across axes, i.e., the choice of kernel -size, stride and zero padding along axis \( j \) only affects the output size of -axis \( j \). Because of that, we will focus on the following simplified -setting: -

    - -
      -
    1. 2-D discrete convolutions (\( N = 2 \)),
    2. -
    3. square inputs (\( i_1 = i_2 = i \)),
    4. -
    5. square kernel size (\( k_1 = k_2 = k \)),
    6. -
    7. same strides along both axes (\( s_1 = s_2 = s \)),
    8. -
    9. same zero padding along both axes (\( p_1 = p_2 = p \)).
    10. -
    -

    This facilitates the analysis and the visualization, but keep in mind that the -results outlined here also generalize to the N-D and non-square cases. -

    - -









    -

    No zero padding, unit strides

    - -

    The simplest case to analyze is when the kernel just slides across every -position of the input (i.e., \( s = 1 \) and \( p = 0 \)). -

    - -

    For any \( i \) and \( k \), and for \( s = 1 \) and \( p = 0 \),

    -$$ -\begin{equation*} - o = (i - k) + 1. -\end{equation*} -$$ - - -









    -

    Zero padding, unit strides

    - -

    To factor in zero padding (i.e., only restricting to \( s = 1 \)), let's consider -its effect on the effective input size: padding with \( p \) zeros changes the -effective input size from \( i \) to \( i + 2p \). In the general case, we can infer the following -relationship -

    - -

    For any \( i \), \( k \) and \( p \), and for \( s = 1 \),

    -$$ -\begin{equation*} - o = (i - k) + 2p + 1. -\end{equation*} -$$ - - -









    -

    Half (same) padding

    - -

    Having the output size be the same as the input size (i.e., \( o = i \)) can be a -desirable property: -

    - -

    For any \( i \) and for \( k \) odd (\( k = 2n + 1, \quad n \in \mathbb{N} \)), \( s = 1 \) and -\( p = \lfloor k / 2 \rfloor = n \), -

    -$$ -\begin{equation*} -\begin{split} - o &= i + 2 \lfloor k / 2 \rfloor - (k - 1) \\ - &= i + 2n - 2n \\ - &= i. -\end{split} -\end{equation*} -$$ - - -









    -

    Full padding

    - -

    While convolving a kernel generally decreases the output size with -respect to the input size, sometimes the opposite is required. This can be -achieved with proper zero padding: -

    - -

    For any \( i \) and \( k \), and for \( p = k - 1 \) and \( s = 1 \),

    -$$ -\begin{equation*} -\begin{split} - o &= i + 2(k - 1) - (k - 1) \\ - &= i + (k - 1). -\end{split} -\end{equation*} -$$ - -

    This is sometimes referred to as full padding, because in this -setting every possible partial or complete superimposition of the kernel on the -input feature map is taken into account. +the kernel with some other function.











    @@ -1091,29 +1332,608 @@ mean or average pooling, which all share the same idea of aggregating the input locally by applying a non-linearity to the content of some patches.

    -

    Since pooling does not involve -zero padding, the relationship describing the general case is as follows: -

    - -

    For any \( i \), \( k \) and \( s \),

    -$$ -\begin{equation*} - o = \left\lfloor \frac{i - k}{s} \right\rfloor + 1. -\end{equation*} -$$ - -









    Pooling types (From Raschka et al)


    -

    Figure 6: A deep CNN

    +

    Figure 5: A deep CNN

    +









    +

    Building convolutional neural networks in Tensorflow and Keras

    + +

    As discussed above, CNNs are neural networks built from the assumption that the inputs +to the network are 2D images. This is important because the number of features or pixels in images +grows very fast with the image size, and an enormous number of weights and biases are needed in order to build an accurate network. +

    + +

    As before, we still have our input, a hidden layer and an output. What's novel about convolutional networks +are the convolutional and pooling layers stacked in pairs between the input and the hidden layer. +In addition, the data is no longer represented as a 2D feature matrix, instead each input is a number of 2D +matrices, typically 1 for each color dimension (Red, Green, Blue). +

    + +









    +

    Setting it up

    + +

    It means that to represent the entire +dataset of images, we require a 4D matrix or tensor. This tensor has the dimensions: +

    +$$ +(n_{inputs},\, n_{pixels, width},\, n_{pixels, height},\, depth) . +$$ + + +









    +

    The MNIST dataset again

    + +

    The MNIST dataset consists of grayscale images with a pixel size of +\( 28\times 28 \), meaning we require \( 28 \times 28 = 724 \) weights to each +neuron in the first hidden layer. +

    + +

    If we were to analyze images of size \( 128\times 128 \) we would require +\( 128 \times 128 = 16384 \) weights to each neuron. Even worse if we were +dealing with color images, as most images are, we have an image matrix +of size \( 128\times 128 \) for each color dimension (Red, Green, Blue), +meaning 3 times the number of weights \( = 49152 \) are required for every +single neuron in the first hidden layer. +

    + + +









    +

    Strong correlations

    + +

    Images typically have strong local correlations, meaning that a small +part of the image varies little from its neighboring regions. If for +example we have an image of a blue car, we can roughly assume that a +small blue part of the image is surrounded by other blue regions. +

    + +

    Therefore, instead of connecting every single pixel to a neuron in the +first hidden layer, as we have previously done with deep neural +networks, we can instead connect each neuron to a small part of the +image (in all 3 RGB depth dimensions). The size of each small area is +fixed, and known as a receptive. +

    + + + +

    Layers of a CNN

    + +

    The layers of a convolutional neural network arrange neurons in 3D: width, height and depth. +The input image is typically a square matrix of depth 3. +

    + +

    A convolution is performed on the image which outputs +a 3D volume of neurons. The weights to the input are arranged in a number of 2D matrices, known as filters. +

    + +

    Each filter slides along the input image, taking the dot product +between each small part of the image and the filter, in all depth +dimensions. This is then passed through a non-linear function, +typically the Rectified Linear (ReLu) function, which serves as the +activation of the neurons in the first convolutional layer. This is +further passed through a pooling layer, which reduces the size of the +convolutional layer, e.g. by taking the maximum or average across some +small regions, and this serves as input to the next convolutional +layer. +

    + +









    +

    Systematic reduction

    + +

    By systematically reducing the size of the input volume, through +convolution and pooling, the network should create representations of +small parts of the input, and then from them assemble representations +of larger areas. The final pooling layer is flattened to serve as +input to a hidden layer, such that each neuron in the final pooling +layer is connected to every single neuron in the hidden layer. This +then serves as input to the output layer, e.g. a softmax output for +classification. +

    + + +









    +

    Prerequisites: Collect and pre-process data

    + + +
    +
    +
    +
    +
    +
    # import necessary packages
    +import numpy as np
    +import matplotlib.pyplot as plt
    +from sklearn import datasets
    +
    +
    +# ensure the same random numbers appear every time
    +np.random.seed(0)
    +
    +# display images in notebook
    +%matplotlib inline
    +plt.rcParams['figure.figsize'] = (12,12)
    +
    +
    +# download MNIST dataset
    +digits = datasets.load_digits()
    +
    +# define inputs and labels
    +inputs = digits.images
    +labels = digits.target
    +
    +# RGB images have a depth of 3
    +# our images are grayscale so they should have a depth of 1
    +inputs = inputs[:,:,:,np.newaxis]
    +
    +print("inputs = (n_inputs, pixel_width, pixel_height, depth) = " + str(inputs.shape))
    +print("labels = (n_inputs) = " + str(labels.shape))
    +
    +
    +# choose some random images to display
    +n_inputs = len(inputs)
    +indices = np.arange(n_inputs)
    +random_indices = np.random.choice(indices, size=5)
    +
    +for i, image in enumerate(digits.images[random_indices]):
    +    plt.subplot(1, 5, i+1)
    +    plt.axis('off')
    +    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    +    plt.title("Label: %d" % digits.target[random_indices[i]])
    +plt.show()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Importing Keras and Tensorflow

    + + +
    +
    +
    +
    +
    +
    from tensorflow.keras import datasets, layers, models
    +from tensorflow.keras.layers import Input
    +from tensorflow.keras.models import Sequential      #This allows appending layers to existing models
    +from tensorflow.keras.layers import Dense           #This allows defining the characteristics of a particular layer
    +from tensorflow.keras import optimizers             #This allows using whichever optimiser we want (sgd,adam,RMSprop)
    +from tensorflow.keras import regularizers           #This allows using whichever regularizer we want (l1,l2,l1_l2)
    +from tensorflow.keras.utils import to_categorical   #This allows using categorical cross entropy as the cost function
    +#from tensorflow.keras import Conv2D
    +#from tensorflow.keras import MaxPooling2D
    +#from tensorflow.keras import Flatten
    +
    +from sklearn.model_selection import train_test_split
    +
    +# representation of labels
    +labels = to_categorical(labels)
    +
    +# split into train and test data
    +# one-liner from scikit-learn library
    +train_size = 0.8
    +test_size = 1 - train_size
    +X_train, X_test, Y_train, Y_test = train_test_split(inputs, labels, train_size=train_size,
    +                                                    test_size=test_size)
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + +

    Running with Keras

    + + + +
    +
    +
    +
    +
    +
    def create_convolutional_neural_network_keras(input_shape, receptive_field,
    +                                              n_filters, n_neurons_connected, n_categories,
    +                                              eta, lmbd):
    +    model = Sequential()
    +    model.add(layers.Conv2D(n_filters, (receptive_field, receptive_field), input_shape=input_shape, padding='same',
    +              activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    +    model.add(layers.MaxPooling2D(pool_size=(2, 2)))
    +    model.add(layers.Flatten())
    +    model.add(layers.Dense(n_neurons_connected, activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    +    model.add(layers.Dense(n_categories, activation='softmax', kernel_regularizer=regularizers.l2(lmbd)))
    +    
    +    sgd = optimizers.SGD(lr=eta)
    +    model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
    +    
    +    return model
    +
    +epochs = 100
    +batch_size = 100
    +input_shape = X_train.shape[1:4]
    +receptive_field = 3
    +n_filters = 10
    +n_neurons_connected = 50
    +n_categories = 10
    +
    +eta_vals = np.logspace(-5, 1, 7)
    +lmbd_vals = np.logspace(-5, 1, 7)
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Final part

    + + + +
    +
    +
    +
    +
    +
    CNN_keras = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)
    +        
    +for i, eta in enumerate(eta_vals):
    +    for j, lmbd in enumerate(lmbd_vals):
    +        CNN = create_convolutional_neural_network_keras(input_shape, receptive_field,
    +                                              n_filters, n_neurons_connected, n_categories,
    +                                              eta, lmbd)
    +        CNN.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=0)
    +        scores = CNN.evaluate(X_test, Y_test)
    +        
    +        CNN_keras[i][j] = CNN
    +        
    +        print("Learning rate = ", eta)
    +        print("Lambda = ", lmbd)
    +        print("Test accuracy: %.3f" % scores[1])
    +        print()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Final visualization

    + + + +
    +
    +
    +
    +
    +
    # visual representation of grid search
    +# uses seaborn heatmap, could probably do this in matplotlib
    +import seaborn as sns
    +
    +sns.set()
    +
    +train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    +test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    +
    +for i in range(len(eta_vals)):
    +    for j in range(len(lmbd_vals)):
    +        CNN = CNN_keras[i][j]
    +
    +        train_accuracy[i][j] = CNN.evaluate(X_train, Y_train)[1]
    +        test_accuracy[i][j] = CNN.evaluate(X_test, Y_test)[1]
    +
    +        
    +fig, ax = plt.subplots(figsize = (10, 10))
    +sns.heatmap(train_accuracy, annot=True, ax=ax, cmap="viridis")
    +ax.set_title("Training Accuracy")
    +ax.set_ylabel("$\eta$")
    +ax.set_xlabel("$\lambda$")
    +plt.show()
    +
    +fig, ax = plt.subplots(figsize = (10, 10))
    +sns.heatmap(test_accuracy, annot=True, ax=ax, cmap="viridis")
    +ax.set_title("Test Accuracy")
    +ax.set_ylabel("$\eta$")
    +ax.set_xlabel("$\lambda$")
    +plt.show()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    The CIFAR01 data set

    + +

    The CIFAR10 dataset contains 60,000 color images in 10 classes, with +6,000 images in each class. The dataset is divided into 50,000 +training images and 10,000 testing images. The classes are mutually +exclusive and there is no overlap between them. +

    + + + +
    +
    +
    +
    +
    +
    import tensorflow as tf
    +
    +from tensorflow.keras import datasets, layers, models
    +import matplotlib.pyplot as plt
    +
    +# We import the data set
    +(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()
    +
    +# Normalize pixel values to be between 0 and 1 by dividing by 255. 
    +train_images, test_images = train_images / 255.0, test_images / 255.0
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Verifying the data set

    + +

    To verify that the dataset looks correct, let's plot the first 25 images from the training set and display the class name below each image.

    + + + +
    +
    +
    +
    +
    +
    class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
    +               'dog', 'frog', 'horse', 'ship', 'truck']
    +plt.figure(figsize=(10,10))
    +for i in range(25):
    +    plt.subplot(5,5,i+1)
    +    plt.xticks([])
    +    plt.yticks([])
    +    plt.grid(False)
    +    plt.imshow(train_images[i], cmap=plt.cm.binary)
    +    # The CIFAR labels happen to be arrays, 
    +    # which is why you need the extra index
    +    plt.xlabel(class_names[train_labels[i][0]])
    +plt.show()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Set up the model

    + +

    The 6 lines of code below define the convolutional base using a common pattern: a stack of Conv2D and MaxPooling2D layers.

    + +

    As input, a CNN takes tensors of shape (image_height, image_width, color_channels), ignoring the batch size. If you are new to these dimensions, color_channels refers to (R,G,B). In this example, you will configure our CNN to process inputs of shape (32, 32, 3), which is the format of CIFAR images. You can do this by passing the argument input_shape to our first layer.

    + + + +
    +
    +
    +
    +
    +
    model = models.Sequential()
    +model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
    +model.add(layers.MaxPooling2D((2, 2)))
    +model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    +model.add(layers.MaxPooling2D((2, 2)))
    +model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    +
    +# Let's display the architecture of our model so far.
    +
    +model.summary()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +

    You can see that the output of every Conv2D and MaxPooling2D layer is a 3D tensor of shape (height, width, channels). The width and height dimensions tend to shrink as you go deeper in the network. The number of output channels for each Conv2D layer is controlled by the first argument (e.g., 32 or 64). Typically, as the width and height shrink, you can afford (computationally) to add more output channels in each Conv2D layer.

    + +









    +

    Add Dense layers on top

    + +

    To complete our model, you will feed the last output tensor from the +convolutional base (of shape (4, 4, 64)) into one or more Dense layers +to perform classification. Dense layers take vectors as input (which +are 1D), while the current output is a 3D tensor. First, you will +flatten (or unroll) the 3D output to 1D, then add one or more Dense +layers on top. CIFAR has 10 output classes, so you use a final Dense +layer with 10 outputs and a softmax activation. +

    + + + +
    +
    +
    +
    +
    +
    model.add(layers.Flatten())
    +model.add(layers.Dense(64, activation='relu'))
    +model.add(layers.Dense(10))
    +Here's the complete architecture of our model.
    +
    +model.summary()
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +

    As you can see, our (4, 4, 64) outputs were flattened into vectors of shape (1024) before going through two Dense layers.

    + +









    +

    Compile and train the model

    + + + +
    +
    +
    +
    +
    +
    model.compile(optimizer='adam',
    +              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    +              metrics=['accuracy'])
    +
    +history = model.fit(train_images, train_labels, epochs=10, 
    +                    validation_data=(test_images, test_labels))
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +









    +

    Finally, evaluate the model

    + + + +
    +
    +
    +
    +
    +
    plt.plot(history.history['accuracy'], label='accuracy')
    +plt.plot(history.history['val_accuracy'], label = 'val_accuracy')
    +plt.xlabel('Epoch')
    +plt.ylabel('Accuracy')
    +plt.ylim([0.5, 1])
    +plt.legend(loc='lower right')
    +
    +test_loss, test_acc = model.evaluate(test_images,  test_labels, verbose=2)
    +
    +print(test_acc)
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +









    Building our own CNN code

    @@ -1123,6 +1943,8 @@ the codebase and fit a CNN that yields a 99% accuracy on the 28x28 MNIST dataset within reasonable time.

    +The codes here were developed by Eric Reber and Gregor Kajda during spring 2023. +

    The CNN is compatible with all schedulers, cost functions and activation functions discussed in constructing our neural network codes. @@ -1613,28 +2435,28 @@ $$ (f \ast g)(t):=\int_{-\infty}^{\infty} f(\tau) g(t-\tau) d \tau. $$ -

    Here, f and g are the two functions on which we want to perform an +

    Here, \( f \) and \( g \) are the two functions on which we want to perform an operation. The outcome of the convolution operation is represented by -\( (f \ast g) \), and it is derived by sliding the function g over f and +\( (f \ast g) \), and it is derived by sliding the function \( g \) over \( f \) and computing the integral of their product at each position. If both functions are continuous, convolution takes the form shown -above. However, if we discretize both f and g, the convolution -operation will take the form of a sum between the elements of f and g: +above. However, if we discretize both \( f \) and \( g \), the convolution +operation will take the form of a sum between the elements of \( f \) and \( g \):

    $$ -(f \ast g)[n]=\sum_{m=0}^{n-1} f[m] g[n-m]. +(f \ast g)[n]=\sum_{m=0}^{n-1} f(m) g(n-m). $$

    The key idea we utilize to extract the information contained in an -image is to slide an \( m \times n \) matrix g over an \( m \times n \) -matrix f. In our case, f represents the image, while g +image is to slide an \( m \times n \) matrix \( g \) over an \( m \times n \) +matrix \( f \). In our case, \( f \) represents the image, while \( g \) represents the kernel, oftentimes called a filter. However, since our convolution will be a two-dimensional variant, we need to extend our mathematical formula with an additional summation:

    $$ -(f \ast g)[i, j]\sum_{m=0}^{M-1}\sum_{n=0}^{N-1} f[m,n] g[i-m, j-n]. +(f \ast g)(i, j)\sum_{m=0}^{M-1}\sum_{n=0}^{N-1} f(m,n) g(i-m, j-n). $$

    It is imperative to note that the size of the kernel g is @@ -1646,7 +2468,7 @@ appropriate kernels for specific tasks.

    To give you an example of how 2D convolution works in practice, -suppose we have an image f of dimension \( 6 \times 6 \) +suppose we have an image \( f \) of dimension \( 6 \times 6 \)

    $$ @@ -1660,7 +2482,7 @@ f = \begin{bmatrix} \end{bmatrix} $$ -

    and a \( 3 \times 3 \) kernel g called a low-pass filter. Note that the +

    and a \( 3 \times 3 \) kernel \( g \) called a low-pass filter. Note that the kernel is usually rotated by 180 degrees during convolution, however this has no effect on this kernel.

    @@ -1675,9 +2497,9 @@ g = \frac{1}{9} $$

    In order to filter the image, we have to extract a \( 3 \times 3 \) -element from the upper left corner of f, and perform element-wise +element from the upper left corner of \( f \), and perform element-wise multiplication of the extracted image pixels with the elements of the -kernel g: +kernel \( g \):

    $$ @@ -1698,21 +2520,21 @@ $$ \frac{9}{9} & \frac{5}{9} & \frac{9}{9} \\ \frac{1}{9} & \frac{5}{9} & \frac{9}{9} \\ \end {bmatrix} -= \textbf{A} += \boldsymbol{A} $$ -

    Then, following the multiplication, we summarize all the elements of the resulting matrix A:

    +

    Then, following the multiplication, we summarize all the elements of the resulting matrix \( \boldsymbol{A} \):

    $$ -(f \ast g)[0, 0]= \sum_{i=0}^{2} \sum_{j=0}^{2} a_{i,j} = 5 +(f \ast g)(0, 0)= \sum_{i=0}^{2} \sum_{j=0}^{2} a_{i,j} = 5, $$ -

    Which corresponds to the first element of the filtered image \( (f \ast g) \).

    +

    which corresponds to the first element of the filtered image \( (f \ast g) \).

    -

    Here we use a stride of 1, a parameter denoted s which describes how -many indexes we move the kernel g to the right before repeating the +

    Here we use a stride of \( S=1 \), a parameter denoted \( S \) which describes how +many indexes we move the kernel \( g \) to the right before repeating the calculations above for the next \( 3 \times 3 \) element of the image -f. It is usually presumed that *s*=1, however, larger values for s +\( f \). It is usually presumed that \( S=1 \), however, larger values for \( S \) can be used to reduce the dimentionality of the filtered image such that the convolution operation is more computationally efficient. In the context of a convolutional neural network, this will become very @@ -1731,18 +2553,23 @@ $$ \end{bmatrix} $$ -

    The result is markedly smaller in shape than the original image. This occurs when using convolution without first padding the image with additional columns and rows, allowing us to keep the original image shape after sliding the kernel over the image. -How many rows and columns we wish to pad the image with depends strictly on the shape of the kernel, as we wish to pad the image with r additional rows and c additional columns. +

    The result is markedly smaller in shape than the original image. This +occurs when using convolution without first padding the image with +additional columns and rows, allowing us to keep the original image +shape after sliding the kernel over the image. How many rows and +columns we wish to pad the image with depends strictly on the shape of +the kernel, as we wish to pad the image with \( r \) additional rows and +\( c \) additional columns.

    $$ -r =\lfloor \frac{kernel\ height}{2} \rfloor \cdot 2 \\ -c =\lfloor \frac{kernel\ width}{2} \rfloor \cdot 2 +r =\lfloor \frac{\mathrm{kernel height}}{2} \rfloor \cdot 2 \\ +c =\lfloor \frac{\mathrm{kernel width}}{2} \rfloor \cdot 2 $$ -

    Note the notation \( \lfloor \frac{kernel width}{2} \rfloor \) means that +

    Note the notation \( \lfloor \frac{\mathrm{kernel width}}{2} \rfloor \) means that we floor the result of the division, meaning we round down to a whole -number in case \( \frac{kernel width}{2} \) results in a floating point +number in case \( \frac{\mathrm{kernel width}}{2} \) results in a floating point number.

    @@ -1768,11 +2595,13 @@ $$ 0 & 8 & 1 & 6 & 4 & 2 & 2 & 0 \\ 0 & 1 & 0 & 5 & 7 & 8 & 2 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ -\] -\end{bmatrix} +\end{bmatrix}. $$ -

    Below we have provided code that demonstrates padding and convolution. As you will see when we run the code, the size of the image will remain unchanged when using padding. 

    +

    Below we have provided code that demonstrates padding and +convolution. As you will see when we run the code, the size of the +image will remain unchanged when using padding.~ +

    @@ -1867,9 +2696,10 @@ $$
    -

    Fun fact: When filtering images, you will see that convolution involves rotating the kernel by 180 degrees. -However, this is not the case when applying convolution in a CNN, where the same operation not rotated by 180 degrees is called -cross-correlation. +

    Fun fact: When filtering images, you will see that convolution +involves rotating the kernel by 180 degrees. However, this is not the +case when applying convolution in a CNN, where the same operation that is not +rotated by 180 degrees is called cross-correlation, which is normally implemented in most libraries.

    @@ -1929,7 +2759,7 @@ feed forward neural network.
    -
    # Now an example using a real image and first a gaussian low-pass filter and then a sobel filter
    +  
    # Now an example using a real image and first a gaussian low-pass filter and then a Sobel filter
     import numpy as np
     import imageio.v3 as imageio
     import matplotlib.pyplot as plt
    @@ -3956,7 +4786,7 @@ cnn.add_OutputLayer(Building convolutional neural networks in Tensorflow and Keras 
    -
    -

    As discussed above, CNNs are neural networks built from the assumption that the inputs -to the network are 2D images. This is important because the number of features or pixels in images -grows very fast with the image size, and an enormous number of weights and biases are needed in order to build an accurate network. -

    - -

    As before, we still have our input, a hidden layer and an output. What's novel about convolutional networks -are the convolutional and pooling layers stacked in pairs between the input and the hidden layer. -In addition, the data is no longer represented as a 2D feature matrix, instead each input is a number of 2D -matrices, typically 1 for each color dimension (Red, Green, Blue). -

    - -









    -

    Setting it up

    - -

    It means that to represent the entire -dataset of images, we require a 4D matrix or tensor. This tensor has the dimensions: -

    -$$ -(n_{inputs},\, n_{pixels, width},\, n_{pixels, height},\, depth) . -$$ - - -









    -

    The MNIST dataset again

    - -

    The MNIST dataset consists of grayscale images with a pixel size of -\( 28\times 28 \), meaning we require \( 28 \times 28 = 724 \) weights to each -neuron in the first hidden layer. -

    - -

    If we were to analyze images of size \( 128\times 128 \) we would require -\( 128 \times 128 = 16384 \) weights to each neuron. Even worse if we were -dealing with color images, as most images are, we have an image matrix -of size \( 128\times 128 \) for each color dimension (Red, Green, Blue), -meaning 3 times the number of weights \( = 49152 \) are required for every -single neuron in the first hidden layer. -

    - - -









    -

    Strong correlations

    - -

    Images typically have strong local correlations, meaning that a small -part of the image varies little from its neighboring regions. If for -example we have an image of a blue car, we can roughly assume that a -small blue part of the image is surrounded by other blue regions. -

    - -

    Therefore, instead of connecting every single pixel to a neuron in the -first hidden layer, as we have previously done with deep neural -networks, we can instead connect each neuron to a small part of the -image (in all 3 RGB depth dimensions). The size of each small area is -fixed, and known as a receptive. -

    - - - -

    Layers of a CNN

    - -

    The layers of a convolutional neural network arrange neurons in 3D: width, height and depth. -The input image is typically a square matrix of depth 3. -

    - -

    A convolution is performed on the image which outputs -a 3D volume of neurons. The weights to the input are arranged in a number of 2D matrices, known as filters. -

    - -

    Each filter slides along the input image, taking the dot product -between each small part of the image and the filter, in all depth -dimensions. This is then passed through a non-linear function, -typically the Rectified Linear (ReLu) function, which serves as the -activation of the neurons in the first convolutional layer. This is -further passed through a pooling layer, which reduces the size of the -convolutional layer, e.g. by taking the maximum or average across some -small regions, and this serves as input to the next convolutional -layer. -

    - -









    -

    Systematic reduction

    - -

    By systematically reducing the size of the input volume, through -convolution and pooling, the network should create representations of -small parts of the input, and then from them assemble representations -of larger areas. The final pooling layer is flattened to serve as -input to a hidden layer, such that each neuron in the final pooling -layer is connected to every single neuron in the hidden layer. This -then serves as input to the output layer, e.g. a softmax output for -classification. -

    - - -









    -

    Prerequisites: Collect and pre-process data

    - - -
    -
    -
    -
    -
    -
    # import necessary packages
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from sklearn import datasets
    -
    -
    -# ensure the same random numbers appear every time
    -np.random.seed(0)
    -
    -# display images in notebook
    -%matplotlib inline
    -plt.rcParams['figure.figsize'] = (12,12)
    -
    -
    -# download MNIST dataset
    -digits = datasets.load_digits()
    -
    -# define inputs and labels
    -inputs = digits.images
    -labels = digits.target
    -
    -# RGB images have a depth of 3
    -# our images are grayscale so they should have a depth of 1
    -inputs = inputs[:,:,:,np.newaxis]
    -
    -print("inputs = (n_inputs, pixel_width, pixel_height, depth) = " + str(inputs.shape))
    -print("labels = (n_inputs) = " + str(labels.shape))
    -
    -
    -# choose some random images to display
    -n_inputs = len(inputs)
    -indices = np.arange(n_inputs)
    -random_indices = np.random.choice(indices, size=5)
    -
    -for i, image in enumerate(digits.images[random_indices]):
    -    plt.subplot(1, 5, i+1)
    -    plt.axis('off')
    -    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    -    plt.title("Label: %d" % digits.target[random_indices[i]])
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -









    -

    Importing Keras and Tensorflow

    - - -
    -
    -
    -
    -
    -
    from tensorflow.keras import datasets, layers, models
    -from tensorflow.keras.layers import Input
    -from tensorflow.keras.models import Sequential      #This allows appending layers to existing models
    -from tensorflow.keras.layers import Dense           #This allows defining the characteristics of a particular layer
    -from tensorflow.keras import optimizers             #This allows using whichever optimiser we want (sgd,adam,RMSprop)
    -from tensorflow.keras import regularizers           #This allows using whichever regularizer we want (l1,l2,l1_l2)
    -from tensorflow.keras.utils import to_categorical   #This allows using categorical cross entropy as the cost function
    -#from tensorflow.keras import Conv2D
    -#from tensorflow.keras import MaxPooling2D
    -#from tensorflow.keras import Flatten
    -
    -from sklearn.model_selection import train_test_split
    -
    -# representation of labels
    -labels = to_categorical(labels)
    -
    -# split into train and test data
    -# one-liner from scikit-learn library
    -train_size = 0.8
    -test_size = 1 - train_size
    -X_train, X_test, Y_train, Y_test = train_test_split(inputs, labels, train_size=train_size,
    -                                                    test_size=test_size)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - -

    Running with Keras

    - - - -
    -
    -
    -
    -
    -
    def create_convolutional_neural_network_keras(input_shape, receptive_field,
    -                                              n_filters, n_neurons_connected, n_categories,
    -                                              eta, lmbd):
    -    model = Sequential()
    -    model.add(layers.Conv2D(n_filters, (receptive_field, receptive_field), input_shape=input_shape, padding='same',
    -              activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    -    model.add(layers.MaxPooling2D(pool_size=(2, 2)))
    -    model.add(layers.Flatten())
    -    model.add(layers.Dense(n_neurons_connected, activation='relu', kernel_regularizer=regularizers.l2(lmbd)))
    -    model.add(layers.Dense(n_categories, activation='softmax', kernel_regularizer=regularizers.l2(lmbd)))
    -    
    -    sgd = optimizers.SGD(learning_rate=eta)
    -    model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
    -    
    -    return model
    -
    -epochs = 100
    -batch_size = 100
    -input_shape = X_train.shape[1:4]
    -receptive_field = 3
    -n_filters = 10
    -n_neurons_connected = 50
    -n_categories = 10
    -
    -eta_vals = np.logspace(-5, 1, 7)
    -lmbd_vals = np.logspace(-5, 1, 7)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -









    -

    Final part

    - - - -
    -
    -
    -
    -
    -
    CNN_keras = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)
    -        
    -for i, eta in enumerate(eta_vals):
    -    for j, lmbd in enumerate(lmbd_vals):
    -        CNN = create_convolutional_neural_network_keras(input_shape, receptive_field,
    -                                              n_filters, n_neurons_connected, n_categories,
    -                                              eta, lmbd)
    -        CNN.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=0)
    -        scores = CNN.evaluate(X_test, Y_test)
    -        
    -        CNN_keras[i][j] = CNN
    -        
    -        print("Learning rate = ", eta)
    -        print("Lambda = ", lmbd)
    -        print("Test accuracy: %.3f" % scores[1])
    -        print()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - -









    -

    Final visualization

    - - - -
    -
    -
    -
    -
    -
    # visual representation of grid search
    -# uses seaborn heatmap, could probably do this in matplotlib
    -import seaborn as sns
    -
    -sns.set()
    -
    -train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    -test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))
    -
    -for i in range(len(eta_vals)):
    -    for j in range(len(lmbd_vals)):
    -        CNN = CNN_keras[i][j]
    -
    -        train_accuracy[i][j] = CNN.evaluate(X_train, Y_train)[1]
    -        test_accuracy[i][j] = CNN.evaluate(X_test, Y_test)[1]
    -
    -        
    -fig, ax = plt.subplots(figsize = (10, 10))
    -sns.heatmap(train_accuracy, annot=True, ax=ax, cmap="viridis")
    -ax.set_title("Training Accuracy")
    -ax.set_ylabel("$\eta$")
    -ax.set_xlabel("$\lambda$")
    -plt.show()
    -
    -fig, ax = plt.subplots(figsize = (10, 10))
    -sns.heatmap(test_accuracy, annot=True, ax=ax, cmap="viridis")
    -ax.set_title("Test Accuracy")
    -ax.set_ylabel("$\eta$")
    -ax.set_xlabel("$\lambda$")
    -plt.show()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    © 1999-2024, Morten Hjorth-Jensen. Released under CC Attribution-NonCommercial 4.0 license diff --git a/doc/pub/week44/ipynb/ipynb-week44-src.tar.gz b/doc/pub/week44/ipynb/ipynb-week44-src.tar.gz index ca0640db170c4b9e684fb0f0329b93a6eaee4b69..24586dcc3af90e5656efb64bc948c4142f65c2c6 100644 GIT binary patch delta 304309 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

    0md0(DsaF7hbQiQ8 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ℜT;lujcaHub!4#=Ly|LxByF7boc?s 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 Bo$mku delta 329901 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

    9n${_zuOxmr6hq|KFjbtI=8);fs$MM;3?ExZ&z< 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%0D6bgP&*-C={r76 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�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 T*MG1757++!4kPpO07eJ^sq71< diff --git a/doc/pub/week44/ipynb/week44.ipynb b/doc/pub/week44/ipynb/week44.ipynb index 749eec626..5ddacc548 100644 --- a/doc/pub/week44/ipynb/week44.ipynb +++ b/doc/pub/week44/ipynb/week44.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "2f2e45af", + "id": "afaf8390", "metadata": { "editable": true }, @@ -14,7 +14,7 @@ }, { "cell_type": "markdown", - "id": "a72fad3a", + "id": "f17d25d5", "metadata": { "editable": true }, @@ -27,7 +27,7 @@ }, { "cell_type": "markdown", - "id": "f18b2d0e", + "id": "736a723a", "metadata": { "editable": true }, @@ -55,7 +55,7 @@ }, { "cell_type": "markdown", - "id": "6d98e193", + "id": "93caafca", "metadata": { "editable": true }, @@ -71,7 +71,7 @@ }, { "cell_type": "markdown", - "id": "4047cdc3", + "id": "c99e8ca9", "metadata": { "editable": true }, @@ -81,7 +81,7 @@ }, { "cell_type": "markdown", - "id": "dfaf7b2e", + "id": "5a52c2d6", "metadata": { "editable": true }, @@ -108,7 +108,7 @@ }, { "cell_type": "markdown", - "id": "c7006347", + "id": "05ae37f5", "metadata": { "editable": true }, @@ -124,7 +124,7 @@ }, { "cell_type": "markdown", - "id": "ae0e4359", + "id": "e174ca11", "metadata": { "editable": true }, @@ -142,7 +142,7 @@ }, { "cell_type": "markdown", - "id": "8e4de8da", + "id": "a8f5c3af", "metadata": { "editable": true }, @@ -172,7 +172,7 @@ }, { "cell_type": "markdown", - "id": "e5741900", + "id": "e784f567", "metadata": { "editable": true }, @@ -202,7 +202,7 @@ }, { "cell_type": "markdown", - "id": "37f1f2f5", + "id": "d28632ea", "metadata": { "editable": true }, @@ -242,7 +242,7 @@ }, { "cell_type": "markdown", - "id": "74f9a266", + "id": "8fdd2e1e", "metadata": { "editable": true }, @@ -269,7 +269,7 @@ }, { "cell_type": "markdown", - "id": "a18c56c1", + "id": "fe077130", "metadata": { "editable": true }, @@ -281,7 +281,7 @@ }, { "cell_type": "markdown", - "id": "cc22cab9", + "id": "62200bea", "metadata": { "editable": true }, @@ -291,7 +291,7 @@ }, { "cell_type": "markdown", - "id": "ac776ad9", + "id": "64a09aab", "metadata": { "editable": true }, @@ -309,7 +309,7 @@ }, { "cell_type": "markdown", - "id": "6082bd12", + "id": "ec7a27eb", "metadata": { "editable": true }, @@ -338,7 +338,7 @@ }, { "cell_type": "markdown", - "id": "53cb4ec8", + "id": "891c4e18", "metadata": { "editable": true }, @@ -360,7 +360,7 @@ }, { "cell_type": "markdown", - "id": "27ff37df", + "id": "f9a224d5", "metadata": { "editable": true }, @@ -382,7 +382,7 @@ }, { "cell_type": "markdown", - "id": "caa0560a", + "id": "74ebf7e1", "metadata": { "editable": true }, @@ -398,7 +398,7 @@ }, { "cell_type": "markdown", - "id": "e2b649c6", + "id": "127a2805", "metadata": { "editable": true }, @@ -415,7 +415,113 @@ }, { "cell_type": "markdown", - "id": "b9e5408b", + "id": "bdfeefdc", + "metadata": { + "editable": true + }, + "source": [ + "## How to do image compression before the era of deep learning\n", + "\n", + "The singular-value decomposition (SVD) algorithm has been for decades one of the standard ways of compressing images.\n", + "The [lectures on the SVD](https://compphysics.github.io/MachineLearning/doc/LectureNotes/_build/html/chapter2.html#the-singular-value-decomposition) give many of the essential details concerning the SVD.\n", + "\n", + "The orthogonal vectors which are obtained from the SVD, can be used to\n", + "project down the dimensionality of a given image. In the example here\n", + "we gray-scale an image and downsize it.\n", + "\n", + "This recipe relies on us being able to actually perform the SVD. For\n", + "large images, and in particular with many images to reconstruct, using the SVD \n", + "may quickly become an overwhelming task. With the advent of efficient deep\n", + "learning methods like CNNs and later generative methods, these methods\n", + "have become in the last years the premier way of performing image\n", + "analysis. In particular for classification problems with labelled images." + ] + }, + { + "cell_type": "markdown", + "id": "5b9c0de1", + "metadata": { + "editable": true + }, + "source": [ + "## The SVD example" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "36668e14", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "\n", + "from matplotlib.image import imread\n", + "import matplotlib.pyplot as plt\n", + "import scipy.linalg as ln\n", + "import numpy as np\n", + "import os\n", + "from PIL import Image\n", + "from math import log10, sqrt \n", + "plt.rcParams['figure.figsize'] = [16, 8]\n", + "# Import image\n", + "A = imread(os.path.join(\"figslides/photo1.jpg\"))\n", + "X = A.dot([0.299, 0.5870, 0.114]) # Convert RGB to grayscale\n", + "img = plt.imshow(X)\n", + "# convert to gray\n", + "img.set_cmap('gray')\n", + "plt.axis('off')\n", + "plt.show()\n", + "# Call image size\n", + "print(': %s'%str(X.shape))\n", + "\n", + "\n", + "# split the matrix into U, S, VT\n", + "U, S, VT = np.linalg.svd(X,full_matrices=False)\n", + "S = np.diag(S)\n", + "m = 800 # Image's width\n", + "n = 1200 # Image's height\n", + "j = 0\n", + "# Try compression with different k vectors (these represent projections):\n", + "for k in (5,10, 20, 100,200,400,500):\n", + " # Original size of the image\n", + " originalSize = m * n \n", + " # Size after compressed\n", + " compressedSize = k * (1 + m + n) \n", + " # The projection of the original image\n", + " Xapprox = U[:,:k] @ S[0:k,:k] @ VT[:k,:]\n", + " plt.figure(j+1)\n", + " j += 1\n", + " img = plt.imshow(Xapprox)\n", + " img.set_cmap('gray')\n", + " \n", + " plt.axis('off')\n", + " plt.title('k = ' + str(k))\n", + " plt.show() \n", + " print('Original size of image:')\n", + " print(originalSize)\n", + " print('Compression rate as Compressed image / Original size:')\n", + " ratio = compressedSize * 1.0 / originalSize\n", + " print(ratio)\n", + " print('Compression rate is ' + str( round(ratio * 100 ,2)) + '%' ) \n", + " # Estimate MQA\n", + " x= X.astype(\"float\")\n", + " y=Xapprox.astype(\"float\")\n", + " err = np.sum((x - y) ** 2)\n", + " err /= float(X.shape[0] * Xapprox.shape[1])\n", + " print('The mean-square deviation '+ str(round( err)))\n", + " max_pixel = 255.0\n", + " # Estimate Signal Noise Ratio\n", + " srv = 20 * (log10(max_pixel / sqrt(err)))\n", + " print('Signa to noise ratio '+ str(round(srv)) +'dB')" + ] + }, + { + "cell_type": "markdown", + "id": "68fd7bf4", "metadata": { "editable": true }, @@ -438,7 +544,7 @@ }, { "cell_type": "markdown", - "id": "2c4541a9", + "id": "af30ff6d", "metadata": { "editable": true }, @@ -450,7 +556,7 @@ }, { "cell_type": "markdown", - "id": "c6ceee4f", + "id": "0b6a0a87", "metadata": { "editable": true }, @@ -462,7 +568,7 @@ }, { "cell_type": "markdown", - "id": "728f683d", + "id": "06204c0f", "metadata": { "editable": true }, @@ -474,7 +580,7 @@ }, { "cell_type": "markdown", - "id": "528c85e8", + "id": "f793221b", "metadata": { "editable": true }, @@ -484,7 +590,7 @@ }, { "cell_type": "markdown", - "id": "34e6559a", + "id": "754789c3", "metadata": { "editable": true }, @@ -496,7 +602,7 @@ }, { "cell_type": "markdown", - "id": "cc39daf4", + "id": "edaf2d6b", "metadata": { "editable": true }, @@ -508,7 +614,100 @@ }, { "cell_type": "markdown", - "id": "a538fef9", + "id": "c10e0e6e", + "metadata": { + "editable": true + }, + "source": [ + "## Mathematics of CNNs\n", + "\n", + "The mathematics of CNNs is based on the mathematical operation of\n", + "**convolution**. In mathematics (in particular in functional analysis),\n", + "convolution is represented by mathematical operations (integration,\n", + "summation etc) on two functions in order to produce a third function\n", + "that expresses how the shape of one gets modified by the other.\n", + "Convolution has a plethora of applications in a variety of\n", + "disciplines, spanning from statistics to signal processing, computer\n", + "vision, solutions of differential equations,linear algebra,\n", + "engineering, and yes, machine learning.\n", + "\n", + "Mathematically, convolution is defined as follows (one-dimensional example):\n", + "Let us define a continuous function $y(t)$ given by" + ] + }, + { + "cell_type": "markdown", + "id": "3e21ff12", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y(t) = \\int x(a) w(t-a) da,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "763cf144", + "metadata": { + "editable": true + }, + "source": [ + "where $x(a)$ represents a so-called input and $w(t-a)$ is normally called the weight function or kernel.\n", + "\n", + "The above integral is written in a more compact form as" + ] + }, + { + "cell_type": "markdown", + "id": "d50d06cc", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y(t) = \\left(x * w\\right)(t).\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "6924641b", + "metadata": { + "editable": true + }, + "source": [ + "The discretized version reads" + ] + }, + { + "cell_type": "markdown", + "id": "187e8990", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y(t) = \\sum_{a=-\\infty}^{a=\\infty}x(a)w(t-a).\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "6470335b", + "metadata": { + "editable": true + }, + "source": [ + "Computing the inverse of the above convolution operations is known as deconvolution and the process is commutative.\n", + "\n", + "How can we use this? And what does it mean? Let us study some familiar examples first." + ] + }, + { + "cell_type": "markdown", + "id": "ec95c776", "metadata": { "editable": true }, @@ -527,7 +726,7 @@ }, { "cell_type": "markdown", - "id": "4936649a", + "id": "d4b3c227", "metadata": { "editable": true }, @@ -539,7 +738,7 @@ }, { "cell_type": "markdown", - "id": "bcc8f865", + "id": "9d58c7b8", "metadata": { "editable": true }, @@ -549,7 +748,7 @@ }, { "cell_type": "markdown", - "id": "914a9307", + "id": "122efa36", "metadata": { "editable": true }, @@ -561,7 +760,7 @@ }, { "cell_type": "markdown", - "id": "a34a624b", + "id": "195156ea", "metadata": { "editable": true }, @@ -571,7 +770,7 @@ }, { "cell_type": "markdown", - "id": "90c3cde7", + "id": "19dc2f3a", "metadata": { "editable": true }, @@ -583,7 +782,7 @@ }, { "cell_type": "markdown", - "id": "5bc6a269", + "id": "4f9f3064", "metadata": { "editable": true }, @@ -596,7 +795,7 @@ }, { "cell_type": "markdown", - "id": "8d72962e", + "id": "478460ae", "metadata": { "editable": true }, @@ -615,7 +814,7 @@ }, { "cell_type": "markdown", - "id": "16256c3c", + "id": "e73dc29f", "metadata": { "editable": true }, @@ -627,7 +826,7 @@ }, { "cell_type": "markdown", - "id": "f4851858", + "id": "5fc26071", "metadata": { "editable": true }, @@ -639,7 +838,7 @@ }, { "cell_type": "markdown", - "id": "c0df4f1d", + "id": "555caa5e", "metadata": { "editable": true }, @@ -649,7 +848,7 @@ }, { "cell_type": "markdown", - "id": "af9edfb7", + "id": "1e18c64b", "metadata": { "editable": true }, @@ -661,7 +860,7 @@ }, { "cell_type": "markdown", - "id": "1c24de9c", + "id": "8f490b21", "metadata": { "editable": true }, @@ -673,7 +872,7 @@ }, { "cell_type": "markdown", - "id": "35cf2246", + "id": "ed3baa45", "metadata": { "editable": true }, @@ -685,17 +884,18 @@ }, { "cell_type": "markdown", - "id": "28b59952", + "id": "2cb35d1f", "metadata": { "editable": true }, "source": [ - "where $m=3$ in our case, the maximum length of the vector $\\alpha$. Note that the vector $\\boldsymbol{\\beta}$ has length $n=4$." + "where $m=3$ in our case, the maximum length of\n", + "the vector $\\alpha$. Note that the vector $\\boldsymbol{\\beta}$ has length $n=4$. Below we will find an even more efficient representation." ] }, { "cell_type": "markdown", - "id": "180983f3", + "id": "b3a4e62a", "metadata": { "editable": true }, @@ -709,7 +909,7 @@ }, { "cell_type": "markdown", - "id": "a69653d6", + "id": "fbc69d73", "metadata": { "editable": true }, @@ -727,18 +927,20 @@ }, { "cell_type": "markdown", - "id": "cacaacab", + "id": "c2692c2d", "metadata": { "editable": true }, "source": [ + "## Commutative process\n", + "\n", "The process is commutative and we can easily see that we can rewrite the multiplication in terms of a matrix holding $\\beta$ and a vector holding $\\alpha$.\n", "In this case we have" ] }, { "cell_type": "markdown", - "id": "761b90f3", + "id": "cdcce1ea", "metadata": { "editable": true }, @@ -756,7 +958,7 @@ }, { "cell_type": "markdown", - "id": "109f2d7f", + "id": "5e82c416", "metadata": { "editable": true }, @@ -765,7 +967,17 @@ "and not implementation purposes. When implementing the above equation\n", "we do not encode (and allocate memory) the matrices explicitely. We\n", "rather code the convolutions in the minimal memory footprint that they\n", - "require.\n", + "require." + ] + }, + { + "cell_type": "markdown", + "id": "066fb9c0", + "metadata": { + "editable": true + }, + "source": [ + "## Toeplitz matrices\n", "\n", "The above matrices are examples of so-called [Toeplitz\n", "matrices](https://link.springer.com/book/10.1007/978-93-86279-04-0). A\n", @@ -776,7 +988,7 @@ }, { "cell_type": "markdown", - "id": "a2bfd662", + "id": "7164a220", "metadata": { "editable": true }, @@ -794,7 +1006,7 @@ }, { "cell_type": "markdown", - "id": "4b1a63ee", + "id": "5b0b6645", "metadata": { "editable": true }, @@ -810,7 +1022,7 @@ }, { "cell_type": "markdown", - "id": "56498e0e", + "id": "78c20ffc", "metadata": { "editable": true }, @@ -825,33 +1037,38 @@ }, { "cell_type": "markdown", - "id": "adcf2b58", + "id": "7d6555e5", "metadata": { "editable": true }, "source": [ "## Generalizing the above one-dimensional case\n", "\n", - "In order to align the above simple case with the more general convolution cases, we rename $\\boldsymbol{\\alpha}$, whose length is $m=3$, with $\\boldsymbol{w}$.\n", - "We will interpret $\\boldsymbol{w}$ as a weight/filter function with which we want to perform the convolution with an input varibale $\\boldsymbol{x}$.\n", - "We replace thus $\\boldsymbol{\\beta}$ with $\\boldsymbol{x}$ and $\\boldsymbol{\\delta}$ with $\\boldsymbol{s}$ and have" + "In order to align the above simple case with the more general\n", + "convolution cases, we rename $\\boldsymbol{\\alpha}$, whose length is $m=3$,\n", + "with $\\boldsymbol{w}$. We will interpret $\\boldsymbol{w}$ as a weight/filter function\n", + "with which we want to perform the convolution with an input variable\n", + "$\\boldsymbol{x}$ of length $n$. We will assume always that the filter\n", + "$\\boldsymbol{w}$ has dimensionality $m \\le n$.\n", + "\n", + "We replace thus $\\boldsymbol{\\beta}$ with $\\boldsymbol{x}$ and $\\boldsymbol{\\delta}$ with $\\boldsymbol{y}$ and have" ] }, { "cell_type": "markdown", - "id": "a89ecd51", + "id": "cb3ae3f4", "metadata": { "editable": true }, "source": [ "$$\n", - "s(i) \\left(x*w\\right)(i)= \\sum_{k=0}^{k=m-1}w(k)x(i-k),\n", + "y(i)= \\left(x*w\\right)(i)= \\sum_{k=0}^{k=m-1}w(k)x(i-k),\n", "$$" ] }, { "cell_type": "markdown", - "id": "836affb6", + "id": "ba84498b", "metadata": { "editable": true }, @@ -862,12 +1079,249 @@ }, { "cell_type": "markdown", - "id": "831d1a03", + "id": "afdd9570", "metadata": { "editable": true }, "source": [ - "## Two-dimensional Objects\n", + "## Memory considerations\n", + "\n", + "This expression leaves us however with some terms with negative\n", + "indices, for example $x(-1)$ and $x(-2)$ which may not be defined. Our\n", + "vector $\\boldsymbol{x}$ has components $x(0)$, $x(1)$, $x(2)$ and $x(3)$.\n", + "\n", + "The index $j$ for $\\boldsymbol{x}$ runs from $j=0$ to $j=3$ since $\\boldsymbol{x}$ is meant to\n", + "represent a third-order polynomial.\n", + "\n", + "Furthermore, the index $i$ runs from $i=0$ to $i=5$ since $\\boldsymbol{y}$\n", + "contains the coefficients of a fifth-order polynomial. When $i=5$ we\n", + "may also have values of $x(4)$ and $x(5)$ which are not defined." + ] + }, + { + "cell_type": "markdown", + "id": "9d8a1259", + "metadata": { + "editable": true + }, + "source": [ + "## Padding\n", + "\n", + "The solution to this is what is called **padding**! We simply define a\n", + "new vector $x$ with two added elements set to zero before $x(0)$ and\n", + "two new elements after $x(3)$ set to zero. That is, we augment the\n", + "length of $\\boldsymbol{x}$ from $n=4$ to $n+2P=8$, where $P=2$ is the padding\n", + "constant (a new hyperparameter), see discussions below as well." + ] + }, + { + "cell_type": "markdown", + "id": "93166a7d", + "metadata": { + "editable": true + }, + "source": [ + "## New vector\n", + "\n", + "We have a new vector defined as $x(0)=0$, $x(1)=0$,\n", + "$x(2)=\\beta_0$, $x(3)=\\beta_1$, $x(4)=\\beta_2$, $x(5)=\\beta_3$,\n", + "$x(6)=0$, and $x(7)=0$.\n", + "\n", + "We have added four new elements, which\n", + "are all zero. The benefit is that we can rewrite the equation for\n", + "$\\boldsymbol{y}$, with $i=0,1,\\dots,5$," + ] + }, + { + "cell_type": "markdown", + "id": "217d00a9", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y(i) = \\sum_{k=0}^{k=m-1}w(k)x(i+(m-1)-k).\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "2dcc48bf", + "metadata": { + "editable": true + }, + "source": [ + "As an example, we have" + ] + }, + { + "cell_type": "markdown", + "id": "28874f6c", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y(4)=x(6)w(0)+x(5)w(1)+x(4)w(2)=0\\times \\alpha_0+\\beta_3\\alpha_1+\\beta_2\\alpha_2,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "2ba7a9dc", + "metadata": { + "editable": true + }, + "source": [ + "as before except that we have an additional term $x(6)w(0)$, which is zero.\n", + "\n", + "Similarly, for the fifth-order term we have" + ] + }, + { + "cell_type": "markdown", + "id": "4daa6524", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y(5)=x(7)w(0)+x(6)w(1)+x(5)w(2)=0\\times \\alpha_0+0\\times\\alpha_1+\\beta_3\\alpha_2.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "c5ef900a", + "metadata": { + "editable": true + }, + "source": [ + "The zeroth-order term is" + ] + }, + { + "cell_type": "markdown", + "id": "a8e76abb", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y(0)=x(2)w(0)+x(1)w(1)+x(0)w(2)=\\beta_0 \\alpha_0+0\\times\\alpha_1+0\\times\\alpha_2=\\alpha_0\\beta_0.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "3c8e17ec", + "metadata": { + "editable": true + }, + "source": [ + "## Rewriting as dot products\n", + "\n", + "If we now flip the filter/weight vector, with the following term as a typical example" + ] + }, + { + "cell_type": "markdown", + "id": "965e447e", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y(0)=x(2)w(0)+x(1)w(1)+x(0)w(2)=x(2)\\tilde{w}(2)+x(1)\\tilde{w}(1)+x(0)\\tilde{w}(0),\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "53e47b06", + "metadata": { + "editable": true + }, + "source": [ + "with $\\tilde{w}(0)=w(2)$, $\\tilde{w}(1)=w(1)$, and $\\tilde{w}(2)=w(0)$, we can then rewrite the above sum as a dot product of\n", + "$x(i:i+(m-1))\\tilde{w}$ for element $y(i)$, where $x(i:i+(m-1))$ is simply a patch of $\\boldsymbol{x}$ of size $m-1$.\n", + "\n", + "The padding $P$ we have introduced for the convolution stage is just\n", + "another hyperparameter which is introduced as part of the\n", + "architecture. Similarly, below we will also introduce another\n", + "hyperparameter called **Stride** $S$." + ] + }, + { + "cell_type": "markdown", + "id": "b7a66ef0", + "metadata": { + "editable": true + }, + "source": [ + "## Cross correlation\n", + "\n", + "In essentially all applications one uses what is called cross correlation instead of the standard convolution described above.\n", + "This means that multiplication is performed in the same direction and instead of the general expression we have discussed above (with infinite sums)" + ] + }, + { + "cell_type": "markdown", + "id": "bd4feb76", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y(i) = \\sum_{k=-\\infty}^{k=\\infty}w(k)x(i-k),\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "1b68f20e", + "metadata": { + "editable": true + }, + "source": [ + "we have now" + ] + }, + { + "cell_type": "markdown", + "id": "76daf815", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y(i) = \\sum_{k=-\\infty}^{k=\\infty}w(k)x(i+k).\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "79ff7b3c", + "metadata": { + "editable": true + }, + "source": [ + "Both TensorFlow and PyTorch (as well as our own code example below),\n", + "implement the last equation, although it is normally referred to as\n", + "convolution. The same padding rules and stride rules discussed below\n", + "apply to this expression as well.\n", + "\n", + "We leave it as an exercise for you to convince yourself that the example we have discussed till now, gives the same final expression using the last expression." + ] + }, + { + "cell_type": "markdown", + "id": "29705589", + "metadata": { + "editable": true + }, + "source": [ + "## Two-dimensional objects\n", "\n", "We are now ready to start studying the discrete convolutions relevant for convolutional neural networks.\n", "We often use convolutions over more than one dimension at a time. If\n", @@ -877,7 +1331,7 @@ }, { "cell_type": "markdown", - "id": "cf94b870", + "id": "8af7b9af", "metadata": { "editable": true }, @@ -889,7 +1343,7 @@ }, { "cell_type": "markdown", - "id": "bc772e6f", + "id": "860db2b8", "metadata": { "editable": true }, @@ -899,7 +1353,7 @@ }, { "cell_type": "markdown", - "id": "27cc1e68", + "id": "3eb3e476", "metadata": { "editable": true }, @@ -911,19 +1365,23 @@ }, { "cell_type": "markdown", - "id": "5da90b0b", + "id": "faf1c3be", "metadata": { "editable": true }, "source": [ - "Normally the latter is more straightforward to implement in a machine larning library since there is less variation in the range of values of $m$ and $n$.\n", + "Normally the latter is more straightforward to implement in a machine\n", + "larning library since there is less variation in the range of values\n", + "of $m$ and $n$.\n", "\n", - "Many deep learning libraries implement cross-correlation instead of convolution (although it is referred to s convolution)" + "As mentioned above, most deep learning libraries implement\n", + "cross-correlation instead of convolution (although it is referred to as\n", + "convolution)" ] }, { "cell_type": "markdown", - "id": "30c98ebe", + "id": "33a2839a", "metadata": { "editable": true }, @@ -935,12 +1393,12 @@ }, { "cell_type": "markdown", - "id": "93e1769f", + "id": "36a0a4cd", "metadata": { "editable": true }, "source": [ - "## CNNs in more detail\n", + "## CNNs in more detail, simple example\n", "\n", "Let assume we have an input matrix $X$ of dimensionality $3\\times 3$\n", "and a $2\\times 2$ filter $W$ given by the following matrices" @@ -948,7 +1406,7 @@ }, { "cell_type": "markdown", - "id": "b3ae8ad4", + "id": "c1d8190e", "metadata": { "editable": true }, @@ -962,7 +1420,7 @@ }, { "cell_type": "markdown", - "id": "1dd2b802", + "id": "d90132aa", "metadata": { "editable": true }, @@ -972,7 +1430,7 @@ }, { "cell_type": "markdown", - "id": "89afce18", + "id": "1bc5f98e", "metadata": { "editable": true }, @@ -985,7 +1443,7 @@ }, { "cell_type": "markdown", - "id": "074722d5", + "id": "125bab1b", "metadata": { "editable": true }, @@ -1000,7 +1458,7 @@ }, { "cell_type": "markdown", - "id": "fae17f03", + "id": "a1cf4eab", "metadata": { "editable": true }, @@ -1012,7 +1470,7 @@ }, { "cell_type": "markdown", - "id": "b172d52a", + "id": "7d134ea5", "metadata": { "editable": true }, @@ -1022,7 +1480,7 @@ }, { "cell_type": "markdown", - "id": "09099568", + "id": "b8b5ba3b", "metadata": { "editable": true }, @@ -1035,7 +1493,7 @@ }, { "cell_type": "markdown", - "id": "c2de9787", + "id": "067c8a61", "metadata": { "editable": true }, @@ -1046,7 +1504,7 @@ }, { "cell_type": "markdown", - "id": "e69bd8be", + "id": "7eabf42d", "metadata": { "editable": true }, @@ -1058,7 +1516,7 @@ }, { "cell_type": "markdown", - "id": "b1d226d4", + "id": "a722d6ed", "metadata": { "editable": true }, @@ -1068,7 +1526,7 @@ }, { "cell_type": "markdown", - "id": "c04b1ec9", + "id": "1719d3d2", "metadata": { "editable": true }, @@ -1083,7 +1541,7 @@ }, { "cell_type": "markdown", - "id": "c719cc67", + "id": "549a1cdd", "metadata": { "editable": true }, @@ -1093,7 +1551,7 @@ }, { "cell_type": "markdown", - "id": "6558bb23", + "id": "e8ad9923", "metadata": { "editable": true }, @@ -1105,7 +1563,7 @@ }, { "cell_type": "markdown", - "id": "3201df64", + "id": "c5d4600d", "metadata": { "editable": true }, @@ -1115,85 +1573,170 @@ }, { "cell_type": "markdown", - "id": "80f20ebd", + "id": "f10091e5", "metadata": { "editable": true }, "source": [ - "## Performing a discrete convolution with padding ([From Raschka et al](https://github.com/rasbt/machine-learning-book))\n", + "## The convolution stage\n", "\n", - "\n", - "\n", - "\n", - "

    Figure 1: A deep CNN

    \n", - "" + "The convolution stage, where we apply different filters $\\boldsymbol{W}$ in\n", + "order to reduce the dimensionality of an image, adds, in addition to\n", + "the weights and biases (to be trained by the back propagation\n", + "algorithm) that define the filters, two new hyperparameters, the so-called\n", + "**padding** $P$ and the stride $S$." ] }, { "cell_type": "markdown", - "id": "a6deca51", + "id": "8e54f0e7", "metadata": { "editable": true }, "source": [ - "## Performing a general discrete convolution ([From Raschka et al](https://github.com/rasbt/machine-learning-book))\n", + "## Finding the number of parameters\n", "\n", - "\n", - "\n", + "In the above example we have an input matrix of dimension $3\\times\n", + "3$. In general we call the input for an input volume and it is defined\n", + "by its width $H_1$, height $H_1$ and depth $D_1$. If we have the\n", + "standard three color channels $D_1=3$.\n", "\n", - "

    Figure 1: A deep CNN

    \n", - "" + "The above example has $W_1=H_1=3$ and $D_1=1$.\n", + "\n", + "When we introduce the filter we have the following additional hyperparameters\n", + "1. $K$ the number of filters. It is common to perform the convolution of the input several times since by experience shrinking the input too fast does not work well\n", + "\n", + "2. $F$ as the filter's spatial extent\n", + "\n", + "3. $S$ as the stride parameter\n", + "\n", + "4. $P$ as the padding parameter\n", + "\n", + "These parameters are defined by the architecture of the network and are not included in the training." ] }, { "cell_type": "markdown", - "id": "85768b9b", + "id": "6822b435", "metadata": { "editable": true }, "source": [ - "## Set of filters\n", + "## New image (or volume)\n", "\n", - "The collection of kernels/filters $W$ defining a discrete convolution has a shape\n", - "corresponding to some permutation of $(n, m, k_1, \\ldots, k_N)$, where" + "Acting with the filter on the input volume produces an output volume\n", + "which is defined by its width $W_2$, its height $H_2$ and its depth\n", + "$D_2$.\n", + "\n", + "These are defined by the following relations" ] }, { "cell_type": "markdown", - "id": "6dc5e06c", + "id": "4e6fef6f", "metadata": { "editable": true }, "source": [ "$$\n", - "\\begin{split}\n", - " n &\\equiv \\text{number of output feature maps},\\\\\n", - " m &\\equiv \\text{number of input feature maps},\\\\\n", - " k_j &\\equiv \\text{kernel size along axis $j$}.\n", - "\\end{split}\n", + "W_2 = \\frac{(W_1-F+2P)}{S}+1,\n", "$$" ] }, { "cell_type": "markdown", - "id": "59832260", + "id": "e06aef00", "metadata": { "editable": true }, "source": [ - "The following properties affect the output size $o_j$ of a convolutional layer\n", - "along axis $j$:\n", + "$$\n", + "H_2 = \\frac{(H_1-F+2P)}{S}+1,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "1ab90aac", + "metadata": { + "editable": true + }, + "source": [ + "and $D_2=K$." + ] + }, + { + "cell_type": "markdown", + "id": "87ac7fd6", + "metadata": { + "editable": true + }, + "source": [ + "## Parameters to train, common settings\n", "\n", - "1. $i_j$: input size along axis $j$,\n", + "With parameter sharing, the convolution involves thus for each filter $F\\times F\\times D_1$ weights plus one bias parameter.\n", "\n", - "2. $k_j$: kernel/filter size along axis $j$,\n", + "In total we have" + ] + }, + { + "cell_type": "markdown", + "id": "36b302f0", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\left(F\\times F\\times D_1)\\right) \\times K+(K\\mathrm{--biases}),\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "81eb0abf", + "metadata": { + "editable": true + }, + "source": [ + "parameters to train by back propagation.\n", "\n", - "3. stride (distance between two consecutive positions of the kernel/filter) along axis $j$,\n", + "It is common to let $K$ come in powers of $2$, that is $32$, $64$, $128$ etc.\n", "\n", - "4. zero padding (number of zeros concatenated at the beginning and at the end of an axis) along axis $j$.\n", + "**Common settings.**\n", "\n", - "For instance, the above examples shows a $2\\times 2$ kernel/filter $\\boldsymbol{W}$ applied to a $3 \\times 3$ input padded with a $0 \\times 0$\n", - "border of zeros using $1 \\times 1$ strides.\n", + "1. $\\begin{array}{c} F=3 & S=1 & P=1 \\end{array}$\n", + "\n", + "2. $\\begin{array}{c} F=5 & S=1 & P=2 \\end{array}$\n", + "\n", + "3. $\\begin{array}{c} F=5 & S=2 & P=\\mathrm{open} \\end{array}$\n", + "\n", + "4. $\\begin{array}{c} F=1 & S=1 & P=0 \\end{array}$" + ] + }, + { + "cell_type": "markdown", + "id": "340ead69", + "metadata": { + "editable": true + }, + "source": [ + "## Examples of CNN setups\n", + "\n", + "Let us assume we have an input volume $V$ given by an image of dimensionality\n", + "$32\\times 32 \\times 3$, that is three color channels and $32\\times 32$ pixels.\n", + "\n", + "We apply a filter of dimension $5\\times 5$ ten times with stride $S=1$ and padding $P=0$.\n", + "\n", + "The output volume is given by $(32-5)/1+1=28$, resulting in ten images\n", + "of dimensionality $28\\times 28\\times 3$.\n", + "\n", + "The total number of parameters to train for each filter is then\n", + "$5\\times 5\\times 3+1$, where the last parameter is the bias. This\n", + "gives us $76$ parameters for each filter, leading to a total of $760$\n", + "parameters for the ten filters.\n", + "\n", + "How many parameters will a filter of dimensionality $3\\times 3$\n", + "(adding color channels) result in if we produce $32$ new images? Use $S=1$ and $P=0$.\n", "\n", "Note that strides constitute a form of **subsampling**. As an alternative to\n", "being interpreted as a measure of how much the kernel/filter is translated, strides\n", @@ -1204,188 +1747,43 @@ }, { "cell_type": "markdown", - "id": "cd657bd8", + "id": "c09104a7", + "metadata": { + "editable": true + }, + "source": [ + "## Summarizing: Performing a general discrete convolution ([From Raschka et al](https://github.com/rasbt/machine-learning-book))\n", + "\n", + "\n", + "\n", + "\n", + "

    Figure 1: A deep CNN

    \n", + "" + ] + }, + { + "cell_type": "markdown", + "id": "c3ee6c93", "metadata": { "editable": true }, "source": [ "## Pooling\n", "\n", - "In addition to discrete convolutions themselves, {\\em pooling\\/} operations\n", + "In addition to discrete convolutions themselves, **pooling** operations\n", "make up another important building block in CNNs. Pooling operations reduce\n", "the size of feature maps by using some function to summarize subregions, such\n", "as taking the average or the maximum value.\n", "\n", "Pooling works by sliding a window across the input and feeding the content of\n", - "the window to a {\\em pooling function}. In some sense, pooling works very much\n", + "the window to a **pooling function**. In some sense, pooling works very much\n", "like a discrete convolution, but replaces the linear combination described by\n", - "the kernel with some other function. Poolin\n", - "provides an example for average pooling, and \n", - "does the same for max pooling.\n", - "\n", - "The following properties affect the output size $o_j$ of a pooling layer\n", - "along axis $j$:\n", - "\n", - "1. $i_j$: input size along axis $j$,\n", - "\n", - "2. $k_j$: pooling window size along axis $j$,\n", - "\n", - "3. $s_j$: stride (distance between two consecutive positions of the pooling window) along axis $j$.\n", - "\n", - "The analysis of the relationship between convolutional layer properties is eased\n", - "by the fact that they don't interact across axes, i.e., the choice of kernel\n", - "size, stride and zero padding along axis $j$ only affects the output size of\n", - "axis $j$. Because of that, we will focus on the following simplified\n", - "setting:\n", - "\n", - "1. 2-D discrete convolutions ($N = 2$),\n", - "\n", - "2. square inputs ($i_1 = i_2 = i$),\n", - "\n", - "3. square kernel size ($k_1 = k_2 = k$),\n", - "\n", - "4. same strides along both axes ($s_1 = s_2 = s$),\n", - "\n", - "5. same zero padding along both axes ($p_1 = p_2 = p$).\n", - "\n", - "This facilitates the analysis and the visualization, but keep in mind that the\n", - "results outlined here also generalize to the N-D and non-square cases." + "the kernel with some other function." ] }, { "cell_type": "markdown", - "id": "c67f706e", - "metadata": { - "editable": true - }, - "source": [ - "## No zero padding, unit strides\n", - "\n", - "The simplest case to analyze is when the kernel just slides across every\n", - "position of the input (i.e., $s = 1$ and $p = 0$).\n", - "\n", - "For any $i$ and $k$, and for $s = 1$ and $p = 0$," - ] - }, - { - "cell_type": "markdown", - "id": "eea17ea9", - "metadata": { - "editable": true - }, - "source": [ - "$$\n", - "o = (i - k) + 1.\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "id": "d4b06ff9", - "metadata": { - "editable": true - }, - "source": [ - "## Zero padding, unit strides\n", - "\n", - "To factor in zero padding (i.e., only restricting to $s = 1$), let's consider\n", - "its effect on the effective input size: padding with $p$ zeros changes the\n", - "effective input size from $i$ to $i + 2p$. In the general case, we can infer the following\n", - "relationship\n", - "\n", - "For any $i$, $k$ and $p$, and for $s = 1$," - ] - }, - { - "cell_type": "markdown", - "id": "2e9e9c2b", - "metadata": { - "editable": true - }, - "source": [ - "$$\n", - "o = (i - k) + 2p + 1.\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "id": "a4dc79e5", - "metadata": { - "editable": true - }, - "source": [ - "## Half (same) padding\n", - "\n", - "Having the output size be the same as the input size (i.e., $o = i$) can be a\n", - "desirable property:\n", - "\n", - "For any $i$ and for $k$ odd ($k = 2n + 1, \\quad n \\in \\mathbb{N}$), $s = 1$ and\n", - "$p = \\lfloor k / 2 \\rfloor = n$," - ] - }, - { - "cell_type": "markdown", - "id": "ebb9e07e", - "metadata": { - "editable": true - }, - "source": [ - "$$\n", - "\\begin{split}\n", - " o &= i + 2 \\lfloor k / 2 \\rfloor - (k - 1) \\\\\n", - " &= i + 2n - 2n \\\\\n", - " &= i.\n", - "\\end{split}\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "id": "b9efae27", - "metadata": { - "editable": true - }, - "source": [ - "## Full padding\n", - "\n", - "While convolving a kernel generally decreases the output size with\n", - "respect to the input size, sometimes the opposite is required. This can be\n", - "achieved with proper zero padding:\n", - "\n", - "For any $i$ and $k$, and for $p = k - 1$ and $s = 1$," - ] - }, - { - "cell_type": "markdown", - "id": "d4b668df", - "metadata": { - "editable": true - }, - "source": [ - "$$\n", - "\\begin{split}\n", - " o &= i + 2(k - 1) - (k - 1) \\\\\n", - " &= i + (k - 1).\n", - "\\end{split}\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "id": "cc2ad8e0", - "metadata": { - "editable": true - }, - "source": [ - "This is sometimes referred to as full padding, because in this\n", - "setting every possible partial or complete superimposition of the kernel on the\n", - "input feature map is taken into account." - ] - }, - { - "cell_type": "markdown", - "id": "1621bad7", + "id": "33f5dc6c", "metadata": { "editable": true }, @@ -1397,29 +1795,12 @@ "consists in splitting the input in (usually non-overlapping) patches and\n", "outputting the maximum value of each patch. Other kinds of pooling exist, e.g.,\n", "mean or average pooling, which all share the same idea of aggregating the input\n", - "locally by applying a non-linearity to the content of some patches.\n", - "\n", - "Since pooling does not involve\n", - "zero padding, the relationship describing the general case is as follows:\n", - "\n", - "For any $i$, $k$ and $s$," + "locally by applying a non-linearity to the content of some patches." ] }, { "cell_type": "markdown", - "id": "c84460b8", - "metadata": { - "editable": true - }, - "source": [ - "$$\n", - "o = \\left\\lfloor \\frac{i - k}{s} \\right\\rfloor + 1.\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "id": "cb274711", + "id": "8a792d17", "metadata": { "editable": true }, @@ -1435,7 +1816,608 @@ }, { "cell_type": "markdown", - "id": "2df1cecc", + "id": "ccba7ad4", + "metadata": { + "editable": true + }, + "source": [ + "## Building convolutional neural networks in Tensorflow and Keras\n", + "\n", + "As discussed above, CNNs are neural networks built from the assumption that the inputs\n", + "to the network are 2D images. This is important because the number of features or pixels in images\n", + "grows very fast with the image size, and an enormous number of weights and biases are needed in order to build an accurate network. \n", + "\n", + "As before, we still have our input, a hidden layer and an output. What's novel about convolutional networks\n", + "are the **convolutional** and **pooling** layers stacked in pairs between the input and the hidden layer.\n", + "In addition, the data is no longer represented as a 2D feature matrix, instead each input is a number of 2D\n", + "matrices, typically 1 for each color dimension (Red, Green, Blue)." + ] + }, + { + "cell_type": "markdown", + "id": "13645c6d", + "metadata": { + "editable": true + }, + "source": [ + "## Setting it up\n", + "\n", + "It means that to represent the entire\n", + "dataset of images, we require a 4D matrix or **tensor**. This tensor has the dimensions:" + ] + }, + { + "cell_type": "markdown", + "id": "7dda54d3", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "(n_{inputs},\\, n_{pixels, width},\\, n_{pixels, height},\\, depth) .\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "1912abac", + "metadata": { + "editable": true + }, + "source": [ + "## The MNIST dataset again\n", + "\n", + "The MNIST dataset consists of grayscale images with a pixel size of\n", + "$28\\times 28$, meaning we require $28 \\times 28 = 724$ weights to each\n", + "neuron in the first hidden layer.\n", + "\n", + "If we were to analyze images of size $128\\times 128$ we would require\n", + "$128 \\times 128 = 16384$ weights to each neuron. Even worse if we were\n", + "dealing with color images, as most images are, we have an image matrix\n", + "of size $128\\times 128$ for each color dimension (Red, Green, Blue),\n", + "meaning 3 times the number of weights $= 49152$ are required for every\n", + "single neuron in the first hidden layer." + ] + }, + { + "cell_type": "markdown", + "id": "92d33009", + "metadata": { + "editable": true + }, + "source": [ + "## Strong correlations\n", + "\n", + "Images typically have strong local correlations, meaning that a small\n", + "part of the image varies little from its neighboring regions. If for\n", + "example we have an image of a blue car, we can roughly assume that a\n", + "small blue part of the image is surrounded by other blue regions.\n", + "\n", + "Therefore, instead of connecting every single pixel to a neuron in the\n", + "first hidden layer, as we have previously done with deep neural\n", + "networks, we can instead connect each neuron to a small part of the\n", + "image (in all 3 RGB depth dimensions). The size of each small area is\n", + "fixed, and known as a [receptive](https://en.wikipedia.org/wiki/Receptive_field)." + ] + }, + { + "cell_type": "markdown", + "id": "afdc6174", + "metadata": { + "editable": true + }, + "source": [ + "## Layers of a CNN\n", + "\n", + "The layers of a convolutional neural network arrange neurons in 3D: width, height and depth. \n", + "The input image is typically a square matrix of depth 3. \n", + "\n", + "A **convolution** is performed on the image which outputs\n", + "a 3D volume of neurons. The weights to the input are arranged in a number of 2D matrices, known as **filters**.\n", + "\n", + "Each filter slides along the input image, taking the dot product\n", + "between each small part of the image and the filter, in all depth\n", + "dimensions. This is then passed through a non-linear function,\n", + "typically the **Rectified Linear (ReLu)** function, which serves as the\n", + "activation of the neurons in the first convolutional layer. This is\n", + "further passed through a **pooling layer**, which reduces the size of the\n", + "convolutional layer, e.g. by taking the maximum or average across some\n", + "small regions, and this serves as input to the next convolutional\n", + "layer." + ] + }, + { + "cell_type": "markdown", + "id": "003f75b8", + "metadata": { + "editable": true + }, + "source": [ + "## Systematic reduction\n", + "\n", + "By systematically reducing the size of the input volume, through\n", + "convolution and pooling, the network should create representations of\n", + "small parts of the input, and then from them assemble representations\n", + "of larger areas. The final pooling layer is flattened to serve as\n", + "input to a hidden layer, such that each neuron in the final pooling\n", + "layer is connected to every single neuron in the hidden layer. This\n", + "then serves as input to the output layer, e.g. a softmax output for\n", + "classification." + ] + }, + { + "cell_type": "markdown", + "id": "6d323d3d", + "metadata": { + "editable": true + }, + "source": [ + "## Prerequisites: Collect and pre-process data" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "f386213c", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# import necessary packages\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from sklearn import datasets\n", + "\n", + "\n", + "# ensure the same random numbers appear every time\n", + "np.random.seed(0)\n", + "\n", + "# display images in notebook\n", + "%matplotlib inline\n", + "plt.rcParams['figure.figsize'] = (12,12)\n", + "\n", + "\n", + "# download MNIST dataset\n", + "digits = datasets.load_digits()\n", + "\n", + "# define inputs and labels\n", + "inputs = digits.images\n", + "labels = digits.target\n", + "\n", + "# RGB images have a depth of 3\n", + "# our images are grayscale so they should have a depth of 1\n", + "inputs = inputs[:,:,:,np.newaxis]\n", + "\n", + "print(\"inputs = (n_inputs, pixel_width, pixel_height, depth) = \" + str(inputs.shape))\n", + "print(\"labels = (n_inputs) = \" + str(labels.shape))\n", + "\n", + "\n", + "# choose some random images to display\n", + "n_inputs = len(inputs)\n", + "indices = np.arange(n_inputs)\n", + "random_indices = np.random.choice(indices, size=5)\n", + "\n", + "for i, image in enumerate(digits.images[random_indices]):\n", + " plt.subplot(1, 5, i+1)\n", + " plt.axis('off')\n", + " plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')\n", + " plt.title(\"Label: %d\" % digits.target[random_indices[i]])\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "7a38d39a", + "metadata": { + "editable": true + }, + "source": [ + "## Importing Keras and Tensorflow" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "11ec9e2e", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "from tensorflow.keras import datasets, layers, models\n", + "from tensorflow.keras.layers import Input\n", + "from tensorflow.keras.models import Sequential #This allows appending layers to existing models\n", + "from tensorflow.keras.layers import Dense #This allows defining the characteristics of a particular layer\n", + "from tensorflow.keras import optimizers #This allows using whichever optimiser we want (sgd,adam,RMSprop)\n", + "from tensorflow.keras import regularizers #This allows using whichever regularizer we want (l1,l2,l1_l2)\n", + "from tensorflow.keras.utils import to_categorical #This allows using categorical cross entropy as the cost function\n", + "#from tensorflow.keras import Conv2D\n", + "#from tensorflow.keras import MaxPooling2D\n", + "#from tensorflow.keras import Flatten\n", + "\n", + "from sklearn.model_selection import train_test_split\n", + "\n", + "# representation of labels\n", + "labels = to_categorical(labels)\n", + "\n", + "# split into train and test data\n", + "# one-liner from scikit-learn library\n", + "train_size = 0.8\n", + "test_size = 1 - train_size\n", + "X_train, X_test, Y_train, Y_test = train_test_split(inputs, labels, train_size=train_size,\n", + " test_size=test_size)" + ] + }, + { + "cell_type": "markdown", + "id": "d725fcbe", + "metadata": { + "editable": true + }, + "source": [ + "## Running with Keras" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8d265c27", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "def create_convolutional_neural_network_keras(input_shape, receptive_field,\n", + " n_filters, n_neurons_connected, n_categories,\n", + " eta, lmbd):\n", + " model = Sequential()\n", + " model.add(layers.Conv2D(n_filters, (receptive_field, receptive_field), input_shape=input_shape, padding='same',\n", + " activation='relu', kernel_regularizer=regularizers.l2(lmbd)))\n", + " model.add(layers.MaxPooling2D(pool_size=(2, 2)))\n", + " model.add(layers.Flatten())\n", + " model.add(layers.Dense(n_neurons_connected, activation='relu', kernel_regularizer=regularizers.l2(lmbd)))\n", + " model.add(layers.Dense(n_categories, activation='softmax', kernel_regularizer=regularizers.l2(lmbd)))\n", + " \n", + " sgd = optimizers.SGD(lr=eta)\n", + " model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])\n", + " \n", + " return model\n", + "\n", + "epochs = 100\n", + "batch_size = 100\n", + "input_shape = X_train.shape[1:4]\n", + "receptive_field = 3\n", + "n_filters = 10\n", + "n_neurons_connected = 50\n", + "n_categories = 10\n", + "\n", + "eta_vals = np.logspace(-5, 1, 7)\n", + "lmbd_vals = np.logspace(-5, 1, 7)" + ] + }, + { + "cell_type": "markdown", + "id": "3f66294b", + "metadata": { + "editable": true + }, + "source": [ + "## Final part" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "52313979", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "CNN_keras = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)\n", + " \n", + "for i, eta in enumerate(eta_vals):\n", + " for j, lmbd in enumerate(lmbd_vals):\n", + " CNN = create_convolutional_neural_network_keras(input_shape, receptive_field,\n", + " n_filters, n_neurons_connected, n_categories,\n", + " eta, lmbd)\n", + " CNN.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=0)\n", + " scores = CNN.evaluate(X_test, Y_test)\n", + " \n", + " CNN_keras[i][j] = CNN\n", + " \n", + " print(\"Learning rate = \", eta)\n", + " print(\"Lambda = \", lmbd)\n", + " print(\"Test accuracy: %.3f\" % scores[1])\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "id": "c0ae00f7", + "metadata": { + "editable": true + }, + "source": [ + "## Final visualization" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d28b398b", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# visual representation of grid search\n", + "# uses seaborn heatmap, could probably do this in matplotlib\n", + "import seaborn as sns\n", + "\n", + "sns.set()\n", + "\n", + "train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))\n", + "test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))\n", + "\n", + "for i in range(len(eta_vals)):\n", + " for j in range(len(lmbd_vals)):\n", + " CNN = CNN_keras[i][j]\n", + "\n", + " train_accuracy[i][j] = CNN.evaluate(X_train, Y_train)[1]\n", + " test_accuracy[i][j] = CNN.evaluate(X_test, Y_test)[1]\n", + "\n", + " \n", + "fig, ax = plt.subplots(figsize = (10, 10))\n", + "sns.heatmap(train_accuracy, annot=True, ax=ax, cmap=\"viridis\")\n", + "ax.set_title(\"Training Accuracy\")\n", + "ax.set_ylabel(\"$\\eta$\")\n", + "ax.set_xlabel(\"$\\lambda$\")\n", + "plt.show()\n", + "\n", + "fig, ax = plt.subplots(figsize = (10, 10))\n", + "sns.heatmap(test_accuracy, annot=True, ax=ax, cmap=\"viridis\")\n", + "ax.set_title(\"Test Accuracy\")\n", + "ax.set_ylabel(\"$\\eta$\")\n", + "ax.set_xlabel(\"$\\lambda$\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "957d569f", + "metadata": { + "editable": true + }, + "source": [ + "## The CIFAR01 data set\n", + "\n", + "The CIFAR10 dataset contains 60,000 color images in 10 classes, with\n", + "6,000 images in each class. The dataset is divided into 50,000\n", + "training images and 10,000 testing images. The classes are mutually\n", + "exclusive and there is no overlap between them." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "875738c6", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "\n", + "from tensorflow.keras import datasets, layers, models\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# We import the data set\n", + "(train_images, train_labels), (test_images, test_labels) = datasets.cifar10.load_data()\n", + "\n", + "# Normalize pixel values to be between 0 and 1 by dividing by 255. \n", + "train_images, test_images = train_images / 255.0, test_images / 255.0" + ] + }, + { + "cell_type": "markdown", + "id": "3d02bafa", + "metadata": { + "editable": true + }, + "source": [ + "## Verifying the data set\n", + "\n", + "To verify that the dataset looks correct, let's plot the first 25 images from the training set and display the class name below each image." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f6fa2efe", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',\n", + " 'dog', 'frog', 'horse', 'ship', 'truck']\n", + "plt.figure(figsize=(10,10))\n", + "for i in range(25):\n", + " plt.subplot(5,5,i+1)\n", + " plt.xticks([])\n", + " plt.yticks([])\n", + " plt.grid(False)\n", + " plt.imshow(train_images[i], cmap=plt.cm.binary)\n", + " # The CIFAR labels happen to be arrays, \n", + " # which is why you need the extra index\n", + " plt.xlabel(class_names[train_labels[i][0]])\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "7c5c4a79", + "metadata": { + "editable": true + }, + "source": [ + "## Set up the model\n", + "\n", + "The 6 lines of code below define the convolutional base using a common pattern: a stack of Conv2D and MaxPooling2D layers.\n", + "\n", + "As input, a CNN takes tensors of shape (image_height, image_width, color_channels), ignoring the batch size. If you are new to these dimensions, color_channels refers to (R,G,B). In this example, you will configure our CNN to process inputs of shape (32, 32, 3), which is the format of CIFAR images. You can do this by passing the argument input_shape to our first layer." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "0849002f", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "model = models.Sequential()\n", + "model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))\n", + "model.add(layers.MaxPooling2D((2, 2)))\n", + "model.add(layers.Conv2D(64, (3, 3), activation='relu'))\n", + "model.add(layers.MaxPooling2D((2, 2)))\n", + "model.add(layers.Conv2D(64, (3, 3), activation='relu'))\n", + "\n", + "# Let's display the architecture of our model so far.\n", + "\n", + "model.summary()" + ] + }, + { + "cell_type": "markdown", + "id": "42a62b52", + "metadata": { + "editable": true + }, + "source": [ + "You can see that the output of every Conv2D and MaxPooling2D layer is a 3D tensor of shape (height, width, channels). The width and height dimensions tend to shrink as you go deeper in the network. The number of output channels for each Conv2D layer is controlled by the first argument (e.g., 32 or 64). Typically, as the width and height shrink, you can afford (computationally) to add more output channels in each Conv2D layer." + ] + }, + { + "cell_type": "markdown", + "id": "be17ccdf", + "metadata": { + "editable": true + }, + "source": [ + "## Add Dense layers on top\n", + "\n", + "To complete our model, you will feed the last output tensor from the\n", + "convolutional base (of shape (4, 4, 64)) into one or more Dense layers\n", + "to perform classification. Dense layers take vectors as input (which\n", + "are 1D), while the current output is a 3D tensor. First, you will\n", + "flatten (or unroll) the 3D output to 1D, then add one or more Dense\n", + "layers on top. CIFAR has 10 output classes, so you use a final Dense\n", + "layer with 10 outputs and a softmax activation." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0a2841f0", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "model.add(layers.Flatten())\n", + "model.add(layers.Dense(64, activation='relu'))\n", + "model.add(layers.Dense(10))\n", + "Here's the complete architecture of our model.\n", + "\n", + "model.summary()" + ] + }, + { + "cell_type": "markdown", + "id": "f70f18a5", + "metadata": { + "editable": true + }, + "source": [ + "As you can see, our (4, 4, 64) outputs were flattened into vectors of shape (1024) before going through two Dense layers." + ] + }, + { + "cell_type": "markdown", + "id": "8ab1a8ab", + "metadata": { + "editable": true + }, + "source": [ + "## Compile and train the model" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "04d76fd5", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "model.compile(optimizer='adam',\n", + " loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n", + " metrics=['accuracy'])\n", + "\n", + "history = model.fit(train_images, train_labels, epochs=10, \n", + " validation_data=(test_images, test_labels))" + ] + }, + { + "cell_type": "markdown", + "id": "5c0392e2", + "metadata": { + "editable": true + }, + "source": [ + "## Finally, evaluate the model" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "2e869c20", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "plt.plot(history.history['accuracy'], label='accuracy')\n", + "plt.plot(history.history['val_accuracy'], label = 'val_accuracy')\n", + "plt.xlabel('Epoch')\n", + "plt.ylabel('Accuracy')\n", + "plt.ylim([0.5, 1])\n", + "plt.legend(loc='lower right')\n", + "\n", + "test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)\n", + "\n", + "print(test_acc)" + ] + }, + { + "cell_type": "markdown", + "id": "66a5e54d", "metadata": { "editable": true }, @@ -1447,6 +2429,8 @@ "the codebase and fit a CNN that yields a 99% accuracy on the 28x28\n", "MNIST dataset within reasonable time.\n", "\n", + "**The codes here were developed by Eric Reber and Gregor Kajda during spring 2023.**\n", + "\n", "The CNN is compatible with all schedulers, cost functions and\n", "activation functions discussed in constructing our neural network\n", "codes.\n", @@ -1466,7 +2450,7 @@ }, { "cell_type": "markdown", - "id": "2fe7b160", + "id": "af73a2bd", "metadata": { "editable": true }, @@ -1490,7 +2474,7 @@ }, { "cell_type": "markdown", - "id": "374dc966", + "id": "9975f54f", "metadata": { "editable": true }, @@ -1512,8 +2496,8 @@ }, { "cell_type": "code", - "execution_count": 1, - "id": "f24a2752", + "execution_count": 13, + "id": "660130ae", "metadata": { "collapsed": false, "editable": true @@ -1654,7 +2638,7 @@ }, { "cell_type": "markdown", - "id": "56000947", + "id": "9f8cf94f", "metadata": { "editable": true }, @@ -1666,8 +2650,8 @@ }, { "cell_type": "code", - "execution_count": 2, - "id": "2d38e983", + "execution_count": 14, + "id": "273ba0b1", "metadata": { "collapsed": false, "editable": true @@ -1680,7 +2664,7 @@ }, { "cell_type": "markdown", - "id": "16b58251", + "id": "154e5891", "metadata": { "editable": true }, @@ -1690,8 +2674,8 @@ }, { "cell_type": "code", - "execution_count": 3, - "id": "e58294f2", + "execution_count": 15, + "id": "8034d8f2", "metadata": { "collapsed": false, "editable": true @@ -1713,7 +2697,7 @@ }, { "cell_type": "markdown", - "id": "ce286b2b", + "id": "f47f75c6", "metadata": { "editable": true }, @@ -1728,8 +2712,8 @@ }, { "cell_type": "code", - "execution_count": 4, - "id": "682d7896", + "execution_count": 16, + "id": "35db1625", "metadata": { "collapsed": false, "editable": true @@ -1776,7 +2760,7 @@ }, { "cell_type": "markdown", - "id": "7b8c38d0", + "id": "446a3378", "metadata": { "editable": true }, @@ -1790,8 +2774,8 @@ }, { "cell_type": "code", - "execution_count": 5, - "id": "da49e588", + "execution_count": 17, + "id": "2df9d962", "metadata": { "collapsed": false, "editable": true @@ -1812,7 +2796,7 @@ }, { "cell_type": "markdown", - "id": "bcaa5376", + "id": "bf534f8c", "metadata": { "editable": true }, @@ -1828,8 +2812,8 @@ }, { "cell_type": "code", - "execution_count": 6, - "id": "68127b5d", + "execution_count": 18, + "id": "a1faf180", "metadata": { "collapsed": false, "editable": true @@ -1888,7 +2872,7 @@ }, { "cell_type": "markdown", - "id": "5d8d2751", + "id": "ace037b0", "metadata": { "editable": true }, @@ -1904,8 +2888,8 @@ }, { "cell_type": "code", - "execution_count": 7, - "id": "11b5c33a", + "execution_count": 19, + "id": "4ab94623", "metadata": { "collapsed": false, "editable": true @@ -1926,7 +2910,7 @@ }, { "cell_type": "markdown", - "id": "9e9909d9", + "id": "156dff4f", "metadata": { "editable": true }, @@ -1943,7 +2927,7 @@ }, { "cell_type": "markdown", - "id": "2d6847cc", + "id": "d4ec39fa", "metadata": { "editable": true }, @@ -1955,42 +2939,42 @@ }, { "cell_type": "markdown", - "id": "7b01aca1", + "id": "f84a7f10", "metadata": { "editable": true }, "source": [ - "Here, f and g are the two functions on which we want to perform an\n", + "Here, $f$ and $g$ are the two functions on which we want to perform an\n", "operation. The outcome of the convolution operation is represented by\n", - "$(f \\ast g)$, and it is derived by sliding the function g over f and\n", + "$(f \\ast g)$, and it is derived by sliding the function $g$ over $f$ and\n", "computing the integral of their product at each position. If both\n", "functions are continuous, convolution takes the form shown\n", - "above. However, if we discretize both f and g, the convolution\n", - "operation will take the form of a sum between the elements of f and g:" + "above. However, if we discretize both $f$ and $g$, the convolution\n", + "operation will take the form of a sum between the elements of $f$ and $g$:" ] }, { "cell_type": "markdown", - "id": "c5164022", + "id": "e4dec2b6", "metadata": { "editable": true }, "source": [ "$$\n", - "(f \\ast g)[n]=\\sum_{m=0}^{n-1} f[m] g[n-m].\n", + "(f \\ast g)[n]=\\sum_{m=0}^{n-1} f(m) g(n-m).\n", "$$" ] }, { "cell_type": "markdown", - "id": "c64787b1", + "id": "2840d48f", "metadata": { "editable": true }, "source": [ "The key idea we utilize to extract the information contained in an\n", - "image is to slide an $m \\times n$ matrix *g* over an $m \\times n$\n", - "matrix *f*. In our case, *f* represents the image, while *g*\n", + "image is to slide an $m \\times n$ matrix $g$ over an $m \\times n$\n", + "matrix $f$. In our case, $f$ represents the image, while $g$\n", "represents the kernel, oftentimes called a filter. However, since our\n", "convolution will be a two-dimensional variant, we need to extend our\n", "mathematical formula with an additional summation:" @@ -1998,19 +2982,19 @@ }, { "cell_type": "markdown", - "id": "e79a7cc9", + "id": "e8e58c03", "metadata": { "editable": true }, "source": [ "$$\n", - "(f \\ast g)[i, j]\\sum_{m=0}^{M-1}\\sum_{n=0}^{N-1} f[m,n] g[i-m, j-n].\n", + "(f \\ast g)(i, j)\\sum_{m=0}^{M-1}\\sum_{n=0}^{N-1} f(m,n) g(i-m, j-n).\n", "$$" ] }, { "cell_type": "markdown", - "id": "089e493f", + "id": "11ea612a", "metadata": { "editable": true }, @@ -2023,12 +3007,12 @@ "appropriate kernels for specific tasks.\n", "\n", "To give you an example of how 2D convolution works in practice,\n", - "suppose we have an image *f* of dimension $6 \\times 6$" + "suppose we have an image $f$ of dimension $6 \\times 6$" ] }, { "cell_type": "markdown", - "id": "61e3dd34", + "id": "836da562", "metadata": { "editable": true }, @@ -2047,19 +3031,19 @@ }, { "cell_type": "markdown", - "id": "63b4d2e4", + "id": "2b3bb8f1", "metadata": { "editable": true }, "source": [ - "and a $3 \\times 3$ kernel *g* called a low-pass filter. Note that the\n", + "and a $3 \\times 3$ kernel $g$ called a low-pass filter. Note that the\n", "kernel is usually rotated by 180 degrees during convolution, however\n", "this has no effect on this kernel." ] }, { "cell_type": "markdown", - "id": "0eef7505", + "id": "3f7c208c", "metadata": { "editable": true }, @@ -2076,20 +3060,20 @@ }, { "cell_type": "markdown", - "id": "e243595e", + "id": "3d8b79f4", "metadata": { "editable": true }, "source": [ "In order to filter the image, we have to extract a $3 \\times 3$\n", - "element from the upper left corner of *f*, and perform element-wise\n", + "element from the upper left corner of $f$, and perform element-wise\n", "multiplication of the extracted image pixels with the elements of the\n", - "kernel *g*:" + "kernel $g$:" ] }, { "cell_type": "markdown", - "id": "686f332f", + "id": "7e21a80b", "metadata": { "editable": true }, @@ -2112,45 +3096,45 @@ "\\frac{9}{9} & \\frac{5}{9} & \\frac{9}{9} \\\\\n", "\\frac{1}{9} & \\frac{5}{9} & \\frac{9}{9} \\\\\n", "\\end {bmatrix}\n", - "= \\textbf{A}\n", + "= \\boldsymbol{A}\n", "$$" ] }, { "cell_type": "markdown", - "id": "eb78894f", + "id": "93aab016", "metadata": { "editable": true }, "source": [ - "Then, following the multiplication, we summarize all the elements of the resulting matrix A:" + "Then, following the multiplication, we summarize all the elements of the resulting matrix $\\boldsymbol{A}$:" ] }, { "cell_type": "markdown", - "id": "c9c1becb", + "id": "4e0b557b", "metadata": { "editable": true }, "source": [ "$$\n", - "(f \\ast g)[0, 0]= \\sum_{i=0}^{2} \\sum_{j=0}^{2} a_{i,j} = 5\n", + "(f \\ast g)(0, 0)= \\sum_{i=0}^{2} \\sum_{j=0}^{2} a_{i,j} = 5,\n", "$$" ] }, { "cell_type": "markdown", - "id": "3c417ed5", + "id": "391389d2", "metadata": { "editable": true }, "source": [ - "Which corresponds to the first element of the filtered image $(f \\ast g)$.\n", + "which corresponds to the first element of the filtered image $(f \\ast g)$.\n", "\n", - "Here we use a stride of 1, a parameter denoted *s* which describes how\n", - "many indexes we move the kernel *g* to the right before repeating the\n", + "Here we use a stride of $S=1$, a parameter denoted $S$ which describes how\n", + "many indexes we move the kernel $g$ to the right before repeating the\n", "calculations above for the next $3 \\times 3$ element of the image\n", - "*f*. It is usually presumed that *s*=1, however, larger values for *s*\n", + "$f$. It is usually presumed that $S=1$, however, larger values for $S$\n", "can be used to reduce the dimentionality of the filtered image such\n", "that the convolution operation is more computationally efficient. In\n", "the context of a convolutional neural network, this will become very\n", @@ -2161,7 +3145,7 @@ }, { "cell_type": "markdown", - "id": "826fbbfb", + "id": "2948c748", "metadata": { "editable": true }, @@ -2179,38 +3163,43 @@ }, { "cell_type": "markdown", - "id": "77fd48f4", + "id": "59ccebe4", "metadata": { "editable": true }, "source": [ - "The result is markedly smaller in shape than the original image. This occurs when using convolution without first padding the image with additional columns and rows, allowing us to keep the original image shape after sliding the kernel over the image.\n", - "How many rows and columns we wish to pad the image with depends strictly on the shape of the kernel, as we wish to pad the image with *r* additional rows and *c* additional columns." + "The result is markedly smaller in shape than the original image. This\n", + "occurs when using convolution without first padding the image with\n", + "additional columns and rows, allowing us to keep the original image\n", + "shape after sliding the kernel over the image. How many rows and\n", + "columns we wish to pad the image with depends strictly on the shape of\n", + "the kernel, as we wish to pad the image with $r$ additional rows and\n", + "$c$ additional columns." ] }, { "cell_type": "markdown", - "id": "643942af", + "id": "3ac3ba4b", "metadata": { "editable": true }, "source": [ "$$\n", - "r =\\lfloor \\frac{kernel\\ height}{2} \\rfloor \\cdot 2 \\\\\n", - "c =\\lfloor \\frac{kernel\\ width}{2} \\rfloor \\cdot 2\n", + "r =\\lfloor \\frac{\\mathrm{kernel height}}{2} \\rfloor \\cdot 2 \\\\\n", + "c =\\lfloor \\frac{\\mathrm{kernel width}}{2} \\rfloor \\cdot 2\n", "$$" ] }, { "cell_type": "markdown", - "id": "2f12dad3", + "id": "60d7d729", "metadata": { "editable": true }, "source": [ - "Note the notation $\\lfloor \\frac{kernel width}{2} \\rfloor$ means that\n", + "Note the notation $\\lfloor \\frac{\\mathrm{kernel width}}{2} \\rfloor$ means that\n", "we floor the result of the division, meaning we round down to a whole\n", - "number in case $\\frac{kernel width}{2}$ results in a floating point\n", + "number in case $\\frac{\\mathrm{kernel width}}{2}$ results in a floating point\n", "number.\n", "\n", "Using those simple equations, we find out by how much we have to\n", @@ -2227,7 +3216,7 @@ }, { "cell_type": "markdown", - "id": "deafb1c4", + "id": "0f223085", "metadata": { "editable": true }, @@ -2242,25 +3231,26 @@ "0 & 8 & 1 & 6 & 4 & 2 & 2 & 0 \\\\\n", "0 & 1 & 0 & 5 & 7 & 8 & 2 & 0 \\\\\n", "0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\\n", - "\n", - "\\end{bmatrix}\n", + "\\end{bmatrix}.\n", "$$" ] }, { "cell_type": "markdown", - "id": "c37c52a4", + "id": "d2a919ef", "metadata": { "editable": true }, "source": [ - "Below we have provided code that demonstrates padding and convolution. As you will see when we run the code, the size of the image will remain unchanged when using padding.~" + "Below we have provided code that demonstrates padding and\n", + "convolution. As you will see when we run the code, the size of the\n", + "image will remain unchanged when using padding.~" ] }, { "cell_type": "code", - "execution_count": 8, - "id": "07ad670f", + "execution_count": 20, + "id": "98158090", "metadata": { "collapsed": false, "editable": true @@ -2342,20 +3332,21 @@ }, { "cell_type": "markdown", - "id": "d2f0a30c", + "id": "e386b5f9", "metadata": { "editable": true }, "source": [ - "Fun fact: When filtering images, you will see that convolution involves rotating the kernel by 180 degrees. \n", - "However, this is not the case when applying convolution in a CNN, where the same operation not rotated by 180 degrees is called \n", - "cross-correlation." + "Fun fact: When filtering images, you will see that convolution\n", + "involves rotating the kernel by 180 degrees. However, this is not the\n", + "case when applying convolution in a CNN, where the same operation that is not\n", + "rotated by 180 degrees is called cross-correlation, which is normally implemented in most libraries." ] }, { "cell_type": "code", - "execution_count": 9, - "id": "69913a2b", + "execution_count": 21, + "id": "b196835c", "metadata": { "collapsed": false, "editable": true @@ -2382,7 +3373,7 @@ }, { "cell_type": "markdown", - "id": "e9edd1cd", + "id": "c598de0f", "metadata": { "editable": true }, @@ -2402,17 +3393,15 @@ }, { "cell_type": "code", - "execution_count": 10, - "id": "7fed274d", + "execution_count": 22, + "id": "1dc54fc6", "metadata": { "collapsed": false, "editable": true }, "outputs": [], "source": [ - "%matplotlib inline\n", - "\n", - "# Now an example using a real image and first a gaussian low-pass filter and then a sobel filter\n", + "# Now an example using a real image and first a gaussian low-pass filter and then a Sobel filter\n", "import numpy as np\n", "import imageio.v3 as imageio\n", "import matplotlib.pyplot as plt\n", @@ -2455,7 +3444,7 @@ }, { "cell_type": "markdown", - "id": "adeee788", + "id": "8fb0749c", "metadata": { "editable": true }, @@ -2469,8 +3458,8 @@ }, { "cell_type": "code", - "execution_count": 11, - "id": "04b2d320", + "execution_count": 23, + "id": "fedc419c", "metadata": { "collapsed": false, "editable": true @@ -2511,7 +3500,7 @@ }, { "cell_type": "markdown", - "id": "910594b6", + "id": "c8370f0c", "metadata": { "editable": true }, @@ -2552,8 +3541,8 @@ }, { "cell_type": "code", - "execution_count": 12, - "id": "b0d8dd57", + "execution_count": 24, + "id": "d97d0055", "metadata": { "collapsed": false, "editable": true @@ -2839,7 +3828,7 @@ }, { "cell_type": "markdown", - "id": "a25330d9", + "id": "0dfbd0fc", "metadata": { "editable": true }, @@ -2862,7 +3851,7 @@ }, { "cell_type": "markdown", - "id": "942832c8", + "id": "739539f5", "metadata": { "editable": true }, @@ -2874,8 +3863,8 @@ }, { "cell_type": "code", - "execution_count": 13, - "id": "b0f7f972", + "execution_count": 25, + "id": "fb42cade", "metadata": { "collapsed": false, "editable": true @@ -2923,7 +3912,7 @@ }, { "cell_type": "markdown", - "id": "55f5198b", + "id": "2037fef5", "metadata": { "editable": true }, @@ -2945,7 +3934,7 @@ }, { "cell_type": "markdown", - "id": "4b1e24f8", + "id": "ee0b7034", "metadata": { "editable": true }, @@ -2966,8 +3955,8 @@ }, { "cell_type": "code", - "execution_count": 14, - "id": "5e1d05ba", + "execution_count": 26, + "id": "37fdedf3", "metadata": { "collapsed": false, "editable": true @@ -3129,7 +4118,7 @@ }, { "cell_type": "markdown", - "id": "a7030c39", + "id": "dbd0a7e6", "metadata": { "editable": true }, @@ -3149,8 +4138,8 @@ }, { "cell_type": "code", - "execution_count": 15, - "id": "8e5e4192", + "execution_count": 27, + "id": "3196697f", "metadata": { "collapsed": false, "editable": true @@ -3213,7 +4202,7 @@ }, { "cell_type": "markdown", - "id": "92e17b3a", + "id": "7af01737", "metadata": { "editable": true }, @@ -3236,8 +4225,8 @@ }, { "cell_type": "code", - "execution_count": 16, - "id": "0a4d96ef", + "execution_count": 28, + "id": "652a07a7", "metadata": { "collapsed": false, "editable": true @@ -3465,7 +4454,7 @@ }, { "cell_type": "markdown", - "id": "53b36589", + "id": "e7caf94b", "metadata": { "editable": true }, @@ -3485,8 +4474,8 @@ }, { "cell_type": "code", - "execution_count": 17, - "id": "5c4ed6ee", + "execution_count": 29, + "id": "fc2c6d43", "metadata": { "collapsed": false, "editable": true @@ -3790,7 +4779,7 @@ }, { "cell_type": "markdown", - "id": "dbdf86e4", + "id": "20e3247d", "metadata": { "editable": true }, @@ -3802,8 +4791,8 @@ }, { "cell_type": "code", - "execution_count": 18, - "id": "a8c7ea94", + "execution_count": 30, + "id": "7841dc47", "metadata": { "collapsed": false, "editable": true @@ -4338,7 +5327,7 @@ }, { "cell_type": "markdown", - "id": "ecf83f56", + "id": "2cba848d", "metadata": { "editable": true }, @@ -4356,8 +5345,8 @@ }, { "cell_type": "code", - "execution_count": 19, - "id": "ed5a9db7", + "execution_count": 31, + "id": "869f4ea1", "metadata": { "collapsed": false, "editable": true @@ -4370,7 +5359,7 @@ }, { "cell_type": "markdown", - "id": "7ab8be5d", + "id": "b7d283b2", "metadata": { "editable": true }, @@ -4385,8 +5374,8 @@ }, { "cell_type": "code", - "execution_count": 20, - "id": "e88fcc22", + "execution_count": 32, + "id": "795b9ebb", "metadata": { "collapsed": false, "editable": true @@ -4412,7 +5401,7 @@ }, { "cell_type": "markdown", - "id": "e09e4222", + "id": "ba9a8be1", "metadata": { "editable": true }, @@ -4430,13 +5419,13 @@ "5. The output layer, with softmax as its activation function and 10 nodes. We use 10 nodes because we will be using a dataset with 10 classes.\n", "\n", "Now, before we can train the model, we need to load in our data. We\n", - "will use the MNIST dataset and use 10000 $28 \\times 28 images$." + "will use the MNIST dataset and use 10000 $28 \\times 28$ images." ] }, { "cell_type": "code", - "execution_count": 21, - "id": "4a179b95", + "execution_count": 33, + "id": "bb4c98fa", "metadata": { "collapsed": false, "editable": true @@ -4471,7 +5460,7 @@ }, { "cell_type": "markdown", - "id": "878aeb2a", + "id": "97a9484f", "metadata": { "editable": true }, @@ -4489,8 +5478,8 @@ }, { "cell_type": "code", - "execution_count": 22, - "id": "acf28e60", + "execution_count": 34, + "id": "539baa72", "metadata": { "collapsed": false, "editable": true @@ -4518,7 +5507,7 @@ }, { "cell_type": "markdown", - "id": "9efc80ac", + "id": "c7bb6ac0", "metadata": { "editable": true }, @@ -4546,8 +5535,8 @@ }, { "cell_type": "code", - "execution_count": 23, - "id": "395f8612", + "execution_count": 35, + "id": "93e706ae", "metadata": { "collapsed": false, "editable": true @@ -4616,7 +5605,7 @@ }, { "cell_type": "markdown", - "id": "5ab9df29", + "id": "c72032ee", "metadata": { "editable": true }, @@ -4634,7 +5623,7 @@ }, { "cell_type": "markdown", - "id": "c5ffd1f4", + "id": "4ae81157", "metadata": { "editable": true }, @@ -4657,8 +5646,8 @@ }, { "cell_type": "code", - "execution_count": 24, - "id": "44a0b4c1", + "execution_count": 36, + "id": "4d2d2032", "metadata": { "collapsed": false, "editable": true @@ -4689,7 +5678,7 @@ }, { "cell_type": "markdown", - "id": "3ea702d5", + "id": "0b61f114", "metadata": { "editable": true }, @@ -4719,7 +5708,7 @@ }, { "cell_type": "markdown", - "id": "b46fc7ea", + "id": "953ca041", "metadata": { "editable": true }, @@ -4729,8 +5718,8 @@ }, { "cell_type": "code", - "execution_count": 25, - "id": "f9af0bae", + "execution_count": 37, + "id": "6f5e8589", "metadata": { "collapsed": false, "editable": true @@ -4780,7 +5769,7 @@ }, { "cell_type": "markdown", - "id": "f17f130c", + "id": "ba379cac", "metadata": { "editable": true }, @@ -4801,7 +5790,7 @@ }, { "cell_type": "markdown", - "id": "9f3ac382", + "id": "951e4f08", "metadata": { "editable": true }, @@ -4811,8 +5800,8 @@ }, { "cell_type": "code", - "execution_count": 26, - "id": "b71a2395", + "execution_count": 38, + "id": "b23c5570", "metadata": { "collapsed": false, "editable": true @@ -4833,7 +5822,7 @@ }, { "cell_type": "markdown", - "id": "36bdd533", + "id": "ecbe9144", "metadata": { "editable": true }, @@ -4849,381 +5838,6 @@ "to remain cognizant of this fact when utilizing FFT as the primary\n", "optimization technique." ] - }, - { - "cell_type": "markdown", - "id": "ebd1818e", - "metadata": { - "editable": true - }, - "source": [ - "## Building convolutional neural networks in Tensorflow and Keras\n", - "\n", - "As discussed above, CNNs are neural networks built from the assumption that the inputs\n", - "to the network are 2D images. This is important because the number of features or pixels in images\n", - "grows very fast with the image size, and an enormous number of weights and biases are needed in order to build an accurate network. \n", - "\n", - "As before, we still have our input, a hidden layer and an output. What's novel about convolutional networks\n", - "are the **convolutional** and **pooling** layers stacked in pairs between the input and the hidden layer.\n", - "In addition, the data is no longer represented as a 2D feature matrix, instead each input is a number of 2D\n", - "matrices, typically 1 for each color dimension (Red, Green, Blue)." - ] - }, - { - "cell_type": "markdown", - "id": "f5dd4522", - "metadata": { - "editable": true - }, - "source": [ - "## Setting it up\n", - "\n", - "It means that to represent the entire\n", - "dataset of images, we require a 4D matrix or **tensor**. This tensor has the dimensions:" - ] - }, - { - "cell_type": "markdown", - "id": "feffdf95", - "metadata": { - "editable": true - }, - "source": [ - "$$\n", - "(n_{inputs},\\, n_{pixels, width},\\, n_{pixels, height},\\, depth) .\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "id": "736a847b", - "metadata": { - "editable": true - }, - "source": [ - "## The MNIST dataset again\n", - "\n", - "The MNIST dataset consists of grayscale images with a pixel size of\n", - "$28\\times 28$, meaning we require $28 \\times 28 = 724$ weights to each\n", - "neuron in the first hidden layer.\n", - "\n", - "If we were to analyze images of size $128\\times 128$ we would require\n", - "$128 \\times 128 = 16384$ weights to each neuron. Even worse if we were\n", - "dealing with color images, as most images are, we have an image matrix\n", - "of size $128\\times 128$ for each color dimension (Red, Green, Blue),\n", - "meaning 3 times the number of weights $= 49152$ are required for every\n", - "single neuron in the first hidden layer." - ] - }, - { - "cell_type": "markdown", - "id": "e676336d", - "metadata": { - "editable": true - }, - "source": [ - "## Strong correlations\n", - "\n", - "Images typically have strong local correlations, meaning that a small\n", - "part of the image varies little from its neighboring regions. If for\n", - "example we have an image of a blue car, we can roughly assume that a\n", - "small blue part of the image is surrounded by other blue regions.\n", - "\n", - "Therefore, instead of connecting every single pixel to a neuron in the\n", - "first hidden layer, as we have previously done with deep neural\n", - "networks, we can instead connect each neuron to a small part of the\n", - "image (in all 3 RGB depth dimensions). The size of each small area is\n", - "fixed, and known as a [receptive](https://en.wikipedia.org/wiki/Receptive_field)." - ] - }, - { - "cell_type": "markdown", - "id": "714b9066", - "metadata": { - "editable": true - }, - "source": [ - "## Layers of a CNN\n", - "\n", - "The layers of a convolutional neural network arrange neurons in 3D: width, height and depth. \n", - "The input image is typically a square matrix of depth 3. \n", - "\n", - "A **convolution** is performed on the image which outputs\n", - "a 3D volume of neurons. The weights to the input are arranged in a number of 2D matrices, known as **filters**.\n", - "\n", - "Each filter slides along the input image, taking the dot product\n", - "between each small part of the image and the filter, in all depth\n", - "dimensions. This is then passed through a non-linear function,\n", - "typically the **Rectified Linear (ReLu)** function, which serves as the\n", - "activation of the neurons in the first convolutional layer. This is\n", - "further passed through a **pooling layer**, which reduces the size of the\n", - "convolutional layer, e.g. by taking the maximum or average across some\n", - "small regions, and this serves as input to the next convolutional\n", - "layer." - ] - }, - { - "cell_type": "markdown", - "id": "8a5afdec", - "metadata": { - "editable": true - }, - "source": [ - "## Systematic reduction\n", - "\n", - "By systematically reducing the size of the input volume, through\n", - "convolution and pooling, the network should create representations of\n", - "small parts of the input, and then from them assemble representations\n", - "of larger areas. The final pooling layer is flattened to serve as\n", - "input to a hidden layer, such that each neuron in the final pooling\n", - "layer is connected to every single neuron in the hidden layer. This\n", - "then serves as input to the output layer, e.g. a softmax output for\n", - "classification." - ] - }, - { - "cell_type": "markdown", - "id": "f1344e2f", - "metadata": { - "editable": true - }, - "source": [ - "## Prerequisites: Collect and pre-process data" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "df65e942", - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "# import necessary packages\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from sklearn import datasets\n", - "\n", - "\n", - "# ensure the same random numbers appear every time\n", - "np.random.seed(0)\n", - "\n", - "# display images in notebook\n", - "%matplotlib inline\n", - "plt.rcParams['figure.figsize'] = (12,12)\n", - "\n", - "\n", - "# download MNIST dataset\n", - "digits = datasets.load_digits()\n", - "\n", - "# define inputs and labels\n", - "inputs = digits.images\n", - "labels = digits.target\n", - "\n", - "# RGB images have a depth of 3\n", - "# our images are grayscale so they should have a depth of 1\n", - "inputs = inputs[:,:,:,np.newaxis]\n", - "\n", - "print(\"inputs = (n_inputs, pixel_width, pixel_height, depth) = \" + str(inputs.shape))\n", - "print(\"labels = (n_inputs) = \" + str(labels.shape))\n", - "\n", - "\n", - "# choose some random images to display\n", - "n_inputs = len(inputs)\n", - "indices = np.arange(n_inputs)\n", - "random_indices = np.random.choice(indices, size=5)\n", - "\n", - "for i, image in enumerate(digits.images[random_indices]):\n", - " plt.subplot(1, 5, i+1)\n", - " plt.axis('off')\n", - " plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')\n", - " plt.title(\"Label: %d\" % digits.target[random_indices[i]])\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "f74d4498", - "metadata": { - "editable": true - }, - "source": [ - "## Importing Keras and Tensorflow" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "id": "ae32fe9c", - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "from tensorflow.keras import datasets, layers, models\n", - "from tensorflow.keras.layers import Input\n", - "from tensorflow.keras.models import Sequential #This allows appending layers to existing models\n", - "from tensorflow.keras.layers import Dense #This allows defining the characteristics of a particular layer\n", - "from tensorflow.keras import optimizers #This allows using whichever optimiser we want (sgd,adam,RMSprop)\n", - "from tensorflow.keras import regularizers #This allows using whichever regularizer we want (l1,l2,l1_l2)\n", - "from tensorflow.keras.utils import to_categorical #This allows using categorical cross entropy as the cost function\n", - "#from tensorflow.keras import Conv2D\n", - "#from tensorflow.keras import MaxPooling2D\n", - "#from tensorflow.keras import Flatten\n", - "\n", - "from sklearn.model_selection import train_test_split\n", - "\n", - "# representation of labels\n", - "labels = to_categorical(labels)\n", - "\n", - "# split into train and test data\n", - "# one-liner from scikit-learn library\n", - "train_size = 0.8\n", - "test_size = 1 - train_size\n", - "X_train, X_test, Y_train, Y_test = train_test_split(inputs, labels, train_size=train_size,\n", - " test_size=test_size)" - ] - }, - { - "cell_type": "markdown", - "id": "9d13359f", - "metadata": { - "editable": true - }, - "source": [ - "## Running with Keras" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "c07fca46", - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "def create_convolutional_neural_network_keras(input_shape, receptive_field,\n", - " n_filters, n_neurons_connected, n_categories,\n", - " eta, lmbd):\n", - " model = Sequential()\n", - " model.add(layers.Conv2D(n_filters, (receptive_field, receptive_field), input_shape=input_shape, padding='same',\n", - " activation='relu', kernel_regularizer=regularizers.l2(lmbd)))\n", - " model.add(layers.MaxPooling2D(pool_size=(2, 2)))\n", - " model.add(layers.Flatten())\n", - " model.add(layers.Dense(n_neurons_connected, activation='relu', kernel_regularizer=regularizers.l2(lmbd)))\n", - " model.add(layers.Dense(n_categories, activation='softmax', kernel_regularizer=regularizers.l2(lmbd)))\n", - " \n", - " sgd = optimizers.SGD(learning_rate=eta)\n", - " model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])\n", - " \n", - " return model\n", - "\n", - "epochs = 100\n", - "batch_size = 100\n", - "input_shape = X_train.shape[1:4]\n", - "receptive_field = 3\n", - "n_filters = 10\n", - "n_neurons_connected = 50\n", - "n_categories = 10\n", - "\n", - "eta_vals = np.logspace(-5, 1, 7)\n", - "lmbd_vals = np.logspace(-5, 1, 7)" - ] - }, - { - "cell_type": "markdown", - "id": "2b40b4c9", - "metadata": { - "editable": true - }, - "source": [ - "## Final part" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "id": "e5ef4326", - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "CNN_keras = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)\n", - " \n", - "for i, eta in enumerate(eta_vals):\n", - " for j, lmbd in enumerate(lmbd_vals):\n", - " CNN = create_convolutional_neural_network_keras(input_shape, receptive_field,\n", - " n_filters, n_neurons_connected, n_categories,\n", - " eta, lmbd)\n", - " CNN.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=0)\n", - " scores = CNN.evaluate(X_test, Y_test)\n", - " \n", - " CNN_keras[i][j] = CNN\n", - " \n", - " print(\"Learning rate = \", eta)\n", - " print(\"Lambda = \", lmbd)\n", - " print(\"Test accuracy: %.3f\" % scores[1])\n", - " print()" - ] - }, - { - "cell_type": "markdown", - "id": "e3bc74cc", - "metadata": { - "editable": true - }, - "source": [ - "## Final visualization" - ] - }, - { - "cell_type": "code", - "execution_count": 31, - "id": "75932707", - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "# visual representation of grid search\n", - "# uses seaborn heatmap, could probably do this in matplotlib\n", - "import seaborn as sns\n", - "\n", - "sns.set()\n", - "\n", - "train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))\n", - "test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))\n", - "\n", - "for i in range(len(eta_vals)):\n", - " for j in range(len(lmbd_vals)):\n", - " CNN = CNN_keras[i][j]\n", - "\n", - " train_accuracy[i][j] = CNN.evaluate(X_train, Y_train)[1]\n", - " test_accuracy[i][j] = CNN.evaluate(X_test, Y_test)[1]\n", - "\n", - " \n", - "fig, ax = plt.subplots(figsize = (10, 10))\n", - "sns.heatmap(train_accuracy, annot=True, ax=ax, cmap=\"viridis\")\n", - "ax.set_title(\"Training Accuracy\")\n", - "ax.set_ylabel(\"$\\eta$\")\n", - "ax.set_xlabel(\"$\\lambda$\")\n", - "plt.show()\n", - "\n", - "fig, ax = plt.subplots(figsize = (10, 10))\n", - "sns.heatmap(test_accuracy, annot=True, ax=ax, cmap=\"viridis\")\n", - "ax.set_title(\"Test Accuracy\")\n", - "ax.set_ylabel(\"$\\eta$\")\n", - "ax.set_xlabel(\"$\\lambda$\")\n", - "plt.show()" - ] } ], "metadata": {}, diff --git a/doc/src/week44/figslides/photo.jpg b/doc/src/week44/figslides/photo.jpg new file mode 100755 index 0000000000000000000000000000000000000000..426220598eebeccb6f8b1e9d69c907834c1593f2 GIT binary patch literal 197326 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
    &ZDg04@ekb60I_P+FH}$^B};iG0Y< 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�*#-~RnW9=5qg9+`S&1X@ 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
    i!Kl;^U#LjK)3|2wJe{oNIaA>HxZ3c7H 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