@@ -698,110 +698,40 @@ The Generator Module
698
698
********************
699
699
700
700
We begin by defining the generator module, which consists of a series of
701
- transposed 2D convolutions, batch normalizations and ReLU activation units. Like
702
- in Python, PyTorch here provides two APIs for model definition: a functional one
703
- where inputs are passed through successive functions, and a more object-oriented
704
- one where we build a ` ` Sequential` ` module containing the entire model as
705
- submodules. Let's see how our generator looks with either API, and you can
706
- decide for yourself which one you prefer. First, using ` ` Sequential` ` :
701
+ transposed 2D convolutions, batch normalizations and ReLU activation units.
702
+ We explicitly pass inputs (in a functional way) between modules in the
703
+ ` ` forward()` ` method of a module we define ourselves:
707
704
708
705
.. code-block:: cpp
709
706
710
- using namespace torch;
711
-
712
- nn::Sequential generator(
713
- // Layer 1
714
- nn::Conv2d(nn::Conv2dOptions(kNoiseSize, 256, 4)
715
- .with_bias(false)
716
- .transposed(true)),
717
- nn::BatchNorm(256),
718
- nn::Functional(torch::relu),
719
- // Layer 2
720
- nn::Conv2d(nn::Conv2dOptions(256, 128, 3)
721
- .stride(2)
722
- .padding(1)
723
- .with_bias(false)
724
- .transposed(true)),
725
- nn::BatchNorm(128),
726
- nn::Functional(torch::relu),
727
- // Layer 3
728
- nn::Conv2d(nn::Conv2dOptions(128, 64, 4)
729
- .stride(2)
730
- .padding(1)
731
- .with_bias(false)
732
- .transposed(true)),
733
- nn::BatchNorm(64),
734
- nn::Functional(torch::relu),
735
- // Layer 4
736
- nn::Conv2d(nn::Conv2dOptions(64, 1, 4)
737
- .stride(2)
738
- .padding(1)
739
- .with_bias(false)
740
- .transposed(true)),
741
- nn::Functional(torch::tanh));
742
-
743
- .. tip::
744
-
745
- A ` ` Sequential` ` module simply performs function composition. The output of
746
- the first submodule becomes the input of the second, the output of the third
747
- becomes the input of the fourth and so on.
748
-
749
- The particular modules chosen, like ` ` nn::Conv2d` ` and ` ` nn::BatchNorm` ` ,
750
- follows the structure outlined earlier. The ` ` kNoiseSize` ` constant determines
751
- the size of the input noise vector and is set to ` ` 100` ` . Notice also that we
752
- use the ` ` torch::nn::Functional` ` module for our activation functions, passing
753
- it ` ` torch::relu` ` for inner layers and ` ` torch::tanh` ` as the final activation.
754
- Hyperparameters were, of course, found via grad student descent.
755
-
756
- .. note::
757
-
758
- The Python frontend has one module for each activation function, like
759
- ` ` torch.nn.ReLU` ` or ` ` torch.nn.Tanh` ` . In C++, we instead only provide the
760
- ` ` Functional` ` module, to which you can pass any C++ function that will be
761
- called inside the ` ` Functional` ` 's ` ` forward()` ` method.
762
-
763
- .. attention::
764
-
765
- No grad students were harmed in the discovery of hyperparameters. They were
766
- fed Soylent regularly.
767
-
768
- For the second approach, we explicitly pass inputs (in a functional way) between
769
- modules in the ` ` forward()` ` method of a module we define ourselves:
770
-
771
- .. code-block:: cpp
772
-
773
- struct GeneratorImpl : nn::Module {
774
- GeneratorImpl(int kNoiseSize)
775
- : conv1(nn::Conv2dOptions(kNoiseSize, 256, 4)
776
- .with_bias(false)
777
- .transposed(true)),
707
+ struct DCGANGeneratorImpl : nn::Module {
708
+ DCGANGeneratorImpl(int kNoiseSize)
709
+ : conv1(nn::ConvTranspose2dOptions(kNoiseSize, 256, 4)
710
+ .bias(false)),
778
711
batch_norm1(256),
779
- conv2(nn::Conv2dOptions (256, 128, 3)
712
+ conv2(nn::ConvTranspose2dOptions (256, 128, 3)
780
713
.stride(2)
781
714
.padding(1)
782
- .with_bias(false)
783
- .transposed(true)),
715
+ .bias(false)),
784
716
batch_norm2(128),
785
- conv3(nn::Conv2dOptions (128, 64, 4)
717
+ conv3(nn::ConvTranspose2dOptions (128, 64, 4)
786
718
.stride(2)
787
719
.padding(1)
788
- .with_bias(false)
789
- .transposed(true)),
720
+ .bias(false)),
790
721
batch_norm3(64),
791
- conv4(nn::Conv2dOptions (64, 1, 4)
722
+ conv4(nn::ConvTranspose2dOptions (64, 1, 4)
792
723
.stride(2)
793
724
.padding(1)
794
- .with_bias(false)
795
- .transposed(true))
725
+ .bias(false))
796
726
{
797
727
// register_module() is needed if we want to use the parameters() method later on
798
728
register_module("conv1", conv1);
799
729
register_module("conv2", conv2);
800
730
register_module("conv3", conv3);
801
731
register_module("conv4", conv4);
802
732
register_module("batch_norm1", batch_norm1);
803
- register_module("batch_norm2", batch_norm1 );
804
- register_module("batch_norm3", batch_norm1 );
733
+ register_module("batch_norm2", batch_norm2 );
734
+ register_module("batch_norm3", batch_norm3 );
805
735
}
806
736
807
737
torch::Tensor forward(torch::Tensor x) {
@@ -812,25 +742,34 @@ modules in the ``forward()`` method of a module we define ourselves:
812
742
return x;
813
743
}
814
744
815
- nn::Conv2d conv1, conv2, conv3, conv4;
816
- nn::BatchNorm batch_norm1, batch_norm2, batch_norm3;
745
+ nn::ConvTranspose2d conv1, conv2, conv3, conv4;
746
+ nn::BatchNorm2d batch_norm1, batch_norm2, batch_norm3;
817
747
};
818
- TORCH_MODULE(Generator);
748
+ TORCH_MODULE(DCGANGenerator);
749
+
750
+ DCGANGenerator generator(kNoiseSize);
751
+
752
+ We can now invoke ` ` forward()` ` on the ` ` DCGANGenerator` ` to map a noise sample to an image.
753
+
754
+ The particular modules chosen, like ` ` nn::ConvTranspose2d` ` and ` ` nn::BatchNorm2d` ` ,
755
+ follows the structure outlined earlier. The ` ` kNoiseSize` ` constant determines
756
+ the size of the input noise vector and is set to ` ` 100` ` . Hyperparameters were,
757
+ of course, found via grad student descent.
819
758
820
- Generator generator;
759
+ .. attention::
821
760
822
- Whichever approach we use, we can now invoke ` ` forward() ` ` on the ` ` Generator ` ` to
823
- map a noise sample to an image .
761
+ No grad students were harmed in the discovery of hyperparameters. They were
762
+ fed Soylent regularly .
824
763
825
764
.. note::
826
765
827
766
A brief word on the way options are passed to built-in modules like ` ` Conv2d` `
828
767
in the C++ frontend: Every module has some required options, like the number
829
- of features for ` ` BatchNorm ` ` . If you only need to configure the required
768
+ of features for ` ` BatchNorm2d ` ` . If you only need to configure the required
830
769
options, you can pass them directly to the module's constructor, like
831
- ` ` BatchNorm (128)` ` or ` ` Dropout(0.5)` ` or ` ` Conv2d(8, 4, 2)` ` (for input
770
+ ` ` BatchNorm2d (128)` ` or ` ` Dropout(0.5)` ` or ` ` Conv2d(8, 4, 2)` ` (for input
832
771
channel count, output channel count, and kernel size). If, however, you need
833
- to modify other options, which are normally defaulted, such as ` ` with_bias ` `
772
+ to modify other options, which are normally defaulted, such as ` ` bias ` `
834
773
for ` ` Conv2d` ` , you need to construct and pass an *options* object. Every
835
774
module in the C++ frontend has an associated options struct, called
836
775
` ` ModuleOptions` ` where ` ` Module` ` is the name of the module, like
@@ -845,36 +784,42 @@ and activations. However, the convolutions are now regular ones instead of
845
784
transposed, and we use a leaky ReLU with an alpha value of 0.2 instead of a
846
785
vanilla ReLU. Also, the final activation becomes a Sigmoid, which squashes
847
786
values into a range between 0 and 1. We can then interpret these squashed values
848
- as the probabilities the discriminator assigns to images being real:
787
+ as the probabilities the discriminator assigns to images being real.
788
+
789
+ To build the discriminator, we will try something different: a ` Sequential` module.
790
+ Like in Python, PyTorch here provides two APIs for model definition: a functional one
791
+ where inputs are passed through successive functions (e.g. the generator module example),
792
+ and a more object-oriented one where we build a ` Sequential` module containing the
793
+ entire model as submodules. Using ` Sequential` , the discriminator would look like:
849
794
850
795
.. code-block:: cpp
851
796
852
797
nn::Sequential discriminator(
853
798
// Layer 1
854
799
nn::Conv2d(
855
- nn::Conv2dOptions(1, 64, 4).stride(2).padding(1).with_bias (false)),
856
- nn::Functional(torch::leaky_relu, 0.2),
800
+ nn::Conv2dOptions(1, 64, 4).stride(2).padding(1).bias (false)),
801
+ nn::LeakyReLU(nn::LeakyReLUOptions().negative_slope( 0.2) ),
857
802
// Layer 2
858
803
nn::Conv2d(
859
- nn::Conv2dOptions(64, 128, 4).stride(2).padding(1).with_bias (false)),
860
- nn::BatchNorm (128),
861
- nn::Functional(torch::leaky_relu, 0.2),
804
+ nn::Conv2dOptions(64, 128, 4).stride(2).padding(1).bias (false)),
805
+ nn::BatchNorm2d (128),
806
+ nn::LeakyReLU(nn::LeakyReLUOptions().negative_slope( 0.2) ),
862
807
// Layer 3
863
808
nn::Conv2d(
864
- nn::Conv2dOptions(128, 256, 4).stride(2).padding(1).with_bias (false)),
865
- nn::BatchNorm (256),
866
- nn::Functional(torch::leaky_relu, 0.2),
809
+ nn::Conv2dOptions(128, 256, 4).stride(2).padding(1).bias (false)),
810
+ nn::BatchNorm2d (256),
811
+ nn::LeakyReLU(nn::LeakyReLUOptions().negative_slope( 0.2) ),
867
812
// Layer 4
868
813
nn::Conv2d(
869
- nn::Conv2dOptions(256, 1, 3).stride(1).padding(0).with_bias (false)),
870
- nn::Functional(torch::sigmoid ));
814
+ nn::Conv2dOptions(256, 1, 3).stride(1).padding(0).bias (false)),
815
+ nn::Sigmoid( ));
871
816
872
- .. note::
817
+ .. tip::
818
+
819
+ A ` ` Sequential` ` module simply performs function composition. The output of
820
+ the first submodule becomes the input of the second, the output of the third
821
+ becomes the input of the fourth and so on.
873
822
874
- When the function we pass to ` ` Functional` ` takes more arguments than a single
875
- tensor, we can pass them to the ` ` Functional` ` constructor, which will forward
876
- them to each function call. For the leaky ReLU above, this means
877
- ` ` torch::leaky_relu(previous_output_tensor, 0.2)` ` is called.
878
823
879
824
Loading Data
880
825
------------
0 commit comments