<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Coding - Yifeitao ｜ Secondary Evolution</title><link>https://yifeitao.com/en/categories/coding/</link><description>On creation, learning and investing</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Tue, 23 Apr 2019 00:00:00 +0000</lastBuildDate><atom:link href="https://yifeitao.com/en/categories/coding/index.xml" rel="self" type="application/rss+xml"/><item><title>Frog and Cache: A Simplified, Practical Dynamic Programming</title><link>https://yifeitao.com/en/frog-and-cache/</link><pubDate>Tue, 23 Apr 2019 00:00:00 +0000</pubDate><guid>https://yifeitao.com/en/frog-and-cache/</guid><description>&lt;h2 id="1-starting-with-a-frog"&gt;1. Starting with a frog&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://i.imgur.com/zuhczaLm.jpg" alt=" " loading="lazy" decoding="async" referrerpolicy="no-referrer" /&gt;
&lt;/p&gt;
&lt;p&gt;A frog wants to hop down &lt;code&gt;n&lt;/code&gt; steps into a pond. Each hop covers 1 or 2 steps. How many distinct ways can it get down there (say, when &lt;code&gt;n=30&lt;/code&gt;)?&lt;/p&gt;
&lt;p&gt;In mathematical terms we want a function &lt;code&gt;f(n)&lt;/code&gt; for the frog&amp;rsquo;s jumps. For a function on non-negative integers we can start from small cases, and it is not hard to get &lt;code&gt;f(1)=1, f(2)=2&lt;/code&gt;. The problem is that brute-force enumeration gets painful fast.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="1-starting-with-a-frog">1. Starting with a frog</h2>
<p><img src="https://i.imgur.com/zuhczaLm.jpg" alt=" " loading="lazy" decoding="async" referrerpolicy="no-referrer" />
</p>
<p>A frog wants to hop down <code>n</code> steps into a pond. Each hop covers 1 or 2 steps. How many distinct ways can it get down there (say, when <code>n=30</code>)?</p>
<p>In mathematical terms we want a function <code>f(n)</code> for the frog&rsquo;s jumps. For a function on non-negative integers we can start from small cases, and it is not hard to get <code>f(1)=1, f(2)=2</code>. The problem is that brute-force enumeration gets painful fast.</p>
<p>Imagine you are that frog facing <code>n</code> steps. On the first hop you can take 1 step, leaving <code>n-1</code> steps with <code>f(n-1)</code> ways down; or you can take 2 steps, leaving <code>n-2</code> steps with <code>f(n-2)</code> ways down. So the answer is nothing new — it is the Fibonacci sequence:</p>
<p>$$
\begin{aligned} F_{0} &amp;=0 \ F_{1} &amp;=1 \ F_{n} &amp;=F_{n-1}+F_{n-2} \quad(\mathrm{n} \geq 2) \end{aligned}
$$</p>
<p>The first step in solving this kind of function-value problem is to find a recurrence. Translate the recurrence into Python:</p>


<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">fib</span>(n):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> n<span style="color:#f92672">==</span><span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> n<span style="color:#f92672">==</span><span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> fib(n<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)<span style="color:#f92672">+</span>fib(n<span style="color:#f92672">-</span><span style="color:#ae81ff">2</span>)</span></span></code></pre></div>


<pre tabindex="0"><code>%%time
fib(30)

Wall time: 269 ms
832040</code></pre>
<p>The runtime is <code>284ms</code> — slow enough. Why? Because there is far too much repeated computation. Take <code>f(5)</code> as an example; the calls look like this:</p>


<pre tabindex="0"><code>f(5)==&gt;f(4), f(3)
f(4)==&gt;f(3), f(2), f(3)==&gt;f(2), f(1)
f(3)==&gt;f(2), f(1), f(2)==&gt;f(1), f(0), f(2)==&gt;f(1), f(0), f(1)
f(2)==&gt;f(1), f(0), f(1), f(1), f(0), f(1), f(0), f(1)
f(1), f(0), f(1), f(1), f(0), f(1), f(0), f(1)</code></pre>
<p>The natural idea, then, is to cache every intermediate result. Fortunately Python ships that &ldquo;battery&rdquo; already.</p>


<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> functools <span style="color:#f92672">import</span> lru_cache
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@lru_cache</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">fib</span>(n):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> n<span style="color:#f92672">==</span><span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> n<span style="color:#f92672">==</span><span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> fib(n<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)<span style="color:#f92672">+</span>fib(n<span style="color:#f92672">-</span><span style="color:#ae81ff">2</span>)</span></span></code></pre></div>


<pre tabindex="0"><code>%%time
fib(30)
Wall time: 0 ns
832040</code></pre>
<p>So fast that no time could be measured. The basic mechanism of <code>lru_cache</code> in Python is a dict whose <code>key</code> is the call argument and whose <code>value</code> is the result for that argument. It is roughly equivalent to this code:</p>


