<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Sasha Kuznetsov's Blog</title>
    <description>Sasha Kuznetsov's experiments and explanations in machine learning, computer vision, quantitative finance, and software, with interactive demos and side projects.</description>
    <link>https://blog.skz.dev/</link>
    <atom:link href="https://blog.skz.dev/feed.xml" rel="self" type="application/rss+xml" />
    <language>en-US</language>
    
      
        <item>
          <title>Interactive AI Paper Explainers with intuitivepapers.ai</title>
          <description>&lt;p class=&quot;description&quot;&gt;
    Can we use AI to explain machine learning papers properly, with the prerequisites, math, and interactive figures?
&lt;/p&gt;

&lt;p&gt;When I read a machine learning paper, I often spend a lot of time looking up the ideas it builds on. A paper describes a new contribution, but understanding that contribution may require working through several other papers first.&lt;/p&gt;

&lt;p&gt;I sometimes ask an LLM to explain a paper to me. This is useful for getting started, but I have found that the explanations can sound convincing while getting important details wrong. A sign in an equation, whether a result is exact or approximate, or which variable is being updated can change the meaning quite a bit. And if I don’t already understand the paper, these errors are difficult to notice.&lt;/p&gt;

&lt;p&gt;I wanted explanations that build up the intuition and math together, with figures I could interact with. I’ve explored this on a smaller scale in my &lt;a href=&quot;/gradient-descent&quot;&gt;gradient descent demo&lt;/a&gt;: changing a parameter and watching what happens can make an algorithm much easier to understand.&lt;/p&gt;

&lt;h2 id=&quot;starting-with-one-paper&quot;&gt;Starting With One Paper&lt;/h2&gt;

&lt;p&gt;I first worked on an &lt;a href=&quot;https://intuitivepapers.ai/diffusionblocks/&quot;&gt;explainer of the DiffusionBlocks paper&lt;/a&gt; with Claude. This took about a week of iteration. Getting the math right meant going back to the original sources, and in the process I found what appeared to be a sign error in the paper’s equations.&lt;/p&gt;

&lt;p&gt;This made me wonder how much of the process I could automate. Could I put together a system that reads a paper and its references, works through the math, creates interactive figures, and checks the resulting explanation?&lt;/p&gt;

&lt;p&gt;The result is &lt;a href=&quot;https://intuitivepapers.ai/&quot;&gt;intuitivepapers.ai&lt;/a&gt;. At the time of the original post, I had used it to produce twenty explainers, covering papers such as Attention, BERT, GANs, VAEs, CLIP, PPO, Mamba, and Adam.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/intuitivepapers/home.png&quot; alt=&quot;intuitivepapers.ai homepage with interactive machine learning paper explainers&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
The intuitivepapers.ai homepage.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/intuitivepapers/library.png&quot; alt=&quot;Library of twenty AI paper explainers with figure previews&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
The paper library. Each card previews a figure from the corresponding explainer.
&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;interactive-explanations&quot;&gt;Interactive Explanations&lt;/h2&gt;

&lt;p&gt;Each explainer starts with the prerequisites and works towards the paper’s main idea. A navigation panel, called the concept tower, lists these concepts in the order they depend on each other. Where it helps, the explanation also includes code to connect the math to an implementation.&lt;/p&gt;

&lt;p&gt;The figures are interactive. For example, PPO uses a clipped objective to limit the incentive for certain changes to a policy. In the figure below, you can change the probability ratio and the sign of the advantage, and see where the objective becomes flat. This makes it easier to see when clipping takes effect than by looking at the equation alone.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/intuitivepapers/ppo-figure.png&quot; alt=&quot;Interactive PPO clipped objective showing the probability ratio and advantage&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Exploring PPO's clipped objective by changing the probability ratio and the sign of the advantage.
&lt;/p&gt;

&lt;p&gt;The figures also work on a phone:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/intuitivepapers/mobile-ppo.png&quot; alt=&quot;PPO paper explainer with an inline figure on a phone&quot; height=&quot;520px&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
The PPO explainer on a phone.
&lt;/p&gt;

&lt;p&gt;Another example is CLIP, which learns to associate images with text. The grid below shows the scores for pairs of images and captions. The diagonal contains the matching pairs; the other cells are mismatches. We can use this to see what the training objective is trying to achieve.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/intuitivepapers/clip-grid.png&quot; alt=&quot;CLIP similarity grid comparing images with matching and mismatched captions&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
CLIP's image and caption scores. The matching pairs lie along the diagonal.
&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;generating-an-explainer&quot;&gt;Generating an Explainer&lt;/h2&gt;

&lt;p&gt;Asking an LLM to explain a paper in one pass wasn’t sufficient for what I wanted. I split the work into stages: reading the sources, checking the claims and equations, designing the figures, writing the explanation, and reviewing the draft.&lt;/p&gt;

&lt;p&gt;The checks use the paper itself, the primary literature it refers to, and the authors’ code. Separate critic passes then look for problems in the draft before it is published. The idea is to make verification an explicit part of the process, rather than assume that a plausible explanation is correct.&lt;/p&gt;

&lt;p&gt;The pipeline runs on a timer on the Mac mini under my desk. It takes the next paper from a queue and works through these steps, including deploying the finished page, mostly without my involvement. Feedback submitted by readers is used to correct the page and update the instructions for future explainers.&lt;/p&gt;

&lt;p&gt;Below is another example of the output, showing the pieces of a Mamba block. The library had over a hundred custom figures when I put this post together, drawn using canvas rather than a charting library.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/intuitivepapers/mamba-block-fig.jpeg&quot; alt=&quot;Diagram showing the components of a Mamba block&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
A breakdown of a Mamba block, from the Mamba explainer.
&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;final-notes&quot;&gt;Final Notes&lt;/h2&gt;

&lt;p&gt;This started with a paper I wanted to understand, and became an experiment in automating the work of explaining one. Checking the sources is useful, but it doesn’t make an automatically generated explanation infallible. The feedback and correction process is part of the project too.&lt;/p&gt;

&lt;p&gt;If there’s a paper you’ve been meaning to work through, take a look at &lt;a href=&quot;https://intuitivepapers.ai/&quot;&gt;intuitivepapers.ai&lt;/a&gt;. You can request a paper or upvote one that’s already in the queue. And if you notice an error in an explanation, please send feedback through the page so it can be corrected.&lt;/p&gt;
</description>
          <pubDate>Fri, 05 Jun 2026 00:00:00 -0700</pubDate>
          <link>https://blog.skz.dev/intuitivepapers-ai-interactive-paper-explainers</link>
          <guid isPermaLink="true">https://blog.skz.dev/intuitivepapers-ai-interactive-paper-explainers</guid>
        </item>
      
    
      
        <item>
          <title>ccmux: Managing AI Coding Agents Across Devices</title>
          <description>&lt;p class=&quot;description&quot;&gt;
    A tmux session manager I built with friends to keep track of coding agents across my machines, and use them from my phone.
&lt;/p&gt;

&lt;p&gt;I often have three or four coding agents running across different machines. Keeping them running is easy enough with tmux, but keeping track of them is another problem. Which machine did I start that task on? Is the agent still working, or has it been waiting for me to answer a question?&lt;/p&gt;

&lt;p&gt;I also wanted to interact with these agents from my phone. If a task is running on my Mac mini, I should be able to check on it and respond without going back to my desk.&lt;/p&gt;

&lt;p&gt;So, together with Daniel Mandragona and Mike Choi, I built &lt;a href=&quot;https://ccmux.ai/&quot;&gt;ccmux&lt;/a&gt;: a tmux session manager that collects coding agent sessions from multiple machines into one terminal interface. Below, I’ll walk through how it works and show some examples.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/ccmux/hero.gif&quot; alt=&quot;ccmux terminal interface showing coding agent sessions across projects&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
A tour of ccmux, with five agent sessions running across four projects.
&lt;/p&gt;

&lt;h2 id=&quot;keeping-track-of-sessions&quot;&gt;Keeping Track of Sessions&lt;/h2&gt;

&lt;p&gt;The dashboard lists the sessions and shows whether each agent is active, idle, or needs input. It also shows the other machines on the network, and tracks Claude’s usage quota.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/ccmux/dashboard.gif&quot; alt=&quot;ccmux dashboard with session status, connected devices, and Claude usage&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
The dashboard. Colours indicate the state of each session; the panels on the right show devices and usage.
&lt;/p&gt;

&lt;p&gt;To open a session, select it and press Enter. This attaches to the agent’s terminal. When you’re done, press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl-b d&lt;/code&gt; to detach and return to the dashboard. The agent continues running on the machine where it started.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/ccmux/attach-detach.gif&quot; alt=&quot;Attaching to a tmux session and returning to the ccmux dashboard&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Attaching to a session, then detaching with Ctrl-b d. tmux keeps the session alive in between.
&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h2&gt;

&lt;p&gt;There are a few pieces to this. tmux keeps the terminal sessions alive, including when you disconnect. &lt;a href=&quot;https://tailscale.com/&quot;&gt;Tailscale&lt;/a&gt; connects the machines through a private network, so they can reach each other even when they aren’t on the same Wi-Fi. The terminal interface is built with &lt;a href=&quot;https://charm.sh/&quot;&gt;Charm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each machine runs a daemon that keeps track of its sessions. ccmux queries these daemons to build the dashboard, and connects to the selected session over SSH or Mosh. The actual agent stays on its original machine; we’re connecting to its terminal from somewhere else.&lt;/p&gt;

&lt;p&gt;Claude Code is the default agent, but ccmux also supports Codex, Cursor, Antigravity, and others. The session management works the same way for each of them, since they run inside tmux.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;using-it-from-a-phone&quot;&gt;Using It From a Phone&lt;/h2&gt;

&lt;p&gt;Phone access was a big part of why I wanted to build this. However, a phone is a somewhat awkward SSH client: connections can drop when it switches networks or goes to sleep. &lt;a href=&quot;https://mosh.org/&quot;&gt;Mosh&lt;/a&gt; helps with this by allowing the connection to recover after an interruption.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/ccmux/phone.gif&quot; alt=&quot;ccmux dashboard in a narrow mobile terminal&quot; height=&quot;420px&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
The terminal interface at phone width, connecting over Mosh.
&lt;/p&gt;

&lt;p&gt;The same interface works in an iPhone terminal, as shown below. Notifications are useful here too: with a compatible terminal app, I can be notified when an agent needs input instead of repeatedly checking on it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/ccmux/moshi.png&quot; alt=&quot;ccmux running in the Moshi terminal app on an iPhone&quot; height=&quot;520px&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
ccmux running in Moshi on an iPhone.
&lt;/p&gt;

&lt;p&gt;We also built a native iOS app that connects to the same daemon over the Tailscale network. It has a session dashboard and an interactive terminal, which connects to tmux through a WebSocket bridge.&lt;/p&gt;

&lt;div style=&quot;text-align:center&quot;&gt;
&lt;img src=&quot;/assets/img/ccmux/ios-dashboard.png&quot; alt=&quot;ccmux iOS app listing coding agent sessions&quot; height=&quot;460&quot; style=&quot;display:inline-block;margin:0 6px&quot; /&gt;
&lt;img src=&quot;/assets/img/ccmux/ios-terminal.png&quot; alt=&quot;Interactive tmux terminal in the ccmux iOS app&quot; height=&quot;460&quot; style=&quot;display:inline-block;margin:0 6px&quot; /&gt;
&lt;/div&gt;

&lt;p class=&quot;caption&quot;&gt;
The native iOS app: session dashboard on the left, terminal on the right.
&lt;/p&gt;

&lt;p&gt;There are two input modes: Raw mode for interacting with the terminal directly, and Chat mode for composing messages to the agent.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/ccmux/ios-mode-toggle.gif&quot; alt=&quot;Switching between Raw and Chat input modes in the ccmux iOS app&quot; height=&quot;460px&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Switching between terminal input and composing a message.
&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;final-notes&quot;&gt;Final Notes&lt;/h2&gt;

&lt;p&gt;I used ccmux daily for months, and other people started using it too. In the meantime, a lot of tools for managing coding agents appeared, including projects with funding and much larger teams. We decided to move on rather than try to keep up with those as a three-person side project.&lt;/p&gt;

&lt;p&gt;I’m glad we built it. It solved a problem I had, and I wanted to put together this post to document the result.&lt;/p&gt;

&lt;p&gt;The site is still at &lt;a href=&quot;https://ccmux.ai/&quot;&gt;ccmux.ai&lt;/a&gt;, and the &lt;a href=&quot;https://github.com/skzv/ccmux&quot;&gt;source is available on GitHub&lt;/a&gt; under FSL-1.1-MIT. If you’d like to try it, the &lt;a href=&quot;https://ccmux.ai/docs/&quot;&gt;setup instructions&lt;/a&gt; cover installation and connecting your machines.&lt;/p&gt;
</description>
          <pubDate>Fri, 05 Jun 2026 00:00:00 -0700</pubDate>
          <link>https://blog.skz.dev/ccmux-tmux-manager-for-ai-coding-agents</link>
          <guid isPermaLink="true">https://blog.skz.dev/ccmux-tmux-manager-for-ai-coding-agents</guid>
        </item>
      
    
      
        <item>
          <title>3D Reconstruction From Public Photos with Machine Learning</title>
          <description>&lt;p class=&quot;description&quot;&gt;
    Can we reconstruct the world from public photos?
&lt;/p&gt;

&lt;h2 id=&quot;mapping-the-world&quot;&gt;Mapping the World&lt;/h2&gt;

&lt;p&gt;The internet provides an abundance of public photos from various sources: Reddit, Youtube, Google Maps photo uploads, and so forth.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/google-maps-photos.png&quot; alt=&quot;Google Maps Photos Example&quot; height=&quot;400px&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Public photos available on Google Maps
&lt;/p&gt;

