<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Audun M Øygard</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://auduno.github.io/"/>
  <updated>2018-08-27T18:27:46.566Z</updated>
  <id>http://auduno.github.io/</id>
  
  <author>
    <name>Audun M Øygard</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Visualizing an art collection</title>
    <link href="http://auduno.github.io/2018/10/27/visualizing-an-art-collection/"/>
    <id>http://auduno.github.io/2018/10/27/visualizing-an-art-collection/</id>
    <published>2018-10-26T22:00:00.000Z</published>
    <updated>2018-08-27T18:27:46.566Z</updated>
    
    <content type="html"><![CDATA[<style>.paintings {text-align:center;}.painting {display:inline-block;padding-left:5px;padding-right:5px;}.dcgan {width:200px;display:inline-block;padding-left:15px;padding-right:15px;}.tsne {margin-left:-15%;width:130%;}@media (min-width: 551px) {.paintings {margin-left:-20%;margin-right:-20%;}.painting {width:175px;}}@media (max-width: 550px) {.painting {width:150px;}}</style><p>In this blog post I'll describe some work I did a while ago in a consulting project together with <a href="http://bengler.no" target="_blank" rel="noopener">Bengler</a>. The work was a consultancy project for the national museum that was started in 2015 and the final result, <a href="http://vy.nasjonalmuseet.no" target="_blank" rel="noopener">VY</a>, was made public in May last year. There were however some more experiments we tried during this project that I thought might be interesting to share.</p><p>I was contacted by <a href="http://bengler.no" target="_blank" rel="noopener">Bengler</a> in 2014 regarding a potential project for <a href="http://www.nasjonalmuseet.no" target="_blank" rel="noopener">Nasjonalmuseet</a>. Nasjonalmuseet is Norways national gallery containing more than 30000 works of art, the most famous of them probably being Edvard Munchs' <i>The Scream</i>. The original proposal was to do something involving face recognition, since I'd released the <a href="http://" target="_blank" rel="noopener">face substitution</a> demo not so long ago, but after some thought we decided to have a look at whether it would be possible to apply Deep Learning to the fine arts collection.</p><h4>Visualization</h4><p>One of our first ideas was to try to visualize the collection in some way. Though the museum had webpages showing off parts of their collection, it was hard to get a complete overview of the entire collection as well as the many subjects found in the collection. <a href="https://en.wikipedia.org/wiki/T-distributed_stochastic_neighbor_embedding" target="_blank" rel="noopener">t-SNE</a> visualizations of deep learning features had worked well for another project of mine, so we decided to give this a try. At this point we had no idea whether this really would work for artworks as well, but as it turns out it did work really well.</p><p>In order to ensure that the embeddings became meaningful, we decided to train a deep learning classifier to classify artworks into art styles and motifs respectively. The models did not get super-high accuracy at that task, but that was not the main goal either. We simply wanted the features from the last layer of the trained classifier, which we used as input to a t-SNE model. As can be seen by the resulting t-SNE embedding below, this worked pretty well.</p><figure class="tsne"><a href="/images/test_tsne_white_2_200_5000.jpg" target="_blank"><img src="/images/test_tsne_white_2_200_2000.jpg" alt="Entire t-SNE"></a><figcaption>Entire t-SNE</figcaption></figure><p>We got smooth transitions from nationalromantic paintings focusing on seas, through fjords, to forests.</p><figure class="tsne"><img src="/images/tsne_part2_sea.jpg" alt="Closeup of the landscape part of the t-SNE"><figcaption>Closeup of the landscape part of the t-SNE</figcaption></figure><p>And from portraits one can clearly see clustering of women, bearded men, and full person portraits.</p><figure class="tsne"><img src="/images/tsne_part1_faces_2000.jpg" alt="Closeup of the portrait part of the t-SNE"><figcaption>Closeup of the portrait part of the t-SNE</figcaption></figure><p>Visualizations were most interesting for paintings. Though they work just as well for drawings, it's harder to get a proper overview of the collection of the drawings, due to the reduced contrast of pencil drawings at small resolutions.</p><p>For the final public work we decided to try out <a href="https://github.com/kylemcdonald/Parametric-t-SNE" target="_blank" rel="noopener">parametric t-SNE</a>, since we would have to make it possible for the t-SNE map to grow without having to retrain it for every addition to the collection. To our surprise, this seemed to give slightly better and more coherent t-SNE maps than the regular t-SNE method.</p><figure class="tsne"><a href="/images/DM_ptsne_keyword_9_whiteBG_200_5000.jpg" target="_blank"><img src="/images/DM_ptsne_keyword_9_whiteBG_200_2000.jpg" alt="Entire t-SNE"></a><figcaption>Parametric t-SNE</figcaption></figure><p>The features from our trained classifier can be used for more things than just visualization. We can also figure out what the typical works from a period looks like, by looking at works close to the mean embedding within a specific period. These works clearly show how typical norwegian painting gradually went from mainly nationalromantic landscape themes in mid 1800s, through to social realism at the turn of the century, and eventually abstraction in the fifties.</p><figure class="paintings"><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.00212" target="_blank" rel="noopener"><img src="/images/1850_1_NG.M.00212_0.jpg" alt="Typical works around 1850" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.00414" target="_blank" rel="noopener"><img src="/images/1850_2_NG.M.00414_0.jpg" alt="Typical works around 1850" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.00211" target="_blank" rel="noopener"><img src="/images/1850_3_NG.M.00211_0.jpg" alt="Typical works around 1850" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.00636-011" target="_blank" rel="noopener"><img src="/images/1850_4_NG.M.00636-011_0.jpg" alt="Typical works around 1850" class="painting"></a><figcaption>Typical works around 1850</figcaption></figure><figure class="paintings"><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.00340" target="_blank" rel="noopener"><img src="/images/1880_1_NG.M.00340_0.jpg" alt="Typical works around 1880" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.00643" target="_blank" rel="noopener"><img src="/images/1880_2_NG.M.00643_0.jpg" alt="Typical works around 1880" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.01568" target="_blank" rel="noopener"><img src="/images/1880_3_NG.M.01568_0.jpg" alt="Typical works around 1880" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.00335" target="_blank" rel="noopener"><img src="/images/1880_4_NG.M.00335_0.jpg" alt="Typical works around 1880" class="painting"></a><figcaption>Typical works around 1880</figcaption></figure><figure class="paintings"><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.01226b" target="_blank" rel="noopener"><img src="/images/1910_1_NG.M.01226b_0.jpg" alt="Typical works around 1910" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.04097" target="_blank" rel="noopener"><img src="/images/1910_2_NG.M.04097_0.jpg" alt="Typical works around 1910" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.04380" target="_blank" rel="noopener"><img src="/images/1910_3_NG.M.04380_0.jpg" alt="Typical works around 1910" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.01016" target="_blank" rel="noopener"><img src="/images/1910_4_NG.M.01016_1.jpg" alt="Typical works around 1910" class="painting"></a><figcaption>Typical works around 1910</figcaption></figure><figure class="paintings"><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.01716" target="_blank" rel="noopener"><img src="/images/1930_1_NG.M.01716_0.jpg" alt="Typical works around 1930" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NMK.2011.0186" target="_blank" rel="noopener"><img src="/images/1930_2_NMK.2011.0186_0.jpg" alt="Typical works around 1930" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.01990" target="_blank" rel="noopener"><img src="/images/1930_3_NG.M.01990_0.jpg" alt="Typical works around 1930" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.02076" target="_blank" rel="noopener"><img src="/images/1930_4_NG.M.02076_0.jpg" alt="Typical works around 1930" class="painting"></a><figcaption>Typical works around 1930</figcaption></figure><figure class="paintings"><a href="http://samling.nasjonalmuseet.no/en/object/MS-00214-1988" target="_blank" rel="noopener"><img src="/images/1950_1_MS-00214-1988_0.jpg" alt="Typical works around 1950" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/MS-03782-1995" target="_blank" rel="noopener"><img src="/images/1950_2_MS-03782-1995_0.jpg" alt="Typical works around 1950" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/MS-04283-1999" target="_blank" rel="noopener"><img src="/images/1950_3_MS-04283-1999_0.jpg" alt="Typical works around 1950" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/MS-01184-1988" target="_blank" rel="noopener"><img src="/images/1950_4_MS-01184-1988_0.jpg" alt="Typical works around 1950" class="painting"></a><figcaption>Typical works around 1950</figcaption></figure><p>Similarly, we can also investigate if there are any stylistic outliers, by looking at works that are far from the mean embedding within each decade.</p><figure class="paintings"><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.01291" target="_blank" rel="noopener"><img src="/images/outl_1880_1_NG.M.01291_0.jpg" alt="Outlier works around 1880" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.02745" target="_blank" rel="noopener"><img src="/images/outl_1880_2_NG.M.02745_0.jpg" alt="Outlier works around 1880" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.02081" target="_blank" rel="noopener"><img src="/images/outl_1880_3_NG.M.02081_0.jpg" alt="Outlier works around 1880" class="painting"></a><a href="http://samling.nasjonalmuseet.no/no/object/NG.M.04242" target="_blank" rel="noopener"><img src="/images/outl_1880_4_NG.M.04242_0.jpg" alt="Outlier works around 1880" class="painting"></a><figcaption>Outlier works around 1880</figcaption></figure><figure class="paintings"><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.01259" target="_blank" rel="noopener"><img src="/images/outl_1910_1_NG.M.01259_0.jpg" alt="Outlier works around 1910" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.03283" target="_blank" rel="noopener"><img src="/images/outl_1910_2_NG.M.03283_0.jpg" alt="Outlier works around 1910" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.01212" target="_blank" rel="noopener"><img src="/images/outl_1910_4_NG.M.01212_0.jpg" alt="Outlier works around 1910" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.02975" target="_blank" rel="noopener"><img src="/images/outl_1910_5_NG.M.02975_0.jpg" alt="Outlier works around 1910" class="painting"></a><figcaption>Outlier works around 1910</figcaption></figure><figure class="paintings"><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.02879" target="_blank" rel="noopener"><img src="/images/outl_1930_1_NG.M.02879_0.jpg" alt="Outlier works around 1930" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.03872" target="_blank" rel="noopener"><img src="/images/outl_1930_2_NG.M.03872_0.jpg" alt="Outlier works around 1930" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.01928" target="_blank" rel="noopener"><img src="/images/outl_1930_3_NG.M.01928_0.jpg" alt="Outlier works around 1930" class="painting"></a><a href="http://samling.nasjonalmuseet.no/en/object/NG.M.02739" target="_blank" rel="noopener"><img src="/images/outl_1930_4_NG.M.02739_0.jpg" alt="Outlier works around 1930" class="painting"></a><figcaption>Outlier works around 1930</figcaption></figure><p>An interesting detail we noted is that outliers often signified trends that became commonplace a few decades later, such as the gradual shift to abstract painting.</p><h4>Faces in the collection</h4><p>Since the original proposal involved doing something with faces in the collection, we also decided to have a look at applying similar visualization methods there. After detecting most faces in the collection and cropping them out, we tried out using a pretrained face-similarity embedding which we fed into a t-SNE model, but though the resulting embedding seemed to separate between male, bearded faces, and more feminine faces, the t-SNE visualizations didn't make quite as much sense as with the painting embeddings above. We suspected this might have to do with the difference between faces in paintings and photographies, so we even collected a dataset of painted fan-art of celebrities which we finetuned the face-similarity model on, but unfortunately it did not seem to have a significant effect on the quality of the visualization.</p><figure class="tsne"><a href="/images/faces_ptsne_6.jpg" target="_blank"><img src="/images/faces_ptsne_6_2000.jpg" alt="t-SNE of faces in the collection"></a><figcaption>t-SNE of faces in the collection</figcaption></figure><p>A follow-up idea we had was to make some kind of web app to allow visitors to find the faces in the collection most similar to their own, but due to time and budget constraints, we decided to scrap this idea. Fortunately, Google created <a href="https://artsandculture.google.com/camera/selfie" target="_blank" rel="noopener">just that</a> a couple of months later, on a much larger collection of artworks.</p><h4>Generating paintings</h4><p>Of course, in 2016, sitting with a dataset of prime nationalromantic art, the temptation to train a Generative Adversarial Network on it was too strong. So naturally, we did just that. To train a DCGAN generating nationalromantic works, we first trained a GAN on a diverse set of styles of paintings from wikimedia, and then finetuned the model to a small augmented dataset of nationalromantic paintings. This is some of the results we got:</p><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/dcgan_01.jpg" alt="DCGAN-generated painting" class="dcgan"><img src="/images/dcgan_02.jpg" alt="DCGAN-generated painting" class="dcgan"><img src="/images/dcgan_03.jpg" alt="DCGAN-generated painting" class="dcgan"><img src="/images/dcgan_04.jpg" alt="DCGAN-generated painting" class="dcgan"><img src="/images/dcgan_05.jpg" alt="DCGAN-generated painting" class="dcgan"><img src="/images/dcgan_06.jpg" alt="DCGAN-generated painting" class="dcgan"><img src="/images/dcgan_07.jpg" alt="DCGAN-generated painting" class="dcgan"><img src="/images/dcgan_08.jpg" alt="DCGAN-generated painting" class="dcgan"><img src="/images/dcgan_09.jpg" alt="DCGAN-generated painting" class="dcgan"><figcaption>Nationalromantic paintings generated with DCGAN</figcaption></figure><p>While they might not pass for authentic works by Tidemand &amp; Gude, they do look like coherent, though vague nationalromantic paintings</p><p>We had no problem training a model (using <a href="https://github.com/carpedm20/DCGAN-tensorflow" target="_blank" rel="noopener">DCGAN-tensorflow</a>) to produce 256x256 paintings, though we quite often ran into the dreaded mode-collapse problem, which means all output converged to an abstract blob, so we'd have to start from scratch. We of course also tried to produce even larger 512x512 paintings, but this was more than our poor GPU RAM could handle. Efforts to scale up 256x256 images with superresolution also didn't work out well, since the superresolution mainly just sharpened artifacts in the image and added more noise.</p><p>Since we finished this project, there has been numerous improvements made to GAN models (one being Nvidia Researchs <a href="http://research.nvidia.com/publication/2017-10_Progressive-Growing-of" target="_blank" rel="noopener">progressive growing GAN</a>), which leads us to believe there is a huge potential to experiment more with GANs and art.</p><h4>Conclusion</h4><p>In finishing, I'd like to give a huge thanks to Bengler and the staff at the national museum, for their belief in the project and the free reins they gave us. Both during and after we worked on this project, we've seen similar projects, such as <a href="https://experiments.withgoogle.com/arts-culture" target="_blank" rel="noopener">Googles Arts &amp; Cultures Experiments</a>, <a href="https://news.artnet.com/art-world/rutgers-artificial-intelligence-art-1019066" target="_blank" rel="noopener">this</a>, <a href="https://medium.com/@ThreadGenius/art-genius-1260726bfebd" target="_blank" rel="noopener">this</a> and <a href="https://towardsdatascience.com/gangogh-creating-art-with-gans-8d087d8f74a1" target="_blank" rel="noopener">this</a>. We believe there is lots of potential for fruitful collaboration between the fields of art and modern machine learning.</p><p>If you're interested in more info about what we did, also take a look at Benglers <a href="http://bengler.no/principalcomponents" target="_blank" rel="noopener">writeup of the project</a>, as well as <a href="https://iconclassblog.com/2017/06/12/iconclass-and-ai/" target="_blank" rel="noopener">this blogpost</a> on our experiments with training a model to classify paintings into iconclass classes.</p><p>If you enjoyed this post, you should  <a href="https://twitter.com/matsiyatzy" target="_blank" rel="noopener">follow me on twitter</a>!</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;style&gt;
.paintings {
	text-align:center;
}

.painting {
	display:inline-block;
	padding-left:5px;
	padding-right:5px;
}

.dcgan {
	width:200
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Peeking inside Convnets</title>
    <link href="http://auduno.github.io/2016/06/18/peeking-inside-convnets/"/>
    <id>http://auduno.github.io/2016/06/18/peeking-inside-convnets/</id>
    <published>2016-06-17T22:00:00.000Z</published>
    <updated>2016-06-18T20:30:53.000Z</updated>
    
    <content type="html"><![CDATA[<p>Convolutional neural networks are used extensively for a number of image related tasks these days. Despite being very successful, they're mostly seen as "black box" models, since it's hard to understand what happens inside the network. There are however methods to "peek inside" the convnets, and thus understand a bit more about how they work.</p><p>In a <a href="/2015/07/29/visualizing-googlenet-classes/">previous blogpost</a> I showed how you could use gradient ascent, with some special tricks, to make a convolutional network visualize the classes it's learnt to classify. In this post I'll show that the same technique can also be used to "peek inside the network" by visualizing what the individual units in a layer detect. To give you an idea of the results, here's some highlights of visualizations of individual units from convolutional layer 5 in the <a href="https://gist.github.com/ksimonyan/fd8800eeb36e276cd6f9#file-readme-md" target="_blank" rel="noopener">VGG-S</a> network:</p><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/vgg_filter_10_crop.jpg" alt="Visualization of filter 10 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg_filter_334_crop.jpg" alt="Visualization of filter 334 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"></figure><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/vgg_filter_345_crop.jpg" alt="Visualization of filter 345 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg_filter_425_crop.jpg" alt="Visualization of filter 425 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Visualization of units 10,334,425 and 435 in convolutional layer 5 in VGG-S.</figcaption></figure><p>From top left we can pretty clearly see the head of a cocker spaniel-type dog, the head of some kind of bird, the ears of a canine, and a seaside coastline. Not all unit vizualisations are as clearly defined as these, but most nevertheless give us some interesting insights into what the individual units detect.</p><p>Earlier methods for figuring out what the units detect (e.g. in <a href="https://arxiv.org/abs/1311.2901" target="_blank" rel="noopener">Zeiler & Fergus</a>) have been to find images which maximally activate the individual units. Here's an example of the images (sampled from numerous crops of 100 000 images in the imagenet validation dataset) which give maximal activations for a specific unit in layer 5 of VGG-S:</p><figure style="margin-bottom:1.3em"><img src="/images/max_activation_05.jpg" alt="Images maximally activating filter 5 in VGG-S"><figcaption>Image crops maximally activating unit 5 in layer 5 VGG-S</figcaption></figure><p>While this gives us an idea of what the unit is detecting, by visualizing the same unit we can see explicitly the details the unit is focusing on. Applying this technique to the same unit as above, we can see that the unit seems to focus on the characteristic pattern on the muzzle of the dog, seemingly ignoring most other details in the image.</p><figure style="margin-bottom:1.3em"><img src="/images/vgg_filter_05_crop.jpg" alt="Visualization of filter 5 in VGG-S" style="width:200px"><figcaption>Visualization of unit 5 in convolutional layer 5 in VGG-S</figcaption></figure><p>We can use our visualization technique to get an overview of what all the different units in a typical layer detects. Here we've focused on convolutional layer 5 in VGG-S, which is the final convolutional layer in that specific network. Seemingly there are a large number of units that detect very specific features, such as (from top left below) forests/bushes in the background, buildings with pitched roofs, individual trees, clouds, collars, brass instruments, ship masts, bottle/jug tops, and seemingly the shoulders of people:</p><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/vgg_filter_94_crop.jpg" alt="Visualization of filter 94 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg_filter_159_crop.jpg" alt="Visualization of filter 159 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg_filter_201_crop.jpg" alt="Visualization of filter 201 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg_filter_432_crop.jpg" alt="Visualization of filter 432 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg_filter_258_crop.jpg" alt="Visualization of filter 258 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg_filter_7_crop.jpg" alt="Visualization of filter 7 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg_filter_136_crop.jpg" alt="Visualization of filter 136 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg_filter_88_crop.jpg" alt="Visualization of filter 88 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vggs_filter_449_crop.jpg" alt="Visualization of filter 449 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Visualization of (from top left) unit 94,159,201,432,258,7,136,88 & 449 in convolutional layer 5 in VGG-S</figcaption></figure><p>What is interesting to notice, is that the network doesn't seem to have learned detailed representations of faces. In e.g. the visualization featuring the collar, the face looks more like a spooky flesh-colored blob than a face. This might be an artifact of the visualization process, but it's not entirely unlikely that the network have either not found it necessary to learn the details, or not had the capacity to learn them.</p><p>There also are a surprisingly large number of units that detect dog-related features. I counted somewhere around 50, out of 512 units in the layer in total, which means a surprising 10% of the network may be dedicated solely to dogs. Here's a small sample of these:</p><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/vgg_filter_249_crop.jpg" alt="Visualization of filter 249 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg_filter_468_crop.jpg" alt="Visualization of filter 468 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"></figure><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/vgg_filter_170_crop.jpg" alt="Visualization of filter 170 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg_filter_75_crop.jpg" alt="Visualization of filter 75 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Visualization of (from top left) unit 249,468,170 & 75 in convolutional layer 5 in VGG-S</figcaption></figure><p>On the other hand I could only find a single unit that clearly detected cat features (!):</p><figure style="margin-bottom:1.3em"><img src="/images/vgg_filter_484_crop.jpg" alt="Visualization of filter 484 in VGG-S" style="width:200px"><figcaption>Visualization of unit 484 in convolutional layer 5 in VGG-S</figcaption></figure><p>Some of the units are more general shape detectors, detecting edges, circles, corners, cones or similar:</p><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/vggs_filter_116_crop.jpg" alt="Visualization of filter 116 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vggs_filter_125_crop.jpg" alt="Visualization of filter 125 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vggs_filter_364_crop.jpg" alt="Visualization of filter 364 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vggs_filter_422_crop.jpg" alt="Visualization of filter 422 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vggs_filter_433_crop.jpg" alt="Visualization of filter 433 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vggs_filter_265_crop.jpg" alt="Visualization of filter 265 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Visualization of (from top left) unit 116,125,364,422,433 & 265 in convolutional layer 5 in VGG-S</figcaption></figure><p>and some seem to detect textures, such as these detecting leopard fur and wood grain:<figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/vggs_filter_27_crop.jpg" alt="Visualization of filter 27 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vggs_filter_497_crop.jpg" alt="Visualization of filter 497 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vggs_filter_243_crop.jpg" alt="Visualization of filter 243 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Visualization of (from left) unit 27,497 & 265 in convolutional layer 5 in VGG-S</figcaption></figure></p><p>Not all of the unit visualizations are so easy to interpret, such as these:</p><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/vggs_filter_252_crop.jpg" alt="Visualization of filter 252 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vggs_filter_164_crop.jpg" alt="Visualization of filter 164 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vggs_filter_300_crop.jpg" alt="Visualization of filter 300 in VGG-S" style="width:200px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Visualization of (from left) unit 252,164 & 300 in convolutional layer 5 in VGG-S</figcaption></figure><p>However, if we find images that maximally activate these units, we can see that they detect respectively grids and more abstract features such as out-of-focus backgrounds, and shallow-focus/macro images.</p><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/max_activations_252.jpg" alt="Images maximally activating filter 252 in VGG-S"><img src="/images/max_activations_164.jpg" alt="Images maximally activating filter 164 in VGG-S"><img src="/images/max_activations_300.jpg" alt="Images maximally activating filter 300 in VGG-S"><figcaption>Images maximally activating (from top) unit 252,164 & 300 in convolutional layer 5 in VGG-S</figcaption></figure><p>Overall, this visualization of the units give us useful insight into what the units in VGG-S detect. However, VGG-S is a relatively shallow network by todays standards, with only 5 convolutional layers. What about visualizing units in deeper networks, such as VGG-16 or GoogLeNet? Unfortunately, this doesn't seem to work as well, though it gives us some interesting results. Here for instance, is a visualization of some units in convolutional layer 4c from GoogLeNet:</p><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/googlenet_4c_0411.jpg" alt="Visualization of filter 411 in Googlenet" style="display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/googlenet_4c_0418.jpg" alt="Visualization of filter 418 in Googlenet" style="display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/googlenet_4c_0223.jpg" alt="Visualization of filter 223 in Googlenet" style="display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/googlenet_4c_0423.jpg" alt="Visualization of filter 423 in Googlenet" style="display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/googlenet_4c_0390.jpg" alt="Visualization of filter 390 in Googlenet" style="display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/googlenet_4c_0340.jpg" alt="Visualization of filter 340 in Googlenet" style="display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Visualization of (from top left) unit 411,418,223,423,390 & 340 in convolutional layer 4c in GoogLeNet</figcaption></figure><p>You might recognize some of these as the "puppyslugs" from DeepDream. While these visualization are more detailed than the ones we get from VGG-S, they also have a tendency to look more psychedelic and unreal. It is not completely clear why this happens, but it seems like the center of the visualization generally seems to be a good representation of what the unit detects, while the edges gives us lots of random details.</p><!-- 4c 418, 4a tree --><p>Similarly for VGG-16, the visualizations we get are much harder to interpret, though we can see in some of these that the unit seems to detect respectively some kind of dog, a theater and a brass instrument (with players as blobs).</p><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/vgg16_filter_13.jpg" alt="Visualization of filter 13 in VGG-16" style="display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg16_filter_10.jpg" alt="Visualization of filter 10 in VGG-16" style="display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg16_filter_0.jpg" alt="Visualization of filter 0 in VGG-16" style="display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg16_filter_4.jpg" alt="Visualization of filter 4 in VGG-16" style="display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg16_filter_17.jpg" alt="Visualization of filter 17 in VGG-16" style="display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/vgg16_filter_2.jpg" alt="Visualization of filter 2 in VGG-16" style="display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Visualization of (from top left) unit 13,10,0,4,17 & 2 in convolutional layer 5 in VGG-16</figcaption></figure><p>A hypothetical reason that these visualizations doesn't work as well for deeper networks, has to do with the nature of the convolutional networks. What each convolutional layer tries to do is to be able to detect specific features, without being sensitive to irrelevant variations such as pose, lighting, partial obstruction etc. In this sense, each convolutional layer "compresses" information and throws away irrelevant details such as pose etc. This works great when doing detection, which is what the network is actually meant to do. However, when we try to run the network in reverse and generate feasible images, for each layer we have to "guess" the irrelevant structural details that have been thrown away, and as the choices made in one layer might not be coordinated with other layers, this in effect introduces some amount of "structural noise" for each layer we have to run in reverse. This might be a minor issue for networks with few layers, such as VGG-S, but as we introduce more and more layers, the cumulative "structural noise" might simply overpower the generated structure in the image, and make the image look much less like what we would recognize as e.g. a dog, and more like what we recognize as the "puppyslugs" seen in DeepDream.</p><p>More investigations might have to be done to tell whether this is actually the reason that visualization fails for deeper networks, but I wouldn't be surprised if this is part of the reason. Below I briefly describe the technical details of how I made these visualizations.</p><h4>Technical details</h4><p>To visualize the features, I'm using pretty much the same technique I described earlier in <a href="">this blogpost</a>, starting from a randomly initialized image, and doing gradient ascent on the image with regards to the activation of a specific unit. We also use blurring between gradient descent iterations (which is equivalent to regularization via a smoothness prior), and gradually reduce the "width" of the blur during gradient descent in order to get natural-looking images. Since units in intermediate layers actually output a grid of activations over the entire image, we choose to optimize a single point in this grid, which gives us a feature visualization corresponding to the units receptive field.</p><p>Another trick I also used, was to modify the network to use leaky ReLUs instead of regular ReLUs, since otherwise the gradient will usually be zero when we start from a blank image, thus hindering initial gradient ascent. Since this modification doesn't seem to have significant effect on the predictions of the network, we can assume it doesn't have a major impact on the feature visualizations.</p><p>I've <a href="https://github.com/auduno/deepdraw/tree/master/filter_visualizations" target="_blank" rel="noopener">released the code</a> I used to make these visualizations, so take a look if you want to know more details.</p><h4>Similar work</h4><p>There has been similar work on visualizing convolutional networks by e.g. Zeiler and Fergus and lately by Yosinski, Nugyen et al. In a <a href="http://arxiv.org/abs/1602.03616" target="_blank" rel="noopener">recent work</a> by Nguyen, they manage to visualize features very well, based on a technique they called "mean-image initialization". Since I started writing this blog post, they've also published a <a href="https://arxiv.org/abs/1605.09304" target="_blank" rel="noopener">new paper</a> using Generative Adversarial Networks as priors for the visualizations, which lead to far far better visualizations than the ones I've showed above. If you are interested, do take a look at their paper or <a href="https://github.com/Evolving-AI-Lab/synthesizing" target="_blank" rel="noopener">the code</a> they've released!</p><p>If you enjoyed this post, you should  <a href="https://twitter.com/matsiyatzy" target="_blank" rel="noopener">follow me on twitter</a>!</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Convolutional neural networks are used extensively for a number of image related tasks these days. Despite being very successful, they&#39;re
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Drawing with GoogLeNet</title>
    <link href="http://auduno.github.io/2015/08/04/drawing-with-googlenet/"/>
    <id>http://auduno.github.io/2015/08/04/drawing-with-googlenet/</id>
    <published>2015-08-03T22:00:00.000Z</published>
    <updated>2016-06-19T11:45:48.000Z</updated>
    
    <content type="html"><![CDATA[<p>In my <a href="/post/125362849838/visualizing-googlenet-classes">previous post</a>, I showed how you can use deep neural networks to generate image examples of the classes it&rsquo;s been trained to classify. Since we&rsquo;ve already started using deep neural networks in ways they were never intended to be used, let&rsquo;s abuse them some more.</p><p>There&rsquo;s nothing constraining us to generate image examples of one class at a time. Let&rsquo;s see what happens if we try to generate two class visualizations close to each other, such as for instance a gorilla and a french horn</p><figure style="margin-bottom:15px"><img src="/images/gorilla_french-horn_crop.jpg" alt="Gorilla playing the french horn"><figcaption>Gorilla playing an odd-looking french horn</figcaption></figure><p>Well, it <em>kind of</em> looks like a gorilla playing the french horn. Or let&rsquo;s try dressing up a gibbon via &ldquo;mixing&rdquo; the gibbon class with some of the clothing classes:</p><figure style="margin-left:auto;margin-right:auto;margin-bottom:15px;text-align:center"><img src="/images/gibbon_poncho_crop.jpg" alt="Gibbon in a poncho" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/gibbon_labcoat_crop.jpg" alt="Gibbon in a labcoat" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>A gibbon in a poncho (left) and an ET-looking gibbon in a labcoat (right)</figcaption></figure><p>Or what about making some scenic nature drawings, such as some foxes underneath an erupting volcano:</p><figure style="margin-bottom:15px"><img src="/images/fox_volcano_3_crop.jpg" alt="Foxes beneath an erupting volcano"><figcaption>Foxes beneath an erupting volcano</figcaption></figure><p>Or a ballpoint pen drawing a castle:</p><figure style="margin-bottom:15px"><img src="/images/pen_and_castle6_crop.jpg" alt="Pen drawing a castle"><figcaption>A vague ballpoint pen drawing a castle</figcaption></figure><p>These mixes of classes <em>kind of</em> work out, though it should be noted that these are the best selections from a number of mixes I tried. It&rsquo;s also tempting to create mixes of animal classes to generate some new kind of monster breeds, but most of the time this doesn&rsquo;t work so well. Here&rsquo;s some I tried though, a mix of a scotch terrier and a tarantula, and a mix of a bee and a gibbon:</p><figure style="margin-left:auto;margin-right:auto;margin-bottom:15px;text-align:center"><img src="/images/mix_scotch-terrier_tarantula2_crop.jpg" alt="Terrier/Tarantula" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/gibbon_and_bee_crop.jpg" alt="Bee/Gibbon" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>A slightly spidery looking scotch terrier (left) and a slightly gibbon-looking bee (right)</figcaption></figure><p>Another fun thing we can do when generatinge images is to do the gradient ascent randomly along paths instead of on a single point. This of course takes a bit longer time, but it allows us to &ldquo;draw&rdquo; with the output, such as for instance drawing a mountain range of alps:</p><figure style="margin-bottom:15px"><a id="alps" href="javascript:;" target="_blank" rel="noopener"><img src="/images/sine_alps_crop_thumb.jpg" alt="Alps"></a></figure><p>or a line of jellyfish:</p><figure style="margin-bottom:15px"><a id="jellyfish" href="javascript:;" target="_blank" rel="noopener"><img src="/images/jellyfish_straight_path_crop_thumb.jpg" alt="Jellyfish"></a></figure><p>or a circle of junco birds:</p><figure style="margin-bottom:15px"><a id="birds" href="javascript:;" target="_blank" rel="noopener"><img src="/images/circle_junco_thumb.jpg" alt="Circle of birds"></a></figure><p>If we try to fill a larger region with visualizations of a class, we can also apply clipping masks, i.e. forcing the pixels to zero in some pattern during gradient ascent. So we can for instance use letters as clipping masks and try to create the alphabet with animals:</p><figure><img src="/images/letter_a_apes_gibbon2.jpg" alt="An A of apes"><figcaption>An A of apes</figcaption></figure><figure><img src="/images/letter_b_bear4.jpg" alt="A B of bears"><figcaption>A B of bears</figcaption></figure><figure style="margin-bottom:15px"><img src="/images/letter_c_cobra3.jpg" alt="A C of cobras"><figcaption>And a C of cobras</figcaption></figure><p>Alright, that&rsquo;s enough abuse of our deep neural network for today. I&rsquo;ve just scratched the surface here, but there are several fun ways to use deep neural networks for creative visual work with a bit of experimentation (and lots of patience). I&rsquo;m going to put the ipython notebooks I used to make these examples in the <a href="https://github.com/auduno/deepdraw" target="_blank" rel="noopener">deepdraw repository</a> as soon as I&rsquo;ve cleaned up the code, so stay tuned <a href="https://twitter.com/matsiyatzy" target="_blank" rel="noopener">via twitter</a>.</p><script type="text/javascript">  window.onload = function() {  $("a#birds").click(function() {    $.fancybox(      [        {          'href' : '/images/circle_junco.jpg',          'title' : 'A circle of birds'        }      ],{      'padding'         : 0,      'transitionIn': 'none',      'transitionOut': 'none',      'type' : 'image',      'changeFade' : 0      }    )  });  $("a#jellyfish").click(function() {    $.fancybox(      [        {          'href' : '/images/jellyfish_straight_path_crop.jpg',          'title' : 'A line of jellyfish'        }      ],{      'padding'         : 0,      'transitionIn': 'none',      'transitionOut': 'none',      'type' : 'image',      'changeFade' : 0      }    )  });  $("a#alps").click(function() {    $.fancybox(      [        {          'href' : '/images/sine_alps_crop.jpg',          'title' : 'An odd looking mountain range of alps'        }      ],{      'padding'         : 0,      'transitionIn': 'none',      'transitionOut': 'none',      'type' : 'image',      'changeFade' : 0      }    )  });}</script>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;In my &lt;a href=&quot;/post/125362849838/visualizing-googlenet-classes&quot;&gt;previous post&lt;/a&gt;, I showed how you can use deep neural networks to gene
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Visualizing GoogLeNet Classes</title>
    <link href="http://auduno.github.io/2015/07/29/visualizing-googlenet-classes/"/>
    <id>http://auduno.github.io/2015/07/29/visualizing-googlenet-classes/</id>
    <published>2015-07-28T22:00:00.000Z</published>
    <updated>2016-06-19T11:46:03.000Z</updated>
    
    <content type="html"><![CDATA[<p>Ever wondered what a deep neural network thinks a Dalmatian should look like? Well, wonder no more.</p><p>Recently Google <a href="http://googleresearch.blogspot.no/2015/06/inceptionism-going-deeper-into-neural.html" target="_blank" rel="noopener">published a post</a> describing how they managed to use deep neural networks to generate class visualizations and modify images through the so called &ldquo;inceptionism&rdquo; method. They later published the code to modify images via the inceptionism method yourself, however, they didn&rsquo;t publish code to generate the class visualizations they show in the same post.</p><p>While I never figured out <em>exactly</em> how Google generated their class visualizations, after butchering the <a href="https://github.com/google/deepdream" target="_blank" rel="noopener">deepdream code</a> and <a href="https://github.com/kylemcdonald/deepdream/blob/master/dream.ipynb" target="_blank" rel="noopener">this</a> ipython notebook from Kyle McDonald, I managed to coach GoogLeNet into drawing these:</p><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/deepdraw_example_33.jpg" alt="Loggerhead turtle" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/deepdraw_example_0783.jpg" alt="Screws" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Generated images from class &ldquo;Loggerhead turtle&rdquo; (left), &ldquo;Screws&rdquo; (right)</figcaption></figure><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/deepdraw_example_260.jpg" alt="Chow chow" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/deepdraw_example_0281.jpg" alt="Tabby cat" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Generated images from class &ldquo;Chow chow&rdquo; (left), &ldquo;Tabby cat&rdquo; (right)</figcaption></figure><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/deepdraw_example_0294.jpg" alt="Brown bear" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/deepdraw_example_254.jpg" alt="Pug" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Generated images from class &ldquo;Brown bear&rdquo; (left), &ldquo;Pug&rdquo; (right)</figcaption></figure><figure style="margin-left:auto;margin-right:auto;text-align:center"><img src="/images/deepdraw_example_bee_309.jpg" alt="Bee" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/deepdraw_examples_76.jpg" alt="Tarantula" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Generated images from class &ldquo;Bee&rdquo; (left), &ldquo;Tarantula&rdquo; (right)</figcaption></figure><figure style="margin-left:auto;margin-right:auto;text-align:center;margin-bottom:15px"><img src="/images/deepdraw_examples_0850.jpg" alt="Teddybear" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/deepdraw_example_0251.jpg" alt="Dalmatians" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Generated images from class &ldquo;Teddybear&rdquo; (left), &ldquo;Dalmatian&rdquo; (right)</figcaption></figure><p>It should be mentioned that all of these images are generated completely from noise, so all information is from the deep neural network, see an example of the gradual generation process below:</p><figure style="margin-bottom:1.3em"><img src="/images/anim_230_1437687161.GIF" alt="gradual learning process"><figcaption>Generation of image example for class &ldquo;Shetland sheepdog&rdquo;</figcaption></figure><p>In this post I&rsquo;ll describe a bit more details on how I generated these images from GoogLeNet, but for those eager to try this out yourself, jump over to <a href="https://github.com/auduno/deepdraw" target="_blank" rel="noopener">github</a> where I&rsquo;ve published ipython notebooks to do this yourself. For more examples of generated images, see some highlights <a href="https://goo.gl/photos/8qcvjnYBQVSGG2eN6" target="_blank" rel="noopener">here</a>, or visualization of all 1000 imagenet classes <a href="https://goo.gl/photos/FfsZZektqpZkdDnKA" target="_blank" rel="noopener">here</a>.</p><br><p>Aside from the fact that our network seems to be drawing with rainbow crayons, it&rsquo;s remarkable to see how detailed the images are. They&rsquo;re far from perfect representations of the objects, but they give us valuable insight into what information the network thinks is essential for an object, and what isn&rsquo;t. For instance, the tabby cats seem to lack faces while the dalmatians are mostly dots. Presumably this doesn&rsquo;t mean that the network hasn&rsquo;t learned the rest of the details of these objects, but simply that the rest of the details are not very discriminate characteristics of that class, so they&rsquo;re ignored when generating the image.</p><p>As google also noted in their post, there are often also details that actually aren&rsquo;t part of the object. For instance, in this visualization of the &ldquo;Saxophone&rdquo; class there&rsquo;s a vague saxophone player holding the instrument:</p><figure style="margin-bottom:1.3em"><img src="/images/sax_777.jpg" alt="saxophone player"><figcaption>Visualization of class &ldquo;Saxophone&rdquo;</figcaption></figure><p>This is presumably because most of the example images used for training had a saxophone player in them, so the network sees them as relevant parts of the object.</p><p>In the next part I&rsquo;ll go a bit into details on how the gradient ascent is done. Note : this is for specially interested, with some knowledge of deep neural networks being necessary.</p><h4>Generating class visualizations with GoogLeNet</h4><p>In order to make a deep neural network generate images, we use a simple trick. Instead of using backpropagation to optimize the weights, which we do during training, we keep the weights fixed and instead optimize the input pixels. However, trying to use unconstrained gradient ascent to get a feasible class visualization works poorly, giving us images such as the one below.</p><figure style="margin-bottom:1.3em"><img src="/images/unblurred_opt_161.jpg" alt="unconstrained optimization"><figcaption>Unconstrained gradient ascent on class &ldquo;Basset hound&rdquo;</figcaption></figure><p>The reason for this is that our unconstrained gradient ascent quickly runs into local maximums that are hard to get out of, with high frequency and low frequency information competing and creating noise. To get around this, we can choose to just optimize the low-frequency information first, which will give us the general structure of the image, and then gradually introduce high-frequency details as we continue gradient ascent, in effect &ldquo;washing out&rdquo; an image. Doing this in a slow way, we manage to ensure that optimization converges with a feasible image. There are two possible routes for doing this:</p><ul><li>applying gaussian blur to the image after we&rsquo;ve applied the gradient step, starting with a large sigma and slowly decreasing it as we iterate</li><li>applying gaussian blur to the gradient, starting with a large sigma and slowly decreasing it as we iterate (note that in this case we also have to use L2-regularization of the pixels to gradually decrease irrelevant noise from previous iterations)</li></ul><p>I&rsquo;ve had best results with the former approach, which is the approach I used to generate the images above, but it might be that someone might get better results with blurring the gradient via messing about with the parameters some more.</p><p>While this approach works okayish for relatively shallow networks like Alexnet, a problem you&rsquo;ll quickly run into when doing this with GoogLeNet, is that as you gradually reduce the amount of blurring applied, the image gets saturated with high-frequency noise like this:</p><figure style="margin-bottom:1.3em"><img src="/images/loss3_161_3.jpg" alt="unconstrained optimization"><figcaption>Gradient ascent on class &ldquo;Basset hound&rdquo; with gradually decreasing blurring</figcaption></figure><p>The reason for this problem is a bit uncertain, but might have to do with the depth of the network. In the <a href="http://arxiv.org/abs/1409.4842" target="_blank" rel="noopener">original paper</a> describing the GoogLeNet architecture, the authors mention that since the network is very deep, with 22 layers, they had to add two auxiliary classifiers at earlier points in the network to efficiently propagate gradients from the loss all the way back to the first layers. These classifiers, which were only used during training, ensured proper gradient flow and made sure that early layers were getting trained as well.</p><p>In our case, the pixels of the image are even further ahead in the network than first layer, so it might not seem so surprising that we have some problems with gradients and recovering a feasible image. Exactly why this affects high-frequency information more than low-frequency information is a bit hard to understand, but it might have to do with gradients for high-frequency information being more sensitive and unstable, due to larger weights for high-frequency information, as mentioned by Yosinski in the appendix to <a href="http://arxiv.org/abs/1506.06579" target="_blank" rel="noopener">this paper</a>.</p><p>While the auxiliary classifiers in GoogleNet are only used during training, there&rsquo;s nothing stopping us from using them for generating images. Doing gradient ascent on the first auxiliary classifier, we get this:</p><figure style="margin-bottom:1.3em"><img src="/images/loss1_161_2.jpg" alt="auxiliary classifier 1 optimization"><figcaption>Gradient ascent on class &ldquo;Basset hound&rdquo; with auxiliary classifier 1</figcaption></figure><p>while the second auxiliary classifier gives us this:</p><figure style="margin-bottom:1.3em"><img src="/images/loss2_161_2.jpg" alt="auxiliary classifier 2 optimization"><figcaption>Gradient ascent on class &ldquo;Basset hound&rdquo; with auxiliary classifier 2</figcaption></figure><p>As can be seen, the first classifier easily manages to generate an image without high-frequency noise , probably because it&rsquo;s &ldquo;closer&rdquo; to the early layers. However, it does not retain the overall structure of the object, and peppers the image with unnecessary details. The reason for the lack of structure is that the deeper a network is, the more structure the network is able to learn. Since the first classifier is so early in the network, it has not yet learned all of the structures deeper layers has. We can similarly see that the second classifier has learned some more structure, but has slightly more problems with high-frequency noise (though not as bad as the final classifier).</p><p>So, is there any way to combine the gradients from these classifiers in order to ensure both structure and high-frequency information is retained? Doing gradient ascent on all three classifiers at the same time unfortunately does not help us much, as we get both poor structure and noisy high-frequency information. Instead, what we can do is to first do gradient ascent from the final classifier, as far as we can before we run into noise, then switch to doing gradient ascent from the second classifier for a while to &ldquo;fill in&rdquo; details, then finally switching to doing gradient ascent from the first classifier to get the final fine-grained details.</p><p>Another trick we used, both to get larger images and better details, was to scale up the image at certain intervals, similar to the &ldquo;octaves&rdquo; used in the deepdream code. Since the input image the network optimizes is restricted to 224x224 pixels, we randomly crop a part of the scaled up image to optimize at each step. Altogether, this gives us this result:</p><figure style="margin-bottom:1.3em"><img src="/images/anim_161_1437760444.GIF" alt="optimization"><figcaption>Gradual generation of image from class &ldquo;Basset hound&rdquo; with scaling and switching between classifiers</figcaption></figure><p>Though this approach gives us nicely detailed images, note that both the scaling and the auxiliary classifiers tend to degrade the overall structure of the image, and particularly larger objects often tend to be &ldquo;torn apart&rdquo;, such as this dog gradually turning into multiple dogs.</p><figure><img src="/images/anim_161_1437685108.GIF" alt="optimization"></figure><p>Since the network actually seems to be capable of creating more coherent objects, it&rsquo;s possible that we could generate better images with clever priors and proper learning rates, though I didn&rsquo;t have any luck with it so far. Purely hypothetically, deep networks with better gradient flow might also be able to recover more detailed and structured images. I&rsquo;ve been curious to see if networks with batch normalization or Parametric ReLUs are better at generating images since they seem to have better gradient flow, so if anyone has a pretrained caffe model with PReLUs or batch normalization, let me know!</p><p>Another detail that&rsquo;s worthy to note is that we did not optimize directly the loss layer, as the softmax denominator makes the gradient ascent put too much weight on reducing other class probabilities. Instead, we optimize the next to last layer, where we can make the gradient ascent focus exclusively on optimizing a likely image from our class.</p><p>As a final side note it&rsquo;s very interesting to compare the images AlexNet and GoogLeNet generate. While the comparison might not be entirely representative, it certainly looks like Googlenet has learned a lot more details and structure than AlexNet.</p><figure style="margin-left:auto;margin-right:auto;text-align:center;margin-bottom:1.3em"><img src="/images/alexnet_382.jpg" alt="alexnet-monkeys" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><img src="/images/deepdraw_example_382.jpg" alt="googlenet-monkeys" style="width:300px;display:inline-block;padding-left:15px;padding-right:15px"><figcaption>Generated images from class &ldquo;Squirrel monkey&rdquo; with AlexNet (left) and GoogLeNet (right).</figcaption></figure><p>Now go ahead and <a href="https://github.com/auduno/deepdraw" target="_blank" rel="noopener">try it yourself</a>! If you figure out other tricks or better choices of parameters for the gradient ascent (there almost certainly are), or just create some cool visualizations, let <a href="https://twitter.com/matsiyatzy" target="_blank" rel="noopener">me know via twitter</a>!</p><p>A big hat tip to google and their original deepdream code, as well as <a href="https://twitter.com/kcimc" target="_blank" rel="noopener">Kyle McDonald</a>, which had the original idea of gradually reducing sigma of gaussian blurring to &ldquo;wash out&rdquo; the image, and kindly shared his code.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Ever wondered what a deep neural network thinks a Dalmatian should look like? Well, wonder no more.&lt;/p&gt;

&lt;p&gt;Recently Google &lt;a href=&quot;http
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Estimation in Sequential Analysis</title>
    <link href="http://auduno.github.io/2015/01/04/estimation-in-sequential-analysis/"/>
    <id>http://auduno.github.io/2015/01/04/estimation-in-sequential-analysis/</id>
    <published>2015-01-03T23:00:00.000Z</published>
    <updated>2016-06-19T11:46:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>In the <a href="/2014/12/25/rapid-a-b-testing-with-sequential-analysis/">previous post</a> I introduced the Sequential Generalized Likelihood Ratio test, which is a sequential A/B-test that in most cases require much smaller sample sizes than the classical fixed sample-size test. In this post I’m going to explain some problems regarding estimation in sequential analysis tests in general, and how they can be solved.</p><p>Sequential analysis tests, such as the sequential GLR test I wrote about in my <a href="/2014/12/25/rapid-a-b-testing-with-sequential-analysis/">previous post</a>, allows us to save time by stopping the test early when it’s possible. However, the fact that the test can stop early has some subtle consequences for the estimates we make after the test is done. Let’s take a look at the average maximum likelihood estimate when applied to the “comparison of proportions” sequential GLR test:</p><figure class="narrowfig"><img src="/images/estimate_01.png" alt="average estimate vs true difference"></figure><p>It seems like the average estimate is slightly off - to get a better view, let’s take a look at just the bias, i.e. the average ML estimate minus the true difference :</p><figure class="narrowfig"><img src="/images/estimate_02.png" alt="bias vs true difference"></figure><p>The estimates are (almost imperceptably) biased inwards when the true difference is close to zero, biased outwards when the difference between proportions are relatively large, and then unbiased again at the extreme ends. This is quite unlike fixed sample-size tests, which have no such bias at all. The reason for this difference is that there is an interaction between the stopping time and the estimate - sequential tests stop early when our samples are more extreme than some threshold, which means that the final estimates we get more often than not will be more extreme than what is true.</p><p>This might become a bit more intuitive if we take a look at a typical sample path for the MLE and the approximate thresholds for stopping the test in terms of the MLE. In this case the true difference is 0.2, and we do a two-sided sequential GLR test with α-level 0.05, β-level 0.10 and indifference region of size 0.1 :</p><figure class="narrowfig"><img src="/images/estimate_thresholds_01.png" alt="ML sample path"></figure><p>As we collect data, the ML estimates jump quite a bit around before converging towards the true difference. As it jumps around, it's likely to cross the threshold at a higher point (as seen happening here after around 70 samples) and thus stop the test at this point. Similarly, when the true difference is close to zero, it will usually stop at values slightly closer to zero than the actual difference. What about the vanishing bias at the extremes? This is because at the most extreme values, the test will almost invariably stop at only a handful of samples, and thus the interaction between the stopping time and the estimate practically disappears.</p><p>So what can we do about this problem? Unfortunately, there is not an uniformly <em>best</em> estimator we can use as a replacement for the MLE. Some of the estimators suggested to fix the bias have much larger mean squared error than the MLE due to having larger variance. However, a simple and commonly used correction (and what we use in the sequential A/B-testing library <a href="https://github.com/auduno/seglir/" target="_blank" rel="noopener">SeGLiR</a>), is the <em>Whitehead bias-adjusted</em> estimate. The Whitehead bias-adjusted estimate is based on the fact that we know that:</p><p style="text-align:center;"><span id="tex1">E(\hat{\theta}) = \theta + b(\theta)</span></p><p>where <span id="tex7">theta</span> is the true difference, <span id="tex8">theta_hat</span> is our estimate of the difference, and <span id="tex5">b(theta)</span> is the bias of our test at <span id="tex6">theta</span>. Given an estimate <span id="tex3">theta_hat</span>, we can then find an approximately bias-adjusted estimate by solving for <span id="tex4">theta_sim</span> so that:</p><p style="text-align:center;"><span id="tex2">\tilde{\theta} + b(\tilde{\theta}) = \hat{\theta}</span></p><p>This can be found by simple simulation and some optimization. Note that there are also other alternative estimators, such as the <a href="http://www.tandfonline.com/doi/abs/10.1081/BIP-120037195?journalCode=lbps20&#.VJ7EIBdAEM" target="_blank" rel="noopener">conditional MLE</a>, but since the brute-force simulation approach to this would take <em>much</em> more time than the Whitehead bias-adjustment, it's not something I've implemented in SeGLiR currently.</p><p>One important thing to note is that the bias problem is not specific to the sequential GLR test or even sequential frequentist tests. In fact any test with a stopping rule that depends on the parameter we estimate, such as Thompson sampling with a stopping rule (as <a href="https://support.google.com/analytics/answer/2844870?hl=en&ref_topic=1745207" target="_blank" rel="noopener">used by google analytics</a>) will have the same problem. John Kruschke discusses this in the context of bayesian analysis in <a href="http://doingbayesiandataanalysis.blogspot.no/2013/11/optional-stopping-in-data-collection-p.html" target="_blank" rel="noopener">this blog post</a>.</p><h4>Precision and Confidence intervals</h4><p>So, given that we've bias-corrected the estimates, how precise are the estimates we get? Unfortunately, estimates from sequential analysis tests often are less precise than the fixed sample-size test. This is not so surprising, since the tests often stop earlier, and we thus have less data to base the estimates on. To see this for yourself, take a look at the estimates given in <a href="http://auduno.github.io/SeGLiR/demo/demo2.html">this demo</a>.</p> <p>For this reason, it is natural to ask for confidence intervals to bound the estimates in sequential analysis tests. Classical fixed sample-size tests use the normal approximation to create confidence intervals for the estimate. This is usually not possible with sequential analysis tests, since the distribution of the test statistics under a stopping rule are very complex and usually impossible to approximate by common distributions. Instead we can resort to bootstrap confidence intervals, which are simple to simulate. These are unfortunately also sensitive to the bias issues above, so the best option is to use a bias-adjusted confidence interval<a href="#footnote1"><sup>[1]</sup></a>. Note that since sequential tests stop early and we often have fewer samples, the confidence intervals will usually be wider than for the fixed sample-size test.</p><p class="footnote" id="footnote1">[1] see Davison & Hinkley : <a href="https://books.google.no/books/about/Bootstrap_Methods_and_Their_Application.html?id=4aCDbm_t8jUC&redir_esc=y&hl=en" target="_blank" rel="noopener">Bootstrap Methods and their applications</a>, chap. 5.3 for details</p><h4>P-values</h4><p>As a little aside, what about p-values, the statistic everyone loves to hate?</p><p>When doing classical hypothesis tests, p-values are usually used to describe the significance of the result we find. This is not quite as good an idea in sequential tests as in fixed sample-size tests. The reason for this is that the p-value is not uniquely defined in sequential tests. The p-value is defined as the probability that we get a result as extreme or more extreme than the one we see, given that the null-hypothesis is true. In fixed sample-size tests, a more extreme result is simply a result where the test statistic is well, more extreme. However, in the sequential setting, we also have the variable of when the test was stopped. So is a more &ldquo;extreme result&rdquo; then a test that stops earlier? Or a test that stops later, but with a more &ldquo;extreme&rdquo; test-statistic? There is no definite answer to this. In the statistical literature there are several different ways to &ldquo;order&rdquo; the outcomes and thus define what is more &ldquo;extreme&rdquo;, but unfortunately there is no consensus on which &ldquo;ordering&rdquo; is the best, which makes p-values in sequential analysis a somewhat ambiguous statistic.</p><p>Nevertheless, in SeGLiR we've implemented a p-value via simple simulation, where we assume that a more &ldquo;extreme result&rdquo; is any result where the test statistic is more extreme than our result, regardless of when the test was stopped. This is what is called a Likelihood Ratio-ordering and is the ordering suggested by Cook & DeMets in their book referenced below.</p><br><p>As we've seen in this post, estimation in sequential tests is a bit more tricky than in fixed sample-size tests. Because sequential tests use much less samples, estimates may be more imprecise, and because of the interaction with the stopping rule they tend to be biased, though there are ways to mitigate the worst effects of this. In an upcoming post, I'm planning to compare sequential analysis tests with other variants of A/B-tests such as multi-armed bandits, and give a little guide on when to choose which test. If you're interested, <a href="https://twitter.com/matsiyatzy" target="_blank" rel="noopener">follow me on twitter</a> for updates.</p><h4>References</h4><p>If you're interested in more details on estimation in sequential tests, here are some recommended books that cover this subject. While these are mostly about group sequential tests, the solutions are the same as in the case with fully sequential tests (which is what I've described in my posts).</p><ul><li>C. Jennison & B. Turnbull : <a href="https://books.google.no/books/about/Group_Sequential_Methods_with_Applicatio.html?id=qBrpTcAYtNQC" target="_blank" rel="noopener">Group Sequential Methods with Applications to Clinical Trials</a>, CRC Press 1999</li><li>T. Cook & D. DeMets : <a href="http://www.amazon.com/Introduction-Statistical-Methods-Clinical-Chapman/dp/1584880279" target="_blank" rel="noopener">Introduction to Statistical Methods for Clinical Trials</a>, Chapman & Hall/CRC Press 2007</li></ul><script>var tex1 = document.getElementById('tex1')katex.render("E(\\hat{\\theta}) = \\theta + b(\\theta)",tex1)katex.render("\\tilde{\\theta} + b(\\tilde{\\theta}) = \\hat{\\theta}",tex2)katex.render("\\hat{\\theta}",tex3)katex.render("\\tilde{\\theta}",tex4)katex.render("b(\\theta)",tex5)katex.render("\\theta",tex6)katex.render("\\theta",tex7)katex.render("\\hat{\\theta}",tex8)</script>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;In the &lt;a href=&quot;/2014/12/25/rapid-a-b-testing-with-sequential-analysis/&quot;&gt;previous post&lt;/a&gt; I introduced the Sequential Generalized Likeli
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Rapid A/B-testing with Sequential Analysis</title>
    <link href="http://auduno.github.io/2014/12/25/rapid-a-b-testing-with-sequential-analysis/"/>
    <id>http://auduno.github.io/2014/12/25/rapid-a-b-testing-with-sequential-analysis/</id>
    <published>2014-12-24T23:00:00.000Z</published>
    <updated>2016-10-18T20:15:49.000Z</updated>
    
    <content type="html"><![CDATA[<p>A common issue with classical A/B-tests, especially when you want to be able to detect small differences, is that the sample size needed can be prohibitively large. In many cases it can take several weeks, months or even years to collect enough data to conclude a test. In this post I&rsquo;ll introduce a very little known test that in many cases severely reduces the number of samples needed, namely the Sequential Generalized Likelihood Ratio Test.</p><a id="more"></a><br><p>The Sequential Generalized Likelihood Ratio test (or sequential GLR test for short) is a test that is surprisingly little known outside of statistical clinical research. Unlike classical fixed sample-size tests, where significance is only checked after all samples have been collected, this test will continously check for significance at every new sample and stop the test as soon as a significant result is detected, while still guaranteeing the same type-1 and type-2 errors as the fixed-samplesize test. This means the test could be stopped as early as after a handful of samples if there is a strong effect present.</p><p>Despite this very nice property, I couldn&rsquo;t find <em>any</em> public implementation of this test, so I&rsquo;ve created a node.js implementation of this test, <a href="https://github.com/auduno/seglir/" target="_blank" rel="noopener">SeGLiR</a>, which can easily be used in web application A/B testing. I&rsquo;ll give a brief example of usage below, but to give you some idea about the potential savings, I&rsquo;ll first show you a comparison of the needed samplesize for a fixed samplesize test versus the sequential GLR test.</p><p>The test I&rsquo;ll compare is a <em>comparison of proportions</em> test, which is commonly used in A/B-testing to compare conversion rates. We compare the tests at the same levels, &alpha;-level 0.05 and &beta;-level 0.10, and say that we want to detect a difference between proportions larger than 0.01 (in sequential analysis this is usually called an &ldquo;indifference region&rdquo; of size 0.01). Note that the expected samplesize for the sequential GLR test vary depending on the true proportions p<sub>1</sub> and p<sub>2</sub>, so we compare the samplesize at different true proportions. We&rsquo;ll first look at the case where the expected samplesize for the sequential GLR test is worst, when the proportions are closest to 0.5.</p><figure><img src="/images/expected_samplesize_fig1.png" alt="samplesize comparison"></figure><p>As you can see, the expected samplesize of the sequential GLR test is much smaller for almost any value of the true difference. The test will stop especially early when there is a large difference between the proportions, so if there is a significant advantage of choosing one of the alternatives, this can be acted upon as early as possible. Let&rsquo;s take a closer look at the expected samplesize when the differences between the true proportions are small.</p><figure><img src="/images/expected_samplesize_fig2.png" alt="samplesize comparison"></figure><p>The only case where the sample size for the sequential GLR test can be expected to be larger, is when the true difference between p<sub>1</sub> and p<sub>2</sub> is just below 0.01, i.e. the smallest difference we were interested in detecting. However, this is just when the proportions are close to 0.5. What about when p<sub>1</sub> and p<sub>2</sub> are farther from 0.5?</p><figure><img src="/images/expected_samplesize_fig3.png" alt="samplesize comparison"></figure><p>Actually, as the true p<sub>1</sub> and p<sub>2</sub> get closer to either 0 or 1, the expected samplesize will <em>always</em> be smaller than the fixed samplesize test. Since this is the <em>expected</em> samplesize, to be sure that the test doesn&rsquo;t often require a much higher samplesize, let&rsquo;s also take a look at the more extreme outcomes, for instance the 5th and 95th percentiles (with p<sub>1</sub> and p<sub>2</sub> close to 0.5 as earlier):</p><figure><img src="/images/expected_samplesize_fig4.png" alt="samplesize comparison"></figure><p>For most of the true differences the samplesize is still lower than the fixed-samplesize test, except for differences below 0.015. A good next question might be if there is a <em>bound</em> to the amount of samples we may have to collect? Actually, there exists a worst-case samplesize for the test, meaning that the test will always conclude before this point. In the example above, the worst-case samplesize, though extremely rare, is at 161103 samples. Note that there is a tradeoff between this worst-case samplesize and the size of the indifference region, which means that a smaller indifference region will lead to a larger worst-case samplesize, and a larger indifference region will lead to a smaller worst-case samplesize.</p><p>Given the very nice samplesize properties we&rsquo;ve seen above, it might not come as a surprise that the sequential GLR test has been shown<a href="#footnote1"><sup>[1]</sup></a> to be the optimal test with regards to minimizing samplesize at a given &alpha;- and &beta;-level.</p><p class="footnote" id="footnote1">[1] <a href="https://books.google.no/books?id=zhsbBAAAQBAJ&lpg=PP1&pg=PA254#v=onepage&q&f=false" target="_blank" rel="noopener">Theorem 5.4.1</a> in Tartakovsky et al, Sequential Analysis, CRC Press 2014</p><h4>Usage</h4><p>You can install <em>SeGLiR</em>, the node.js library I&rsquo;ve implemented for doing these types of tests, via node package manager : `npm install seglir`. Here&rsquo;s an example of how to set up and run a similar sequential GLR test as the one above in node.js.</p><pre class="prettyprint lang-js" style="margin-bottom:1.7em;overflow-x:scroll;">var glr = require('seglir')// set up an instance of a test, with indifference region of size 0.01,//   alpha-level 0.05 and beta-level 0.10var test = glr.test("bernoulli","two-sided",0.01,0.05,0.10)</pre><p>When setting up any statistical hypothesis test, you need to calculate the test statistic thresholds at which the null-hypothesis or the alternative hypothesis is rejected for a given &alpha;- and &beta;-level. Unfortunately, unlike the fixed samplesize tests, there is no analytical way to calculate these thresholds for the sequential GLR test, so SeGLiR will use simulation to find them. This simulation can take some time and doesn&rsquo;t always converge, so I&rsquo;ve added some precalculated thresholds for the most common levels. It probably saves a bit of time to check these precalculated thresholds in the <a href="http://auduno.github.io/SeGLiR/documentation/reference.html">reference</a> before setting up a test.</p><p>Add data as it comes in, until the instance returns either &ldquo;true&rdquo; (the null hypothesis was accepted, i.e. there is no difference between the proportions) or &ldquo;false&rdquo; (the alternative hypothesis was accepted, i.e. there is a difference between the proportions).</p><pre class="prettyprint lang-js" style="margin-bottom:1.7em;overflow-x:scroll;">test.addData({x:0,y:1})test.addData({x:0})test.addData({y:0})test.addData({x:1,y:0})// add more data until the function returns either "true" or "false"</pre><p>When the test is done, you can get estimates of the true parameters by estimate():</p><pre class="prettyprint lang-js" style="margin-bottom:1.7em;">test.estimate()</pre><p>To get more details about functions, check out the <a href="http://auduno.github.io/SeGLiR/documentation/reference.html">SeGLiR reference</a>. Try out comparing the fixed samplesize test and the sequential GLR yourself (using SeGLiR) in <a href="http://auduno.github.io/SeGLiR/demo/demo.html">this demo</a>.</p><br><p>To sum up, the sequential GLR test is an alternative to fixed samplesize tests that usually are much faster, at the cost of a large, but rare, worst-case samplesize. Another slight drawback with sequential tests is that post-analysis estimation can be a bit more tricky. I&rsquo;ll elaborate on this in my next post, as well as talk a bit about the solutions I&rsquo;ve implemented in SeGLiR. <a href="https://twitter.com/matsiyatzy" target="_blank" rel="noopener">Follow me on twitter</a> if you want to get updates!</p><br><p>If you&rsquo;re interested in a very brief introduction to the mathematical details of the sequential GLR test, take a look at the <a href="http://auduno.github.io/SeGLiR/documentation/reference.html">SeGLiR reference</a>. For a more rigorous mathematical introduction, see these excellent references:</p><ul><li>Tartakovsky, Nikiforov &amp; Basseville : <a href="http://www.amazon.com/Sequential-Analysis-Hypothesis-Changepoint-Probability/dp/1439838208" target="_blank" rel="noopener">Sequential Analysis</a>, CRC Press 2014</li><li>Bartroff, Lai &amp; Shih : <a href="http://www.amazon.com/Sequential-Experimentation-Clinical-Trials-Statistics/dp/1461461138/ref=sr_1_1?s=books&ie=UTF8&qid=1416693815&sr=1-1&keywords=sequential%20experimentation%20in%20clinical%20trialsD" target="_blank" rel="noopener">Sequential Experimentation in Clinical Trials</a>, Springer 2012</li></ul><script>$(document).ready( function(){    prettyPrint();});</script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;A common issue with classical A/B-tests, especially when you want to be able to detect small differences, is that the sample size needed can be prohibitively large. In many cases it can take several weeks, months or even years to collect enough data to conclude a test. In this post I&amp;rsquo;ll introduce a very little known test that in many cases severely reduces the number of samples needed, namely the Sequential Generalized Likelihood Ratio Test.&lt;/p&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Some nice ML-libraries</title>
    <link href="http://auduno.github.io/2014/08/29/some-nice-ml-libraries/"/>
    <id>http://auduno.github.io/2014/08/29/some-nice-ml-libraries/</id>
    <published>2014-08-28T22:00:00.000Z</published>
    <updated>2016-06-19T11:46:54.000Z</updated>
    
    <content type="html"><![CDATA[<p>I recently had a go at the Kaggle <a href="https://www.kaggle.com/c/acquire-valued-shoppers-challenge" target="_blank" rel="noopener">Acquire Valued Shoppers Challenge</a>. This competition was a bit special in that the dataset was 22 GB, one of the biggest datasets they&rsquo;ve had in a competition. While 22 GB may not quite qualify as <em>big data</em>, it&rsquo;s certainly something that your average laptop will choke on when using standard methods. Ordinarily I&rsquo;d reach for <a href="http://scikit-learn.org/stable/" target="_blank" rel="noopener">scikit-learn</a> for these tasks, but in this case some of the methods in scikit-learn were a bit slow, and some other libraries had nice methods that scikit-learn didn&rsquo;t have, so I decided to try out some other libraries. In this post I&rsquo;ll give a brief look at some of the alternative machine learning libraries that I used during the competition.</p><p>In the Kaggle challenge, the intention was to predict whether a customer would become a &ldquo;repeat buyer&rdquo; of a product after trying the product. To give some examples of usage of the libraries I&rsquo;m going through, I&rsquo;ll use the features I created for the challenge, and predict probabilities of whether the customer was a &ldquo;repeat buyer&rdquo;. To follow the examples, you can download the features <a href="https://github.com/auduno/Kaggle-Acquire-Valued-Shoppers-Challenge" target="_blank" rel="noopener">here</a> and set up the training data for the examples like this:</p><pre class="prettyprint lang-py" style="overflow-x:scroll">import pandas as pdtrain_data = pd.io.parsers.read_csv("./features/train/all_features.csv.gz", sep=" ", compression="gzip")train_label = train_data['label']del train_data['label']del train_data['repeattrips']test_data = pd.io.parsers.read_csv("./features/test/all_features.csv.gz", sep=" ", compression="gzip")del test_data['label']del test_data['repeattrips']</pre><h4>XGBoost</h4><p><a href="https://github.com/tqchen/xgboost" target="_blank" rel="noopener">XGBoost</a> (short for &lsquo;extreme gradient boosting&rsquo;) is a library solely devoted to, you guessed it, <a href="https://en.wikipedia.org/wiki/Gradient_boosting" target="_blank" rel="noopener">gradient boosting</a>. Gradient boosting tends to be a frustrating affair, since it usually performs extremely well, but can also be very slow to train. Usually you would solve this by throwing several cores at the problem and use parallelization to speed it up, but neither scikit-learn or R&rsquo;s implementation is parallellizable, and so there doesn&rsquo;t seem to be much we can do. Fortunately there does exist alternative implementations that <em>do</em> support parallelization, and one of these is XGBoost.</p><p>XGBoost has a python API, so it is very easy to integrate into a python workflow. An advantage XGBoost has compared to scikit-learn, is that while scikit-learn only has support for gradient boosting with decision trees as &ldquo;base learners&rdquo;, XGBoost also has support for linear models as base learners. In our cross-validation tests, this gave us a nice improvement in predictions.</p><p>Another nice feature XGBoost has is that it will print out prediction error on a given test set for every 10 iterations over the training set, which allows you to monitor approximately when it starts to overfit. This can be used to tune how many rounds of training you want to do (in scikit-learn this is called <em>n_estimators</em>). On the other hand, XGBoost does not have support for feature-importances calculation, but they might implement this soon (see <a href="https://github.com/tqchen/xgboost/issues/11" target="_blank" rel="noopener">this issue</a>).</p><p>In our example we first we create XGBoost train and test datasets, using the custom XGBoost DMatrix objects. We next set up our parameters: in the &ldquo;param&rdquo; dictionary, we set the <em>max_depth</em> of the decision trees, the <em>learning rate</em> of the boosting, here called <em>eta</em>, the objective of the learning (in our case logistic, since this is classification) and the number of threads we&rsquo;d like to use. Since we had four cores when running this example, we set this to four threads. The number of rounds to do is set directly when we call the train method. We train via calling <em>xgb.train()</em>, and we can then call <em>predict</em> on our returned train object to get our predictions. Simple!</p><pre class="prettyprint lang-py" style="overflow-x:scroll"># import the xgboost library from wherever you built itimport syssys.path.append('/home/audun/xgboost-master/python/')import xgboost as xgbdtrain = xgb.DMatrix( train_data.values, label=train_label.values)dtest = xgb.DMatrix(test_data.values)param = {'bst:max_depth':3, 'bst:eta':0.1, 'silent':1, 'objective':'binary:logistic', 'nthread':4, 'eval_metric':'auc'}num_round = 100bst = xgb.train( param, dtrain, num_round )pred_prob = bst.predict( dtest )</pre><figure><img src="/images/barchart1.jpg" alt="Timing"><figcaption>Comparison of training time (seconds) of Scikit-learn&rsquo;s GradientBoostingClassifier versus XGBoost</figcaption></figure><p>In our tests with four cores, it ran around four times as fast as scikit-learn&rsquo;s GradientBoostingClassifier, which probably reflects the parallellization. With more cores, this would probably allow us to speed up the training even more. For some more detailed tutorials on how to use XGBoost, take a look at the <a href="https://github.com/tqchen/xgboost/wiki" target="_blank" rel="noopener">documentation here</a>.</p><br><p>A common problem with large data sets, is that usually you need to have the training data in memory to train on it. When the data set is big, this is obviously not going to work. A solution to this is so-called out-of-core algorithms, which commonly means only looking at one example from the training set at a time, in other words keeping the training data &ldquo;out of core&rdquo;. Scikit-learn has support for out-of-core/online learning via <a href="http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.SGDClassifier.html" target="_blank" rel="noopener">SGDClassifier</a>, but in addition there also exists some other libraries that are pretty speedy:</p><h4>Sofia-ml</h4><p><a href="https://code.google.com/archive/p/sofia-ml/" target="_blank" rel="noopener">Sofia-ml</a> currently supports SVM, logistic regression or perceptron methods and uses some speedy fitting algorithms known as &ldquo;Pegasos&rdquo; (short for &ldquo;primal estimatimated sub-gradient solver for SVM&rdquo;). &ldquo;Pegasos&rdquo; has an advantage in that you do not need to define pesky parameters such as learning rate (see <a href="https://lingpipe-blog.com/2009/04/08/convergence-relative-sgd-pegasos-liblinear-svmlight-svmper/" target="_blank" rel="noopener">this article</a>). Another nice feature in sofia-ml is that it supposedly also can optimize ROC area via selecting smart choices of samples when iterating over the dataset. &ldquo;ROC area&rdquo; is also known as AUC, which happened to be the score measure in our competition (and numerous other Kaggle competitions).</p><p>Using sofia-ml is pretty straightforward, but since it is a command-line tool, it easily seems a bit esoteric for those used to scikit-learn. Before we call the training, we have to write out the data to an input format called &ldquo;SVMlight sparse data format&rdquo; which originally comes from the library <a href="http://svmlight.joachims.org/" target="_blank" rel="noopener">SMVlight</a>, but has since been adopted by a number of other machine learning libraries. In our tests, what took the longest time was actually writing out the data, so we found it very helpful to use Mathieu Blondel&rsquo;s library <a href="https://github.com/mblondel/svmlight-loader" target="_blank" rel="noopener">svmlight-loader</a>, which does the writing out in C++. Note that there are also <a href="http://scikit-learn.org/stable/modules/generated/sklearn.datasets.dump_svmlight_file.html" target="_blank" rel="noopener">tools for handling SVMlight formats</a> in scikit-learn, but they&rsquo;re not quite as fast as this one.</p><p>There is no python wrapper for sofia-ml, but it&rsquo;s quite easy to do everything from python:</p><pre class="prettyprint lang-py" style="overflow-x:scroll">from svmlight_loader import dump_svmlight_filefrom subprocess import callimport numpy as npfrom sklearn.preprocessing import StandardScaler# normalize datass = StandardScaler()train_data_norm = ss.fit_transform(train_data)test_data_norm = ss.transform(test_data)# change these filenames to reflect your system!model_file = "/home/audun/data/sofml.model"training_file = "/home/audun/data/train_data.dat"test_file = "/home/audun/data/test_data.dat"pred_file = "/home/audun/data/pred.csv"# note that for sofia-ml (and vowpal wabbit), labels need to be {-1,1}, not {0,1}, so we change themtrain_label.values[np.where(train_label == 0)] = -1# export datadump_svmlight_file(train_data_norm, train_label, training_file, zero_based=False)dump_svmlight_file(test_data_norm, np.zeros((test_data.shape[0],)), test_file, zero_based=False)</pre><p>We call the training and prediction with a python subprocess. In our first line, we specify via command-line parameters that the learner type is SVM fitted with stochastic gradient descent, use loop type ROC (to optimize for AUC), set prediction type &ldquo;logistic&rdquo; in order to get classifications, and do 200000 gradient descent updates. Many more possible command-line parameters are listed <a href="https://code.google.com/archive/p/sofia-ml/" target="_blank" rel="noopener">here</a>. In the second line we create predictions on our test data from our model file, and we then read it in again via <em>pandas</em>. Note that in the case of logistic predictions, sofia-ml returns untransformed predictions, so we need to transform the predictions via the logistic transformation to get probabilities.</p><pre class="prettyprint lang-py" style="overflow-x:scroll"># train via subprocess callcall("~/sofia-ml/sofia-ml --learner_type sgd-svm --loop_type roc --prediction_type logistic --iterations 200000 --training_file "+training_file+" --model_out "+model_file, shell = True)# create test data via subprocess callcall("~/sofia-ml/sofia-ml --model_in "+model_file+" --test_file "+test_file+" --results_file "+pred_file, shell = True)# read in test datapred_prob = pd.io.parsers.read_csv(pred_file, sep="\t", names=["pred","true"])['pred']# do logistic transformation to get probabilitiespred_prob = 1./(1.+np.exp(-pred_prob))</pre><p>In our tests, fitting using Sofia-ml was extremely speedy, around 3 seconds!</p><h4>Vowpal Wabbit</h4><p>This is probably the most well-known library to do fast out-of-core learning, and operates pretty similarly to sofia-ml. Vowpal Wabbit has support for doing SVM, logistic regression, linear regression and quantile regression via optimizing for respectively hinge loss, logit loss, squared loss and quantile loss. Since Vowpal Wabbit is written in C++, carefully optimized and has some tricks up it&rsquo;s sleeve, it&rsquo;s very fast and performs very competitively on a lot of tasks.</p><p>Vowpal Wabbit, like sofia-ml, is a command line program, and uses a <a href="https://github.com/JohnLangford/vowpal_wabbit/wiki/Input-format" target="_blank" rel="noopener">slight modification</a> of the SVMlight sparse data format for input. Since the differences between SVMlight and Vowpal Wabbits format were pretty small, we used the svmlight-loader library here as well, and modified the files to suit Vowpal Wabbit afterwards.</p><p>At the time of the competition, I didn&rsquo;t find any python wrappers, but it seems there is now a python wrapper <a href="https://github.com/JohnLangford/vowpal_wabbit/blob/master/python/pyvw.py" target="_blank" rel="noopener">under development here</a>. It&rsquo;s not documented yet, so I&rsquo;ll just use regular python methods to call Vowpal Wabbit in this example. First we have to write out training and test data:</p><pre class="prettyprint lang-py" style="overflow-x:scroll">training_file = "/home/audun/data/vw_trainset.csv"training2_file = "/home/audun/data/vw_trainset2.csv"test_file = "/home/audun/data/vw_testset.csv"test2_file = "/home/audun/data/vw_testset2.csv"pred_file = "/home/audun/data/pred.csv"model_file = "/home/audun/data/vw_trainset_model.vw"dump_svmlight_file(train_data, train_label, training_file, zero_based=False)dump_svmlight_file(test_data, np.zeros((test_data.shape[0],)), test_file, zero_based=False)# add specifics for vowpal wabbit formatimport stringfi = open(training_file,"r")of = open(training2_file,"w")for lines in fi:li = lines.strip().split()of.write( li[0] )of.write(" | ")of.write( string.join(li[1:]," ") + "\n")of.close()fi.close()fi = open(test_file,"r")of = open(test2_file,"w")for lines in fi:li = lines.strip().split()of.write( li[0] )of.write(" | ")of.write( string.join(li[1:]," ") + "\n")of.close()fi.close()</pre><p>We then do a subprocess call to run Vowpal Wabbit from the command line. There are a lot of possible parameters to the command line, but all of them are listed <a href="https://github.com/JohnLangford/vowpal_wabbit/wiki/Command-line-arguments" target="_blank" rel="noopener">here</a>. The first line trains a model with logistic loss (i.e. for classification) on our training set, doing 40 passes over the data. The second line predicts data from our testset, based on our trained model, and writes the predictions out to a file.</p><pre class="prettyprint lang-py" style="overflow-x:scroll"># traincall("~/vowpalwabbit/vw "+training2_file+" -c -k --passes 40 -f "+model_file+" --loss_function logistic", shell=True)# predictcall("~/vowpalwabbit/vw "+test2_file+" -t -i "+model_file+" -r "+pred_file, shell=True)</pre><p>Next, we load the predictions from the output file. Note that like with sofia-ml the predictions need to be logistic transformed to get probabilities.</p><pre class="prettyprint lang-py" style="overflow-x:scroll">pred_prob = pd.io.parsers.read_csv(pred_file, names=["pred"])['pred']pred_prob = 1./(1.+np.exp(-pred_prob))</pre><p>Training is very fast, around 9 secs, even though the dataset is sizable. For a more in-depth tutorial on how to use Vowpal Wabbit take a look at <a href="https://github.com/JohnLangford/vowpal_wabbit/wiki/Tutorial" target="_blank" rel="noopener">the tutorial</a> in their github repo.</p><br><p>So there you go, some nice, not-so-well-known machine learning libraries! In the competition overall, with the help of these libraries, I managed to end up in the top 10%, and together with my 4th place in the earlier loan default prediction competition, this earned me a &ldquo;kaggle master&rdquo; badge.</p><figure><img src="/images/badge_crop.jpg" alt="Kaggle master badge"><figcaption>Kaggle master badge.</figcaption></figure><p>If you know of any other unknown but great libraries, let me know. And If you liked this blogpost, you should <a href="https://twitter.com/matsiyatzy" target="_blank" rel="noopener">follow me on twitter!</a></p><script>$(document).ready( function(){    prettyPrint();});</script>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;I recently had a go at the Kaggle &lt;a href=&quot;https://www.kaggle.com/c/acquire-valued-shoppers-challenge&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Acq
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Twisting faces</title>
    <link href="http://auduno.github.io/2014/04/29/twisting-faces/"/>
    <id>http://auduno.github.io/2014/04/29/twisting-faces/</id>
    <published>2014-04-28T22:00:00.000Z</published>
    <updated>2017-05-01T06:43:09.000Z</updated>
    
    <content type="html"><![CDATA[<p>In the <a href="/post/61888277175/fitting-faces">previous post</a>, I explained how <a href="https://github.com/auduno/clmtrackr" target="_blank" rel="noopener">CLMtrackr</a> was put together. Since then, my examples of <a href="https://auduno.github.io/clmtrackr/examples/facesubstitution.html">face substitution</a> and <a href="https://auduno.github.io/clmtrackr/examples/clm_emotiondetection.html">emotion detection</a> has received a fair amount of attention, so in this post I&rsquo;m going to explain a bit about how these are put together as well, plus comment on some fixes that I&rsquo;ve done to CLMtrackr recently.</p><h4>Face substitution</h4><p>This demo was inspired by a <a href="https://vimeo.com/29348533" target="_blank" rel="noopener">face substitution demo</a> by Arturo Castro &amp; Kyle McDonald. Basically it substitutes, or overlays, another persons face over your face, and does some fancy tricks to make it look natural. To do this with CLMtrackr, we first have to annotate the face in the image we want to substitute, and we can then deform this face (using <a href="https://github.com/auduno/clmtrackr/blob/dev/js/face_deformer.js" target="_blank" rel="noopener">face_deformer.js</a>) to the same shape as your face, and overlay it in the exact same pose and position.</p><figure><img src="/images/post_deform3.jpg" alt="Deformed face"><figcaption>An annotated face in an image, the normalized face, and the face deformed to another position.</figcaption></figure><p>But in order to make it look natural (or creepy, as some would say), we also have to use a method called <em>poisson blending</em>. Usually, when you paste one image onto another, it&rsquo;s easy to tell that there&rsquo;s been a copy-paste operation, since the colors of the edges of the pasted image won&rsquo;t quite match up with the background.</p><figure><img src="/images/poisson_example.jpg" alt="Poisson blending with a fighter jet"><figcaption>A fighter jet copy-pasted into an image, without poisson blending (left) and with poisson blending (right).</figcaption></figure><figure><img src="/images/poisson_face2.jpg" alt="Poisson blending with a fighter jet"><figcaption>An applied face mask without poisson blending (left) and with poisson blending (right).</figcaption></figure><p>Poisson blending counteracts this, by smoothing the <em>color gradients</em> on the edges of the pasted image with the background image, so that the transformation from one image to the other will look smooth. We also then have to change the gradients of the rest of the pasted image, so we end up with a huge differential equation that needs to be solved. Thankfully, I didn&rsquo;t have to implement the algorithms for solving this myself, since <a href="https://twitter.com/wellflat" target="_blank" rel="noopener">&lsquo;wellflat&rsquo;</a> had already <a href="https://github.com/wellflat/imageprocessing-labs/tree/master/cv/poisson_blending" target="_blank" rel="noopener">implemented it in javascript</a>. Kudos to him! The poisson blending for the most part works very well, and you get a seamless blend of the two images. Note that since the poisson blending takes a bit of time in javascript, I only do the blending on the initialization of the image (i.e. when switching faces). This means that if you change the lighting after switching faces, the blending might look a bit off. If you&rsquo;re interested in some more info about poisson blending, see for instance <a href="http://www.ctralie.com/Teaching/PoissonImageEditing/" target="_blank" rel="noopener">this article</a>.</p><!--[ I've put up an example of using any image for face substitution. It can be a bit tricky to get it to work, but try it out. ]--><h4>Emotion detection</h4><p>For the emotion detection demo, I used a pretty basic classification method called <a href="https://en.wikipedia.org/wiki/Logistic_regression" target="_blank" rel="noopener">logistic regression</a>. We already have a parametric model of the face, so we can use the parameters of the model as features. For training, we annotate images of people expressing the emotions we are interested in and project these annotations onto our PCA decomposition (as described in the previous post) to get the closest parametrization. These parameters are then input as training data for the regression. The classification works relatively OK, but a better method would be to first establish some neutral &ldquo;baseline&rdquo; for each person before classifying, since there is some variation from person to person which throws off the classification.</p><p>Another classification solution might be to use random forests, (which <a href="https://github.com/karpathy/forestjs" target="_blank" rel="noopener">happens to be implemented</a> in javascript). This usually gives better classification results, but probably is a bit slower, so I didn&rsquo;t try it out. Since most of the emotion classifiers are only trained on 20 or so positive examples, we would also probably get much better classification with more data. Code for training your own classifier with logistic regression <a href="https://github.com/auduno/clmtools/blob/master/pdm_builder/create_classifier.py" target="_blank" rel="noopener">is here</a>, so give it a spin if you&rsquo;re interested in improving it!</p><p>A fun side effect of the emotion classifier is that we can illustrate the learned emotions by using the regression coefficients as parameters for our facial model:</p><figure><img src="/images/emotions.jpg" alt="the face model"><figcaption>From top left: Anger, Disgust, Fear, Happiness, Surprise, Sadness</figcaption></figure><p>Some of these learned emotions look very similar, which caused the classifier to have a hard time distinguishing them. Interestingly, we can also negate the coefficients to see what the opposites of the learned emotions look like:</p><figure><img src="/images/emotions_neg.jpg" alt="the face model"><figcaption>From top left, the opposites of: Anger, Disgust, Fear, Happiness, Surprise, Sadness</figcaption></figure><p>Play with the visualizations of the learned emotion model <a href="http://auduno.github.io/clmtrackr/examples/classviewer.html">here</a></p><p>The classification method is not only restricted to emotions, so we could also try to classify whether a person is male or female. Try out a demo of this <a href="https://auduno.github.io/clmtrackr/examples/clm_genderdetection.html">here</a>, though note that it&rsquo;s not really that accurate. Below are the resulting faces from the learned gender classifier:</p><figure><img src="/images/post_gender2.jpg" alt="the face model"><figcaption>Male (left), Female (right)</figcaption></figure><h4>Deforming the face</h4><p>Some other toy examples I&rsquo;ve added is <a href="https://auduno.github.io/clmtrackr/examples/facedeform.html">live face deformation</a> and <a href="https://auduno.github.io/clmtrackr/examples/caricature.html">live &ldquo;caricatures&rdquo;</a>.</p><figure><img src="/images/post_facedeformation2.jpg" alt="Deformed face"><figcaption>Deformed face</figcaption></figure><p>Both of these demos are based on capturing your face, deforming the face in some way, and pasting it back over your original face. The caricature demo was fairly easy to put together - the parameters in our parametric model of face describe the &ldquo;offsets&rdquo; from a mean face, meaning that these offsets distinguish any face from an &ldquo;average face&rdquo;. We can use this to create very simple &ldquo;caricatures&rdquo;, where we exaggerate the difference from the mean face by multiplying the parameters, and then overlay the deformed face with the new parameters over the original video. We can of course also modify (add constant offsets to) the parameters manually, i.e. deform your own face in realtime, which gives rise to the face deformation demo.</p><h4>Improvements</h4><p>As I discussed doing in my previous blog post, I&rsquo;ve also added <a href="https://en.wikipedia.org/wiki/Local_binary_patterns" target="_blank" rel="noopener">local binary patterns</a> and <a href="https://en.wikipedia.org/wiki/Sobel_operator" target="_blank" rel="noopener">sobel gradients</a> as preprocessing for responses. Especially local binary patterns seem to be more precise than raw responses, at the cost of some slowdown (due to need to preprocess patches). Since they&rsquo;re slower, they&rsquo;re not used by default, so you&rsquo;ll have to enable them on initialization if you want to use them. Check out the reference for documentation on how to enable the different types of responses. There&rsquo;s also the possibility to <em>blend</em> or <em>cycle</em> through different types of responses, which in theory might improve precision, a la <a href="https://en.wikipedia.org/wiki/Ensemble_learning" target="_blank" rel="noopener">ensemble models</a>. Try out the different responses and combinations <a href="https://auduno.github.io/clmtrackr/examples/clm_video_responses.html">here</a>.</p><br><p>In other news, CLMtrackr was used in this years april fools on reddit : <a href="http://www.redditblog.com/2014/03/headdit-revolutionary-new-way-to-browse.html" target="_blank" rel="noopener">&ldquo;headdit&rdquo;</a>. For an april fools, the gesture recognition worked surprisingly well, though I&rsquo;ll admit to not throwing away my mouse and keyboard just yet.</p><p>If you liked this blogpost, you should <a href="https://twitter.com/matsiyatzy" target="_blank" rel="noopener">follow me on twitter!</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;In the &lt;a href=&quot;/post/61888277175/fitting-faces&quot;&gt;previous post&lt;/a&gt;, I explained how &lt;a href=&quot;https://github.com/auduno/clmtrackr&quot; target=
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Fitting faces</title>
    <link href="http://auduno.github.io/2014/01/05/fitting-faces/"/>
    <id>http://auduno.github.io/2014/01/05/fitting-faces/</id>
    <published>2014-01-04T23:00:00.000Z</published>
    <updated>2018-05-19T08:13:56.365Z</updated>
    
    <content type="html"><![CDATA[<p>A while ago I put out a semi-finished version of <a href="https://github.com/auduno/clmtrackr" target="_blank" rel="noopener">CLMtrackr</a>, which is a javascript library for fitting a facial model to faces in images or video. Fitting a facial model is useful in cases where you need precise positions of facial features, such as for instance emotion detection, face masking and person identification. Though the tracking is pretty processor-intensive, we manage to reach real-time tracking in modern browsers, even though it&rsquo;s implemented in javascript. If you&rsquo;re interested in seeing some applications of CLMtrackr, check out the demos of <a href="https://auduno.github.io/clmtrackr/examples/facesubstitution.html">face substitution</a>, <a href="https://auduno.github.io/clmtrackr/examples/facedeform.html">face deformation</a>, or <a href="https://auduno.github.io/clmtrackr/examples/clm_emotiondetection.html">emotion detection</a>.</p><p>In this post, I&rsquo;ll explain a few details about how CLMtrackr is put together.</p> <p>First off, here&rsquo;s an example of CLMtrackr tracking a face real-time:</p><figure><iframe src="https://player.vimeo.com/video/75659453" width="360" height="281" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen></iframe><figcaption>An example of fitting/tracking a face in the canonical <a href="http://www-prima.inrialpes.fr/FGnet/data/01-TalkingFace/talking_face.html" target="_blank" rel="noopener">talking face</a> video</figcaption></figure><h4>How does CLMtrackr work:</h4><p>CLMtrackr is based on the algorithms described in <a href="http://www.ri.cmu.edu/pub_files/2009/9/CameraReady-6.pdf" target="_blank" rel="noopener">this paper</a> by Jason Saragih &amp; Simon Lucey, more precisely &ldquo;Face Alignment through Subspace Constrained Mean-Shifts&rdquo;. The explanation in the paper is pretty dense, so I&rsquo;ll try to do a simpler explanation here.</p><p>Our aim is to fit a facial model to a face in an image or video from an approximate initialization. In our case the facial model consists of 70 points, see below.</p><figure><img src="/images/facemodel.png" alt="the face model"><figcaption>The facial model</figcaption></figure><p>The algorithm fits the facial model by using 70 small classifiers, i.e. one classifier for each point in the model. Given an initial approximate position, the classifiers search a small region (thus the name &lsquo;local&rsquo;) around each point for a better fit, and the model is then moved incrementally in the direction giving the best fit, gradually converging on the optimal fit.</p><figure><img src="/images/facemodel_w_filters_crop1.jpg" alt="image"><figcaption>The model with some of the linear kernels used for classification</figcaption></figure><p>I&rsquo;ll go on to describe the facial model and classifiers and how we create/train them.</p><h4>Model</h4><p>A face is relatively easy to model, since it doesn&rsquo;t really vary that much from person to person apart from posture and expression. Such a model <em>could</em> be manually built, but it is far easier to learn from annotated data, in our case faces where the feature points have been marked (annotated). Since annotating faces takes a surprisingly long time, we used some existing annotation from <a href="http://www.milbo.org/muct/" target="_blank" rel="noopener">the MUCT database</a> (with slight modifications), plus some faces we manually annotated ourselves.</p><figure><img src="/images/annotated2_small.jpg" alt="annotated face"><figcaption>An annotated face from the MUCT database</figcaption></figure><p>To build a model from these annotations, we use Principal Component Analysis, or PCA for short. We first calculate the mean points of all the annotations, and then use PCA to extract the variations of the faces as linear combinations of vectors, or <strong>components</strong>. Very roughly explained, PCA will extract these <strong>components</strong> in order of importance, i.e. how much of the variation in face can be accounted for by each component. Since the first handful of these components manage to cover most of the variation in face postures, we can toss away the rest without any loss in model precision.</p><p>The first components that PCA extract will usually cover basic variations from posture, such as yaw, pitch, then followed by opening and closing mouth, smile, etc.</p><figure><iframe src="https://www.auduno.com/clmtrackr/docs/param_model/clm_pca.html" width="500" height="300" style="border:0px;"></iframe><figcaption>The first of the extracted components</figcaption></figure><p>Any facial pose can then be modelled as the mean points plus weighted combinations of these components, and the weights can be thought of as &ldquo;parameters&rdquo; for the facial model. Check out the complete model <a href="https://auduno.github.io/clmtrackr/examples/modelviewer_pca.html">here</a></p><p>From the PCA, we also store the eigenvalues of each component, which tells us the standard deviation of the weights of each component according to the facial poses in our annotated data <a href="#footnote1"><sup>[1]</sup></a>, which is very useful when we want to regularize the weights in the optimization step.</p><p><em>Note</em> : PCA is not the only method you can use to extract a parametric face model. You could also use for instance Sparse PCA which will lead to &ldquo;sparse&rdquo; transformations. Sparse PCA doesn&rsquo;t give us any significant improvements in fitting/tracking, but often gives us components which seem more natural, which is useful for adjusting the regularization of each components weights manually. Test out a parametric face model based on <a href="https://auduno.github.io/clmtrackr/examples/modelviewer_spca.html">Sparse PCA</a>.</p><p class="footnote" id="footnote1">[1] : this also means that it is important that the faces used for training the model is a good selection of faces in a variety of different poses and expressions, otherwise we end up with a model which is too strictly regularized and doesn&rsquo;t manage to model &ldquo;extreme&rdquo; poses</p><h4>Patches</h4><p>As I mentioned, we have one classifier for each each point in the model, so 70 classifiers altogether for our model. To train these classifiers, say for instance the classifier for point 27 (the left pupil), we crop a X by X patch centered on the marked position of point 27 in each of our annotated facial images. This set of patches are then used as input for training a classifier.</p><figure><img src="/images/training_patches.png" alt="training"><figcaption>Training a classifier on patches from the annotated faces</figcaption></figure><p>The classifier we use could be any classifier suited for image classification, such as Logistic Regression, SVM, regular Correlation Filters and even Random Forests, but in our case we implemented a SVM classifier with an linear kernel (which is what the original paper suggests), and also a MOSSE filter. More about implementation issues of these below.</p><p>When using these classifiers in fitting the model, we crop a searchwindow around each of our initial approximate positions, and apply the respective classifier to a grid of Y by Y pixels within the searchwindow. We thus get a Y * Y &ldquo;response&rdquo; output which maps the probability of each of these pixels being the &ldquo;aligned&rdquo; feature point.</p><figure><img src="/images/response_fig2b.jpg" alt="the response from the patch"><figcaption>The cropped patch and &ldquo;response&rdquo; for the left pupil</figcaption></figure><h4>Optimization</h4><p>So, given that we have the responses from our classifiers, how do we apply this information to fit the facial model in the best possible way? </p><p>For each of the responses, we calculate the way the model should move in order to go to the region with highest likelihood. This is calculated by mean-shift (which is roughly equivalent to gradient descent). We then regularize this movement by constraining the &ldquo;new positions&rdquo; to the coordinate space spanned by the facial model. In this way we ensure that the points of the model does not move in a manner that is inconsistent with the model overall. This process is done iteratively, which means the facial model will gradually converge towards the optimal fit <a href="#footnote2"><sup>[2]</sup></a>.</p><p class="footnote" id="footnote2">[2] : this happens to be a case of expectation-maximization, where finding the best movement according to responses is the expectation step and regularization to model is the maximization step</p><h4>Initalization &amp; checking</h4><p>One thing that has to be noted is that since the searchwindows we use are pretty small, the model is not able to fit to a face if is outside the &ldquo;reach&rdquo; of these searchwindows <a href="#footnote3"><sup>[3]</sup></a>. Therefore it is critical that we initialize the model in a place not too far from it&rsquo;s &ldquo;true&rdquo; position. To do so, we first use a face detector to find the rough bounding box of the face, and then identify the approximate positions of eyes and nose via a correlation filter. We then use procrustes analysis to roughly fit the mean facial model to the found positions of the eyes and nose, and use this as the initial placement of the model.</p><p>Altogether, this is what initialization and fitting looks like when slowed down:</p><figure><img src="/images/anim1.gif" alt="fitting the face model"><figcaption>The initialization and fitting of the face model</figcaption></figure><p>While we&rsquo;re tracking and fitting the face, we also need to check that the model hasn&rsquo;t drifted too far away from the &ldquo;true&rdquo; position of the face. A way to do this, is to check once every second or so that the approximate region that the face model covers, seems to resemble a face. We do this using the same classifiers as on the patches, logistic regression, only trained on the entire face. If the face model does not seem to be on top of a face, we reinitialize the face detection.</p><p class="footnote" id="footnote3">[3] : we could of course just make the searchwindows bigger, but every pixel we widen the searchwindow increases the time to fit exponentially, so we prefer to use small windows</p><h4>Performance/implementation issues</h4><p>The straightforward implementation of this algorithm in javascript is pretty slow. The main bottleneck is the classifiers which are called several times for each point in the model on every iteration. Depending on the size of the searchwindow (n) and the size of the classifier patches (m), the straightforward implementation is an O(m<sup>2</sup> * n<sup>2</sup>) operation. Using convolution via FFT we can bring it down to O(n log(n)), but this is still slower than what we want. Fortunately, the linear kernels lends itself excellently to fast computation via the GPU, which we can do via WebGL, available on most browsers these days. Of course, webGL was never meant to be used for scientifical computing, only graphical rendering, so we have to jump through some hoops to get it to work.</p><p>The main problem we have is that while most graphic cards support floating point calculations and we can easily <em>import</em> floating points to the GPU, there is no way to <em>export</em> floating point numbers back to javascript in WebGL. We are only able to <em>read</em> the pixels (which only support 8-bit ints) rendered by the GPU to the canvas. To get around this, we have to use a trick : we &ldquo;pack&rdquo; our 32-bit floats into four 8-bit ints, &ldquo;export&rdquo; them by drawing them to canvas, then read the pixels and &ldquo;unpack&rdquo; them back into 32-bit floats again on the javascript side. In our case we split the floats across each of the four channels (R,G,B,A), which means that each rendered pixel holds one float. Though this seems like a lot of hassle for some performance tweaks, it&rsquo;s worth it, since the WebGL implementation is twice as fast as the javascript implementation.</p><figure><img src="/images/packing.png" alt="packing and unpacking floats in the GPU"><figcaption>Packing and unpacking the floats from the GPU</figcaption></figure><p>Once we get the responses, we have to deal with the matrix math in order to do regularization. This is another bottleneck, and really exposes the huge differences in speed of numerical computing between the javascript engines of the different browsers. I used the excellent library &ldquo;numeric.js&rdquo; to do these calculations - it currently seems to be the fastest and most full-featured matrix library out there for javascript, and I highly recommend it to anyone thinking of doing matrix math in javascript.</p><p>In our final benchmark, we managed to run around 70 iterations of the algorithm (with default settings) per second in Chrome, which is good enough to fit and track a face in real-time.</p><h4>Improvements</h4><p>CLMtrackr is by no means perfect, and you may notice that it doesn&rsquo;t fit postures that deviates from the mean shape all that well. This is due to the classifiers not being discriminate enough. We tried training the classifiers on the gradient of the patches, but this is slower and not all that much better overall. Optimally each response would be an ensemble of SVM, gradient and local binary filters (which I never got around to implementing), but for the current being, this would probably run too slow. If you have some ideas to fix this, let me know!</p><p>Another improvement which might improve tracking is using a 3D model instead of a 2D model. Creating a 3D model is however a more difficult task, since it involves inferring a 3D model from 2D images, and I could never get around to implementing it.</p><p>Oh, and there&rsquo;s also things such as structured SVM learning, but that will have to wait until another time.</p><br><p>Have you used CLMtrackr for anything cool? Let me know! If you liked this article, you should <a href="https://twitter.com/matsiyatzy" target="_blank" rel="noopener">follow me on twitter</a>.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;A while ago I put out a semi-finished version of &lt;a href=&quot;https://github.com/auduno/clmtrackr&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CLMtrackr&lt;/
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Building a budgeting service</title>
    <link href="http://auduno.github.io/2013/04/07/building-a-budgeting-service-pt2/"/>
    <id>http://auduno.github.io/2013/04/07/building-a-budgeting-service-pt2/</id>
    <published>2013-04-06T22:00:00.000Z</published>
    <updated>2016-06-19T11:47:47.000Z</updated>
    
    <content type="html"><![CDATA[<p>As I wrote in <a href="/post/40422171982/building-a-budgeting-service">my last blog post</a>, around 3 years ago I decided to try to build a budgeting service like <a href="https://www.mint.com/" target="_blank" rel="noopener">mint.com</a> for the norwegian market. After around a year, having reached the prototype stage, I decided to take a short break from further building, to think about the business details. This quickly turned into an &hellip; extended break.</p> <p>In this post I&rsquo;ll write out some of the reasons I stopped working on it, and finally, some lessons learned.</p><p>During the process of mocking up the prototype, I&rsquo;d already considered some business models:</p><ul><li><h5>lead generation for loans, savings and credit card accounts</h5>    <p>In other words, getting a payment from the banks in return for sending users to one of their banking services. This is the business model that mint.com used, where they would recommend you banking services that might save you money. Of these, lead generation for loans probably is the most profitable, as loans are a large part of the business model for banks, and they&rsquo;re willing to pay quite a bit for leads.</p></li><li><h5>third-party solution for banks</h5>    <p>This would mean selling the service of scraping and categorization to the banks as a plugin service in their own online banks. This didn&rsquo;t seem all that feasible to me, since a majority of banks already used a third-party service (<a href="https://www.evry.com/" target="_blank" rel="noopener">EVRY</a>, formerly known as EDB) to run their online banks, and these were likely to want to make it themselves instead of buying it from another third party. As I later discovered, the few banks that actually ran their own online banks, mostly the larger banks (DnB, Gjensidige, etc.), were already building budgeting services themselves.</p>    </li><li><h5>freemium solution (&ldquo;pay for premium&rdquo;-solution)</h5>    <p>This is not a model I gave a lot of thought, since it was unlikely that the amount of users that were willing to pay for this services in Norway was large enough.</p>    </li></ul><p>The lead generation business model seemed less risky, so that was my main plan. However, there were a lot of problems with the business model that I never managed to solve.</p><h4>Profitability</h4><p>My main worry was the economic feasibility. Scraping all the different online banks demanded that we always keep the scraper up to date, which meant a lot of manual maintenance. The big question was whether the costs spent on maintenance would be balanced by profits from lead generation. This was really hard to answer since I didn&rsquo;t know exactly how much maintenance was needed, or how much the banks were willing to pay for leads. In a presentation held by Aaron Patzer of mint.com, he mentioned that they had a revenue of around 30$ per user. The revenue probably would have been higher in Norway, but I was never sure how many users we&rsquo;d get. Norway is after all a considerably smaller market than the US. Even though we managed to get a considerable share of the potential market size, given around 30$ per user we&rsquo;d only be talking about a couple of million NOK in revenue, which is not a lot, considering maintenance costs.</p><h4>Marketing costs</h4><p>The second big worry was how to get people use this service. Most Norwegians have never heard of budgeting services, which means it would be a problem to get people to try it at the outset. This would be especially hard since the service asked them to log in to their bank account, something that no other service in Norway (as far as I know) asks you to do. We would have to do quite a lot of outreach to ensure that users were certain that the service was safe. The best bet would probably be to market the service through personal economy sites, such as <a href="http://www.dinepenger.no/" target="_blank" rel="noopener">dinepenger.no</a>, which already had a number of economy calculators and manual budgeting tools, and somehow get them to vouch for the service. Any kind of certification, such as TRUSTe, Verisign, and RSA, would probably also help here.</p><h4>Legality</h4><p>Though it wasn&rsquo;t strictly illegal to scrape the account statements from online banks, a lot of the banks actually had clauses in their terms of services stating that the user was not allowed to give any third parties access to their online bank accounts. Any scraping done on remote servers would of course be a breach of these terms of services. Though it was deemed unlikely that the banks actually would do any counteractions against users allowing this (it would after all be the users, not us, they would be targeting legally), there was a major risk in that they might make life hard for us (i.e. making it harder to scrape) and that they might claim our services were insecure. On our side, we might claim that the users had a right to own their own information and that the banks were just trying to stop users from finding cheaper banking services. Some people I discussed this with, suggested that the banks were unlikely to say anything publicly, as <em>any</em> kind of discussion around the security of the banks would be negative PR for the banks. It <a href="http://www.dn.no/tekno/2012/06/13/-blir-du-svindlet-sa-star-du-personlig-ansvarlig" target="_blank" rel="noopener">turned out later</a> that &ldquo;finanstilsynet&rdquo; (which is Norways&rsquo; higher authority for banks) actually <em>were</em> willing to warn against giving up your information to these kinds of services in very negative tones, so the worry wasn&rsquo;t exactly unwarranted. Given this kind of pushback, it would probably have been an uphill battle.</p><br><br><p>Altogether, these issues, especially concerns around profitability and legality, made me uncertain whether it really was worthwhile to continue with the project.</p><p>The real reason I stopped working on it, though, was that I really, really needed a break. By the time the prototype was done, I&rsquo;d been working on this project in most of my spare time for a year. While the plan was to take a break from building to figure out whether the business was really profitable, I was too fatigued with the whole project, and though I thought about it from time to time, I didn&rsquo;t really make a serious effort. The short break quickly grew into months, and I gradually started thinking about other projects.</p><p>So, here&rsquo;s some of my lessons learned:</p><h4>Lessons learned</h4><ul><li><h5>Figure out the business model first</h5><p>Though it&rsquo;s fun building stuff and you learn a lot doing it, if you end up building something that never earns money, you&rsquo;re either in the not-for-profit business or you&rsquo;ve wasted your time. Earning money (at least enough to keep day-to-day operations running) is and should be priority number one for any startup, so you should focus on that as early as possible. The issues with legality and maintenance cost vs profit, are actually something that I could have figured out before I started building it.</p></li><li><h5>Involve more people at an early stage.</h5><p>Having someone to discuss with and share the stress (and victories) with, is worth way more than you might think. Other people might add knowledge or points of view you don&rsquo;t have, and you&rsquo;re probably likely to pick up on problems with your business model earlier, though that depends on how balanced you are as a team. In my case it would have been optimal to work with someone else that had experience with the banking business, as they could tell me more about the profitability aspects around lead generation. Including other people also meant it would have been easier to keep up motivation, or at least spin the project into something else, which partly was the reason I stopped.</p></li></ul><h4>Postscript</h4><p>In hindsight, an automated personal budgeting service for the Norwegian market is probably unviable. Mint.com had a lot of advantages in that they were buying the transaction feeds from a third party, <a href="http://www.yodlee.com/" target="_blank" rel="noopener">Yodlee</a>, since they didn&rsquo;t directly have to deal with maintenance costs or legality issues. The only way that I see such a service could be viable in Norway, is if the banks start supporting a common API to get transaction information. This would considerably lower maintenance costs and stop any legality concerns. However, this is unlikely to be initiated by the banks, so it would probably have to be enforced through some sort of regulation. There are other services based on lead generation that are viable though, and since I stopped working on my project some have turned up.</p><img src="/images/penger.no_front_crop.jpg" alt="image"><p>The site <a href="https://www.penger.no/" target="_blank" rel="noopener">penger.no</a>, which has simplified applying for loans from several banks at once, has abandoned the entire personal budgeting service and gone directly for the lead generation. Instead of getting information about users from their personal budgets, they&rsquo;ve simply asked the users to punch in the details themselves. The only drawback I can think of with this, is that the leads might be less qualified (the banks get less real information about income and spending), and thus banks might pay less for the leads.</p><p>Penger.no have also solved a lot of the issues with marketing, since the service is partially owned by <a href="http://www.dinepenger.no/" target="_blank" rel="noopener">dinepenger.no</a> and <a href="http://www.finn.no/" target="_blank" rel="noopener">finn.no</a>. Dinepenger.no is able to give it credibility and means it can reach out to exactly those users that are interested in this kind of service, while ownership by finn.no (one of the top ten sites in Norway) means that they can advertise cheaply on pages of finn.no.</p><p>Some banks, such as <a href="https://www.dnb.no/" target="_blank" rel="noopener">DnB</a>, <a href="https://skandiabanken.no/" target="_blank" rel="noopener">Skandiabanken</a> and <a href="https://www.storebrand.no/" target="_blank" rel="noopener">Storebrand</a>, have also implemented their version of budgeting tools as part of their online banking services. I haven&rsquo;t seen any that I&rsquo;m really satisfied with in terms of user experience and integration, though. These tools are of course also only based on transactions from the accounts the user has in these banks, so they do not give you the complete overview of your economy that I was interested in (unless you have all finance information (stocks, loans, savings, debit account, BSU) in one bank). What would have been really interesting, is an online bank where the budgeting integration was really thought through, like what <a href="https://www.simple.com/" target="_blank" rel="noopener">Simple</a> seem to be building in the US. I can only hope that some banks over here (or a startup) will try to to copy what they&rsquo;re doing. Meanwhile, it looks like I&rsquo;ll have to resort to spreadsheets for my complete budgeting needs&hellip;</p><br><p>If you liked this article, you should <a href="https://twitter.com/matsiyatzy" target="_blank" rel="noopener">follow me on twitter</a>.</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;As I wrote in &lt;a href=&quot;/post/40422171982/building-a-budgeting-service&quot;&gt;my last blog post&lt;/a&gt;, around 3 years ago I decided to try to buil
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Building a budgeting service</title>
    <link href="http://auduno.github.io/2013/01/21/building-a-budgeting-service/"/>
    <id>http://auduno.github.io/2013/01/21/building-a-budgeting-service/</id>
    <published>2013-01-20T23:00:00.000Z</published>
    <updated>2016-06-19T11:47:58.000Z</updated>
    
    <content type="html"><![CDATA[<p>So, it&rsquo;s been over a year since I &ldquo;took a break&rdquo; from working on my stealth <s>startup</s> project, and I guess it&rsquo;s safe to say that I&rsquo;m not going to pick it up again. Around 3 years ago, inspired by the success of the personal budgeting service <a href="https://www.mint.com/" target="_blank" rel="noopener">mint.com</a> in the US, and wanting something similar myself, I started investigating possibilities for making a personal budgeting service for the norwegian market. I ended up working on the project in my spare time for over a year.</p><p>In this post I&rsquo;ll go through the challenges I encountered, some of the solutions, and in a later post I&rsquo;ll go through the reasons I stopped working on it, and some lessons learned.</p><p>In short, I decided to prototype a web service for personal budgeting, i.e. setting up an overview of how much money you spend each month, how you spend it, tips for spending less, as well as other useful information. The budget was supposed to be set up automatically (as <a href="http://www.mint.com/" target="_blank" rel="noopener">mint.com</a> did) based on transaction information from users&rsquo; bankaccount statements. In order to do this, my web service had to pull the transaction information from the banking websites, categorize the transactions (in order to find out how money was spent), and finally present the aggregated information in a sensible way to the user.</p><p>It was not obvious that this would be possible at all when I started investigating it, since dealing with norwegian banks have some specific challenges that I&rsquo;ll get to below. I anyway started mocking up a prototype around the spring of 2010, and ended up working on it until the fall of 2011.</p><h4>The prototype</h4><p>The working title was, pretty arbitrarily, &ldquo;Nano&rdquo;, and this is what the final prototype looked like (click image for slideshow):</p><figure><a id="images" href="javascript:;" target="_blank" rel="noopener"><img src="/images/Oversikt_thumb_400.jpg" alt="image"></a><figcaption>Click image for slideshow</figcaption></figure><p>The resulting web service was actually able to pull down transaction information from an users&rsquo; bankaccount (after the user had provided login information), categorize the transactions, and present a very simple overview of trends and expenses. It was neither polished nor perfect, but it managed to do what it was supposed to.</p><p>The main challenges in building the web service was getting the transaction details from the banks and managing to categorize the transactions based on the relatively limited information we got. I&rsquo;ll go through how I solved these here.</p><h4>Getting the transactions</h4><p>From what I could gather, the way <a href="https://www.mint.com/" target="_blank" rel="noopener">mint.com</a> (or rather, <a href="http://www.yodlee.com/" target="_blank" rel="noopener">Yodlee</a>) collected information from the bank accounts of users was by a mixture of <a href="https://en.wikipedia.org/wiki/Open_Financial_Exchange" target="_blank" rel="noopener">existing financial APIs</a>, and simply scraping the users&rsquo; bank account using login username and password that the user shared with mint. It was, unfortunately, not straightforward to do the same in Norway.</p><p>Norwegian banks have no APIs to access bank account information, at least not with details such as historical transactions. Most banks allow you to download your account information in excel format when you&rsquo;re logged in, but there is no API to do so for third parties, and getting users to download and upload the excel sheets to the web service manually was not really an option.</p><p>As for scraping the websites, unlike the web banking solutions in the US, where username and password is sufficient to get complete access to a users&rsquo; bank account details, scandinavian banks all have <a href="https://en.wikipedia.org/wiki/Two-factor_authentication" target="_blank" rel="noopener">two-factor authentication</a> (called <a href="https://www.bankid.no/" target="_blank" rel="noopener">BankID</a>). Two-factor authentication usually means that in addition to a password, you also need input from something the user has, usually a code-chip or a challenge/response code-card. This is much more secure, but unfortunately makes logging into banks without having the code-chip or code-card impossible, so just passing the username and password to a remote server and letting it do the scraping would not be possible.</p><p>To get around this, the easiest idea I could come up with, was to simply open the bank website in a small iframe <em>inside</em> our web service, expose the bank&rsquo;s own login mechanism directly to the user, let the user log in, and then use javascript/DOM events to scrape the bankaccount and send the information to our server in the background. This actually worked great for a few months, the only disadvantage being that the user had to wait while the scraper did its work in the background, and could not close the browser window while it was going on.</p><figure><img src="/images/BankID_crop.jpg" alt="The iframed BankID login"><figcaption>The iframed BankID login</figcaption></figure><p>Unfortunately, as I painfully discovered a few months later, the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options" target="_blank" rel="noopener">X-Frame-Options response header</a> had just became a semi-standard and trickled into most browsers around this time. This header enabled site owners to specify whether it was allowed to &ldquo;frame&rdquo; their website inside another page. Not surprisingly, most banks promptly specified that this should not be allowed, so I had to start from scratch. In hindsight, I&rsquo;m surprised this was possible at all when I started, as it was a massive opportunity to spoof banking sites and manipulate users into giving away their login information, if used maliciously.</p><p>As a quick fix, I tried to use extensions to modify the X-Frame-Options headers and work around the restrictions. Though I managed to do it, it proved to only be possible in Firefox, so I discarded it as an option. Also, getting the user to install an extension as the first step of the web service would probably make for truly horrible conversion rates.</p><p>Since I couldn&rsquo;t do the scraping inside the users&rsquo; browser, the only option was then to anyway try to do the scraping remotely. I would still have to expose the login mechanism to the user somehow, though. I originally thought about trying to expose it via remote display (such as VNC), but found that a much more robust solution was to simply mirror the login mechanism instead. This was not trivial, as <a href="https://www.bankid.no/" target="_blank" rel="noopener">BankID</a>, the two-factor authentication mechanism used in Norway, is implemented as a Java plugin, which means you can&rsquo;t use regular DOM APIs for interacting with it. As such, any automated login couldn&rsquo;t be done with regular javascript web automation tools (such as Selenium). Instead, I ended up using <a href="http://www.sikuli.org/" target="_blank" rel="noopener">Sikuli</a>, which is an automation tool based on OCR and image recognition. This worked surprisingly well, the result was that the login information would be passed to the remote server, and any type of BankID challenge could be channeled back to the user and responded to in a timely manner. After the login was done, the scraping could continue remotely.</p><figure><img src="/images/Sikuli_script.jpg" alt="The iframed BankID login"><figcaption>Sikuli scripting with image recognition</figcaption></figure><p>In the end I had a mechanism that was relatively painless for the user. On first using the web service, and whenever the user wanted to update with most recent transaction information, the user would log in to their bank via an interface that was similar to BankID, and the remote server would then take over and scrape all details. After scraping was done on the server, the transaction information was passed back to the webserver, where it would be categorized and exposed to the user.</p><p>The main drawback was that there was no way to update the transaction information at a later stage without the user logging in to the bank again. Mint.com&rsquo;s mobile app enabled you to view your always updated account information and budget while on the go, but this would not be possible here. I speculated that it might have been possible to never log out of the bank on the remote server, keep the browsing session open <em>forever</em>, and then just scrape whenever we needed it, but this sounded a bit too fragile, and banks would probably have put an end to it as soon as they discovered it. As I started work on the web service, there was some testing of BankID on mobile, which might have been feasible to use for a mobile app, but given that it was (and still is) only available to <em>some</em> banks and phone operators, I never tested it out.</p><h4>Classification</h4><p>Once I&rsquo;d managed to scrape the transaction details from the users&rsquo; bank accounts, we needed to classify the transactions, which was by far the most interesting part of the work. Most transactions looked like this: the transaction amount, the type of transaction (visa, sales, giro or otherwise) and a character string (the &ldquo;vendor id&rdquo;) which served to identify the vendor where the transaction was done. The challenge was then to use these details to classify the transaction as specific expenses, such as <em>food</em>, <em>gym</em>, <em>gas</em>, <em>cinema</em>, etc.</p><p>From what I could deduce, the format of the vendor ids was supposed to be something like this:</p><img src="/images/vendorformat1.png" alt="The vendor ID format"><p>The major portion of transactions were from pretty well known norwegian chains, such as &ldquo;REMA 1000&rdquo;, &ldquo;ICA&rdquo; &amp; &ldquo;Clas Ohlson&rdquo;, which means it was trivial to identify these (and the corresponding category) with a simple lookup. The rest, though, were tricky. When the vendor was not a major chain, we needed to get the address in order to do a yellow pages lookup.</p><p>Judging from the format above, we should be able to <a href="https://en.wikipedia.org/wiki/Tokenization" target="_blank" rel="noopener">tokenize</a> the strings and pull out the address very easily. That, however, often proved to be problematic. Here are some examples of vendor ids from transactions:</p><ul style="list-style-type : none">  <li>28.01 T BIL ARKITEKT STE OSLO</li>  <li>23.01 ICA Supermarked  Alexsa OSLO</li>  <li>23.02 FLYTOGET1021015 OSLO S 245012</li>  <li>08.02 KIWI STORGATEN  . .</li>  <li>07.02 ORANGE MINI SEN UELANDSGT 61 OSLO</li>  <li>26.03 POINT NAT JERNBANE 0161 OSLO</li>  <li>15.02 JAVA ULLEV&Aring;LSVEIN OSLO</li>  <li>17.09 OLYMPEN, MOB 1  GRNLANDSL 15 0194 OSLO</li>  <li>01.02 R&Oslash;TTERNATURPROD ULLEV&Aring;LSVN.3 OSLO</li>  <li>23.02 CG 0130 KJ&Oslash;KKEN KJ&Oslash;KKENAVD UETG</li>  <li>22.06 Kl&oslash;fta &Oslash;st Bili stsente KL&Oslash;FTA</li>  <li>16.11 SHELL AS   AS</li>  <li>15.05 ST.HANSHAUGEN ULLEV&Aring;LSVN.  OSLO</li></ul><p>Since each field had character limits, a lot of long street names or company names were abruptly cut short or creatively shortened (such as <em>grnlandsl</em> to mean <em>gr&oslash;nlandsleiret</em>). Company names and adresses could be concatenated. Street numbers and zip codes might or might not be present in almost any field. Some just wrote the address, not the vendor name. Some didn&rsquo;t write the address. Some vendor ids were so misspelled that I can only assume the vendor was under the influence while punching it in.</p><p>Misspellings were relatively easy to solve with <a href="https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance" target="_blank" rel="noopener">edit-distance</a>, but in order to figure out what was feasible edits, we needed to look up all known possible addresses, placenames and zip-codes, which fortunately was provided for free in a downloadable database-format by <a href="http://www.bring.no/" target="_blank" rel="noopener">Posten</a>. With a liberal amount of lookups in this database, we very often could figure out the most likely tokenization and corresponding address and vendor. There was quite a lot of manual tuning involved to make it work optimally, though.</p><p>What I didn&rsquo;t have access to, was how probable each address or place was, which might have helped a lot for ambiguous addresses. Going forward, I could probably have used some sort of public register to calculate population density for each address/region and learned how probable each feasible address was this way.</p><p>Anyhow, once I had the top 10 most likely address and vendor names, I could easily do a lookup in yellow pages and see there what type of business the vendor was registered under, making it easy to classify.</p><br><p>All around I managed to get to around 85% classification error with this method, on a limited set of transactions (my own, plus transactions from some friends). In a real transaction list most transactions were usually from major chains (REMA 1000, Kiwi, ICA, etc), so classification would probably be correct somewhere around 90-95% of the time. The rest we would have to ask the user to categorize.</p><p>Using external lookup web services, such as yellow pages, would probably not have been feasible on scale, since some of them I&rsquo;d have to pay quite a bit for. Categorization would also have taken way too long time this way. Going further, I probably would have started out seeding the database with user data and input from external services and used this as training input to a machine learning classifier, which could then be used to try to categorize the vendors based on address and name. If we had very low confidence in some classification, we could resort to more complex processing involving yellow pages as last resort. In a real system, we would also learn from input from users, which would help greatly in categorizing ambiguous vendor ids.</p><br><p>In <a href="/post/47357960809/building-a-budgeting-service-pt-2">my next post</a>, I go through some of the reasons I stopped working on the prototype.</p><br><p>If you liked this article, you should <a href="https://twitter.com/matsiyatzy" target="_blank" rel="noopener">follow me on twitter</a>.</p><script type="text/javascript">  window.onload = function() {  $("a#images").click(function() {    $.fancybox(      [        {          'href' : '/images/Mainpage_comp.jpg',          'title' : 'The landing page'        },        {          'href' : '/images/Oversikt1_fix_comp.jpg',          'title' : 'Overview of aggregated balance'        },        {          'href' : '/images/Kontoer1_comp.jpg',          'title' : 'Overview of accounts'        },        {          'href' : '/images/Transaksjon1_fixed_comp.jpg',          'title' : 'Overview of transactions and classifications'        },        {          'href' : '/images/Utgifter1_comp.jpg',          'title' : 'Piechart overview of categorized expenses'        },        {          'href' : '/images/Utgifter2_comp.jpg',          'title' : 'Barchart overview of categorized expenses'        },        {          'href' : '/images/Firstscreen_fix_comp.jpg',          'title' : 'First page for new users'        },        {          'href' : '/images/Firstscreen1_login_comp.jpg',          'title' : 'First page for new users, with iframed BankID login'        }      ],{      'padding'         : 0,      'transitionIn': 'none',      'transitionOut': 'none',      'type' : 'image',      'changeFade' : 0      }    )  });}</script>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;So, it&amp;rsquo;s been over a year since I &amp;ldquo;took a break&amp;rdquo; from working on my stealth &lt;s&gt;startup&lt;/s&gt; project, and I guess it&amp;rsqu
      
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Head tracking with WebRTC</title>
    <link href="http://auduno.github.io/2012/06/15/head-tracking-with-webrtc/"/>
    <id>http://auduno.github.io/2012/06/15/head-tracking-with-webrtc/</id>
    <published>2012-06-14T22:00:00.000Z</published>
    <updated>2016-06-19T11:48:11.000Z</updated>
    
    <content type="html"><![CDATA[<iframe src="https://player.vimeo.com/video/44049736" width="500" height="281" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen></iframe><p>A lot of new exciting standards are coming to browsers these days, among them the <a href="https://www.w3.org/TR/webrtc/" target="_blank" rel="noopener">WebRTC standard</a>, which adds support for streaming video and audio from native devices such as a webcamera. One of the exciting things that this enables, is so called <em>head tracking</em>. We decided to do a little demonstration of this for the <a href="http://www.opera.com/computer" target="_blank" rel="noopener">Opera 12</a> release, which is the first desktop browser to support video-streaming via the getUserMedia API.</p><p>If you haven&rsquo;t tried our fancy game out already, do so here:</p><p><a href="http://www.shinydemos.com/facekat/" alt="facekat" target="_blank" rel="noopener"><img src="/images/facekat2.png" alt="facekat"></a></p><p>The demo in the topmost video can be found <a href="https://auduno.github.io/headtrackr/examples/targets.html">here</a>, though note that this needs WebGL support as well. Both demos work best if your camera is mounted over your screen (like internal webcameras on most laptops) and when your face is evenly lighted. And of course you have to have <a href="http://caniuse.com/#feat=stream" target="_blank" rel="noopener">a browser that supports getUserMedia</a> and a computer with a webcamera.</p><p>The javascript library which I made for the task, <em>headtrackr.js</em>, is now available freely <a href="https://github.com/auduno/headtrackr/" target="_blank" rel="noopener">here</a>. It&rsquo;s not currently well documented, but I&rsquo;ll try to do so in the coming weeks. In this post I&rsquo;ll give you a very rough overview of how it&rsquo;s put together.</p><p>My implementation of head tracking consists of four main parts:</p><ul><li>a face detector</li><li>a tracking mechanism</li><li>a smoother</li><li>the headposition calculation</li></ul><p><img src="/images/figure3d.png" alt="diagram"></p><p>For the face detection, we use an existing javascript library called <a href="https://github.com/liuliu/ccv" target="_blank" rel="noopener">ccv</a>. This library uses a <a href="https://en.wikipedia.org/wiki/Viola%E2%80%93Jones_object_detection_framework" target="_blank" rel="noopener">Viola-Jones type</a> algorithm (with <a href="http://liuliu.me/eyes/javascript-face-detection-explained/" target="_blank" rel="noopener">some modifications</a>) for detecting the face, which is a very fast and reasonably precise face detection algorithm. We could have used this to detect the face in every videoframe, however, this would probably not have run in real-time. It also would not have been able to detect the face in all positions, for instance if the head was tilted, or turned slightly away from the camera.</p><p>Instead we use a more lightweight object tracking algorithm called <em>camshift</em>, which we initialize with the position of the face we detected. The camshift algorithm is an algorithm that tracks any object in an image (or video) just based on its color histogram and the color histogram of the surrounding elements, see <a href="http://www.cognotics.com/opencv/servo_2007_series/part_3/sidebar.html" target="_blank" rel="noopener">this article</a> for details. Our javascript implementation was ported from an <a href="http://www.libspark.org/browser/as3/FaceIt/trunk/src/org/libspark/faceit/camshift?rev=2813" target="_blank" rel="noopener">actionscript library called FaceIt</a>, with some modifications. You can test the camshift-algorithm alone <a href="https://auduno.github.io/headtrackr/examples/camshift.html">here</a>.</p><p>Though the camshift algorithm is pretty fast, it&rsquo;s also a bit unprecise and will jump a bit around, which can cause annoying jittering of the face tracking. Therefore we apply a smoother for each position we receive. In our case we use double exponential smoothing, as it&rsquo;s pretty easy to calculate.</p><p>We now know the approximate position and size of the face in the image. In order to calculate the position of the head, we need to know one more thing. Webcameras have widely differing angles of &ldquo;field of view&rdquo;, which will affect the size and position of the face in the video. For an example, see the image below (courtesy of <a href="https://www.flickr.com/photos/freeparking/507248108/" target="_blank" rel="noopener">D Flam</a>). To get around this, we estimate the &ldquo;field of view&rdquo; of the current camera, by assuming that the user at first initialization is sitting around 60 cms away from the camera (which is a comfortable distance from the screen, at least for laptop displays), and then seeing how large portion of the image the face fills. This estimated &ldquo;field of view&rdquo; is then used for the rest of the head tracking session.</p><p><a href="https://www.flickr.com/photos/freeparking/507248108/" target="_blank" rel="noopener"><img src="/images/fov_56_70.png" alt="Image courtesy of http://www.flickr.com/people/freeparking/"></a></p><p>Using this &ldquo;field of view&rdquo;-estimate, and some assumptions about the average size of a person&rsquo;s face, we can calculate the distance of the head from the camera by way of some trigonometry. I won&rsquo;t go into the details, but here&rsquo;s a figure. Hope you remember your maths!</p><p><img src="/images/trig01.png" alt="trigonometry diagram"></p><p>Calculating the x- and y-position relative to the camera is a similar exercise. At this point we have the position of the head in relation to the camera. In the <a href="http://www.shinydemos.com/facekat/" target="_blank" rel="noopener">facekat demo</a> above, we just used these positions as the input to a mouseEvent-type controller.</p><p>If we want to go further to create the <a href="https://en.wikipedia.org/wiki/Head-coupled_perspective" target="_blank" rel="noopener"><em>head-coupled perspective</em></a> seen in the first video, we&rsquo;ll have to use the headpositions to directly control the camera in a 3D model. To get the completely correct perspective we also have to use an off-axis view (aka <em>asymmetric frustum</em>). This is because we want to counteract the distortion that arises when the user is looking at the screen from an angle, perhaps best explained by the figure below.</p><p><img src="/images/offaxisFigure.png" alt="off-axis view diagram"></p><p>In our case we used the excellent 3D library <a href="https://github.com/mrdoob/three.js/" target="_blank" rel="noopener">three.js</a>. In three.js it&rsquo;s pretty straightforward to create the off-axis view if we abuse the interface called <em>camera.setViewOffset</em>.</p><p>Overall, the finished result works decently, at least if you have a good camera and even lighting. Note that the effect looks much more convincing on video, as we then have no visual cue for the depth of the other objects in the scene, while in real life our eyes are not so easily fooled.</p><p>One of the problems I stumbled upon while working with this demo, was that the quality of webcameras vary widely. Regular webcameras often have a lot of chromatic aberration on the edges of the field of view due to cheap lenses, which dramatically affects the tracking effectiveness outside of the immediate center of the video. In my experience the built-in cameras on Apple Macbooks had very little such distortion. You get what you pay for, I guess.</p><p>Most webcameras also adjust brightness and whitebalance automatically, which in our case is not very helpful, as it messes up the camshift tracking. Often the first thing that happens when video starts streaming is that the camera starts to adjust whitebalance, which means that we have to check that the colors are stable before doing any sort of face detection. If the camera adjusts the brightness a lot after we&rsquo;ve started tracking the face, there&rsquo;s not much we can do except reinitiate the face detection.</p><p><br></p><p>To give credit where credit is due, the inspiration for this demo was <a href="https://www.youtube.com/embed/Jd3-eiid-Uw" target="_blank" rel="noopener">this video</a> that was buzzing around the web a couple of years ago. In it, Johnny Chung Lee had hacked a Wii remote to capture the motions of the user. Later on, <a href="http://iihm.imag.fr/en/demo/hcpmobile/" target="_blank" rel="noopener">some french researchers</a> decided to try out the same thing without the Wii remote. Instead of motion sensors they used the front-facing camera of the Ipad to detect and track the rough position of the head, with pretty convincing results. The result is available as the Ipad app <a href="http://iihm.imag.fr/francone/i3D/" target="_blank" rel="noopener">i3D</a> and can be seen here:</p><iframe width="450" height="253" src="https://www.youtube.com/embed/19XZJa15hOs" frameborder="0" allowfullscreen></iframe><p>Although head-coupled perspective might not be ready for any type of generic interaction via the web camera <em>yet</em>, it works fine with simple games like <a href="http://www.shinydemos.com/facekat/" target="_blank" rel="noopener"><em>facekat</em></a>. I&rsquo;m sure there are many improvements that can make it more precise and failproof, though. The library and demos were patched together pretty fast, and there are several improvements that I didn&rsquo;t get time to test out, such as:</p><ul><li>tweaking the settings of the camshift algorithm</li><li>using other tracking algorithms, such as <a href="http://www.robinhewitt.com/pubs/BayesShiftTracker.pdf" target="_blank" rel="noopener">bayesian mean shift</a>, which also uses information about the background immediately surrounding the face</li><li>maybe using edge detection to further demarcate the edges of the face, though this might be a bit heavy on processing</li><li>using requestAnimationFrame instead of setIntervals</li><li>using hue and saturation for the camshift algorithm (which the original camshift paper suggests) instead of RGB</li></ul><p>If you feel like implementing any of these, feel free to <a href="https://github.com/auduno/headtrackr" target="_blank" rel="noopener">grab a fork</a>! Meanwhile, I&rsquo;m pretty sure we&rsquo;ll see many more exciting things turn up once WebRTC becomes supported across more browsers, check out <a href="https://vimeo.com/41666669" target="_blank" rel="noopener">this</a> for instance&hellip;</p><p><strong>Update</strong>: a slightly edited version of this post, which also includes some more details about the trigonometry calculations, was <a href="https://dev.opera.com/articles/head-tracking-with-webrtc/" target="_blank" rel="noopener">published at dev.opera.com</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;iframe src=&quot;https://player.vimeo.com/video/44049736&quot; width=&quot;500&quot; height=&quot;281&quot; frameborder=&quot;0&quot; webkitallowfullscreen=&quot;&quot; mozallowfullscreen=&quot;
      
    
    </summary>
    
    
  </entry>
  
</feed>