<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">fib</span>(n):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> n <span style="color:#f92672">in</span> fib<span style="color:#f92672">.</span>cache:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> fib<span style="color:#f92672">.</span>cache[n]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> n<span style="color:#f92672">==</span><span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>        ans <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> n<span style="color:#f92672">==</span><span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>        ans <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span>:
</span></span><span style="display:flex;"><span>        ans <span style="color:#f92672">=</span> fib(n<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)<span style="color:#f92672">+</span>fib(n<span style="color:#f92672">-</span><span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>    fib<span style="color:#f92672">.</span>cache[n] <span style="color:#f92672">=</span> ans
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> ans
</span></span><span style="display:flex;"><span>fib<span style="color:#f92672">.</span>cache <span style="color:#f92672">=</span> {}</span></span></code></pre></div>
<p>Of course, for this specific problem we can use a more careful caching scheme, or even drop recursion in favor of a loop (equivalent to keeping only two cached values, which cuts space usage a lot — but if we need to compute many different <code>n</code> values over and over, the previous method may be the better fit):</p>


<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">fib</span>(n):
</span></span><span style="display:flex;"><span>    a, b <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(n):
</span></span><span style="display:flex;"><span>        a, b <span style="color:#f92672">=</span> b, a<span style="color:#f92672">+</span>b
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> a</span></span></code></pre></div>
<p>This problem is identical to <a href="https://leetcode-cn.com/problems/climbing-stairs"
   rel="noopener noreferrer" target="_blank" class="external">leetcode 70</a>
. The python3 solution on leetcode is:</p>


<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> functools <span style="color:#f92672">import</span> lru_cache
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Solution</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@lru_cache</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">climbStairs</span>(self, n: int) <span style="color:#f92672">-&gt;</span> int:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> n<span style="color:#f92672">==</span><span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> n<span style="color:#f92672">==</span><span style="color:#ae81ff">1</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>climbStairs(n<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)<span style="color:#f92672">+</span>self<span style="color:#f92672">.</span>climbStairs(n<span style="color:#f92672">-</span><span style="color:#ae81ff">2</span>)</span></span></code></pre></div>
<p>Runtime 52 ms, memory 13.2MB.</p>
<h2 id="2-a-simplified-practical-dynamic-programming">2. A simplified, practical dynamic programming</h2>
<p>The frog gives us a fairly general lesson for problems where a recurrence can be constructed:</p>
<ol>
<li>Find a recurrence relation, write the recursive function, and the problem becomes solving a set of subproblems;</li>
<li>To avoid recomputing the same subproblem, use a cache — trade space for time.</li>
</ol>
<p>General algorithm textbooks and interview write-ups spend a lot of time designing that cache structure. In real engineering we are usually not that sensitive to using a bit more cache space, so writing the recursive function plus a generic cache basically solves the problem. Only when cache space itself becomes a concern do we need to think about a smaller cache tailored to the problem.</p>
<p>To test this approach, let us look at a few more problems, pulled straight from leetcode.</p>
<h3 id="21-maximum-subarray">2.1 <a href="https://leetcode-cn.com/problems/maximum-subarray/"
   rel="noopener noreferrer" target="_blank" class="external">Maximum Subarray</a>
</h3>
<p>Given an integer array <code>nums</code>, find the contiguous subarray with the largest sum (the subarray contains at least one element) and return that sum.</p>
<p><strong>Example:</strong></p>
<blockquote>
<p>Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: The contiguous subarray [4,-1,2,1] has the largest sum, 6.</p>
</blockquote>
<p>Consider the recurrence for the maximum sum obtainable by a subarray ending at each position in the array.
$$
\begin{aligned}  f(0)&amp;=nums(0) \ f(k)&amp;=max(f(k-1), 0)+nums(k) \quad(k&gt;0) \end{aligned}
$$
From that, the final answer is easy:
$$
ans = max_{i=0}^n(f(i))
$$
Translated into python3 on leetcode:</p>


<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> functools <span style="color:#f92672">import</span> lru_cache
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Solution</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">maxSubArray</span>(self, nums: List[int]) <span style="color:#f92672">-&gt;</span> int:
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>nums <span style="color:#f92672">=</span> nums
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> max(self<span style="color:#f92672">.</span>f(i) <span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(len(nums)))
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@lru_cache</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">f</span>(self, k):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> k <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>nums[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> max(self<span style="color:#f92672">.</span>f(k<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>), <span style="color:#ae81ff">0</span>) <span style="color:#f92672">+</span> self<span style="color:#f92672">.</span>nums[k]</span></span></code></pre></div>
<p>Runtime 76 ms, memory 13.7 MB.</p>
<h3 id="22-minimum-path-sum">2.2 <a href="https://leetcode-cn.com/problems/minimum-path-sum/"
   rel="noopener noreferrer" target="_blank" class="external">Minimum Path Sum</a>
</h3>
<p>Given an <em>m</em> x <em>n</em> grid of non-negative integers, find a path from the top-left corner to the bottom-right corner such that the sum of the numbers along the path is minimized.</p>
<p><strong>Example:</strong></p>
<blockquote>
<p>Input:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
Output: 7
Explanation: The path 1→3→1→1→1 has the smallest sum.</p>
</blockquote>
<p>Treating each cell of the matrix as the bottom-right corner and computing the minimum path sum to it gives the recurrence below:</p>
<p>$$
\begin{align}
f(0, 0) &amp;= grid(0, 0) \\
f(x, 0) &amp;= f(x-1, 0) + grid(x, 0) \quad(x&gt;0) \\
f(0, y) &amp;= f(0, y-1) + grid(0, y) \quad(y&gt;0) \\
f(x, y) &amp;= \min(f(x-1, y), f(x, y-1)) + grid(x, y) \quad(x&gt;0, y&gt;0)
\end{align}
$$</p>
<p>Translated into python3 on leetcode:</p>


<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> functools <span style="color:#f92672">import</span> lru_cache
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Solution</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">minPathSum</span>(self, grid: List[List[int]]) <span style="color:#f92672">-&gt;</span> int:
</span></span><span style="display:flex;"><span>        self<span style="color:#f92672">.</span>grid <span style="color:#f92672">=</span> grid
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>f(len(grid)<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, len(grid[<span style="color:#ae81ff">0</span>])<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">@lru_cache</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">f</span>(self, x, y):
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> x <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">and</span> y <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>grid[<span style="color:#ae81ff">0</span>][<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">elif</span> y <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>f(x<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">0</span>) <span style="color:#f92672">+</span> self<span style="color:#f92672">.</span>grid[x][<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">elif</span> x <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> self<span style="color:#f92672">.</span>f(<span style="color:#ae81ff">0</span>, y<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>) <span style="color:#f92672">+</span> self<span style="color:#f92672">.</span>grid[<span style="color:#ae81ff">0</span>][y]
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> min(self<span style="color:#f92672">.</span>f(x<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>,y), self<span style="color:#f92672">.</span>f(x,y<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)) <span style="color:#f92672">+</span> self<span style="color:#f92672">.</span>grid[x][y]</span></span></code></pre></div>
<p>Runtime 1052ms, memory 13.9M.</p>
]]></content:encoded><category>coding</category><category domain="tags">Algorithms</category><category domain="tags">Dynamic Programming</category></item><item><title>Scores and Loss Functions for Binary Classification</title><link>https://yifeitao.com/en/score-and-loss-function-of-binary-classification/</link><pubDate>Wed, 21 Feb 2018 00:00:00 +0000</pubDate><guid>https://yifeitao.com/en/score-and-loss-function-of-binary-classification/</guid><description>&lt;p&gt;Binary classification is one of the most basic problems in supervised learning. This post surveys the many machine learning algorithms that solve it, from the angle of score values and loss functions. Detailed treatments of these algorithms can be found in the standard textbooks&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="loss.png" alt=" " loading="lazy" decoding="async" referrerpolicy="no-referrer" width="528" height="370" /&gt;
&lt;/p&gt;</description><content:encoded><![CDATA[<p>Binary classification is one of the most basic problems in supervised learning. This post surveys the many machine learning algorithms that solve it, from the angle of score values and loss functions. Detailed treatments of these algorithms can be found in the standard textbooks<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<p><img src="loss.png" alt=" " loading="lazy" decoding="async" referrerpolicy="no-referrer" width="528" height="370" />
</p>
<p>Given a dataset $D={(\mathbf x_1, y_1), (\mathbf x_2, y_2), &hellip;, (\mathbf x_m, y_m)}$ , where $y_i\in {-1, 1} $ and $\mathbf x_i=(x_{i1}, x_{i2}, &hellip;x_{id})$ is a d-dimensional vector. Note that some sources prefer ${0, 1}$ for the two classes. Which numbers you use makes no difference in principle, but it does produce small differences in how the math is written.</p>
<p>&ldquo;Score value&rdquo; is not a formal term in machine learning algorithms, but it is a useful lens to adopt. We want a scoring function $s(\mathbf x)$ for a sample $\mathbf x$, and its basic property is simple: it must have the same sign as the target value $y$ — positive samples score positive, negative samples score negative — so that the final binary classification function is $sign(s(\mathbf x))$.</p>
<p>So how do we build the scoring function? The intuitive idea is a weighted sum over the sample&rsquo;s $d$ attributes, which expresses how important each attribute is to the score. A score built this way does not necessarily discriminate at the value 0, so we add a bias term, giving the scoring function $s(\mathbf x)=w^T\mathbf x+b$. Many algorithms derive from this basic scoring function.</p>
<p>The basic criterion for a binary classification problem is the classification error rate. Per sample, the loss is 0 when the classification is correct and 1 when it is wrong; this loss is usually called the 0-1 loss, written $l_{0-1}=\mathbb{I}[sign(s(\mathbf x))\neq y]$.</p>
<p>If we consider positive and negative samples uniformly and let $z=s(\mathbf x)*y$ , then $z$ is always positive when the classification is correct. Call it the absolute score: $z$ measures whether the classification is right. Then $l_{0-1}=\mathbb{I}[sign(z)= -1]$, so $l_{0-1}$ is a piecewise function of $z$. From the perspective of optimization theory, this function has no gradient or <a href="https://zh.wikipedia.org/wiki/%E6%AC%A1%E5%AF%BC%E6%95%B0"
   rel="noopener noreferrer" target="_blank" class="external">subgradient</a>
, so we need to find an approximation of it to optimize. The basic criteria for that approximation $\widetilde l$ are: it is always non-negative; and when it is small, $l_{0-1}$ is small.</p>
<p>The score is a linear combination of the attributes $X$; from another angle, the score is also a linear combination of the weights $w$. In general we transform $X$ into a new feature space to get $\phi (\mathbf x)$. The score is then $z=(w^T\mathbf \phi (\mathbf x)+b)y$. The analysis above is essentially unchanged, only the model is more complex. Unless stated otherwise, the following analysis stays in the original space.</p>
<h2 id="1-linear-regression">1 Linear regression</h2>
<p>We require the score to have the same sign as the target. If we set a narrower goal, we can make the score itself ${-1, 1}$; that is the linear regression problem, with the squared error as the loss function. (This admits an intuitive reading, and it can also be derived from maximum likelihood by assuming the sample error is normally distributed; that is not the focus of this post.) Note that $l_{sqr}=(s(\mathbf x)-y)^2=(s(\mathbf x) * y-y^2)^2=(z-1)^2$, so the error function is a quadratic function of the absolute score $z$. As the figure shows, $l_{sqr}$ is always greater than $l_{0-1}$, and when $l_{sqr}$ is relatively small, $l_{0-1}$ is relatively small too, so it can serve as an approximation of $l_{0-1}$.</p>
<p>Linear regression has a closed-form solution $w=(X^TX)^{-1}X^Ty$, so binary classification with linear regression is very simple, but the results are generally not good. Why? The $l_{sqr}$ curve shows that when the absolute score is large, $l_{0-1}$ should be 0; intuitively, a large absolute score is a good thing, indicating high discriminative power, yet $l_{sqr}$ penalizes it more heavily. That is unreasonable. Linear regression can still give a passable $w$, usable as an initial value for other algorithms.</p>
<h2 id="2-perceptron-learning">2 Perceptron learning</h2>
<p>Perceptron learning can be understood through the following intuition: for the absolute score, when it is positive the classification is correct and we consider the error to be 0; when it is negative, the farther it is from 0 the larger the error, i.e. $l_p=max(0, -z)$. The $l_p$ curve is also an approximation of $l_{0-1}$. It is a piecewise function too, but it admits a subgradient: for $z\geqslant 0$ the gradient is taken as 0, and for $z&lt;0$, $\frac{dl_p}{dw}=\frac {dl_p}{dz}\frac{dz}{dw}=-1*y\mathbf x=-y\mathbf x$. Using stochastic gradient descent, $w\leftarrow w+\eta y\mathbf x$ — that is, we optimize only when the score is wrong, which is exactly what the perceptron learning algorithm does.</p>
<h2 id="3-support-vector-machine">3 Support vector machine</h2>
<p>The perceptron&rsquo;s error function can be improved further. We want the absolute score to be sufficiently large, so that we are more confident a sample is positive; since $z$ scales with $w,b$, we may define &ldquo;sufficiently large&rdquo; as greater than 1. This gives $l_{hinge}=max(0, 1-z)$, known as the hinge loss. Accounting for structural risk, our optimization objective is</p>
<p>$$
\underset{w,b}{min}=\frac{1}{2}\left |w  \right |^2+C\sum_{i=1}^{m}max(0, 1-z)
$$</p>
<p>With the slack variable method this becomes the soft-margin support vector machine, and the model is found by quadratic programming.</p>
<p>In terms of score, a sufficiently large score gives the model good robustness on new samples, and that is exactly where the support vector machine excels. If a feature space is introduced, solving the SVM requires computing many inner products; simplifying the inner products in feature space into a kernel function in the original space brings in the kernel method.</p>
<h2 id="4-logistic-regression">4 Logistic regression</h2>
<p>On top of the $z$ value, we want it to yield a probability that measures how likely, or how strongly, the classification is correct. That is, map the range of $z$ from $(-\infty , +\infty )$ to $(0,1)$. The sigmoid function is a good choice: it is differentiable and gives exactly probability 0.5 at 0. That is,</p>
<p>$$
sigmoid(z)=\frac {1}{1+exp(-z)}
$$</p>
<p>Then, based on this sigmoid function, we emulate the loss function $l_{0-1}(z)$. We require the loss to decrease with respect to $z$ and to stay positive; taking the log of the probability and then negating it gives</p>
<p>$$
l_{log}(z)=-log(sigmoid(z))=log(1+exp(-z))
$$</p>
<p>The figure shows that this is also an approximation of $l_{0-1}(z)$. Taking the log loss as the optimization objective:</p>
<p>$$
\underset{w,b}{min}=\sum_{i=1}^{m}log(1+exp(-z))+ \lambda\left |w  \right |^2
$$</p>
<p>This is regularized logistic regression. Since $l_{log}(z)$ is differentiable everywhere, it can be solved with optimization methods such as gradient descent.</p>
<h2 id="5-adaboost">5 AdaBoost</h2>
<p>The exponential function also satisfies the basic conditions for approximating $l_{0-1}$, i.e. the loss is defined as $l_{exp}(z)=exp(-z)$.</p>
<p>Combining the exponential loss function with an additive model yields the AdaBoost algorithm.</p>
<p>An additive model can be understood like this: suppose the d-dimensional vector $\phi(\mathbf x)$ we obtain in feature space corresponds to d weak classifiers, that is, classifiers a little better than a 50% random guess. The so-called Boosting method effectively combines these weak classifiers to build a strong classifier. An additive model is still essentially a linear model, and still has the scoring function $s(\mathbf x)=w^T\mathbf \phi (\mathbf x)$. (Since a weak classifier can carry its own bias, we take $b=0$.) The only difference is that here $\phi(\mathbf x)$ represents a weak classifier rather than a simple attribute transform (which, in essence, it still is).</p>
<p>AdaBoost is a step-by-step iterative process. Suppose that after $m-1$ rounds of iteration we have the scoring function $s_{m-1}(\mathbf x)=\sum_{i=1}^{m-1}w_i\phi_i(\mathbf x)$, and after the $m$-th round we obtain $w_m, \phi_m(\mathbf x), s_m(\mathbf X)$; then $s_m(\mathbf X)=s_{m-1}(\mathbf X)+w_m\phi_m(\mathbf x)$. AdaBoost&rsquo;s optimization objective is to make the scoring function obtained at each step minimize the exponential loss, that is,</p>
<p>$$
w_m, \phi_m = arg\underset{w,\phi}{min}\sum_{i=1}^Nexp(-z_{mi})  \quad z_{mi}=y_is_m(\mathbf X_i)
$$</p>
<p>Further derivation of this expression is not detailed here.</p>
<h2 id="summary">Summary</h2>
<p>Six loss functions so far. All of them are monotonically non-increasing functions of the score, and the last five are surrogate approximations of the 0-1 loss. Whether solving the surrogate yields the solution of the original problem has been studied in depth, under the name of the &ldquo;consistency&rdquo; of surrogate losses<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>.</p>
<p>In addition, if we need to find all positive examples among the samples and rank them, the score must already carry ranking properties while being optimized. The log loss and exponential loss are monotonically decreasing, so the scores computed by models optimized on them can take part in ranking; the other loss functions are not monotonically decreasing, so their scores are not suitable for ranking.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Statistical Learning Methods, Li Hang&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Machine Learning, Zhou Zhihua&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://projecteuclid.org/download/pdf_1/euclid.aos/1079120130"
   rel="noopener noreferrer" target="_blank" class="external">https://projecteuclid.org/download/pdf_1/euclid.aos/1079120130</a>
&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>]]></content:encoded><category>coding</category><category domain="tags">Machine Learning</category><category domain="tags">Classification</category></item><item><title>Developing Highly Configurable Software</title><link>https://yifeitao.com/en/development-of-highly-configurable-software/</link><pubDate>Tue, 17 Jan 2017 00:00:00 +0000</pubDate><guid>https://yifeitao.com/en/development-of-highly-configurable-software/</guid><description>&lt;p&gt;Configurability is one of the important problems in software engineering practice. Configuration files, together with the executable, form the elements of runtime version control for a piece of software. Interpreted languages such as Visual Basic, Python and Lua, which usually show up in the form of so-called &amp;ldquo;scripts&amp;rdquo;, further raise the configurability of large software like operating systems and games, while domain-driven design, even as it emphasizes the domain model, has also pushed forward domain-specific languages that describe configuration domain models. In industrial control, software configurability is described by the concept of configuration; in game development, it is usually described in terms of engines and scripts.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Configurability is one of the important problems in software engineering practice. Configuration files, together with the executable, form the elements of runtime version control for a piece of software. Interpreted languages such as Visual Basic, Python and Lua, which usually show up in the form of so-called &ldquo;scripts&rdquo;, further raise the configurability of large software like operating systems and games, while domain-driven design, even as it emphasizes the domain model, has also pushed forward domain-specific languages that describe configuration domain models. In industrial control, software configurability is described by the concept of configuration; in game development, it is usually described in terms of engines and scripts.</p>
<h2 id="1-basic-principles">1 Basic principles</h2>
<p>The root of software complexity is the complexity of the real world. When software complexity exceeds what a person can hold in their head, development becomes difficult or fails outright. A good software engineering methodology supplies a good abstract model of the real world, managing complexity through a sound hierarchy of abstraction so that a person can grasp the software level by level, from the whole down to the details. Complexity that comes from the real world does not shrink or vanish, but it does get constrained.</p>
<p>High configurability can, to some degree, solve the problem of fast-changing requirements: take the invariant part of the software as the model, and constrain the volatile part through configuration files and the like.</p>
<pre class="mermaid">graph TB
    subgraph &#34;Software 1&#34;
        complexity2[Complexity]--&gt;abstract[Common abstraction]
    end
    subgraph &#34;Software&#34;
        complexity1[Complexity]--&gt;complexity3[Complexity]
    end</pre>