&lt;p&gt;I wondered: is it possible to create a 3D map of the world from all this data? Cameras remove all the 3D information when the photo is taken - but using state of the art machine learning, we can bring it back, and turn a photo like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/safeway-1.webp&quot; alt=&quot;Safeway Input Image 0&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Public Safeway input image 0.
&lt;/p&gt;

&lt;p&gt;Into a 3D model like this:&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; style=&quot;width: 100%; height: auto;&quot;&gt;
        &lt;source src=&quot;assets/video/3d-reconstruction/safeway-1-recording.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;Another example, from the famous Singapore Airport:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/sg-airport-0.jpeg&quot; alt=&quot;SG Airport Input Image&quot; height=&quot;600px&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Public SG Airport input image.
&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; style=&quot;width: 100%; height: auto;&quot;&gt;
        &lt;source src=&quot;assets/video/3d-reconstruction/sg-airport-recording.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;And even an image of a forest:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/forest-0.jpg&quot; alt=&quot;Forest Input Image&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Public Forest input image.
&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; style=&quot;width: 100%; height: auto;&quot;&gt;
        &lt;source src=&quot;assets/video/3d-reconstruction/forest-recording.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;To achieve this, I used an ML depth model and some linear algebra.&lt;/p&gt;

&lt;h2 id=&quot;camera-projection&quot;&gt;Camera Projection&lt;/h2&gt;

&lt;p&gt;We can consider a camera as performing a projection from 3D to 2D, as in the image below. This removes information about the 3rd dimension: depth.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/camera-projection.png&quot; alt=&quot;Camera Projection Visualization&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Camera projection from 3D to 2D. Source: [1]
&lt;/p&gt;

&lt;p&gt;Our task is to recover this 3rd dimension, and then figure out how to undo this projection.&lt;/p&gt;

&lt;h2 id=&quot;camera-intrinsics&quot;&gt;Camera Intrinsics&lt;/h2&gt;

&lt;p&gt;It is not sufficient to know the depth for every pixel in the image to reconstruct it in 3D. This is because the properties of the camera - most importantly the focal length - determine how points in 3D get mapped to pixels in 2D, and so to undo this mapping, we need to have these properties of the camera. Consider the demonstration of this below, where different focal lengths produce significantly different images:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/face-focal-length.gif&quot; alt=&quot;Face vs Focal Length Demonstration&quot; height=&quot;400px&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
A demonstration of the effect of focal length on image. Source: [2]
&lt;/p&gt;

&lt;p&gt;Logically it follows that if we want to reconstruct the face in 3D from any of these images, we need to know the camera properties.&lt;/p&gt;

&lt;p&gt;Using properties of similar triangles shown below, it can be seen that the relation between the image points and the 3D points are simple:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
x = f \frac{X}{Z} \space\space\space\space\space y = f \frac{Y}{Z} \tag{1} \label{eq:camera perspective projection}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/camera-perspective-focal.png&quot; alt=&quot;Camera Perspective Projection&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Camera Perspective Projection
&lt;/p&gt;

&lt;p&gt;Reversing this transformation to map image points back to 3D, we find:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
X = \frac{x \cdot Z}{f} \space\space\space\space\space Y = \frac{y \cdot Z}{f} \space\space\space\space\space Z = Z \tag{2} \label{eq:inverse camera perspective projection}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;So, the two missing pieces we need are the depth at each pixel, \(Z\), and the focal length of the camera, \(f\). Neither of these are immediately available from public photos. The EXIF data, which may have camera information, is typically removed.&lt;/p&gt;

&lt;p&gt;Note, it’s also possible to describe the full transformation between 3D and 2D coordinate frames, taking into account the camera’s pose, shown below. But for the purpose of this article, we just consider the camera’s local coordinate frame.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/complete-camera-model.jpg&quot; alt=&quot;Complete Camera Model&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Complete Camera Model. Source: [3]
&lt;/p&gt;

&lt;h2 id=&quot;depth-masks&quot;&gt;Depth Masks&lt;/h2&gt;

&lt;p&gt;Apple’s recently released &lt;a href=&quot;https://github.com/apple/ml-depth-pro&quot;&gt;DepthPro model&lt;/a&gt; made this project possible. While depth models have existed for a long time, I noticed this model was different in two ways:
    1. It provided depth in an absolute, metric scale, which meant 3D reconstructions would actually have metric proportions, even when generated from a single mono image
    2. It estimated the focal length of the camera for me&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/depth-pro-teaser.jpg&quot; alt=&quot;Depth Pro Teaser&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Depth Pro Teaser. Source: [4]
&lt;/p&gt;

&lt;p&gt;I ran this model on a bunch of public photos, estimating the depth masks and focal lengths, as shown below. For example, for this image:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/safeway-1.webp&quot; alt=&quot;Safeway Input Image 1&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Public Safeway input image 1.
&lt;/p&gt;

&lt;p&gt;The corresponding estimated depth mask and focal length are:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/safeway-1-depth-black.png&quot; alt=&quot;Safeway depth mask 1&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Safeway depth mask 1 and focal length predicted by the DepthPro model.
&lt;/p&gt;

&lt;p&gt;I then used equations \((2)\) to map each pixel back into 3D, created a point cloud, and then visualized it with Open3D:&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; style=&quot;width: 100%; height: auto;&quot;&gt;
        &lt;source src=&quot;assets/video/3d-reconstruction/safeway-1-recording.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/div&gt;

&lt;h2 id=&quot;3d-reconstruction&quot;&gt;3D Reconstruction&lt;/h2&gt;

&lt;p&gt;Check out all my examples, below. Notably, check out the NYC skyline example. I was curious to see how well it would work on a huge scene, like the skyline of NYC. As expected, the depth pro model did not produce a good depth mask. The training dataset almost certainly focused on smaller scales.&lt;/p&gt;

&lt;h3 id=&quot;coex-mall&quot;&gt;COEX Mall&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/coex-0.jpg&quot; alt=&quot;Coex Mall Input Image&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Public COEX Mall input image.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/coex-0-depth-black.png&quot; alt=&quot;Coex Mall depth mask&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
COEX Mall depth mask and focal length predicted by the DepthPro model. 
&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; style=&quot;width: 100%; height: auto;&quot;&gt;
        &lt;source src=&quot;assets/video/3d-reconstruction/coex-recording.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/div&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h3 id=&quot;forest&quot;&gt;Forest&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/forest-0.jpg&quot; alt=&quot;Forest Input Image&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Public Forest input image.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/forest-0-depth-black.png&quot; alt=&quot;Forest depth mask&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Forest depth mask and focal length predicted by the DepthPro model.
&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; style=&quot;width: 100%; height: auto;&quot;&gt;
        &lt;source src=&quot;assets/video/3d-reconstruction/forest-recording.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/div&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h3 id=&quot;nyc-skyline&quot;&gt;NYC Skyline&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/nyc-0.jpg&quot; alt=&quot;NYC Input Image&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Public NYC input image.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/nyc-depth-black.png&quot; alt=&quot;NYC depth mask&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
NYC depth mask and focal length predicted by the DepthPro model.
&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; style=&quot;width: 100%; height: auto;&quot;&gt;
        &lt;source src=&quot;assets/video/3d-reconstruction/nyc-recording.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/div&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h3 id=&quot;safeway-1&quot;&gt;Safeway 1&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/safeway-0.webp&quot; alt=&quot;Safeway Input Image 0&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Public Safeway input image 0.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/safeway-0-depth-black.png&quot; alt=&quot;Safeway depth mask 0&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Safeway depth mask 0 and focal length predicted by the DepthPro model.
&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; style=&quot;width: 100%; height: auto;&quot;&gt;
        &lt;source src=&quot;assets/video/3d-reconstruction/safeway-0-recording.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/div&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h3 id=&quot;safeway-2&quot;&gt;Safeway 2&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/safeway-1.webp&quot; alt=&quot;Safeway Input Image 1&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Public Safeway input image 1.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/safeway-1-depth-black.png&quot; alt=&quot;Safeway depth mask 1&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Safeway depth mask 1 and focal length predicted by the DepthPro model.
&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; style=&quot;width: 100%; height: auto;&quot;&gt;
        &lt;source src=&quot;assets/video/3d-reconstruction/safeway-1-recording.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/div&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h3 id=&quot;singapore-airport&quot;&gt;Singapore Airport&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/sg-airport-0.jpeg&quot; alt=&quot;SG Airport Input Image&quot; height=&quot;600px&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Public SG Airport input image.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/3d-reconstruction/sg-airport-0-depth-black.png&quot; alt=&quot;SG Airport depth mask&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
SG Airport depth mask and focal length predicted by the DepthPro model.
&lt;/p&gt;

&lt;div style=&quot;text-align: center;&quot;&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; style=&quot;width: 100%; height: auto;&quot;&gt;
        &lt;source src=&quot;assets/video/3d-reconstruction/sg-airport-recording.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/div&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;footnotes&quot;&gt;Footnotes&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Camera projection from 3D to 2D. Source: &lt;a href=&quot;https://www.researchgate.net/figure/The-perspective-projection-of-a-camera-model_fig2_324584663&quot;&gt;ResearchGate&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;A demonstration of the effect of focal length on image. Source: &lt;a href=&quot;https://www.diyphotography.net/gif-explains-changing-focal-length-impacts-portrait&quot;&gt;DIY Photography&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Complete Camera Model. Source: &lt;a href=&quot;https://robotacademy.net.au/lesson/summary-of-image-geometry/&quot;&gt;Robot Academy&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Depth Pro Teaser. Source: &lt;a href=&quot;https://github.com/apple/ml-depth-pro&quot;&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
          <pubDate>Thu, 21 Aug 2025 00:00:00 -0700</pubDate>
          <link>https://blog.skz.dev/3d-reconstruction-from-public-photos</link>
          <guid isPermaLink="true">https://blog.skz.dev/3d-reconstruction-from-public-photos</guid>
        </item>
      
    
      
        <item>
          <title>Predicting How the Market Will Open Tomorrow</title>
          <description>&lt;p&gt;&lt;em&gt;Predicting the Future&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One weekend there was bad economic news, and I was wondering how it would impact the market at open on Monday. I looked into this question, and I learned that
while the stock market is closed, futures trading of underlying indices continues, and we can look to the futures market to understand how traders are already reacting to and pricing in new information - like a crystal ball 🔮.&lt;/p&gt;

&lt;p&gt;As future prices and
current prices are related, we can calculate what the futures markets are pricing as the current
fair value of each index by discounting the future value backwards in time. This gives us the implied
opening price of each index even before the market opens - so on Sunday night we can see whether the market
will likely open &lt;span style=&quot;color: #66ff66;&quot;&gt;higher&lt;/span&gt; or &lt;span style=&quot;color: #ff6666;&quot;&gt;lower&lt;/span&gt; the next day.&lt;/p&gt;

&lt;p&gt;Below, I walk through all the pieces of this calculation, culminating in a dashboard I built that gives us a sneak preview of the the next day’s opening movements at &lt;a href=&quot;https://impliedopen.com&quot;&gt;impliedopen.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/predicting-market-open/save-game.png&quot; alt=&quot;A preview of the dashboard I built which gives us a sneak peek of market movements the next day.&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
A preview of the dashboard I built which gives us a sneak peek of market movements the next day.
&lt;/p&gt;

&lt;h2 id=&quot;futures&quot;&gt;Futures&lt;/h2&gt;

&lt;p&gt;Futures (future contracts) are instruments that allow traders to bet on the &lt;em&gt;future&lt;/em&gt; price of an underlying asset at the future contract’s date of expiry. Note the price and expiry date of the SP500 future below. It’s currently trading higher than the SP500 price, but what does this tell us about what the current value of the SP500 is?&lt;/p&gt;

&lt;p&gt;To relate the futures’ price to the current fair value being priced by the futures market, we have to account for the time between now and the future expiry date.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/predicting-market-open/futures-price.png&quot; alt=&quot;SP500 Future Contract&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
SP500 Future Contract
&lt;/p&gt;

&lt;p&gt;We do this by using the cost of carry model, which takes into account the risk-free interest rate,
the dividend yield, and the time to maturity. It assumes that the price of the index should grow at the current
risk-free rate (essentialy a no arbitrage assumption) minus decreases in price due to divedends being paid out.&lt;/p&gt;

&lt;p&gt;We can express this relationship as:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
    F = S \times e^{(r - q) \times t} \tag{0}  \label{eq:coc}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;where \(F\) is the futures price, \(S\) is the current stock price, \(r\) is the risk-free interest
rate, \(q\) is the dividend yield, and \(t\) is the time to maturity.&lt;/p&gt;

&lt;h2 id=&quot;going-back-in-time&quot;&gt;Going Back in Time&lt;/h2&gt;

&lt;p&gt;To calculate the current fair value of the stock price from the futures price, we can discount the futures price backwards in time, inverting the above formula:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
    S = F \times e^{-(r - q) \times t} \tag{1}  \label{eq:discount}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;Before the market opens, the fair value is our best estimate of the opening price of the index.&lt;/p&gt;

&lt;h2 id=&quot;risk-free-rate&quot;&gt;Risk Free Rate&lt;/h2&gt;

&lt;p&gt;Does a risk-free investment even exist? Well, treasury bills - debt issued by the US treasury - are considered (nearly) riskless because:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;US government has a low chance of default; it can print its own money has a good history of paying debts&lt;/li&gt;
  &lt;li&gt;short maturity reduces exposure to interest rate changes and long-term uncertainties&lt;/li&gt;
  &lt;li&gt;very liquid market&lt;/li&gt;
  &lt;li&gt;no credit risk as the US government won’t go bankrupt like in the case of corporate or municipal bonds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the risk-free rate in these calculations, I choose the 3 month T-bill.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/predicting-market-open/3m-bond-yield-1.png&quot; alt=&quot;3 Month US Treasury Yield&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
3 Month US Treasury Yield
&lt;/p&gt;

