Style Transfer

Experiments

Finetuning Content Loss

The content loss at a layer is defined as the MSE Loss between the features of the current image being optimized and the content image. Intuitively then it makes sense that the content loss would be most effective at conv_1 as this layer captures features that’re less abstract than downstream layers, less transformed, and closer to ‘true’ L2 pixel loss. This was my suspicion and it was confirmed. conv_1 loss converges much more quickly than loss at other layers. The following table shows the loss per layer per iteration.

Name 50100150200250300
Tubingen Conv1 0.0000150.0000080.0000080.0000080.0000080.000008
Tubingen Conv2 0.0284420.0028060.0005910.0001980.0000680.000026
Tubingen Conv3 0.2306520.115140.0783140.0617570.0545830.052213
Tubingen Conv4 0.7648960.3388390.2097980.1498720.1134880.089226
Tubingen Conv5 2.2712081.0215230.6801260.5225120.432447N/A
Falling Water Conv1 0.0000140.0000030.0000030.0000030.0000030.000003
Falling Water Conv5 3.7694312.0877761.5081891.206741.0230510.896233

As you can see loss for conv_1 converges within the first 100 iterations and results in a lower overall loss than all the other layers. The loss for deeper layers is higher as expected. Throughout the rest of the experiments we’ll use conv_1 for our content loss.

Results of Finetuning Content Loss

Noise Type 050100150200250300
Noise 1 Iteration 0 - Noise 1 Iteration 50 - Noise 1 Iteration 100 - Noise 1 Iteration 150 - Noise 1 Iteration 200 - Noise 1 Iteration 250 - Noise 1 Iteration 300 - Noise 1
Noise 2 Iteration 0 - Noise 2 Iteration 50 - Noise 2 Iteration 100 - Noise 2 Iteration 150 - Noise 2 Iteration 200 - Noise 2 Iteration 250 - Noise 2 Iteration 300 - Noise 2

Here is a table of loss.

Name 50100150200250300
Noise 1 0.0000140.0000030.0000030.0000030.0000030.000003
Noise 2 0.0000160.0000020.0000020.0000020.0000020.000002

The difference between the inputs was 1253 which makes sense as it’s just noise while their difference at the end was 3.41. Noise 1 had an L2 Loss of 2.5185 with ground truth while Noise 2 had an L2 Loss of 2.3263. Pretty good!

Texture Synthesis

Similar to content_loss we want to compute style loss as well. What is style loss? One idea is to use the Gram Matrix which is the correlation of two vectors in every dimension. This is just G=XXTG = XX^T. We then want to minimize the loss between our computed gram matrix and the gram matrix encoding the style of our style target. Unlike content where as we get deeper the L2 loss becomes less meaningful with style it’s certaintly possible for important style information to be encoded by deeper layers.

To find the ideal style loss I just ran texture synthesis over all possible combinations of layers to calculate style loss at. The below table summarizes the results.

Texture Synthesis Tuning

Note the leftmost column indicates which conv layers were being used ie 12 --> conv_1, conv_2

Conv Style Layers / Iteration Count 050100150250300
1 1_0 1_50 1_100 1_150 1_250 1_300
12 12_0 12_50 12_100 12_150 12_250 12_300
123 123_0 123_50 123_100 123_150 123_250 123_300
1234 1234_0 1234_50 1234_100 1234_150 1234_250 1234_300
12345 12345_0 12345_50 12345_100 12345_150 12345_250 12345_300
1235 1235_0 1235_50 1235_100 1235_150 1235_250 1235_300
124 124_0 124_50 124_100 124_150 124_250 124_300
1245 1245_0 1245_50 1245_100 1245_150 1245_250 1245_300
125 125_0 125_50 125_100 125_150 125_250 125_300
13 13_0 13_50 13_100 13_150 13_250 13_300
134 134_0 134_50 134_100 134_150 134_250 134_300
1345 1345_0 1345_50 1345_100 1345_150 1345_250 1345_300
135 135_0 135_50 135_100 135_150 135_250 135_300
14 14_0 14_50 14_100 14_150 14_250 14_300
145 145_0 145_50 145_100 145_150 145_250 145_300
15 15_0 15_50 15_100 15_150 15_250 15_300
2 2_0 2_50 2_100 2_150 2_250 2_300
23 23_0 23_50 23_100 23_150 23_250 23_300
234 234_0 234_50 234_100 234_150 234_250 234_300
2345 2345_0 2345_50 2345_100 2345_150 2345_250 2345_300
235 235_0 235_50 235_100 235_150 235_250 235_300
24 24_0 24_50 24_100 24_150 24_250 24_300
245 245_0 245_50 245_100 245_150 245_250 245_300
25 25_0 25_50 25_100 25_150 25_250 25_300
3 3_0 3_50 3_100 3_150 3_250 3_300
34 34_0 34_50 34_100 34_150 34_250 34_300
345 345_0 345_50 345_100 345_150 345_250 345_300
35 35_0 35_50 35_100 35_150 35_250 35_300
4 4_0 4_50 4_100 4_150 4_250 4_300
45 45_0 45_50 45_100 45_150 45_250 45_300
5 5_0 5_50 5_100 5_150 5_250 5_300

