Table of Contents

8. Autoenkodery

8.1. Wprowadzenie

W niniejszym rozdziale omówimy działanie autoenkoderów, które są rodzajem sieci neuronowych wykorzystywanych do uczenia się reprezentacji danych wejściowych w sposób nienadzorowany. Autoenkodery składają się z dwóch głównych części: enkodera i dekodera. Enkoder przekształca dane wejściowe w reprezentację o niższej wymiarowości (tzw. warstwa latentna), natomiast dekoder rekonstruuje dane wejściowe z tej reprezentacji.

8.1.1. Enkoder

Enkoder jest odpowiedzialny za przekształcenie danych wejściowych w reprezentację o niższej wymiarowości. Proces ten polega na stopniowym zmniejszaniu liczby neuronów w kolejnych warstwach sieci, co prowadzi do uzyskania tzw. warstwy latentnej (bottleneck layer). Warstwa ta zawiera skompresowaną reprezentację danych wejściowych, która zachowuje istotne cechy i wzorce.

8.1.2. Dekoder

Dekoder jest odpowiedzialny za rekonstrukcję danych wejściowych z reprezentacji warstwy latentnej. Proces ten polega na stopniowym zwiększaniu liczby neuronów w kolejnych warstwach sieci, aż do uzyskania wymiarów zgodnych z danymi wejściowymi. Celem dekodera jest odtworzenie danych wejściowych w możliwie jak najwierniejszy sposób.

8.1.3. Warstwa latentna

Warstwa latentna (bottleneck layer) jest kluczowym elementem autoenkodera. Zawiera skompresowaną reprezentację danych wejściowych, która jest następnie wykorzystywana przez dekoder do rekonstrukcji danych. Warstwa ta wymusza na sieci nauczenie się istotnych cech danych wejściowych, eliminując jednocześnie szum i nieistotne informacje.

8.2. Autoenkodery z warstwami gęstymi

Poniżej przedstawiono przykład implementacji autoenkodera z warstwami gęstymi w C#. Model ten składa się z enkodera, warstwy latentnej oraz dekodera. W przykładzie zastosowano funkcje aktywacji Leaky ReLU, Softsign oraz Tanh.

internal class AutoencoderDenseModel(int bottleneckDim, SeededRandom? random, string? modelFilePath = null)
    : BaseModel<float[,], float[,]>(new MeanSquaredErrorLoss(MseReduction.ElementMean), random, modelFilePath)
{
    private Layer<float[,], float[,]>? _bottleneckLayer;
    private Layer<float[,], float[,]>? _firstDecoderLayer;

    protected override LayerListBuilder<float[,], float[,]> CreateLayerListBuilder()
    {
        ParamInitializer initializer = new GlorotInitializer(Random);

        return
            // Encoder
            AddLayer(new DenseLayer(178, new LeakyReLU2D(), initializer))
            .AddLayer(new DenseLayer(46, new LeakyReLU2D(), initializer))

            // Bottleneck
            .AddLayer(_bottleneckLayer = new DenseLayer(bottleneckDim, new Softsign(), initializer))

            // Decoder
            .AddLayer(_firstDecoderLayer = new DenseLayer(46, new LeakyReLU2D(), initializer))
            .AddLayer(new DenseLayer(178, new LeakyReLU2D(), initializer))
            .AddLayer(new DenseLayer(784, new Tanh2D(), initializer));
    }

    /// <summary>
    /// Gets the encoded representation (latent data) produced by the bottleneck layer of the model.
    /// </summary>
    /// <returns>
    /// A two-dimensional array of floating-point values representing the output of the bottleneck layer.
    /// </returns>
    /// <exception cref="InvalidOperationException">Thrown if the bottleneck layer output is not available.</exception>
    public float[,] GetEncodedRepresentation()
    {
        return _bottleneckLayer?.Output
            ?? throw new InvalidOperationException("Bottleneck layer output is not available.");
    }

    /// <summary>
    /// Forward encoded representation and return the decoded output. This can be used to visualize the output of the
    /// decoder part of the autoencoder based on randomly generated encoded data or to see how the decoder reconstructs
    /// the input data from the encoded (bottleneck) representation.
    /// </summary>
    public float[,] Decode(float[,] encoded)
    {
        // We need to pass the encoded data through the first decoder layer and then through the remaining layers of the model.

        if (_firstDecoderLayer is null)
            throw new InvalidOperationException("Decoder layer is not initialized.");

        return InferFromLayer(_firstDecoderLayer, encoded);
    }
}

