Update.
authorFrancois Fleuret <francois@fleuret.org>
Thu, 23 Aug 2018 22:10:57 +0000 (00:10 +0200)
committerFrancois Fleuret <francois@fleuret.org>
Thu, 23 Aug 2018 22:10:57 +0000 (00:10 +0200)
ae_size.py
blacklab.jpg [new file with mode: 0644]
hallu-result.png [new file with mode: 0644]
hallu.py [new file with mode: 0755]

index 25ecc4a..8afb101 100755 (executable)
@@ -12,9 +12,9 @@ def minimal_input_size(w, layer_specs):
         return w
     else:
         kernel_size, stride = layer_specs[0]
-        w = math.ceil((w - kernel_size) / stride) + 1
-        w = minimal_input_size(w, layer_specs[1:])
-        return int((w - 1) * stride + kernel_size)
+        v = int(math.ceil((w - kernel_size) / stride)) + 1
+        v = minimal_input_size(v, layer_specs[1:])
+        return (v - 1) * stride + kernel_size
 
 ######################################################################
 
@@ -22,9 +22,10 @@ def minimal_input_size(w, layer_specs):
 
 if __name__ == "__main__":
 
-    layer_specs = [ (11, 5), (5, 4), (3, 2), (3, 2) ]
+    layer_specs = [ (17, 5), (5, 4), (3, 2), (3, 2) ]
 
     layers = []
+
     for kernel_size, stride in layer_specs:
         layers.append(nn.Conv2d(1, 1, kernel_size, stride))
 
diff --git a/blacklab.jpg b/blacklab.jpg
new file mode 100644 (file)
index 0000000..fd85537
Binary files /dev/null and b/blacklab.jpg differ
diff --git a/hallu-result.png b/hallu-result.png
new file mode 100644 (file)
index 0000000..5911382
Binary files /dev/null and b/hallu-result.png differ
diff --git a/hallu.py b/hallu.py
new file mode 100755 (executable)
index 0000000..6b0b303
--- /dev/null
+++ b/hallu.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+
+# ImageMagick's montage to make the mosaic
+#
+# montage hallu-*.png -tile 5x6 -geometry +1+1 result.png
+
+import PIL, torch, torchvision
+from torch.nn import functional as F
+
+class MultiScaleEdgeEnergy(torch.nn.Module):
+    def __init__(self):
+        super(MultiScaleEdgeEnergy, self).__init__()
+        k = torch.exp(- torch.tensor([[-2., -1., 0., 1., 2.]])**2 / 2)
+        k = (k.t() @ k).view(1, 1, 5, 5)
+        self.register_buffer('gaussian_5x5', k / k.sum())
+
+    def forward(self, x):
+        u = x.view(-1, 1, x.size(2), x.size(3))
+        result = 0.0
+        while min(u.size(2), u.size(3)) > 5:
+            blurry  = F.conv2d(u, self.gaussian_5x5, padding = 2)
+            result += (u - blurry).view(u.size(0), -1).pow(2).sum(1)
+            u = F.avg_pool2d(u, kernel_size = 2, padding = 1)
+        return result.view(x.size(0), -1).sum(1)
+
+img = torchvision.transforms.ToTensor()(PIL.Image.open('blacklab.jpg'))
+img = img.view((1,) + img.size())
+ref_input = 0.5 + 0.5 * (img - img.mean()) / img.std()
+
+mse_loss = torch.nn.MSELoss()
+edge_energy = MultiScaleEdgeEnergy()
+
+layers = torchvision.models.vgg16(pretrained = True).features
+layers.eval()
+
+if torch.cuda.is_available():
+    edge_energy.cuda()
+    ref_input = ref_input.cuda()
+    layers.cuda()
+
+for l in [ 5, 7, 12, 17, 21, 28 ]:
+    model = torch.nn.Sequential(layers[:l])
+    ref_output = model(ref_input).detach()
+
+    for n in range(5):
+        input = ref_input.new_empty(ref_input.size()).uniform_(-0.01, 0.01).requires_grad_()
+        optimizer = torch.optim.Adam( [ input ], lr = 1e-2)
+        for k in range(1000):
+            output = model(input)
+            loss = mse_loss(output, ref_output) + 1e-3 * edge_energy(input)
+            optimizer.zero_grad()
+            loss.backward()
+            optimizer.step()
+
+        img = 0.5 + 0.2 * (input - input.mean()) / input.std()
+        result_name = 'hallu-l%02d-n%02d.png' % (l, n)
+        torchvision.utils.save_image(img, result_name)
+
+        print('Wrote ' + result_name)