<h2 id="2-how-to-build-it">2 How to build it</h2>
<p>To build highly configurable software, you should first establish a general software model for the domain, then apply configuration techniques to that model. Different configurations plus the model finally produce different software instances.</p>
<pre class="mermaid">graph TB
    subgraph &#34;Software 2&#34;
        config2[Software configuration 2]--&gt;abstract2[General model]
    end
    subgraph &#34;Software 1&#34;
        config1[Software configuration 1]--&gt;abstract1[General model]
    end</pre>

<h3 id="21-modeling">2.1 Modeling</h3>
<p>Modeling is a hard problem. It depends on the scenario at design time, requires domain knowledge and design experience, and calls for close collaboration with domain experts. There are a few classic books on this: <a href="https://book.douban.com/subject/26819666/"
   rel="noopener noreferrer" target="_blank" class="external">Domain-Driven Design</a>
 and <a href="https://book.douban.com/subject/25844633/"
   rel="noopener noreferrer" target="_blank" class="external">Implementing Domain-Driven Design</a>
.</p>
<h3 id="22-configuration-techniques">2.2 Configuration techniques</h3>
<h4 id="221-configuration-files">2.2.1 Configuration files</h4>
<p>Configuration files are the most widely used and most basic configuration technique. They mainly describe the data a program needs at startup and while running, and generally do not describe the program&rsquo;s runtime logic directly. Common formats include ini, xml and json; a quick comparison:</p>
<table>
  <thead>
      <tr>
          <th>Format</th>
          <th>Expressiveness</th>
          <th>Readability</th>
          <th>Typical use</th>
          <th>Example</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>ini</td>
          <td>Weak, cannot nest</td>
          <td>Best</td>
          <td>Program startup parameters.</td>
          <td>Startup directory, IP address.</td>
      </tr>
      <tr>
          <td>xml</td>
          <td>Strongest</td>
          <td>Worst</td>
          <td>Serializing complex objects.</td>
          <td>Data frame structure and parsing rules.</td>
      </tr>
      <tr>
          <td>json</td>
          <td>Middling</td>
          <td>Middling</td>
          <td>Web API interfaces.</td>
          <td>A weather forecast service API.</td>
      </tr>
  </tbody>