Listing 8.1. Definicja modelu autoenkodera z warstwami gęstymi

Note

Powyższy kod w pełnej wersji znajduje się na GitHub.

8.3. Autoenkodery z warstwami konwolucyjnymi

internal class AutoencoderConvModel(int bottleneckDim, SeededRandom? random, string? modelFilePath = null)
    : BaseModel<float[,,,], float[,,,]>(new MeanSquaredErrorLoss4D(MseReduction.ElementMean), random, modelFilePath)
{

    private Layer<float[,], float[,]>? _bottleneckLayer;
    private Layer<float[,], float[,]>? _firstDecoderLayer;

    protected override LayerListBuilder<float[,,,], float[,,,]> CreateLayerListBuilder()
    {
        ParamInitializer initializer = new GlorotInitializer(Random);

        return
            // 1. Encoder
            // 1 * 28 * 28
            AddLayer(new Conv2DLayer(
                kernels: 32,
                kernelHeight: 3,
                kernelWidth: 3,
                activationFunction: new Tanh4D(),
                paramInitializer: initializer
            ))
            // 32 * 28 * 28
            .AddLayer(new MaxPooling2DLayer(2, 2))
            // 32 * 14 * 14
            .AddLayer(new FlattenLayer())

            // 2. Bottleneck
            // 32 * 14 * 14 = 6272
            .AddLayer(_bottleneckLayer = new DenseLayer(bottleneckDim, new Tanh2D(), initializer))

            // 3. Decoder
            // bottleneckDim
            .AddLayer(_firstDecoderLayer = new DenseLayer(32 * 14 * 14, new LeakyReLU2D(), initializer))
            // 32 * 14 * 14 = 6272 as a flattened representation
            .AddLayer(new UnflattenLayer(32, 14, 14))
            // 32 * 14 * 14
            .AddLayer(new Upsample2DLayer(2, 2))
            // 32 * 28 * 28
            .AddLayer(new Conv2DLayer(
                kernels: 1,
                kernelHeight: 3,
                kernelWidth: 3,
                activationFunction: new Tanh4D(),
                paramInitializer: initializer
            ));

        // 1 * 28 * 28 as output
    }

    /// <summary>
    /// Gets the encoded representation (latent data) produced by the bottleneck layer of the model.
    /// </summary>
    /// <returns>
    /// A two-dimensional array of floating-point values representing the output of the bottleneck layer.
    /// </returns>
    /// <exception cref="InvalidOperationException">Thrown if the bottleneck layer output is not available.</exception>
    public float[,] GetEncodedRepresentation()
    {
        return _bottleneckLayer?.Output
            ?? throw new InvalidOperationException("Bottleneck layer output is not available.");
    }

    /// <summary>
    /// Forward encoded representation and return the decoded output. This can be used to visualize the output of the
    /// decoder part of the autoencoder based on randomly generated encoded data or to see how the decoder reconstructs
    /// the input data from the encoded (bottleneck) representation.
    /// </summary>
    public float[,,,] Decode(float[,] encoded)
    {
        // We need to pass the encoded data through the first decoder layer and then through the remaining layers of the model.

        if (_firstDecoderLayer is null)
            throw new InvalidOperationException("Decoder layer is not initialized.");

        return InferFromLayer(_firstDecoderLayer, encoded);
    }
}

Listing 8.2. Definicja modelu autoenkodera z warstwami konwolucyjnymi

Note

Powyższy kod w pełnej wersji znajduje się na GitHub.


Created: 2026-05-15

Last modified: 2026-07-25

Title: 8. Autoenkodery

Tags: [C#] [Sieci neuronowe] [Biblioteka] [NeuralNetworks] [MNIST] [CNN] [Convolutional Neural Networks] [Autoencoders] [Autoenkodery]