I ended up going with 1_3_4 just because I liked it. For reference, 1_3_4 had a style loss of 3.466813 after 300 iterations while 1_2_3_4_5 the initial default iteration had a loss of 14.167436 after 300 iterations. There were experiments with lower losses like 1_2_3_4 with 2.999 or 1_2_4 with 1.36 but I didn’t like the look as much.

I think that each layer captures more of the higher or lowever levels of style details which makes sense. As you go from top to bottom the details go from coarser to finer showing the effects of deeper layers.

Texture synthesis generated by calculating style loss at `conv_1`, `conv_3`, `conv_4`. After 300 iterations this resulted in a loss of `3.466813`
Texture synthesis generated by calculating style loss at `conv_1`, `conv_3`, `conv_4`. After 300 iterations this resulted in a loss of `3.466813`

Texture Synthesis Results

Similar to the content loss experiments above here is texture synthesis run on two noise patterns.

Noise Type 050100150200250300
Noise 2 Iteration 0 - Noise 2 Iteration 50 - Noise 2 Iteration 100 - Noise 2 Iteration 150 - Noise 2 Iteration 200 - Noise 2 Iteration 250 - Noise 2 Iteration 300 - Noise 2
Noise 3 Iteration 0 - Noise 3 Iteration 50 - Noise 3 Iteration 100 - Noise 3 Iteration 150 - Noise 3 Iteration 200 - Noise 3 Iteration 250 - Noise 3 Iteration 300 - Noise 3
Name 50100150200250300
Noise 1 37.57702316.58452610.5898947.1496635.048983.749736
Noise 2 38.90560216.39564510.2724877.0531434.7672463.256261

The L2 loss between the outputs was 307 which makes sense as there’s no reason for them to have a low pixel loss.

As you can see the texture differ in their layout but do indeed capture the starry night texture. Going forward I’ll use conv_1, conv_3, conv_4 to calculate style loss.

Hyperparameter Tuning

To figure out the hyper parameters for content and style weight I ran a search over params [1.e+00, 1.e+01, 1.e+02, 1.e+03, 1.e+04, 1.e+05, 1.e+06, 1.e+07, 1.e+08]

for style_weight and content_weight.

This generated a lot of images and a lot of optimizer instability at weird ratios. In retrospect it would’ve made more sense to have just do a search over the relative ratios of weight and style. That said, I would’ve never discovered the optimizer instability.

Below are some of the weird results.

I settled on relative ratios of content_weights = 1, style_weights = 1000000

Final Results

Below are the final results with all the previous experiments.

Content Initialization

frida_kahlo picasso starry_night the_scream
/src/assets/assgn4/experiment9/dancing /src/assets/assgn4/experiment9/dancing_frida_kahlo_300_Content_Initialization /src/assets/assgn4/experiment9/dancing_picasso_300_Content_Initialization /src/assets/assgn4/experiment9/dancing_starry_night_300_Content_Initialization /src/assets/assgn4/experiment9/dancing_the_scream_300_Content_Initialization
/src/assets/assgn4/experiment9/fallingwater /src/assets/assgn4/experiment9/fallingwater_frida_kahlo_300_Content_Initialization /src/assets/assgn4/experiment9/fallingwater_picasso_300_Content_Initialization /src/assets/assgn4/experiment9/fallingwater_starry_night_300_Content_Initialization /src/assets/assgn4/experiment9/fallingwater_the_scream_300_Content_Initialization
/src/assets/assgn4/experiment9/phipps /src/assets/assgn4/experiment9/phipps_frida_kahlo_300_Content_Initialization /src/assets/assgn4/experiment9/phipps_picasso_300_Content_Initialization /src/assets/assgn4/experiment9/phipps_starry_night_300_Content_Initialization /src/assets/assgn4/experiment9/phipps_the_scream_300_Content_Initialization
/src/assets/assgn4/experiment9/tubingen /src/assets/assgn4/experiment9/tubingen_frida_kahlo_300_Content_Initialization /src/assets/assgn4/experiment9/tubingen_picasso_300_Content_Initialization /src/assets/assgn4/experiment9/tubingen_starry_night_300_Content_Initialization /src/assets/assgn4/experiment9/tubingen_the_scream_300_Content_Initialization
/src/assets/assgn4/experiment9/wally /src/assets/assgn4/experiment9/wally_frida_kahlo_300_Content_Initialization /src/assets/assgn4/experiment9/wally_picasso_300_Content_Initialization /src/assets/assgn4/experiment9/wally_starry_night_300_Content_Initialization /src/assets/assgn4/experiment9/wally_the_scream_300_Content_Initialization