</table>
<h4 id="222-scripting-languages">2.2.2 Scripting languages</h4>
<p>Large programs are often built by combining several programming languages: a compiled language implements the relatively fixed parts and the parts where runtime performance is critical, while an interpreted language implements the volatile parts. Linux, for instance, implements many tools in C and then combines them through bash scripts; large games are built on a C++ engine and use Lua scripts to describe the logic of game actions.</p>
<h4 id="223-domain-specific-languages">2.2.3 Domain-specific languages</h4>
<p>A domain-specific language sits between ordinary configuration files and program code: a computer programming language with restricted expressiveness, aimed at one particular domain.</p>
<p>Domain-specific languages divide further into internal DSLs, external DSLs and language workbenches. An internal DSL is a special way of writing code in the host language and hardly counts as configuration; an external DSL designs a new language to do the expressing and still falls within the range of textual configuration. A language workbench mainly offers a visual way of configuring, otherwise known as visual programming.</p>
<p>A DSL sits between configuration files and scripts. It offers high expressiveness within its domain, is easier for domain experts to understand, and speeds up turning expert knowledge into software.</p>
<p>Depending on the model, all three techniques can be applied in highly configurable software development. The table below compares their main characteristics and typical uses.</p>
<table>
  <thead>
      <tr>
          <th>Technique</th>
          <th>Expressiveness</th>
          <th>Readability</th>
          <th>Typical use</th>
          <th>Example</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Configuration files</td>
          <td>Describes the data the program needs to run; generally does not describe runtime logic directly.</td>
          <td>Depends on the specific format.</td>
          <td>Program startup parameters.</td>
          <td>Startup directory, network address.</td>
      </tr>
      <tr>
          <td>Scripting languages</td>
          <td>Describes runtime logic directly.</td>
          <td>Understandable only to programmers, not to domain experts.</td>
          <td>Describing volatile logic when the basic interface is fixed.</td>
          <td>Python scripts describing a monitoring workflow on top of a C++ device-monitoring interface.</td>
      </tr>
      <tr>
          <td>Domain-specific languages</td>
          <td>Describes the configuration of the domain model, and so describes runtime logic indirectly.</td>
          <td>Understandable to programmers and to domain experts.</td>
          <td>A well-accepted domain model exists, and its configuration is rich and volatile.</td>
          <td>Expressing fault handling with a &ldquo;state–response&rdquo; model, and writing a DSL for the actual content.</td>
      </tr>
  </tbody>