&lt;p&gt;We assume that the underlying assets will grow at atleast the risk-free rate, because otherwise investors wouldn’t bother putting their money in these instruments.&lt;/p&gt;

&lt;h2 id=&quot;dividend-yield&quot;&gt;Dividend Yield&lt;/h2&gt;

&lt;p&gt;While we assume that the underlying instruments will grow at atleast the risk-free rate, we have to account for the fact that the underlying indices will drop when dividends are paid out by a commensurate amount. To do this, I am using the most recent dividend yield of each index - but a more accurate model will probably anticipate what the forward looking dividend yield will be.&lt;/p&gt;

&lt;p&gt;Below is the latest SP500 dividend yield which I use for my calculations.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/predicting-market-open/sp500-dividend-yield.png&quot; alt=&quot;Latest SP500 Yield&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Latest SP500 Dividend Yield
&lt;/p&gt;

&lt;h2 id=&quot;putting-it-all-together&quot;&gt;Putting it All Together&lt;/h2&gt;

&lt;p&gt;I collect the last index price, the current trading futures price, my best estimate of the dividend yield, risk-free rate, and time to expiry, and calculate my current best estimate of the fair value of each major index. I’ve put all the pieces together on &lt;a href=&quot;https://impliedopen.com&quot;&gt;impliedopen.com&lt;/a&gt;, which I’ve embedded below:&lt;/p&gt;

&lt;iframe src=&quot;https://impliedopen.com&quot; width=&quot;100%&quot; height=&quot;1200px&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;This dashboard is most useful when the futures market is open but the regular market is closed - so we can use the futures market to understand what traders are pricing in before the regular market open. When the regular market is open, it provides the “true” current value of each index - as priced by the market.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/predicting-market-open/market-status.png&quot; alt=&quot;Market Status View&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Market Status View
&lt;/p&gt;

&lt;h2 id=&quot;final-notes&quot;&gt;Final Notes&lt;/h2&gt;

&lt;p&gt;I put this dashboard together pretty quickly, and ended up just scraping a lot of the values which I cache once a minute on my own API :) My apologies if things break or it goes down - at some point I will replace things with proper APIs, or this dashboard will become irrelevant as markets move towards 24/7 trading. In any case, I wanted to put together this blog post for permenance.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/predicting-market-open/it-is-what-it-is.png&quot; alt=&quot;It is what it is.&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
It is what it is.
&lt;/p&gt;

&lt;p&gt;If you notice errors in my calculations or have suggestions or other feedback, please reach out to me and let me know!&lt;/p&gt;
</description>
          <pubDate>Sun, 23 Mar 2025 00:00:00 -0700</pubDate>
          <link>https://blog.skz.dev/predicting-how-the-market-will-open-tomorrow</link>
          <guid isPermaLink="true">https://blog.skz.dev/predicting-how-the-market-will-open-tomorrow</guid>
        </item>
      
    
      
        <item>
          <title>A Telegram Bot for Importing Sticker.ly Packs</title>
          <description>&lt;p class=&quot;description&quot;&gt;
    Import animated Sticker.ly packs into Telegram with ease
&lt;/p&gt;

&lt;style&gt;
video {
    display: block;
    margin-left: auto;
    margin-right: auto;
    max-width: 100%;
}
&lt;/style&gt;

&lt;p&gt;&lt;a href=&quot;https://sticker.ly&quot;&gt;Sticker.ly&lt;/a&gt; is a popular sticker pack creation and management app for Android and iPhone devices with over 100 million downloads on &lt;a href=&quot;https://play.google.com/store/apps/details?id=com.snowcorp.stickerly.android&amp;amp;hl=en_US&amp;amp;gl=US&quot;&gt;the Google Play store&lt;/a&gt; alone. It supports importing sticker packs into iMessage, WhatsApp, and Telegram. However, the ability to import animated sticker packs is limited to WhatsApp only.&lt;/p&gt;

&lt;p&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; height=&quot;600&quot;&gt;
        &lt;source src=&quot;assets/video/telegram-stickerly-import-bot/stickerly-animated-import-cropped.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/p&gt;

&lt;p&gt;As an avid Telegram user and evangelist, I often encourage my friends to join the platform to communicate with me. Many of them are frustrated by the lack of access to the rich sticker library available on other platforms like WhatsApp.  I promised to fix this, and I finally did. Introducing: the &lt;a href=&quot;https://t.me/StickerlyImportBot&quot;&gt;Sticker.ly Sticker Pack Import Bot&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; height=&quot;400&quot;&gt;
        &lt;source src=&quot;assets/video/telegram-stickerly-import-bot/stickerly-bot-screen.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/p&gt;

&lt;p&gt;To use the bot, simply provide a Sticker.ly pack URL, such as &lt;a href=&quot;https://sticker.ly/s/HBBCR8&quot;&gt;https://sticker.ly/s/HBBCR8&lt;/a&gt;. The bot will then convert the stickers into an animated format compatible with Telegram and provide a link for you to add the animated sticker pack to your library.&lt;/p&gt;

&lt;p&gt;
    &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; disableRemotePlayback=&quot;&quot; height=&quot;500&quot;&gt;
        &lt;source src=&quot;assets/video/telegram-stickerly-import-bot/stickerly-import.mp4&quot; type=&quot;video/mp4&quot; /&gt;
        Your browser does not support the video tag.
    &lt;/video&gt;
&lt;/p&gt;

&lt;p&gt;Although the bot also supports importing static sticker packs, I recommend against using it for this purpose, since Sticker.ly and Telegram natively support the import of static packs out of the box.&lt;/p&gt;

&lt;p&gt;Please note that the bot is currently in beta, so some failures may occur. If you encounter issues with creating a sticker pack, try again and follow the in-bot instructions to delete a partially created sticker pack if necessary.&lt;/p&gt;
</description>
          <pubDate>Wed, 05 Apr 2023 00:00:00 -0700</pubDate>
          <link>https://blog.skz.dev/telegram-stickerly-import-bot</link>
          <guid isPermaLink="true">https://blog.skz.dev/telegram-stickerly-import-bot</guid>
        </item>
      
    
      
        <item>
          <title>Blackbox Optimization and Hyperparameter Tuning With Google's Vizier</title>
          <description>&lt;p class=&quot;description&quot;&gt;
    Automatically and intelligently optimize any kind of system
&lt;/p&gt;

&lt;style&gt;
img {
  max-height: 350px;
}
&lt;/style&gt;

&lt;p&gt;When you build an machine learning model - or some other kind of algorithmic system - you will have to make choices about the architecture of your system. For example, for a machine learning application, you may need to decide how many neurons to have in a neural network layer, or the rate of gradient descent. Or if you are building a &lt;a href=&quot;https://en.wikipedia.org/wiki/Particle_filter&quot;&gt;particle filter&lt;/a&gt; to track the state of a robot, you may need to choose the shape of a likelihood function. You may be looking for a buffer size and thread count that minimize your use of computing resources. How can you find the optimal architecture for your system?&lt;/p&gt;

&lt;p&gt;You could try to find it manually. You can try different values for the number of neurons. You could try different rates of gradient descent. But this process is slow and tedious, and suceptible to pitfalls. Thankfully there exist services that can optimize your system for you, in an intelligent and efficient way.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;introducing-vizier&quot;&gt;Introducing Vizier&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/google/vizier&quot;&gt;Vizier&lt;/a&gt; is an optimization service created at Google for blackbox and hyperparameter optimization. It can be used to optimize any kind of system as long as you can pass inputs to the system - that are suggested by Vizier - and get outputs from the system - metrics that Vizier is trying to optimize, such as by maximizing or minimizing them. The system itself is treated as a blackbox.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/blackbox-optimization-with-vizier/black-box-optimization.png&quot; alt=&quot;Blackbox Optimization&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Blackbox optimization doesn't requre any understanding of the system itself, as long as you can pass inputs and get outputs. Source: Per Instance Algorithm Configuration for Continuous Black Box Optimization - Scientific Figure on ResearchGate. Available from: https://www.researchgate.net/figure/black-box-Optimization_fig1_322035981 [accessed 23 Feb, 2023]
&lt;/p&gt;

&lt;p&gt;Unlike non-blackbox optimization systems such as &lt;a href=&quot;https://blog.skz.dev/gradient-descent&quot;&gt;gradient descent&lt;/a&gt; which require an understanding of the relationship between the parameters of the system and the metric being minimized or maximized, a blackbox system like Vizier does not require any insight into the system itself. It can still search the parameter space efficiently for you, and find the values of the parameters that optimize your system. This also makes it more robust to complex, discontinuous and non-convex metric functions.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;parameters-vs-hyperparameters&quot;&gt;Parameters vs Hyperparameters&lt;/h2&gt;

&lt;p&gt;Aside: the distinction comes from machine learning applications, where “parameters” are considered the values inside the model used for inference and found through training on a dataset, while hyperparameters describe the architecture of the model or the training procedure. However, a blackbox optimization service like Vizier can be used to optimize any kind of system, but it won’t be an efficient way of training a machine learning model. But it is a good way to optimize the architecture of the model.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;an-example&quot;&gt;An Example&lt;/h2&gt;

&lt;p&gt;I am going to use an example that is not machine learning related, but still demonstrates the power and simplicity of a blackbox optimization algorithm such as Vizier. Say we have a noisy signal like the one below:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/blackbox-optimization-with-vizier/some-noisy-signal.png&quot; alt=&quot;Some noisy signal&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Some noisy signal.
&lt;/p&gt;

&lt;p&gt;Let’s try to find an analytical function that fits this signal. We can’t decide if we want to fit a &lt;a href=&quot;https://en.wikipedia.org/wiki/Normal_distribution&quot;&gt;Gaussian&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/Laplace_distribution&quot;&gt;Laplace&lt;/a&gt; function to the signal, and both functions have their own parameters - such as mean and variance - that we are also trying to estimate.&lt;/p&gt;

