Tian Junhttps://tianjun.me/rssAll about Tian Junen-USSat, 07 Dec 2019 04:15:05 GMTrfeed v1.1.1https://github.com/svpino/rfeed/blob/master/README.mdAbout_This_Sitehttps://tianjun.me/essays/About_This_Site<body> <h1 id="about-this-site">About This Site</h1> <h2 id="todo">TODO</h2> <p>貌似又出了一次故障,看来得抽空重写下这个后台了。。。 这次得好好思考下了(至少得管个三五年吧......)。</p> <h2 id="记录个意外">记录个意外</h2> <p>前天手抽不知为何意外删除了Dropbox的同步目录,清楚地记得删的是软链接,卡住有两三秒没删完,猛地觉得不对,赶紧Ctrl+C撤销了,然并卵,网速出奇地好,Server迅速同步了。后来发现Dropbox居然还有恢复功能,兴高采烈地恢复了。然而,忘了关掉文件监视器,于是所有被删掉的文件又刷新了一遍。。。</p> <p>算了,懒得再恢复时间戳了,权当留个纪念吧。</p> <hr/> <h2 id="bugfix">BugFix</h2> <p>在尝试了OneDrive(最新版有个坑爹的bug,由于采用了lazy同步的模式,某些目录没法直接通过代码访问)、GoogleDrive(家里的电脑安装不上。。。居然没代理)之后,改用了Dropbox同步static file,刚发现其同步模式居然是先删掉本地文件再下载并创建,导致watchdog也同样做了一次这样的操作,于是文章的创建时间和更新时间都会变成最近一次更新。考虑到delete是低频且高危的操作,先将其从watchdog的监听事件里删了,这样只能手动到后台执行命令删除,算是个折中的解决方案吧。</p> <h2 id="update">Update</h2> <p>最近好不容易挤了些时间更新了下本站,算是保持了一年一更的节奏吧😆。事实上,更新本站最主要的原因是,最近这段时间都没有用Clojure了,以至于一直没空做一些更新,这次干脆把许多想要做的更新都做了。后端用的flask,加了些同步机制和https。</p> <p>这半年变化挺大的,包括心态,工作,工具链等等。等了很久的Clojure 1.9终于发布了,不过却很少写了,倒是因为业余兴趣的需要,转向了Julialang,至于兴趣能坚持多久,随缘吧。</p> <p>原来tianjun.ml的域名,在十一长假期间忘了续,结果被抢注了,居然还有这种操作......,吓得我赶紧又注册了个juntian.me的域名重定向到了目前的这个网站上。</p> <p>这次更新的过程中,删了部分没啥营养的文章(主要是不知道怎么写英文的title,至于为什么文章title都换成英文的了,啊,说来话长),部分评论迁移的时候没了(真的是没了,不是我删了,要怪就怪disqus咯)。</p> <p>后面本站应该会频繁更新一段时间,主要是NLP方面的一些内容,也算是对过去的半年有个交代吧~</p> <hr/> <h2 id="记录下用clojure重写的本站的过程">记录下用Clojure重写的本站的过程</h2> <p>最早打算重写这个网站的原因是为了方便同步网站的信息。由于以前是自己写的后台管理页面,每次更新内容都需要在网页上写东西,以前可能不是太大的问题,不过现在在家里的网总是断断续续的,而且近来记录笔记的习惯也倾向于本地化记录了,所以打算在网站与本地之间开通个接口方便同步一些信息。另外,对网站的内容做了一些精简,希望以后写的东西还是更professional一些吧。</p> <p>其实,我也不知道整个过程花了多少时间,断断续续地,偶尔想起来的时候熟悉下相关库的api,遇到不懂的地方,也只能花时间死磕各个库的源码实现。不过,大部分代码应该是这几天放假的时候写的(总共也就几百行),源码可以在<a href="https://github.com/findmyway/blog-clj">这里</a>看到。</p> <p>记录几点体会吧:</p> <ol style="list-style-type: decimal"> <li>抽象和隔离的意识要比以前强一些了,会有意识地从逻辑层和代码层抽出一些共通的部分出来;</li> <li>测试的引入会改变自己写代码的方式,会有意识地去考虑如何方便进行测试;</li> <li>对错误和异常的处理还缺少很深入的思考与实践;</li> </ol> <h2 id="bugfix-1">BugFix</h2> <p>记录昨天fix的一个Bug,之前后台自动保存的功能出了个bug,直接导致一个文档的content同步覆盖掉了另一个文档的content。要命的是不知道怎么复现这个bug。查看log日志,所有的请求都正常。昨晚跟同学讨论了下,感觉应该是js自动保存逻辑出问题了。仔细review了下代码,果真是。</p> <p>原来的实现是,自动保存和markdown渲染封装在同一个按键检测事件里,并加入了一个10秒钟的延时,如果10秒钟内没有按键触发则发起PUT请求保存当前内容。原来的代码所存在的问题是,编辑一个文件的时候,如果10秒钟内切换到了另外一个文档,那么,会导致读取的id和content不一致,具体说明如下:</p> <pre class="javascript"><code>$('#response').keyup(function () { var v = $('#response').val(), s = markdown.core.mdToHtml(v); $("#rendered").html(s) MathJax.Hub.Queue(["Typeset",MathJax.Hub,"rendered"]); if(save_time_out) { clearTimeout(save_time_out); } //10 秒钟内检测到输入则重置延时 save_time_out = setTimeout(function () { var ref = $('#tree-container').jstree(true), sel = ref.get_selected(); if(!sel.length) { return false; } sel = sel[0]; $.ajax({ type: "PUT", url: "/essays/"+sel, data: JSON.stringify({"id":sel, "body": v}), // 问题出在这里, //PUT请求时,传递的是10秒钟前读到的v, //而此时的blog_id(即变量sel)可能变了, //导致一个文档的content更新到另一个文档里去了 // 更改后的代码每次PUT重新读取当前的content和id // data: JSON.stringify({"id":sel, "body": $('#response').val() }), contentType: "application/json; charset=utf-8", datatype: "json", success: function(data){ save_tree("Blog " + sel + " Content AutoSaved: ") }, failure: function(err){console.log(err)} }) }, 10000); });</code></pre> <h2 id="update-1">Update</h2> <p>貌似这个网站保持每年更新一次的频率。最近这次又重新折腾了下,不过目前依然是半成品。这次折腾的时间稍微有点长,前前后后大概有一个月时间吧。先说说改版的原因:</p> <ol style="list-style-type: decimal"> <li>印象笔记基本被我弃用,而且原来写的网站后台同步系统有点小bug一直没有修复;</li> <li>最近一段时间学习了下Clojure,挺有意思的一门语言,想动手实践下;</li> <li>原来的HTML5模板总觉得被我改的有点丑,想再重新写一版;</li> <li>希望将网站的后台管理系统设计成个性化的笔记管理系统,一定程度上替代印象笔记,为知笔记等;</li> </ol> <p>针对上面几点,分别对网站做了以下改进:</p> <ol style="list-style-type: decimal"> <li>抛弃原来的同步系统后,重写了后台的文档管理体系统,整个编辑界面借鉴了<a href="/static/essay_resources/About_This_Site/www.zybuluo.com/">作业部落</a>的设计方案,双屏切分(左边编辑,右边预览),导航栏用于管理文档结构,由于是自己用,舍弃了账户管理模块;</li> <li>网页框架采用了<a href="http://www.luminusweb.net/">luminus</a>,我觉得这就是个大杂烩,把各个优秀的部件组织在一起,然后提供一些高可用的模板,上手非常方便。数据存储方面,抛弃了之前用的SQLite,换了Redis(没别的原因,只是对Redis用着熟悉点),文件存储上,仍然采用了<a href="http://www.qiniu.com/">七牛云存储</a>,<a href="https://github.com/killme2008/clj.qiniu">qiniu-clojure-sdk</a>的作者封装得真简洁......网页权限管理用到了之前说的Buddy,简单易行;后台管理的安全性方面,目前暂时把csrf模块给去掉了,因为后台有很多的ajax请求,还没想好该怎么绑定csrf-token,缺少这方面的经验,这应该是整个网站最大的安全漏洞;</li> <li>重写前端页面。这块挺花时间的,主要是自己对js不熟,又重新翻了下<a href="http://book.douban.com/subject/10546125/">JavaScript高级程序设计</a>这本书,然后熟悉了下jquery和react,感觉react写前端的想法确实有点不太一样,然后继续往前学习了下ClojureScript以及对react封装的库OM,完全刷新了我对前端的认识(我承认之前一直很BS前端干的活......),然而时间精力有限,这些都仅仅是浅尝辄止。最后前端部分主要用jquery写的;</li> </ol> <h2 id="说说感受">说说感受</h2> <p>整个从前端到后端全都写了一遍,说实话,觉得挺累的。很多地方都是从零开始,完全没有经验可谈。除了HTML是在WebStorm下写的,其余都是在vim下写的,并不是我排斥IDE,而是IDE用着效率太低了。</p> <p>总的来说,我挺喜欢Clojure这门语言的,简洁,有魔力!可惜的是我到目前为止还算不上入门,也没体会到其在处理并发问题上的优势。而且,最近写clojure的过程中,也读了不少框架的源码,感觉很多代码还是很难懂。接下来的一段时间估计没空深入学习了,毕竟clojure又不能拿来跑RNN......我能想到后面会用到clojure的地方估计就是用它来写SICP的习题了......</p> <hr> <h2 id="写在前面">写在前面</h2> <p>之前网站一直放在<a href="http://www.sinaapp.com/">SAE</a>上,除了每月扣点<strong>豆子</strong>,用着也没啥问题,除了扩展性不太好之外。不过,间歇性的出了几次意外,后台往sql中写入数据的时候,不知道是啥原因,提交后页面卡死了,然后再去sae后台一看,哗啦啦几百豆子没了......我总共才送了1.5k,无语了。想了半天也没找出时什么原因,只知道是全都扣在sql的读写上了。也罢,懒得在上面折腾了,写的东西暂时都放在印象笔记里。前些天忽然想起来github送的DigitalOcean优惠券还没用,最近有点闲时间,再折腾了一把。之前的后台是刚熟悉python的时候写的,现在再看看,真是渣渣......然后动手重新写了一遍部署到DO上(回头看了一下,其实还是渣渣......忧桑)。这段时间用印象笔记用着很爽,主要是方便,所以,这次后台的改动主要就一个,利用Evernote的api把网站后台跟印象笔记打通了,这样便于随时积累,持续更新。<del>另外,rss给去掉了,有兴趣的话加个印象笔记吧~</del></p> <h2 id="整体结构">整体结构</h2> <p>怕自己以后给忘了,画个图,以后再修改起来也方便。<img alt="图1 Site Design" src="/static/essay_resources/About_This_Site/site_design.png"/></p> <p>跟印象笔记api打交道的过程中,坑很多,最惨的一个莫过于,在sandbox中测试的好好的,换成印象笔记的token后就出错了,各种google,居然有人给出了<a href="http://stackoverflow.com/questions/23098372/evernote-invalid-token">解决方案</a>,把service_host改一下就好了,另外这里只是简单采用轮询的方式,因为对实时性要求不高,所以没用所谓的webhook。</p> <p>目前网站布在新加坡节点,感觉速度上还行吧,没有网上说的那么差。万一不稳定了再换换。新加坡的节点有ipv6这个很不错。配合shadowsocks用着很快。</p> <p>其实本来打算把shares部分大改的,主要是想跟自己的虾米账户打通,已经写了一半了,忽然发现国外不能访问国内的虾米,真是个蛋疼的世界。唉,真是应了那句话。</p> <blockquote> <p>墙外的想到墙内去,墙内的想到墙外来......</p> </blockquote> <p>算了,以后有空再弄弄,往所里的电脑上布个代理做跳板。也许下次会直接弄个music的二级域名出来。</p> <h2 id="说点细节">说点细节</h2> <p>印象笔记和数据库的同步通过定时器实现,由于印象笔记的特殊性,每一条笔记对应正文和资源(包括图片等附件)两部分,因此将同步过程分为两步。</p> <ol style="list-style-type: decimal"> <li><p>首先查询对应笔记本中每条笔记的更新时间,同时检索本地数据库中每一条记录的更新时间。然后提取需要更新和新建的记录。正文部分用正则做一个粗过滤(替换掉div,br标签)后写入数据库(其实可以经过markdown渲染之后再写入数据库,这样响应会更快,不过,这部分响应时间相比建立连接的时间而言可以忽略不计,而且有时候需要在后台查看下过滤的结果),同时提取附件相关的内容转换成链接格式。并将涉及到的资源写入数据库中的一张表。</p></li> <li><p>扫描上面的资源表,对于没有下载到本地和同步到cdn的资源分别下载和上传,并校验。</p></li> </ol> <p>分成两步的最主要原因是,如果附件比较大,很容易出现下载上传失败的情况,从而导致整个文档更新失败。将下载和上传部分独立出来后,不会影响文档内容的同步,如果某些大文件没法自动同步可手动在后台同步。</p> <p>最后绑定下域名,分别把www.tianjun.ml和www.tianjun.me通过301重定向到主域名上。</p> <h2 id="谈谈感受">谈谈感受</h2> <p>自己这么亲自折腾一把后,才会对现在的云平台带来的好处感受更深。其实说到底,像安装配置apache这些都是dirty work,云平台把这些集成后,可以说大大免去了这些苦力活,稳定性也有保证。</p> <p>很久没写点东西了,最近会陆陆续续整理些出来,坚持坚持~</p> </hr></body>Jun TianFri, 08 Nov 2019 16:38:24 GMTAbout_This_SiteTesthttps://tianjun.me/essays/Test<body> <h1 id="markdown-basics">Markdown Basics</h1> <p>以下是引用:</p> <blockquote> <p>This is a MPE extended feature.</p> </blockquote> <h2 id="加粗斜体删除等">加粗,斜体,删除等</h2> <p><em>This text will be italic</em> <em>This will also be italic</em></p> <p><strong>This text will be bold</strong> <strong>This will also be bold</strong></p> <p><em>You <strong>can</strong> combine them</em></p> <p><del>This text will be strikethrough</del></p> <h2 id="无序列表嵌套">无序列表(嵌套)</h2> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 2a</li> <li>Item 2b</li> </ul> <h2 id="有序列表嵌套">有序列表(嵌套)</h2> <ol style="list-style-type: decimal"> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> <li>Item 3a</li> <li>Item 3b</li> </ol> <h2 id="图片">图片</h2> <div class="figure"> <img alt="GitHub Logo" src="/static/essay_resources/Test/github_logo.png"/> <p class="caption">GitHub Logo</p> </div> <div class="figure"> <img alt="Remote IMG" src="https://assets-cdn.github.com/images/modules/logos_page/Octocat.png"/> <p class="caption">Remote IMG</p> </div> <h2 id="链接">链接</h2> <p>http://github.com - automatic! <a href="http://github.com">GitHub</a></p> <h2 id="横线">横线</h2> <hr/> <p>Hyphens</p> <hr/> <p>Asterisks</p> <hr/> <p>Underscores</p> <h2 id="inline-code">Inline code</h2> <p>For example: <code>lambda x: x^2</code>.</p> <h2 id="code">Code</h2> <p>python and julia only</p> <pre class="language-python"><code>import os os.listdir('.')</code></pre> <h3 id="tables">Tables</h3> <p>You can create tables by assembling a list of words and dividing them with hyphens <code>-</code> (for the first row), and then separating each column with a pipe <code>|</code>:</p> <table> <thead> <tr class="header"> <th>First Header</th> <th>Second Header</th> </tr> </thead> <tbody> <tr class="odd"> <td>Content from cell 1</td> <td>Content from cell 2</td> </tr> <tr class="even"> <td>Content in the first column</td> <td>Content in the second column</td> </tr> </tbody> </table> <h3 id="emoji-font-awesome">Emoji &amp; Font-Awesome</h3> <blockquote> <p>This only works for <code>markdown-it parser</code> but not <code>pandoc parser</code>.<br/> Enabled by default. You can disable it from the package settings. 👍</p> </blockquote> <p>😄 :fa-car:</p> <pre><code>:smile: :fa-car:</code></pre> <h3 id="footnotes">Footnotes</h3> <p>Content <a class="footnoteRef" href="/static/essay_resources/Test/#fn1" id="fnref1"><sup>1</sup></a></p> <h3 id="mark">Mark</h3> <p>==marked==</p> <pre class="markdown"><code>==marked==</code></pre> <div class="alert alert-success"> <p>Great!</p> </div> <h2 id="equation">Equation</h2> <p><span class="math display">\[ \begin{equation} a^2 + b^2 = c^2 \end{equation} \]</span></p> <h2 id="references">References</h2> <ul> <li><a href="https://guides.github.com/features/mastering-markdown/">Mastering Markdown</a></li> <li><a href="https://daringfireball.net/projects/markdown/basics">Daring Fireball: Markdown Basics</a></li> </ul> <div class="footnotes"> <hr/> <ol> <li id="fn1"><p>Hi! This is a footnote<a href="/static/essay_resources/Test/#fnref1">↩</a></p></li> </ol> </div> </body>Jun TianThu, 17 Oct 2019 09:29:16 GMTTestWrite_Python_in_Lisphttps://tianjun.me/essays/Write_Python_in_Lisp<body> <h1 id="write-python-in-lisp">Write Python in Lisp</h1> <p>上周在Clojure微信群里,<a href="https://github.com/chanshunli">Steve Chan</a>分享了个关于<a href="https://github.com/hylang/hy">Hylang</a>的链接,让人眼前一亮,原来Python居然还可以这么写!经过这几天的摸索,意外地感觉不错,在这里推荐给大家试试,有兴趣的话可以看完官网的doc后再回来看下文。</p> <h2 id="hylang是什么">Hylang是什么?</h2> <p>Hy是基于python的lisp方言,与python的互操作非常顺畅,有点类似clojure于java的关系。从安装上可以看出,Hy实际上就是一个普通的python库,在python代码中可以直接<code>import hy</code>之后,把<code>.hy</code>文件当做普通的python文件,import其中的变量。核心代码部分,该有的也都有(最重要的当然是macro),可以从clojure无障碍迁移过来。</p> <p>由于是直接把lisp代码转换成AST,开启<code>--spy</code>模式之后,可以看到每一行lisp代码转换之后的python代码,各种库的操作也完全没有障碍。试用了一些常用的库,基本没有什么大问题。目前感觉不是够顺畅的地方,反而是一些外围,比如没有很好的编辑环境。社区的vim插件提供的功能很弱,为此我特地入坑spacemacs!emacs对应的插件稍微好点,提供了发送代码到repl的功能,不过最重要的仍然是,没有代码补全,网上有人提供了一些静态的补全方案,通过提取hy库中的关键词和当前buffer中的变量名来补全(没有配置成功……),不过实际使用中会大量调用python库,因此急需像python里的anaconda-mode一类工具提供辅助补全。再比如静态语法检查,调试。</p> <h2 id="hylang不是">Hylang不是……</h2> <h3 id="hylang不是clojure">Hylang不是Clojure</h3> <p>这个是首先需要意识到的一点。尽管在语法和许多函数上和clojure很像,但是因为底层实现和语言的定位不一样,这其中的许多函数不再是clojure中对应函数的完整复制。以下列举一些很容易碰到的问题:</p> <ol type="1"> <li>muttable &amp; immutable. Hylang本身的定位是鼓励与python的互操作,因此大量的操作都是基于python本身的数据结构,需要非常小心数据随时都可能改变。在写Hylang代码的时候需要时刻提醒自己,“我写的是python代码!代码都会最终转换成python代码去执行!”,社区里最近也在讨论引入immutable的数据结构,不知道这块以后会怎么发展。</li> <li>lazy. Hylang中大多数代码的lazy实现都是基于generators实现的了iterable,这下就蛋疼了。在python里,生成器访问一次之后,如果你不保存的话,数据就没有了……所以你会发现<code>(take n coll)</code>中,如果xs是一个iterable的数据,上面的代码执行多次是可能得到不同结果的。甚至如果不保存的话,没法访问已经被访问过的内容。不过好在0.12.0之后提供了lazy sequences,一定程度上缓解了这个问题。</li> <li>in-place operations.在python中,许多函数都是默认in-place的,比如<code>sort</code>,<code>random.shuffle</code>等,有些可能提供了对应的非in-place的函数(如<code>sorted</code>),有些则没有。这点需要格外注意,否则,定义变量的时候很可能返回值就是个<code>None</code>。不过在<code>numpy</code>,<code>tensorflow</code>,<code>pandas</code>等库中,这点考虑得比较全面</li> <li>scope. 看github上过去关于<code>let</code>绑定的issue,可以深入了解这块内容。在不确定变量名的scope时,可以看看对应的python代码。</li> </ol> <h2 id="体验过程中的一些坑">体验过程中的一些坑</h2> <ol type="1"> <li>文件名。写过了clojure的话会习惯<code>-</code>作为连接符,<code>hy</code>的文件名需要转换成<code>_</code>连接符,否则在python代码中不能import。</li> <li>某些函数的的实现有bug。我自己在尝试的过程中就发现了<code>partition</code>函数的实现有点问题,在github上提了个issue。社区里的反应还是挺快的,第二天就解决并合并到master上了。</li> <li>参数传递过程中,运用apply传递positional和named arguments时,需要分别用list和dict对二者封装,不能偷懒直接用一个list搞定。</li> </ol> <h2 id="hylang适合写点什么">Hylang适合写点什么?</h2> <p>写Hylang也就这几天,对macro的感受还不是很强烈,主要是写了点日常的数据分析代码和tensorflow中的tutorial,以下是一些个人感受:</p> <ul> <li>如果只是写一些调用API的代码,其实不太适合。比如我在翻译tensorflow的tutorial过程中,需要反复去查对应的API,很繁琐,而且已有的框架会在不知不觉中对写lisp风格的代码有一些限制,从而使得python代码更适合命令式地处理逻辑。</li> <li>适合更抽象层的数据预处理逻辑。这块写起来会很舒服,对读代码和写代码的人来说,都是一种享受。可以将二者结合,这部分代码用hy处理后以接口的形式暴露给模型构建部分,最后再用hy糅合train,valid,test的过程。当然,现在某些库(tensorlayer)实际上把直接跟tensorflow打交道的部分做了很浅的一层封装,整体易用性更高了。</li> </ul> <p>最后,一点学习经验:</p> <blockquote> <p>When I’m learning something new, I sometimes find myself practicing EDD (exception-driven development). I try to evaluate some code, get an exception or error message, and then Google the error message to figure out what the heck happened. – <em>Mastering Clojure Macros</em></p> </blockquote> <p>另外,这个语言还是太小众了,玩玩就可以了,别太当真……</p> </body>Jun TianThu, 17 Oct 2019 09:29:16 GMTWrite_Python_in_LispUnderstanding_Variational_Autoencoderhttps://tianjun.me/essays/Understanding_Variational_Autoencoder<body> <h1 id="understanding-variational-autoencoder">Understanding Variational Autoencoder</h1> <p>本文主要记录自己在学习<a href="https://arxiv.org/abs/1603.00788">Automatic Differentiation Variational Inference</a>过程中的一些参考资料和理解。</p> <h2 id="熵entropy">熵(Entropy)</h2> <p>以下借用《Statistical Rethinking》一书中的部分内容来理解Entropy及其相关的内容。</p> <p>假设今天天气预报告诉我们,明天有可能下雨(记为事件A),该事件有一定的不确定性,等到第二天结束的时候,不论第二天是否下了雨,之前的不确定性都消失了。换句话说,在第二天看到事件A的结果时(下雨或没下雨),我们获取了一定的信息。</p> <blockquote> <p><strong>信息</strong>:在观测到某一事件发生的结果之后,不确定性的降低程度。</p> </blockquote> <p>直观上,衡量信息的指标需要满足一下三点:</p> <ol type="1"> <li>连续性。如果该指标不满足连续性,那么一点微小的概率变化会导致很大的不确定性的变化。</li> <li>递增性。随着可能发生的事件越多,不确定性越大。比如有两个城市需要预测天气,A城市的有一半的可能下雨,一半的可能是晴天,而B城市下雨、下冰雹和晴天的概率分别为1/3,那么我们希望B城市的不确定性更大一些,毕竟可能性空间更大。</li> <li>叠加性。将明天是否下雨记为事件A,明天是否刮风记为事件B,假设二者相互独立,那么将事件A的不确定性与事件B的不确定性之和,与(下雨/刮风、不下雨/刮风、下雨/不刮风、不下雨/不刮风)这四个事件发生的不确定性之和相等。</li> </ol> <p>信息熵的表达形式刚好满足以上三点:</p> <p><span class="math display">\[ \begin{equation} \begin{split} H(p) &amp; = - \mathbb{E} \log \left(p_i\right) \\ &amp; = - \sum_{i=1}^{n} p_i \log\left(p_i \right) \end{split} \end{equation}\]</span></p> <p>简单来说,熵就是概率对数的加权平均。</p> <h2 id="k-l散度kullback-leibler-divergence">K-L散度(Kullback-Leibler Divergence )</h2> <blockquote> <p><strong>散度</strong>:用某个分布去描述另外一个分布时引入的不确定性。</p> </blockquote> <p>散度的定义如下:</p> <p><span class="math display">\[ \begin{equation} \begin{split} D_{KL}(p,q) &amp; = \sum_{i \in I} p_i \left( \log (p_i) - \log (q_i) \right) \\ &amp; = \sum_{i \in I} p_i \log \left( \frac {p_i} {q_i} \right) \end{split} \label{KL} \end{equation} \]</span></p> <p>KL散度是大于等于0的,可以通过<a href="https://en.wikipedia.org/wiki/Gibbs%27_inequality">Gibb’s不等式</a>证明:</p> <p>首先,我们知道:</p> <p><span class="math display">\[ \begin{equation} \ln x \le x - 1 \end{equation} \]</span></p> <p>于是,根据<span class="math inline">\(\eqref{KL}\)</span>中KL散度的定义,可以得到如下不等式:</p> <p><span class="math display">\[ \begin{equation} \begin{split} -\sum_{i \in I} p_i \log \left( \frac {q_i} {p_i} \right) &amp; \ge - \sum_{i \in I} p_i \left( \frac {q_i - p_i} {p_i}\right) \\ &amp;= -\sum_{i \in I} (q_i - p_i) \\ &amp;= 1 - \sum_{i \in I} q_i \\ &amp;\ge 0 \end{split} \end{equation} \]</span></p> <p>可以看出,只有当两个分布一一对应相等的时候才取0。</p> <p>如果将KL散度拆开,可以看作是交叉熵与信息熵之差:</p> <p><span class="math display">\[ \begin{equation} D_{KL} = H(p,q) - H(p) \end{equation} \]</span></p> <p>关于KL散度,一个很重要的特性是,<span class="math inline">\(KL(p,q)\)</span>一般不等于<span class="math inline">\(KL(q,p)\)</span>,也就是说,KL散度是有方向性的。这里借用<a href="https://book.douban.com/subject/26607925/">Statistical Rethinking</a>一书第6章中的例子来解释下。</p> <p>假设在地球上随机选一地点,该点位于水面和陆地的概率分别为0.7和0.3,记为<span class="math inline">\(q=(0.7,0.3)\)</span>,我们知道火星非常干燥,假设相应的概率为<span class="math inline">\(p=(0.01,0.99)\)</span>,可以算出<span class="math inline">\(KL(p,q)=1.14\)</span>,<span class="math inline">\(KL(q,p)=2.62\)</span>。可以看出,用火星上的分布去估计地球上的分布时,得到的散度更大。直观可以这么理解:一个地球人第一次到火星上时,有很大概率落在陆地上,根据他在地球上的先验,落在陆地上的概率为0.3,因而不会特别惊讶;相反,一个火星人第一次落到地球上时,大概率会落到水面上,这对于火星人来说,是非常惊讶的事(火星上只有0.01的概率),因而其KL散度更大。因此,通常如果选择一个熵值较大的分布去估计某个真实分布时,得到的KL散度会更小一些。</p> <h2 id="the-evidence-lower-bound">The Evidence Lower Bound</h2> <p>给定<span class="math inline">\(\boldsymbol{x} = x_{1:n}\)</span>为观测变量,<span class="math inline">\(\boldsymbol{z}=z_{1:m}\)</span>为隐变量,对应的联合概率为<span class="math inline">\(p(\boldsymbol{z}, \boldsymbol{x})\)</span>,后验可以写成:</p> <p><span class="math display">\[ \begin{equation} p(\boldsymbol{z} | \boldsymbol{x}) = \frac{p(\boldsymbol{z}, \boldsymbol{x})}{p(\boldsymbol{x})} \label{posterior} \end{equation} \]</span></p> <p>其中<span class="math inline">\(p(\boldsymbol{x})\)</span>称为证据:</p> <p><span class="math display">\[ \begin{equation} p(\boldsymbol{x}) = \int p(\boldsymbol{z}, \boldsymbol{x})d\boldsymbol{z} \end{equation} \]</span></p> <p>变分推断背后的思想是,用一些简单的参数化分布(记为<span class="math inline">\(Q_{\phi}(\boldsymbol{z} | \boldsymbol{x})\)</span>)去拟合后验分布<span class="math inline">\(P(\boldsymbol{z}|\boldsymbol{x})\)</span>,通过调整参数<span class="math inline">\(\phi\)</span>使得<span class="math inline">\(Q_{\phi}\)</span>尽可能接近<span class="math inline">\(P(\boldsymbol{z}|\boldsymbol{x})\)</span>,从而转换成优化问题。衡量二者相似度的方法之一就是用前面提到的KL散度,按理说,我们应该最小化<span class="math inline">\(KL(P,Q)\)</span>,不过实际使用中通常是最小化<span class="math inline">\(KL(Q,P)\)</span>,前面也介绍了,二者实际上是不同的,可以参考阅读<a href="http://blog.evjang.com/2016/08/variational-bayes.html">A Beginner’s Guide to Variational Methods: Mean-Field Approximation</a>一文中的<em>Forward KL vs. Reverse KL</em>和<a href="http://wiseodd.github.io/techblog/2016/12/21/forward-reverse-kl/">KL Divergence: Forward vs Reverse?</a>部分来了解为什么优化<span class="math inline">\(KL(Q,P)\)</span>。</p> <p><span class="math display">\[ \begin{equation} KL(q(\boldsymbol{z}) \| p(\boldsymbol{z}|\boldsymbol{x})) = \mathbb{E} [ \log q(\boldsymbol{z})] - \mathbb{E} [\log p(\boldsymbol{z}|\boldsymbol{x})] \end{equation} \]</span></p> <p>这里的<span class="math inline">\(\mathbb{E}\)</span>是相对<span class="math inline">\(q(\boldsymbol{z})\)</span>的期望,将<span class="math inline">\(\eqref{posterior}\)</span>代入可得:</p> <p><span class="math display">\[ \begin{equation} KL(q(\boldsymbol{z}) \| p(\boldsymbol{z}|\boldsymbol{x})) = \mathbb{E} [ \log q(\boldsymbol{z})] - \mathbb{E} [\log p(\boldsymbol{z},\boldsymbol{x})] + \log p(\boldsymbol{x}) \label{KLqp} \end{equation} \]</span></p> <p>由于<span class="math inline">\(p(\boldsymbol{x})\)</span>是固定的,于是最小化上式中的KL等价于最大化下面的证据下界:</p> <p><span class="math display">\[ \begin{equation} ELBO(q) = \mathbb{E}[\log p(\boldsymbol{z},\boldsymbol{x})] - \mathbb{E} [\log q(\boldsymbol{z})] \label{ELBO} \end{equation} \]</span></p> <p>上式中的联合概率又可以表示成先验乘以似然,于是有:</p> <p><span class="math display">\[ \begin{equation} \begin{split} ELBO(q) &amp; = \mathbb{E} [\log p(\boldsymbol{z})] + \mathbb{E} [\log p(\boldsymbol{x}| \boldsymbol{z})] - \mathbb{E} [\log q(\mathbb{z})] \\ &amp; =\mathbb{E}[\log p(\boldsymbol{x}|\boldsymbol{z})] - KL(q(\boldsymbol{z})\| p(\boldsymbol{z})) \end{split} \end{equation} \]</span></p> <p>直观上看,第一项是似然的期望,最大化ELBO意味着我们希望隐变量能够很好地解释观测数据;第二项是隐变量的先验与估计之间的KL散度,越小越好。由<span class="math inline">\(\eqref{KLqp}\)</span>和<span class="math inline">\(\eqref{ELBO}\)</span>可以得到:</p> <p><span class="math display">\[ \begin{equation} \log p(\boldsymbol{x}) = KL(q(\boldsymbol{z}) \| p(\boldsymbol{z}|\boldsymbol{x})) + ELBO(q) \end{equation} \]</span></p> <p>上面式左边是证据,由于KL散度大于等于0,因而右边的第二项ELBO也就称作证据下界。</p> <h2 id="variational-autoencoder">Variational Autoencoder</h2> <p>关于VAE的文章很多,这里就不详细介绍了。<a href="https://arxiv.org/abs/1312.6114">VAE</a>的原文不太好读懂,建议先读<a href="https://arxiv.org/abs/1606.05908">Tutorial on Variational Autoencoders</a>,然后可以看看一些代码实现,比如<a href="http://wiseodd.github.io/techblog/2016/12/10/variational-autoencoder/">Variational Autoencoder: Intuition and Implementation</a>,和这里<a href="http://kvfrans.com/variational-autoencoders-explained/">Variational Autoencoders Explained</a>。</p> <h2 id="read-more">Read More</h2> <ul> <li><a href="http://blog.evjang.com/2016/08/variational-bayes.html">A Beginner’s Guide to Variational Methods: Mean-Field Approximation</a></li> <li><a href="http://www.openias.org/variational-coin-toss">Variational Coin Toss</a></li> <li><a href="https://arxiv.org/abs/1601.00670">Variational Inference: A Review for Statisticians</a></li> </ul> </body>Jun TianThu, 17 Oct 2019 09:29:16 GMTUnderstanding_Variational_AutoencoderSome_Interesting_Quil_Exampleshttps://tianjun.me/essays/Some_Interesting_Quil_Examples<body> <h1 id="about-quil">About Quil</h1> <p><a href="http://quil.info/">Quil</a>是Clojure下用来画图的一个库,对<a href="https://processing.org/">Processing</a>做了封装。Processing想必大家都知道,用来画图非常有意思,我第一次接触Processing是在图书馆里看到了<a href="https://book.douban.com/subject/26264736/">代码本色:用编程模拟自然系统 [The Nature of Code:Simulating Natural Systems with Processing]</a>。不过只是大致翻了翻,有个基本的印象,最近碰巧想到用clojure来画图,于是找到了quil这个库。Processing这个库本身是用java写的(现在已经有很多语言的扩展了),因此用clojure写起来相当方便,而且得益于clojure中许多函数式编程的思想,写代码的感觉非常顺畅!以后深入了解下二者后写个详细的对比分析。本文主要是记录下平时写的一些比较有意思的动图。</p> <h1 id="lissajous-曲线的动画演示">Lissajous 曲线的动画演示</h1> <p>第一次看到Lissajous曲线是从<a href="http://www.matrix67.com/blog/archives/6947">Matrix67</a>的文章里看到的,这类曲线还是蛮有意思的,包括Quil官网上也有几个类似的例子,这里我也用Quil画了一个图~</p> <figure> <img alt="Lissajous.gif" src="/static/essay_resources/Some_Interesting_Quil_Examples/Lissajous.gif"/><figcaption>Lissajous.gif</figcaption> </figure> <p>代码如下:</p> <pre class="clojure"><code>(ns hello-quil.core (:require [quil.core :as q] [quil.middleware :as m])) (defn setup [] (q/frame-rate 30) (q/background 255) (q/color-mode :hsb 10 1 1)) (defn f [t] [(* 200 (q/sin (* t 13))) (* 200 (q/sin (* t 18))) ]) (defn draw-plot [f from to step] (doseq [two-points (-&gt;&gt; (range from to step) (map f) (partition 2 1))] (apply q/line two-points))) (defn draw [] (q/with-translation [(/ (q/width) 2) (/ (q/height) 2)] (let [t (/ (q/frame-count) 80)] (q/stroke 1 1 1) (q/line (f t) (f (+ t (/ 1 80)))) (q/save-frame "./data/Lissajous-####.png")))) (q/defsketch trigonometry :size [500 500] :draw draw :setup setup) </code></pre> </body>Jun TianThu, 17 Oct 2019 09:29:16 GMTSome_Interesting_Quil_Examples