Noise Initialization

frida_kahlo picasso starry_night the_scream
/src/assets/assgn4/experiment9/dancing /src/assets/assgn4/experiment9/dancing_frida_kahlo_300_Noise_Initialization /src/assets/assgn4/experiment9/dancing_picasso_300_Noise_Initialization /src/assets/assgn4/experiment9/dancing_starry_night_300_Noise_Initialization /src/assets/assgn4/experiment9/dancing_the_scream_300_Noise_Initialization
/src/assets/assgn4/experiment9/fallingwater /src/assets/assgn4/experiment9/fallingwater_frida_kahlo_300_Noise_Initialization /src/assets/assgn4/experiment9/fallingwater_picasso_300_Noise_Initialization /src/assets/assgn4/experiment9/fallingwater_starry_night_300_Noise_Initialization /src/assets/assgn4/experiment9/fallingwater_the_scream_300_Noise_Initialization
/src/assets/assgn4/experiment9/phipps /src/assets/assgn4/experiment9/phipps_frida_kahlo_300_Noise_Initialization /src/assets/assgn4/experiment9/phipps_picasso_300_Noise_Initialization /src/assets/assgn4/experiment9/phipps_starry_night_300_Noise_Initialization /src/assets/assgn4/experiment9/phipps_the_scream_300_Noise_Initialization
/src/assets/assgn4/experiment9/tubingen /src/assets/assgn4/experiment9/tubingen_frida_kahlo_300_Noise_Initialization /src/assets/assgn4/experiment9/tubingen_picasso_300_Noise_Initialization /src/assets/assgn4/experiment9/tubingen_starry_night_300_Noise_Initialization /src/assets/assgn4/experiment9/tubingen_the_scream_300_Noise_Initialization
/src/assets/assgn4/experiment9/wally /src/assets/assgn4/experiment9/wally_frida_kahlo_300_Noise_Initialization /src/assets/assgn4/experiment9/wally_picasso_300_Noise_Initialization /src/assets/assgn4/experiment9/wally_starry_night_300_Noise_Initialization /src/assets/assgn4/experiment9/wally_the_scream_300_Noise_Initialization

Below are some of the weird results.

I found there was no difference in runtime which was bizarre. I also think that my noise strength was too strong for noise intilization. If I had run them longer than 300 iterations maybe the content loss would’ve gone down but I’m very happy with the content intilization ones.

Finally there are some of my own images.

Bells & Whistles

Other Images

I also ran my style transfer on images from previous assignments. I did a poor job cropping the matplotlib edges and so we get to see stylized matplotlib templates. More papers should have stylized matplotlib templates.

Poisson blended plane with Starry Night
Poisson blended plane with Starry Night
Poisson blend of the country side with Starry Night
Poisson blend of the country side with Starry Night
Cats generated with wassertein loss with Frida Kahlo style
Cats generated with wassertein loss with Frida Kahlo style
Cats generated with wassertein loss with Starry Night style
Cats generated with wassertein loss with Starry Night style

Adversarial Learning

Recently I was listening to the following podcast on the show Oxide & Friends about Adversarial Machine Learning. They talked about how they optimize their images for a dual loss of desired adversarial behaviour while also not getting stopped by safety filters. In a classic classification vision context this would mean optimizing for a model to say that an image is an apple when really it’s a banana.

Anyways, I had the idea from this that instead of treating loss as strictly positive I could optimize for two different things. In general I ran experiments where I subtracted content loss from later layers from my base layer with the effect that hopefully the optimizer would lead to images that have high loss for deeper features. My initial experiments I thought were a failure until after playing around with the parameters I began to see Falling Water emerge. Below are my results as well as their parameters.

All results were initially using conv3 as our negative content layer. The nubmer then indicates the following experiments:

1 --> 0.00001 content_weight=100 style_weight =1000000 
2 --> 0.0001 content_weight=100 style_weight =1000000 
3 --> 0.0001 content_weight=100 style_weight =1000000  now using conv_2 as our base layer
4 --> conv1 as base layer and conv5 as negative layer
5 --> conv1 as base layer and conv4, conv5 as negative layer

While not perfect the results indicate that you can do interesting things like penalizing certain details while emphasizing others. For instance, there seems to be more style transfer on the borders letting the house remain.

Results

Appendix

All tests were run with the following fixed rand seed

RAND_SEED = 152
torch.manual_seed(RAND_SEED)
random.seed(RAND_SEED