&lt;p&gt;As long as we can give Vizier a metric we are trying to optimize, it will find an optimal function for us.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;setting-up-the-problem&quot;&gt;Setting Up The Problem&lt;/h2&gt;
&lt;p&gt;The first parameter we want to search is the model type: Gaussian or Laplace:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{
    'parameter_id': 'model',
    'categorical_value_spec' : {
        'values': [
            'gaussian',
            'laplace',
        ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But each model has its own set of parameters that defines its shape. Let’s call these “child parameters” of each model.&lt;/p&gt;

&lt;p&gt;For the Gaussian, these are the mean &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mu&lt;/code&gt; (\(\mu\)) and standard deviation &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sigma&lt;/code&gt; (\(\sigma\)).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/blackbox-optimization-with-vizier/normal-distribution.png&quot; alt=&quot;Gaussian distribution with parameters mu and sigma.&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Gaussian distribution with parameters mu and sigma. Taken from: https://commons.wikimedia.org/wiki/File:Normal_Distribution_PDF.svg
&lt;/p&gt;

&lt;p&gt;For the Laplace - the notation depends on the literature - but generally there is the location parameter or mean &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mu&lt;/code&gt; (\(\mu\)) and scale factor &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b&lt;/code&gt; (\(b\)).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/blackbox-optimization-with-vizier/laplace-distribution.png&quot; alt=&quot;Laplace distribution with parameters mu and b.&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Laplace distribution with parameters mu and b. Taken from: https://commons.wikimedia.org/wiki/File:Laplace_pdf_mod.svg
&lt;/p&gt;

&lt;p&gt;For disambiguity from the mean of the Gaussian, let’s refer to the location parameter of the Laplace distribution as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mu&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When trying to fit the Gaussian distribution, we don’t want Vizier to try to estimate the parameters of the Laplace distribution and vice versa. Luckily we can specify the parameters of each distribution as child, or conditional, parameters of the parent model parameter:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;'conditional_parameter_specs': [
{
    &quot;parameter_spec&quot;: {
        &quot;parameter_id&quot;: &quot;mu&quot;,
        &quot;scale_type&quot;: 'UNIT_LINEAR_SCALE',
        &quot;double_value_spec&quot;: {
        &quot;min_value&quot;: 0,
        &quot;max_value&quot;: 10.0,
        },
    },
    &quot;parent_categorical_values&quot;: {
        &quot;values&quot;: ['gaussian']
    }
},
{
    &quot;parameter_spec&quot;: {
        &quot;parameter_id&quot;: &quot;sigma&quot;,
        &quot;scale_type&quot;: 'UNIT_LINEAR_SCALE',
        &quot;double_value_spec&quot;: {
        &quot;min_value&quot;: 1e-1,
        &quot;max_value&quot;: 5.0,
        },
    },
    &quot;parent_categorical_values&quot;: {
        &quot;values&quot;: ['gaussian']
    }
},
    {
    &quot;parameter_spec&quot;: {
        &quot;parameter_id&quot;: &quot;a&quot;,
        &quot;scale_type&quot;: 'UNIT_LINEAR_SCALE',
        &quot;double_value_spec&quot;: {
        &quot;min_value&quot;: 0,
        &quot;max_value&quot;: 10.0,
        },
    },
    &quot;parent_categorical_values&quot;: {
        &quot;values&quot;: ['laplace']
    }
},
{
    &quot;parameter_spec&quot;: {
        &quot;parameter_id&quot;: &quot;b&quot;,
        &quot;scale_type&quot;: 'UNIT_LINEAR_SCALE',
        &quot;double_value_spec&quot;: {
        &quot;min_value&quot;: 1e-1,
        &quot;max_value&quot;: 5.0,
        },
    },
    &quot;parent_categorical_values&quot;: {
        &quot;values&quot;: ['laplace']
    }
}]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Lastly, we need to define the metric that Vizier will be to optimizing. I want to fit a function to the signal as closely as possible, and I will evaluate how well it fits the signal by calculating the sum of squares of residuals between the analytical function and the data. The smaller this number, the better the fit. All we need to tell Vizier is that there is this metric we are trying to minimize:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;metric_ssr = {
    'metric_id': 'sum_of_squared_residuals',
    'goal': 'MINIMIZE',
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We put all of this together and that defines our problem:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;study = {
    'display_name': 'vizier_experiment',
    'study_spec' :
    {
        'parameters': [param_model],
        'metrics': [metric_ssr]
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that Vizier has no understanding of the parameters themselves. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gaussian&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Laplace&lt;/code&gt; are simply types of a categorical parameter, and to Vizier have no meaning besides that. They are given meaning by my code that consumes them, but to Vizier, that code lives in a blackbox. I’m leaving out the code for brevity, but you can find it all at the link at the end of the article to see what I mean.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;searching&quot;&gt;Searching&lt;/h2&gt;

&lt;p&gt;For each trial, Vizier suggests a set of parameters for us to try. We evaluate how well these parameters work ourselves, calculate the metric (the sum of squared residuals) and return the result of the metric evaluation to Vizier. Vizier uses the result to generate the next set of parameters to try.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/blackbox-optimization-with-vizier/vizier-trials.png&quot; alt=&quot;Vizier suggesting parameters for trials.&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Vizier suggesting parameters for trials.
&lt;/p&gt;

&lt;p&gt;Notice that Vizier only suggests Gaussian parameters when the model is Gaussian, and vice versa for the Laplace model.&lt;/p&gt;

&lt;p&gt;How does Vizier search for optimal parameters? It can do a grid or random search, but the real magic is the combination of other algorithms it employs: Gaussian process bandits, linear combination search, or their variants.&lt;/p&gt;

&lt;p&gt;Via GCP we can check these cool charts of Vizier’s progress:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/blackbox-optimization-with-vizier/study-progress-table.png&quot; alt=&quot;A study table showing the evolution of the metric being optimized and the parameters of each trial&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
A study table showing the evolution of the metric being optimized and the parameters of each trial.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/blackbox-optimization-with-vizier/parallel-chart.png&quot; alt=&quot;A parallel chart showing the sets of parameters attempted and the resulting metric value&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
A parallel chart showing the sets of parameters attempted and the resulting metric value.
&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;optimized-fit&quot;&gt;Optimized Fit&lt;/h2&gt;

&lt;p&gt;We can see that the Laplace distribution slightly wins out over the Gaussian:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/blackbox-optimization-with-vizier/optimal-parallel-chart.png&quot; alt=&quot;Parallel chart zoomed in to show the top trials that minimize the sum of squared residuals. A better fit is found with a Laplace distribution than a Gaussian distribution.&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Parallel chart zoomed in to show the top trials that minimize the sum of squared residuals. A better fit is found with a Laplace distribution than a Gaussian distribution.
&lt;/p&gt;

&lt;p&gt;We query Vizier for the optimal trial parameters and plot them:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/blackbox-optimization-with-vizier/noisy-signal-fit.png&quot; alt=&quot;Optimized fit to noisy signal&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Optimized parameters that fit the noisy signal.
&lt;/p&gt;

&lt;p&gt;It’s almost a perfect fit! The parameters Vizier found are close to the parameters I used to generate the signal before adding noise: a Laplace distribution with \(a=5\) and \(b=2\).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/blackbox-optimization-with-vizier/original-laplace-signal.png&quot; alt=&quot;Original laplace signal&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Original Laplace signal.
&lt;/p&gt;

&lt;p&gt;For comparison, a Gaussian would have a rounder peak and thinner tails.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/blackbox-optimization-with-vizier/gaussian-signal.png&quot; alt=&quot;Gaussian signal&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
A comparative Gaussian distribution.
&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;how-can-i-reproduce-this&quot;&gt;How Can I Reproduce This?&lt;/h2&gt;

&lt;p&gt;The colab I used to generate this article can be found &lt;a href=&quot;https://github.com/skzv/vizier-blog-post/blob/main/vizier_blog_post.ipynb&quot;&gt;here&lt;/a&gt;. I used GCP’s Vertex AI Vizier API because it generates some cool charts like the ones I included in this post, but be aware it can be expensive ($1/trial). Vizier is also open-source and available &lt;a href=&quot;https://github.com/google/vizier&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
          <pubDate>Thu, 23 Feb 2023 00:00:00 -0800</pubDate>
          <link>https://blog.skz.dev/blackbox-optimization-with-vizier</link>
          <guid isPermaLink="true">https://blog.skz.dev/blackbox-optimization-with-vizier</guid>
        </item>
      
    
      
        <item>
          <title>Monte Carlo Options Pricing</title>
          <description>&lt;p&gt;&lt;em&gt;The options casino&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Predicting the movement of stock prices is an alluring challenge with the promise of riches. Unfortunately, predicting future stock prices consistently and reliably is generally considered impossible. However, we can use models to make useful predictions, manage risk, and profit &lt;em&gt;probalistically&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;geometric-brownian-motion&quot;&gt;Geometric Brownian Motion&lt;/h2&gt;
&lt;p&gt;One such commonly used model is geometric Brownian motion (&lt;a href=&quot;https://stats.libretexts.org/Bookshelves/Probability_Theory/Probability_Mathematical_Statistics_and_Stochastic_Processes_(Siegrist)/18%3A_Brownian_Motion/18.04%3A_Geometric_Brownian_Motion&quot;&gt;1&lt;/a&gt;, &lt;a href=&quot;http://www.columbia.edu/~ks20/FE-Notes/4700-07-Notes-GBM.pdf&quot;&gt;2&lt;/a&gt;) - in fact, the famous &lt;a href=&quot;https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model&quot;&gt;Black-Scholes options pricing formula&lt;/a&gt; assumes this model as well. This has the form&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
 S(t) = S(0)\exp{((\mu-\frac{\sigma^2}{2})t + \sigma B(t))} \tag{0}  \label{eq:gbm}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;where \(B(t)\) is standard Brownian motion. If we let \(S(0)\) to be the initial stock price, \(\mu\) to be the mean &lt;a href=&quot;https://en.wikipedia.org/wiki/Rate_of_return#Logarithmic_or_continuously_compounded_return&quot;&gt;compounding return&lt;/a&gt;, and \(\sigma\) to be the &lt;a href=&quot;https://en.wikipedia.org/wiki/Volatility_(finance)&quot;&gt;volatility&lt;/a&gt; of a stock, then we can generate very realistic price paths.&lt;/p&gt;

&lt;div id=&quot;plot-0&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;span class=&quot;slider-container&quot;&gt;
    &lt;span class=&quot;slider-label&quot;&gt;\(\mu\)&lt;/span&gt;
    &lt;span&gt;
        &lt;input class=&quot;slider&quot; id=&quot;mean-slider&quot; type=&quot;range&quot; min=&quot;-2.5&quot; max=&quot;1&quot; value=&quot;0&quot; step=&quot;0.01&quot; /&gt;
    &lt;/span&gt;
    &lt;span class=&quot;slider-value&quot; id=&quot;mean-slider-value&quot;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;slider-container&quot;&gt;
    &lt;span class=&quot;slider-label&quot;&gt;\(\sigma\)&lt;/span&gt;
    &lt;span&gt;
        &lt;input class=&quot;slider&quot; id=&quot;sigma-slider&quot; type=&quot;range&quot; min=&quot;0&quot; max=&quot;1&quot; value=&quot;0&quot; step=&quot;0.01&quot; /&gt;
    &lt;/span&gt;
    &lt;span class=&quot;slider-value&quot; id=&quot;sigma-slider-value&quot;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;button class=&quot;regenerate-button&quot; onclick=&quot;regenerate0()&quot;&gt;Regenerate&lt;/button&gt;&lt;/p&gt;

&lt;div style=&quot;display:none&quot; id=&quot;mean-0&quot;&gt;0&lt;/div&gt;
&lt;div style=&quot;display:none&quot; id=&quot;sigma-0&quot;&gt;0&lt;/div&gt;
&lt;div style=&quot;display:none&quot; id=&quot;option-mean-0&quot;&gt;0&lt;/div&gt;

&lt;p&gt;So, what does this mean, and why is this a good model for security prices?&lt;/p&gt;

&lt;h3 id=&quot;logarithmic-returns&quot;&gt;Logarithmic Returns&lt;/h3&gt;
&lt;p&gt;In quantitative finance it is common to deal with continuously compounded returns rather than simple returns. What that means is that instead of examining a simple return between trading periods \(1 + R_t = \frac{S(t)}{S(t-1)}\) we will examine the compounding return \(r_t = \ln{(1 + R_t)} = \ln{\frac{S(t)}{S(t-1))}}\). This has the very nice property that one can recover the price of a stock by simply summing up the logarithmic returns as:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
 S(t) = S(0)\exp{(r_1 + r_2 + ... + r_{t-1})} \tag{1}  \label{eq:rr}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;This is also convenient because a common assumption in finance is that the logarithmic returns of a stock are independent and normally distributed with mean \(\mu\) and variance \(\sigma^2\). Note that for small returns, simple returns \(R_t\) approximate logarthmic returns \(r_t\) since \(r_t = \ln{(1 + R_t)} \approx R_t\), so the difference between the two returns is not substantial, and thus one can assume both simple and logarathmic returns are normally distributed. Next, if the logarithmic returns are normally distributed, then this in turn implies the stock price is &lt;em&gt;lognormally&lt;/em&gt; distributed. This has been observed empirically to some extent, although in real life stock prices tend to have “fatter tails” - meaning rare events happen more often than a normal distribution would predict. Here are the monthly returns for IBM stock showing this behaviour.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/monte-carlo-stocks/IBM-returns.png&quot; alt=&quot;IBM monthly returns&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Monthly simple and logarithmic returns of IBM stock with fitted normal distributions. We observe more returns at the extremities of the distributions than the normal distribution would predict. We call these 'fat tails' or 'excess kurtosis'. This image has been taken from 'Analysis of Financial Time Series, 3rd Ed.' by Ruey S. Tsay.
&lt;/p&gt;

&lt;p&gt;This model also works well because it only permits positive security prices. Securities tend to only have positive prices (although in some cases they can go negative - see the &lt;a href=&quot;https://assets.weforum.org/editor/kE8TpkO7bzo3dLFWEh9UDvgqRuZPOmURhnh0FfwOVYc.png&quot;&gt;oil crash of 2020&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So, we want a process that generates a series of normally distributed returns \(r_t\) - which invites Brownian motion as a natural choice.&lt;/p&gt;

&lt;h2 id=&quot;brownian-motion&quot;&gt;Brownian motion&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://stats.libretexts.org/Bookshelves/Probability_Theory/Probability_Mathematical_Statistics_and_Stochastic_Processes_(Siegrist)/18%3A_Brownian_Motion/18.01%3A_Standard_Brownian_Motion&quot;&gt;Brownian motion&lt;/a&gt; describes the motion of a particle in a fluid or gas. Such a particle bounces around “randomly” within the fluid surrounding it. The path it creates appears quite noisy and random. It can essentially be considered a limiting form of the random walk where both the time between steps and the step length approach zero (with some caveats: namely, if the time step is \(t\), the step length must be \(\sqrt{t}\) - this produces a process whose variance scales exactly with time). Consider flipping a coin to determine whether to take a step forward or backward. Now let the time between steps approach zero. You would trace a path that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/monte-carlo-stocks/wiener-process-zoom.png&quot; alt=&quot;1-D Brownian Motion&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
1 dimensional [arithmetic] Brownian motion. Geometric Brownian motion is the exponential of this process. &lt;a target=&quot;_blank&quot; href=&quot;http://creativecommons.org/licenses/by-sa/3.0/&quot; title=&quot;Creative Commons Attribution-Share Alike 3.0&quot;&gt;CC BY-SA 3.0&lt;/a&gt;, &lt;a target=&quot;_blank&quot; href=&quot;https://commons.wikimedia.org/w/index.php?curid=1426987&quot;&gt;Source&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Brownian motion has the very nice property that the trajectories are normally distributed, which is exactly what we are looking for. We can simulate a Brownian process with discrete steps by sampling from a normal distribution (although if you only cared about the terminal value of a time series, you could sample from any distribution with unit variance; due to the central limit theorem the sum of random variables would approach a Gaussian distribution in the long term anyway).&lt;/p&gt;

&lt;p&gt;Hence, Brownian motion provides us with the series of normally distributed logarathmic returns \(r_t\) as required in \(\eqref{eq:rr}\):&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
 r(t) = (\mu-\frac{\sigma^2}{2})t + \sigma B(t) \tag{2}  \label{eq:bm}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;Taking the exponential of this process, we’ve recovered an equation for modelling stock price movement.&lt;/p&gt;

&lt;p&gt;Of particular interest here is the drift rate \(\mu - \frac{\sigma^2}{2}\) which causes the process to drift linearly, as the name suggests. \(\mu\) is obvious - it’s the mean continuosly compounding return for the stock, and in the absence of noise (or volatility), the stock price would only grow (or decrease) by this compounding amount. But what about \(\frac{\sigma^2}{2}\)? Well, it turns out that the expected value of a lognormally distributed variable is \(\exp(\mu + \sigma^2/2)\). Since the stock price is &lt;em&gt;lognormally distributed&lt;/em&gt; (while returns are &lt;em&gt;normally distributed&lt;/em&gt;), and since we require that the expected value of the stock price after continuous compounding to be \(S(0)\exp(\mu t)\), we must apply the correction term of \(- \frac{\sigma^2}{2}\) to the process.&lt;/p&gt;

&lt;p&gt;There’s also a philsophical argument to be made for choosing Brownian motion as the basis for this model. A common assumption made in finance is that markets are efficient, so there should be no arbitrage opportunities. Hence, the market should be unpredictable - if an actor could predict it, that would be an arbitrage opportunity that would be exploited away. Hence, random noise (which is what Brownian motion is, essentially) is a good choice for modelling markets.&lt;/p&gt;

&lt;h2 id=&quot;option-pricing&quot;&gt;Option Pricing&lt;/h2&gt;

&lt;p&gt;Monte-Carlo simulation is a statistical technique inspired by the casinos of Monaco. Much like gamblers resigning their fates to probability, we hand over the results of statistical analysis to chance. By running enough trials, we can make conclusions with statistical significance.&lt;/p&gt;

&lt;p&gt;Consider evaluating a call option with a strike prices of $105 for this stock. What would be the expected value of the option at expiry, given a geometric Brownian model for the stock’s movement? We can estimate this by generating a series of price paths, each of which can be considered a trial. The terminal value of each trial is related to the intrinsic value of the option via the relation \(V = \max(0, S - K)\) where \(S\) is the stock price and \(K\) is the strike price. The mean terminal value provides us with an estimate of the option’s value at expiry, while the standard deviation of terminal values provides us with a measure of how certain we are that the option will expire close to this value.&lt;/p&gt;

&lt;div id=&quot;plot-1&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;button class=&quot;regenerate-button&quot; onclick=&quot;regenerate1()&quot;&gt;Regenerate&lt;/button&gt;&lt;/p&gt;
&lt;div id=&quot;mean-1&quot;&gt;0&lt;/div&gt;
&lt;div id=&quot;sigma-1&quot;&gt;0&lt;/div&gt;
&lt;div id=&quot;option-mean-1&quot;&gt;0&lt;/div&gt;

&lt;p&gt;We can increase the number of trials to increase the statistical certainty of the estimate. Re-running the simulation (by clicking “Regenerate”) on a large number of trials (below) yields a much smaller discrepency between experiments than on a small number of trials (above).&lt;/p&gt;

&lt;div id=&quot;plot-2&quot;&gt;&lt;/div&gt;
&lt;p&gt;&lt;button class=&quot;regenerate-button&quot; onclick=&quot;regenerate2()&quot;&gt;Regenerate&lt;/button&gt;&lt;/p&gt;
&lt;div id=&quot;mean-2&quot;&gt;0&lt;/div&gt;
&lt;div id=&quot;sigma-2&quot;&gt;0&lt;/div&gt;
&lt;div id=&quot;option-mean-2&quot;&gt;0&lt;/div&gt;

&lt;h2 id=&quot;more-advanced-scenarios&quot;&gt;More Advanced Scenarios&lt;/h2&gt;
&lt;p&gt;Monte-Carlo simulation of terminal values is a relatively simple simulation, and one that is not too useful as this analysis can probably be completed analytically. The true power of Monte-Carlo simulation is unlocked when analysing scenarios that are more difficult to solve analytically, if not impossible. For example, if we wanted to analyse an American-style option, which can be exercised anytime, we might want to count the probability of a stock price exceeding the strike price at any time during the lifetime of that option. We could do this by counting how many trials cross the strike price boundry, which is easy to do with a Monte-Carlo simulation.&lt;/p&gt;

&lt;script src=&quot;https://cdn.plot.ly/plotly-2.4.2.min.js&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjs/9.5.1/math.js&quot; integrity=&quot;sha512-AfRcJIj922x/jSJpQLnry0DYIBg6EGCtwk/MiQ6QvDlzb7kNFxH8EdqXLkaXXY3YHQS9FrSb8H7LzuLn0CZQ1A==&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;/assets/js/monte-carlo-options.js&quot; async=&quot;&quot;&gt;&lt;/script&gt;

</description>
          <pubDate>Fri, 17 Dec 2021 00:00:00 -0800</pubDate>
          <link>https://blog.skz.dev/monte-carlo-options-pricing</link>
          <guid isPermaLink="true">https://blog.skz.dev/monte-carlo-options-pricing</guid>
        </item>
      
    
      
        <item>
          <title>Interactive Gradient Descent Demo</title>
          <description>&lt;p&gt;&lt;em&gt;What is this special algorithm?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Gradient descent is an optimization algorithm for finding the (local) minimum of a function.&lt;/p&gt;

&lt;h2 id=&quot;why-is-that-useful&quot;&gt;Why is that useful?&lt;/h2&gt;

&lt;p&gt;Say you have some function \(f(x,y)\). This function may represent a cost to you, and \(x\) and \(y\) are some inputs that vary the cost. Naturally, you are going to try to find the values of \(x\) and \(y\) that minimize the cost. This is a problem of finding the minimum of \(f(x,y)\). For example, we may want to find the minimum of this function, which is plotted below:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
f(x,y)=3(1 - x)^2 e^{- x^2 - (y + 1)^2} - 10(\frac{x}{5} - x^3 - y^5) e^{-x^2 - y^2} - \frac{1}{3} e^{-(x+1)^2 - y^2} \tag{0}  \label{eq:cost_func}
\end{align}

    $$
&lt;/div&gt;

&lt;div id=&quot;plot-0&quot;&gt;&lt;/div&gt;

&lt;p&gt;This is where gradient descent can help.&lt;/p&gt;

&lt;h2 id=&quot;why-gradient-descent&quot;&gt;Why gradient descent?&lt;/h2&gt;

&lt;p&gt;From elementary calculus, you may remember a procedure for finding the minimum of a function. You could find the first derivative, set it to zero, and solve that equation:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
f'(x,y)=0 \tag{1}  \label{eq:first_deriv}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;This will reveal the locations of the extrema (maxima and minima) of the function. The sign of the second derivative \(f''(x,y)\) can reveal whether the extrema are local maxima or minima. However, solving the first derivative equation \(\eqref{eq:first_deriv}\) can be non-trivial, and often require numeric methods. And as your cost function involves higher and higher dimensions, e.g. \(f(x,y,z,w,q,u,v,...)\) the problem can become unmanageable. Gradient descent provides an efficient procedure for locating minima by utilizing the first derivative in a clever way.&lt;/p&gt;

&lt;h2 id=&quot;the-gradient-descent-algorithm&quot;&gt;The gradient descent algorithm&lt;/h2&gt;

&lt;p&gt;The motivation behind gradient descent is intuitive, and I bet you would arrive at the same procedure in an analogous physical situation. Imagine you find yourself in a thick fog, and you can only see a few feet in front of you and feel the slope of the ground beneath you. You are trying to get to the bottom of a valley as quickly as possible. Surely, you won’t waste your time walking along a flat slope - or worse, head uphill. To descend quickly, you will inspect the slope at your location, and proceed in the direction where the slope is steepest downwards. Gradient is a synonym for slope, hence, you would be performing gradient descent. Conversely, if you were trying to find the top of the hill, you would follow the steepest part of the surface up, wherever you are. This is gradient &lt;em&gt;ascent&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;gradient&quot;&gt;Gradient&lt;/h2&gt;
&lt;p&gt;Gradient is essentially another word for slope. Most people will be familiar with finding a slope in a 1-dimensional situation. In 1-D, since one can only proceed either forward or backward, the slope is clearly defined. However, in higher dimensions - imagine you find yourself on a hike - the slope may have a different value depending on which direction you go. How does one define the slope at this point?&lt;/p&gt;

&lt;p&gt;In vector calculus, one uses the gradient operator \(\nabla\), which is a vector of the slopes (or first derivatives) along each dimension.&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
\nabla f(x,y)= \begin{bmatrix}
           \frac{\partial f}{dx} (x,y) \\
           \frac{\partial f}{dy} (x,y)
         \end{bmatrix} \tag{2}  \label{eq:gradient}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;On a 2D surface defined by \(f(x,y)\), this would be the slope along the x-axis and the slope along the y-axis. The slope in any particular direction (which is important, since we are not constrained to move only along either the x-axis or the y-axis) can be found by summing the corresponding x and y components of the slope in that direction. In other words, the dot product of a direction vector with the gradient produces the slope in that direction.&lt;/p&gt;

&lt;p&gt;Another interesting property of the gradient is that it gives the direction of the steepest slope. One can see this mathematically - since the slope in any particular direction can be found by dotting that direction vector with the gradient, the dot product is maximized when the gradient is dotted with itself!&lt;/p&gt;

&lt;p&gt;Below, I plot the negative gradient of \(f(x,y)\) with Mathematica. This gives us the direction of steepest descent at every point on \(f\).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/gradient-descent/gradient.png&quot; alt=&quot;Gradient of f&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Gradient of f plotted as a vector field.
&lt;/p&gt;

&lt;h2 id=&quot;follow-the-steepest-part-down&quot;&gt;Follow the steepest part down&lt;/h2&gt;
&lt;p&gt;Earlier we agreed that if you were blindly navigating down a valley, you would follow the steepest direction of the ground down if you wanted to get to the bottom as quickly as possible. Mathematically, you are traversing down the valley along the gradient at each point, since the gradient vector at each point is the direction of the steepest descent. Looking at the image above, we can see that means following the arrows will lead us to a minimum (but be wary - each minimum has its own basin of attraction! More on that later).&lt;/p&gt;

&lt;p&gt;Thus we can define a procedure. It may go something like this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Inspect the slope at your position and determine the direction of steepest descent&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Walk 5 steps in that direction&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you are still descending, go to 1. Otherwise, you’re at the bottom!&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You may be wondering how I arrived at 5 steps. Well, it was totally arbitrary. But we can let it be a parameter of our procedure. Let’s call it a descent rate, and assign it the symbol \(\alpha\). The larger the descent rate, the less often we’ll have to inspect the slope around us. Computationally, this is more efficient - but as you’ll see, this can lead to pitfalls.&lt;/p&gt;

&lt;p&gt;Furthermore, we can make another optimization. When the local slope is still steep, we are probably far from the bottom of the valley. But as the slope gets flatter, we can figure that we are probably approaching the bottom. Hence we may want to descend at a rate proportional to the gradient. Let’s define the gradient with the symbol \(\bar{g}\), and redefine our gradient descent procedure:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Inspect the slope at your position and determine the direction of steepest descent&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Walk \(\alpha \times \|\bar{g}\|\) steps in that direction&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you are still descending, go to 1. Otherwise, you’re at the bottom!&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next, how do we determine if we are still descending? One approach is to check how much we’ve descended on every iteration. If we’re barely changing altitude, then we’re probably essentially at the bottom already.&lt;/p&gt;

&lt;p&gt;Mathematically, our procedure looks like:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
\bar{p}_{n+1} = \bar{p}_n - \alpha \nabla f(x,y) \tag{3}  \label{eq:grad_descent}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;where \(p_n\) is our position at the \(n^{th}\) iteration of our descent. Try it out on the demo below. Varying the rate \(\alpha\) varies the rate of the descent, while varying \(X\) and \(Y\) vary the initial starting point on the surface. What do you notice?&lt;/p&gt;

&lt;div id=&quot;plot-1&quot;&gt;&lt;/div&gt;

&lt;p&gt;&lt;span class=&quot;slider-container&quot;&gt;
    &lt;span class=&quot;slider-label&quot;&gt;X&lt;/span&gt;
    &lt;span&gt;
        &lt;input class=&quot;slider&quot; id=&quot;x-slider&quot; type=&quot;range&quot; min=&quot;-3&quot; max=&quot;3&quot; value=&quot;0&quot; step=&quot;0.05&quot; /&gt;
    &lt;/span&gt;
    &lt;span class=&quot;slider-value&quot; id=&quot;x-slider-value&quot;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;slider-container&quot;&gt;
    &lt;span class=&quot;slider-label&quot;&gt;Y&lt;/span&gt;
    &lt;input class=&quot;slider&quot; id=&quot;y-slider&quot; type=&quot;range&quot; min=&quot;-3&quot; max=&quot;3&quot; value=&quot;0&quot; step=&quot;0.05&quot; /&gt;
    &lt;span class=&quot;slider-value&quot; id=&quot;y-slider-value&quot;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class=&quot;slider-container&quot;&gt;
    &lt;span class=&quot;slider-label&quot;&gt;α&lt;/span&gt;
    &lt;input class=&quot;slider&quot; id=&quot;alpha-slider&quot; type=&quot;range&quot; min=&quot;0&quot; max=&quot;0.2&quot; value=&quot;0&quot; step=&quot;0.001&quot; /&gt;
    &lt;span class=&quot;slider-value&quot; id=&quot;alpha-slider-value&quot;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/p&gt;

&lt;h2 id=&quot;observations&quot;&gt;Observations&lt;/h2&gt;

&lt;p&gt;You may notice that gradient descent is sensitive to the initial position. It will pull you towards a local minimum, which may not be the global minimum. The area of the domain that causes gradient descent to converge to a particular minimum is known as that minima’s basin of attraction.&lt;/p&gt;

&lt;p&gt;You may also notice that increasing the descent rate \(\alpha\) reduces the number of iterations required to arrive at the minimum. But making it too large causes the algorithm to overshoot, and possibly diverge.&lt;/p&gt;

&lt;h2 id=&quot;machine-learning&quot;&gt;Machine learning&lt;/h2&gt;

&lt;p&gt;You’ve probably heard of gradient descent in the context of machine learning. In that context, the cost function represents the error of our machine learning model. For example, it may be the error of classifying images, and the inputs to the cost function are the weights of the model we are varying. Hence, we can use gradient descent to find the optimal configuration of weights that minimize the classification error of the neural network! In this field, the descent rate \(\alpha\) has a special name: it is known as the &lt;em&gt;learning rate&lt;/em&gt;. Maybe it’s what our brains have been doing all along.&lt;/p&gt;

&lt;script src=&quot;https://cdn.plot.ly/plotly-2.4.2.min.js&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjs/9.5.1/math.js&quot; integrity=&quot;sha512-AfRcJIj922x/jSJpQLnry0DYIBg6EGCtwk/MiQ6QvDlzb7kNFxH8EdqXLkaXXY3YHQS9FrSb8H7LzuLn0CZQ1A==&quot; crossorigin=&quot;anonymous&quot; referrerpolicy=&quot;no-referrer&quot;&gt;&lt;/script&gt;

&lt;script src=&quot;/assets/js/gradient-descent.js&quot; async=&quot;&quot;&gt;&lt;/script&gt;

</description>
          <pubDate>Mon, 15 Nov 2021 00:00:00 -0800</pubDate>
          <link>https://blog.skz.dev/gradient-descent</link>
          <guid isPermaLink="true">https://blog.skz.dev/gradient-descent</guid>
        </item>
      
    
      
        <item>
          <title>The Lucid Motors Configurator is A Video Game Streaming to Your Browser</title>
          <description>&lt;p class=&quot;description&quot;&gt;
    It makes sense when you realize video game engines are the best simulators out there.
&lt;/p&gt;

&lt;h2 id=&quot;a-clever-solution&quot;&gt;A Clever Solution&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.barrons.com/articles/lucid-churchill-merger-ipo-ev-51627308359&quot;&gt;Lucid Motors went public on Monday&lt;/a&gt;, and although I am not going to be buying a Lucid anytime soon, I found myself looking at &lt;a href=&quot;https://www.lucidmotors.com/air/configure/&quot;&gt;their vehicle configurator&lt;/a&gt; out of curiosity. It is a piece of art. It allows you to observe and interact with the vehicle from almost every direction, inside and out:&lt;/p&gt;

&lt;div style=&quot;width:100%;height:100%;position:relative;padding-bottom:52.344%;&quot;&gt;&lt;iframe src=&quot;https://streamable.com/e/cudr66&quot; frameborder=&quot;0&quot; width=&quot;100%&quot; height=&quot;100%&quot; allowfullscreen=&quot;&quot; style=&quot;width:100%;height:100%;position:absolute;left:0px;top:0px;overflow:hidden;&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;p class=&quot;caption video-caption&quot;&gt;Lucid's configurator for their flagship elite vehicle, the Lucid Air Dream.&lt;/p&gt;

&lt;p&gt;You can simulate various colours and trims, open and close the doors, and even retract the center console display. The backdrop of the Golden Gate bridge reflects off the glassy puddles. The configurator preserves all sorts of details of the vehicle, including lights under the door handles that activate when you are using them. I feel like I got an accurate enough feel for the vehicle, I don’t even have to see it in person anymore.&lt;/p&gt;

&lt;p&gt;So how did Lucid manage to pull this off? Can you expect the average user’s web browser to render a scene like this in real-time, reflections and all? The answer is probably not. It works because the scene you are seeing is rendered with Unity, a video game engine, running in the cloud - offloading the intense computational load to an Amazon server - and streamed back to your browser via WebRTC - a peer-to-peer video streaming protocol.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-model&quot;&gt;The Model&lt;/h2&gt;

&lt;p&gt;Initially, I naively assumed that the model was being rendered in my browser via the latest &lt;a href=&quot;https://en.wikipedia.org/wiki/WebGL&quot;&gt;WebGL library&lt;/a&gt; (an API for rendering 2D and 3D graphics in web browsers with hardware acceleration). And since the model must have been rendered locally, it had to exist in my RAM somewhere, so I could extract it and load it up in a 3D model editor. Specifically, I wanted to see if I could remove the sun visors interrupting the almost seamless glass canopy - an effect achieved by Tesla’s Model X, I’m not sure why Lucid didn’t follow.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/lucid-configurator-is-a-streaming-video-game/lucid-air-sun-visors.png&quot; alt=&quot;Lucid Air sun visors interrupting the glass canopy&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Lucid Air sun visors interrupting the glass canopy.
&lt;/p&gt;

&lt;p&gt;So I dove into the code. After some time with the browser debug tools, skimming thousands of lines of minified Javascript, recording and examining the network requests between my laptop and Lucid’s servers, I realized the answer was quite literally just being printed in the console the whole time:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/lucid-configurator-is-a-streaming-video-game/zlbolt-webrtc.png&quot; alt=&quot;The Configurator is a Video Stream&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
The Configurator is a webRTC stream.
&lt;/p&gt;

&lt;p&gt;There’s no local model being rendered. What I’m seeing is a video.&lt;/p&gt;

&lt;p&gt;So what’s happening here? The configurator initializes with a 2D display - that is, a choppy panorama of images which provide the illusion of a 3D interaction. This is how most online interactive 3D displays are constructed. You can see a demo of that below:&lt;/p&gt;

&lt;div style=&quot;width:100%;height:100%;position:relative;padding-bottom:52.344%;&quot;&gt;&lt;iframe src=&quot;https://streamable.com/e/ptjitc&quot; frameborder=&quot;0&quot; width=&quot;100%&quot; height=&quot;100%&quot; allowfullscreen=&quot;&quot; style=&quot;width:100%;height:100%;position:absolute;left:0px;top:0px;overflow:hidden;&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;p class=&quot;caption video-caption&quot;&gt;A choppy 3D view, created by a series of 2D images.&lt;/p&gt;

&lt;p&gt;Then, the client tests my bandwidth and latency, and if it’s fast enough to support a realtime video feed, it upgrades the session to 3D:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/lucid-configurator-is-a-streaming-video-game/latency-test.png&quot; alt=&quot;Latency Test&quot; /&gt;
&lt;img src=&quot;/assets/img/lucid-configurator-is-a-streaming-video-game/bandwidth-test.png&quot; alt=&quot;Bandwidth Test&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
You upgrade to a 3D session if you have the bandwidth.
&lt;/p&gt;

&lt;p&gt;This initiates a webRTC connection. &lt;a href=&quot;https://en.wikipedia.org/wiki/WebRTC&quot;&gt;WebRTC&lt;/a&gt; is a peer-to-peer streaming protocol. The scene is rendered elsewhere, and streamed via webRTC to my browser.&lt;/p&gt;

&lt;p&gt;Inspecting the contents of their &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;get_token&lt;/code&gt; request reveals some more details about their mechanics.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/lucid-configurator-is-a-streaming-video-game/get-token-request.png&quot; alt=&quot;Contents of the get_token request&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Contents of the get_token request.
&lt;/p&gt;

&lt;p&gt;The first box reveals that, to test your bandwidth, they partially download from &lt;a href=&quot;https://libzl.zlthunder.net/bandwidth-test-img.png&quot;&gt;a 25 MB test image&lt;/a&gt; to your device, and time how long that takes. This is the image, if you’re curious (don’t worry, I’ve compressed it down 10x).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/lucid-configurator-is-a-streaming-video-game/bandwidth-test-image.png&quot; alt=&quot;Bandwidth Test Image&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Doesn't have much to do with automobiles, but it will test your bandwidth just fine.
&lt;/p&gt;

&lt;p&gt;By the way, &lt;a href=&quot;https://www.gimp.org/tutorials/Floating_Logo/Plasma.jpg&quot;&gt;this image seems to be an effect produced by the plasma plugin from GIMP&lt;/a&gt;. It loads quite slowly; as it resembles noise it must be largely incompressible.&lt;/p&gt;

&lt;p&gt;Opening up &lt;a href=&quot;chrome://webrtc-internals/&quot;&gt;chrome://webrtc-internals/&lt;/a&gt; reveals some details about the webRTC connection streaming the 3D scene to my browser.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/lucid-configurator-is-a-streaming-video-game/lucid-webrtc.png&quot; alt=&quot;WebRTC Info&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Chrome's debug tools provide more information about the webRTC connection.
&lt;/p&gt;

&lt;p&gt;At this point, you may have noticed that none of the URLs here are on a lucid domain, but rather under zlthunder. Who are they?&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;zerolight&quot;&gt;ZeroLight&lt;/h2&gt;

&lt;p&gt;Searching the web for zlthunder brings you to a company called &lt;a href=&quot;https://zerolight.com/&quot;&gt;ZeroLight&lt;/a&gt;. They indicate that they provide cloud-based 3D visualization, specializing in automotive. It all makes sense now!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/lucid-configurator-is-a-streaming-video-game/zero-light.png&quot; alt=&quot;Zero Light&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Cloud based 3D visualization specialists.
&lt;/p&gt;

&lt;p&gt;It wasn’t much longer until I found &lt;a href=&quot;https://unity3d.com/showcase/case-stories/zerolight&quot;&gt;this blog post from Unity&lt;/a&gt; (the prominent video game engine) explaining how ZeroLight leveraged the engine to create a realistic virtual showroom, with optimized physics, reflections, and all.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/lucid-configurator-is-a-streaming-video-game/unity-zero-light.png&quot; alt=&quot;Zero Light&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Video games have brought us a long way.
&lt;/p&gt;

&lt;p&gt;Now I put all the pieces together. When I connect to the site, they load a 2D configurator. If my internet connection can handle it, they connect me to an instance of Unity - a 3D video game engine - running on an AWS server. The server receives my mouse inputs, rotating and zooming the field of view, renders the results in the cloud, and streams them back to me.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;sounds-expensive&quot;&gt;Sounds Expensive&lt;/h2&gt;

&lt;p&gt;Imagine you have a 1,000 users connected to the site: you need a 1,000 hardware accelerated instances of a video game engine receiving inputs and streaming back large amounts of realtime video- just like the cloud video games products that are Stadia and GeForce Now, among others. But it seems they’re very aware of that: to save on costs, they disconnect you after 60s of inactavity:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/lucid-configurator-is-a-streaming-video-game/inactivity-downgrade.png&quot; alt=&quot;Inactivity downgrade&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Downgrading on idle must save a lot of money.
&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;what-is-a-video-game-engine-anyway&quot;&gt;What is a Video Game Engine Anyway?&lt;/h2&gt;

&lt;p&gt;Although it may seem a surprising use of a video game engine at first, a video game engine is really just an optimized simulator. Established game engines like Unity incorporate decades of research into simulating light rays and other physical phenomona. So if you want to simulate reality, even if that reality is a showroom, a video game engine is a perfect, ready-to-use tool, that can be extended to many applications.&lt;/p&gt;

&lt;p&gt;In hindsight, I probably could have figured out how Lucid’s configurator worked by doing some searching and coming across the article from Unity. But reverse-engineering the configurator provided a closer look inside the challenges, solutions, and mechanics of their product, and I felt like a detective working from the ground up. What I found most amusing, though, was the incredible complexity we have achieved in technology today. A super realistic physics simulator, running on a remote server in an Amazon data center, receiving commands from my computer and streaming back the video feed at the speed of light, so that I can play with a car configurator. And all we needed to get to the moon was a calculator!&lt;/p&gt;
</description>
          <pubDate>Wed, 28 Jul 2021 00:00:00 -0700</pubDate>
          <link>https://blog.skz.dev/lucid-configurator-is-a-streaming-video-game</link>
          <guid isPermaLink="true">https://blog.skz.dev/lucid-configurator-is-a-streaming-video-game</guid>
        </item>
      
    
      
        <item>
          <title>Bringing Colour and Life to Black and White Photos</title>
          <description>&lt;p class=&quot;description&quot;&gt;
    Machine learning allows us to colourise black and white photos with ease, but at what risk?
&lt;/p&gt;

&lt;h2 id=&quot;a-different-universe&quot;&gt;A Different Universe&lt;/h2&gt;

&lt;p&gt;Although colourised photos have technically existed for a century or more (&lt;a href=&quot;https://www.loc.gov/pictures/related/?fi=name&amp;amp;q=Prokudin-Gorski%20%2C%20Serge%20%20Mikha%20lovich%2C%201863-1944&quot;&gt;Sergey Prokudin-Gorsky and Piotr Vedenisov, pioneers of colour photography, traveled around Imperial Russia documenting it in full colour&lt;/a&gt;), the majority of the 20th century was documented in black and white. Even later in the century, much of the world did not have the luxury of colour film- my father was developing his own black and white photos into the 1980s-90s in Soviet Ukraine, and while Americans recorded full colour footage of the Vietnam War, the North’s perspective seems to be relegated to black and white photography.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/boy-sim-river.jpeg&quot; alt=&quot;Young boy near the Sim River&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Young boy near the Sim River, a very early colour photograph from Imperial Russia. This was achieved by taking multiple pictures of the same scene with different colour filters. Courtesy of the Library of Congress.
&lt;/p&gt;

&lt;p&gt;Some will argue that black and white footage has its own special quality, and this may be true. But for me black and white footage disconnects me from the scene. It is clearly distant in time. We all associate black and white footage with the past, as the trope of black and white footage in cinematagrophy to refer to a memory shows. And as someone who experiences reality in full colour, black and white footage is difficult to experience. I struggle to put myself into the scene or the characters’ shoes, because in my mind it exists in a different, black and white universe. And yet when that footage is colourized, all of sudden everything appears more real, more believable, more tangible. It is then clear that whatever is depicted in the footage is of the same Earth and of the same people, people like you or me.&lt;/p&gt;

&lt;p&gt;And so when my father recently began digitizing our collection of family photos - as physical photos deteriorate, or are otherwise at risk of being lost forever, along with the histories, memories, and legacies preserved by them - I felt an urge to colourise them, too - so that I could experience them to a fuller extent, and so that my family members could reminisce about their loved ones who are no longer around or remember the memories of their past and their youth.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;colorizing-photos-with-ai&quot;&gt;Colorizing Photos With AI&lt;/h2&gt;

&lt;p&gt;Color restoration can be done manually and laboriously. Machine learned models offer a quick and inexpensive way to colourize photos, but they have their limitations, as we will discuss later in this post. I did not train my own model for this purpose. Others have already invested thousands of hours into developing and training models to do exactly that. I found an existing model that does a very good job.&lt;/p&gt;

&lt;p&gt;I used the &lt;a href=&quot;https://deepai.org/machine-learning-model/colorizer&quot;&gt;Image Colorization API from DeepAi&lt;/a&gt;. The production model deployed by DeepAi appears to be a form of the DeOldify model, which can be found at &lt;a href=&quot;https://github.com/jantic/DeOldify&quot;&gt;this github repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I don’t think the DeepAi API allows you to adjust the model parameters, but you can if you run it yourself from the repo above or &lt;a href=&quot;https://colab.research.google.com/github/jantic/DeOldify/blob/master/ImageColorizerColabStable.ipynb&quot;&gt;a colab instance&lt;/a&gt;. &lt;a href=&quot;https://www.myheritage.com/incolor&quot;&gt;MyHeritage&lt;/a&gt; also offers this API as a service with tunable parameters.&lt;/p&gt;

&lt;p&gt;What I did was create a quick script that enables bulk colorization of directories. It iterates over all the photos in a directory, sends them off to Image Colorization API from DeepAi, and downloads the result into a separate folder (ignoring images that have already been colourised). If that would be useful to you, just set your own DeepAi API key as an environment variable and run &lt;a href=&quot;https://github.com/skzv/colorize-photos&quot;&gt;this script&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;DL_API_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&amp;lt;your-deep-ai-key&amp;gt;
python3 main.py /path/to/images
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;my-family-photos&quot;&gt;My Family Photos&lt;/h2&gt;
&lt;p&gt;Here are some examples of the results on our family photos.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/mama-and-vova.jpg&quot; alt=&quot;Mama with Vova&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
My mother holding my brother in early '90s Kherson, Ukraine. Courtesy of the Kuznetsov family.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/mama-and-vova-colorized.jpg&quot; alt=&quot;Mama with Vova&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Now in colour.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/my-grandmother-allarkadina.jpg&quot; alt=&quot;Babushka Allarkadina&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
My grandmother, Allarkadina, likely sometime in the '50s, on her way to university. She graduated from multiple institutes, specializing in electrical engineering, and had a successful career as a marine electrical engineer.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/my-grandmother-alla-colourised.jpeg&quot; alt=&quot;Babushka Allarkadina&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
In colour.
&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Looks so nice and alive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those words from my father upon seeing the newly coloured photos made it all worth it.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;photos-from-the-vietnam-war&quot;&gt;Photos From the Vietnam War&lt;/h2&gt;
&lt;p&gt;A couple weeks after preparing a bulk colourising script for my family photos, a post titled &lt;a href=&quot;https://news.ycombinator.com/item?id=27283624&quot;&gt;“The Vietnam War from the North Vietnamese Side”&lt;/a&gt; made it to the top of Hacker News. It seemed like a perfect opportunity to apply the script, and offer a new perspective on historical footage. Just as &lt;a href=&quot;https://www.imdb.com/title/tt7905466/&quot;&gt;“They Shall Not Grow Old”&lt;/a&gt; made the horrors of WW1 living and real, so did I aim to make the characters and scenes from the perspective of North Vietnam.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/vietnam-agent-orange.jpeg&quot; alt=&quot;Mama with Vova&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Destruction of the jungle wrought by Agent Orange.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/vietnam-agent-orange-colourised.jpg&quot; alt=&quot;Mama with Vova&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
In colour.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/vietnam-downed-aircraft.jpeg&quot; alt=&quot;Mama with Vova&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Locals inspecting a downed American aircraft piloted by Lt. Stephen Owen Musselman. The pilot did not survive- war is hell.
&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/vietnam-downed-aircraft-colourised.jpg&quot; alt=&quot;Mama with Vova&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
In colour.
&lt;/p&gt;

&lt;p&gt;All the original black and white images can be found &lt;a href=&quot;https://rarehistoricalphotos.com/vietnam-war-images-from-vietnamese-photographers/&quot;&gt;here&lt;/a&gt;, with the colourised versions &lt;a href=&quot;https://imgur.com/a/aJbpMjf&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;response-on-hacker-news&quot;&gt;Response on Hacker News&lt;/h2&gt;

&lt;p&gt;My efforts were met with both praise&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/praise.png&quot; alt=&quot;Praise&quot; /&gt;&lt;/p&gt;

&lt;p&gt;and contempt&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/contempt.png&quot; alt=&quot;Contempt&quot; /&gt;&lt;/p&gt;
&lt;p class=&quot;caption&quot;&gt;
Too bad about the [flagged] comment, because it raised an interesting point. The comment read something like 'Please don't do this.'
&lt;/p&gt;

&lt;p&gt;but most importantly, they generated a lot of very interesting discussions.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/discussion.png&quot; alt=&quot;Discussion&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The full thread spurred by the colourised photos can be found &lt;a href=&quot;https://news.ycombinator.com/item?id=27284795&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;limitations&quot;&gt;Limitations&lt;/h2&gt;

&lt;p&gt;There were two aspects of the discussion that stood out to me. First, was the reminder of the limitations of AI.&lt;/p&gt;

&lt;p&gt;For example, the model invoked on an old image of the Golden Gate under construction produces a white bridge. It’s not wholly inaccurate, to be fair- the bridge was indeed white early in its construction, but by the time the picture was taken, the bridge had already been covered in red primer (according to the source at the &lt;a href=&quot;https://github.com/jantic/DeOldify&quot;&gt;DeOldify repository&lt;/a&gt;). So while the training data probably did contain the Golden Gate under construction, and the model could identify the same bridge under construction here, it could not discern the date and know that the bridge had already been primed with red at this point.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/golden-gate.jpeg&quot; alt=&quot;A White Golden Gate?&quot; /&gt;&lt;/p&gt;
&lt;p class=&quot;caption&quot;&gt;
A white Golden Gate? It was white- but not when this picture was taken.
&lt;/p&gt;

&lt;p&gt;Some scenes are ambiguous. For example, based on the features in the following, the model cannot differentiate between the ground being field or asphalt, and incorrectly colours the asphalt green, where a human colouriser may get it right.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/a-celebration-colourised.jpg&quot; alt=&quot;A celebration&quot; /&gt;&lt;/p&gt;
&lt;p class=&quot;caption&quot;&gt;
The model can't tell that the ground is asphalt and not grass.
&lt;/p&gt;

&lt;p&gt;Another interesting observation was that clothes seemed to often lack distinctive hues, textures, features, or context that made them accurately colourisable. The model tended to colour clothes purple - unless they had distinctive features or context. This is probably because when the model cannot make a good inference about the colour of the clothes, purple is a conservative bet, somewhere in the middle of the ambiguous colour space, that minimizes the training loss.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/colourising-black-and-white-photos/vietnam-divers-colourised.jpg&quot; alt=&quot;Vietnamese divers - all purple?&quot; /&gt;&lt;/p&gt;
&lt;p class=&quot;caption&quot;&gt;
I don't think clothes in the past were as often as purple as this model makes it out to be.
&lt;/p&gt;

&lt;p&gt;Indeed, my father requested to correct the colour of some of the articles in the photos. Unfortunately I did not pursue this request, nor is it immediately obvious to me how I could. But as one commenter remarked, even when the colours are wrong, they still bring the images to life.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;ethical-considerations&quot;&gt;Ethical Considerations&lt;/h2&gt;

&lt;p&gt;Now that some of the limitations are clear, we are in a good position to consider the ethical ramifications of a colourising AI. This is was the second aspect of discussion that stood out to me. Ethics did not occur to me when I undertook the project, but some of the feedback I received spurred me to reflect, and for that I am thankful.&lt;/p&gt;

&lt;p&gt;The main ethical consideration is truth. Similar to the controversy around deepfakes confusing us to what is true, artificial coloration can alter our experience of history. The AI makes choices about the colours of the past, which are not necessarily accurate. For example, if we keep and disseminate the white version of the Golden Gate, produced by such an AI, then the version of the bridge that is experienced by viewers is not historically accurate. Of course, neither is the black and white version. Perhaps the trouble is that viewers of the black and white image know that’s not what that past looked like, but viewers of the coloured image are easily tricked into believing that is history as it was.&lt;/p&gt;

&lt;p&gt;Some may go as far as to say such retouching is rewriting of history, and indeed, sometimes it appears so. Take this recent example:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Cambodia has called on US media group Vice to withdraw an article that featured newly-colourised photographs of victims of the Khmer Rouge, saying the images are an insult to the dead because some had been altered to add smiles.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p class=&quot;caption&quot; style=&quot;margin-top:-25px;&quot;&gt;&lt;a href=&quot;https://www.aljazeera.com/news/2021/4/11/cambodia-condemns-vice-for-altered-khmer-rouge-images&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which is a very valid complaint. It appears the model was over-fit, likely trained on mostly smiling faces.  It thus altered the faces of the victims to more closely resemble the training data- by smiling. It is a good example of the pitfalls of AI, especially where applied carelessly.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.myheritage.com/incolor&quot;&gt;MyHeritage&lt;/a&gt;, which offers a color restoration service based on the same DeOldify model I have used, attempts to make a compromise by watermarking all their retouched images. I think they make a compelling point:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We differentiate colorized photos from those photographed originally in color using a special embossed palette symbol in the bottom left corner of colorized photos. While highly realistic, colorized photos have colors that are simulated by automatic algorithms and these colors may not be identical to the real-life colors of the original image. The palette icon appears on all colorized photos so that users can tell them apart from photos that have real colors. We hope that this responsible practice will be adopted by others who use photo colorization technology. Photos that have undergone color restoration do not have an icon because those colors are authentic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my case, my family knew that I had artificially colourised our photos, and they could tell the colours were not wholly accurate. But even with the wrong colours, the photos, and people and scenes within them, were brought back to life. Just as when I was a high-school volunteer in a retirement home, and I was able to transport a senior resident to his childhood home in Dresden via Google Streetview, which he had not visited in 50 years, I was able to use AI to teleport our family  to good times decades in the past. Technology is a tool, and I’m thankful I live in an age where magic like this is possible.&lt;/p&gt;
</description>
          <pubDate>Sat, 12 Jun 2021 00:00:00 -0700</pubDate>
          <link>https://blog.skz.dev/bringing-colour-and-life-to-black-and-white-photos</link>
          <guid isPermaLink="true">https://blog.skz.dev/bringing-colour-and-life-to-black-and-white-photos</guid>
        </item>
      
    
      
        <item>
          <title>Arbitrage as a Shortest Path Problem</title>
          <description>&lt;p class=&quot;description&quot;&gt;
    An explanation of arbitrage and a look at an efficient algorithm to find riskless instantaneous arbitrage opportunities.
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Who doesn’t like to make money?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And what if you could turn the problem of making money into the problem of finding the shortest-path? We can do that in at least one particular way: by exploiting arbitrage opportunities.&lt;/p&gt;

&lt;h2 id=&quot;what-is-arbitrage&quot;&gt;What is arbitrage?&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Arbitrage&lt;/em&gt; is the act of buying or selling things across different markets, or in different forms, to profit from differences in prices. And the people who engage in it? They’re known as &lt;em&gt;arbitrageurs&lt;/em&gt; — a fancy title indeed.&lt;/p&gt;

&lt;p&gt;Let’s start with an example. Say Paul, Peter, and Bob live in a village, where they barter carrots, potatoes, and lettuce. Bob trades potatoes for carrots, Peter trades lettuce for potatoes, and Paul trades lettuce for carrots.&lt;/p&gt;

&lt;p&gt;Furthermore, Bob will trade 2 potatoes for a carrot, Peter will trade 1 lettuce for 2 potatoes, and Paul will trade 2 carrots for 1 lettuce. If we were to treat each individual as a market for their respective goods, what would the exchange rates look like?&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Market&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Exchange Rate&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Bob (potatoes for carrots)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2 potato/carrot&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Peter (lettuce for potatoes)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;1 lettuce/2 potato = 0.5 lettuce/potato&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;Paul (carrots for lettuce)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;2 carrot/lettuce&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p class=&quot;caption&quot;&gt;
Exchange rates for goods in our village.
&lt;/p&gt;

&lt;p&gt;Do you sense an opportunity?&lt;/p&gt;

&lt;p&gt;Being the enterprising individual that you are, you may try to exploit it. Starting with 5 carrots, you approach Bob, and trade your 5 carrots for 10 potatoes, at the rate at which he is willing to trade.&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
5 \text{ carrots} \times 2 \space \frac{\text{potatoes}}{\text{carrots}} = 10 \text{ potatoes} \tag{1} \label{eq:arb_example}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;Next, you approach Peter with your potatoes, knowing that he’ll trade you 5 lettuce for them. Then you approach Paul with your 5 lettuce, and he trades 10 carrots for your lettuce.&lt;/p&gt;

&lt;p&gt;After a few smart trades, you’ve doubled your carrot wealth. You turned 5 carrots into 10, by exploiting an arbitrage opportunity.&lt;/p&gt;

&lt;p&gt;Later on, the villagers may develop more sophisticated markets where Bob, Peter, and Paul aren’t the only traders. Instead this little village might develop a carrot/lettuce market, a lettuce/potato market, and a potato/carrot market, where the going rate for each trade will fluctuate based on what people are willing to trade.&lt;/p&gt;

&lt;p&gt;But the arbitrage principle remains the same. And the opportunity can be exploited until the traders offering to exchange at those rates run out of carrots, lettuce, and potatoes to trade. The arbitrage opportunity is exploited until the market reaches an equilibrium.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;arbitrage-in-modern-times&quot;&gt;Arbitrage in Modern Times&lt;/h2&gt;

&lt;p&gt;When you think of modern markets, you probably don’t think of trading carrots, potatoes, and lettuce. If you’re into forex, you’re more likely thinking of trading dollars, pounds, and yen. And in that case, we would be dealing with a dollar/pound market, a pound/yen market, and a yen/dollar market, with many traders acting on each market. From now on, let’s use those currencies as examples of things to trade, but keep in mind the principles can apply to all sorts of trade-able things.&lt;/p&gt;

&lt;p&gt;Say at a given time, the exchange rates are as follows:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Market&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Exchange Rate&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;pound (£) / dollar ($)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.8 pounds/dollar&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;yen (¥) / pound (£)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;100 yen/pound&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;dollars ($) / yen (¥)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.013 dollars/yen&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p class=&quot;caption&quot;&gt;
An example of currency market exchange rates.
&lt;/p&gt;

&lt;p&gt;If you learned anything from Bob, Peter, and Paul trading carrots, lettuce, and potatoes, you’d sense an opportunity here.&lt;/p&gt;

&lt;p&gt;If you trade $1 for pounds, you’ll end up with £0.8. Trading that for yen, you’ll end up with ¥80. You take your yen to the yen/dollar exchange where you trade it back for dollars… but now you have $1.04!&lt;/p&gt;

&lt;p&gt;But you have to act quickly, before another arbitrageur beats you to the punch. These opportunities only exist temporarily, until the liquidity is used up and the rates equalize.&lt;/p&gt;

&lt;p&gt;The perceptive among you may notice that we haven’t considered exchange fees in our example. Of course, you’d have to take those into account to calculate if a profitable arbitrage opportunity actually exists.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;acting-quickly&quot;&gt;Acting Quickly&lt;/h2&gt;

&lt;p&gt;Hopefully you have some intuition to understand why acting fast is paramount. Exchange rates fluctuate quickly, and there is only a limited amount of “thing” available at that exchange rate.&lt;/p&gt;

&lt;p&gt;While we’ve worked with relatively simple examples here, arbitrage opportunities can span many trades, becoming incredibly complex. Our examples used 3 trades, but what if you needed 10? And in a network of 20 currencies with a market for each pair, how quickly could you find an opportunity?&lt;/p&gt;

&lt;p&gt;Using a computer is an obvious answer. But we need an efficient algorithm, lest someone else beat us to the opportunity.&lt;/p&gt;

&lt;p&gt;To achieve that, we can leverage a few clever insights in mathematics and computer science.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;markets-as-a-graph&quot;&gt;Markets as a Graph&lt;/h2&gt;

&lt;p&gt;Graphs are an incredibly important structure that has found its uses in numerous applications. Many social and natural structures can be modeled with graphs, and it turns out that markets are one of them.&lt;/p&gt;

&lt;p&gt;In our case, let’s treat each currency as a node. Moving from node to node corresponds to trading one currency for another.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/arbitrage-as-a-shortest-path-problem/currency-graph-white.png&quot; alt=&quot;Simplified Currency Graph&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
Simplified graph — there should be a distinct edge in each direction.
&lt;/p&gt;

&lt;p&gt;So moving along an edge, between nodes, should transform the amount of currency by the exchange rate.&lt;/p&gt;

&lt;p&gt;That means moving from the dollar node to the pound node corresponds to multiplying by 0.8 pounds/dollar. Let’s assign the exchange rate as the weight of each edge.&lt;/p&gt;

&lt;p&gt;Note that the exchange rate in each direction will be &lt;em&gt;approximately&lt;/em&gt; the reciprocal of each other. That means if the rate to convert pounds to dollars is 0.8 pounds/dollar, the rate in the other direction will be 1/(0.8 pounds/dollar) = 1.25 dollars/pound. The consequence for us is that we need to be careful to treat buying and selling on each market as distinct, directed edges, with different weights.&lt;/p&gt;

&lt;p&gt;The reason that the exchange rates in both directions are only &lt;em&gt;approximately&lt;/em&gt; reciprocal is due to small differences in the prices to buy and sell currencies, known as the buy-sell spread. For example, if at a given moment you can buy pounds at 0.8 pounds/dollar (the current price somebody will sell to you), but can sell dollars for pounds at 0.82 pounds/dollar (or 1.22 dollars/pound, and the current price somebody will buy from you), your graph model will look like this (excluding the other exchange rates for simplicity):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/arbitrage-as-a-shortest-path-problem/currency-graph-w-exchange-rates-white.png&quot; alt=&quot;Currency Graph with Exchange Rates&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A series of trades can be modeled by moving along edges in this graph, and the result of the trades is computed by &lt;strong&gt;&lt;em&gt;multiplying&lt;/em&gt;&lt;/strong&gt; the edge weights as you move along them.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;seeing-the-opportunity&quot;&gt;Seeing the Opportunity&lt;/h2&gt;

&lt;p&gt;Now that we have a working model, what are we looking for in our graph that corresponds to an arbitrage opportunity?&lt;/p&gt;

&lt;p&gt;To determine whether a series of trades is profitable, we need a consistent metric of profitability. In other words, if we begin our series of trades in dollars, then we’ll need to end it in dollars, too. And by comparing the amount of dollars we ended up with to the amount we started with, we’ll know if it was profitable or not.&lt;/p&gt;

&lt;p&gt;In our graph, that means that our series of trades must end at the same node from which it started. In our case, we started at the dollar node, and ended in the dollar node. In graph terminology, we call that a cycle. So we know we’re looking for some sort of cycle- but what kind of cycle makes it profitable?&lt;/p&gt;

&lt;p&gt;Notice that if we multiply along the edges of a cycle, we transform the units of the effective exchange rate.&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
a \space \frac{\cancel{£}}{$} \times b \space \frac{¥}{\cancel{£}} = ab \space \frac{¥}{$}  \tag{2} \label{eq:units_of_exchange_rate}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;However, when we return to our starting node, the quantity becomes unit-less. It transforms from a rate of exchange, to a ratio of return! Traversing a cycle on our graph and computing the product of exchange rates along the way corresponds to calculating the ratio of return we would get after completing the series of trades.&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
a \space \frac{\cancel{£}}{\cancel{$}} \times b \space \frac{\cancel{¥}}{\cancel{£}} \times c \space \frac{\cancel{$}}{\cancel{¥}} = abc \space \text{[dimensionless]}  \tag{3} \label{eq:unitless_exchange}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;If the market is perfectly efficient, our return ratio, \(abc\), will be 1, because the exchange rates have equalized. If the product of weights is greater than 1, say 1.02, then our arbitrage opportunity would have made us a 2% return.&lt;/p&gt;

&lt;p&gt;Therefore, generalizing to an arbitrary number of trades, an arbitrage opportunity corresponds to the following inequality:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
\prod_i^n{e_i} = e_ie_2...e_n &amp;gt; 1  \tag{4} \label{eq:arbitrage_return}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;where \(e_i\) corresponds to the \(i^{th}\) exchange rate, for each trade \(i\), over n trades.&lt;/p&gt;

&lt;p&gt;So what we need is an algorithm that will find a cycle on the graph of markets, where the product of edge weights is greater than 1. You might be able to invent an algorithm to do that - but in computer science, as in life in general, it’s useful to reduce problems to ones you already know how to solve.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-bellman-ford-algorithm&quot;&gt;The Bellman-Ford Algorithm&lt;/h2&gt;

&lt;p&gt;The problem of finding shortest paths is a common and fundamental problem in computer science, which can be applied to many different scenarios. An obvious one, by drawing a correspondence between a graph and a map, is that of finding the shortest route on a map. But with some cleverness, many other kinds of problems can be transformed into a shortest-path problem, as well. What I’m going to prove to you is that the problem of finding arbitrage opportunities is one such problem.&lt;/p&gt;

&lt;p&gt;First, let’s establish what the shortest path problem is. Given two nodes in a graph, \(s\) and \(t\), the shortest path is that path which minimizes the &lt;em&gt;sum&lt;/em&gt; of edge weights. In other words, we move along the path from \(s\) to \(t\), adding up edge weights along the way, the path with the minimum sum is the shortest path — the path with the smallest cost.&lt;/p&gt;

&lt;p&gt;Next, it will be helpful to understand that there are different classes of shortest-path problems. In the obvious example — like the shortest route on a map — edge weights must be positive. There’s no way, barring a time machine, that driving down a road will reduce your travel time. In a graph with only positive edge weights, Dijkstra’s famous algorithm will compute the shortest path to all nodes in the graph.&lt;/p&gt;

&lt;p&gt;However, there is no reason a graph cannot have negative edge weights. In that case, moving along that edge reduces the total cost of the path. But, if you have a &lt;em&gt;cycle&lt;/em&gt; which has a negative weight, then you can keep traversing that &lt;em&gt;cycle&lt;/em&gt; forever — every time, lowering your overall cost of the path — and your shortest path will have a cost approaching \(-\infty\). In that case, it would be very useful for our shortest-path algorithm to have a mechanism to identify negative weight cycles. Otherwise the shortest path would get stuck circling the negative weight cycle, forever.&lt;/p&gt;

&lt;p&gt;The Bellman-Ford algorithm is exactly that algorithm. A more general version of Dijkstra’s shortest-path algorithm, it can handle negative weights. To do so, it detects &lt;em&gt;negative weight cycles&lt;/em&gt; — cycles in a graph along which &lt;em&gt;adding&lt;/em&gt; up the weights produces a negative value.&lt;/p&gt;

&lt;p&gt;But how does an algorithm which can find cycles where the &lt;strong&gt;&lt;em&gt;sum&lt;/em&gt;&lt;/strong&gt; of edges is less than 0 help us, when we need an algorithm that can detect cycles where the &lt;strong&gt;&lt;em&gt;product&lt;/em&gt;&lt;/strong&gt; of edges is greater than 1?&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;log-to-the-rescue&quot;&gt;Log to the Rescue&lt;/h2&gt;

&lt;p&gt;The next insight is that a product can be turned into a sum by applying the logarithmic function, thanks to the identity:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
\log{ab} = \log{a} + \log{b}  \tag{5} \label{eq:log_product_identity}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;Thereby we can transform our problem of finding a cycle with a &lt;em&gt;product&lt;/em&gt; greater than 1, to a problem of finding a cycle with a &lt;em&gt;sum&lt;/em&gt; greater than 0! We do that by taking the log of each exchange rate, and using that as the weight of each edge.&lt;/p&gt;

&lt;p&gt;Let’s show that by taking the log of both sides of our inequality. First, taking the log of the left-hand side transforms the problem of computing a product into computing a sum:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
\log{\prod_i^n{e_i}} = \log{e_ie_2...e_n} = \log{e_1} + ... + \log{e_n} = \sum_i^n{\log{e_i}} \tag{6} 
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;The log of the right side just transforms the 1 to a 0:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
\sum_i^n{\log{e_i}} &amp;gt; \log{1} \rightarrow \sum_i^n{\log{e_i}} &amp;gt; 0 \tag{7}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;We’re close, but not quite there. The final step, to reduce our problem to one that we can solve with this known algorithm, is to multiply each edge weight by -1. This turns the problem of finding a positive weight cycle, into finding a negative one:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
\sum_i^n{-\log{e_i}} &amp;lt; 0 \tag{8}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;Which we know the Bellman-Ford algorithm can do! Constructing a graph as specified and executing the Bellman-Ford algorithm on it will quickly and efficiently find arbitrage opportunities for us, because we’ve turned the arbitrage problem into the problem of finding the shortest path — &lt;em&gt;the infinitely shortest path&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/img/arbitrage-as-a-shortest-path-problem/neg-log-currency-graph-white.png&quot; alt=&quot;Negative Log Currency Graph&quot; /&gt;&lt;/p&gt;

&lt;p class=&quot;caption&quot;&gt;
We need to find a negative weight cycle, where the weights are the negative logarithm of the exchange rates.
&lt;/p&gt;

&lt;p&gt;In hindsight, it’s obvious that there should be a correspondence between a negative weight cycle — which lowers the cost of the path every time you traverse it — to an arbitrage opportunity, which makes you a profit every time you traverse it. The key insight is transforming a problem of finding a product greater than 1 into finding a sum less than 0, by applying -log to the edge weights.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;prove-it&quot;&gt;Prove It&lt;/h2&gt;

&lt;p&gt;Let’s run this algorithm on our exchange rates to see if it correctly identifies the arbitrage opportunity. Transforming the exchange rates by -log, we get:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;Market&lt;/th&gt;
      &lt;th style=&quot;text-align: center&quot;&gt;-log(Exchange Rate)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;pound (£) / dollar ($)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;0.223&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;yen (¥) / pound (£)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;-4.605&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;dollars ($) / yen (¥)&lt;/td&gt;
      &lt;td style=&quot;text-align: center&quot;&gt;4.343&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Summing over the trades, our equality holds- &lt;em&gt;we found a negative weight cycle&lt;/em&gt;!&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
\sum_i^n{-\log{e_i}} = 0.223 - 4.605 + 4.343 = -0.039 \label{eq:neg_log_exchange_rate_example} \tag{9}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;We can undo the logarithmic operation to restore the product, and calculate the return:&lt;/p&gt;

&lt;div style=&quot;width: 100%; overflow-x: auto;&quot;&gt;
    $$
        
\begin{align}
\prod_i^n{e_i} &amp;amp;= \exp{\sum_i^n{-\log{e_i}}} \\
\prod_i^n{e_i} &amp;amp;= \exp(0.039) = 1.04 \tag{10}
\end{align}

    $$
&lt;/div&gt;

&lt;p&gt;Which is exactly the 4% return we calculated earlier.&lt;/p&gt;

&lt;div class=&quot;divider&quot;&gt;&lt;/div&gt;

&lt;h2 id=&quot;in-the-real-world&quot;&gt;In the Real World&lt;/h2&gt;

&lt;p&gt;Since an arbitrage opportunity corresponds to a negative weight cycle, it seems that we could traverse the cycle forever to make infinite money. Of course, that is not the case.&lt;/p&gt;

&lt;p&gt;The liquidity — the volume available before the price changes significantly- available for any arbitrage opportunity is limited, and is quickly exploited by algorithmic traders pushing the boundaries of computing technology and the laws of physics to beat each other.&lt;/p&gt;

&lt;p&gt;That being said, I hope you found this exercise in applying graph theory and a well known shortest path algorithm to solving a problem in the finance domain — and making money — as interesting as I always have.&lt;/p&gt;
</description>
          <pubDate>Sun, 25 Apr 2021 00:00:00 -0700</pubDate>
          <link>https://blog.skz.dev/arbitrage-as-a-shortest-path-problem</link>
          <guid isPermaLink="true">https://blog.skz.dev/arbitrage-as-a-shortest-path-problem</guid>
        </item>
      
    
  </channel>
</rss>