</table>
<h2 id="3-engineering-practice">3 Engineering practice</h2>
<p>As the figure shows, highly configurable software development actually changes the traditional development process, and raises the standing of software configuration in the software life cycle. What follows describes the impact of high configurability on each phase of traditional development.</p>
<pre class="mermaid">graph TB
    subgraph &#34;Highly configurable software process&#34;
    	requirements2[Requirements analysis]--&gt;construct2[Building the software core]
    	construct2--&gt;config[Software configuration]
        config--&gt;test2[Testing the software core]
        config--&gt;config_test[Testing the configuration]
		test2--&gt;maintain2[Maintaining the software core] 
		config_test--&gt;config_maintain[Maintaining the configuration]
    end
    
    subgraph &#34;General software process&#34;
    	requirements1[Requirements analysis]--&gt;construct1[Software construction]
        construct1--&gt;test1[Software testing]
		test1--&gt;maintain1[Software maintenance] 
    end</pre>

<h3 id="31-requirements-analysis">3.1 Requirements analysis</h3>
<p>At the requirements phase, compared with ordinary development, highly configurable development has to analyze two extra questions:</p>
<ol>
<li>Whether high configurability is necessary. This is decided mainly by the software&rsquo;s scope of applicability: is the software meant to be generalized, and are the requirements it must meet volatile? The more scenarios it has to fit and the more volatile the requirements, the greater the necessity. If the software serves one basically fixed scenario, there is no need to use configuration.</li>
<li>Separate model requirements from configuration requirements. When gathering requirements, gather them for all the software&rsquo;s application scenarios, analyze the requirement extensions that generalizing may bring, study as many cases as possible, dig down to extract the invariants of the system, abstract the system&rsquo;s model, and identify the volatile parts as configuration requirements.</li>
</ol>
<h3 id="32--software-construction">3.2  Software construction</h3>
<p>Construction in highly configurable development splits into two phases:</p>
<ol>
<li>Building the software core: done by programmers. High configurability places high demands on architecture design. Under the object-oriented paradigm you need to understand and apply OO design principles deeply, make heavy use of the various structural design patterns, and use runtime association techniques such as reflection and dependency injection.</li>
<li>Configuring the software: can be done by a programmer, or by a domain expert. Since configuration has become an important part of the software, configuration files should be managed as strictly as source code and put under version control. Manual configuration is prone to error, so provide visual configuration tools wherever possible and restrict users from editing configuration at will.</li>
</ol>
<h3 id="33-software-testing">3.3 Software testing</h3>
<p>Corresponding to the two construction phases, testing of highly configurable software also splits into two parts:</p>
<ol>
<li>
<p>Testing the software core. Test cases should cover configurations for a variety of scenarios, and those scenarios must include the boundary cases of the model, so that as many core defects as possible are eliminated by testing rather than surfacing after configuration when the software is rolled out.</p>
</li>
<li>
<p>Testing the configuration. Only after configuration testing can the software be applied in a concrete scenario. Configuration is often done by domain experts rather than programmers, so it is best to validate the configuration and to provide testing tools and a written test procedure for the configured state, so that domain experts can complete the testing too.</p>
</li>
</ol>
<h3 id="34--software-maintenance">3.4  Software maintenance</h3>
<p>Corresponding to the two construction phases, maintenance of highly configurable software also splits into two parts:</p>
<ol>
<li>
<p>Maintaining the software core: general feature upgrades are done by upgrading the core. Changes to core functionality should keep configurations compatible; when the configuration format has to change, provide an upgrade and conversion facility for configurations.</p>
</li>
<li>
<p>Maintaining the configuration: when user requirements and application scenarios change in ways the core anticipated, a domain expert can make the change by editing the configuration. Configuration gradually becomes an important asset of the user.</p>
</li>
</ol>
]]></content:encoded><category>coding</category><category domain="tags">Software Engineering</category><category domain="tags">Programming</category></item><item><title>Debugging Rules!: Debugging Is a Technical Skill</title><link>https://yifeitao.com/en/debug-is-technology/</link><pubDate>Sat, 24 Dec 2016 00:00:00 +0000</pubDate><guid>https://yifeitao.com/en/debug-is-technology/</guid><description>&lt;p&gt;Usually, when an engineer calls something an art, or even mysticism, it means the thing is hard and there aren&amp;rsquo;t many rules to follow. Debugging is one of those. Practically every programmer has been flogged half to death by a bug; sometimes you turn it over and over and the problem seems gone, but you don&amp;rsquo;t know why, so you file it under inexplicable mysticism. The most infuriating kind is the bug that shows up now and then and is hard to reproduce — the technical name is Heisenberg bug.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Usually, when an engineer calls something an art, or even mysticism, it means the thing is hard and there aren&rsquo;t many rules to follow. Debugging is one of those. Practically every programmer has been flogged half to death by a bug; sometimes you turn it over and over and the problem seems gone, but you don&rsquo;t know why, so you file it under inexplicable mysticism. The most infuriating kind is the bug that shows up now and then and is hard to reproduce — the technical name is Heisenberg bug.</p>
<p>But software is still within the realm of science and technology, and debugging ought to be a technical skill too. <em>Debugging Rules!</em> is a rare book that talks about debugging as technique. Its nine general principles apply not only to developing and maintaining software and hardware, but even to daily life. Finding this book took some effort; luckily Turing Community still sells the <a href="http://www.ituring.com.cn/book/84"
   rel="noopener noreferrer" target="_blank" class="external">legitimate e-book</a>
