Jekyll2023-10-14T01:23:09+00:00https://jinyu.li/feed.xmlJinyu Lia personal journalPhoto2023-10-06T01:21:09+00:002023-10-06T01:21:09+00:00https://jinyu.li/2023/10/06/photo<p><img class="displayed" src="https://jinyu.li/assets/images/2023-10-06-trip.jpg" /></p>
<p>旅途中偶得~</p>class Life : public SLAM<Life>2022-10-06T12:24:36+00:002022-10-06T12:24:36+00:00https://jinyu.li/2022/10/06/xlii<p>The long dormancy is over, now it’s the time for new adventures.</p>
<figure>
<blockquote>
<p>The excitement of learning separates youth from old age. As long as you're learning you're not old.</p>
</blockquote>
<figcaption>—Rosalyn S. Yalow</figcaption>
</figure>The long dormancy is over, now it’s the time for new adventures.写出了一个有趣的 bug ……2019-05-08T18:53:29+00:002019-05-08T18:53:29+00:00https://jinyu.li/2019/05/08/bug-or-art<p><img class="displayed" src="https://jinyu.li/assets/images/2019-05-08-bug-or-art.png" /></p>
<p>背景建筑图片来自 <a href="https://commons.wikimedia.org/wiki/File:P1180494_Paris_IV_Notre-Dame_d%C3%A9tail_sud_rwk.jpg">Wikimedia</a> 。</p>微软从来不会数数2018-10-22T10:00:00+00:002018-10-22T10:00:00+00:00https://jinyu.li/2018/10/22/ms-do-not-count<p>如题,亮点自寻……</p>
<p><img class="displayed" src="https://jinyu.li/assets/images/2018-10-22-ms-do-not-count.png" /></p>如题,亮点自寻……旋转变量求导2018-07-10T11:35:00+00:002018-07-10T11:35:00+00:00https://jinyu.li/2018/07/10/jacobian-of-rotation<p>在涉及空间三维运动的问题中经常会遇到含有旋转变量的能量,最优化这样的能量时,便需要对旋转变量进行求导。
在当前旋转的切空间上,运用李代数的方法进行局部求导已经是一种标准的做法,但其中推导比较复杂,不熟悉的话容易出错。
为此,这篇文章整理了一些常见的包含旋转变量的能量形式以及它们的导数。</p>
<h2 id="基本方法">基本方法</h2>
<p>我们回避过于形式化的完整推导,采用一种仿照对偶数<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>的简易推导方法。如果对于一个函数 $f$ 在点 $x$ 处有
\(f(x)+\delta f = f(x+\delta x)\)
其中,$\delta x$ 和 $\delta f$ 看作是微元或者对偶量,那么我们就可以从中提取 $\delta f/\delta x$ 作为 $f(x)$ 的导数。
从具体步骤上来说,首先在需要求导的点 $x$ 上添加微元 $\delta x$ 后应用函数 $f$ ,然后将其展开为 $f(x)$ 加上微元余项的形式,最后就可以提取导数了。</p>
<h2 id="旋转小量">旋转小量</h2>
<p>三维旋转只有三个自由度,然而它高度非线性,因而我们采用李代数的方法,在切空间中进行求导。这样一来,旋转小量便对应了三维的向量。旋转切空间的李代数到旋转的李群之间由<strong>指数映射</strong>和<strong>对数映射</strong>联系起来,对一个旋转 $R$ 添加小量微扰 $\delta R$ 可以表示为 $R\oplus \delta R = R\exp \delta R$ 。相对的,两个相近的旋转 $R_1$ 和 $R_2$ 之间的差可以表示为 $R_2 \ominus R_1 = \log(R_1^\top R_2)$ 。</p>
<p>此外,我们有一些有用的关系:</p>
<ol>
<li>$\exp(\delta R) = I+[\delta R]_\times$</li>
<li>$\exp(\delta R)U = U\exp(U^\top\delta R)$</li>
<li>$\log(U\exp(\delta R)) = \log U + J_r^{-1}(\log U)\delta R$</li>
</ol>
<p>注意由于 $\delta R$ 是微元,上面三个关系的等号可以认为成立,否则第一和第三个关系只在 $\delta R$ 比较小时近似成立。</p>
<h2 id="常见基本形式的导数">常见基本形式的导数</h2>
<h3 id="1-三维点的旋转">1. 三维点的旋转</h3>
<p>空间三维点的旋转对应于 $f_1(R) = Rx$ 或 $f_2(R) = R^\top x$ ,其中 $x$ 是三维常向量。以 $f_1$ 为例:</p>
\[\begin{align}
f_1(R\oplus\delta R) - f_1(R)
&= (R\exp\delta R)x-Rx \nonumber \\
&= R(I+[\delta R]_\times)x - Rx \nonumber \\
&= R[\delta R]_\times x \nonumber \\
&= -R[x]_\times \delta R
\end{align}\]
<p>因此,对应的导数为 $-R[x]_\times$ 。</p>
<p>下表为上述两个形式对应的导数:</p>
<table>
<thead>
<tr>
<th>$i$</th>
<th>$f_i(R)$</th>
<th>$[f_i(R\oplus\delta R)-f_i(R)]/\delta R$</th>
</tr>
</thead>
<tbody>
<tr>
<td>$1$</td>
<td>$Rx$</td>
<td>$-R[x]_\times$</td>
</tr>
<tr>
<td>$2$</td>
<td>$R^\top x$</td>
<td>$[R^\top x]_\times$</td>
</tr>
</tbody>
</table>
<h3 id="2-旋转的复合">2. 旋转的复合</h3>
<p>旋转的复合对应于 $f_3(R) = UR$ 、$f_4(R) = UR^\top$ 、$f_5(R) = RU$ 和 $f_6(R) = R^\top U$ 四种形式中的一种,其中 $U$ 为一个常量旋转。由于这四个形式最终结果是旋转,根据参数化方式的不同,结果的维度也不一样,我们转而在结果的切空间内求导,此时导数对应了 $3\times3$ 矩阵。结果的导数形式中,所有的旋转也相应采用其 $3\times3$ 矩阵形式表达,以 $f_5$ 为例:</p>
\[\begin{align}
f_5(R\oplus \delta R) \ominus f_5(R)
&= (R\exp (\delta R) U) \ominus f_5(R) \nonumber \\
&= (RU \exp (U^\top \delta R)) \ominus (RU) \nonumber \\
&= \log((RU)^\top RU\exp(U^\top\delta R)) \nonumber \\
&= U^\top\delta R
\end{align}\]
<p>因此,对应的切空间内的导数为 $U^\top$ 。
下表列出了上述四个形式对应的导数:</p>
<table>
<thead>
<tr>
<th>$i$</th>
<th>$f_i(R)$</th>
<th>$[f_i(R\oplus \delta R) \ominus f_i(R)]/\delta R$</th>
</tr>
</thead>
<tbody>
<tr>
<td>$3$</td>
<td>$UR$</td>
<td>$I_{3\times 3}$</td>
</tr>
<tr>
<td>$4$</td>
<td>$UR^\top$</td>
<td>$-R$</td>
</tr>
<tr>
<td>$5$</td>
<td>$RU$</td>
<td>$U^\top$</td>
</tr>
<tr>
<td>$6$</td>
<td>$R^\top U$</td>
<td>$-U^\top R$</td>
</tr>
</tbody>
</table>
<h3 id="3-旋转的差值">3. 旋转的差值</h3>
<p>旋转的差值对应于 $f_7(R) = \log(UR)$ 、 $f_8(R) = \log(UR^\top)$ 、$f_9(R) = \log(RU)$ 和 $f_{10}=\log(R^\top U)$ 四种形式中的一种。以 $f_9$ 为例:</p>
\[\begin{align}
f_9(R\oplus\delta R)-f_9(R)
&= \log(R\exp(\delta R) U) - f_9(R) \nonumber \\
&= \log(RU\exp(U^\top\delta R)) - f_9(R) \nonumber \\
&= \log(RU)+J_r^{-1}(\log(RU))U^\top \delta R-f_9(R) \nonumber \\
&= J_r^{-1}(\log(RU))U^\top\delta R = J_r^{-1}(f_9(R))U^\top\delta R
\end{align}\]
<p>因此,对应的导数为 $J_r^{-1}(f_9(R))U^\top$ 。</p>
<p>下表列出了这四个形式对应的导数:</p>
<table>
<thead>
<tr>
<th>$i$</th>
<th>$f_i(R)$</th>
<th>$[f_i(R\oplus \delta R)-f_i(R)]/\delta R$</th>
</tr>
</thead>
<tbody>
<tr>
<td>$7$</td>
<td>$\log(UR)$</td>
<td>$J_r^{-1}(f_7(R))$</td>
</tr>
<tr>
<td>$8$</td>
<td>$\log(UR^\top)$</td>
<td>$-J_r^{-1}(f_8(R))R$</td>
</tr>
<tr>
<td>$9$</td>
<td>$\log(RU)$</td>
<td>$J_r^{-1}(f_9(R))U^\top$</td>
</tr>
<tr>
<td>$10$</td>
<td>$\log(R^\top U)$</td>
<td>$-J_r^{-1}(f_{10}(R))U^\top R$</td>
</tr>
</tbody>
</table>
<p>在面对复合函数时,只要正确使用链式法则结合上面的导数,便可以得到想要的结果了。</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p><a href="https://en.wikipedia.org/wiki/Dual_number">Dual Number on Wikipedia</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>在涉及空间三维运动的问题中经常会遇到含有旋转变量的能量,最优化这样的能量时,便需要对旋转变量进行求导。 在当前旋转的切空间上,运用李代数的方法进行局部求导已经是一种标准的做法,但其中推导比较复杂,不熟悉的话容易出错。 为此,这篇文章整理了一些常见的包含旋转变量的能量形式以及它们的导数。OpenCV 中进行 YUV420 到 RGB 的转换2018-03-28T10:46:00+00:002018-03-28T10:46:00+00:00https://jinyu.li/2018/03/28/opencv-yuv-to-rgb<p>最近的一个项目中,需要从远程接收 H.264 编码的视频流,然后在本地进行解码后使用图像。</p>
<p>本地从 H.264 码流解码出的图像采用了 YCbCr(I420) 格式,对于一帧 $W\times H$ 的图像,它将像素色彩的三个分量分别储存在三个平面上,也就是三个缓冲区,首尾相连。第一个平面对应 Y 色度平面,储存了亮度信息,它的大小也是 $W\times H$。第二个和第三个平面对应 Cb 和 Cr 色度平面,储存了蓝色和红色的色差信息,由于利用了人眼视觉上对色彩的空间分布相对不敏感的特点,这两个色度平面对原始图像的两个方向上各进行了$\frac12$的降采样,也就是说每$2\times 2$的像素共用一组 $(C_b,C_r)$ 色差信息。由于这个原因,这两个平面的大小都是 $\frac W2 \times \frac H2$。</p>
<p>理论虽然是这样,实际要用 OpenCV 来做就比较麻烦了(并不)。</p>
<p>先说说麻烦在那里:由于三个色度平面大小不一,在如何使用 <code class="language-plaintext highlighter-rouge">cv::Mat</code> 表示上就产生了许多的猜测。网上一番搜索,基本都是介绍原理,实现需要一个像素一个像素手工转换。对于我这种懒人,我是非常希望可以用一句 <code class="language-plaintext highlighter-rouge">cv::cvtColor</code> 搞定的。这样做有很多的好处,比如代码更精简,比如工作量小,比如可以更快<del>,比如有问题了可以甩锅给 OpenCV</del>…… 可稍微看了一下文档就会意识到, <strong>OpenCV 的文档根本就是垃圾,从文档是不可能知道该怎么用的!</strong></p>
<p>那么真的要自己去实现一个么?<strong>不需要!</strong> <code class="language-plaintext highlighter-rouge">cv::cvtColor</code> 究竟支不支持这样的转换呢?<strong>支持!</strong></p>
<p>所以,怎么做呢?</p>
<p>有的时候,代码就是最好的文档(不过 OpenCV 又一次证明我错了)。经过浏览 OpenCV 中 <code class="language-plaintext highlighter-rouge">cv::cvtColor</code> 的代码实现,我注意到了这样的段落(项目中使用的是 OpenCV 2.4.13):</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// :::</span>
<span class="k">case</span> <span class="n">CV_YUV2BGR_YV12</span><span class="p">:</span> <span class="k">case</span> <span class="n">CV_YUV2RGB_YV12</span><span class="p">:</span> <span class="k">case</span> <span class="n">CV_YUV2BGRA_YV12</span><span class="p">:</span> <span class="k">case</span> <span class="n">CV_YUV2RGBA_YV12</span><span class="p">:</span>
<span class="k">case</span> <span class="n">CV_YUV2BGR_IYUV</span><span class="p">:</span> <span class="k">case</span> <span class="n">CV_YUV2RGB_IYUV</span><span class="p">:</span> <span class="k">case</span> <span class="n">CV_YUV2BGRA_IYUV</span><span class="p">:</span> <span class="k">case</span> <span class="n">CV_YUV2RGBA_IYUV</span><span class="p">:</span>
<span class="p">{</span>
<span class="c1">//http://www.fourcc.org/yuv.php#YV12 == yuv420p -> It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes.</span>
<span class="c1">//http://www.fourcc.org/yuv.php#IYUV == I420 -> It comprises an NxN Y plane followed by (N/2)x(N/2) U and V planes</span>
<span class="c1">// :::</span>
</code></pre></div></div>
<p>从这里可以看到 <code class="language-plaintext highlighter-rouge">CV_YUV2{BGR|RGB}[A]_{YV12|IYUV}</code> 系列枚举值对应了我们需要的色度转换。通过进一步阅读后面的处理(朋友,我替你读过了,所以不要读了,太浪费生命),可以知道正确的方法是建立一个 <code class="language-plaintext highlighter-rouge">cv::Mat</code> ,直接按照标准 YCbCr(I420) 的平面大小分配空间并填充数据,然后调用 <code class="language-plaintext highlighter-rouge">cv::cvtColor</code> 。整个过程的代码如下:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cv</span><span class="o">::</span><span class="n">Mat</span> <span class="nf">YUV420_to_BGR888</span><span class="p">(</span>
<span class="kt">int</span> <span class="n">width</span><span class="p">,</span> <span class="kt">int</span> <span class="n">height</span><span class="p">,</span>
<span class="k">const</span> <span class="n">uchar</span> <span class="o">*</span> <span class="n">Y</span><span class="p">,</span> <span class="k">const</span> <span class="n">uchar</span> <span class="o">*</span> <span class="n">Cb</span><span class="p">,</span> <span class="k">const</span> <span class="n">uchar</span> <span class="o">*</span> <span class="n">Cr</span><span class="p">,</span>
<span class="kt">int</span> <span class="n">strideY</span><span class="p">,</span> <span class="kt">int</span> <span class="n">strideCbCr</span>
<span class="p">)</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">uvwidth</span> <span class="o">=</span> <span class="n">width</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">uvheight</span> <span class="o">=</span> <span class="n">height</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">size</span> <span class="o">=</span> <span class="n">width</span> <span class="o">*</span> <span class="n">height</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">uvsize</span> <span class="o">=</span> <span class="n">uvwidth</span> <span class="o">*</span> <span class="n">uvheight</span><span class="p">;</span>
<span class="c1">// whole data size = Y + U + V = W*H + (W/2)*(H/2)*2 = W*(H+H/2)</span>
<span class="n">cv</span><span class="o">::</span><span class="n">Mat</span> <span class="n">YCbCrData</span><span class="p">(</span><span class="n">cv</span><span class="o">::</span><span class="n">Size</span><span class="p">(</span><span class="n">width</span><span class="p">,</span> <span class="n">height</span> <span class="o">+</span> <span class="n">uvheight</span><span class="p">),</span> <span class="n">CV_8UC1</span><span class="p">);</span>
<span class="c1">// get the pointer to the beginning of each plane.</span>
<span class="n">uchar</span><span class="o">*</span> <span class="n">pY</span> <span class="o">=</span> <span class="n">YCbCrData</span><span class="p">.</span><span class="n">data</span><span class="p">;</span>
<span class="n">uchar</span><span class="o">*</span> <span class="n">pCb</span> <span class="o">=</span> <span class="n">pY</span> <span class="o">+</span> <span class="n">size</span><span class="p">;</span>
<span class="n">uchar</span><span class="o">*</span> <span class="n">pCr</span> <span class="o">=</span> <span class="n">pCb</span> <span class="o">+</span> <span class="n">uvsize</span><span class="p">;</span>
<span class="c1">// copy Y channel for each line</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">height</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">pY</span> <span class="o">+</span> <span class="n">i</span><span class="o">*</span><span class="n">width</span><span class="p">,</span> <span class="n">Y</span> <span class="o">+</span> <span class="n">i</span><span class="o">*</span><span class="n">strideY</span><span class="p">,</span> <span class="n">width</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// copy Cb and Cr channel for each line</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">uvheight</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">pCb</span> <span class="o">+</span> <span class="n">i</span><span class="o">*</span><span class="n">uvwidth</span><span class="p">,</span> <span class="n">Cb</span> <span class="o">+</span> <span class="n">i</span><span class="o">*</span><span class="n">strideCbCr</span><span class="p">,</span> <span class="n">uvwidth</span><span class="p">);</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">pCr</span> <span class="o">+</span> <span class="n">i</span><span class="o">*</span><span class="n">uvwidth</span><span class="p">,</span> <span class="n">Cr</span> <span class="o">+</span> <span class="n">i</span><span class="o">*</span><span class="n">strideCbCr</span><span class="p">,</span> <span class="n">uvwidth</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">cv</span><span class="o">::</span><span class="n">Mat</span> <span class="n">BGRData</span><span class="p">(</span><span class="n">cv</span><span class="o">::</span><span class="n">Size</span><span class="p">(</span><span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">),</span> <span class="n">CV_8UC3</span><span class="p">);</span>
<span class="n">cv</span><span class="o">::</span><span class="n">cvtColor</span><span class="p">(</span><span class="n">YCbCrData</span><span class="p">,</span> <span class="n">BGRData</span><span class="p">,</span> <span class="n">CV_YUV2BGR_IYUV</span><span class="p">);</span>
<span class="k">return</span> <span class="n">BGRData</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>在上面的代码里,我假定了 <code class="language-plaintext highlighter-rouge">width</code> 和 <code class="language-plaintext highlighter-rouge">height</code> 都是偶数,奇数的情形还请自行处理。此外,可以看到我这里对 YCbCr 和 YUV 不加区分的使用了,这两者根据场合还是存在不同的。fourcc 的网站上提供了比较详细的信息,可以参考 <a href="http://www.fourcc.org/yuv.php">YUV pixel formats</a> 和 <a href="http://www.fourcc.org/fccyvrgb.php">YUV to RGB Conversion</a> 。</p>最近的一个项目中,需要从远程接收 H.264 编码的视频流,然后在本地进行解码后使用图像。在 vector 里使用 bool2018-01-25T05:39:00+00:002018-01-25T05:39:00+00:00https://jinyu.li/2018/01/25/vector-bool<blockquote>
<p>Q: 我想在 <code class="language-plaintext highlighter-rouge">std::vector</code> 里使用 <code class="language-plaintext highlighter-rouge">bool</code> 可以么?<br />
A: 当然是不可以!</p>
</blockquote>
<blockquote>
<p>Q: 为什么不可以?<br />
A: 其实也可以……</p>
</blockquote>
<blockquote>
<p>Q: 一会儿可以一会儿不可以,别逗我行不行?<br />
A: 你试试 <code class="language-plaintext highlighter-rouge">&v[0]</code> ……</p>
</blockquote>
<blockquote>
<p>Q: 为啥别的类型都行,但 <code class="language-plaintext highlighter-rouge">std::vector<bool></code> 就不让 <code class="language-plaintext highlighter-rouge">&v[0]</code> ?<br />
A: 因为 <code class="language-plaintext highlighter-rouge">std::vector</code> 对 <code class="language-plaintext highlighter-rouge">bool</code> 进行了特化, 把若干 <code class="language-plaintext highlighter-rouge">bool</code> 压缩存储在了整数类型的每个二进制位上,因此无法取地址。</p>
</blockquote>
<blockquote>
<p>Q: 好吧那怎么办……<br />
A: 下面这段代码送给你,good luck and have fun!</p>
</blockquote>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">boolean</span> <span class="p">{</span>
<span class="n">boolean</span><span class="p">()</span> <span class="o">=</span> <span class="k">default</span><span class="p">;</span>
<span class="n">boolean</span><span class="p">(</span><span class="kt">bool</span> <span class="n">value</span><span class="p">)</span> <span class="o">:</span> <span class="n">value</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="p">{}</span>
<span class="k">operator</span> <span class="kt">bool</span><span class="o">&</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="n">value</span><span class="p">;</span> <span class="p">}</span>
<span class="k">operator</span> <span class="k">const</span> <span class="kt">bool</span><span class="o">&</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">value</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">bool</span><span class="o">*</span> <span class="k">operator</span><span class="o">&</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="o">&</span><span class="n">value</span><span class="p">;</span> <span class="p">}</span>
<span class="k">const</span> <span class="kt">bool</span><span class="o">*</span> <span class="k">operator</span><span class="o">&</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="o">&</span><span class="n">value</span><span class="p">;</span> <span class="p">}</span>
<span class="nl">private:</span>
<span class="kt">bool</span> <span class="n">value</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>Q: 我想在 std::vector 里使用 bool 可以么? A: 当然是不可以!最小二乘问题(十五)2017-12-05T15:22:36+00:002017-12-05T15:22:36+00:00https://jinyu.li/2017/12/05/least-squares-15<p>对于线性最小二乘问题 $\min\|Ax-b\|^2$ ,我们知道它的最优解同时是标准方程 $A^TAx = A^Tb$ 的解。</p>
<p>如果设 $M = A^TA$ ,在 $A$ 列满秩时,$M$ 是对称正定矩阵。我们知道,存在唯一的下三角矩阵 $L$ ,在对角元大于零的前提下满足 $LL^T = M$ 。这被称为矩阵 $M$ 的 Cholesky 分解。</p>
<p>与此同时,如果我们对 $A$ 进行 QR 分解,同样存在唯一的 $QR=A$ 满足 $R$ 的对角元大于零。将后者代入 $M=A^TA$ ,便可发现 $R^TR = R^TQ^TQR = A^TA = M = LL^T$ 。这意味着:矩阵 $A$ 的 QR 分解对应着矩阵 $M$ 的 Cholesky 分解。</p>
<p>回到最开始的线性最小二乘问题,若 $QR = A$ ,对原始问题的余项进行正交投影变换得到如下的新问题:</p>
\[\min\|Q^T(Ax-b)\|^2 = \min\|Rx-Q^Tb\|^2\]
<p>它的标准方程是 $R^TRx=R^TQ^Tb$,也就恰好是 $A^TAx=A^Tb$ 。这也就是说这个新问题具有与原问题相同的最优解。</p>
<p>前面的一系列分析基于了 $A$ 列满秩的假设,在 $A$ 的行数小于列数时,这是不成立的。但抛开矩阵分解结果唯一性,上面的 QR 分解与 Cholesky 分解的类推关系依旧成立。这意味着我们依旧可以用这种正交投影的方式来得到与原始问题具有相同解的新问题。</p>对于线性最小二乘问题 $\min\|Ax-b\|^2$ ,我们知道它的最优解同时是标准方程 $A^TAx = A^Tb$ 的解。The Busy Indicator2017-11-29T14:01:58+00:002017-11-29T14:01:58+00:00https://jinyu.li/2017/11/29/busy-indicator<p>当手机从一天不充没问题变成每天两充还不够的时候,不是手机电池不行了,是接近年底了真的很忙啊……</p>当手机从一天不充没问题变成每天两充还不够的时候,不是手机电池不行了,是接近年底了真的很忙啊……“阈值”优化2017-11-20T13:53:39+00:002017-11-20T13:53:39+00:00https://jinyu.li/2017/11/20/thresholding-optimization<p>有时我们想求一系列数值 $f_1, f_2, \dots, f_n$ 中绝对值大于 $\lambda$ 的部分,如何非常有逼格地做这件事儿呢?</p>
<p>有趣的是,下面的优化解答了这一问题,其中 $f=(f_1, f_2, \dots, f_n)^T$ 是这些数值构成的列向量:</p>
\[\min \frac12\|x-f\|_2^2+\lambda\|x\|_1\]
<p>求解令上面问题最优的 $x$ ,则 $x$ 中非零项所在的维度便对应了想要的 $f_i$ 所在的维度。更具体地说,$x_i = \mathrm{sgn}(f_i)\cdot\max(|f_i|-\lambda, 0)$ 。这个操作等于<strong>将 $f$ 的所有元素向零方向收缩 $\lambda$</strong>,因此得名 shrinkage 。</p>
<p><img class="displayed" src="https://jinyu.li/assets/images/2017-11-20-thresholding-optimization.png" /></p>
<p>从这个看似简单的优化,可以扩展出很多复杂的数学工具,包括著名的 LASSO 和 SVT 算法。</p>有时我们想求一系列数值 $f_1, f_2, \dots, f_n$ 中绝对值大于 $\lambda$ 的部分,如何非常有逼格地做这件事儿呢?