.</p>
<p>Honestly, many of the author&rsquo;s examples lean toward hardware, so there&rsquo;s a gap when reading them, but the nine principles are genuinely practical. In my earlier development and debugging work I probably summed up a few of them without realizing it, yet after reading the book it still felt like being handed a martial arts manual. Now let me walk through the nine principles again to deepen my understanding.</p>
<h2 id="1-understand-the-system">1 Understand the system</h2>
<p>This is the most important principle. To analyze a bug you obviously have to understand how the system works, which means learning some fundamentals and carefully reading the documentation for the specific libraries, tools and technologies involved. I only got into the habit of seriously reading official documentation in the last few years. Search engines made us Google-oriented programmers: facing a problem we want it solved in one shot, and the result is usually a pile of detours, all because we begrudge the time to read the signposts. The book particularly insists on reading the manual &ldquo;word for word&rdquo; — a blunt blow to the impatient. Starting to debug without understanding the system is the same lineage as writing code without understanding the principles; the common name for it is &ldquo;programming by coincidence&rdquo;.</p>
<h2 id="2-make-it-fail">2 Make it fail</h2>
<p>This principle is about why and how to reproduce a bug. A bug definitely happens under some particular condition; peel it apart layer by layer until you find that condition and you&rsquo;re halfway there. From my hardware maintenance experience: it helps enormously if the person who found the bug records the whole process in detail. Many bugs need fairly narrow conditions to appear, hence one of a programmer&rsquo;s stock phrases, &ldquo;works on my machine&rdquo;. For this kind of bug, first try to find a way to simulate it, for instance speeding up the interaction and running stress tests; second, have a detailed and sensible logging mechanism inside the software, so the reproducing conditions can be found from the inside.</p>
<h2 id="3-dont-think-look">3 Don&rsquo;t think, look</h2>
<p>This one stresses the importance of observation: reality beats idle speculation. Language shapes thinking, so the phrase on hitting a bug should be &ldquo;let me look&rdquo; rather than &ldquo;I guess it&rsquo;s because&rdquo;. We should try every means to see the details of the bug clearly, so a mature program should carry debugging infrastructure; in my experience, logging at minimum is indispensable. Junior programmers tend to debug entirely with breakpoints, except a breakpoint itself changes the flow of execution (especially with multiple threads). Guessing is still useful, of course: it narrows the range of observation, or at least sets a priority for what to look at first, which is where experience pays off. But no matter what, experience cannot replace observation.</p>
<h2 id="4-divide-and-conquer">4 Divide and conquer</h2>
<p>Binary search turns lookup from linear time into logarithmic time. It isn&rsquo;t only an algorithm inside programs, it&rsquo;s a methodology for debugging. Once a system has this awareness, the key question is how to divide the system — back to principle 1. At the very least, when you debug with breakpoints, bisection can locate the bug, and the same principle applies when inserting log statements. The book also mentions bugs influencing each other; I&rsquo;d say there&rsquo;s even a bug seesaw. The way out is to let none of them go. When debugging reveals that the code quality is really bad, refactoring is sometimes necessary — you can&rsquo;t build a tower on loose sand — and after an effective refactor the bug often turns up and gets removed on its own.</p>
<h2 id="5-change-one-thing-at-a-time">5 Change one thing at a time</h2>
<p>This is like the control principle in experimental science: examine one variable at a time, compare the normal case against the abnormal case, and never ignore any difference in test conditions. The previous principle says let no bug go, but they still have to be handled one by one, and when refactoring don&rsquo;t hack away at code you don&rsquo;t understand. Much code is dangerous because one pull of a hair moves the whole body; I think that&rsquo;s the biggest risk in fixing bugs — you often remove one bug and add several. Back to principle 1: understand the system. Although understanding other people&rsquo;s code is easier said than done, which is a reminder to build up good karma when writing code: don&rsquo;t imagine code is written for the compiler, it&rsquo;s written for whoever maintains it later.</p>
<h2 id="6-keep-an-audit-trail">6 Keep an audit trail</h2>
<p>This principle is about why and how to keep records. I think of it as a doctor taking a history: you need to ask the right questions and get concrete, useful answers. My own experience is that once I start debugging a bug I sink in completely and can&rsquo;t get out; writing things down pulls me out of the mental swamp and lets me keep correcting my plan and method for debugging. With some specialized system software you need to train the users in how to record bug occurrences in effective language; without records, the person debugging has to ask the right questions to jog the user&rsquo;s memory. Beyond that, a bug you can&rsquo;t crack at midnight deserves a night&rsquo;s sleep, letting divergent thinking work on its own; usually it&rsquo;s solved by morning.</p>
<h2 id="7-check-the-plug">7 Check the plug</h2>
<p>This principle is roughly about so-called &ldquo;stupid mistakes&rdquo; — like my mother telling me the computer speakers have gone silent, and my first question having to be whether the power is on. Or writing main as mian. Once I set a breakpoint and it just wouldn&rsquo;t hit; later I found that two blocks of code looked much alike and I had set the breakpoint in the wrong place. Depending on the scenario and your experience, ask yourself whether you&rsquo;ve made the mistake that is common in this scenario; that often solves a pile of problems on the spot, just as the troubleshooting FAQ in many appliance manuals also tells you to check the plug. You could say a stupid mistake is stupid in difficulty, not in frequency.</p>
<h2 id="8-get-a-fresh-view">8 Get a fresh view</h2>
<p>This principle is about asking for help and talking things over. The important part is using another person&rsquo;s viewpoint to break your own mental set, so it&rsquo;s best to describe only the symptoms in detail and not your guesses, lest you contaminate their judgment. For a programmer, Google-oriented programming is astonishingly powerful at this point: nine out of ten problems don&rsquo;t need you to actually ask anyone, just the right search terms. For Chinese programmers, searching in English is a required skill, and of course really understanding what you find matters too — don&rsquo;t swallow every pill you see. If Google genuinely can&rsquo;t find it, that proves you&rsquo;ve climbed a level; then go ask on communities like Stack Overflow.</p>
<h2 id="9-if-you-didnt-fix-it-it-aint-fixed">9 If you didn&rsquo;t fix it, it ain&rsquo;t fixed</h2>
<p>This principle says don&rsquo;t count on luck, and don&rsquo;t play ostrich just because the bug only flashed by once. What has to come will come; the bill isn&rsquo;t cancelled, only deferred. Real situations can be complicated, of course — software engineering is a compromise among time, cost and quality — but even when you let some bugs go, work out the consequences that may follow and put adequate safeguards in place, which is why many software certification schemes include software safety integrity levels. This also touches on reproducing the bug and generalizing the fix: confirm that you have really found the cause, provide an effective fix, and sweep for all similar problems. Fix one memory leak and you have to fix every piece of code that causes that kind of leak.</p>
<h2 id="postscript-how-to-write-code-that-is-easy-to-debug">Postscript: how to write code that is easy to debug</h2>
<p>Seen from the other side, the principles of debugging help with building software itself. Debugging only patches holes, after all; better to write code with fewer bugs and leave yourself more handles for when you do debug. A brief summary of my own experience, which probably needs continual refinement:</p>
<ol>
<li>The program should provide a logging mechanism, emitting debug information in the debug build, and even in release recording the details of what went wrong (an exception thrown in C#, say);</li>
<li>Write code fit for humans to read, otherwise whoever maintains it later can&rsquo;t understand it, let alone debug it. <em>Code Complete</em> says the most about how; besides the various idiomatic ways of writing, I think putting yourself in the reader&rsquo;s shoes matters a lot;</li>
<li>When using libraries or tools, try to understand the principles and read the official documentation carefully. Some libraries explain in detail how to avoid memory leaks and how to handle multithreading; read it clearly before you use it and you plant fewer seeds of disaster — at least you won&rsquo;t be a headless fly guessing at causes when the debugging comes;</li>
<li>Write code that is easy to test, so that problems can be isolated during debugging, or bisected. TDD practice covers this fairly well; I need to work on it myself.</li>
</ol>
]]></content:encoded><category>coding</category><category domain="tags">Programming</category><category domain="tags">Debugging</category><category domain="tags">Reading notes</category></item></channel></rss>