├── resources ├── public │ ├── GRP-1.png │ ├── love.png │ ├── didi-1.png │ ├── didi-2.jpg │ ├── Lissajous.gif │ ├── Statistical-Rethinking-1.png │ ├── prism.css │ ├── prism.clojure.js │ └── cssstyle.css ├── unpublished-md │ ├── Learn Haskell.md │ ├── base.md │ ├── 重复掷一个骰子,第一次看到连续2次都是6时,投掷次数的期望是?.md │ ├── 这只是个开始.......md │ ├── 给自己放一天假~.md │ ├── Optional in Java 8.md │ ├── 一张图说cuda.md │ ├── Some Interesting Quil Examples.md │ ├── The Gambler's Ruin Problem.md │ ├── 理解Clojure中Buddy认证模块.md │ ├── The EM Algorithm.md │ ├── 一个微博爬虫的设计原型.md │ ├── Write Python in Lisp.md │ ├── 聊聊最近.md │ ├── (4)一起用python之基础篇——入门书.md │ ├── Statistical Rethinking 读书笔记.md │ ├── 最近的一些感悟.md │ ├── Throwing Eggs from a Building.md │ ├── 我的面试题.md │ ├── Data Science London + Scikit-learn.md │ ├── 使用Pandas的一点经验和技巧.md │ ├── 解读libFM.md │ ├── Data Visualization and Analysis in Recsys Challenge.md │ ├── About This Site.md │ ├── CS3110学习笔记.md │ ├── Understanding Variational Autoencoder.md │ ├── All About Zipper.md │ ├── Deep Learning 相关库简介.md │ └── Machine Learning A Bayesian and Optimization Perspective.md ├── templates │ ├── disqus.html │ ├── about.html │ └── base.html └── published-html │ ├── 这只是个开始.......html │ ├── 给自己放一天假~.html │ ├── 一张图说cuda.html │ ├── 理解Clojure中Buddy认证模块.html │ ├── 聊聊最近.html │ ├── 一个微博爬虫的设计原型.html │ ├── The EM Algorithm.html │ ├── Write Python in Lisp.html │ ├── (4)一起用python之基础篇——入门书.html │ ├── 最近的一些感悟.html │ ├── Data Science London + Scikit-learn.html │ ├── Statistical Rethinking 读书笔记.html │ ├── Throwing Eggs from a Building.html │ ├── 使用Pandas的一点经验和技巧.html │ ├── 解读libFM.html │ └── Data Visualization and Analysis in Recsys Challenge.html ├── .gitignore ├── test └── blog_clj │ ├── handler_test.clj │ └── redis_io_test.clj ├── src └── blog_clj │ ├── upload_download.clj │ ├── schedules.clj │ ├── handler.clj │ ├── webhooks.clj │ ├── page_generators.clj │ ├── redis_io.clj │ ├── parse.clj │ └── sync.clj ├── LICENSE ├── project.clj └── README.md /resources/public/GRP-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/blog-clj/master/resources/public/GRP-1.png -------------------------------------------------------------------------------- /resources/public/love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/blog-clj/master/resources/public/love.png -------------------------------------------------------------------------------- /resources/public/didi-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/blog-clj/master/resources/public/didi-1.png -------------------------------------------------------------------------------- /resources/public/didi-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/blog-clj/master/resources/public/didi-2.jpg -------------------------------------------------------------------------------- /resources/unpublished-md/Learn Haskell.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | 3 |
TEST
4 | 5 | # Start From Here 6 | -------------------------------------------------------------------------------- /resources/public/Lissajous.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/blog-clj/master/resources/public/Lissajous.gif -------------------------------------------------------------------------------- /resources/public/Statistical-Rethinking-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/findmyway/blog-clj/master/resources/public/Statistical-Rethinking-1.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | config.sh 12 | .DS_Store 13 | -------------------------------------------------------------------------------- /resources/unpublished-md/base.md: -------------------------------------------------------------------------------- 1 | 6 | [toc] 7 | 8 |
TEST
9 | 10 | # Start From Here 11 | -------------------------------------------------------------------------------- /resources/unpublished-md/重复掷一个骰子,第一次看到连续2次都是6时,投掷次数的期望是?.md: -------------------------------------------------------------------------------- 1 | 6 | [toc] 7 | 8 |
TEST
9 | 10 | # Start From Here 11 | -------------------------------------------------------------------------------- /test/blog_clj/handler_test.clj: -------------------------------------------------------------------------------- 1 | (ns blog-clj.handler-test 2 | (:require [clojure.test :refer :all] 3 | [ring.mock.request :as mock] 4 | [blog-clj.handler :refer :all])) 5 | 6 | (deftest test-app 7 | (testing "main route" 8 | (let [response (app (mock/request :get "/"))] 9 | (is (= (:status response) 200)))) 10 | 11 | (testing "not-found route" 12 | (let [response (app (mock/request :get "/invalid"))] 13 | (is (= (:status response) 404))))) 14 | -------------------------------------------------------------------------------- /src/blog_clj/upload_download.clj: -------------------------------------------------------------------------------- 1 | (ns blog-clj.upload-download 2 | (:require [clj.qiniu :as qiniu] 3 | [environ.core :refer [env]] 4 | [clojure.string :as string])) 5 | 6 | (def bucket "ontheroad") 7 | 8 | (qiniu/set-config! 9 | :access-key (env :qiniu-ak) 10 | :secret-key (env :qiniu-sk) 11 | :up-host "http://up.qiniug.com") 12 | 13 | (defn upload 14 | [f] 15 | (let [file-path (str (env :html-path) f) 16 | upload-key (str "upload/" (last (string/split f #"/")))] 17 | (qiniu/upload-bucket bucket upload-key file-path))) 18 | -------------------------------------------------------------------------------- /src/blog_clj/schedules.clj: -------------------------------------------------------------------------------- 1 | (ns blog-clj.schedules 2 | (:require [chime :refer [chime-at]] 3 | [clj-time.core :as t] 4 | [clj-time.periodic :refer [periodic-seq]] 5 | [taoensso.timbre :refer [info]])) 6 | 7 | ;; start some schedule tasks when server start 8 | ;(def job (chime-at (take 10 (periodic-seq (t/now) (-> 10 t/seconds))) 9 | ;(fn [time] 10 | ;(spit "test.txt" (str time "\n") :append true)))) 11 | 12 | (defn start 13 | "start some schedules when server starts" 14 | [] 15 | (info "Starting Server...")) 16 | 17 | (defn stop 18 | "stop some schedules when server shutdown" 19 | [] 20 | (info "Stoping Server...") 21 | ;(job) 22 | ) 23 | -------------------------------------------------------------------------------- /resources/unpublished-md/这只是个开始.......md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # 这只是个开始...... 3 |
2013-10-07 08:54:00
4 |
2013-10-07 08:54:00
5 |
13
6 |
Life
7 | 国庆假期结束,来这儿也一个多月了,慢慢适应着这里的节奏,不紧不慢。走在路上的时候,难免会觉得学校里太过冷清,相比之下,中南的本部算是热闹的了,这阵子应该是招聘会的旺季。不过冷清也未尝不是件好事,充足的时间和空间让人更加专注于当下的学习。 8 | 9 | 也许是太过自信,本以为自己对环境的适应力算是可以的,没想开学后直到这个假期才找到点状态。一直想到这儿来更新下,无奈,各种琐事实在让人难以静下来思考,而且,这期间一直在想整个网站的写作风格应该是怎样的。我希望做一件事就一直坚持下去,当然,前提是在开始这件事的时候就给自己一个坚定的信念,这件事是值得的。 10 | 11 | 《为什么你应该(从现在开始就)写博客 》一文对我的影响比较大,一开始写blog可能就只是一个强迫自己去思考的过程,平时的思维是极不稳定的,走在路上时的灵机一动,或者是听课时天马行空般的想像很可能全都淹没在平日的琐事里。只有坐在桌前,集中注意力努力围绕某些事深入思考时,才真正接近事物的本质。书写过程可以看做是思维的提炼,完完全全dive into it,然后提炼出有所价值的东西。在这里,价值的意义也许没有那么大,但积少成多,对于个人来说未尝不是份值得珍藏和回味的记忆。 12 | 13 | 以后写的东西,或许偏理论,或许偏实践又或许是偏向于生活,我相信,都会是自己思考所得。 14 | 15 | 这只是个开始...... 16 | 17 | 一旦开始,便没有终点...... -------------------------------------------------------------------------------- /resources/unpublished-md/给自己放一天假~.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | 3 | # 给自己放一天假~ 4 |
2014-03-16 10:41:00
5 |
2014-03-16 10:41:00
6 |
26
7 |
Picture,Life
8 | 9 | ![](http://ontheroad.qiniudn.com/20140316_1.png) 10 | ![](http://ontheroad.qiniudn.com/20140316_2.png) 11 | ![](http://ontheroad.qiniudn.com/20140316_3.png) 12 | ![](http://ontheroad.qiniudn.com/20140316_4.png) 13 | ![](http://ontheroad.qiniudn.com/20140316_5.png) 14 | ![](http://ontheroad.qiniudn.com/20140316_6.png) 15 | ![](http://ontheroad.qiniudn.com/20140316_7.png) 16 | ![](http://ontheroad.qiniudn.com/20140316_8.png) 17 | ![](http://ontheroad.qiniudn.com/20140316_9.png) 18 | ![](http://ontheroad.qiniudn.com/20140316_10.png) 19 | ![](http://ontheroad.qiniudn.com/20140316_11.png) 20 | ![](http://ontheroad.qiniudn.com/20140316_12.png) 21 | ![](http://ontheroad.qiniudn.com/20140316_13.png) 22 | 23 | 给自己放一天假,整理下心情~~~ -------------------------------------------------------------------------------- /resources/templates/disqus.html: -------------------------------------------------------------------------------- 1 |
2 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Tian Jun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/blog_clj/handler.clj: -------------------------------------------------------------------------------- 1 | (ns blog-clj.handler 2 | (:require [compojure.core :refer :all] 3 | [compojure.route :as route] 4 | [ring.middleware.defaults :refer [wrap-defaults site-defaults]] 5 | [blog-clj.page-generators :refer [gen-blog-page 6 | gen-blogtag-search-page 7 | gen-about-page 8 | gen-rss]] 9 | [blog-clj.parse :refer [page-404]] 10 | [blog-clj.webhooks :refer [sync-hook]])) 11 | 12 | (defroutes app-routes 13 | (GET "/" [] (gen-blog-page "/essays/")) 14 | (GET "/about/" [] (gen-about-page)) 15 | (GET "/essays/:blog-id" [blog-id :as {uri :uri}] (gen-blog-page blog-id uri)) 16 | (GET "/essays/:blog-id/" [blog-id :as {uri :uri}] (gen-blog-page blog-id uri)) 17 | (GET "/search/" [tag-type tag] (gen-blogtag-search-page tag tag-type)) 18 | (GET "/rss/" [] (gen-rss)) 19 | (POST "/github-webhooks/" req (sync-hook req)) 20 | (route/resources "/") 21 | (route/not-found (page-404))) 22 | 23 | (def app 24 | (-> app-routes 25 | (wrap-defaults (assoc-in site-defaults [:security :anti-forgery] false)))) 26 | -------------------------------------------------------------------------------- /resources/unpublished-md/Optional in Java 8.md: -------------------------------------------------------------------------------- 1 | 6 | [toc] 7 | 8 |
Java
9 | 10 | # Java 8 中的Optional 11 | 12 | 最近在公司的一些内部服务中,主要都在用Java8开发,各种数据流的操作非常方便,虽然还赶不上clojure,但是也很不错了。在公司内部做服务的开发会有些头疼的问题,比如协作和代码审查。这里就聊聊协作中的一个细节:Optional。 13 | 14 | 因为类的主体是自己设计的,中间一些参数传递的都是Optional类型,协作的时候,对方很困惑,为什么需要Optional呢?好处是啥?我说,“预防空指针的问题呀~”。然后对方默默地写了一行代码:``long x = getFinishTime().orElse(null)``,然后欢快地用``if(x == null){...}else{...}``继续写代码去了。这个时候我就在思考另外一个问题:如果一个这样简单的概念都很难让大家广泛接受,那Haskell中那些复杂的特性又该如何可持续发展呢? 15 | 16 | ## 如何理解? 17 | 18 | Optional的API介绍有很多,这里不重复介绍。我自己将其主要分为3类: 19 | 20 | 1. initial(``empty``,``of``, ``ofNullable``) 21 | 2. transform (``filter``, ``flatmap``, ``get``, ``isPresent``, ``map``, ``orElse``, ``orElseGet``, ``orElseThrow``) 22 | 3. action (``ifPresent``) 23 | 24 | 初看可能觉得,transform和action只是些语法上的简便处理,我实际使用中最大的体会有两点,一是真的大大减少了空指针的异常,毕竟传递一个Optional变量的时候,就好像变量自己会说话,“Hey,注意检查我哦~”,通过合理地使用Optional变量,可以很方便地定位一些本不应该出现空指针的问题,这在协作的时候很方便;此外,Optional变量能够很好地嵌入到stream中(虽然还有一点点不太方便的地方,Java9中有改进),使得整体的代码更简洁,可阅读性更高。 25 | 26 | 那有么有啥技巧呢? 27 | 28 | ## 减少``get``的使用! 29 | 30 | 显然,如果频繁使用``get``方法,那就意味着频繁使用``isPresent``,结果就是回到原来``if...else``的老路上了。可以参考[Java 8 Optional – Replace your get() calls](https://reversecoding.net/java-8-optional-replace-get-examples/)理解如何做代码替换。当然,有一点需要注意,不必强制将所有get都替换掉,Java8的一些函数式方法不是特别完善,有时候灵活处理下反而更方便。 31 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject blog-clj "0.1.0-SNAPSHOT" 2 | :license {:name "MIT License" 3 | :url "https://opensource.org/licenses/MIT" 4 | :author "Tian Jun" 5 | :email "tianjun@tianjun.me" 6 | :year 2016 7 | :key "mit"} 8 | :description "The source code of my blog" 9 | :url "http://tianjun.me" 10 | :min-lein-version "2.0.0" 11 | :dependencies [[org.clojure/clojure "1.8.0"] 12 | [compojure "1.5.1"] 13 | [ring/ring-defaults "0.2.1"] 14 | [com.taoensso/carmine "2.14.0"] 15 | [enlive "1.1.6"] 16 | [clj.qiniu "0.1.2"] 17 | [jarohen/chime "0.1.9"] 18 | [clj-time "0.12.0"] 19 | [hickory "0.6.0"] 20 | [clj-rss "0.2.3"] 21 | [com.taoensso/timbre "4.7.4"] 22 | [ring/ring-json "0.4.0"] 23 | [pandect "0.6.0"] 24 | [org.clojure/data.json "0.2.6"] 25 | [environ "1.1.0"] 26 | ] 27 | :plugins [[lein-ring "0.9.7"] 28 | [lein-environ "1.1.0"]] 29 | :ring {:handler blog-clj.handler/app 30 | :init blog-clj.schedules/start 31 | :destroy blog-clj.schedules/stop} 32 | :profiles 33 | {:dev {:dependencies [[javax.servlet/servlet-api "2.5"] 34 | [ring/ring-mock "0.3.0"] 35 | [midje "1.6.3"]]}}) 36 | -------------------------------------------------------------------------------- /resources/published-html/这只是个开始.......html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 这只是个开始...... 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | 这只是个开始...... 18 |
  • 19 |
20 | 21 | 22 |

这只是个开始......

23 | 24 |
Life
25 | 26 |

国庆假期结束,来这儿也一个多月了,慢慢适应着这里的节奏,不紧不慢。走在路上的时候,难免会觉得学校里太过冷清,相比之下,中南的本部算是热闹的了,这阵子应该是招聘会的旺季。不过冷清也未尝不是件好事,充足的时间和空间让人更加专注于当下的学习。

27 | 28 |

也许是太过自信,本以为自己对环境的适应力算是可以的,没想开学后直到这个假期才找到点状态。一直想到这儿来更新下,无奈,各种琐事实在让人难以静下来思考,而且,这期间一直在想整个网站的写作风格应该是怎样的。我希望做一件事就一直坚持下去,当然,前提是在开始这件事的时候就给自己一个坚定的信念,这件事是值得的。

29 | 30 |

《为什么你应该(从现在开始就)写博客 》一文对我的影响比较大,一开始写blog可能就只是一个强迫自己去思考的过程,平时的思维是极不稳定的,走在路上时的灵机一动,或者是听课时天马行空般的想像很可能全都淹没在平日的琐事里。只有坐在桌前,集中注意力努力围绕某些事深入思考时,才真正接近事物的本质。书写过程可以看做是思维的提炼,完完全全dive into it,然后提炼出有所价值的东西。在这里,价值的意义也许没有那么大,但积少成多,对于个人来说未尝不是份值得珍藏和回味的记忆。

31 | 32 |

以后写的东西,或许偏理论,或许偏实践又或许是偏向于生活,我相信,都会是自己思考所得。

33 | 34 |

这只是个开始......

35 | 36 |

一旦开始,便没有终点......

37 | 38 | 39 | 40 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /resources/templates/about.html: -------------------------------------------------------------------------------- 1 |
2 |

About Me

3 |

我是田俊,90s,2013年毕业于中南大学自动化专业,2016年毕业于中科院自动化所,目前在滴滴做些算法方面的工作。

4 |

比较喜欢New Age, Post Rock风格的音乐。

5 |

感兴趣的研究方向包括:Machine Learning, Data Mining, Deep Learning, Natural Language Processing等。

6 |

日常编程主要用Python和Java,略懂Clojure和Golang,偶尔改改C/C++代码。

7 | 8 |

About This Site

9 | 10 |

想在这里写些东西,留下属于自己的回忆

11 | 12 |

阅读,思考,生活......

13 | 14 |

我会(尽量)坚持每周在这里更新,努力做些事情,改变些东西

15 | 16 |

Contact Me

17 |

E-mail: tianjun@tianjun.me

18 |

19 | 20 |
21 | 39 | 40 | 41 |
42 | -------------------------------------------------------------------------------- /resources/published-html/给自己放一天假~.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 给自己放一天假~ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | 给自己放一天假~ 18 |
  • 19 |
20 | 21 | 22 |

给自己放一天假~

23 | 24 |
Picture,Life
25 | 26 |

27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |

39 | 40 |

给自己放一天假,整理下心情~~~

41 | 42 | 43 | 44 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /resources/unpublished-md/一张图说cuda.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # 一张图说cuda 3 |
2014-05-13 09:35:00
4 |
2014-05-13 09:35:00
5 |
29
6 |
Cuda
7 | [![cuda.jpg](http://ontheroad.qiniudn.com/20140513-2.jpg)](http://ontheroad.qiniudn.com/20140513.jpg) 8 | 9 | ([点击看大图](http://ontheroad.qiniudn.com/20140513.jpg)) 10 | 11 | 没去上几次高性能计算的课,有不完善的地方还请见谅。赶在明天考试之前复习了下课件,加上自己的一些理解随手画了画。 12 | 13 | 说说自己对并行计算的一点浅显认识。以前一直以为,并行计算嘛,就是fork出进程然后各干各的,最后汇总下结果。但发现实际中并行计算往往并非是完全独立的,相反,各个进程之间往往需要各种同步和交流机制。这在一定程度上对编程能力提出了巨大挑战,一方面需要对任务进行分块,另一方面需要自己控制好同步和交流的节奏(弄不好就出现计算结果飘忽不定的情况)。也就是说,原来在串行程序中根本不会出现的资源读写问题,到了并行程序里可能就会成为大问题。许多事情都需要自己手动控制好,就算再有耐心的程序猿看到了也会有些抓狂。只能寄希望以后某一天编译器可以变得足够智能,把苦逼的程序猿解放出来。另外不得不说,pycuda的封装做得很好,只是我没比较过性能上的差异。 14 | 15 | 为了写大作业,我不得不重拾很久都没碰过的c语言。嗯,指针真是个好东西,有时候真是无比怀念啊......不过,数组越界的问题,完全靠自觉了......唉,凡事都是有得有失。不过我觉得自己应该不会再用c来并行计算了,如果可以,我还是更愿意用一些高层的语言来写,尽管性能上可能会有些损失,但是,相比调试上所花的时间精力来说,值了。(我能说我花了一个多星期来调试大作业么...最后真的是把并行程序和串行程序对照着比较每一次的中间结果。。。然后发现是个相当无厘头的bug!人生啦,就这么毁在bug上了。。。) 16 | 17 | 不过,抛开这些底层的细节。借用老师在课上的一句话来说,并行编程的确是一门艺术活。真正能体现思考的魅力。至少我在编程的时候,脑海里走的是这么个流程。 18 | 19 | - 首先,在脑海里虚拟出成千上万个脑细胞,把每个脑细胞想象成一个thread 20 | 21 | - 再把任务分配到每个脑细胞,并且将其分组group成一个个block 22 | 23 | - 将思绪按照时间维发散下去,read,write,sync, communicate...... 24 | 25 | - 然后,从每个group里挑选出个老大,负责最后信息的汇总 26 | 27 | - 最后,唉,又耗费了好多脑细胞。。。清空Memory,重来。。。 28 | 29 | 关于并行编程的一些思考,推荐看看一个系列文章[【1】](http://devblogs.nvidia.com/parallelforall/thinking-parallel-part-i-collision-detection-gpu/)、[【2】](http://devblogs.nvidia.com/parallelforall/thinking-parallel-part-ii-tree-traversal-gpu/)、[【3】](http://devblogs.nvidia.com/parallelforall/thinking-parallel-part-iii-tree-construction-gpu/),文中关于为什么要学习并行编程,以及并行编程和串行编程的一些差异讲得很不错,而且给出了一些例子。 30 | 31 | 最后,但求明天考试顺利。。。 -------------------------------------------------------------------------------- /resources/unpublished-md/Some Interesting Quil Examples.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | 3 |
Clojure,GIF
4 | 5 | # About Quil 6 | 7 | [Quil][]是Clojure下用来画图的一个库,对[Processing][]做了封装。Processing想必大家都知道,用来画图非常有意思,我第一次接触Processing是在图书馆里看到了[代码本色:用编程模拟自然系统 \[The Nature of Code:Simulating Natural Systems with Processing\]](https://book.douban.com/subject/26264736/)。不过只是大致翻了翻,有个基本的印象,最近碰巧想到用clojure来画图,于是找到了quil这个库。Processing这个库本身是用java写的(现在已经有很多语言的扩展了),因此用clojure写起来相当方便,而且得益于clojure中许多函数式编程的思想,写代码的感觉非常顺畅!以后深入了解下二者后写个详细的对比分析。本文主要是记录下平时写的一些比较有意思的动图。 8 | 9 | [Quil]:http://quil.info/ 10 | [Processing]:https://processing.org/ 11 | 12 | # Lissajous 曲线的动画演示 13 | 14 | 第一次看到Lissajous曲线是从[Matrix67](http://www.matrix67.com/blog/archives/6947)的文章里看到的,这类曲线还是蛮有意思的,包括Quil官网上也有几个类似的例子,这里我也用Quil画了一个图~ 15 | 16 | ![Lissajous.gif](../public/Lissajous.gif) 17 | 18 | 代码如下: 19 | 20 | ```clojure 21 | (ns hello-quil.core 22 | (:require [quil.core :as q] 23 | [quil.middleware :as m])) 24 | 25 | (defn setup [] 26 | (q/frame-rate 30) 27 | (q/background 255) 28 | (q/color-mode :hsb 10 1 1)) 29 | 30 | (defn f [t] 31 | [(* 200 (q/sin (* t 13))) (* 200 (q/sin (* t 18))) ]) 32 | 33 | (defn draw-plot [f from to step] 34 | (doseq [two-points (->> (range from to step) 35 | (map f) 36 | (partition 2 1))] 37 | (apply q/line two-points))) 38 | 39 | (defn draw [] 40 | (q/with-translation [(/ (q/width) 2) (/ (q/height) 2)] 41 | (let [t (/ (q/frame-count) 80)] 42 | (q/stroke 1 1 1) 43 | (q/line (f t) 44 | (f (+ t (/ 1 80)))) 45 | (q/save-frame "./data/Lissajous-####.png")))) 46 | 47 | (q/defsketch trigonometry 48 | :size [500 500] 49 | :draw draw 50 | :setup setup) 51 | 52 | ``` 53 | 54 | # -------------------------------------------------------------------------------- /resources/unpublished-md/The Gambler's Ruin Problem.md: -------------------------------------------------------------------------------- 1 | 6 | [toc] 7 | 8 |
Probability
9 | 10 | # The Gambler's Ruin Problem 11 | 12 | 最近在看[概率统计](https://book.douban.com/subject/10827481/)和[Explorations in Monte Carlo Methods](https://book.douban.com/subject/4236768/)这两本书的时候,都有看到这个例子,单独拎出来记录下。 13 | 14 | ## 问题描述 15 | 16 | 假设A、B两人一起玩一个游戏,A获胜的概率为$p$,B获胜的概率为$1-p$,A手上有$i$块钱,B手上有$k-i$块钱(即两人的总和为$k$),每次输的一方要给对方1块钱。重复该游戏直到其中一方破产,求最终A剩余的钱为$k$的概率。 17 | 18 | 我们可以将该过程抽象出来:假设A是坐标轴上位于$i$处的一点,每次随机向左或向右移动单位长,向右移动的概率为$p$,向左移动的概率为$1-p$,重复该过程,直到A运动到原点或者位置$k$时结束,那么最终该点位于位置$k$的概率为多少? 19 | 20 | ![](../public/GRP-1.png) 21 | 22 | ## 问题分析 23 | 24 | 这里将A最终位于点$k$记为事件$W$,直观上看,假如$p=1/2$,那么$k-i$相对于$i-0$越大,那么$P(W)$也就越小。由于A每次只能移动一步,于是,该问题可以看做是动态规划的问题。 25 | 26 | 记每次移动后A的位置为$a$,于是有: 27 | 28 | $$ 29 | \begin{equation} 30 | \mathrm{P}(W|a=i)=p \mathrm{P}(W|a=i+1) + (1-p) \mathrm{P}(W|a=i-1) 31 | \end{equation} 32 | $$ 33 | 34 | 为了方便,记$\mathrm{P}(W|a=i)$为$a_i$,于是上式可以写成如下通项公式: 35 | 36 | $$\begin{equation} 37 | (1-p)(a_{i} - a_{i-1})=p(a_{i+1} - a_{i}) 38 | \end{equation} 39 | $$ 40 | 41 | 然后,根据边界条件$a_0 = 0$和$a_k = 1$可以得到以下式: 42 | 43 | $$ 44 | \begin{equation} 45 | \begin{split} 46 | a_2 - a_1 &= \frac{1-p}{p} a_1 \\ 47 | a_3 - a_2 &= \frac{1-p}{p} (a_2 - a_1) \; &= \left(\frac{1-p}{p}\right)^2 a_1 \\ 48 | \vdots \\ 49 | a_{k-1} - a_{k-2} &= \frac{1-p}{p} (a_{k-2} - a_{k-3}) \; &= \left(\frac{1-p}{p}\right)^{k-2} a_1 \\ 50 | 1 - a_{k-1} &= \frac{1-p}{p} (a_{k-1} - a_{k-2}) \; &= \left(\frac{1-p}{p}\right)^{k-1} a_1 \\ 51 | \end{split} 52 | \end{equation} 53 | $$ 54 | 55 | 求和之后可以得到: 56 | $$ 57 | \begin{equation} 58 | 1 - a_1 = a_1 \sum_{i=1}^{k-1} \left( \frac{1-p}{p} \right) ^ i 59 | \end{equation} 60 | $$ 61 | 62 | ## 讨论 63 | 64 | -------------------------------------------------------------------------------- /src/blog_clj/webhooks.clj: -------------------------------------------------------------------------------- 1 | (ns blog-clj.webhooks 2 | (:require [pandect.algo.sha1 :refer [sha1-hmac]] 3 | [environ.core :refer [env]] 4 | [ring.util.response :refer [status response]] 5 | [clojure.java.shell :refer [sh]] 6 | [blog-clj.sync :refer [sync-blogs]] 7 | [clojure.data.json :as json] 8 | [clojure.string :as string] 9 | [clojure.set :refer [union difference]])) 10 | 11 | (defn get-file-changes 12 | [push-payload] 13 | (let [payload (json/read-str push-payload :key-fn keyword) 14 | commits (:commits payload) 15 | is-publish? #(string/starts-with? % "resources/published-html/") 16 | trim-prefix #(string/replace % "resources/published-html/" "") 17 | get-changed-files (fn [change-type] 18 | (map trim-prefix 19 | (filter is-publish? 20 | (apply union 21 | (map #(set (change-type %)) 22 | commits))))) 23 | [added removed modified] (map #(set (get-changed-files %)) [:added :removed :modified]) 24 | real-added (difference added removed) 25 | real-removed (difference removed added) 26 | real-modified (difference modified added removed)] 27 | [real-added real-removed real-modified])) 28 | 29 | (defn sync-hook 30 | [req] 31 | (let [event-type (get (:headers req) "x-github-event") 32 | signature (get (:headers req) "x-hub-signature") 33 | post-body (slurp (:body req)) 34 | hook-key (env :github-webhook-secret) 35 | hmac (str "sha1=" (sha1-hmac post-body hook-key))] 36 | (cond 37 | (and (= hmac signature) 38 | (= event-type "push")) 39 | (do 40 | (sh "git" "pull" :dir (env :html-path)) 41 | (apply sync-blogs (get-file-changes post-body)) 42 | "A push event received!") 43 | (and (= hmac signature) 44 | (not= event-type "push")) 45 | (format "Event %s received!" event-type) 46 | :else (status (response "Fake POST") 403)))) 47 | -------------------------------------------------------------------------------- /resources/unpublished-md/理解Clojure中Buddy认证模块.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # 理解Clojure中Buddy认证模块 3 |
2015-11-13 18:34:40
4 |
2015-11-13 17:32:24
5 |
63
6 |
Web,Clojure
7 | 有关加密解密的知识一直是自己的盲点,最近在看如何用用clojure写网站,顺带学习了下cookie和session相关的知识,在这里介绍下Buddy这个库,并总结下自己的理解,不对的地方恳请指正。Buddy提供了基于Ring的一些安全认证相关的接口,下面内容就此展开。 8 | 9 | 先解释几个术语: 10 | 11 | - **Handler**:在Ring中,request和response都可以看做map,handler就是对request的内容分析后返回相应response的函数 12 | 13 | - **Backend**:Buddy模块中每个backend都包含2个部分Authentication和Authorization,其中Authentication包含parse(将需要的认证数据从request中提取出来)和authenticate(根据parse提取的信息判断是否通过认证)两个步骤,Authorization则包含发生认证错误后如何处理该错误并返回相应response的实现。 14 | 15 | 16 | 根据Authentication和Authorization的实现不同,Buddy模块提供了5种实现,下面以最简单的http基本认证为例: 17 | 18 | ##Httpbasic 19 | 20 | Httpbasic的处理逻辑如下图所示: 21 | 22 | ![test.png_c3821b28623e8949810b26bf23a414d4](http://ontheroad.qiniudn.com/blog/resources/test.png_c3821b28623e8949810b26bf23a414d4/w660) 23 | 24 | Httpbasic方式的缺点参见[这里](http://security.stackexchange.com/questions/67427/what-are-the-disadvantages-of-implementing-http-authentication-in-a-web-applicat): 25 | 26 | 1. 密码采用base64编码,很容易反向转换; 27 | 28 | 2. 每次请求都要传输密码(增加了攻击概率; 29 | 30 | 3. 密码缓存在本地浏览器,容易通过CSRF窃取; 31 | 32 | 采用https协议的话,仅仅能解决第一点问题。 33 | 34 | ##Session 35 | 36 | 该方案舍弃了httpbasic中传输username passwd的步骤,把验证和授权独立开来,授权放在login界面逻辑里去处理,将request中的:identity字段作为验证结果。而验证部分则只考虑是否存在:identity字段。 37 | 38 | ##Token 39 | 40 | 基于Token的方法则是将原来Httpbasic处理过程里,request的header中Authorization内容改为了token,从而避免直接存储用户名和密码,然后服务器端存储token和用户的对应关系。 41 | 42 | ##JWS Token 43 | 44 | 由于上面的token需要保存在服务器端,在用户量很大的时候,token的存储压力会很大,JWS token的用途就是将用户名密码加入签名后写进header的Authorization中,这样服务器端并不需要存储token到username的映射关系,只需要对token解码即可。这样做的好处是,不像Httpbasic简单采用base64对用户名密码编码,签名后的token很难破解得到原始的用户名密码信息。 45 | 46 | ##JWE Token 47 | 48 | JWE Token处理的过程和JWS Token很像,区别在于引入非对称加密,将一部分敏感信息用公钥加密,服务端用私钥解密。 49 | 50 | ##参考 51 | 52 | 关于无状态认证的两篇讨论,里面提到了如何用python实现: 53 | 54 | [http://lucumr.pocoo.org/2013/11/17/my-favorite-database/](http://lucumr.pocoo.org/2013/11/17/my-favorite-database/) 55 | 56 | [http://www.niwi.nz/2014/06/07/stateless-authentication-with-api-rest/](http://www.niwi.nz/2014/06/07/stateless-authentication-with-api-rest/) 57 | 58 | -------------------------------------------------------------------------------- /resources/published-html/一张图说cuda.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 一张图说cuda 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | 一张图说cuda 18 |
  • 19 |
20 | 21 | 22 |

一张图说cuda

23 | 24 |
Cuda
25 | 26 |

cuda.jpg

27 | 28 |

(点击看大图)

29 | 30 |

没去上几次高性能计算的课,有不完善的地方还请见谅。赶在明天考试之前复习了下课件,加上自己的一些理解随手画了画。

31 | 32 |

说说自己对并行计算的一点浅显认识。以前一直以为,并行计算嘛,就是fork出进程然后各干各的,最后汇总下结果。但发现实际中并行计算往往并非是完全独立的,相反,各个进程之间往往需要各种同步和交流机制。这在一定程度上对编程能力提出了巨大挑战,一方面需要对任务进行分块,另一方面需要自己控制好同步和交流的节奏(弄不好就出现计算结果飘忽不定的情况)。也就是说,原来在串行程序中根本不会出现的资源读写问题,到了并行程序里可能就会成为大问题。许多事情都需要自己手动控制好,就算再有耐心的程序猿看到了也会有些抓狂。只能寄希望以后某一天编译器可以变得足够智能,把苦逼的程序猿解放出来。另外不得不说,pycuda的封装做得很好,只是我没比较过性能上的差异。

33 | 34 |

为了写大作业,我不得不重拾很久都没碰过的c语言。嗯,指针真是个好东西,有时候真是无比怀念啊......不过,数组越界的问题,完全靠自觉了......唉,凡事都是有得有失。不过我觉得自己应该不会再用c来并行计算了,如果可以,我还是更愿意用一些高层的语言来写,尽管性能上可能会有些损失,但是,相比调试上所花的时间精力来说,值了。(我能说我花了一个多星期来调试大作业么...最后真的是把并行程序和串行程序对照着比较每一次的中间结果。。。然后发现是个相当无厘头的bug!人生啦,就这么毁在bug上了。。。)

35 | 36 |

不过,抛开这些底层的细节。借用老师在课上的一句话来说,并行编程的确是一门艺术活。真正能体现思考的魅力。至少我在编程的时候,脑海里走的是这么个流程。

37 | 38 |
    39 |
  • 首先,在脑海里虚拟出成千上万个脑细胞,把每个脑细胞想象成一个thread

  • 40 |
  • 再把任务分配到每个脑细胞,并且将其分组group成一个个block

  • 41 |
  • 将思绪按照时间维发散下去,read,write,sync, communicate......

  • 42 |
  • 然后,从每个group里挑选出个老大,负责最后信息的汇总

  • 43 |
  • 最后,唉,又耗费了好多脑细胞。。。清空Memory,重来。。。

  • 44 |
45 | 46 |

关于并行编程的一些思考,推荐看看一个系列文章【1】、【2】、【3】,文中关于为什么要学习并行编程,以及并行编程和串行编程的一些差异讲得很不错,而且给出了一些例子。

47 | 48 |

最后,但求明天考试顺利。。。

49 | 50 | 51 | 52 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /resources/unpublished-md/The EM Algorithm.md: -------------------------------------------------------------------------------- 1 | 6 | [toc] 7 | 8 |
Algorithm
9 | 10 | # EM算法 11 | 12 | 偶然看到一篇[文章](http://www.nature.com/nbt/journal/v26/n8/full/nbt1406.html)讲EM算法,感觉讲得很清晰。文中用到了一个抛硬币的问题,这里“搬运”过来,重新阐述下。 13 | 14 | ## 问题 15 | 16 | 假设现在有两枚硬币,随机抛掷之后,正面朝上的概率分别设为$\theta_A$和$\theta_B$,然后我们做了5轮实验,每轮随机选一枚硬币(记为$z_i$),抛10次并记录结果(记为$\boldsymbol{x}_i$),现在我们希望估计出$\theta_A$和$\theta_B$的值。 17 | 18 | ## 最大似然 19 | 20 | 假设$z_i$是已知的,那么只需最大化对数似然$\log (x, z; \theta)$即可,根据采样独立性假设,将log似然对$\theta$求导之后,可以得到$\theta$的点估计: 21 | 22 | $$ 23 | \begin{equation} 24 | \hat {\theta_A} = \frac {选择硬币A时正面朝上的次数} {选择硬币A的次数} 25 | \end{equation} 26 | $$ 27 | 28 | ## EM 29 | 30 | 假如隐变量$z$不可知,那么就无法通过上面的方法求解了。不过,我们可以先随机设置$\theta$的一组取值,然后根据观测数据$\boldsymbol{x}$“猜测”隐变量的分布$z$,再利用该隐变量的估计值和观测数据$\boldsymbol{x}$,按照前面最大似然的做法去计算$\theta$的值,如此迭代直至$\theta$收敛。 31 | 32 | 需要注意的是,这里“猜测”的是$z$的分布,而不是具体的某个值,联系到K-means算法中,通常的做法是将每个中心点做替换,这里相当于只是做“软分类”。 33 | 34 | 具体流程见原图: 35 | 36 | ![](http://www.nature.com/nbt/journal/v26/n8/images/nbt1406-F1.gif) 37 | 38 | 下面我用Python代码将图中的流程复现下: 39 | 40 | ```python 41 | import numpy as np 42 | from scipy.stats import binom 43 | 44 | observed = """ 45 | HTTTHHTHTH 46 | HHHHTHHHHH 47 | HTHHHHHTHH 48 | HTHTTTHHTT 49 | THHHTHHHTH""" 50 | 51 | x = np.array([[1 if c=="H" else 0 for c in line] for line in observed.strip().split()]) 52 | x_heads = x.sum(axis=1) 53 | x_tails = 10 - x.sum(axis=1) 54 | assert np.all(x_heads == np.array([5,9,8,4,7])) 55 | 56 | def calc_z(theta): 57 | za_est = binom.pmf(x_heads, 10, theta[0]) 58 | zb_est = binom.pmf(x_heads, 10, theta[1]) 59 | return za_est / (za_est + zb_est), zb_est / (za_est + zb_est) 60 | 61 | def calc_theta(z): 62 | h_A = (x_heads * z[0]).sum() 63 | t_A = (x_tails * z[0]).sum() 64 | h_B = (x_heads * z[1]).sum() 65 | t_B = (x_tails * z[1]).sum() 66 | return h_A / (h_A + t_A), h_B / (h_B + t_B) 67 | 68 | def is_nearly_same(theta_pre, theta_cur): 69 | return np.abs(sum(theta_pre) - sum(theta_cur)) < 1e-5 70 | 71 | theta_pre = [0.6, 0.5] 72 | 73 | for i in range(1000): 74 | theta_cur = calc_theta(calc_z(theta_pre)) 75 | if is_nearly_same(theta_pre, theta_cur): 76 | print i, theta_cur 77 | break 78 | else: 79 | theta_pre = theta_cur 80 | # 11 (0.7967829009034072, 0.51959543422720311) 81 | ``` 82 | 83 | 显然,EM只能找到局部最优解,可以通过设置多个随机起始点来缓解这个问题,原文对这个问题有讨论。 84 | 85 | 此外,一点个人感受,这里随机初始点有点像先验,然后通过似然更新,得到一次后验估计(即先验和最大似然的中和),如此迭代,有意思。 -------------------------------------------------------------------------------- /resources/public/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+bash+c+cpp+elixir+erlang+go+haskell+java+julia+latex+markdown+ocaml+python+scala+scheme */ 2 | /** 3 | * okaidia theme for JavaScript, CSS and HTML 4 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 5 | * @author ocodia 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: #f8f8f2; 11 | background: none; 12 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | word-wrap: normal; 19 | line-height: 1.5; 20 | 21 | -moz-tab-size: 4; 22 | -o-tab-size: 4; 23 | tab-size: 4; 24 | 25 | -webkit-hyphens: none; 26 | -moz-hyphens: none; 27 | -ms-hyphens: none; 28 | hyphens: none; 29 | } 30 | 31 | /* Code blocks */ 32 | pre[class*="language-"] { 33 | padding: 1em; 34 | margin: .5em 0; 35 | overflow: auto; 36 | border-radius: 0.3em; 37 | } 38 | 39 | :not(pre) > code[class*="language-"], 40 | pre[class*="language-"] { 41 | background: #272822; 42 | } 43 | 44 | /* Inline code */ 45 | :not(pre) > code[class*="language-"] { 46 | padding: .1em; 47 | border-radius: .3em; 48 | white-space: normal; 49 | } 50 | 51 | .token.comment, 52 | .token.prolog, 53 | .token.doctype, 54 | .token.cdata { 55 | color: slategray; 56 | } 57 | 58 | .token.punctuation { 59 | color: #f8f8f2; 60 | } 61 | 62 | .namespace { 63 | opacity: .7; 64 | } 65 | 66 | .token.property, 67 | .token.tag, 68 | .token.constant, 69 | .token.symbol, 70 | .token.deleted { 71 | color: #f92672; 72 | } 73 | 74 | .token.boolean, 75 | .token.number { 76 | color: #ae81ff; 77 | } 78 | 79 | .token.selector, 80 | .token.attr-name, 81 | .token.string, 82 | .token.char, 83 | .token.builtin, 84 | .token.inserted { 85 | color: #a6e22e; 86 | } 87 | 88 | .token.operator, 89 | .token.entity, 90 | .token.url, 91 | .language-css .token.string, 92 | .style .token.string, 93 | .token.variable { 94 | color: #f8f8f2; 95 | } 96 | 97 | .token.atrule, 98 | .token.attr-value, 99 | .token.function { 100 | color: #e6db74; 101 | } 102 | 103 | .token.keyword { 104 | color: #66d9ef; 105 | } 106 | 107 | .token.regex, 108 | .token.important { 109 | color: #fd971f; 110 | } 111 | 112 | .token.important, 113 | .token.bold { 114 | font-weight: bold; 115 | } 116 | .token.italic { 117 | font-style: italic; 118 | } 119 | 120 | .token.entity { 121 | cursor: help; 122 | } 123 | 124 | -------------------------------------------------------------------------------- /resources/unpublished-md/一个微博爬虫的设计原型.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # 一个微博爬虫的设计原型 3 |
2014-10-28 07:01:00
4 |
2014-10-28 07:01:00
5 |
33
6 |
Spider
7 | ##梳理下 8 | 上个月去了趟南京,回来后再加上个国庆假期,整个人就闲下来了.每天,也没太多事情可做,有时候,很享受这样的日子,呵.闲下来的时候,总想找点事做来打发打发时间. 9 | 10 | 目前的兴趣点在网络和图论这块,再加上之前接触了一下图数据库neo4j,感觉得这货以后很有潜力,于是乎,希望能够找个实际点的东西来练练手,然后,很短视地想到适合用图数据库的地方--社交网络.其实也没多想,就敲定了新浪微博这个平台(别问我为啥偏偏是微博). 11 | 12 | 在这里完整记录下自己考虑问题的整个过程(虽然暂时搁置下来了),希望以后考虑其他问题的时候,能从中有所借鉴吧. 13 | 14 | ##目的 15 | 16 | 一开始希望做的事情是,能够用图数据库neo4j来存储一些社交网络上的数据,这样以后分析起来效率会高很多,也算是搭建个平台起来吧. 17 | 18 | ##设计 19 | 20 | 做爬虫这种事,必然有很多人已经做过了,所以,最重要的是前期调研,然后,找到了知乎上点赞最多的[这个](http://www.zhihu.com/question/20899988)回答,总的来说,很有收获.所以,自己后面数据流的设计基本是参照这个来的.为了后面阐述方便,先看[大图](http://ontheroad.qiniudn.com/20141020_122432.jpeg): 21 | 22 | ![](http://ontheroad.qiniudn.com/20141020_122432.jpeg?imageView2/2/w/660) 23 | 24 | **注**:上图有个问题,实际上不应该使用Queue,而是使用set或者order set的结构来存储 25 | 26 | 可能大多数人做爬虫只是简单的上来先写好网络接口,配置好存储方式就开始了.不过,在这里,我是希望整个过程能够有一个良好的可扩展性.因此,需要考虑的事情会更多. 27 | 28 | 首先,明确两条主线.**时间流**和**数据流**. 29 | 30 | 先说**数据流**这里采用自顶向下的思考方式.首先来考虑数据在图数据库中怎么存储的,假设现在有了1kw的用户及其微博(平均每人提取300条),那么该如何在图数据中表示这张超大的关系网呢?图数据库中基本的元素是点和边,自然一种最简单的方式是,以用户为node,以用户和用户之间的关系为edge来构造这个图.初看起来似乎完全没有问题,但是这里把许多细节高度抽象化了,还有很多细节需要考虑,比如: 31 | 32 | 1. 微博内容存在哪?(这个可以存在用户节点内,以json格式组织,问题不大) 33 | 2. 如何体现用户与用户之间关系(edge)的差异性?(比如,虽然我同时关注了几百号人,但是我跟其中的某些人关系很熟,经常互动,但是另外一些人可能虽然关注了但很少联系) 34 | 3. 通过微博@用户的关系如何体现?用户对微博的评论,点赞,转发呢???(有个改进的方案,微博还是放在用户节点下,但是通过丰富用户与用户之间关系(edge)的属性(加入评论,转发,@等),强化用户关系之间的描述) 35 | 4. 图数据库的优势体现在哪? 36 | 37 | 上面的分析可以发现,最大的问题在于,微博怎么放.由于微博在这里已经不再是一个简单的文本,而是负担起了网络中连接器的功能,所以有必要将其独立出来作为存储的节点,从而用户与用户之间,用户与微博之间,微博与微博之间的关系更加清晰.那么这样子做是否可行呢?有一个最大的担忧是,将微博作为节点的化,整个数据库的节点数目会接近30亿,那么这就对neo4j的性能提出要求了,neo4j能否容纳这么大量的节点数目呢?经过一番调研,发现是可以做到的,问题是,一台小型机恐怕有点容不下,需要分布式的结构,但是neo4j的社区版不支持分布式安装,还好有个人版.这块先这样.再往下,每个爬虫爬到数据后,通过pipeline将数据扔到数据库里.至于数据去重的部分,放在爬虫的功能设计中去. 38 | 39 | 再从**时间流**的角度考虑如何爬数据.简单的分析可以看出,用户之间的关系网很简单,而微博的关系网比较复杂,需要打开每一个微博的页面单独去爬评论,点赞之类的.这会导致用户数变得不可控,这里说的不可控是指,在整个网络中会产生大量的孤立节点.考虑到后期分析用户之间关系的目的,这些数据会变得根本不可用.因此,设计思路就变成了,首先爬取用户与用户之间完整的关系网,然后,通过微博的内容来完善该关系网.因此,从时间上考虑,可以简单分为以下3步. 40 | 41 | 1. 随机选取种子用户 42 | 2. 爬取用户的关系网络,同时爬取用户的个人信息 43 | 3. 等整个数据库中用户数达到一定规模后,停止爬取用户网络,继续爬取用户的个人信息(这个会比较慢) 44 | 4. 遍历数据库中的用户,读取其微博内容,以及爬取的网络中其他用户对该微博的评论,赞等 45 | 46 | 爬虫需要设计好如何避免重复,这里采用分布式的redis来实现该过程.每次开始爬数据之前,先向redis发起请求获取一批用户id,爬完后返回结果到pipeline,pipeline负责向neo4j保存数据,同时向redis放入下一步应该要爬的用户id. 47 | 48 | 这样子,整个流程就走通了. 49 | 50 | 于是乎,兴高采烈地写代码咯~ 51 | 52 | 先用requests写了个登陆程序,再换成scrapy环境下的,接着写好pipeline,配置好redis和neo4j,这些都很顺畅. 53 | 54 | 可是,刚开始爬了几分钟,挂了... 55 | 56 | WTF!!! 57 | 58 | ip被封了... 59 | 60 | 悲剧的,好吧,用代理, 61 | 62 | 问题来了,代理上哪找,免费的没几个可用, 63 | 64 | 更蛋疼的问题是,异地登陆要验证码...... 65 | 66 | 虽然微博的验证码没那么复杂,但是时间成本略大... 67 | 68 | 总之,这个事情就这么暂时搁置下来了,只能说,what a pity 69 | -------------------------------------------------------------------------------- /resources/unpublished-md/Write Python in Lisp.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | 3 |
Python,Lisp,Hylang
4 | 5 | # Write Python in Lisp 6 | 7 | 上周在Clojure微信群里,[Steve Chan](https://github.com/chanshunli)分享了个关于[Hylang](https://github.com/hylang/hy)的链接,让人眼前一亮,原来Python居然还可以这么写!经过这几天的摸索,意外地感觉不错,在这里推荐给大家试试,有兴趣的话可以看完官网的doc后再回来看下文。 8 | 9 | ## Hylang是什么? 10 | 11 | Hy是基于python的lisp方言,与python的互操作非常顺畅,有点类似clojure于java的关系。从安装上可以看出,Hy实际上就是一个普通的python库,在python代码中可以直接`import hy`之后,把`.hy`文件当做普通的python文件,import其中的变量。核心代码部分,该有的也都有(最重要的当然是macro),可以从clojure无障碍迁移过来。 12 | 13 | 由于是直接把lisp代码转换成AST,开启`--spy`模式之后,可以看到每一行lisp代码转换之后的python代码,各种库的操作也完全没有障碍。试用了一些常用的库,基本没有什么大问题。目前感觉不是够顺畅的地方,反而是一些外围,比如没有很好的编辑环境。社区的vim插件提供的功能很弱,为此我特地入坑spacemacs!emacs对应的插件稍微好点,提供了发送代码到repl的功能,不过最重要的仍然是,没有代码补全,网上有人提供了一些静态的补全方案,通过提取hy库中的关键词和当前buffer中的变量名来补全(没有配置成功......),不过实际使用中会大量调用python库,因此急需像python里的anaconda-mode一类工具提供辅助补全。再比如静态语法检查,调试。 14 | 15 | ## Hylang不是...... 16 | 17 | ### Hylang不是Clojure 18 | 19 | 这个是首先需要意识到的一点。尽管在语法和许多函数上和clojure很像,但是因为底层实现和语言的定位不一样,这其中的许多函数不再是clojure中对应函数的完整复制。以下列举一些很容易碰到的问题: 20 | 21 | 1. muttable & immutable. Hylang本身的定位是鼓励与python的互操作,因此大量的操作都是基于python本身的数据结构,需要非常小心数据随时都可能改变。在写Hylang代码的时候需要时刻提醒自己,“我写的是python代码!代码都会最终转换成python代码去执行!”,社区里最近也在讨论引入immutable的数据结构,不知道这块以后会怎么发展。 22 | 2. lazy. Hylang中大多数代码的lazy实现都是基于generators实现的了iterable,这下就蛋疼了。在python里,生成器访问一次之后,如果你不保存的话,数据就没有了......所以你会发现`(take n coll)`中,如果xs是一个iterable的数据,上面的代码执行多次是可能得到不同结果的。甚至如果不保存的话,没法访问已经被访问过的内容。不过好在0.12.0之后提供了lazy sequences,一定程度上缓解了这个问题。 23 | 3. in-place operations.在python中,许多函数都是默认in-place的,比如`sort`,`random.shuffle`等,有些可能提供了对应的非in-place的函数(如`sorted`),有些则没有。这点需要格外注意,否则,定义变量的时候很可能返回值就是个`None`。不过在`numpy`,`tensorflow`,`pandas`等库中,这点考虑得比较全面 24 | 4. scope. 看github上过去关于`let`绑定的issue,可以深入了解这块内容。在不确定变量名的scope时,可以看看对应的python代码。 25 | 26 | ## 体验过程中的一些坑 27 | 28 | 1. 文件名。写过了clojure的话会习惯`-`作为连接符,`hy`的文件名需要转换成`_`连接符,否则在python代码中不能import。 29 | 2. 某些函数的的实现有bug。我自己在尝试的过程中就发现了`partition`函数的实现有点问题,在github上提了个issue。社区里的反应还是挺快的,第二天就解决并合并到master上了。 30 | 3. 参数传递过程中,运用apply传递positional和named arguments时,需要分别用list和dict对二者封装,不能偷懒直接用一个list搞定。 31 | 32 | ## Hylang适合写点什么? 33 | 34 | 写Hylang也就这几天,对macro的感受还不是很强烈,主要是写了点日常的数据分析代码和tensorflow中的tutorial,以下是一些个人感受: 35 | 36 | - 如果只是写一些调用API的代码,其实不太适合。比如我在翻译tensorflow的tutorial过程中,需要反复去查对应的API,很繁琐,而且已有的框架会在不知不觉中对写lisp风格的代码有一些限制,从而使得python代码更适合命令式地处理逻辑。 37 | - 适合更抽象层的数据预处理逻辑。这块写起来会很舒服,对读代码和写代码的人来说,都是一种享受。可以将二者结合,这部分代码用hy处理后以接口的形式暴露给模型构建部分,最后再用hy糅合train,valid,test的过程。当然,现在某些库(tensorlayer)实际上把直接跟tensorflow打交道的部分做了很浅的一层封装,整体易用性更高了。 38 | 39 | 最后,一点学习经验: 40 | 41 | > 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. -- *Mastering Clojure Macros* 42 | 43 | 另外,这个语言还是太小众了,玩玩就可以了,别太当真...... -------------------------------------------------------------------------------- /src/blog_clj/page_generators.clj: -------------------------------------------------------------------------------- 1 | (ns blog-clj.page-generators 2 | (:require [blog-clj.parse :refer [base-template blogtag-search-template about-template page-404]] 3 | [blog-clj.redis-io :refer [get-all-blogs-titles get-blog get-blogids-with-tag]] 4 | [clojure.set] 5 | [hickory.render :refer [hickory-to-html]] 6 | [clj-rss.core :as rss])) 7 | 8 | (def blogroll 9 | ;; [[url name description] 10 | [["http://laike9m.com/" "laike9m" "左兄写的文档是我等学习的典范~"] 11 | ["http://hujiaweibujidao.github.io/" "Hujiawei" "大牛不解释,望其项背,哈哈~"] 12 | ["http://oilbeater.com/" "Oilbeater" "北京大学操作系统实验室滴葫芦娃~"] 13 | ["http://jacoxu.com/" "jacoxu" "实验室里的师兄哦,更新滴灰常勤快~"] 14 | ["http://qimingyu.com/" "Qi Mingyu" "我高中同学,在做安全相关的工作~"]]) 15 | 16 | (defn- tag-counts 17 | [tag-sets] 18 | (sort-by last > (apply merge-with + (map #(zipmap % (repeat 1)) tag-sets)))) 19 | 20 | (defn gen-blog-page 21 | "generate the blog content page" 22 | ([blog-id blog-url] 23 | (let [blog-detail (get-blog blog-id) 24 | nav-type "/" 25 | archives (map #(take 2 %) (get-all-blogs-titles)) 26 | all-blog-ids (map second archives) 27 | all-blog-tags (tag-counts (map #(:tags (get-blog % :tags)) all-blog-ids))] 28 | (if (some #{blog-id} all-blog-ids) 29 | (base-template (merge blog-detail 30 | {:nav-type nav-type 31 | :archives archives 32 | :all-blog-tags all-blog-tags 33 | :blogroll blogroll 34 | :blog-url blog-url})) 35 | (page-404)))) 36 | ([base-url] 37 | (let [blog-id (-> (get-all-blogs-titles) 38 | first 39 | second)] 40 | (gen-blog-page blog-id (str base-url blog-id))))) 41 | 42 | (defn gen-blogtag-search-page 43 | [tag tag-type] 44 | (let [blog-ids (get-blogids-with-tag tag) 45 | blog-titles (map #(:title (get-blog % :title)) blog-ids) 46 | archives (map #(take 2 %) (get-all-blogs-titles)) 47 | all-blog-ids (map second archives) 48 | all-blog-tags (tag-counts (map #(:tags (get-blog % :tags)) all-blog-ids))] 49 | (blogtag-search-template 50 | tag 51 | (map vector blog-titles blog-ids) 52 | all-blog-tags))) 53 | 54 | (defn gen-about-page 55 | [] 56 | (about-template)) 57 | 58 | (defn gen-rss 59 | [] 60 | (rss/channel-xml 61 | {:title "TianJun MachineLearning" 62 | :link "http://tianjun.me" 63 | :description "在这里,记录些自己的学习、思考与感悟。"} 64 | (for [[title id _] (take 5 (get-all-blogs-titles))] 65 | (let [{:keys [title blog-id body]} (get-blog id :title :blog-id :body)] 66 | {:title title 67 | :description (hickory-to-html body) 68 | :link (str "http://tianjun.me" "/essays/" blog-id)})))) 69 | -------------------------------------------------------------------------------- /resources/public/prism.clojure.js: -------------------------------------------------------------------------------- 1 | Prism.languages.clojure = { 2 | 'comment': /;+[^\r\n]*(\r?\n|$)/g, 3 | 'string': /(")(\\?.)*?\1/g, 4 | 'operator ': /(::|[:|'])\b[a-zA-Z][a-zA-Z0-9*+!-_?]*\b/g, //used for symbols and keywords 5 | 'keyword': { 6 | pattern: /([^\w+*'?-])(def|if|do|let|quote|var|fn|loop|recur|throw|try|monitor-enter|\.|new|set!|def\-|defn|defn\-|defmacro|defmulti|defmethod|defstruct|defonce|declare|definline|definterface|defprotocol|defrecord|deftype|defproject|ns|\*|\+|\-|\->|\/|<|<=|=|==|>|>=|\.\.|accessor|agent|agent-errors|aget|alength|all-ns|alter|and|append-child|apply|array-map|aset|aset-boolean|aset-byte|aset-char|aset-double|aset-float|aset-int|aset-long|aset-short|assert|assoc|await|await-for|bean|binding|bit-and|bit-not|bit-or|bit-shift-left|bit-shift-right|bit-xor|boolean|branch\?|butlast|byte|cast|char|children|class|clear-agent-errors|comment|commute|comp|comparator|complement|concat|conj|cons|constantly|cond|if-not|construct-proxy|contains\?|count|create-ns|create-struct|cycle|dec|deref|difference|disj|dissoc|distinct|doall|doc|dorun|doseq|dosync|dotimes|doto|double|down|drop|drop-while|edit|end\?|ensure|eval|every\?|false\?|ffirst|file-seq|filter|find|find-doc|find-ns|find-var|first|float|flush|for|fnseq|frest|gensym|get-proxy-class|get|hash-map|hash-set|identical\?|identity|if-let|import|in-ns|inc|index|insert-child|insert-left|insert-right|inspect-table|inspect-tree|instance\?|int|interleave|intersection|into|into-array|iterate|join|key|keys|keyword|keyword\?|last|lazy-cat|lazy-cons|left|lefts|line-seq|list\*|list|load|load-file|locking|long|loop|macroexpand|macroexpand-1|make-array|make-node|map|map-invert|map\?|mapcat|max|max-key|memfn|merge|merge-with|meta|min|min-key|name|namespace|neg\?|new|newline|next|nil\?|node|not|not-any\?|not-every\?|not=|ns-imports|ns-interns|ns-map|ns-name|ns-publics|ns-refers|ns-resolve|ns-unmap|nth|nthrest|or|parse|partial|path|peek|pop|pos\?|pr|pr-str|print|print-str|println|println-str|prn|prn-str|project|proxy|proxy-mappings|quot|rand|rand-int|range|re-find|re-groups|re-matcher|re-matches|re-pattern|re-seq|read|read-line|reduce|ref|ref-set|refer|rem|remove|remove-method|remove-ns|rename|rename-keys|repeat|replace|replicate|resolve|rest|resultset-seq|reverse|rfirst|right|rights|root|rrest|rseq|second|select|select-keys|send|send-off|seq|seq-zip|seq\?|set|short|slurp|some|sort|sort-by|sorted-map|sorted-map-by|sorted-set|special-symbol\?|split-at|split-with|str|string\?|struct|struct-map|subs|subvec|symbol|symbol\?|sync|take|take-nth|take-while|test|time|to-array|to-array-2d|tree-seq|true\?|union|up|update-proxy|val|vals|var-get|var-set|var\?|vector|vector-zip|vector\?|when|when-first|when-let|when-not|with-local-vars|with-meta|with-open|with-out-str|xml-seq|xml-zip|zero\?|zipmap|zipper)(?=[^\w+*'?-])/g, 7 | lookbehind: true 8 | }, 9 | 'boolean': /\b(true|false)\b/g, 10 | 'number': /\b-?(0x)?\d*\.?\d+\b/g, 11 | 'punctuation': /[{}\[\](),]/g 12 | }; 13 | -------------------------------------------------------------------------------- /resources/unpublished-md/聊聊最近.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # 聊聊最近 3 |
2015-10-07 16:58:09
4 |
2015-10-07 16:36:29
5 |
61
6 |
Life
7 | 离上次更新有三个多月了吧,这期间乱七八糟的事很多,最近放了个长假,也该静下来了。今天先在这里记录点杂事,接下来尽量多写些有价值的东西。 8 | 9 | 10号域名到期,提前先续费了,懒得折腾域名的转入转出,继续用的namecheap,也就19刀。顺便看了下网站的统计信息,me域名的访问量只占了1/5,远不及ml域名,汗。。。 10 | 11 | 12 | ##学习 13 | 14 | 先说点学习上的。编程语言方面,最近在学Clojure,感觉这门语言学起来蛮好玩的。说起来也有点意思,其实一开始我是准备学习下scala的,跟风嘛,Spark这么火,怎么说也得了解了解。然后翻了几本scala方面的书,没太有感觉。主要是因为这货跟java太像了!然后还夹杂了一大堆语法。虽说写出来的代码相比java已经简洁了许多,但是出于对java的厌恶我学习的动力大大降低。在有java基础之后学习scala的迁移成本其实是很低的,但是我希望在学习一门新的编程语言的时候,这门语言不应该是我所熟悉的范畴(比如学完python后学ruby),否则很容易陷入到语言本身的语法设定中去了。我希望这门语言与我熟悉的语言之间有比较大的gap,能学到更多编程思想方面的东西。然后那段时间慢慢地就把scala放下了,偶然一次看到了关于jvm平台上的scala还有groovy和clojure的讨论,发现clojure似乎跟自己之前学过的语言有很大不同,主要是因为它是Lisp的一个分支,另外函数式编程的思想也贯穿其中,于是一头扎进去学到现在。 15 | 16 | 读的相关书主要有4本,《clojure编程》、《clojure编程乐趣》(这本书英文版的有第二版了,我对比了下,大部分是一样的,就是最后多了几个章节)、《clojure经典实例》、《七周七并发模型》(当然,这本书不只是介绍了clojure的并发模型,我对并发的内容缺少实践,这部分学得不是特别深)。目前还处于打基础的过程中,缺少一些实际项目来练手,了解storm似乎是个不错的开始。学习clojure带来的最大收获是思考问题上的改变。我发现函数式编程思想在数据处理问题上有很大优势,理解其中的精髓后发现现在写的python代码也越来越简洁了......以后总结下自己的学习心得后再专门讨论下这块。 17 | 18 | nlp这块最近在做的就是把autoencoder和lstm融一下试试效果,感觉最近做科研的这块很无力。真的是很没有方向感,不知道该怎么深入下去,也就靠读读论文打发时间了......有时候看着那些水文啊,真是无力吐槽了,然后就会安慰安慰自己,唉,不读博也罢。说个上周的一件小事,为了做融合,先要把autoencoder那边的matlab代码改成python的,方便共享cost信息。其实我也没怎么看matlab代码,很久没用过了,听师弟讲了讲代码流程后大致明白了其结构和实现方式,于是花了一天时间草草写了个测试用例。同一个batch的数据反复迭代后,cost是呈现收敛趋势的,然后感觉应该没问题了。给大家讲了讲实现细节后修改了几处错误。 19 | 20 | 本以为这样就可以了,不过徐老师强调让我验证下matlab和python两边的输出结果是否一致。其实一开始我的内心是拒绝的。。。(其实是不屑的...)哥写的怎么会有问题呢???无奈,写样例,测试......打脸了......第一轮前向结果就不一致,甚至最后收敛的结果也总是相差了一点点。看了半天愣是没找出问题所在,更要命的是matlab的代码简直就是个blackbox。实在找不出问题了,我只能把问题归为matlab里神奇的边界条件所导致的结果差异......然而,老师并不信我这套,一定要两边输出结果一致才对! 21 | 22 | “老师,这个,matlab的代码我不会debug...” 23 | 24 | “那我们一起来看” 25 | 26 | “。。。” 27 | 28 | 然后,一整个下午坐在老师旁边一行行读代码,测试,验证。最后的最后,坑在了一个实在是微不足道的地方。计算cost的时候通常为了避免对0求log会加上一个很小的实数$1.0e^{-10}$,然而,我的样本有一个mask操作,mask以外的cost都为0,本应该对mask部分的cost加上$1.0e^{-10}$后求-log,再求mean,结果我代码里为了图方便,先整体加上$1.0e^{-10}$后求-log再求mean。但是,$-log^{(0 + 1.0e^{-10})}$ 并不是趋于0的呀!!!因为自己的失误,还让别人跟着一起填坑......实在是惭愧。 29 | 30 | 总结下来,一是永远不要对自己过度自信,我不知道多少次因为这个毛病坑人坑己了......被女朋友说,被同学说,被老师说,以后还是要更务实一些;二是“凡是有可能出bug的代码一定有问题”,“墨菲定律”?,这个也不是第一次了,好几次隐约觉得可能代码写得有点问题,结果最后都被坑的不轻...... 31 | 32 | 33 | ##找工作 34 | 35 | 再聊聊找工作的事。算了算,自己总共就投了5份简历,(BAT+美团+京东),其它的也就没投了,要么是显然去不了,要么是实在不感兴趣。剩下的时间,可能更有针对地去投几家外企,发现了两家还不错的公司,试试看~ 36 | 37 | 百度那边走的就是正常的流程,宣讲会,投简历,面试。面试百度应该是自己的第一次正式面试,有点紧张......现在印象最深的就是二面问了个智力题,想了半天答错了@_@。三面的话,针对自己做的事情,聊的level稍高点。总的来说面试的人都还不错。阿里的话,我就不吐槽了。腾讯是唯一参加笔试的,本来就对腾讯不太感兴趣,好奇跟着参加了个笔试,出的都是什么题啊,简直了!美团那边面试的感觉页很不错,师兄推荐过去的,中午在那吃饭的时候听他们聊了些工作上的事,觉得和自己做的事情挺对胃口的。只是,有时候真的觉得对自己的职业生涯缺少很明确的规划。以至于真要做出选择的时候,很是迷茫......蛮感激曾师兄跟自己聊了许多,不管最后去不去美团,那次谈话都是很大的收获。京东的面试,就一个感觉,我是不是来错了地......面试的人一点都不鸟我...... 38 | 39 | 其实从内心来讲,自己倾向于往计算广告方向发展,商业价值更大一些,但这块需要一个好的成长环境。不过呢,觉得做nlp这块好像也还不错,把功底打扎实了以后做周边的东西都能跟得上,不过总觉得有那么一丝丝想法,是不是有必要读个博把这块的工作做得更深入呢?唉,现实的情况是,先得找到个工作,然后再谈实现个人价值。理想和现实的纠缠...... 40 | 41 | ##最后 42 | 43 | 这个网站的代码好久没更新了,积累了不少问题一直没改,近期打算做一些改进,当时图简便直接用的django框架,接下来如果有时间的化,可能会换个框架,说不定会拿clojure试水~ 44 | -------------------------------------------------------------------------------- /resources/unpublished-md/(4)一起用python之基础篇——入门书.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # (4)一起用python之基础篇——入门书 3 |
2014-03-02 06:51:00
4 |
2014-03-02 06:51:00
5 |
24
6 |
Python,Book
7 | ##写在前面 8 | 9 | 从快毕业的时候在图书馆里借来第一本有关python的书算起,接触python的时间也不过半年有余。时间真的很短,很难有什么经验之谈,自己至今也仍有许多需要学习的地方。不过对于怎么入门这一块,倒是颇有感触。在这里记录下来,也许能对后人有所帮助吧~ 10 | 11 | ##我是怎么开始了解python 12 | 13 | 快毕业的时候,在中南的图书馆里瞎逛,偶然之间看到这么一本书,《可爱的python》。第一眼看上去,只是觉得书名还挺新颖的,反正也是闲着,抽出来看看吧。“**人生苦短,我用python**”,这是我在封面上看到的第一句话,这感叹句实在太吸引眼球,以至于这么长时间后,我早忘了书中讲的什么内容。留在脑海中的就只有封面上的这句话和作者的前言。 14 | 15 | 当时看完前言部分,我就感慨良多。一本好的编程入门书,不应该是一上来就告诉你怎么写Hello World,给你介绍变量、函数、控制流 blablabla...,而是作者站在一个朋友的角度来和你谈心,告诉你他自己学习这门编程语言的经历,他自己所体会到的这门编程语言的魅力在哪里,有哪些优点和不足之处,怎样能够更快更好地熟悉这门语言。这感觉就和当初学C++时候读的第一本书《Thinkng in C++》一样。作者提到,由于python这门语言的特殊性,对它的学习并不必拘泥于传统的教科书式的学习方式,而是重点在“使用”中学习,其基本思想就是用最短的时间掌握python最基础最核心的语法,然后在使用中碰到具体的问题时候,再去主动学习相关知识。这个观念对我的影响很深,可以说,回顾自己的历程,基本就是按照这个原则来的,而且收获确实很多。 16 | 17 | 下面就结合我自己的学习经历,谈谈刚入门时候的基本原则。 18 | 19 | ###你只需要掌握最基础的 20 | 21 | 刚开始学习python的时候,可能会查看许多书,这些书为了能够涵盖得尽量全面,往往会涉及语言方方面面的细节。但是,*并不是每一个知识点都是你所需要的*。一开始你只需要掌握最基础的那部分知识。你可能会问,“我哪知道哪些是最基础的东西呢?” 我觉得,一个很简单的判断方法就是,拿起书都第一遍的时候,如果你能硬著头皮看下去并且能够理解里面所讲的内容,那很好,这就是最基础的。如果看了第一遍后云里雾里,鬼才知道哪天会用得上这些东西。OK,专门找个小笔记本,记下这部分内容方便以后查阅,然后,`跳过`这部分。我在第一次看decorator装饰器这个部分的时候实在看不下去,也不知道可能会有啥用,果断跳过,最近上高性能计算的课,学习下cuda的python接口时,里面都是装饰器修饰的函数,才又好好学习来一下,结合来自己的实际问题,这样理解起来也就更深入。 22 | 23 | ###脚踏实地,出来混,迟早是要还的 24 | 25 | 记住,前面你跳过的那些问题,迟早是会冒出来的。你自己得清醒地意识到,这种**刻舟求剑**式的做法,是存在一些弊端的,虽然大多数时候,这些弊端不过是自己动手来实现一些别人已经实现来的东西,多花点时间精力罢了,但还有的时候,你可能会付出沉重的代价。类似的教训实在太多,比如看书的时候觉得itertools这个包没有太大用就跳过了,后来有一天要实现个排列组合的算法时花了很长时间来实现,结果偶然一天看到这货居然内置在iterrools里了;还有迭代器和生成器那部分,一开始以为自己可能用不到,后来要对一堆很大的文本做分析时候才发现内存不够了......所以说,出来混,迟早是要还的,那些跳过了东西,迟早某一天要出来坑你一把。那肿么办咧,**跳还是不跳**,这是个问题,个人觉得,刚入门的时候,还是能跳就跳吧。等自己对这门语言产生兴趣了,再来深入了解其语言的细节,也不算太晚。 26 | 27 | ###多读书,都好书 28 | 29 | 关于python的书虽不如C++,Java之类的那么多,但好书却不少了,这半年看了有十多本书了吧,整体感觉质量都挺不错。以下按照由浅入深的顺序来推荐给大家。 30 | 31 | - 相信我,你看的第一份文档,应该是[The Python Tutorial](http://docs.python.org/3/tutorial/index.html)。什么?英语的看不懂!我去,你都还没开始看!!! 32 | - 看完上面的教程后,你可能会有种意犹未尽的感觉,难道,只需要这么点知识我就算入门了吗?如果你看完毫无压力,我只能说真的,这样就算入门。不过除此之外还有另外一些讲解python基础书,也值得一看。你应该把大多数时间花在上面这份tutorial上,下面(1)中基础点的书应该是当作补充。看这几本书的时候,牢记上面的两条原则!(我是不会告诉你下面的这些书大多都有中文版的:~) 33 | 34 | 1. 基础点的:[A Byte of Python](http://files.swaroopch.com/python/byte_of_python.pdf), [learn python the hard way](http://learnpythonthehardway.org/book/) 35 | 2. 稍稍进阶点的:[dive into python 3](http://www.diveintopython3.net/) 36 | 3. 需要当工具书一样看的:[The Python Standard Library byExample](http://it-ebooks.info/book/1506/) 37 | 4. 骨灰级的:[Python Cookbook, 3rd Edition](http://it-ebooks.info/book/2334/) 38 | 39 | ###好用才是王道 40 | 41 | 看完上面这些书,你应该对python的基本语法特性,内部的标准库有了很深的了解。但是,我最想说的是,并不一定要等的你把这些书都读完了才开始做些事,(事实上,读完那份tutorial你就可以动手做很多事了)。你应该很清楚的知道自己要用python来做什么!!!想当初大一学c语言时候,学了也不知道为什么而学,所以啊,最后学完了那些语法知识后全都丢到一边,我那时候哪还知道c可以用来干那么多事。就我自己而言,学习python的目的是为了在一定程度上代替matlab作为科学计算工具,利用其丰富的包来实现许多功能,另外,用python写的代码可读性很高,不管是自己写还是读别人的代码,都是一种享受。 42 | 43 | 我想,你也一定有自己使用python目的,比如想用python爬网络上的资源,比如要用python建个网站,又或者是要和服务器上的后台打交道...你总可以找到自己要学习的那个部分,记住,`把重点花在这里!`。然后,等你对python有一些感性认识了,某一天自然会想起来要了解下python的底层是怎么实现的,为什么这样做比那样做更好等等问题。 44 | 45 | 编程语言说到底也只是工具罢了,工具固然是越好用越好,但更重要的是你要知道拿这些工具去解决什么样问题,以及怎样去解决! -------------------------------------------------------------------------------- /resources/unpublished-md/Statistical Rethinking 读书笔记.md: -------------------------------------------------------------------------------- 1 | 6 | [toc] 7 | 8 |
Book
9 | 10 | # Statistical Rethinking 读书笔记 11 | 12 | 最近这个月断断续续读完了Statistical Rethinking一书,感觉这本书还是挺适合入门的。作者的文风很好,每一章开头都会引入一个有意思的例子方便读者对本章的内容有一个大概的理解,不过书中的代码部分主要用到了自己写的一个库,这么做有好处也有坏处,好处是整本书中代码部分会相当简洁,侧重理解概念而不拘泥于代码细节;不过坏处是对于我这种不太熟悉R代码的人来说有种雾里看花的感觉,整体上讲,作者对二者平衡得很好,即使没有R基础也能很好地理解大部分内容,只是练习部分会稍微吃力点,后面感觉自己还会重读这本书。 13 | 14 | 另外作者还录制了[教学视频](https://pan.baidu.com/s/1skZIvu9),大致看了几课,感觉蛮不错,不过不如看书来得快。 15 | 16 | 以下是本书中的一些要点: 17 | 18 | ##Chapter 2 19 | 提纲挈领的一部分。作者用small worlds类比观测到的世界,而large worlds则对应真实世界,我们无法知道真实世界是怎样的,只能根据观测到的世界去修正我们的猜测,由此引出了先验、似然和后验的概念。这章最核心的是要理解quadratic approximation,作者用`map`函数对其作了封装,前面几章会频繁用到。 20 | 21 | 关于MAP、ML等有个很不错的[介绍材料](https://engineering.purdue.edu/kak/Tutorials/Trinity.pdf)可以参考。 22 | 23 | ##Chapter 3 24 | 理解HDPI的概念,可以尝试自己动手实现下这个函数,比我想象中要简单。可以参考下[这里](http://stackoverflow.com/questions/22284502/highest-posterior-density-region-and-central-credible-region) 25 | 26 | ##Chapter 4 27 | 重点理解高斯分布的内涵,这一点在PRML/MLAPP中也都有提到,思想是一致的。 28 | 29 | ##Chapter 5 30 | 从一元线性回归过度到多元线性回归的时候,会遇到几个典型的问题。变量之间存在相关性时,后验分析会出现不符合常识的问题。此外还分析了引入哑变量对类别变量进行编码的影响。 31 | 32 | ##Chapter 6 33 | 过拟合和欠拟合,一个经典问题。作者的出发点很新奇,从信息熵的角度出发,把交叉熵、KL散度、(样本内/样本外)偏差联系了起来,然后引入正则先验的概念。本章最关键的是信息准则,这对于我来说是个全新的概念,后面几章中都反复用到该指标进行模型比较和评估等。 34 | 35 | 信息熵的表示如下: 36 | $$ 37 | \begin{equation} 38 | H(p) = -\sum_{i=1}^{n}p_i log(p_i) 39 | \label{entropy} 40 | \end{equation} 41 | $$ 42 | 稍微改写下形式: 43 | $$ 44 | \begin{equation} 45 | H(p) = \mathbb{E}H(p_i) 46 | \end{equation} 47 | $$ 48 | 其中$\mathbb{E}$表示求期望,$H(p_i)=log(\frac{1}{p_i})$,其含义是概率越低信息量越大,求log是为了保证相互独立事件的信息量之和等于这些事件同时发生的信息量。 49 | 50 | > **K-L散度**:用一个分布去描述另一个分布时引入的不确定性。 51 | 52 | ![屏幕快照 2017-05-06 19.00.39.png](../public/Statistical-Rethinking-1.png) 53 | 54 | $$ 55 | \begin{equation} 56 | \begin{split} 57 | D_{KL}(p,q) & = H(p,q) - H(p) \\ 58 | & = -\sum_{i}p_i log(q_i) - \left(- \sum_{i}p_i log(p_i) \right) \\ 59 | & = - \sum_i p_i \left(log(q_i) - log(p_i) \right) 60 | \end{split} 61 | \label{klexplained} 62 | \end{equation} 63 | $$ 64 | 65 | 式子$\eqref{klexplained}$中的$H(p,q)$表示**交叉熵**。 66 | 67 | 关于KL散度,有一篇[博客](https://www.countbayesie.com/blog/2017/5/9/kullback-leibler-divergence-explained)写得更详细写,可以参考。 68 | 69 | ##Chapter 7 70 | 71 | 这一章重点分析了多个变量之间存在相互影响时的情况,感觉自己在做数据分析的时候,好像经常忽略了这点。 72 | 73 | ##Chapter 8 74 | 75 | MCMC和HMC的解释很直观。关于采样链(Chain),有效采样个数等都有说明。 76 | 开篇提到的Good King的例子很好玩,我也重写了下: 77 | 78 | ```clojure 79 | (def N 10) 80 | (def counts (vec (range 1 (inc N)))) 81 | 82 | (defn move 83 | [i] 84 | (rand-nth [(mod (dec i) N) 85 | (mod (inc i) N)])) 86 | 87 | (defn play 88 | [i] 89 | (let [j (move i) 90 | count-i (nth counts i) 91 | count-j (nth counts j)] 92 | (if (or (> count-j count-i) 93 | (<= (rand) (/ count-j count-i))) 94 | j i))) 95 | 96 | (->> (iterate play (rand-int N)) 97 | (take 100000) 98 | frequencies 99 | (sort-by first)) 100 | ``` 101 | 102 | ##Chapter 9 103 | 104 | 简单介绍了下广义线性模型,理解这里提到的两类连结函数(link function),本质上是将参数从不同的值域映射到$[-\infty,+\infty]$,留意其中参数的可解释性。 105 | 106 | 后面几章中看得比较粗略,我主要看了下多层模型、零膨胀问题和缺失值的问题。 107 | -------------------------------------------------------------------------------- /resources/unpublished-md/最近的一些感悟.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # 最近的一些感悟 3 |
2016-08-15 10:07:25
4 |
2016-08-14 23:45:43
5 |
105
6 |
7 | 看了下时间,距离上次更新博客差不多三个月了,也意味着来滴滴有三个月了。梳理了下,这三个月的事也挺多的,毕业答辩、毕业、实习和工作。现在回顾这三个月,感觉转变真的很大,趁今天入职培训有点闲暇的时间,记录下这段时间的感受,也算是分享下自己的近况吧。 8 | 9 | # 1. 三个月里学到了些什么? 10 | 11 | 回想三个月前,自己还坐在实验室的工位上写代码做数据分析,如今那地方早已被大一的小朋友们占满了,让人不得不感慨良多。自从上一篇博客烂尾后,就去了滴滴实习,也就是现在工作的地方。其实,前期并没做很多事情,因为毕业的事,实习也都是三天打鱼两天嗮网,到每个月底数工资的时候发现,其实一个月里也就半个月在公司。实际的工作时间虽然不多,不过这三个月下来也马马虎虎对整个项目流程有了些了解,对整个公司也有了更深的认识,随便记录点东西,留着以后回头再看。 12 | 13 | ## 1.1 Spark 14 | 15 | 以前虽然对spark也有所了解,然而并没有应用的场景。去组里最早的任务也就是熟悉下spark任务和相关的脚本了,期间对spark 的API有了一定的熟悉,不过到现在为止,也没怎么熟悉MLLib相关的应用和实现。最早的时候,是接手组里另外两个实习生的一些特征提取的工作,讲真,理解完代码时的一个感受是,“嗯,终于看到比我的Python写得还烂的了...",不过呢,等到自己后面写的时候才发现,自己写出来的一些特征提取代码也强不到哪去(后面再细聊这块)。 16 | 17 | 新人用spark面临的第一个问题一般就是,要不要学Scala呢???记得很久之前学了一段时间的scala,应该是2015年年底附近吧,到最近早就忘得一干二净了,记得当时Scala留给自己的印象就一个字,”杂“。深知这语言里的坑太多,于是实习期间的时候果断跳过了,平时的spark任务基本都是调的Python API,除了期间因为代码交流的原因,又复习了下Scala的语法。然而,大规模云平台上使用Python的一个问题是,包管理(据说Golang的人老习惯拿这点对比Python了)。如果要在这条路上继续走的话,我估计Scala还是得硬着头皮去掌握。。。闲暇的时候尝试了下clojure下的两个spark相关的库,一个是flambo,还有一个sparking,后者感觉还不错,写出的代码要优雅得多,不过,工作归工作,写出来的东西毕竟是要跟其他人沟通的,clojure还是太小众,也就自己玩玩而已了。 18 | 19 | ## 2.2 进度控制和管理 20 | 21 | 技术或者算法方面的学习不是特别多,不过这段时间学到的最重要的应该就是进度的控制和自我管理了。可能自己是自由散漫惯了,在所里的干活的时候都是看心情,早上可能都去得比较晚,完成实验什么的其实并没有什么具体的规划,走一步看一步的那种感觉吧,有状态的时候感觉效率奇高,没状态的时候基本一整天都莫名其妙地过去了。而去了公司的一个整体感受是,一整个项目的分工和拆解相当明确,个人负责自己的一块,再拼接成一个整体。其实刚开始那段时间很不适应,主要原因是总想着对整个项目都有个完整了解后,再考虑去完成自己擅长的那部分。然而,事实是我足足花了一个多月的时间去梳理整个项目,这期间自己的贡献基本为0(更要命的是我在新来的实习生身上似乎也看到了同样的现象),后来观察组里其他人的工作方式,几乎都会把任务拆解到很小的可实现的几个点上,与此同时不断熟悉上下游的代码,这样同时兼顾了杂事的处理和业务逻辑的学习。要是回到3个月前,我一定要告诉自己的一件事就是,不要贪多,step by step! 22 | 23 | 24 | # 2. 写项目和做比赛的一些区别 25 | 26 | 知乎上有人讨论过一个问题[你实践中学到的最重要的机器学习经验是什么](http://www.zhihu.com/question/46301335),显然我还不够格去讨论这个问题,不过呢,我倒是可以分享一些关于完成项目和作比赛的一些区别。 27 | 28 | 29 | 记得有天和陈大师聊到这个话题,讨论的重点是为啥滴滴比赛的时候都是各种模型的融合,咱做项目干的都是又low又苦逼的特征**拉取**工作(注意关键词)。后来陈大师说了句意味深长滴话:因为这样子效率最高呀!呵呵,会心一笑,不得不承认,同样的工作时间条件下,扩充特征确实是最保守的做法。 30 | 31 | 个人觉得,做比赛和做项目的最大差别,就是特征抽取了。比赛过程里,一般数据集是给定的,我们根据数据集和问题的特性尽可能去构造出能够描述问题本质的特征体系。由于时间有限,这期间最重要的一件事不是去扩充特征,而是特征的筛选,因为在有限数据集的条件下,top级选手之间的特征差异往往并没有很大。至于筛选的方法,就各显神通了,有人倾向于根据模型得到的特征筛选,有人倾向于自动化的多模型筛选,也有人喜欢直接上神经网络的,总之,有用就行。 32 | 33 | 其次,我认为是迭代效率的差异。很久以前,我一直认为,除了特征之外,最重要的就是模型融合了,最近好好想了下这个问题,其实比模型融合更重要的是迭代效率。同样走在一条未知的探索道路上,最接近终点的往往是试错最多的团队。迭代效率越高,短时间内尝试的方案也就越多,所以,模型融合也只是迭代效率高的表现形式之一而不是全部。此外,这也可以解释为什么一些横扫各大比赛的冠军队往往更容易在一个新的赛事上获胜,经验是其一,极高的执行效率和工具化的特征生产链也是重要的一环。这段时间的实际工作里,深切感受到了前期积累的重要性,很多生产流程都相当原始,许多事都觉得有些无能为力。 34 | 35 | 最后是敢于尝试,项目相关的东西,总是偏向于保守的,而做比赛的包袱则没有那么大,许多最新的一些算法都是值得去尝试和改进的,从最近滴滴比赛的几个PPT里可以看到,还是有些队用了神经网络的一些东西,包括DNN,GRU网络等,也有不错的效果。 36 | 37 | # 3. 一些工作了才发现的事 38 | 39 | - 个人能做的事其实很小很小,平台的重要性远大于其它。 40 | - 缺少主动探索的精神,对大多数人而言,“工作”,也只是一份工作而已。 41 | - 时间不够用,真心觉得时间才是最宝贵的资源。(现在都不推塔了。。。) 42 | - 保持记录的习惯很受益,每天都写写wiki,有利于减少一些沟通成本。 43 | - 代码积累,以前可能都是随手写写脚本什么的,信手拈来。工作后写代码很重要的一个环节是代码复用,多积累一些snippets还是蛮有意义的。 44 | - 测试!对别人负责,也对自己负责。 45 | 46 | # 4. 近期的一些规划 47 | 48 | 1. 重写下网站,保持更新的频率。发现公司的网没法访问blog,打算改写下后台文件的传输方式,还是回归到原始的推送到github方式算了,利用github上的hook同步到server端备份,撤掉后台管理。 49 | 2. 熟练掌握下tensorflow,这个毕竟还是以后工业级应用的主要工具库,theano之类的还是更偏学术点。 50 | 3. 熟练掌握下clojure并发的部分(主要是最近受了Golang的刺激......),此外就是结合业务思考下如何灵活地去写一些DSL相关的东西。 51 | 4. 读下手头上的书,《计算机程序的构造与解释》(刚看一半,后面两章应该要花不少时间)、《计算的本质 深入剖析程序和计算机》(挺有意思的一本书,Ruby还是不太熟,应该会拿Python来重写一遍)、《具体数学》和《统计学习基础》(算是扫尾吧,保持手感最重要~) 52 | 5. Follow下顶会上一些有意思的paper,多动手实践下~ -------------------------------------------------------------------------------- /resources/unpublished-md/Throwing Eggs from a Building.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # Throwing Eggs from a Building 3 |
2015-02-03 16:08:25
4 |
2015-02-03 07:27:53
5 |
57
6 |
Algorithm
7 | 下午做练习题时碰到的问题,*Algorithms, 4th*中的**1.4.24**和**1.4.25**。思考了之后发现是个挺有意思的问题。下面结合题目和网上的一些资料做个简单的分析。 8 | 9 | ##1. 问题描述 10 | 11 | 原文的问题是这样的: 12 | 13 | > 1.4.24 *Throwing eggs from a building.* Suppose that you have an N-story building and plenty of eggs. Suppose also that an egg is broken if it is thrown off floor F or higher, and intact otherwise. First, devise a strategy to determine the value of F such that the number of broken eggs is $\sim lg N$ when using $\sim lg N$ throws, then find a way to reduce the cost to $\sim 2lg F$. 14 | 15 | 简单来说就是这么个意思: 16 | 17 | > 在一座N层楼房上往下扔**假鸡蛋**,这些**假鸡蛋**只有在第F层楼及以上才会被摔破(没有摔破的鸡蛋可以接着用)。为了找到这个F,设计一个思路使得尝试的次数接近$\sim lg N$并且最后摔破的鸡蛋个数接近$\sim lg N$,然后想办法降低到$\sim 2lg F$。 18 | 19 | ###1.1 二分法 20 | 21 | 首先想到的应该是用二分法来解决这个问题。从中间数(N/2)开始,如果鸡蛋摔破了,就继续往下找。显然,如果鸡蛋在1楼才摔破的话,摔破的鸡蛋个数和尝试的次数都接近$\sim lg N$。问题在于,如何降低到$\sim 2lg F$? 22 | 23 | ###1.2 减少摔碎的鸡蛋个数 24 | 25 | 试着思考下这个问题的特殊之处,**如果某一次尝试的过程中鸡蛋没有摔破,那么可以接着用**!回顾前面的二分法中的极端情况,每次尝试都摔碎了个蛋,太浪费了。也就是说,如果换成从下往上搜索而不是从中间开始搜索,那么最初的几次尝试很大可能不会摔碎蛋的。于是,最简单的应该就是拿着鸡蛋从1楼开始往上一层一层试,直到鸡蛋摔碎。不过这样虽然摔碎的鸡蛋的个数降到了**1个**,但是测试的次数却上升到了**F个**。 26 | 27 | ###1.3 平衡摔碎的鸡蛋数和尝试的次数 28 | 29 | 从前面的分析可以感受到,如果降低摔碎的鸡蛋个数,测试的次数就上去了,反之亦然。那么如何平衡呢?前面是拿着鸡蛋一层一层往上测试,这样子太慢了!不妨每隔两层(三层,五层?)测试一次,摔碎了的话再退回来。这样测试的次数将低了一些,但是仍然是线性的。还是太慢了!干脆换成指数增长。假设第k次摔碎了,然后再倒回来采用二分查找。这样摔碎的鸡蛋个数是$\sim lg(F/2)$(前面在第$1,2,4,...,2^k-1$层测试的时候没摔碎,只有后面$2^k-1到 2^k$之间 二分查找时候才有可能摔碎),而尝试的次数  为$\sim 2lg F$。 30 | 31 | ###1.4 疑惑 32 | 33 | 疑惑的是,这样子虽然肯定可以降低摔碎鸡蛋的个数,但并不一定能降低尝试的次数啊 34 | 35 | ##2 问题扩展 36 | 37 | > 1.4.25 *Throwing two eggs from a building.* Consider the previous question, but now suppose you only have two eggs, and your cost model is the number of throws. Devise a strategy to determine F such that the number of throws is at most $2\sqrt{N}$, then find a way to reduce the cost to $\sim c\sqrt{F}$. This is analogous to a situation where search hits (egg intact) are much cheaper than misses(egg broken). 38 | 39 | ###2.1 简单的尝试 40 | 41 | 现在的问题变成了,如果只有两个鸡蛋,怎样才能降低查找次数? 42 | 43 | 不考虑其他的,假设楼房有100层,现在手上有两个蛋,先跑到50楼扔一个了再说,人品不好,蛋碎了,那么接下来该怎么办?老老实实拿着剩下的那个从一楼开始尝试吧,最多试50次。直觉告诉我们,一开始就选太大了反而得不偿失。事实上,借用前一个问题的分析思路,可以尝试用线性增长的方式来解决这个问题(显然这次不能用指数增长了,还得check最后一个区间的……)。假如每次往上提升采用一个**定步长**,其实可以算出最优解。设步长为x,那么最大的查找次数为$x + \frac{100}{x}$,其最优解近似于$2\sqrt{N}$ 44 | 45 | ###2.2 变步长 46 | 47 | 前面这种解法的瓶颈点在哪呢?最后一步! 48 | 49 | 假设鸡蛋在最后一步那地方碎了,此时已经走了$\frac{100}{x}$步,然后还需要再尝试10次(从90到100层之间,也就是步长的长度)。 50 | 51 | 通过采用**变步长**的思想,前面这个解决方案还可以稍微改进一些。我们知道,二分法求解问题的核心思想是**最大化信息熵**,通俗点说,就是每check一次之后,不管目标值在check点的左边还是右边,对我来说需要check的次数是相同的。对应到这个问题上,设第一步的步长为$x_1$,不管蛋碎了没有,我希望接下来check的次数都是相同的。显然,如果蛋碎了,我就只剩一个蛋了,还需要从下往上check$x_1-1$次,如果蛋没碎,此时只要$x_1$选取的合理,满足前面最大熵要求,我仍然只需要$x_1 - 1$次就能找到F。如此迭代下去便不难发现,最后可以得到公式$x_1 + (x_1-1) + (x_1 - 1 - 1) + ... + 1 = N$,从而可以求解出初始步长。 52 | 53 | ##3 更多的鸡蛋~ 54 | 55 | 如果鸡蛋的个数变成3个,4个,5个...... 56 | 57 | 采用**减而治之**的思想,可以很好去解决这个问题。以3个鸡蛋为例子: 58 | 59 | > 1. 首先,得确定第一个步长$x_1$, 60 | > 2. 如果碎了,则问题转换成了2个鸡蛋$x_1-1$层楼的问题,(根据前面的方法假设该问题的最优解为$x_c$); 61 | > 3. 如果没碎,则转换成3个鸡蛋$N-x_1$层楼的问题 62 | 63 | 通过迭代的方法比较容易求解该类问题。 64 | 65 | 更多有意思的分析可以看看[这里](http://datagenetics.com/blog/july22012/index.html)。 66 | 下图是从上文中摘出的一幅图,用来说明不同情况下需要的蛋的个数。 67 | 68 | ![graphl.png_184fd5a153422104b0054be061ec2a80](http://ontheroad.qiniudn.com/blog/resources/graphl.png_184fd5a153422104b0054be061ec2a80/w660) 69 | 70 | -------------------------------------------------------------------------------- /resources/unpublished-md/我的面试题.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | 3 |
InterviewQuestions
4 | 5 | # 我的面试题 6 | 7 | 最近在知乎的时间轴上总是出现面试相关的问题,忽然想到一个比较有意思的事情,不妨把我平时遇到的一些问题提炼出来,写一个面试题汇总,下次再有人砸简历过来的时候先扔过去自测下,哈哈哈~ 8 | 9 | 计划先写10个问题,暂时没打算给出标准答案,但是会写出问题的来源、我的思考过程以及一些hint,内容会涉及方方面面。 10 | 11 | ## 核心原则 12 | 13 | 来滴滴工作快一年了,关于公司文化,我印象最深刻的一点是: 14 | 15 | > 招三年后能成为你上司的人 16 | 17 | 第一次看到这句话应该是在公司的洗手间。需要强调的是,这句话本身很值得玩味(刚去滴滴的时候恰好是四周年),成就他人,同时还要看到他人的增长潜力,对标自己现在的水平,以及目前自己上司的水平(技术/管理/沟通),呵呵,想出这句话来的人真有才。我个人对这个原则基本认同。 18 | 19 | ## 那么,问题来了 20 | 21 | ### Q0: 你所掌握的语言是如何影响你的思维过程的? 22 | 23 | **问题来源**:《程序开发心理学》第12章的思考题。 24 | 25 | **问题描述**:大多数应聘的人都会在简历上描述自己掌握或者了解的编程语言都有哪些,有些人可能对某种语言的细节和各种奇技淫巧非常熟悉,有些人可能对各种工具库的使用情况了如指掌,不过,大多数人可能没有想过这些语言是如何影响我们的思维过程的。 26 | 27 | **Hint**: 每个人对这个问题的答案都不太一样,一方面是想从这个问题了解对方对于自己所掌握的语言的理解程度,从而对其编程能力有一个大致的预估;另一方面是了解对方的思维习惯和抽象能力,从而看出以后的工作中与此人共事是否愉快。对我自己而言,C中的指针(Reference)、Python中的Notebook(trial-and-error)、Clojure中的REPL和Macro(hot-fix,DRY等)是我一下子能想到的几点。 28 | 29 | ### Q1:概率 30 | **问题描述**: 31 | 2M4. Suppose you have a deck with only three cards. Each card has two sides, and each side is either black or white. One card has two black sides. e second card has one black and one white side. e third card has two white sides. Now suppose all three cards are placed in a bag and shu ed. Someone reaches into the bag and pulls out a card and places it at on a table. A black side is shown facing up, but you don’t know the color of the side facing down. Show that the probability that the other side is also black is 2/3. Use the counting method (Section 2 of the chapter) to approach this problem. is means counting up the ways that each card could produce the observed data (a black side facing up on the table). 32 | 2M5. Now suppose there are four cards: B/B, B/W, W/W, and another B/B. Again suppose a card is drawn from the bag and a black side appears face up. Again calculate the probability that the other side is black. 33 | 34 | **问题来源**:*Statistical Rethinking*一书第二章的习题 35 | 36 | **Hint**:这个问题很基础,但很有意思,与之相似的一个问题是“三门问题”。这个问题还可以从很多个角度去扩展,比如计算信息熵、交叉熵等,总的来说就是希望对方能够对先验、似然、后验等概念很熟悉。进一步,还会考察下宏平均、微平均、辛普森悖论,以及均值、中位数、众数等基础概念的理解,以确保日常的数据分析中不会犯一些常识性错误。 37 | 38 | **问题描述** 39 | 9. Simulation of a queuing problem: a clinic has three doctors. Patients come into the clinic at random, starting at 9 a.m., according to a Poisson process with time parameter 10 minutes: that is, the time after opening at which the first patient appears follows an exponential distribution with expectation 10 minutes and then, after each patient arrives, the waiting time until the next patient is independently exponentially distributed, also with expectation 10 minutes. When a patient arrives, he or she waits until a doctor is available. The amount of time spent by each doctor with each patient is a random variable, uniformly distributed between 5 and 20 minutes. The office stops admitting new patients at 4 p.m. and closes when the last patient is through with the doctor. (a) Simulate this process once. How many patients came to the office? How many had to wait for a doctor? What was their average wait? When did the office close? (b) Simulate the process 100 times and estimate the median and 50% interval for each of the summaries in (a). 40 | 41 | **问题来源**:Bayesian Data Analysis 3 ed. 第一章习题部分第9题 42 | 43 | **Hint**:这个问题之前似乎在某个面试题集锦中有看到过,能对结果做出解释就更好了。 44 | 45 | ### Q2:Simulation 46 | 47 | **问题描述:** 48 | 49 | **问题来源**:Mini Metro 50 | 51 | **Hint**:TODO 52 | 53 | ### Q3:Regression 54 | 55 | **问题描述**: 56 | 57 | **问题来源**: 微信运动 58 | 59 | **Hint**: 60 | 61 | ### Q4:看地铁问题 -------------------------------------------------------------------------------- /src/blog_clj/redis_io.clj: -------------------------------------------------------------------------------- 1 | (ns blog-clj.redis-io 2 | (:require [taoensso.carmine :as car :refer [wcar]] 3 | [clojure.set :refer [difference]] 4 | [clj-time.coerce :as c] 5 | [clojure.walk :refer [keywordize-keys]])) 6 | 7 | (def server1-conn {:pool {} :spec {}}) 8 | 9 | (defmacro wcar* [& body] `(car/wcar server1-conn ~@body)) 10 | 11 | (def ^:dynamic site "tianjun.me:") ; global prefix 12 | (defn mk-key [k] #(str site k %)) 13 | (def blog-key (mk-key "blog_")) 14 | (def tag-key (mk-key "tag_")) 15 | (defn all-blogs [] (str site "all_blogs")) 16 | (defn blog-id-key [] (str site "blog_id")) 17 | 18 | (defn get-blog 19 | "get contents given a blog-id" 20 | ([blog-id] 21 | (let [bkey (blog-key blog-id)] 22 | (keywordize-keys 23 | (apply hash-map (wcar* (car/hgetall bkey)))))) 24 | ([blog-id & blog-keys] 25 | (let [bkey (blog-key blog-id)] 26 | (zipmap blog-keys 27 | (wcar* (apply car/hmget bkey blog-keys)))))) 28 | 29 | (defn get-blogids-with-tag 30 | [tag] 31 | (wcar* (car/smembers (tag-key tag)))) 32 | 33 | (defn get-all-blogs-titles 34 | "this function is used to generate blog-roll 35 | the result is sorted by update-time in default 36 | 37 | return ((title blog-id timestamp) ...) 38 | " 39 | [] 40 | (let [blog-ids-with-time (partition 2 (wcar* (car/zrevrange (all-blogs) 0 -1 :withscores))) 41 | titles (wcar* (mapv #(car/hget (blog-key (first %)) :title) blog-ids-with-time))] 42 | (map #(conj %1 %2) blog-ids-with-time titles))) 43 | 44 | (defn- update-tags 45 | "get the tags to delete and tags to add 46 | then update the blog's tags and 47 | related invert-index-set" 48 | [blog-id new-tags] 49 | (let [bkey (blog-key blog-id) 50 | old-tags (set (wcar* (car/hget bkey :tags))) 51 | tags-to-add (difference new-tags old-tags) 52 | tags-to-delete (difference old-tags new-tags)] 53 | (wcar* (car/hmset bkey :tags new-tags) 54 | (mapv #(car/sadd (tag-key %) blog-id) tags-to-add) 55 | (mapv #(car/srem (tag-key %) blog-id) tags-to-delete)))) 56 | 57 | (defn update-blog 58 | "update the contents of a blog 59 | 60 | `contents` is a map of following keys: 61 | 62 | `:blog-id`: this field is required 63 | `:title`: the blog title 64 | `:tags`: the tags of blog 65 | `:body`: the content of blog in markdown 66 | `:create-time`: the create_time of blog 67 | `:update-time`: last update time, this field is required! 68 | 69 | TODO: Add field checker, throw Exception" 70 | [contents] 71 | (let [blog-id (:blog-id contents) 72 | bkey (blog-key blog-id)] 73 | (when (contains? contents :tags) 74 | (update-tags blog-id (:tags contents))) 75 | (wcar* (mapv #(apply car/hmset bkey %) contents) 76 | (car/zadd (all-blogs) (c/to-long (:update-time contents)) blog-id)))) 77 | 78 | (defn new-blog 79 | "create a new blog-id and then call update-blog 80 | the update-time is required!" 81 | [contents] 82 | (let [new-blog-id (wcar* (car/incr (blog-id-key))) 83 | new-contents (merge contents 84 | {:blog-id new-blog-id 85 | :create-time (:update-time contents)})] 86 | (update-blog new-contents))) 87 | 88 | (defn delete-blog 89 | [blog-id] 90 | (update-tags blog-id []) ; clean tags and contents 91 | (wcar* (car/zrem (all-blogs) blog-id) ; remove from all-blog set 92 | (car/del (blog-key blog-id)) ; del blog-key 93 | )) 94 | -------------------------------------------------------------------------------- /resources/published-html/理解Clojure中Buddy认证模块.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 理解Clojure中Buddy认证模块 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | 理解Clojure中Buddy认证模块 18 |
      19 |
    • 20 | Httpbasic 21 |
    • 22 |
    • 23 | Session 24 |
    • 25 |
    • 26 | Token 27 |
    • 28 |
    • 29 | JWS Token 30 |
    • 31 |
    • 32 | JWE Token 33 |
    • 34 |
    • 35 | 参考 36 |
    • 37 |
    38 |
  • 39 |
40 | 41 | 42 |

理解Clojure中Buddy认证模块

43 | 44 |
Web,Clojure
45 | 46 |

有关加密解密的知识一直是自己的盲点,最近在看如何用用clojure写网站,顺带学习了下cookie和session相关的知识,在这里介绍下Buddy这个库,并总结下自己的理解,不对的地方恳请指正。Buddy提供了基于Ring的一些安全认证相关的接口,下面内容就此展开。

47 | 48 |

先解释几个术语:

49 | 50 |
    51 |
  • Handler:在Ring中,request和response都可以看做map,handler就是对request的内容分析后返回相应response的函数

  • 52 |
  • Backend:Buddy模块中每个backend都包含2个部分Authentication和Authorization,其中Authentication包含parse(将需要的认证数据从request中提取出来)和authenticate(根据parse提取的信息判断是否通过认证)两个步骤,Authorization则包含发生认证错误后如何处理该错误并返回相应response的实现。

  • 53 |
54 | 55 |

根据Authentication和Authorization的实现不同,Buddy模块提供了5种实现,下面以最简单的http基本认证为例:

56 | 57 |

Httpbasic

58 | 59 |

Httpbasic的处理逻辑如下图所示:

60 | 61 |

test.png_c3821b28623e8949810b26bf23a414d4

62 | 63 |

Httpbasic方式的缺点参见这里:

64 | 65 |
    66 |
  1. 密码采用base64编码,很容易反向转换;

  2. 67 |
  3. 每次请求都要传输密码(增加了攻击概率;

  4. 68 |
  5. 密码缓存在本地浏览器,容易通过CSRF窃取;

  6. 69 |
70 | 71 |

采用https协议的话,仅仅能解决第一点问题。

72 | 73 |

Session

74 | 75 |

该方案舍弃了httpbasic中传输username passwd的步骤,把验证和授权独立开来,授权放在login界面逻辑里去处理,将request中的:identity字段作为验证结果。而验证部分则只考虑是否存在:identity字段。

76 | 77 |

Token

78 | 79 |

基于Token的方法则是将原来Httpbasic处理过程里,request的header中Authorization内容改为了token,从而避免直接存储用户名和密码,然后服务器端存储token和用户的对应关系。

80 | 81 |

JWS Token

82 | 83 |

由于上面的token需要保存在服务器端,在用户量很大的时候,token的存储压力会很大,JWS token的用途就是将用户名密码加入签名后写进header的Authorization中,这样服务器端并不需要存储token到username的映射关系,只需要对token解码即可。这样做的好处是,不像Httpbasic简单采用base64对用户名密码编码,签名后的token很难破解得到原始的用户名密码信息。

84 | 85 |

JWE Token

86 | 87 |

JWE Token处理的过程和JWS Token很像,区别在于引入非对称加密,将一部分敏感信息用公钥加密,服务端用私钥解密。

88 | 89 |

参考

90 | 91 |

关于无状态认证的两篇讨论,里面提到了如何用python实现:

92 | 93 |

http://lucumr.pocoo.org/2013/11/17/my-favorite-database/

94 | 95 |

http://www.niwi.nz/2014/06/07/stateless-authentication-with-api-rest/

96 | 97 | 98 | 99 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /resources/unpublished-md/Data Science London + Scikit-learn.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # Data Science London + Scikit-learn 3 |
2015-01-02 08:48:11
4 |
2014-12-24 07:05:15
5 |
55
6 |
Kaggle,Competition
7 | 先感受一下这40维特征的分布情况: 8 | ![trainX_boxplot.png_1e40bdfe4c91adf0be46f972ddced4a8](http://ontheroad.qiniudn.com/blog/resources/trainX_boxplot.png_1e40bdfe4c91adf0be46f972ddced4a8/w660) 9 | 10 | 从这个图可以得到几个信息,一是特征都分布在0附近,特征之间没有很大的差异性,比较均衡;二是每一维特征的盒子分布的长度大概是1:3,有点接近高斯分布。事实上,针对每一维画一个直方图(其实这里画KDE的图更合适)可以粗略看出其分布情况: 11 | 12 | ![figure_1.png_de15b800f757d0cf0f2109708484a671](http://ontheroad.qiniudn.com/blog/resources/figure_1.png_de15b800f757d0cf0f2109708484a671/w660) 13 | 14 | 上图中蓝绿色分别表示label分别为0,1对应到第5维特征时候的特征分布。 15 | 图1是把label为0和1的所有训练集同时表示在了一张图上,那么将二者分开来看看呢? 16 | 17 | ![figure_1.png_456f3c80035a7d9175061fd5660c8949](http://ontheroad.qiniudn.com/blog/resources/figure_1.png_456f3c80035a7d9175061fd5660c8949/w660) 18 | 19 | 这样稍微可以看出点差别了,比较明显的是,有几维特征(比如第5,13维)在左右两边的分布出现明显的偏差。因此,可以断定,对于该数据集而言,少量的特征占有主要地位,而其他大多数特征只有较低的权重。当然,这样不够精确,下面对其量化下。 20 | 21 | 分别尝试用相关性衡量,逻辑回归,Lasso, svc,以及决策树模型,对初始特征的权值进行衡量,min-max归一化后的图如下 22 | 23 | ![featre_weights.png_1457d11c67fcde06d2825823fe891e81](http://ontheroad.qiniudn.com/blog/resources/featre_weights.png_1457d11c67fcde06d2825823fe891e81/w660) 24 | 25 | 可以看到,权值较大的特征大概是15维左右。下图是根据pca得到的特征权重。 26 | 27 | ![download.png_5ba6c5f6839ea5ac60601240a9b0fbb1](http://ontheroad.qiniudn.com/blog/resources/download.png_5ba6c5f6839ea5ac60601240a9b0fbb1/w660) 28 | 29 | 对特征了解了这么多,就来指定分类方法啦~ 30 | 31 | 首先根据tutorial试下水,用svm对40维特征做训练,采用rbf核,5折交叉验证的结果是0.922,可以看到,svm对于这类有着强特征的数据表现不错。为了进一步提升,结合前面的分析可知,首先可以做的是对原始数据降噪。很容易想到的就是主成分分析。这里做pca降维的时候,需要结合训练集和测试集(主要是因为训练集才1000而测试集有9000,因此需要引入测试集对训练集进行强化)。同样采用5折交叉验证,当pca降维到12,13维附近的时候,最好能达到0.95左右(线上测试结果大概0.945附近)。实际上,降维的程度直接影响该结果。 32 | 33 | 那么,到此为止,似乎就遇到瓶颈了。提取主成分,分类,一气呵成。从前面两步可以看出,提升的关键在于对数据的清理,那么,能不能在此基础上继续往前探索呢?我们知道主成分分析有其局限性,那么还有哪些其他改进的办法呢?沿着这个思路,尝试采用sparse filtering 对该降维的过程做出改进。 34 | 35 | 直觉告诉我们,先用pca降维的维数作为基础,对原有的数据做sparse-filtering,同样5折交叉验证大概是0.928左右,可见跟使用原始的svm差别不大,修改sparse-filtering使用的维度,遍历之后如下图所示: 36 | 37 | ![figure_1.png_fa552b3f3ac0d78acf38f96dbe50e1f4](http://ontheroad.qiniudn.com/blog/resources/figure_1.png_fa552b3f3ac0d78acf38f96dbe50e1f4/w660) 38 | 39 | 我们知道,sparse-filtering与pca降维的显著差异之一是,pca降维只找主要关系,而SF则是找特征之间的内在组合关系。(后面可以利用该特性进一步提升),从图中可以看出,采用SF的最好效果要略优于pca降维后的效果,可以达到0.955附近,但是实际线上测试不过0.94。这说明存在一定的过拟合现象,个人感觉,最主要的原因时后面的svm层过度调参所致。 40 | 41 | 可以尝试换成逻辑回归层作为分类试试。遗憾的是LR并不能达到svm那样的效果(主要是缺少核函数的映射过程,尝试的结果可想而知)。另外,感觉采用auto-encoder后可能效果要比sf要稍好一些,等熟悉了pylearn2后可以尝试下。 42 | 43 | SF的效果带给人的启发之一是,原来40维特征,通过SF处理后,可以得到和pca降维后近似的结果,但是维度的变化范围比较广(20~50)。也就是说,我们的目的并不是**降维**,降维只是**手段**之一,我们的目的是提取**有用的信息**。理解这一点后就好说了。 44 | 45 | 回顾上面所做的,实际上就是一个简单的3层网络结构。似乎,稀疏表示之后再分类的最好的效果止步于0.95。由于训练集数据较少,大量的数据是未标记的。那么,一个很**单纯的想法**就是,将测试集的预测结果中准确度极高的部分并入训练集,扩充训练集样本,强化训练。论坛里已经有人这么[尝试](http://www.kaggle.com/c/data-science-london-scikit-learn/forums/t/4986/ideas-that-worked-or-did-not)过,似乎,没有太大提升。我估计,主要原因是,从测试集中提取到的概率值极高的那部分数据实际上是远离svm边界的数据,这部分数据加入到训练集中,对于区分那些边界附近的点意义不大。 46 | 47 | 整理下思路,现在的瓶颈似乎是在svm上,那些边界点难以分隔。因此,可以尝试加入一些弱分类器,另外适当调整参数C。 48 | 49 | 先用决策树试一下。本地测试0.8。换随机森林,先将训练集二八分,80%为``train_set``,20%为``val_set``,然后用40课树训练``train_set``,分别预测``train_set``,``val_set``,得到40维的概率值,分别记为``rf_train_set``,``rf_val_set``。再用svm训练这个``rf_train_set``,用得到的最佳参数去预测``rf_val_set``。结果是,该模型对``rf_train_set``的结果能达到100%,但是对``rf_val_set``仅仅只有84%左右。看来rf的预测值只能用来打辅助。分析其原因,一方面数据量太少,过拟合无疑。另外随机森林的数量和深度也需要适当限制,否则如果单颗树就陷入过拟合,其效果必然不好。 50 | 51 | 最后再尝试下融合。只简单试了一下特征融合,没做太多参数上的调优,但是小有提升,线上接近0.96。仔细想了想,尝试只能到此为止了。然后去论坛里找了找大家的解决方案,思路很新颖。通过分析数据的分布得出结论``数据是造的``。于是乎,对症下药,针对该数据的分布情况,采用GMM估计出原始分布的参数,再用简单分类器以GMM的结果为特征再次分类。我用svm复现了下该方法,确实很厉害,接近0.99。最后拿第一的那位也是融合了下rf的结果后做到的。 52 | 53 | -------------------------------------------------------------------------------- /resources/unpublished-md/使用Pandas的一点经验和技巧.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # 使用Pandas的一点经验和技巧 3 |
2015-05-18 08:41:24
4 |
2015-05-18 08:39:42
5 |
59
6 |
7 | 8 | 上周刚做完ijcai中阿里赞助的一个比赛的第一赛季,无缘前三,甚是可惜。整个过程中都是采用pandas来抽取特征的,总的来说相当畅快,因此希望能够分享些用pandas过程中的经验技巧(坑),其中包含了许多从stackoverflow上查找的答案(我尽量引出出处,好多我找不到原始问答了),但愿大家少走弯路。暂时先写了这7点,以后想到了再补充,另外我把github上的一个[pandas-cookbook](https://github.com/ia-cas/pandas-cookbook)翻译了一下,快速上手应该是够了,更多的还是遇到问题去查看官方文档。 9 | 10 | ##1.读取csv文件的时候,注意指定数据类型 11 | 12 | `read_csv`函数默认会把数值转换成int或者float,但是有时候对于一些ID列而言,这并不是好事。一不小心对ID列加减乘除操作后根本不会有任何提示!此外,string型的数据和int型的数据在打印出来后是没有区别的,有时候你明明看到一个df里有index为12345的这一行数据,df.ix['12345']返回的却是空,很可能原始的index是int型的,换成df.ix[12345]就行了。 13 | 14 | ##2.灵活使用map/applymap函数 15 | 16 | applymap是针对DataFrame类型的,而map则是针对Series类型的。而且,根据文档里的描述map可接收的变量类型要比applymap更丰富,除了函数之外还有dict和Series类型。因此我通常用map来完成一些String类型变量到int类型变量的转换操作,以及一些变量的拉伸(log)剪裁异常值处理等。 17 | 18 | ##3.选取指定行和列 19 | 20 | 这个看起来很简单,不过实际操作起来有不少值得注意的地方,再加上官方文档写得很长很绕,很多时候都不知道该怎么用。 21 | 22 | 首先,说个好习惯。选取指定列的时候,一般有两种方法,一是直接用`df['col_1']`,还有就是`df.col_1`。在列名和默认函数不冲突的情况下,个人建议如果是新增一列,一般在等号`=`左边使用`df['col_1']`的形式,如果是更新一列数据,一般使用`df.col_1`的形式。在等号`=`右边尽量使用`df.col_1`的形式。除非列名是1,2,3,4这种没法通过df.1的形式访问。这样子做的优点是代码结构清晰,一看就明白这是在添加还是更新操作。 23 | 24 | 关于按行切片,说实话很少用。因为大多时候不会直接选取某几行,更多的时候是做一个mask操作,通过条件表达式得到bool变量后再选取。 25 | 26 | pandas的各种选取操作很容易让人搞混,[这里](http://stackoverflow.com/questions/28757389/loc-vs-iloc-vs-ix-vs-at-vs-iat)有人问了,总结起来就是: 27 | 28 | - loc: only work on index 29 | - iloc: work on position 30 | - ix: You can get data from dataframe without to be this in the index 31 | - at: get scalar values. It's a very fast loc 32 | - iat: Get scalar values. I'ts a very fast iloc 33 | 34 | 后面两个先不管,首先来看,我们选取某一列数据的时候,可以通过df['a']来得到一个Series对象,但如果'a'不是列名的话,就会报'KeyError'的错。如果是要获取某一行呢?那就用df.loc['a'],这里’a'就是行的index,所以如果你已知某一行的index,就用.loc来访问这行数据。此外,如果只是知道在第几行,不知道确切的index,那么就用.iloc。切记,这里的iloc是integer-loc的缩写,我一开始理解为了index-loc的缩写,结果就乱了。。。最后的ix既可以接收index有可以接收行号作为参数,不过我一般尽量避免用这个。 35 | 36 | 来个进阶点的问题: 37 | 38 | > 现在我有一个样本集df,我想将其随机打乱后抽取其中的N个作为训练集剩下的作为测试集,该怎么做呢? 39 | 40 | 先想想,答案见[这里](http://stackoverflow.com/questions/15923826/random-row-selection-in-pandas-dataframe) 41 | 42 | ##4.[怎么重命名某些列](http://stackoverflow.com/questions/11346283/renaming-columns-in-pandas) 43 | 44 | 45 | `df.rename(columns={'$a': 'a', '$b': 'b'}, inplace=True)` 46 | 47 | 很多时候我们抽取特征时,需要对特征重命名之后再与其他特征merge以避免命名冲突。实际使用的时候,比上面的稍微会复杂点。例如 48 | 49 | `df.rename(columns={x:x+'_user' for x in df.columns if not x.endswith('_user')},inplace=True)` 50 | 51 | ##5.如何合并多列数据得到一个新的DataFrame 52 | 53 | 我们知道pd.concat可以将多行数据合在一起,通过指定参数axis=1即可按列合并 54 | 55 | ##6.要习惯级联 56 | 57 | 有时候完成某个功能可能会需要很长的一句话才搞定,比如`df1.merge(df2,how='left').merge(df3,how='left').merge(df4')`等等。这样子写出来的代码比较丑,呃不过受限于python的格式要求,没法像java一样换行那样写得很优雅。但我自己仍觉得这样能避免过多的中间变量,有助于思维的连贯性~ 58 | 59 | ##7.groupby的并行化 60 | 61 | groupby相当好用,但是数据量特别大的时候,有个很头疼的问题,慢! 62 | 63 | 当然,增加参数sort=False可以稍微缓解这个问题,但是,还是不能忍。最关键的问题在于,单线程!服务器上有40个核,却只用了1个,太浪费了!所以简单写个多核的版本。(IPython Notebook据说可以很方便地写并行程序,但是,尝试后失败了@_@) 64 | 65 | ```python 66 | from multiprocessing import Pool 67 | 68 | def get_fs(group): 69 | # extract features 70 | # ...... 71 | 72 | return pd.DataFrame({'f1':f1,'f2':f2},index=[0]) 73 | 74 | def get_parallel_features(groups): 75 | p = Pool() 76 | result = None 77 | try: 78 | result = pd.concat(p.map(get_fs, groups)) 79 | except Exception as e: 80 | print "ERROR:",e.strerror 81 | finally: 82 | p.close() 83 | p.join() 84 | return result 85 | 86 | features = get_parallel_features([gp for key, gp in df.groupby(['xxx'])]) 87 | ``` 88 | 89 | 不过上面代码有点问题,可以看到传入的数据是一个列表`[gp for key, gp in df.groupby(['xxx'])]`,如果数据量很大的化这样子一次性读进来是相当吃内存的。不过根据我在stackoverflow上查到的资料显示,即使传一个iterator过去也无济于事,因为Pool().map操作的内部还是会先把iterator的内容全部读出来然后再操作。所以这个并行版本有待进一步优化。 90 | 91 | 92 | -------------------------------------------------------------------------------- /resources/published-html/聊聊最近.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 聊聊最近 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | 聊聊最近 18 |
      19 |
    • 20 | 学习 21 |
    • 22 |
    • 23 | 找工作 24 |
    • 25 |
    • 26 | 最后 27 |
    • 28 |
    29 |
  • 30 |
31 | 32 | 33 |

聊聊最近

34 | 35 |
Life
36 | 37 |

离上次更新有三个多月了吧,这期间乱七八糟的事很多,最近放了个长假,也该静下来了。今天先在这里记录点杂事,接下来尽量多写些有价值的东西。

38 | 39 |

10号域名到期,提前先续费了,懒得折腾域名的转入转出,继续用的namecheap,也就19刀。顺便看了下网站的统计信息,me域名的访问量只占了1/5,远不及ml域名,汗。。。

40 | 41 |

学习

42 | 43 |

先说点学习上的。编程语言方面,最近在学Clojure,感觉这门语言学起来蛮好玩的。说起来也有点意思,其实一开始我是准备学习下scala的,跟风嘛,Spark这么火,怎么说也得了解了解。然后翻了几本scala方面的书,没太有感觉。主要是因为这货跟java太像了!然后还夹杂了一大堆语法。虽说写出来的代码相比java已经简洁了许多,但是出于对java的厌恶我学习的动力大大降低。在有java基础之后学习scala的迁移成本其实是很低的,但是我希望在学习一门新的编程语言的时候,这门语言不应该是我所熟悉的范畴(比如学完python后学ruby),否则很容易陷入到语言本身的语法设定中去了。我希望这门语言与我熟悉的语言之间有比较大的gap,能学到更多编程思想方面的东西。然后那段时间慢慢地就把scala放下了,偶然一次看到了关于jvm平台上的scala还有groovy和clojure的讨论,发现clojure似乎跟自己之前学过的语言有很大不同,主要是因为它是Lisp的一个分支,另外函数式编程的思想也贯穿其中,于是一头扎进去学到现在。

44 | 45 |

读的相关书主要有4本,《clojure编程》、《clojure编程乐趣》(这本书英文版的有第二版了,我对比了下,大部分是一样的,就是最后多了几个章节)、《clojure经典实例》、《七周七并发模型》(当然,这本书不只是介绍了clojure的并发模型,我对并发的内容缺少实践,这部分学得不是特别深)。目前还处于打基础的过程中,缺少一些实际项目来练手,了解storm似乎是个不错的开始。学习clojure带来的最大收获是思考问题上的改变。我发现函数式编程思想在数据处理问题上有很大优势,理解其中的精髓后发现现在写的python代码也越来越简洁了......以后总结下自己的学习心得后再专门讨论下这块。

46 | 47 |

nlp这块最近在做的就是把autoencoder和lstm融一下试试效果,感觉最近做科研的这块很无力。真的是很没有方向感,不知道该怎么深入下去,也就靠读读论文打发时间了......有时候看着那些水文啊,真是无力吐槽了,然后就会安慰安慰自己,唉,不读博也罢。说个上周的一件小事,为了做融合,先要把autoencoder那边的matlab代码改成python的,方便共享cost信息。其实我也没怎么看matlab代码,很久没用过了,听师弟讲了讲代码流程后大致明白了其结构和实现方式,于是花了一天时间草草写了个测试用例。同一个batch的数据反复迭代后,cost是呈现收敛趋势的,然后感觉应该没问题了。给大家讲了讲实现细节后修改了几处错误。

48 | 49 |

本以为这样就可以了,不过徐老师强调让我验证下matlab和python两边的输出结果是否一致。其实一开始我的内心是拒绝的。。。(其实是不屑的...)哥写的怎么会有问题呢???无奈,写样例,测试......打脸了......第一轮前向结果就不一致,甚至最后收敛的结果也总是相差了一点点。看了半天愣是没找出问题所在,更要命的是matlab的代码简直就是个blackbox。实在找不出问题了,我只能把问题归为matlab里神奇的边界条件所导致的结果差异......然而,老师并不信我这套,一定要两边输出结果一致才对!

50 | 51 |

“老师,这个,matlab的代码我不会debug...”

52 | 53 |

“那我们一起来看”

54 | 55 |

“。。。”

56 | 57 |

然后,一整个下午坐在老师旁边一行行读代码,测试,验证。最后的最后,坑在了一个实在是微不足道的地方。计算cost的时候通常为了避免对0求log会加上一个很小的实数\(1.0e^{-10}\),然而,我的样本有一个mask操作,mask以外的cost都为0,本应该对mask部分的cost加上\(1.0e^{-10}\)后求-log,再求mean,结果我代码里为了图方便,先整体加上\(1.0e^{-10}\)后求-log再求mean。但是,\(-log^{(0 + 1.0e^{-10})}\) 并不是趋于0的呀!!!因为自己的失误,还让别人跟着一起填坑......实在是惭愧。

58 | 59 |

总结下来,一是永远不要对自己过度自信,我不知道多少次因为这个毛病坑人坑己了......被女朋友说,被同学说,被老师说,以后还是要更务实一些;二是“凡是有可能出bug的代码一定有问题”,“墨菲定律”?,这个也不是第一次了,好几次隐约觉得可能代码写得有点问题,结果最后都被坑的不轻......

60 | 61 |

找工作

62 | 63 |

再聊聊找工作的事。算了算,自己总共就投了5份简历,(BAT+美团+京东),其它的也就没投了,要么是显然去不了,要么是实在不感兴趣。剩下的时间,可能更有针对地去投几家外企,发现了两家还不错的公司,试试看~

64 | 65 |

百度那边走的就是正常的流程,宣讲会,投简历,面试。面试百度应该是自己的第一次正式面试,有点紧张......现在印象最深的就是二面问了个智力题,想了半天答错了@_@。三面的话,针对自己做的事情,聊的level稍高点。总的来说面试的人都还不错。阿里的话,我就不吐槽了。腾讯是唯一参加笔试的,本来就对腾讯不太感兴趣,好奇跟着参加了个笔试,出的都是什么题啊,简直了!美团那边面试的感觉页很不错,师兄推荐过去的,中午在那吃饭的时候听他们聊了些工作上的事,觉得和自己做的事情挺对胃口的。只是,有时候真的觉得对自己的职业生涯缺少很明确的规划。以至于真要做出选择的时候,很是迷茫......蛮感激曾师兄跟自己聊了许多,不管最后去不去美团,那次谈话都是很大的收获。京东的面试,就一个感觉,我是不是来错了地......面试的人一点都不鸟我......

66 | 67 |

其实从内心来讲,自己倾向于往计算广告方向发展,商业价值更大一些,但这块需要一个好的成长环境。不过呢,觉得做nlp这块好像也还不错,把功底打扎实了以后做周边的东西都能跟得上,不过总觉得有那么一丝丝想法,是不是有必要读个博把这块的工作做得更深入呢?唉,现实的情况是,先得找到个工作,然后再谈实现个人价值。理想和现实的纠缠......

68 | 69 |

最后

70 | 71 |

这个网站的代码好久没更新了,积累了不少问题一直没改,近期打算做一些改进,当时图简便直接用的django框架,接下来如果有时间的化,可能会换个框架,说不定会拿clojure试水~

72 | 73 | 74 | 75 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /resources/published-html/一个微博爬虫的设计原型.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 一个微博爬虫的设计原型 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | 一个微博爬虫的设计原型 18 |
      19 |
    • 20 | 梳理下 21 |
    • 22 |
    • 23 | 目的 24 |
    • 25 |
    • 26 | 设计 27 |
    • 28 |
    29 |
  • 30 |
31 | 32 | 33 |

一个微博爬虫的设计原型

34 | 35 |
Spider
36 | 37 |

梳理下

38 | 39 |

上个月去了趟南京,回来后再加上个国庆假期,整个人就闲下来了.每天,也没太多事情可做,有时候,很享受这样的日子,呵.闲下来的时候,总想找点事做来打发打发时间.

40 | 41 |

目前的兴趣点在网络和图论这块,再加上之前接触了一下图数据库neo4j,感觉得这货以后很有潜力,于是乎,希望能够找个实际点的东西来练练手,然后,很短视地想到适合用图数据库的地方--社交网络.其实也没多想,就敲定了新浪微博这个平台(别问我为啥偏偏是微博).

42 | 43 |

在这里完整记录下自己考虑问题的整个过程(虽然暂时搁置下来了),希望以后考虑其他问题的时候,能从中有所借鉴吧.

44 | 45 |

目的

46 | 47 |

一开始希望做的事情是,能够用图数据库neo4j来存储一些社交网络上的数据,这样以后分析起来效率会高很多,也算是搭建个平台起来吧.

48 | 49 |

设计

50 | 51 |

做爬虫这种事,必然有很多人已经做过了,所以,最重要的是前期调研,然后,找到了知乎上点赞最多的这个回答,总的来说,很有收获.所以,自己后面数据流的设计基本是参照这个来的.为了后面阐述方便,先看大图:

52 | 53 |

54 | 55 |

注:上图有个问题,实际上不应该使用Queue,而是使用set或者order set的结构来存储

56 | 57 |

可能大多数人做爬虫只是简单的上来先写好网络接口,配置好存储方式就开始了.不过,在这里,我是希望整个过程能够有一个良好的可扩展性.因此,需要考虑的事情会更多.

58 | 59 |

首先,明确两条主线.时间流和数据流.

60 | 61 |

先说数据流这里采用自顶向下的思考方式.首先来考虑数据在图数据库中怎么存储的,假设现在有了1kw的用户及其微博(平均每人提取300条),那么该如何在图数据中表示这张超大的关系网呢?图数据库中基本的元素是点和边,自然一种最简单的方式是,以用户为node,以用户和用户之间的关系为edge来构造这个图.初看起来似乎完全没有问题,但是这里把许多细节高度抽象化了,还有很多细节需要考虑,比如:

62 | 63 |
    64 |
  1. 微博内容存在哪?(这个可以存在用户节点内,以json格式组织,问题不大)
  2. 65 |
  3. 如何体现用户与用户之间关系(edge)的差异性?(比如,虽然我同时关注了几百号人,但是我跟其中的某些人关系很熟,经常互动,但是另外一些人可能虽然关注了但很少联系)
  4. 66 |
  5. 通过微博@用户的关系如何体现?用户对微博的评论,点赞,转发呢???(有个改进的方案,微博还是放在用户节点下,但是通过丰富用户与用户之间关系(edge)的属性(加入评论,转发,@等),强化用户关系之间的描述)
  6. 67 |
  7. 图数据库的优势体现在哪?
  8. 68 |
69 | 70 |

上面的分析可以发现,最大的问题在于,微博怎么放.由于微博在这里已经不再是一个简单的文本,而是负担起了网络中连接器的功能,所以有必要将其独立出来作为存储的节点,从而用户与用户之间,用户与微博之间,微博与微博之间的关系更加清晰.那么这样子做是否可行呢?有一个最大的担忧是,将微博作为节点的化,整个数据库的节点数目会接近30亿,那么这就对neo4j的性能提出要求了,neo4j能否容纳这么大量的节点数目呢?经过一番调研,发现是可以做到的,问题是,一台小型机恐怕有点容不下,需要分布式的结构,但是neo4j的社区版不支持分布式安装,还好有个人版.这块先这样.再往下,每个爬虫爬到数据后,通过pipeline将数据扔到数据库里.至于数据去重的部分,放在爬虫的功能设计中去.

71 | 72 |

再从时间流的角度考虑如何爬数据.简单的分析可以看出,用户之间的关系网很简单,而微博的关系网比较复杂,需要打开每一个微博的页面单独去爬评论,点赞之类的.这会导致用户数变得不可控,这里说的不可控是指,在整个网络中会产生大量的孤立节点.考虑到后期分析用户之间关系的目的,这些数据会变得根本不可用.因此,设计思路就变成了,首先爬取用户与用户之间完整的关系网,然后,通过微博的内容来完善该关系网.因此,从时间上考虑,可以简单分为以下3步.

73 | 74 |
    75 |
  1. 随机选取种子用户
  2. 76 |
  3. 爬取用户的关系网络,同时爬取用户的个人信息
  4. 77 |
  5. 等整个数据库中用户数达到一定规模后,停止爬取用户网络,继续爬取用户的个人信息(这个会比较慢)
  6. 78 |
  7. 遍历数据库中的用户,读取其微博内容,以及爬取的网络中其他用户对该微博的评论,赞等
  8. 79 |
80 | 81 |

爬虫需要设计好如何避免重复,这里采用分布式的redis来实现该过程.每次开始爬数据之前,先向redis发起请求获取一批用户id,爬完后返回结果到pipeline,pipeline负责向neo4j保存数据,同时向redis放入下一步应该要爬的用户id.

82 | 83 |

这样子,整个流程就走通了.

84 | 85 |

于是乎,兴高采烈地写代码咯~

86 | 87 |

先用requests写了个登陆程序,再换成scrapy环境下的,接着写好pipeline,配置好redis和neo4j,这些都很顺畅.

88 | 89 |

可是,刚开始爬了几分钟,挂了...

90 | 91 |

WTF!!!

92 | 93 |

ip被封了...

94 | 95 |

悲剧的,好吧,用代理,

96 | 97 |

问题来了,代理上哪找,免费的没几个可用,

98 | 99 |

更蛋疼的问题是,异地登陆要验证码......

100 | 101 |

虽然微博的验证码没那么复杂,但是时间成本略大...

102 | 103 |

总之,这个事情就这么暂时搁置下来了,只能说,what a pity

104 | 105 | 106 | 107 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /resources/published-html/The EM Algorithm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | The EM Algorithm 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 22 |
    23 |
  • 24 | EM算法 25 |
      26 |
    • 27 | 问题 28 |
    • 29 |
    • 30 | 最大似然 31 |
    • 32 |
    • 33 | EM 34 |
    • 35 |
    36 |
  • 37 |
38 | 39 | 40 |
Algorithm
41 | 42 |

EM算法

43 | 44 |

偶然看到一篇文章讲EM算法,感觉讲得很清晰。文中用到了一个抛硬币的问题,这里“搬运”过来,重新阐述下。

45 | 46 |

问题

47 | 48 |

假设现在有两枚硬币,随机抛掷之后,正面朝上的概率分别设为\(\theta_A\)和\(\theta_B\),然后我们做了5轮实验,每轮随机选一枚硬币(记为\(z_i\)),抛10次并记录结果(记为\(\boldsymbol{x}_i\)),现在我们希望估计出\(\theta_A\)和\(\theta_B\)的值。

49 | 50 |

最大似然

51 | 52 |

假设\(z_i\)是已知的,那么只需最大化对数似然\(\log (x, z; \theta)\)即可,根据采样独立性假设,将log似然对\(\theta\)求导之后,可以得到\(\theta\)的点估计:

53 | 54 |

\[ 55 | \begin{equation} 56 | \hat {\theta_A} = \frac {选择硬币A时正面朝上的次数} {选择硬币A的次数} 57 | \end{equation} 58 | \]

59 | 60 |

EM

61 | 62 |

假如隐变量\(z\)不可知,那么就无法通过上面的方法求解了。不过,我们可以先随机设置\(\theta\)的一组取值,然后根据观测数据\(\boldsymbol{x}\)“猜测”隐变量的分布\(z\),再利用该隐变量的估计值和观测数据\(\boldsymbol{x}\),按照前面最大似然的做法去计算\(\theta\)的值,如此迭代直至\(\theta\)收敛。

63 | 64 |

需要注意的是,这里“猜测”的是\(z\)的分布,而不是具体的某个值,联系到K-means算法中,通常的做法是将每个中心点做替换,这里相当于只是做“软分类”。

65 | 66 |

具体流程见原图:

67 | 68 |

69 | 70 |

下面我用Python代码将图中的流程复现下:

71 | 72 |
import numpy as np
 73 | from scipy.stats import binom
 74 | 
 75 | observed = """
 76 | HTTTHHTHTH
 77 | HHHHTHHHHH
 78 | HTHHHHHTHH
 79 | HTHTTTHHTT
 80 | THHHTHHHTH"""
 81 | 
 82 | x = np.array([[1 if c=="H" else 0 for c in line] for line in observed.strip().split()])
 83 | x_heads = x.sum(axis=1)
 84 | x_tails = 10 - x.sum(axis=1)
 85 | assert np.all(x_heads == np.array([5,9,8,4,7]))
 86 | 
 87 | def calc_z(theta):
 88 |     za_est = binom.pmf(x_heads, 10, theta[0])
 89 |     zb_est = binom.pmf(x_heads, 10, theta[1])
 90 |     return za_est / (za_est + zb_est), zb_est / (za_est + zb_est)
 91 | 
 92 | def calc_theta(z):
 93 |     h_A = (x_heads * z[0]).sum()
 94 |     t_A = (x_tails * z[0]).sum()
 95 |     h_B = (x_heads * z[1]).sum()
 96 |     t_B = (x_tails * z[1]).sum()
 97 |     return h_A / (h_A + t_A), h_B / (h_B + t_B)    
 98 | 
 99 | def is_nearly_same(theta_pre, theta_cur):
100 |     return np.abs(sum(theta_pre) - sum(theta_cur)) < 1e-5
101 | 
102 | theta_pre = [0.6, 0.5]
103 | 
104 | for i in range(1000):
105 |     theta_cur = calc_theta(calc_z(theta_pre))
106 |     if is_nearly_same(theta_pre, theta_cur):
107 |         print i, theta_cur
108 |         break
109 |     else:
110 |         theta_pre = theta_cur
111 | # 11 (0.7967829009034072, 0.51959543422720311)
112 | 113 |

显然,EM只能找到局部最优解,可以通过设置多个随机起始点来缓解这个问题,原文对这个问题有讨论。

114 | 115 |

此外,一点个人感受,这里随机初始点有点像先验,然后通过似然更新,得到一次后验估计(即先验和最大似然的中和),如此迭代,有意思。

116 | 117 | 118 | 119 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /src/blog_clj/parse.clj: -------------------------------------------------------------------------------- 1 | (ns blog-clj.parse 2 | (:require [hiccup.core :as hc] 3 | [clojure.string] 4 | [clojure.java.io :as jio] 5 | [net.cgrand.enlive-html :as el :refer [defsnippet deftemplate]])) 6 | 7 | ;; highlight the navbar 8 | (defsnippet navbar-sp "templates/base.html" 9 | [:div.col-xs-3] 10 | [url-prefix] 11 | [[:a (el/attr-has :href url-prefix)]] (el/add-class "active")) 12 | 13 | ;; fill blog-post title 14 | (defsnippet blog-title "templates/base.html" 15 | [:h1.post-title] 16 | [blog-id blog-title] 17 | [:a] (el/do-> (el/content blog-title) 18 | (el/set-attr :href (str "/essays/" blog-id)))) 19 | 20 | ;; generate blogroll sidebar 21 | (defn blogroll-sp 22 | [blogrolls] 23 | (el/html [:div {:class "sidebar-container"} 24 | [:h1 "Blogroll"] 25 | (for [[url blog-name desc] blogrolls] 26 | [:p [:a {:href url} blog-name] [:span desc]])])) 27 | 28 | ;; generate archives sidebar 29 | (defn archives-sp 30 | [archives] 31 | (el/html [:div {:class "sidebar-container"} 32 | [:h1 "Archives"] 33 | [:ul 34 | (for [[title id] archives] 35 | [:li [:a {:href (str "/essays/" id)} title]])]])) 36 | 37 | ;; generate tags sidebar 38 | (defn tags-sp 39 | [tag-list tag-type] 40 | (el/html [:div {:class "sidebar-container"} 41 | [:h1 "Tags"] 42 | [:ul 43 | (for [[tag counts] tag-list] 44 | [:li [:a {:href (format "/search/?tag-type=%s&tag=%s" tag-type tag)} 45 | (if (empty? tag) "NIL" tag)] 46 | [:span {:class "badge"} (str counts)]])]])) 47 | 48 | ;; generate toc sidebar 49 | (defn toc-sp 50 | [toc] 51 | (el/html-snippet 52 | (hc/html [:div {:class "sidebar-container"} 53 | [:h1 "TOC"] 54 | (apply str (el/emit* toc))]))) 55 | 56 | (defn blog-tag-sp 57 | [tags] 58 | (clojure.string/join 59 | "," 60 | (for [tag tags] 61 | (hc/html [:a {:href (str "/search/?tag-type=blog&tag=" tag)} tag])))) 62 | 63 | (defn disqus-sp 64 | [page-url page-id] 65 | (let [disqus-template (.getResource (clojure.lang.RT/baseLoader) "templates/disqus.html")] 66 | (el/html-snippet (format (slurp disqus-template) 67 | page-url 68 | page-id)))) 69 | 70 | (deftemplate base-template "templates/base.html" 71 | [{:keys [nav-type blogroll archives all-blog-tags body title 72 | update-time create-time tags blog-id blog-url toc]}] 73 | [:title] (el/content title) 74 | [:div.blog-nav] (el/content (navbar-sp nav-type)) 75 | [:h1.post-title] (el/substitute (blog-title blog-id title)) 76 | [:span.update-time] (el/content update-time) 77 | [:span.create-time] (el/content create-time) 78 | [:span.blog-tags] (el/html-content (blog-tag-sp tags)) 79 | [:div.post-content] (el/content body) 80 | [:div.post] (el/after (disqus-sp blog-url blog-id)) 81 | ;; sidebars 82 | [:div.sidebar] (el/do-> 83 | (el/prepend (toc-sp toc)) 84 | (el/append (blogroll-sp blogroll)) 85 | (el/append (archives-sp archives)) 86 | (el/append (tags-sp all-blog-tags "blog")))) 87 | 88 | ; ============ 89 | (defn search-tag-sp 90 | [tag blog-infos] 91 | (el/html [:div {:class "content"} 92 | [:h1 {:class "text-center"} (str "Tag:" tag)] 93 | [:p {:class "post-title"} 94 | (for [[title id] blog-infos] 95 | [:p [:a {:href (str "/essays/" id)} title]])]])) 96 | 97 | (deftemplate blogtag-search-template "templates/base.html" 98 | [tag blog-infos all-blog-tags] 99 | [:div.content] (el/substitute (search-tag-sp tag blog-infos)) 100 | [:div.sidebar] (el/append (tags-sp all-blog-tags "blog"))) 101 | 102 | ; ============ 103 | (defn about-sp 104 | [] 105 | (let [about-html (.getResource (clojure.lang.RT/baseLoader) "templates/about.html")] 106 | (el/html-snippet (slurp about-html)))) 107 | 108 | (deftemplate about-template "templates/base.html" 109 | [] 110 | [:div.blog-nav] (el/content (navbar-sp "/about/")) 111 | [:div.content] (el/substitute (about-sp))) 112 | 113 | (deftemplate page-404 "templates/base.html" 114 | [] 115 | [:div.content] (el/content "抱歉,最近网站做了一些调整,删除了部分内容,如果你对将要访问的内容非常感兴趣,请直接联系我~")) 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blog-clj 2 | 3 | 这里记录的是我博客的后端代码,这是第二版了,第一版是刚学clojure的时候写的,各方面都写得很凌乱,如今又看了下clojure,还是打算重写下后端的逻辑,同时加入单元测试,希望这一版能够写得好看点~ 4 | 5 | ## Prerequisites 6 | 7 | 首先需要安装 [Leiningen][] 2.0.0 或者更新的版本;然后还需要安装[redis][] 3.2.0或者更新的版本,部署的系统是Ubuntu 14.04。 8 | 9 | [leiningen]: https://github.com/technomancy/leiningen 10 | [redis]: http://redis.io/ 11 | 12 | ## Introduction 13 | 14 | src目录下文件的说明如下: 15 | 16 | ``` 17 | ├── src 18 | │   └── blog_clj 19 | │   ├── handler.clj ;; 用compojure处理请求 20 | │   ├── page_generators.clj ;; 从redis里获取数据后填充template 21 | │   ├── parse.clj ;; 解析markdown导出的html文件,提取meta信息 22 | │   ├── redis_io.clj ;; 与redis交互的一些函数接口 23 | │   ├── schedules.clj ;; 用于开启一些周期性的调度任务,暂时没用到,之前是规划着周期性fetch一些信息用的 24 | │   ├── sync.clj ;; 用于把本地html文件同步到数据库中 25 | │   ├── upload_download.clj ;; 处理些上传下载的任务 26 | │   └── webhooks.clj ;; 处理github的webhook,同步文件的更新 27 | ``` 28 | 29 | 需要说明的几点: 30 | 31 | 1. 抛弃了以前直接从md文件渲染得到html文件的做法,原因很简单,clj下的markdown渲染库只找到了[markdown-clj][]这一个,但是支持的功能实在有限,作者更新得也比较慢,以我现在的水平自己重写一个还是需要花不少时间。而且本地写markdown文件现在用的是[MacDown][],如何同步也是个挺麻烦的事,也考虑过直接与[MacDown][]交互,但没找到合适的接口,所以干脆直接解析html文件好了。 32 | 2. 模板引擎方面,采用了[Enlive][]和[Hiccup][],优点自然是template与data分离,这样不必像在[Django][]中一样在template中嵌入数据与逻辑。但[Enlive][]似乎作者不打算维护了,后期可能需要在[Hickory][]的基础上做一些封装,替换掉[Enlive][]; 33 | 34 | [markdown-clj]: https://github.com/yogthos/markdown-clj 35 | [MacDown]: http://macdown.uranusjr.com/ 36 | [Enlive]: https://github.com/cgrand/enlive 37 | [Hiccup]: https://github.com/weavejester/hiccup 38 | [Django]: https://www.djangoproject.com/ 39 | [Hickory]: https://github.com/davidsantiago/hickory 40 | 41 | ## Deploy 42 | 43 | 首先,需要配置几个环境变量: 44 | 45 | ```bash 46 | export qiniu_sk="XXX" 47 | export qiniu_ak="XXX" 48 | export html_path="/path/to/blog-clj/resources/published-html/" 49 | export upload_path="http://your-qiniu.qiniudn.com/upload/" 50 | 51 | # gitub仓库对应的webhook密码 52 | export github_webhook_secret="XXX" 53 | ``` 54 | 55 | 然后,本地开发调试直接在根目录下运行``lein ring server``,修改代码后,server会自动重新reload对应的命名空间。 56 | 57 | 部署的话,需要先编译``lein ring uberjar``,采用Supervisor部署,一方面是因为Ubuntu下的Upstart配置了老半天,环境变量就是传不进去,很无奈;另一方面采用Supervisor可以让本地的(Mac)环境和线上的环境尽量一致,方便调试。 58 | 59 | 1. 新建deploy用户, ``sudo adduser -m deploy && sudo passwd -l deploy`` 60 | 2. 将代码放在deploy用户的目录下 61 | 3. 安装并运行Supervisor 62 | 4. 在``/etc/supervisor/conf.d/blog.conf``写入以下内容,然后``supervisorctl reread && supervisorctl update && supervisorctl start blog``即可。 63 | 64 | ``` 65 | [program:blog] 66 | environment=qiniu_sk="XXX",qiniu_ak="XXX",html_path="/path/to/blog-clj/resources/published-html/",upload_path="/path/to/blog-clj/resources/published-html/",github_webhook_secret="XXX" 67 | command= java -jar target/blog-clj-0.1.0-SNAPSHOT-standalone.jar 68 | directory=/path/to/blog-clj 69 | autostart=true 70 | autorestart=true 71 | startretries=3 72 | user=deploy 73 | ``` 74 | 75 | 76 | ## TODO 77 | 78 | - [ ] log日志重定向到文件 79 | - [ ] 增加对错误异常的处理 80 | - [ ] eastwood 静态检查 81 | 82 | ## Resources 83 | 84 | - [Easily Deploy Your Clojure Web Site](http://www.braveclojure.com/quests/deploy/) 85 | 86 | ## License 87 | 88 | ``` 89 | MIT License 90 | 91 | Copyright (c) 2016 Tian Jun 92 | 93 | Permission is hereby granted, free of charge, to any person obtaining a copy 94 | of this software and associated documentation files (the "Software"), to deal 95 | in the Software without restriction, including without limitation the rights 96 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 97 | copies of the Software, and to permit persons to whom the Software is 98 | furnished to do so, subject to the following conditions: 99 | 100 | The above copyright notice and this permission notice shall be included in all 101 | copies or substantial portions of the Software. 102 | 103 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 104 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 105 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 106 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 107 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 108 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 109 | SOFTWARE. 110 | ``` 111 | -------------------------------------------------------------------------------- /resources/published-html/Write Python in Lisp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Write Python in Lisp 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | Write Python in Lisp 18 |
      19 |
    • 20 | Hylang是什么? 21 |
    • 22 |
    • 23 | Hylang不是...... 24 |
        25 |
      • 26 | Hylang不是Clojure 27 |
      • 28 |
      29 |
    • 30 |
    • 31 | 体验过程中的一些坑 32 |
    • 33 |
    • 34 | Hylang适合写点什么? 35 |
    • 36 |
    37 |
  • 38 |
39 | 40 | 41 |
Python,Lisp,Hylang
42 | 43 |

Write Python in Lisp

44 | 45 |

上周在Clojure微信群里,Steve Chan分享了个关于Hylang的链接,让人眼前一亮,原来Python居然还可以这么写!经过这几天的摸索,意外地感觉不错,在这里推荐给大家试试,有兴趣的话可以看完官网的doc后再回来看下文。

46 | 47 |

Hylang是什么?

48 | 49 |

Hy是基于python的lisp方言,与python的互操作非常顺畅,有点类似clojure于java的关系。从安装上可以看出,Hy实际上就是一个普通的python库,在python代码中可以直接import hy之后,把.hy文件当做普通的python文件,import其中的变量。核心代码部分,该有的也都有(最重要的当然是macro),可以从clojure无障碍迁移过来。

50 | 51 |

由于是直接把lisp代码转换成AST,开启--spy模式之后,可以看到每一行lisp代码转换之后的python代码,各种库的操作也完全没有障碍。试用了一些常用的库,基本没有什么大问题。目前感觉不是够顺畅的地方,反而是一些外围,比如没有很好的编辑环境。社区的vim插件提供的功能很弱,为此我特地入坑spacemacs!emacs对应的插件稍微好点,提供了发送代码到repl的功能,不过最重要的仍然是,没有代码补全,网上有人提供了一些静态的补全方案,通过提取hy库中的关键词和当前buffer中的变量名来补全(没有配置成功......),不过实际使用中会大量调用python库,因此急需像python里的anaconda-mode一类工具提供辅助补全。再比如静态语法检查,调试。

52 | 53 |

Hylang不是......

54 | 55 |

Hylang不是Clojure

56 | 57 |

这个是首先需要意识到的一点。尽管在语法和许多函数上和clojure很像,但是因为底层实现和语言的定位不一样,这其中的许多函数不再是clojure中对应函数的完整复制。以下列举一些很容易碰到的问题:

58 | 59 |
    60 |
  1. muttable & immutable. Hylang本身的定位是鼓励与python的互操作,因此大量的操作都是基于python本身的数据结构,需要非常小心数据随时都可能改变。在写Hylang代码的时候需要时刻提醒自己,“我写的是python代码!代码都会最终转换成python代码去执行!”,社区里最近也在讨论引入immutable的数据结构,不知道这块以后会怎么发展。
  2. 61 |
  3. lazy. Hylang中大多数代码的lazy实现都是基于generators实现的了iterable,这下就蛋疼了。在python里,生成器访问一次之后,如果你不保存的话,数据就没有了......所以你会发现(take n coll)中,如果xs是一个iterable的数据,上面的代码执行多次是可能得到不同结果的。甚至如果不保存的话,没法访问已经被访问过的内容。不过好在0.12.0之后提供了lazy sequences,一定程度上缓解了这个问题。
  4. 62 |
  5. in-place operations.在python中,许多函数都是默认in-place的,比如sort,random.shuffle等,有些可能提供了对应的非in-place的函数(如sorted),有些则没有。这点需要格外注意,否则,定义变量的时候很可能返回值就是个None。不过在numpy,tensorflow,pandas等库中,这点考虑得比较全面
  6. 63 |
  7. scope. 看github上过去关于let绑定的issue,可以深入了解这块内容。在不确定变量名的scope时,可以看看对应的python代码。
  8. 64 |
65 | 66 |

体验过程中的一些坑

67 | 68 |
    69 |
  1. 文件名。写过了clojure的话会习惯-作为连接符,hy的文件名需要转换成_连接符,否则在python代码中不能import。
  2. 70 |
  3. 某些函数的的实现有bug。我自己在尝试的过程中就发现了partition函数的实现有点问题,在github上提了个issue。社区里的反应还是挺快的,第二天就解决并合并到master上了。
  4. 71 |
  5. 参数传递过程中,运用apply传递positional和named arguments时,需要分别用list和dict对二者封装,不能偷懒直接用一个list搞定。
  6. 72 |
73 | 74 |

Hylang适合写点什么?

75 | 76 |

写Hylang也就这几天,对macro的感受还不是很强烈,主要是写了点日常的数据分析代码和tensorflow中的tutorial,以下是一些个人感受:

77 | 78 |
    79 |
  • 如果只是写一些调用API的代码,其实不太适合。比如我在翻译tensorflow的tutorial过程中,需要反复去查对应的API,很繁琐,而且已有的框架会在不知不觉中对写lisp风格的代码有一些限制,从而使得python代码更适合命令式地处理逻辑。
  • 80 |
  • 适合更抽象层的数据预处理逻辑。这块写起来会很舒服,对读代码和写代码的人来说,都是一种享受。可以将二者结合,这部分代码用hy处理后以接口的形式暴露给模型构建部分,最后再用hy糅合train,valid,test的过程。当然,现在某些库(tensorlayer)实际上把直接跟tensorflow打交道的部分做了很浅的一层封装,整体易用性更高了。
  • 81 |
82 | 83 |

最后,一点学习经验:

84 | 85 |
86 |

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. -- Mastering Clojure Macros

87 |
88 | 89 |

另外,这个语言还是太小众了,玩玩就可以了,别太当真......

90 | 91 | 92 | 93 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /resources/published-html/(4)一起用python之基础篇——入门书.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | (4)一起用python之基础篇——入门书 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | (4)一起用python之基础篇——入门书 18 |
      19 |
    • 20 | 写在前面 21 |
    • 22 |
    • 23 | 我是怎么开始了解python 24 |
        25 |
      • 26 | 你只需要掌握最基础的 27 |
      • 28 |
      • 29 | 脚踏实地,出来混,迟早是要还的 30 |
      • 31 |
      • 32 | 多读书,都好书 33 |
      • 34 |
      • 35 | 好用才是王道 36 |
      • 37 |
      38 |
    • 39 |
    40 |
  • 41 |
42 | 43 | 44 |

(4)一起用python之基础篇——入门书

45 | 46 |
Python,Book
47 | 48 |

写在前面

49 | 50 |

从快毕业的时候在图书馆里借来第一本有关python的书算起,接触python的时间也不过半年有余。时间真的很短,很难有什么经验之谈,自己至今也仍有许多需要学习的地方。不过对于怎么入门这一块,倒是颇有感触。在这里记录下来,也许能对后人有所帮助吧~

51 | 52 |

我是怎么开始了解python

53 | 54 |

快毕业的时候,在中南的图书馆里瞎逛,偶然之间看到这么一本书,《可爱的python》。第一眼看上去,只是觉得书名还挺新颖的,反正也是闲着,抽出来看看吧。“人生苦短,我用python”,这是我在封面上看到的第一句话,这感叹句实在太吸引眼球,以至于这么长时间后,我早忘了书中讲的什么内容。留在脑海中的就只有封面上的这句话和作者的前言。

55 | 56 |

当时看完前言部分,我就感慨良多。一本好的编程入门书,不应该是一上来就告诉你怎么写Hello World,给你介绍变量、函数、控制流 blablabla...,而是作者站在一个朋友的角度来和你谈心,告诉你他自己学习这门编程语言的经历,他自己所体会到的这门编程语言的魅力在哪里,有哪些优点和不足之处,怎样能够更快更好地熟悉这门语言。这感觉就和当初学C++时候读的第一本书《Thinkng in C++》一样。作者提到,由于python这门语言的特殊性,对它的学习并不必拘泥于传统的教科书式的学习方式,而是重点在“使用”中学习,其基本思想就是用最短的时间掌握python最基础最核心的语法,然后在使用中碰到具体的问题时候,再去主动学习相关知识。这个观念对我的影响很深,可以说,回顾自己的历程,基本就是按照这个原则来的,而且收获确实很多。

57 | 58 |

下面就结合我自己的学习经历,谈谈刚入门时候的基本原则。

59 | 60 |

你只需要掌握最基础的

61 | 62 |

刚开始学习python的时候,可能会查看许多书,这些书为了能够涵盖得尽量全面,往往会涉及语言方方面面的细节。但是,并不是每一个知识点都是你所需要的。一开始你只需要掌握最基础的那部分知识。你可能会问,“我哪知道哪些是最基础的东西呢?” 我觉得,一个很简单的判断方法就是,拿起书都第一遍的时候,如果你能硬著头皮看下去并且能够理解里面所讲的内容,那很好,这就是最基础的。如果看了第一遍后云里雾里,鬼才知道哪天会用得上这些东西。OK,专门找个小笔记本,记下这部分内容方便以后查阅,然后,跳过这部分。我在第一次看decorator装饰器这个部分的时候实在看不下去,也不知道可能会有啥用,果断跳过,最近上高性能计算的课,学习下cuda的python接口时,里面都是装饰器修饰的函数,才又好好学习来一下,结合来自己的实际问题,这样理解起来也就更深入。

63 | 64 |

脚踏实地,出来混,迟早是要还的

65 | 66 |

记住,前面你跳过的那些问题,迟早是会冒出来的。你自己得清醒地意识到,这种刻舟求剑式的做法,是存在一些弊端的,虽然大多数时候,这些弊端不过是自己动手来实现一些别人已经实现来的东西,多花点时间精力罢了,但还有的时候,你可能会付出沉重的代价。类似的教训实在太多,比如看书的时候觉得itertools这个包没有太大用就跳过了,后来有一天要实现个排列组合的算法时花了很长时间来实现,结果偶然一天看到这货居然内置在iterrools里了;还有迭代器和生成器那部分,一开始以为自己可能用不到,后来要对一堆很大的文本做分析时候才发现内存不够了......所以说,出来混,迟早是要还的,那些跳过了东西,迟早某一天要出来坑你一把。那肿么办咧,跳还是不跳,这是个问题,个人觉得,刚入门的时候,还是能跳就跳吧。等自己对这门语言产生兴趣了,再来深入了解其语言的细节,也不算太晚。

67 | 68 |

多读书,都好书

69 | 70 |

关于python的书虽不如C++,Java之类的那么多,但好书却不少了,这半年看了有十多本书了吧,整体感觉质量都挺不错。以下按照由浅入深的顺序来推荐给大家。

71 | 72 |
    73 |
  • 相信我,你看的第一份文档,应该是The Python Tutorial。什么?英语的看不懂!我去,你都还没开始看!!!
  • 74 |
  • 看完上面的教程后,你可能会有种意犹未尽的感觉,难道,只需要这么点知识我就算入门了吗?如果你看完毫无压力,我只能说真的,这样就算入门。不过除此之外还有另外一些讲解python基础书,也值得一看。你应该把大多数时间花在上面这份tutorial上,下面(1)中基础点的书应该是当作补充。看这几本书的时候,牢记上面的两条原则!(我是不会告诉你下面的这些书大多都有中文版的:~)

    75 | 76 |
      77 |
    1. 基础点的:A Byte of Python, learn python the hard way
    2. 78 |
    3. 稍稍进阶点的:dive into python 3
    4. 79 |
    5. 需要当工具书一样看的:The Python Standard Library byExample
    6. 80 |
    7. 骨灰级的:Python Cookbook, 3rd Edition
    8. 81 |
  • 82 |
83 | 84 |

好用才是王道

85 | 86 |

看完上面这些书,你应该对python的基本语法特性,内部的标准库有了很深的了解。但是,我最想说的是,并不一定要等的你把这些书都读完了才开始做些事,(事实上,读完那份tutorial你就可以动手做很多事了)。你应该很清楚的知道自己要用python来做什么!!!想当初大一学c语言时候,学了也不知道为什么而学,所以啊,最后学完了那些语法知识后全都丢到一边,我那时候哪还知道c可以用来干那么多事。就我自己而言,学习python的目的是为了在一定程度上代替matlab作为科学计算工具,利用其丰富的包来实现许多功能,另外,用python写的代码可读性很高,不管是自己写还是读别人的代码,都是一种享受。

87 | 88 |

我想,你也一定有自己使用python目的,比如想用python爬网络上的资源,比如要用python建个网站,又或者是要和服务器上的后台打交道...你总可以找到自己要学习的那个部分,记住,把重点花在这里!。然后,等你对python有一些感性认识了,某一天自然会想起来要了解下python的底层是怎么实现的,为什么这样做比那样做更好等等问题。

89 | 90 |

编程语言说到底也只是工具罢了,工具固然是越好用越好,但更重要的是你要知道拿这些工具去解决什么样问题,以及怎样去解决!

91 | 92 | 93 | 94 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /resources/published-html/最近的一些感悟.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 最近的一些感悟 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | 最近的一些感悟 18 |
  • 19 |
  • 20 | 1. 三个月里学到了些什么? 21 |
      22 |
    • 23 | 1.1 Spark 24 |
    • 25 |
    • 26 | 2.2 进度控制和管理 27 |
    • 28 |
    29 |
  • 30 |
  • 31 | 2. 写项目和做比赛的一些区别 32 |
  • 33 |
  • 34 | 3. 一些工作了才发现的事 35 |
  • 36 |
  • 37 | 4. 近期的一些规划 38 |
  • 39 |
40 | 41 | 42 |

最近的一些感悟

43 | 44 |
45 | 46 |

看了下时间,距离上次更新博客差不多三个月了,也意味着来滴滴有三个月了。梳理了下,这三个月的事也挺多的,毕业答辩、毕业、实习和工作。现在回顾这三个月,感觉转变真的很大,趁今天入职培训有点闲暇的时间,记录下这段时间的感受,也算是分享下自己的近况吧。

47 | 48 |

1. 三个月里学到了些什么?

49 | 50 |

回想三个月前,自己还坐在实验室的工位上写代码做数据分析,如今那地方早已被大一的小朋友们占满了,让人不得不感慨良多。自从上一篇博客烂尾后,就去了滴滴实习,也就是现在工作的地方。其实,前期并没做很多事情,因为毕业的事,实习也都是三天打鱼两天嗮网,到每个月底数工资的时候发现,其实一个月里也就半个月在公司。实际的工作时间虽然不多,不过这三个月下来也马马虎虎对整个项目流程有了些了解,对整个公司也有了更深的认识,随便记录点东西,留着以后回头再看。

51 | 52 |

1.1 Spark

53 | 54 |

以前虽然对spark也有所了解,然而并没有应用的场景。去组里最早的任务也就是熟悉下spark任务和相关的脚本了,期间对spark 的API有了一定的熟悉,不过到现在为止,也没怎么熟悉MLLib相关的应用和实现。最早的时候,是接手组里另外两个实习生的一些特征提取的工作,讲真,理解完代码时的一个感受是,“嗯,终于看到比我的Python写得还烂的了...",不过呢,等到自己后面写的时候才发现,自己写出来的一些特征提取代码也强不到哪去(后面再细聊这块)。

55 | 56 |

新人用spark面临的第一个问题一般就是,要不要学Scala呢???记得很久之前学了一段时间的scala,应该是2015年年底附近吧,到最近早就忘得一干二净了,记得当时Scala留给自己的印象就一个字,”杂“。深知这语言里的坑太多,于是实习期间的时候果断跳过了,平时的spark任务基本都是调的Python API,除了期间因为代码交流的原因,又复习了下Scala的语法。然而,大规模云平台上使用Python的一个问题是,包管理(据说Golang的人老习惯拿这点对比Python了)。如果要在这条路上继续走的话,我估计Scala还是得硬着头皮去掌握。。。闲暇的时候尝试了下clojure下的两个spark相关的库,一个是flambo,还有一个sparking,后者感觉还不错,写出的代码要优雅得多,不过,工作归工作,写出来的东西毕竟是要跟其他人沟通的,clojure还是太小众,也就自己玩玩而已了。

57 | 58 |

2.2 进度控制和管理

59 | 60 |

技术或者算法方面的学习不是特别多,不过这段时间学到的最重要的应该就是进度的控制和自我管理了。可能自己是自由散漫惯了,在所里的干活的时候都是看心情,早上可能都去得比较晚,完成实验什么的其实并没有什么具体的规划,走一步看一步的那种感觉吧,有状态的时候感觉效率奇高,没状态的时候基本一整天都莫名其妙地过去了。而去了公司的一个整体感受是,一整个项目的分工和拆解相当明确,个人负责自己的一块,再拼接成一个整体。其实刚开始那段时间很不适应,主要原因是总想着对整个项目都有个完整了解后,再考虑去完成自己擅长的那部分。然而,事实是我足足花了一个多月的时间去梳理整个项目,这期间自己的贡献基本为0(更要命的是我在新来的实习生身上似乎也看到了同样的现象),后来观察组里其他人的工作方式,几乎都会把任务拆解到很小的可实现的几个点上,与此同时不断熟悉上下游的代码,这样同时兼顾了杂事的处理和业务逻辑的学习。要是回到3个月前,我一定要告诉自己的一件事就是,不要贪多,step by step!

61 | 62 |

2. 写项目和做比赛的一些区别

63 | 64 |

知乎上有人讨论过一个问题你实践中学到的最重要的机器学习经验是什么,显然我还不够格去讨论这个问题,不过呢,我倒是可以分享一些关于完成项目和作比赛的一些区别。

65 | 66 |

记得有天和陈大师聊到这个话题,讨论的重点是为啥滴滴比赛的时候都是各种模型的融合,咱做项目干的都是又low又苦逼的特征拉取工作(注意关键词)。后来陈大师说了句意味深长滴话:因为这样子效率最高呀!呵呵,会心一笑,不得不承认,同样的工作时间条件下,扩充特征确实是最保守的做法。

67 | 68 |

个人觉得,做比赛和做项目的最大差别,就是特征抽取了。比赛过程里,一般数据集是给定的,我们根据数据集和问题的特性尽可能去构造出能够描述问题本质的特征体系。由于时间有限,这期间最重要的一件事不是去扩充特征,而是特征的筛选,因为在有限数据集的条件下,top级选手之间的特征差异往往并没有很大。至于筛选的方法,就各显神通了,有人倾向于根据模型得到的特征筛选,有人倾向于自动化的多模型筛选,也有人喜欢直接上神经网络的,总之,有用就行。

69 | 70 |

其次,我认为是迭代效率的差异。很久以前,我一直认为,除了特征之外,最重要的就是模型融合了,最近好好想了下这个问题,其实比模型融合更重要的是迭代效率。同样走在一条未知的探索道路上,最接近终点的往往是试错最多的团队。迭代效率越高,短时间内尝试的方案也就越多,所以,模型融合也只是迭代效率高的表现形式之一而不是全部。此外,这也可以解释为什么一些横扫各大比赛的冠军队往往更容易在一个新的赛事上获胜,经验是其一,极高的执行效率和工具化的特征生产链也是重要的一环。这段时间的实际工作里,深切感受到了前期积累的重要性,很多生产流程都相当原始,许多事都觉得有些无能为力。

71 | 72 |

最后是敢于尝试,项目相关的东西,总是偏向于保守的,而做比赛的包袱则没有那么大,许多最新的一些算法都是值得去尝试和改进的,从最近滴滴比赛的几个PPT里可以看到,还是有些队用了神经网络的一些东西,包括DNN,GRU网络等,也有不错的效果。

73 | 74 |

3. 一些工作了才发现的事

75 | 76 |
    77 |
  • 个人能做的事其实很小很小,平台的重要性远大于其它。
  • 78 |
  • 缺少主动探索的精神,对大多数人而言,“工作”,也只是一份工作而已。
  • 79 |
  • 时间不够用,真心觉得时间才是最宝贵的资源。(现在都不推塔了。。。)
  • 80 |
  • 保持记录的习惯很受益,每天都写写wiki,有利于减少一些沟通成本。
  • 81 |
  • 代码积累,以前可能都是随手写写脚本什么的,信手拈来。工作后写代码很重要的一个环节是代码复用,多积累一些snippets还是蛮有意义的。
  • 82 |
  • 测试!对别人负责,也对自己负责。
  • 83 |
84 | 85 |

4. 近期的一些规划

86 | 87 |
    88 |
  1. 重写下网站,保持更新的频率。发现公司的网没法访问blog,打算改写下后台文件的传输方式,还是回归到原始的推送到github方式算了,利用github上的hook同步到server端备份,撤掉后台管理。
  2. 89 |
  3. 熟练掌握下tensorflow,这个毕竟还是以后工业级应用的主要工具库,theano之类的还是更偏学术点。
  4. 90 |
  5. 熟练掌握下clojure并发的部分(主要是最近受了Golang的刺激......),此外就是结合业务思考下如何灵活地去写一些DSL相关的东西。
  6. 91 |
  7. 读下手头上的书,《计算机程序的构造与解释》(刚看一半,后面两章应该要花不少时间)、《计算的本质 深入剖析程序和计算机》(挺有意思的一本书,Ruby还是不太熟,应该会拿Python来重写一遍)、《具体数学》和《统计学习基础》(算是扫尾吧,保持手感最重要~)
  8. 92 |
  9. Follow下顶会上一些有意思的paper,多动手实践下~
  10. 93 |
94 | 95 | 96 | 97 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /resources/published-html/Data Science London + Scikit-learn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Data Science London + Scikit-learn 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | Data Science London + Scikit-learn 18 |
  • 19 |
20 | 21 | 22 |

Data Science London + Scikit-learn

23 | 24 |
Kaggle,Competition
25 | 26 |

先感受一下这40维特征的分布情况: 27 | trainX_boxplot.png_1e40bdfe4c91adf0be46f972ddced4a8

28 | 29 |

从这个图可以得到几个信息,一是特征都分布在0附近,特征之间没有很大的差异性,比较均衡;二是每一维特征的盒子分布的长度大概是1:3,有点接近高斯分布。事实上,针对每一维画一个直方图(其实这里画KDE的图更合适)可以粗略看出其分布情况:

30 | 31 |

figure_1.png_de15b800f757d0cf0f2109708484a671

32 | 33 |

上图中蓝绿色分别表示label分别为0,1对应到第5维特征时候的特征分布。 34 | 图1是把label为0和1的所有训练集同时表示在了一张图上,那么将二者分开来看看呢?

35 | 36 |

figure_1.png_456f3c80035a7d9175061fd5660c8949

37 | 38 |

这样稍微可以看出点差别了,比较明显的是,有几维特征(比如第5,13维)在左右两边的分布出现明显的偏差。因此,可以断定,对于该数据集而言,少量的特征占有主要地位,而其他大多数特征只有较低的权重。当然,这样不够精确,下面对其量化下。

39 | 40 |

分别尝试用相关性衡量,逻辑回归,Lasso, svc,以及决策树模型,对初始特征的权值进行衡量,min-max归一化后的图如下

41 | 42 |

featre_weights.png_1457d11c67fcde06d2825823fe891e81

43 | 44 |

可以看到,权值较大的特征大概是15维左右。下图是根据pca得到的特征权重。

45 | 46 |

download.png_5ba6c5f6839ea5ac60601240a9b0fbb1

47 | 48 |

对特征了解了这么多,就来指定分类方法啦~

49 | 50 |

首先根据tutorial试下水,用svm对40维特征做训练,采用rbf核,5折交叉验证的结果是0.922,可以看到,svm对于这类有着强特征的数据表现不错。为了进一步提升,结合前面的分析可知,首先可以做的是对原始数据降噪。很容易想到的就是主成分分析。这里做pca降维的时候,需要结合训练集和测试集(主要是因为训练集才1000而测试集有9000,因此需要引入测试集对训练集进行强化)。同样采用5折交叉验证,当pca降维到12,13维附近的时候,最好能达到0.95左右(线上测试结果大概0.945附近)。实际上,降维的程度直接影响该结果。

51 | 52 |

那么,到此为止,似乎就遇到瓶颈了。提取主成分,分类,一气呵成。从前面两步可以看出,提升的关键在于对数据的清理,那么,能不能在此基础上继续往前探索呢?我们知道主成分分析有其局限性,那么还有哪些其他改进的办法呢?沿着这个思路,尝试采用sparse filtering 对该降维的过程做出改进。

53 | 54 |

直觉告诉我们,先用pca降维的维数作为基础,对原有的数据做sparse-filtering,同样5折交叉验证大概是0.928左右,可见跟使用原始的svm差别不大,修改sparse-filtering使用的维度,遍历之后如下图所示:

55 | 56 |

figure_1.png_fa552b3f3ac0d78acf38f96dbe50e1f4

57 | 58 |

我们知道,sparse-filtering与pca降维的显著差异之一是,pca降维只找主要关系,而SF则是找特征之间的内在组合关系。(后面可以利用该特性进一步提升),从图中可以看出,采用SF的最好效果要略优于pca降维后的效果,可以达到0.955附近,但是实际线上测试不过0.94。这说明存在一定的过拟合现象,个人感觉,最主要的原因时后面的svm层过度调参所致。

59 | 60 |

可以尝试换成逻辑回归层作为分类试试。遗憾的是LR并不能达到svm那样的效果(主要是缺少核函数的映射过程,尝试的结果可想而知)。另外,感觉采用auto-encoder后可能效果要比sf要稍好一些,等熟悉了pylearn2后可以尝试下。

61 | 62 |

SF的效果带给人的启发之一是,原来40维特征,通过SF处理后,可以得到和pca降维后近似的结果,但是维度的变化范围比较广(20~50)。也就是说,我们的目的并不是降维,降维只是手段之一,我们的目的是提取有用的信息。理解这一点后就好说了。

63 | 64 |

回顾上面所做的,实际上就是一个简单的3层网络结构。似乎,稀疏表示之后再分类的最好的效果止步于0.95。由于训练集数据较少,大量的数据是未标记的。那么,一个很单纯的想法就是,将测试集的预测结果中准确度极高的部分并入训练集,扩充训练集样本,强化训练。论坛里已经有人这么尝试过,似乎,没有太大提升。我估计,主要原因是,从测试集中提取到的概率值极高的那部分数据实际上是远离svm边界的数据,这部分数据加入到训练集中,对于区分那些边界附近的点意义不大。

65 | 66 |

整理下思路,现在的瓶颈似乎是在svm上,那些边界点难以分隔。因此,可以尝试加入一些弱分类器,另外适当调整参数C。

67 | 68 |

先用决策树试一下。本地测试0.8。换随机森林,先将训练集二八分,80%为train_set,20%为val_set,然后用40课树训练train_set,分别预测train_set,val_set,得到40维的概率值,分别记为rf_train_set,rf_val_set。再用svm训练这个rf_train_set,用得到的最佳参数去预测rf_val_set。结果是,该模型对rf_train_set的结果能达到100%,但是对rf_val_set仅仅只有84%左右。看来rf的预测值只能用来打辅助。分析其原因,一方面数据量太少,过拟合无疑。另外随机森林的数量和深度也需要适当限制,否则如果单颗树就陷入过拟合,其效果必然不好。

69 | 70 |

最后再尝试下融合。只简单试了一下特征融合,没做太多参数上的调优,但是小有提升,线上接近0.96。仔细想了想,尝试只能到此为止了。然后去论坛里找了找大家的解决方案,思路很新颖。通过分析数据的分布得出结论数据是造的。于是乎,对症下药,针对该数据的分布情况,采用GMM估计出原始分布的参数,再用简单分类器以GMM的结果为特征再次分类。我用svm复现了下该方法,确实很厉害,接近0.99。最后拿第一的那位也是融合了下rf的结果后做到的。

71 | 72 | 73 | 74 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/blog_clj/sync.clj: -------------------------------------------------------------------------------- 1 | (ns blog-clj.sync 2 | (:require [clojure.java.shell :refer [sh]] 3 | [environ.core :refer [env]] 4 | [hickory.select :as s] 5 | [hickory.zip :refer [hickory-zip]] 6 | [clojure.zip :as zip] 7 | [clj-time.format :as f] 8 | [clj-time.local :as l] 9 | [clj-time.coerce :as c] 10 | [clj-time.core :as t] 11 | [hickory.render :refer [hickory-to-html]] 12 | [clojure.string :as string] 13 | [blog-clj.redis-io :refer [get-all-blogs-titles get-blog new-blog delete-blog update-blog]] 14 | [clojure.data :refer [diff]] 15 | [hickory.core :as hk] 16 | [blog-clj.upload-download :refer [upload]] 17 | [taoensso.timbre :refer [info]]) 18 | (:import [java.io File])) 19 | 20 | (defn refact-node 21 | [root] 22 | (let [try-remove (fn [cur-loc] 23 | (if (or (= "tags" (:id (:attrs (zip/node cur-loc)))) 24 | (= "toc" (:class (:attrs (zip/node cur-loc))))) 25 | (zip/remove cur-loc) 26 | cur-loc)) 27 | try-refact (fn [cur-loc] 28 | (let [href (:href (:attrs (zip/node cur-loc))) 29 | src (:src (:attrs (zip/node cur-loc)))] 30 | (cond 31 | (and (not (nil? href)) (string/starts-with? href "../public/")) 32 | (do 33 | (let [upload-result (upload href)] 34 | (info (format "uploade:%s, upload status:%d" href (:status upload-result)))) 35 | (zip/edit cur-loc #(assoc-in % [:attrs :href] (string/replace href #"\.\./public/" (env :upload-path))))) 36 | (and (not (nil? src)) (string/starts-with? src "../public/")) 37 | (do 38 | (let [upload-result (upload src)] 39 | (info (format "uploade:%s, upload status:%d" src (:status upload-result)))) 40 | (zip/edit cur-loc #(assoc-in % [:attrs :src] (string/replace src #"\.\./public/" (env :upload-path))))) 41 | :else cur-loc)))] 42 | (loop [cur-loc root] 43 | (if (zip/end? (zip/next cur-loc)) 44 | (zip/root (-> cur-loc 45 | try-refact 46 | try-remove)) 47 | (recur (-> cur-loc 48 | try-refact 49 | try-remove 50 | zip/next)))))) 51 | 52 | (defn get-meta 53 | [file] 54 | (let [file-abs-path (str (env :html-path) file) 55 | file-hk (hk/as-hickory (hk/parse (slurp file-abs-path))) 56 | content-of #(->> file-hk 57 | (s/select (s/id %)) 58 | first 59 | :content 60 | first) 61 | toc-str (first 62 | (s/select (s/class :toc) file-hk))] 63 | {:toc toc-str 64 | :title (string/replace file #"\.html" "") 65 | :body (refact-node (hickory-zip (first (s/select (s/tag :body) file-hk)))) 66 | :update-time (f/unparse 67 | (f/formatter-local "yyyy-MM-dd HH:mm:ss") 68 | (t/to-time-zone 69 | (l/local-now) 70 | (t/time-zone-for-offset 8))) 71 | :tags (let [tag-str (content-of :tags)] 72 | (if (nil? tag-str) 73 | #{} 74 | (set (string/split tag-str #","))))})) 75 | 76 | (defn sync-blogs 77 | [added-blogs removed-blogs modified-blogs] 78 | (info "syncing...") 79 | (let [all-blogs-infos (get-all-blogs-titles) 80 | all-htmls-ids (into {} (for [[title id timestamp] all-blogs-infos] [title id]))] 81 | (do 82 | (when (not-empty added-blogs) 83 | (info (format "new blogs found: %s." 84 | (string/join ", " added-blogs)))) 85 | (when (not-empty removed-blogs) 86 | (info (format "blogs to delete: %s." 87 | (string/join ", " removed-blogs)))) 88 | (when (not-empty modified-blogs) 89 | (info (format "blogs are updated: %s." 90 | (string/join ", " modified-blogs)))) 91 | (doall (map #(new-blog (get-meta %)) added-blogs)) 92 | (doall (map #(update-blog (let [blog-meta (get-meta %)] 93 | (assoc blog-meta 94 | :blog-id 95 | (get all-htmls-ids 96 | (:title blog-meta))))) 97 | modified-blogs)) 98 | (doall (map 99 | #(delete-blog (get all-htmls-ids (string/replace % #"\.html" ""))) 100 | removed-blogs))))) 101 | -------------------------------------------------------------------------------- /resources/unpublished-md/解读libFM.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # 解读libFM 3 |
2016-01-18 13:38:55
4 |
2015-06-24 09:44:47
5 |
60
6 |
Algorithm
7 | ##写在前面 8 | 9 | 由于之前的比赛中用到了这个工具,所以顺带对矩阵分解以及FM深入学习了下。本文将结合其算法原理和Cpp源码,说说自己的使用心得,另外讲讲如何将Cpp源码分别用Python和Java改写。 10 | 11 | ##问题的本质 12 | 13 | 对于推荐系统一类问题来说,最核心的就是衡量用户对未接触target的感兴趣程度。CF一类算法的思想是通过相似度计算来直接估计感兴趣程度,矩阵分解一类的思想则是借助隐变量的映射间接得到对target的感兴趣程度。那么,FM呢? 14 | 15 | 个人理解是,FM和**词向量**一类的做法有相通之处。通过分别对用户和商品等构建一个向量,训练结束后根据用户的向量和商品向量之间的内积,估计出用户对商品的感兴趣程度。OK,借用论文[Factorization Machines]中的一个例子来说明下: 16 | 17 | 假设观测到的数据集如下: 18 | 19 | 20 | (A, TI, 5) 21 | (A, NH, 3) 22 | (A, SW, 1) 23 | (B, SW, 4) 24 | (B, ST, 5) 25 | (C, TI, 1) 26 | (C, SW, 5) 27 | 28 | 现在我们要估计A对ST的感兴趣程度,显然,如果用传统的分类算法,由于训练集中没有出现(A, ST)的pair,所以得到的感兴趣程度是0.但是FM衡量的是$(v_A, v_{ST})$之间的相似度,具体来说,是这么做的:由观测数据(B, SW, 4)和(C, SW, 5)可以得到$v_B$和$v_C$之间的相似度较高,此外,根据(A, TI, 5)和(C, TI, 1)可以推测$v_A$和$v_C$之间的相似度较低。而根据(B, SW, 4)和(B, ST, 5)可以发现,$v_{SW}$和$v_{ST}$之间的相似度较高。计算$v_A$和$v_{ST}$便可得知,A对ST的感兴趣程度较低。从这个角度来看,FM似乎是借用一种向量化表示来融合了基于用户和基于商品的协同过滤。 29 | 30 | ##理解其数学模型 31 | 32 | 根据前面的描述,给定一个用户商品(U,I)pair后,我们只需计算下式即可得到估计值: 33 | 34 | $$\hat{y}(x)=\langle v_u, v_I\rangle$$ 35 | 36 | 但我们这么做的实际上是有几个潜在假设的: 37 | 1.默认特征矩阵中只有User和Item两类特征; 38 | 2.User和Item维度的特征值为1; 39 | 40 | 实际问题中除了User和Item,还通常有Time,UserContext和ItemContext等等维度的特征。此外,不同User和Item的权值并不一样,所以并不都是1。将上式改写下便得到了FM的原型: 41 | 42 | $$\hat{y}(x)=w_0+\sum_{i=1}^{n}w_i x_i + \sum_{i=1}^{n}\sum_{j=i+1}^{n}\langle v_i, v_j\rangle x_i x_j$$ 43 | 44 | 其中$w_0$是全局bias,$w_i$是每维特征的权重, $\langle v_i, v_j \rangle$可以看做是交互特征的权重。 45 | 46 | ##计算过程优化 47 | 48 | 显然,上式的计算复杂度为$O(kn^2)$,利用二次项展开式可以将计算复杂度降低到线性复杂度: 49 | 50 | $$ 51 | \begin{split} 52 | &\sum_{i=1}^{n} \sum_{j=i+1}^{n} \langle v_i, v_j \rangle x_i x_j\\ 53 | &=\frac{1}{2}\sum_{i=1}^{n} \sum_{j=1}^{n} \langle v_i, v_j \rangle x_i x_j - \frac{1}{2} \sum_{i=1}^{n}\langle v_i,v_j \rangle x_i x_i \\ 54 | &=\frac{1}{2}\left(\sum_{i=1}^{n}\sum_{j=1}^{n}\sum_{f=1}^{k} v_{i,f}v_{j,f} x_i x_j - \sum_{i=1}^{n}\sum_{f=1}^{k}v_{i,f}v_i x_i x_i\right)\\ 55 | &=\frac{1}{2}\sum_{f=1}^{k}\left(\left( \sum_{i=1}^{n} v_{i,f} x_i \right) \left( \sum_{j=1}^{n} v_{j,f} x_j \right) - \sum_{i=1}^{n} v_{i,f}^2 x_i^2\right) \\ 56 | &=\frac{1}{2}\sum_{f=1}^{k}\left(\left( \sum_{i=1}^{n}v_{i,f} x_i \right)^2 - \sum_{i=1}^{n}v_{i,f}^{2} x_i^2\right) 57 | \end{split} 58 | $$ 59 | 60 | 当v的维度从2维扩展到d维时,上式也可以做对应的扩展。对上式中的变量($w_0, w_i, v_{i,f}$)分别求导可以得到下式: 61 | 62 | $$ 63 | \frac{\partial}{\partial \theta} \hat{y}(x) = \left\{ 64 | \begin{aligned} 65 | &1,\hspace{3in}if\ \theta \ is\ w_0\\ 66 | &x_i, \hspace{2.9in}if\ \theta \ is\ w_i\\ 67 | &x_i\Sigma_{j=1}^{n}v_{j,f}x_j - v_{i,f}x_i^2, \hspace{1.15in} if\ \theta \ is\ v_{i,f}\\ 68 | \end{aligned} 69 | \right. 70 | $$ 71 | 72 | ##C+ +源码解析 73 | 74 | 下面以libfm默认的mcmc方法用于分类任务为例,对其C+ +实现做一个简单分析。 75 | 76 | 概括地来说,训练的过程分为两步:一是计算误差,二是更新系数($w_i$和$v_{i,f}$)。具体流程如下: 77 | 78 | 1. 读入参数,并初始化。对于mcmc方法,重要的参数只有三个,一是`-iter`,即迭代次数;二是`-dim`,用于确定上面的v的维度;三是`init_stdev`;用于控制v和w的初始值(用正态分布随机初始化,由`fm.init()`完成)。 79 | 80 | 2. 接下来调用`_learn(train, test)`来完成整个训练过程。其中cache是一个e_q_term的对象,该对象中的e用于存储最后的误差项,q可以看做一个缓存,存储一些中间计算结果(不得不说,作者把内存用到了极致!!!各种省内存的做法啊)。把`_learn`函数的核心部分剥离出来就是下面的内容: 81 | 82 | ```C 83 | void _learn(Data& train, Data& test){ 84 | // ... 85 | predict_data_and_write_to_eterms(data, cache); // 根据v和w分别计算在训练集和测试集上的估计值,并保存到cache中 86 | //根据训练集的target值,计算训练集上的偏差 87 | for (uint c = 0; c < train.num_cases; c++) { 88 | cache[c].e = cache[c].e - train.target(c); 89 | } 90 | 91 | // 迭代更新 92 | for (uint i = num_complete_iter; i < num_iter; i++) { 93 | //... 94 | draw_all(train); //根据偏差(cache中的e部分)和训练集数据更新v和w 95 | predict_data_and_write_to_eterms(data, cache); // 用更新后的v和w继续计算训练集和测试集上的估计值 96 | } 97 | ``` 98 | 99 | 3. 先看一下`predict_data_and_write_to_eterms`函数,其实就是求解$\hat{y}$的一个过程。将其分解为2个部分,即$\sum_{i=1}^{n}w_i x_i$和$\sum_{i=1}^{n}\sum_{j=i+1}^{n}\langle v_i, v_j\rangle x_i x_j$(对mcmc方法而言$w_0$项为0),后者的线性时间简化版可以分成$\frac{1}{2}\sum_{f=1}^{k}\left(\left( \sum_{i=1}^{n}v_{i,f} x_i \right)^2\right)$ 和$\frac{1}{2}\sum_{f=1}^{k}\left(\sum_{i=1}^{n}v_{i,f}^{2} x_i^2\right)$这两块。这样子,`predict_data_and_write_to_eterms`函数对应的源码就好理解了。需要注意的一点是,源码中的Data数据结构实际上是CSC格式的稀疏矩阵,因此C++源码计算过程中是转置后按行取值计算的。理解这一点后,再用python或java改写该部分时,便可直接使用矩阵计算的方式,避免for循环带来的开销。 100 | 101 | 4. ``draw_all``函数稍微复杂点。先假设没有meta文件(特征的分组信息),前面我们已经得到了v,w参数的梯度,mcmc的做法是计算梯度并更新之后,对结果分别加入了正态分布和gamma分布的采样过程,与此同时,作者边更新v和w,根据新旧v和w的变化程度同步更新error(这个部分理解得不是很透彻,一般而言会在v和w完全更新后再更新e)。最后,对训练集上的目标值做一个截断高斯分布采样,利用采样值对error进一步更新。 102 | 103 | 5. meta信息的加入,相当于对每类特征内部多了一个正则项,在我使用的过程中,加不加正则项对结果的影响还是蛮大的。主要原因可能是我不同特征组的scale差异有点大,如果采用全局的正则项,难以精确描述不同特征组之间的差异。 104 | 105 | ##改写成python和java版本 106 | 107 | 由于比赛平台是java的,需要将C++代码改写成java,前期验证功能性的时候,为了快速分析中间变量,先用python写了一遍。整体框架都一致,核心是实现`predict_data_and_write_to_eterms`和`draw_all`这两个函数。其中`predict_data_and_write_to_eterms`的实现比较简单,分别借用python下的scipy中的csc矩阵和java下matrix-toolkits-java包中的FlexCompColMatrix可以将C++源码中的for循环大大简化。但是对于`draw_all`函数却不太轻松,最核心的问题是前面提到的,C++源码中在更新w和v的同时还在更新error项,导致无法一次通过矩阵运算后再更新error(事实上我一开始这么干过,但是跑出来的效果差太远),无奈,只能和C++一样用循环迭代更新,效率上慢了将近一个数量级。 -------------------------------------------------------------------------------- /resources/public/cssstyle.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Yanone Kaffeesatz'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Yanone Kaffeesatz Regular'), local('YanoneKaffeesatz-Regular'), url(http://ontheroad.qiniudn.com/blog/static/css/YDAoLskQQ5MOAgvHUQCcLfGwxTS8d1Q9KiDNCMKLFUM.woff2) format('woff2'); 6 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 7 | } 8 | 9 | @font-face { 10 | font-family: 'Tangerine'; 11 | font-style: normal; 12 | font-weight: 400; 13 | src: local('Tangerine'), url(http://ontheroad.qiniudn.com/blog/static/css/HGfsyCL5WASpHOFnouG-RFtXRa8TVwTICgirnJhmVJw.woff2) format('woff2'); 14 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 15 | } 16 | 17 | html 18 | { height: 100%;} 19 | 20 | body { 21 | font-family: "Hiragino Sans GB", "冬青黑体简体中文", "Microsoft YaHei", "微软雅黑", SimSun, "宋体", Heiti, "黑体", 'droid_sansregular', arial, sans-serif; 22 | background: #F7F7F7 url(http://ontheroad.qiniudn.com/blog/static/images/pattern.png) fixed; 23 | color: #555; 24 | font-size: 16.5px; 25 | } 26 | 27 | .post-title { 28 | text-align: center; 29 | } 30 | .post-time { 31 | text-align: right; 32 | margin: 0 30px 20px 0; 33 | } 34 | .post-content p { 35 | text-indent:2em; 36 | } 37 | 38 | h1, .h1, 39 | h2, .h2, 40 | h3, .h3, 41 | h4, .h4, 42 | h5, .h5, 43 | h6, .h6 { 44 | font-family: 'Yanone Kaffeesatz', arial, sans-serif; 45 | color: #555; 46 | margin-top:20px; 47 | margin-bottom:20px; 48 | font-size: 24px; 49 | font-weight:800; 50 | } 51 | 52 | h1, .h1{ 53 | font-size: 30px; 54 | font-weight:500; 55 | } 56 | 57 | blockquote { 58 | padding-left: 1em; 59 | margin-left: 2em; 60 | } 61 | 62 | a:link,a:visited{ 63 | 64 | text-decoration:none; 65 | color: #35BDF5; 66 | } 67 | 68 | a:hover,a:active 69 | { 70 | color:#F14E23; 71 | text-decoration:none;} 72 | 73 | .post-content ol{ 74 | line-style-position: inside; 75 | } 76 | 77 | .sidebar-container ul { 78 | padding:10px; 79 | } 80 | 81 | .sidebar-container ul li { 82 | text-indent:0; 83 | list-style-type: circle; 84 | margin: 0 0 0 0; 85 | padding: 0 0 4px 5px; 86 | font-size: 14px; 87 | } 88 | 89 | .content img { 90 | max-width: 100%; 91 | } 92 | 93 | .share img { 94 | max-height: 600px; 95 | } 96 | 97 | .blog-header{ 98 | padding-top: 30px; 99 | text-align: center; 100 | } 101 | 102 | #blog-title { 103 | font-family: 'Yanone Kaffeesatz', arial, sans-serif; 104 | font-size: 200%; 105 | } 106 | 107 | #blog-slogan { 108 | font-family: 'Tangerine', arial, sans-serif; 109 | font-size: 200%; 110 | } 111 | 112 | .blog-nav { 113 | font-family: 'Tangerine', arial, sans-serif; 114 | margin-bottom: 30px; 115 | font-size: 100%; 116 | } 117 | 118 | .blog-nav .active { 119 | font-weight:900; 120 | padding-bottom:16px; 121 | } 122 | 123 | .content { 124 | float: left; 125 | margin-right:20px; 126 | } 127 | 128 | .post { 129 | margin-bottom: 150px; 130 | } 131 | 132 | .share { 133 | margin-bottom: 50px; 134 | background: #f5f5f5; 135 | border-radius: 10px; 136 | } 137 | 138 | .sidebar-flag{ 139 | float:left; 140 | display: inline; 141 | } 142 | 143 | .paperclip { 144 | float: left; 145 | position: relative; 146 | z-index: 0; 147 | vertical-align: middle; 148 | margin: -27px 0 -60px -30px; 149 | } 150 | 151 | .sidebar-container{ 152 | border-color: #f0f0f0; 153 | border-style:solid; 154 | border-radius: 20px; 155 | padding-left: 10px; 156 | margin-left: 10px; 157 | margin-bottom: 10px; 158 | } 159 | 160 | .sidebar-container h1{ 161 | text-align: center; 162 | } 163 | 164 | /*#query {*/ 165 | /*width: 100px;*/ 166 | /*}*/ 167 | #footer { 168 | text-align:center; 169 | margin-top:30px; 170 | } 171 | 172 | @media (min-width: 992px) { 173 | #blog-title { 174 | text-align: left; 175 | font-size: 300%; 176 | } 177 | #blog-slogan{ 178 | text-align: right; 179 | font-size: 300%; 180 | } 181 | .blog-nav { 182 | font-size: 200%; 183 | } 184 | .container { 185 | width: 992px; 186 | } 187 | } 188 | 189 | #sidebar-trigger { 190 | width: 15px; 191 | position: fixed; 192 | float:left; 193 | padding-top: 100px; 194 | z-index: 2; 195 | height: 80%; 196 | cursor: pointer; 197 | background-color: #f0f0f0; 198 | border-radius: 0 10px 10px 0; 199 | font-family: "Glyphicons Halflings", arial, sans-serif; 200 | } 201 | 202 | #sidebar-trigger:hover { 203 | background-color: #e7e7e7; 204 | } 205 | 206 | .icons { 207 | text-align:center; 208 | } 209 | 210 | #weixin-QR-code { 211 | display: none; 212 | max-width:200px; 213 | } 214 | 215 | .glyphicon { 216 | padding: 5px; 217 | } 218 | 219 | span.badge { 220 | margin-left: 5px; 221 | } 222 | 223 | .post-content blockquote p{ 224 | text-indent:0; 225 | } 226 | 227 | .post-content ol p{ 228 | text-indent:0; 229 | } 230 | -------------------------------------------------------------------------------- /resources/published-html/Statistical Rethinking 读书笔记.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Statistical Rethinking 读书笔记 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 22 |
    23 |
  • 24 | Statistical Rethinking 读书笔记 25 |
      26 |
    • 27 | Chapter 2 28 |
    • 29 |
    • 30 | Chapter 3 31 |
    • 32 |
    • 33 | Chapter 4 34 |
    • 35 |
    • 36 | Chapter 5 37 |
    • 38 |
    • 39 | Chapter 6 40 |
    • 41 |
    • 42 | Chapter 7 43 |
    • 44 |
    • 45 | Chapter 8 46 |
    • 47 |
    • 48 | Chapter 9 49 |
    • 50 |
    51 |
  • 52 |
53 | 54 | 55 |
Book
56 | 57 |

Statistical Rethinking 读书笔记

58 | 59 |

最近这个月断断续续读完了Statistical Rethinking一书,感觉这本书还是挺适合入门的。作者的文风很好,每一章开头都会引入一个有意思的例子方便读者对本章的内容有一个大概的理解,不过书中的代码部分主要用到了自己写的一个库,这么做有好处也有坏处,好处是整本书中代码部分会相当简洁,侧重理解概念而不拘泥于代码细节;不过坏处是对于我这种不太熟悉R代码的人来说有种雾里看花的感觉,整体上讲,作者对二者平衡得很好,即使没有R基础也能很好地理解大部分内容,只是练习部分会稍微吃力点,后面感觉自己还会重读这本书。

60 | 61 |

另外作者还录制了教学视频,大致看了几课,感觉蛮不错,不过不如看书来得快。

62 | 63 |

以下是本书中的一些要点:

64 | 65 |

Chapter 2

66 | 67 |

提纲挈领的一部分。作者用small worlds类比观测到的世界,而large worlds则对应真实世界,我们无法知道真实世界是怎样的,只能根据观测到的世界去修正我们的猜测,由此引出了先验、似然和后验的概念。这章最核心的是要理解quadratic approximation,作者用map函数对其作了封装,前面几章会频繁用到。

68 | 69 |

关于MAP、ML等有个很不错的介绍材料可以参考。

70 | 71 |

Chapter 3

72 | 73 |

理解HDPI的概念,可以尝试自己动手实现下这个函数,比我想象中要简单。可以参考下这里

74 | 75 |

Chapter 4

76 | 77 |

重点理解高斯分布的内涵,这一点在PRML/MLAPP中也都有提到,思想是一致的。

78 | 79 |

Chapter 5

80 | 81 |

从一元线性回归过度到多元线性回归的时候,会遇到几个典型的问题。变量之间存在相关性时,后验分析会出现不符合常识的问题。此外还分析了引入哑变量对类别变量进行编码的影响。

82 | 83 |

Chapter 6

84 | 85 |

过拟合和欠拟合,一个经典问题。作者的出发点很新奇,从信息熵的角度出发,把交叉熵、KL散度、(样本内/样本外)偏差联系了起来,然后引入正则先验的概念。本章最关键的是信息准则,这对于我来说是个全新的概念,后面几章中都反复用到该指标进行模型比较和评估等。

86 | 87 |

信息熵的表示如下: 88 | \[ 89 | \begin{equation} 90 | H(p) = -\sum_{i=1}^{n}p_i log(p_i) 91 | \label{entropy} 92 | \end{equation} 93 | \] 94 | 稍微改写下形式: 95 | \[ 96 | \begin{equation} 97 | H(p) = \mathbb{E}H(p_i) 98 | \end{equation} 99 | \] 100 | 其中\(\mathbb{E}\)表示求期望,\(H(p_i)=log(\frac{1}{p_i})\),其含义是概率越低信息量越大,求log是为了保证相互独立事件的信息量之和等于这些事件同时发生的信息量。

101 | 102 |
103 |

K-L散度:用一个分布去描述另一个分布时引入的不确定性。

104 |
105 | 106 |

屏幕快照 2017-05-06 19.00.39.png

107 | 108 |

\[ 109 | \begin{equation} 110 | \begin{split} 111 | D_{KL}(p,q) & = H(p,q) - H(p) \\ 112 | & = -\sum_{i}p_i log(q_i) - \left(- \sum_{i}p_i log(p_i) \right) \\ 113 | & = - \sum_i p_i \left(log(q_i) - log(p_i) \right) 114 | \end{split} 115 | \label{klexplained} 116 | \end{equation} 117 | \]

118 | 119 |

式子\(\eqref{klexplained}\)中的\(H(p,q)\)表示交叉熵。

120 | 121 |

关于KL散度,有一篇博客写得更详细写,可以参考。

122 | 123 |

Chapter 7

124 | 125 |

这一章重点分析了多个变量之间存在相互影响时的情况,感觉自己在做数据分析的时候,好像经常忽略了这点。

126 | 127 |

Chapter 8

128 | 129 |

MCMC和HMC的解释很直观。关于采样链(Chain),有效采样个数等都有说明。 130 | 开篇提到的Good King的例子很好玩,我也重写了下:

131 | 132 |
(def N 10)
133 | (def counts (vec (range 1 (inc N))))
134 | 
135 | (defn move
136 |   [i]
137 |   (rand-nth [(mod (dec i) N)
138 |              (mod (inc i) N)]))
139 | 
140 | (defn play
141 |   [i]
142 |   (let [j (move i)
143 |         count-i (nth counts i)
144 |         count-j (nth counts j)]
145 |     (if (or (> count-j count-i)
146 |             (<= (rand) (/ count-j count-i)))
147 |       j i)))
148 | 
149 | (->> (iterate play (rand-int N))
150 |      (take 100000)
151 |      frequencies
152 |      (sort-by first))
153 | 154 |

Chapter 9

155 | 156 |

简单介绍了下广义线性模型,理解这里提到的两类连结函数(link function),本质上是将参数从不同的值域映射到\([-\infty,+\infty]\),留意其中参数的可解释性。

157 | 158 |

后面几章中看得比较粗略,我主要看了下多层模型、零膨胀问题和缺失值的问题。

159 | 160 | 161 | 162 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /resources/published-html/Throwing Eggs from a Building.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Throwing Eggs from a Building 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | Throwing Eggs from a Building 18 |
      19 |
    • 20 | 1. 问题描述 21 |
        22 |
      • 23 | 1.1 二分法 24 |
      • 25 |
      • 26 | 1.2 减少摔碎的鸡蛋个数 27 |
      • 28 |
      • 29 | 1.3 平衡摔碎的鸡蛋数和尝试的次数 30 |
      • 31 |
      • 32 | 1.4 疑惑 33 |
      • 34 |
      35 |
    • 36 |
    • 37 | 2 问题扩展 38 |
        39 |
      • 40 | 2.1 简单的尝试 41 |
      • 42 |
      • 43 | 2.2 变步长 44 |
      • 45 |
      46 |
    • 47 |
    • 48 | 3 更多的鸡蛋~ 49 |
    • 50 |
    51 |
  • 52 |
53 | 54 | 55 |

Throwing Eggs from a Building

56 | 57 |
Algorithm
58 | 59 |

下午做练习题时碰到的问题,Algorithms, 4th中的1.4.24和1.4.25。思考了之后发现是个挺有意思的问题。下面结合题目和网上的一些资料做个简单的分析。

60 | 61 |

1. 问题描述

62 | 63 |

原文的问题是这样的:

64 | 65 |
66 |

1.4.24 Throwing eggs from a building. Suppose that you have an N-story building and plenty of eggs. Suppose also that an egg is broken if it is thrown off floor F or higher, and intact otherwise. First, devise a strategy to determine the value of F such that the number of broken eggs is \(\sim lg N\) when using \(\sim lg N\) throws, then find a way to reduce the cost to \(\sim 2lg F\).

67 |
68 | 69 |

简单来说就是这么个意思:

70 | 71 |
72 |

在一座N层楼房上往下扔假鸡蛋,这些假鸡蛋只有在第F层楼及以上才会被摔破(没有摔破的鸡蛋可以接着用)。为了找到这个F,设计一个思路使得尝试的次数接近\(\sim lg N\)并且最后摔破的鸡蛋个数接近\(\sim lg N\),然后想办法降低到\(\sim 2lg F\)。

73 |
74 | 75 |

1.1 二分法

76 | 77 |

首先想到的应该是用二分法来解决这个问题。从中间数(N/2)开始,如果鸡蛋摔破了,就继续往下找。显然,如果鸡蛋在1楼才摔破的话,摔破的鸡蛋个数和尝试的次数都接近\(\sim lg N\)。问题在于,如何降低到\(\sim 2lg F\)?

78 | 79 |

1.2 减少摔碎的鸡蛋个数

80 | 81 |

试着思考下这个问题的特殊之处,如果某一次尝试的过程中鸡蛋没有摔破,那么可以接着用!回顾前面的二分法中的极端情况,每次尝试都摔碎了个蛋,太浪费了。也就是说,如果换成从下往上搜索而不是从中间开始搜索,那么最初的几次尝试很大可能不会摔碎蛋的。于是,最简单的应该就是拿着鸡蛋从1楼开始往上一层一层试,直到鸡蛋摔碎。不过这样虽然摔碎的鸡蛋的个数降到了1个,但是测试的次数却上升到了F个。

82 | 83 |

1.3 平衡摔碎的鸡蛋数和尝试的次数

84 | 85 |

从前面的分析可以感受到,如果降低摔碎的鸡蛋个数,测试的次数就上去了,反之亦然。那么如何平衡呢?前面是拿着鸡蛋一层一层往上测试,这样子太慢了!不妨每隔两层(三层,五层?)测试一次,摔碎了的话再退回来。这样测试的次数将低了一些,但是仍然是线性的。还是太慢了!干脆换成指数增长。假设第k次摔碎了,然后再倒回来采用二分查找。这样摔碎的鸡蛋个数是\(\sim lg(F/2)\)(前面在第\(1,2,4,...,2^k-1\)层测试的时候没摔碎,只有后面\(2^k-1到 2^k\)之间 二分查找时候才有可能摔碎),而尝试的次数  为\(\sim 2lg F\)。

86 | 87 |

1.4 疑惑

88 | 89 |

疑惑的是,这样子虽然肯定可以降低摔碎鸡蛋的个数,但并不一定能降低尝试的次数啊

90 | 91 |

2 问题扩展

92 | 93 |
94 |

1.4.25 Throwing two eggs from a building. Consider the previous question, but now suppose you only have two eggs, and your cost model is the number of throws. Devise a strategy to determine F such that the number of throws is at most \(2\sqrt{N}\), then find a way to reduce the cost to \(\sim c\sqrt{F}\). This is analogous to a situation where search hits (egg intact) are much cheaper than misses(egg broken).

95 |
96 | 97 |

2.1 简单的尝试

98 | 99 |

现在的问题变成了,如果只有两个鸡蛋,怎样才能降低查找次数?

100 | 101 |

不考虑其他的,假设楼房有100层,现在手上有两个蛋,先跑到50楼扔一个了再说,人品不好,蛋碎了,那么接下来该怎么办?老老实实拿着剩下的那个从一楼开始尝试吧,最多试50次。直觉告诉我们,一开始就选太大了反而得不偿失。事实上,借用前一个问题的分析思路,可以尝试用线性增长的方式来解决这个问题(显然这次不能用指数增长了,还得check最后一个区间的……)。假如每次往上提升采用一个定步长,其实可以算出最优解。设步长为x,那么最大的查找次数为\(x + \frac{100}{x}\),其最优解近似于\(2\sqrt{N}\)

102 | 103 |

2.2 变步长

104 | 105 |

前面这种解法的瓶颈点在哪呢?最后一步!

106 | 107 |

假设鸡蛋在最后一步那地方碎了,此时已经走了\(\frac{100}{x}\)步,然后还需要再尝试10次(从90到100层之间,也就是步长的长度)。

108 | 109 |

通过采用变步长的思想,前面这个解决方案还可以稍微改进一些。我们知道,二分法求解问题的核心思想是最大化信息熵,通俗点说,就是每check一次之后,不管目标值在check点的左边还是右边,对我来说需要check的次数是相同的。对应到这个问题上,设第一步的步长为\(x_1\),不管蛋碎了没有,我希望接下来check的次数都是相同的。显然,如果蛋碎了,我就只剩一个蛋了,还需要从下往上check\(x_1-1\)次,如果蛋没碎,此时只要\(x_1\)选取的合理,满足前面最大熵要求,我仍然只需要\(x_1 - 1\)次就能找到F。如此迭代下去便不难发现,最后可以得到公式\(x_1 + (x_1-1) + (x_1 - 1 - 1) + ... + 1 = N\),从而可以求解出初始步长。

110 | 111 |

3 更多的鸡蛋~

112 | 113 |

如果鸡蛋的个数变成3个,4个,5个......

114 | 115 |

采用减而治之的思想,可以很好去解决这个问题。以3个鸡蛋为例子:

116 | 117 |
118 |
    119 |
  1. 首先,得确定第一个步长\(x_1\),
  2. 120 |
  3. 如果碎了,则问题转换成了2个鸡蛋\(x_1-1\)层楼的问题,(根据前面的方法假设该问题的最优解为\(x_c\));
  4. 121 |
  5. 如果没碎,则转换成3个鸡蛋\(N-x_1\)层楼的问题
  6. 122 |
123 |
124 | 125 |

通过迭代的方法比较容易求解该类问题。

126 | 127 |

更多有意思的分析可以看看这里。 128 | 下图是从上文中摘出的一幅图,用来说明不同情况下需要的蛋的个数。

129 | 130 |

graphl.png_184fd5a153422104b0054be061ec2a80

131 | 132 | 133 | 134 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /resources/unpublished-md/Data Visualization and Analysis in Recsys Challenge.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # Data Visualization and Analysis in Recsys Challenge 3 |
2015-02-03 07:23:41
4 |
2015-01-21 00:51:00
5 |
56
6 |
DataVisualization
7 | 本来想写点关于[pandas](http://pandas.pydata.org/)的,写了点后感觉,毕竟只是个工具,怎么写都只会变成技术文档。还是来点实际的In Acion之类的东西,所以本文就结合pandas和[Recsys Challenge 2015](http://2015.recsyschallenge.com/)的数据来做一些简单分析。侧重点在如何理解和分析数据。 8 | 9 | - 数据的下载和描述见[这里](http://2015.recsyschallenge.com/challenge.html) 10 | 11 | - 下文中绘图代码在[这里](http://nbviewer.ipython.org/gist/findmyway/8802f75eacc14c107993),(不建议在低于16G内存的机器上直接跑该脚本,中间变量很吃内存。某些不需要的中间变量可以del后释放掉。) 12 | 13 | - 原始网页的Leaderboard 太丑了...我做了个更Fancy的,有兴趣看看[这里](http://recsys.tianjun.ml) 14 | 15 | 另外本文参考了以下内容: 16 | 17 | > 1. [http://aloneindecember.com/words/recsys-challenge-part-i/](http://aloneindecember.com/words/recsys-challenge-part-i/) 18 | 19 | > 2. [http://aloneindecember.com/words/recsys-challenge-part-ii/](http://aloneindecember.com/words/recsys-challenge-part-ii/) 20 | 21 | > 3. [Can we approximate the upper bound score for the 2015 recsys challenge? | Recommended](https://thierrysilbermann.wordpress.com/2015/01/13/can-we-approximate-the-upper-bound-score-for-the-2015-recsys-challenge/) 22 | 23 | > 4. [土人之NLP日志: How to build a naive (very naive) system scored over 30,000 in RecSysChallenge 2015?](http://playwithnlp.blogspot.sg/2014/12/how-to-build-naive-very-naive-system.html) 24 | 25 | 26 | ##1. 加载数据 27 | 28 | 首先加载数据,pandas(以下简称pd)的[read_csv](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.io.parsers.read_csv.html)函数相当丰富,但基本参数和np的loadtxt比较接近,主要差别在于,pd对于二维数据采用了类似SQL的处理方式,读入数据的时候会涉及到Index列的处理。另外结合数据描述,在读入数据的时候需要将TimeStamp设置为datetime.datetime类型。 29 | 30 | ##2. 准备基础数据 31 | 32 | pd可以对数据做一些类似SQL的处理,本文最常用的一个函数便是groupby。由于click和buy数据分别由两个文件存放,为了便于分析,首先将click和buy中相同的字段通过concat连接在一起得到total。提取buy中所有sessID,然后在total中增加一列IsBuy来区分每一条record所属的sessID最后是否有购买行为。分别对click,buy,total按照SessionID分组,得到click_sesID_group,buy_sesID_group和total_sesID_group。针对click中的category信息,构建items2category的字典。 33 | 34 | 35 | ##3. 数据规模初探 36 | 37 | 对分组后的数据简单的总数统计可以发现,最后购买了东西的session的平均点击次数是8.74次,而没买东西的session所对应的平均点击次数却只有3.2次。显然这合乎常理,**在确认购买东西之前,人们会反复查看和对比。如果只是随便点击两下似乎直接购买的可能不大。** 38 | 39 | 通常,为了更可视化地描述人们在每个session中的点击次数,会画出如下的直方图来。 40 | 41 | ![a.png_a9b6f1920752df9ac5ec07ab83a06369](http://ontheroad.qiniudn.com/blog/resources/a.png_a9b6f1920752df9ac5ec07ab83a06369/w660) 42 | 43 | 看到这张图的第一时间,你可能会说,“噢,典型的泊松分布”,然后,没有然后了。其实这样的表示带来的信息量很有限。前面提到点击和购买session的平均次数差异很大,为了可视化地表示出这种差异,不妨将两部分数据拆分开来,选取[0,25]这一密集的区间,将二者的数据做对比,用图来说话。 44 | 45 | ![b.png_bae06f391292f2407b24beface0acaaa](http://ontheroad.qiniudn.com/blog/resources/b.png_bae06f391292f2407b24beface0acaaa/w660) 46 | 47 | 这里做了一个归一化处理。这样看起来就清楚多了。对于click数据,大部分session都只点了两三次,而且随着点击次数的增加,session个数衰减得非常快。而buy数据(最后产生了购买行为的session)则显示,其衰减的速度要缓慢得多。如果一个session的点击次数超过了10,那么极大可能会产生购买行为。 48 | 49 | ##4. 加入时间序列分析 50 | 51 | 我们把session作为一个整体来看待,在这里选取每个session的第一次点击记录来代表。将total_sesID_group中的每个小组按照时间排序后,用first()函数得到每个小组的第一条记录。用apply函数将datetime类型的TimeStamp转换成月,然后绘制直方图,同时记录购买点击比例。 52 | 53 |
 54 | 
55 | ![c.png_91ca2e0846a89cd147e3533244ac5e30](http://ontheroad.qiniudn.com/blog/resources/c.png_91ca2e0846a89cd147e3533244ac5e30/w660) 56 | 57 | 虽然点击量和购买量在7月8月有一定的起伏,但是比例整体保持不变。再将TimeStamp转换成星期,分析更细致的变化趋势。 58 | 59 | ![d.png_b8c026947f2c28889731b0ede52192e5](http://ontheroad.qiniudn.com/blog/resources/d.png_b8c026947f2c28889731b0ede52192e5/w660) 60 | 61 | 62 | 从每周的成交量来看,大致还算比较稳定,在均值0.055附近抖动,而且第26周附近,点击量和购买量虽然同时跳水,但比例几乎保持不变。需要注意的是,由于测试集是从与训练集相同时间段中抽出来的一部分session,这样的稳定性有利于数据的一致性分析。 63 | 64 | 继续细化,将时间细化成天,得到下图。 65 | 66 | ![e.png_3b1b190ad55cb7818b3c5d8a35703090](http://ontheroad.qiniudn.com/blog/resources/e.png_3b1b190ad55cb7818b3c5d8a35703090/w660) 67 | 68 | 规律性相当强,几乎每到周二就跌到谷底,在周日达到峰值。有点意思。那再具体看看一个星期里,每天的点击量购买量究竟差别多大。 69 | 70 | ![f.png_5948456e64dfab1166e9b9fdc6c17525](http://ontheroad.qiniudn.com/blog/resources/f.png_5948456e64dfab1166e9b9fdc6c17525/w660) 71 | 72 | 周二的点击量降到了高峰时期的1/3,购买点击比降到了一半。继续细化。绘制24小时流量图如下。 73 | 74 | ![g.png_a6b1bbdc7972a1b185e137ad0a1321ae](http://ontheroad.qiniudn.com/blog/resources/g.png_a6b1bbdc7972a1b185e137ad0a1321ae/w660) 75 | 76 | 9~10点附近一个高峰,18~19点附近一个高峰。 77 | 78 | 继续细化。(细化无止境...) 79 | 80 | 考虑这样一个问题,最后产生购买的session和没有购买的session之间在点击时间上会有怎样的差别呢?会不会最后产生购买的用户在一个页面上倾向于花更多的时间?带着这个问题,分别将点击session和购买session相邻两次点击之间的时间差求平均后表示出来。(横轴表示第x个点击,纵轴表示该次点击与距离上一次点击的平均时间。 81 | 82 | ![h.png_3057f6bac63838a0e44ba4bacc8ead9d](http://ontheroad.qiniudn.com/blog/resources/h.png_3057f6bac63838a0e44ba4bacc8ead9d/w660) 83 | 84 | 两条线的交汇点大概是(10,120)附近。一定程度上验证了前面的设想。但随着点击次数的增加,购买的曲线和点击的曲线差不多杂糅在一起。可能点击次数太大后,单从时间因素无法区分用户是否会购买。(这张图的绘制含义描述起来稍微有点绕,有兴趣可以去看源码如何画出来的,就更好理解了)个人比较疑惑的一点是,为什么头两次点击之间的时间会相差3到4分钟这么长...... 85 | 86 | ##5. Category和Price信息 87 | 88 | 由于Category和price(quantity)信息都有很多缺失值,这一点需要格外注意。先说Category信息,用面积来表示每一类的每天的点击量,选取主要的几类绘制如下: 89 | 90 | ![i.png_c1847099bcd3e8a287e422412cb82ecf](http://ontheroad.qiniudn.com/blog/resources/i.png_c1847099bcd3e8a287e422412cb82ecf/w660) 91 | 92 | 93 | 可以看到,Category的缺失信息集中在上半年(category为0表示类别信息缺失),下半年打折类别的点击量很多(类别为S表示打折)。因此在后面分析的时候需要格外注意该分界线。 94 | 95 | 同样的方法分析Price信息。 96 | 97 | ![j.png_c869d85b15b2346775ce97c8b1d034b9](http://ontheroad.qiniudn.com/blog/resources/j.png_c869d85b15b2346775ce97c8b1d034b9/w660) 98 | 99 | 有意思的是缺失值占了一半,但却集中在中间那几个月。如果要利用price和quantity信息的化,恐怕需要作分区处理了。 100 | 101 | 最后看一下价格的波动区间吧。其实下图用kde更合适,而不是直方图。 102 | ![k.png_4d8865499475d03c8cfd97131711e7de](http://ontheroad.qiniudn.com/blog/resources/k.png_4d8865499475d03c8cfd97131711e7de/w660) 103 | -------------------------------------------------------------------------------- /resources/unpublished-md/About This Site.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | 3 |
Web,Django,Clojure
4 | 5 | # About This Site 6 | 7 | ## 记录下用Clojure重写的本站的过程 8 | 9 | 最早打算重写这个网站的原因是为了方便同步网站的信息。由于以前是自己写的后台管理页面,每次更新内容都需要在网页上写东西,以前可能不是太大的问题,不过现在在家里的网总是断断续续的,而且近来记录笔记的习惯也倾向于本地化记录了,所以打算在网站与本地之间开通个接口方便同步一些信息。另外,对网站的内容做了一些精简,希望以后写的东西还是更professional一些吧。 10 | 11 | 其实,我也不知道整个过程花了多少时间,断断续续地,偶尔想起来的时候熟悉下相关库的api,遇到不懂的地方,也只能花时间死磕各个库的源码实现。不过,大部分代码应该是这几天放假的时候写的(总共也就几百行),源码可以在[这里](https://github.com/findmyway/blog-clj)看到。 12 | 13 | 记录几点体会吧: 14 | 15 | 1. 抽象和隔离的意识要比以前强一些了,会有意识地从逻辑层和代码层抽出一些共通的部分出来; 16 | 2. 测试的引入会改变自己写代码的方式,会有意识地去考虑如何方便进行测试; 17 | 3. 对错误和异常的处理还缺少很深入的思考与实践; 18 | 19 | ##BugFix 20 | 21 | 记录昨天fix的一个Bug,之前后台自动保存的功能出了个bug,直接导致一个文档的content同步覆盖掉了另一个文档的content。要命的是不知道怎么复现这个bug。查看log日志,所有的请求都正常。昨晚跟同学讨论了下,感觉应该是js自动保存逻辑出问题了。仔细review了下代码,果真是。 22 | 23 | 原来的实现是,自动保存和markdown渲染封装在同一个按键检测事件里,并加入了一个10秒钟的延时,如果10秒钟内没有按键触发则发起PUT请求保存当前内容。原来的代码所存在的问题是,编辑一个文件的时候,如果10秒钟内切换到了另外一个文档,那么,会导致读取的id和content不一致,具体说明如下: 24 | 25 | ```javascript 26 | $('#response').keyup(function () { 27 | var v = $('#response').val(), 28 | s = markdown.core.mdToHtml(v); 29 | $("#rendered").html(s) 30 | MathJax.Hub.Queue(["Typeset",MathJax.Hub,"rendered"]); 31 | 32 | if(save_time_out) { clearTimeout(save_time_out); } //10 秒钟内检测到输入则重置延时 33 | save_time_out = setTimeout(function () { 34 | var ref = $('#tree-container').jstree(true), 35 | sel = ref.get_selected(); 36 | if(!sel.length) { return false; } 37 | sel = sel[0]; 38 | $.ajax({ 39 | type: "PUT", 40 | url: "/essays/"+sel, 41 | data: JSON.stringify({"id":sel, "body": v}), // 问题出在这里, 42 | //PUT请求时,传递的是10秒钟前读到的v, 43 | //而此时的blog_id(即变量sel)可能变了, 44 | //导致一个文档的content更新到另一个文档里去了 45 | // 更改后的代码每次PUT重新读取当前的content和id 46 | // data: JSON.stringify({"id":sel, "body": $('#response').val() }), 47 | contentType: "application/json; charset=utf-8", 48 | datatype: "json", 49 | success: function(data){ 50 | save_tree("Blog " + sel + " Content AutoSaved: ") 51 | }, 52 | failure: function(err){console.log(err)} 53 | }) 54 | }, 55 | 10000); 56 | }); 57 | ``` 58 | 59 | ##Update 60 | 61 | 貌似这个网站保持每年更新一次的频率。最近这次又重新折腾了下,不过目前依然是半成品。这次折腾的时间稍微有点长,前前后后大概有一个月时间吧。先说说改版的原因: 62 | 63 | 1. 印象笔记基本被我弃用,而且原来写的网站后台同步系统有点小bug一直没有修复; 64 | 2. 最近一段时间学习了下Clojure,挺有意思的一门语言,想动手实践下; 65 | 3. 原来的HTML5模板总觉得被我改的有点丑,想再重新写一版; 66 | 4. 希望将网站的后台管理系统设计成个性化的笔记管理系统,一定程度上替代印象笔记,为知笔记等; 67 | 68 | 针对上面几点,分别对网站做了以下改进: 69 | 70 | 1. 抛弃原来的同步系统后,重写了后台的文档管理体系统,整个编辑界面借鉴了[作业部落](www.zybuluo.com/)的设计方案,双屏切分(左边编辑,右边预览),导航栏用于管理文档结构,由于是自己用,舍弃了账户管理模块; 71 | 2. 网页框架采用了[luminus](http://www.luminusweb.net/),我觉得这就是个大杂烩,把各个优秀的部件组织在一起,然后提供一些高可用的模板,上手非常方便。数据存储方面,抛弃了之前用的SQLite,换了Redis(没别的原因,只是对Redis用着熟悉点),文件存储上,仍然采用了[七牛云存储](http://www.qiniu.com/),[qiniu-clojure-sdk](https://github.com/killme2008/clj.qiniu)的作者封装得真简洁......网页权限管理用到了之前说的Buddy,简单易行;后台管理的安全性方面,目前暂时把csrf模块给去掉了,因为后台有很多的ajax请求,还没想好该怎么绑定csrf-token,缺少这方面的经验,这应该是整个网站最大的安全漏洞; 72 | 3. 重写前端页面。这块挺花时间的,主要是自己对js不熟,又重新翻了下[JavaScript高级程序设计](http://book.douban.com/subject/10546125/)这本书,然后熟悉了下jquery和react,感觉react写前端的想法确实有点不太一样,然后继续往前学习了下ClojureScript以及对react封装的库OM,完全刷新了我对前端的认识(我承认之前一直很BS前端干的活......),然而时间精力有限,这些都仅仅是浅尝辄止。最后前端部分主要用jquery写的; 73 | 74 | ##说说感受 75 | 76 | 整个从前端到后端全都写了一遍,说实话,觉得挺累的。很多地方都是从零开始,完全没有经验可谈。除了HTML是在WebStorm下写的,其余都是在vim下写的,并不是我排斥IDE,而是IDE用着效率太低了。 77 | 78 | 总的来说,我挺喜欢Clojure这门语言的,简洁,有魔力!可惜的是我到目前为止还算不上入门,也没体会到其在处理并发问题上的优势。而且,最近写clojure的过程中,也读了不少框架的源码,感觉很多代码还是很难懂。接下来的一段时间估计没空深入学习了,毕竟clojure又不能拿来跑RNN......我能想到后面会用到clojure的地方估计就是用它来写SICP的习题了...... 79 | 80 | ==================================== 81 | 82 | ##写在前面 83 | 84 | 之前网站一直放在[SAE](http://www.sinaapp.com/)上,除了每月扣点**豆子**,用着也没啥问题,除了扩展性不太好之外。不过,间歇性的出了几次意外,后台往sql中写入数据的时候,不知道是啥原因,提交后页面卡死了,然后再去sae后台一看,哗啦啦几百豆子没了......我总共才送了1.5k,无语了。想了半天也没找出时什么原因,只知道是全都扣在sql的读写上了。也罢,懒得在上面折腾了,写的东西暂时都放在印象笔记里。前些天忽然想起来github送的DigitalOcean优惠券还没用,最近有点闲时间,再折腾了一把。之前的后台是刚熟悉python的时候写的,现在再看看,真是渣渣......然后动手重新写了一遍部署到DO上(回头看了一下,其实还是渣渣......忧桑)。这段时间用印象笔记用着很爽,主要是方便,所以,这次后台的改动主要就一个,利用Evernote的api把网站后台跟印象笔记打通了,这样便于随时积累,持续更新。~~另外,rss给去掉了,有兴趣的话加个印象笔记吧~~~ 85 | 86 | ##整体结构 87 | 88 | 怕自己以后给忘了,画个图,以后再修改起来也方便。![site.png_d2006a2a0cf19cc0de218e331d0ebf2b](http://ontheroad.qiniudn.com/blog/resources/site.png_d2006a2a0cf19cc0de218e331d0ebf2b/w660) 89 | 90 | 跟印象笔记api打交道的过程中,坑很多,最惨的一个莫过于,在sandbox中测试的好好的,换成印象笔记的token后就出错了,各种google,居然有人给出了[解决方案](http://stackoverflow.com/questions/23098372/evernote-invalid-token),把service_host改一下就好了,另外这里只是简单采用轮询的方式,因为对实时性要求不高,所以没用所谓的webhook。 91 | 92 | 目前网站布在新加坡节点,感觉速度上还行吧,没有网上说的那么差。万一不稳定了再换换。新加坡的节点有ipv6这个很不错。配合shadowsocks用着很快。 93 | 94 | 其实本来打算把shares部分大改的,主要是想跟自己的虾米账户打通,已经写了一半了,忽然发现国外不能访问国内的虾米,真是个蛋疼的世界。唉,真是应了那句话。 95 | 96 | > 墙外的想到墙内去,墙内的想到墙外来...... 97 | 98 | 算了,以后有空再弄弄,往所里的电脑上布个代理做跳板。也许下次会直接弄个music的二级域名出来。 99 | 100 | ##说点细节 101 | 102 | 印象笔记和数据库的同步通过定时器实现,由于印象笔记的特殊性,每一条笔记对应正文和资源(包括图片等附件)两部分,因此将同步过程分为两步。 103 | 104 | 1. 首先查询对应笔记本中每条笔记的更新时间,同时检索本地数据库中每一条记录的更新时间。然后提取需要更新和新建的记录。正文部分用正则做一个粗过滤(替换掉div,br标签)后写入数据库(其实可以经过markdown渲染之后再写入数据库,这样响应会更快,不过,这部分响应时间相比建立连接的时间而言可以忽略不计,而且有时候需要在后台查看下过滤的结果),同时提取附件相关的内容转换成链接格式。并将涉及到的资源写入数据库中的一张表。 105 | 106 | 2. 扫描上面的资源表,对于没有下载到本地和同步到cdn的资源分别下载和上传,并校验。 107 | 108 | 分成两步的最主要原因是,如果附件比较大,很容易出现下载上传失败的情况,从而导致整个文档更新失败。将下载和上传部分独立出来后,不会影响文档内容的同步,如果某些大文件没法自动同步可手动在后台同步。 109 | 110 | 最后绑定下域名,分别把www.tianjun.ml和www.tianjun.me通过301重定向到主域名上。 111 | 112 | ##谈谈感受 113 | 114 | 自己这么亲自折腾一把后,才会对现在的云平台带来的好处感受更深。其实说到底,像安装配置apache这些都是dirty work,云平台把这些集成后,可以说大大免去了这些苦力活,稳定性也有保证。 115 | 116 | 很久没写点东西了,最近会陆陆续续整理些出来,坚持坚持~ -------------------------------------------------------------------------------- /resources/published-html/使用Pandas的一点经验和技巧.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 使用Pandas的一点经验和技巧 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | 使用Pandas的一点经验和技巧 18 |
      19 |
    • 20 | 1.读取csv文件的时候,注意指定数据类型 21 |
    • 22 |
    • 23 | 2.灵活使用map/applymap函数 24 |
    • 25 |
    • 26 | 3.选取指定行和列 27 |
    • 28 |
    • 29 | 4.怎么重命名某些列 30 |
    • 31 |
    • 32 | 5.如何合并多列数据得到一个新的DataFrame 33 |
    • 34 |
    • 35 | 6.要习惯级联 36 |
    • 37 |
    • 38 | 7.groupby的并行化 39 |
    • 40 |
    41 |
  • 42 |
43 | 44 | 45 |

使用Pandas的一点经验和技巧

46 | 47 |
48 | 49 |

上周刚做完ijcai中阿里赞助的一个比赛的第一赛季,无缘前三,甚是可惜。整个过程中都是采用pandas来抽取特征的,总的来说相当畅快,因此希望能够分享些用pandas过程中的经验技巧(坑),其中包含了许多从stackoverflow上查找的答案(我尽量引出出处,好多我找不到原始问答了),但愿大家少走弯路。暂时先写了这7点,以后想到了再补充,另外我把github上的一个pandas-cookbook翻译了一下,快速上手应该是够了,更多的还是遇到问题去查看官方文档。

50 | 51 |

1.读取csv文件的时候,注意指定数据类型

52 | 53 |

read_csv函数默认会把数值转换成int或者float,但是有时候对于一些ID列而言,这并不是好事。一不小心对ID列加减乘除操作后根本不会有任何提示!此外,string型的数据和int型的数据在打印出来后是没有区别的,有时候你明明看到一个df里有index为12345的这一行数据,df.ix['12345']返回的却是空,很可能原始的index是int型的,换成df.ix[12345]就行了。

54 | 55 |

2.灵活使用map/applymap函数

56 | 57 |

applymap是针对DataFrame类型的,而map则是针对Series类型的。而且,根据文档里的描述map可接收的变量类型要比applymap更丰富,除了函数之外还有dict和Series类型。因此我通常用map来完成一些String类型变量到int类型变量的转换操作,以及一些变量的拉伸(log)剪裁异常值处理等。

58 | 59 |

3.选取指定行和列

60 | 61 |

这个看起来很简单,不过实际操作起来有不少值得注意的地方,再加上官方文档写得很长很绕,很多时候都不知道该怎么用。

62 | 63 |

首先,说个好习惯。选取指定列的时候,一般有两种方法,一是直接用df['col_1'],还有就是df.col_1。在列名和默认函数不冲突的情况下,个人建议如果是新增一列,一般在等号=左边使用df['col_1']的形式,如果是更新一列数据,一般使用df.col_1的形式。在等号=右边尽量使用df.col_1的形式。除非列名是1,2,3,4这种没法通过df.1的形式访问。这样子做的优点是代码结构清晰,一看就明白这是在添加还是更新操作。

64 | 65 |

关于按行切片,说实话很少用。因为大多时候不会直接选取某几行,更多的时候是做一个mask操作,通过条件表达式得到bool变量后再选取。

66 | 67 |

pandas的各种选取操作很容易让人搞混,这里有人问了,总结起来就是:

68 | 69 |
    70 |
  • loc: only work on index
  • 71 |
  • iloc: work on position
  • 72 |
  • ix: You can get data from dataframe without to be this in the index
  • 73 |
  • at: get scalar values. It's a very fast loc
  • 74 |
  • iat: Get scalar values. I'ts a very fast iloc
  • 75 |
76 | 77 |

后面两个先不管,首先来看,我们选取某一列数据的时候,可以通过df['a']来得到一个Series对象,但如果'a'不是列名的话,就会报'KeyError'的错。如果是要获取某一行呢?那就用df.loc['a'],这里’a'就是行的index,所以如果你已知某一行的index,就用.loc来访问这行数据。此外,如果只是知道在第几行,不知道确切的index,那么就用.iloc。切记,这里的iloc是integer-loc的缩写,我一开始理解为了index-loc的缩写,结果就乱了。。。最后的ix既可以接收index有可以接收行号作为参数,不过我一般尽量避免用这个。

78 | 79 |

来个进阶点的问题:

80 | 81 |
82 |

现在我有一个样本集df,我想将其随机打乱后抽取其中的N个作为训练集剩下的作为测试集,该怎么做呢?

83 |
84 | 85 |

先想想,答案见这里

86 | 87 |

4.怎么重命名某些列

88 | 89 |

df.rename(columns={'$a': 'a', '$b': 'b'}, inplace=True)

90 | 91 |

很多时候我们抽取特征时,需要对特征重命名之后再与其他特征merge以避免命名冲突。实际使用的时候,比上面的稍微会复杂点。例如

92 | 93 |

df.rename(columns={x:x+'_user' for x in df.columns if not x.endswith('_user')},inplace=True)

94 | 95 |

5.如何合并多列数据得到一个新的DataFrame

96 | 97 |

我们知道pd.concat可以将多行数据合在一起,通过指定参数axis=1即可按列合并

98 | 99 |

6.要习惯级联

100 | 101 |

有时候完成某个功能可能会需要很长的一句话才搞定,比如df1.merge(df2,how='left').merge(df3,how='left').merge(df4')等等。这样子写出来的代码比较丑,呃不过受限于python的格式要求,没法像java一样换行那样写得很优雅。但我自己仍觉得这样能避免过多的中间变量,有助于思维的连贯性~

102 | 103 |

7.groupby的并行化

104 | 105 |

groupby相当好用,但是数据量特别大的时候,有个很头疼的问题,慢!

106 | 107 |

当然,增加参数sort=False可以稍微缓解这个问题,但是,还是不能忍。最关键的问题在于,单线程!服务器上有40个核,却只用了1个,太浪费了!所以简单写个多核的版本。(IPython Notebook据说可以很方便地写并行程序,但是,尝试后失败了@_@)

108 | 109 |
from multiprocessing import Pool
110 | 
111 | def get_fs(group):
112 |      # extract features
113 |      # ......
114 |  
115 |      return pd.DataFrame({'f1':f1,'f2':f2},index=[0])
116 | 
117 | def get_parallel_features(groups):
118 |     p = Pool()
119 |     result = None
120 |     try:
121 |         result = pd.concat(p.map(get_fs, groups))
122 |      except Exception as e:
123 |          print "ERROR:",e.strerror
124 |      finally:
125 |          p.close()
126 |          p.join()
127 |      return result
128 | 
129 | features = get_parallel_features([gp for key, gp in df.groupby(['xxx'])])
130 | 131 |

不过上面代码有点问题,可以看到传入的数据是一个列表[gp for key, gp in df.groupby(['xxx'])],如果数据量很大的化这样子一次性读进来是相当吃内存的。不过根据我在stackoverflow上查到的资料显示,即使传一个iterator过去也无济于事,因为Pool().map操作的内部还是会先把iterator的内容全部读出来然后再操作。所以这个并行版本有待进一步优化。

132 | 133 | 134 | 135 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /resources/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tian Jun Machine Learning 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |
26 |
Tian Jun
27 |
28 |
29 |
Fight For Dream!
30 |
31 |
32 | 33 |
34 |
35 | Essays 36 |
37 |
38 | About 39 |
40 |
41 | 42 |
43 |
44 |
45 |
46 |

47 | Sample Title 48 |

49 |

50 |

Tags: TAG,TAG,TAG

51 |

Update Time: 2016-11-11 11:11:11

52 |
53 |
54 |

55 |

Create Time: 2016-09-09 09:09:09

56 |
57 |
58 |
59 |
60 |
61 | 62 |
63 | 64 |
65 | 66 |
67 |
68 |

69 | 70 | 71 | 72 | 73 |

74 |

75 | 76 | 77 | 78 | 79 |

80 |

81 |
82 |
83 |
84 | 85 |
86 |
87 |
88 | 89 |
90 |
91 | Design From HTML5webtemplates 92 |
93 | This work by Tian Jun is licensed under a 94 |
95 | Creative Commons 96 | Attribution-NonCommercial 4.0 International License. 97 |
98 | Creative Commons License 99 | 103 |
104 |
105 | 106 |
107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /resources/unpublished-md/CS3110学习笔记.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | 3 |
OCaml,DataStructure,FunctionalProgramming
4 | 5 | # Why 6 | 7 | 最初看到[CS3110][]这门课也挺巧合的,之前写这个网站的时候,用到了clojure下的一个库[clojure.zip][],然后知道其中的实现是根据[Functional Perl The Zipper][]这篇paper实现的,尝试去读这篇论文的时候,看到里面用的sample使用OCaml写的,里面的第一句话就是: 8 | 9 | > The main drawback to the purely applicative paradigm of programming is that 10 | many efficient algorithms use destructive operations in data structures such as bit 11 | vectors or character arrays or other mutable hierarchical classification structures, 12 | which are not immediately modelled as purely applicative data structures. A well 13 | known solution to this problem is called *functional arrays* (Paulson, 1991) 14 | 15 | 然而,我连function arrays在这里是指什么都不知道,于是google了下,碰巧就看到了[CS3110][]这门课,初略看了下,感觉挺有意思的一门课,所以打算完整学习下,提升下自己对Functional Programming的理解。 16 | 17 | [CS3110]: http://www.cs.cornell.edu/courses/cs3110/2016fa/ 18 | [clojure.zip]: https://clojuredocs.org/clojure.zip 19 | [Functional Perl The Zipper]: https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf 20 | 21 | # LEC 1 22 | 23 | 这部分主要是关于FP的一些介绍,大部分内容在之前学习clojure的时候已经有所了解了,主要是关于immutability 和FP更elegant。有意思的是,在slides里看到了那句经典语句的出处: 24 | 25 | > “A language that doesn't affect the 26 | way you think about programming 27 | is not worth knowing.” -- Alan J. Perlis 28 | 29 | # LEC 2 30 | 31 | 学习一门编程语言的5个方面: 32 | 33 | 1. Syntax 34 | 2. Semantics 35 | 3. Idioms (个人感觉这一点需要在反复读别人代码的过程中加深体会) 36 | 4. Libraries 37 | 5. Tools 38 | 39 | 有一个观点我觉得挺好,``We don’t complain about syntax``,可能做研究的人更看重一门语言背后的思想,至于语法层面的东西反而不太care。不过咱大多数人都比价肤浅点,因而一门语言是否能被广泛推广的重要原因之一就是语法是否友好...... 40 | 41 | 在OCaml中每一个expression包含了type-checking。这点是这节课终点介绍的内容,需要注意的是,function也是一种value,其对应的type则是由function的输入和输出的type共通构成的。 42 | 43 | 后面的pipeline(即``|>``)与clojure中的``->``宏应该是一个意思。 44 | 45 | > Every OCaml function takes exactly one argument. 46 | 47 | 这句话的意思应该是说,OCaml里的函数默认都是Currying了的。难怪在utop里打印出来的函数类型看起来都有点奇怪,一开始还很困惑如果返回值是函数的话为什么没有区分参数和返回值的类型。(今天看了个知乎的问题[设计闭包(Closure)的初衷是为了解决什么问题?](https://www.zhihu.com/question/51402215),又多了些理解。) 48 | 49 | # LEC 3 50 | 51 | 这一课主要是list和模式匹配。 52 | 53 | 这里list采用``[]``的语法糖来代替``::``表示list的构建。需要注意的是,list成员的类型需要保持一致。在形式上与lisp中的list一致,不过在类型做出了限制。 54 | 55 | 同样,由于类型系统的引入,pattern match的ei类型也需要保持一致。有意思的是,在这里做模式匹配的pattern不仅仅是类型的匹配,还把destruction解构的思想也引入了,从而可以做诸如``a::b::c::[]``的匹配。 56 | 57 | ``List.hd``和``List.tl``分别对应``first``和``rest``(或者``car``,``cdr``),不过讲义里不建议用这个,更倾向模式匹配。 58 | 59 | 另外讲义里还提到了尾递归(Tail Recursion),OCaml里是支持尾递归优化的。有兴趣的话可以深入了解下不同语言对尾递归优化的支持情况。 60 | 61 | # LEC 4 62 | 63 | let expression 是可以嵌套的。(感觉这写法有点蠢......) 64 | 不过这章的进阶版match介绍可以跟clojure中的解构匹敌了。加入类型后更复杂了。 65 | 66 | > (p : t): a pattern with an explicit type annotation. 67 | 68 | 关于option,**In Java, every object reference is implicitly an option.**一句类比就解释清楚了。记得Scala中也有option,按照讲义中的解释,由于类型系统的存在,option能在一定程度上避免C/Java中不经检查使用空指针的问题。特地查了下,为啥clojure中没有类似的用法,了解下有助于理解不同语言的理念[Why is the use of Maybe/Option not so pervasive in Clojure?](http://stackoverflow.com/questions/5839697/why-is-the-use-of-maybe-option-not-so-pervasive-in-clojure)。 69 | 70 | # LEC 5 71 | 72 | ## LEC 5.1 type的基本理解 73 | 74 | ``type``的赋值应该可以理解为C中的``typedef``。这一章花了不少时间来消化(差不多5个上班前和下班后的时间),对type的认识稍微有些清晰了。 75 | 76 | 在Python等语言里,抽象层次一般是从Object开始,然后是抽象类A,接下来各种类的继承。而在这一章的内容则是从底层的数据类型开始采用bottom-up的思想介绍type的。 77 | 78 | 首先是最基础的int, float等类型,然后引入tuple后,有了``int * int``等类似的类型,不过这样的类型不太好描述,于是可以通过type对其重命名下``type point = float * float``,这类用法与以前对于type的理解一致。 79 | 80 | type的第二种用法是枚举,然而,这枚举有点不一样。课件里给了这样一个例子``type day = Sun | Mon | Tue | Wed | Thu | Fri | Sat ``,课件里没提到的一点是,这里的枚举对象命名必须是大写开头的!而且这里的枚举对象并不是普通的``int, float``等基本类型,应该把它当做一个独立的实体来看待。我一开始很难接受这样的定义,因为在Python语言里,``Sun``等必须是个变量要声明好,或者就直接是个``string``类型的基础变量,又或者像clojure一样独立出一个``:key``这样的类型出来,否则很容易让人将这里的类型变量与普通的变量弄混。 81 | 82 | PS: 刚刚查看了下文档,看到大小写的变量名是有特殊意义的。 83 | > Case modifications are meaningful in OCaml: in effect capitalized words are reserved for constructors and module names in OCaml; in contrast regular variables (functions or identifiers) must start by a lowercase letter 84 | 85 | type的第三种用法是对第二种用法的扩展。将之前的枚举对象改成了**构造器**,``type t = C1 [of t1] | ... | Cn [of tn]``讲义中的一个例子如下: 86 | 87 | ```ocaml 88 | type point = float * float 89 | type shape = 90 | | Point of point 91 | | Circle of point * float (* center and radius *) 92 | | Rect of point * point 93 | ``` 94 | 95 | 然后,可以传递构造器中指定类型的数据来得到对应类型的值,例如: 96 | 97 | ```ocaml 98 | let p = Point (1. , 3.) 99 | let c = Circle ((1., 2.), 3.) 100 | let r = Rect ((-1., -2.), (1., 2.)) 101 | ``` 102 | 103 | 同时,上面的``shape``对象可以在类型匹配的过程中解构得到其中的基本元素,然后做相应的运算: 104 | 105 | ```ocaml 106 | let pi = 4.0 *. atan 1.0 107 | let area = function 108 | | Point _ -> 0.0 109 | | Circle (_,r) -> pi *. (r ** 2.0) 110 | | Rect ((x1,y1),(x2,y2)) -> 111 | let w = x2 -. x1 in 112 | let h = y2 -. y1 in 113 | w *. h 114 | 115 | let center = function 116 | | Point p -> p 117 | | Circle (p,_) -> p 118 | | Rect ((x1,y1),(x2,y2)) -> 119 | ((x2 +. x1) /. 2.0, 120 | (y2 +. y1) /. 2.0) 121 | 122 | let area_of_p = area p 123 | let center_of_r = center r 124 | ``` 125 | 126 | ## LEC 5.2 recursive type 127 | 128 | 最典型的就是树结构的定义: 129 | 130 | ``` 131 | type node = {value:int; next:mylist} 132 | and mylist = Nil | Node of node 133 | ``` 134 | 135 | ## LEC 5.3 parameterized variants 136 | 137 | 可以类比模板类,比如java中的``List``,只不过这里的语法有点不一样,类型是反过来了的,比如一个泛型的list是``type 'a mylist = Nil | Cons of 'a * 'a mylist``这样定义的,对于一个具体的数据,前面代码中的``'a``可以是任意实际的类型,比如int。``let x = Cons (3, Cons (1, Nil))``就是一个``int mylist``类型数据的实例。 138 | 139 | LEC 5 中的Natural numbers部分很有意思,以前看SICP的时候,对这个概念理解得不清楚,现在看了代码后又有了更深的理解。 140 | 141 | # LEC 6 142 | 143 | 这部分主要是关于高阶函数的一些应用,理解起来应该难度不大,课后习题部分需要花点时间。有个需要注意的地方是``fold_left``与``fold_right``的区别。``fold_left``是可以做到尾递归优化的,而``fold_right``则不是,如果确实需要的话,需要把列表翻转后再使用``fold_left``。 144 | 145 | Pipeline的书写方式确实优雅一些,可能自己写代码的思维习惯还没有使用过来,感觉从右往左读代码也不是特别麻烦的一件事,只要有合适的缩进来表示。 146 | 147 | # LEC 7 148 | 149 | 这部分主要是OCaml模块化的介绍,包的引入和抽象与其它语言是基本一致的,不同之处在于类型系统单独用一个接口文件来描述,有点像抽象类,但也不完全是。另外具体的实现文件并不是对应了以前Java中类的实现,反而是操作具体的数据。OCaml好像是有自己一套关于类的定义。 150 | 151 | 152 | # READING LIST 153 | 154 | - [Introduction to Objective Caml](http://courses.cms.caltech.edu/cs134/cs134b/book.pdf) 155 | - [Real World OCaml](https://realworldocaml.org/v1/en/html/a-guided-tour.html) -------------------------------------------------------------------------------- /resources/unpublished-md/Understanding Variational Autoencoder.md: -------------------------------------------------------------------------------- 1 | 6 | [toc] 7 | 8 |
Algorithm
9 | 10 | 本文主要记录自己在学习[Automatic Differentiation Variational Inference](https://arxiv.org/abs/1603.00788)过程中的一些参考资料和理解。 11 | 12 | ## 熵(Entropy) 13 | 14 | 以下借用《Statistical Rethinking》一书中的部分内容来理解Entropy及其相关的内容。 15 | 16 | 假设今天天气预报告诉我们,明天有可能下雨(记为事件A),该事件有一定的不确定性,等到第二天结束的时候,不论第二天是否下了雨,之前的不确定性都消失了。换句话说,在第二天看到事件A的结果时(下雨或没下雨),我们获取了一定的信息。 17 | 18 | > **信息**:在观测到某一事件发生的结果之后,不确定性的降低程度。 19 | 20 | 直观上,衡量信息的指标需要满足一下三点: 21 | 22 | 1. 连续性。如果该指标不满足连续性,那么一点微小的概率变化会导致很大的不确定性的变化。 23 | 2. 递增性。随着可能发生的事件越多,不确定性越大。比如有两个城市需要预测天气,A城市的有一半的可能下雨,一半的可能是晴天,而B城市下雨、下冰雹和晴天的概率分别为1/3,那么我们希望B城市的不确定性更大一些,毕竟可能性空间更大。 24 | 3. 叠加性。将明天是否下雨记为事件A,明天是否刮风记为事件B,假设二者相互独立,那么将事件A的不确定性与事件B的不确定性之和,与(下雨/刮风、不下雨/刮风、下雨/不刮风、不下雨/不刮风)这四个事件发生的不确定性之和相等。 25 | 26 | 信息熵的表达形式刚好满足以上三点: 27 | 28 | $$ 29 | \begin{equation} 30 | \begin{split} 31 | H(p) & = - \mathbb{E} \log \left(p_i\right) \\ 32 | & = - \sum_{i=1}^{n} p_i \log\left(p_i \right) 33 | \end{split} 34 | \end{equation}
$$ 35 | 36 | 简单来说,熵就是概率对数的加权平均。 37 | 38 | ## K-L散度(Kullback-Leibler Divergence ) 39 | 40 | > **散度**:用某个分布去描述另外一个分布时引入的不确定性。 41 | 42 | 散度的定义如下: 43 | 44 | $$ 45 | \begin{equation} 46 | \begin{split} 47 | D_{KL}(p,q) & = \sum_{i \in I} p_i \left( \log (p_i) - \log (q_i) \right) \\ 48 | & = \sum_{i \in I} p_i \log \left( \frac {p_i} {q_i} \right) 49 | \end{split} 50 | \label{KL} 51 | \end{equation} 52 | $$ 53 | 54 | KL散度是大于等于0的,可以通过[Gibb's不等式][Gibb's inequality]证明: 55 | 56 | 首先,我们知道: 57 | 58 | $$ 59 | \begin{equation} 60 | \ln x \le x - 1 61 | \end{equation} 62 | $$ 63 | 64 | 于是,根据$\eqref{KL}$中KL散度的定义,可以得到如下不等式: 65 | 66 | $$ 67 | \begin{equation} 68 | \begin{split} 69 | -\sum_{i \in I} p_i \log \left( \frac {q_i} {p_i} \right) & \ge - \sum_{i \in I} p_i \left( \frac {q_i - p_i} {p_i}\right) \\ 70 | &= -\sum_{i \in I} (q_i - p_i) \\ 71 | &= 1 - \sum_{i \in I} q_i \\ 72 | &\ge 0 73 | \end{split} 74 | \end{equation} 75 | $$ 76 | 77 | 可以看出,只有当两个分布一一对应相等的时候才取0。 78 | 79 | 如果将KL散度拆开,可以看作是交叉熵与信息熵之差: 80 | 81 | $$ 82 | \begin{equation} 83 | D_{KL} = H(p,q) - H(p) 84 | \end{equation} 85 | $$ 86 | 87 | 关于KL散度,一个很重要的特性是,$KL(p,q)$一般不等于$KL(q,p)$,也就是说,KL散度是有方向性的。这里借用[Statistical Rethinking][SR]一书第6章中的例子来解释下。 88 | 89 | 假设在地球上随机选一地点,该点位于水面和陆地的概率分别为0.7和0.3,记为$q=(0.7,0.3)$,我们知道火星非常干燥,假设相应的概率为$p=(0.01,0.99)$,可以算出$KL(p,q)=1.14$,$KL(q,p)=2.62$。可以看出,用火星上的分布去估计地球上的分布时,得到的散度更大。直观可以这么理解:一个地球人第一次到火星上时,有很大概率落在陆地上,根据他在地球上的先验,落在陆地上的概率为0.3,因而不会特别惊讶;相反,一个火星人第一次落到地球上时,大概率会落到水面上,这对于火星人来说,是非常惊讶的事(火星上只有0.01的概率),因而其KL散度更大。因此,通常如果选择一个熵值较大的分布去估计某个真实分布时,得到的KL散度会更小一些。 90 | 91 | ## The Evidence Lower Bound 92 | 93 | 给定$\boldsymbol{x} = x_{1:n}$为观测变量,$\boldsymbol{z}=z_{1:m}$为隐变量,对应的联合概率为$p(\boldsymbol{z}, \boldsymbol{x})$,后验可以写成: 94 | 95 | $$ 96 | \begin{equation} 97 | p(\boldsymbol{z} | \boldsymbol{x}) = \frac{p(\boldsymbol{z}, \boldsymbol{x})}{p(\boldsymbol{x})} 98 | \label{posterior} 99 | \end{equation} 100 | $$ 101 | 102 | 其中$p(\boldsymbol{x})$称为证据: 103 | 104 | $$ 105 | \begin{equation} 106 | p(\boldsymbol{x}) = \int p(\boldsymbol{z}, \boldsymbol{x})d\boldsymbol{z} 107 | \end{equation} 108 | $$ 109 | 110 | 变分推断背后的思想是,用一些简单的参数化分布(记为$Q_{\phi}(\boldsymbol{z} | \boldsymbol{x})$)去拟合后验分布$P(\boldsymbol{z}|\boldsymbol{x})$,通过调整参数$\phi$使得$Q_{\phi}$尽可能接近$P(\boldsymbol{z}|\boldsymbol{x})$,从而转换成优化问题。衡量二者相似度的方法之一就是用前面提到的KL散度,按理说,我们应该最小化$KL(P,Q)$,不过实际使用中通常是最小化$KL(Q,P)$,前面也介绍了,二者实际上是不同的,可以参考阅读[A Beginner's Guide to Variational Methods: Mean-Field Approximation][]一文中的*Forward KL vs. Reverse KL*和[KL Divergence: Forward vs Reverse?](http://wiseodd.github.io/techblog/2016/12/21/forward-reverse-kl/)部分来了解为什么优化$KL(Q,P)$。 111 | 112 | $$ 113 | \begin{equation} 114 | KL(q(\boldsymbol{z}) \| p(\boldsymbol{z}|\boldsymbol{x})) = \mathbb{E} [ \log q(\boldsymbol{z})] - \mathbb{E} [\log p(\boldsymbol{z}|\boldsymbol{x})] 115 | \end{equation} 116 | $$ 117 | 118 | 这里的$\mathbb{E}$是相对$q(\boldsymbol{z})$的期望,将$\eqref{posterior}$代入可得: 119 | 120 | $$ 121 | \begin{equation} 122 | 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}) 123 | \label{KLqp} 124 | \end{equation} 125 | $$ 126 | 127 | 由于$p(\boldsymbol{x})$是固定的,于是最小化上式中的KL等价于最大化下面的证据下界: 128 | 129 | $$ 130 | \begin{equation} 131 | ELBO(q) = \mathbb{E}[\log p(\boldsymbol{z},\boldsymbol{x})] - \mathbb{E} [\log q(\boldsymbol{z})] 132 | \label{ELBO} 133 | \end{equation} 134 | $$ 135 | 136 | 上式中的联合概率又可以表示成先验乘以似然,于是有: 137 | 138 | $$ 139 | \begin{equation} 140 | \begin{split} 141 | ELBO(q) & = \mathbb{E} [\log p(\boldsymbol{z})] + \mathbb{E} [\log p(\boldsymbol{x}| \boldsymbol{z})] - \mathbb{E} [\log q(\mathbb{z})] \\ 142 | & =\mathbb{E}[\log p(\boldsymbol{x}|\boldsymbol{z})] - KL(q(\boldsymbol{z})\| p(\boldsymbol{z})) 143 | \end{split} 144 | \end{equation} 145 | $$ 146 | 147 | 直观上看,第一项是似然的期望,最大化ELBO意味着我们希望隐变量能够很好地解释观测数据;第二项是隐变量的先验与估计之间的KL散度,越小越好。由$\eqref{KLqp}$和$\eqref{ELBO}$可以得到: 148 | 149 | $$ 150 | \begin{equation} 151 | \log p(\boldsymbol{x}) = KL(q(\boldsymbol{z}) \| p(\boldsymbol{z}|\boldsymbol{x})) + ELBO(q) 152 | \end{equation} 153 | $$ 154 | 155 | 上面式左边是证据,由于KL散度大于等于0,因而右边的第二项ELBO也就称作证据下界。 156 | 157 | ## Variational Autoencoder 158 | 159 | 关于VAE的文章很多,这里就不详细介绍了。[VAE][]的原文不太好读懂,建议先读[Tutorial on Variational Autoencoders][],然后可以看看一些代码实现,比如[Variational Autoencoder: Intuition and Implementation](http://wiseodd.github.io/techblog/2016/12/10/variational-autoencoder/),和这里[Variational Autoencoders Explained](http://kvfrans.com/variational-autoencoders-explained/)。 160 | 161 | ## Read More 162 | 163 | - [A Beginner's Guide to Variational Methods: Mean-Field Approximation][] 164 | - [Variational Coin Toss](http://www.openias.org/variational-coin-toss) 165 | - [Variational Inference: A Review for Statisticians](https://arxiv.org/abs/1601.00670) 166 | 167 | [Gibb's inequality]:https://en.wikipedia.org/wiki/Gibbs%27_inequality 168 | [SR]:https://book.douban.com/subject/26607925/ 169 | [A Beginner's Guide to Variational Methods: Mean-Field Approximation]:http://blog.evjang.com/2016/08/variational-bayes.html 170 | [Tutorial on Variational Autoencoders]:https://arxiv.org/abs/1606.05908 171 | [VAE]:https://arxiv.org/abs/1312.6114 -------------------------------------------------------------------------------- /resources/published-html/解读libFM.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 解读libFM 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | 解读libFM 18 |
      19 |
    • 20 | 写在前面 21 |
    • 22 |
    • 23 | 问题的本质 24 |
    • 25 |
    • 26 | 理解其数学模型 27 |
    • 28 |
    • 29 | 计算过程优化 30 |
    • 31 |
    • 32 | C+ +源码解析 33 |
    • 34 |
    • 35 | 改写成python和java版本 36 |
    • 37 |
    38 |
  • 39 |
40 | 41 | 42 |

解读libFM

43 | 44 |
Algorithm
45 | 46 |

写在前面

47 | 48 |

由于之前的比赛中用到了这个工具,所以顺带对矩阵分解以及FM深入学习了下。本文将结合其算法原理和Cpp源码,说说自己的使用心得,另外讲讲如何将Cpp源码分别用Python和Java改写。

49 | 50 |

问题的本质

51 | 52 |

对于推荐系统一类问题来说,最核心的就是衡量用户对未接触target的感兴趣程度。CF一类算法的思想是通过相似度计算来直接估计感兴趣程度,矩阵分解一类的思想则是借助隐变量的映射间接得到对target的感兴趣程度。那么,FM呢?

53 | 54 |

个人理解是,FM和词向量一类的做法有相通之处。通过分别对用户和商品等构建一个向量,训练结束后根据用户的向量和商品向量之间的内积,估计出用户对商品的感兴趣程度。OK,借用论文[Factorization Machines]中的一个例子来说明下:

55 | 56 |

假设观测到的数据集如下:

57 | 58 |
(A, TI, 5)
 59 | (A, NH, 3)
 60 | (A, SW, 1)
 61 | (B, SW, 4)
 62 | (B, ST, 5)
 63 | (C, TI, 1)
 64 | (C, SW, 5)
65 | 66 |

现在我们要估计A对ST的感兴趣程度,显然,如果用传统的分类算法,由于训练集中没有出现(A, ST)的pair,所以得到的感兴趣程度是0.但是FM衡量的是\((v_A, v_{ST})\)之间的相似度,具体来说,是这么做的:由观测数据(B, SW, 4)和(C, SW, 5)可以得到\(v_B\)和\(v_C\)之间的相似度较高,此外,根据(A, TI, 5)和(C, TI, 1)可以推测\(v_A\)和\(v_C\)之间的相似度较低。而根据(B, SW, 4)和(B, ST, 5)可以发现,\(v_{SW}\)和\(v_{ST}\)之间的相似度较高。计算\(v_A\)和\(v_{ST}\)便可得知,A对ST的感兴趣程度较低。从这个角度来看,FM似乎是借用一种向量化表示来融合了基于用户和基于商品的协同过滤。

67 | 68 |

理解其数学模型

69 | 70 |

根据前面的描述,给定一个用户商品(U,I)pair后,我们只需计算下式即可得到估计值:

71 | 72 |

\[\hat{y}(x)=\langle v_u, v_I\rangle\]

73 | 74 |

但我们这么做的实际上是有几个潜在假设的: 75 | 1.默认特征矩阵中只有User和Item两类特征; 76 | 2.User和Item维度的特征值为1;

77 | 78 |

实际问题中除了User和Item,还通常有Time,UserContext和ItemContext等等维度的特征。此外,不同User和Item的权值并不一样,所以并不都是1。将上式改写下便得到了FM的原型:

79 | 80 |

\[\hat{y}(x)=w_0+\sum_{i=1}^{n}w_i x_i + \sum_{i=1}^{n}\sum_{j=i+1}^{n}\langle v_i, v_j\rangle x_i x_j\]

81 | 82 |

其中\(w_0\)是全局bias,\(w_i\)是每维特征的权重, \(\langle v_i, v_j \rangle\)可以看做是交互特征的权重。

83 | 84 |

计算过程优化

85 | 86 |

显然,上式的计算复杂度为\(O(kn^2)\),利用二次项展开式可以将计算复杂度降低到线性复杂度:

87 | 88 |

\[ 89 | \begin{split} 90 | &\sum_{i=1}^{n} \sum_{j=i+1}^{n} \langle v_i, v_j \rangle x_i x_j\\ 91 | &=\frac{1}{2}\sum_{i=1}^{n} \sum_{j=1}^{n} \langle v_i, v_j \rangle x_i x_j - \frac{1}{2} \sum_{i=1}^{n}\langle v_i,v_j \rangle x_i x_i \\ 92 | &=\frac{1}{2}\left(\sum_{i=1}^{n}\sum_{j=1}^{n}\sum_{f=1}^{k} v_{i,f}v_{j,f} x_i x_j - \sum_{i=1}^{n}\sum_{f=1}^{k}v_{i,f}v_i x_i x_i\right)\\ 93 | &=\frac{1}{2}\sum_{f=1}^{k}\left(\left( \sum_{i=1}^{n} v_{i,f} x_i \right) \left( \sum_{j=1}^{n} v_{j,f} x_j \right) - \sum_{i=1}^{n} v_{i,f}^2 x_i^2\right) \\ 94 | &=\frac{1}{2}\sum_{f=1}^{k}\left(\left( \sum_{i=1}^{n}v_{i,f} x_i \right)^2 - \sum_{i=1}^{n}v_{i,f}^{2} x_i^2\right) 95 | \end{split} 96 | \]

97 | 98 |

当v的维度从2维扩展到d维时,上式也可以做对应的扩展。对上式中的变量(\(w_0, w_i, v_{i,f}\))分别求导可以得到下式:

99 | 100 |

\[ 101 | \frac{\partial}{\partial \theta} \hat{y}(x) = \left\{ 102 | \begin{aligned} 103 | &1,\hspace{3in}if\ \theta \ is\ w_0\\ 104 | &x_i, \hspace{2.9in}if\ \theta \ is\ w_i\\ 105 | &x_i\Sigma_{j=1}^{n}v_{j,f}x_j - v_{i,f}x_i^2, \hspace{1.15in} if\ \theta \ is\ v_{i,f}\\ 106 | \end{aligned} 107 | \right. 108 | \]

109 | 110 |

C+ +源码解析

111 | 112 |

下面以libfm默认的mcmc方法用于分类任务为例,对其C+ +实现做一个简单分析。

113 | 114 |

概括地来说,训练的过程分为两步:一是计算误差,二是更新系数(\(w_i\)和\(v_{i,f}\))。具体流程如下:

115 | 116 |
    117 |
  1. 读入参数,并初始化。对于mcmc方法,重要的参数只有三个,一是-iter,即迭代次数;二是-dim,用于确定上面的v的维度;三是init_stdev;用于控制v和w的初始值(用正态分布随机初始化,由fm.init()完成)。

  2. 118 |
  3. 接下来调用_learn(train, test)来完成整个训练过程。其中cache是一个eqterm的对象,该对象中的e用于存储最后的误差项,q可以看做一个缓存,存储一些中间计算结果(不得不说,作者把内存用到了极致!!!各种省内存的做法啊)。把_learn函数的核心部分剥离出来就是下面的内容:

    119 | 120 |
    void _learn(Data& train, Data& test){
    121 |  // ...
    122 |  predict_data_and_write_to_eterms(data, cache); // 根据v和w分别计算在训练集和测试集上的估计值,并保存到cache中
    123 |  //根据训练集的target值,计算训练集上的偏差
    124 |  for (uint c = 0; c < train.num_cases; c++) {
    125 |  cache[c].e = cache[c].e - train.target(c);
    126 |  }
    127 | 
    128 |  // 迭代更新
    129 |  for (uint i = num_complete_iter; i < num_iter; i++) {
    130 |  //...
    131 |  draw_all(train); //根据偏差(cache中的e部分)和训练集数据更新v和w
    132 |  predict_data_and_write_to_eterms(data, cache); // 用更新后的v和w继续计算训练集和测试集上的估计值
    133 |  }
  4. 134 |
  5. 先看一下predict_data_and_write_to_eterms函数,其实就是求解\(\hat{y}\)的一个过程。将其分解为2个部分,即\(\sum_{i=1}^{n}w_i x_i\)和\(\sum_{i=1}^{n}\sum_{j=i+1}^{n}\langle v_i, v_j\rangle x_i x_j\)(对mcmc方法而言\(w_0\)项为0),后者的线性时间简化版可以分成\(\frac{1}{2}\sum_{f=1}^{k}\left(\left( \sum_{i=1}^{n}v_{i,f} x_i \right)^2\right)\) 和\(\frac{1}{2}\sum_{f=1}^{k}\left(\sum_{i=1}^{n}v_{i,f}^{2} x_i^2\right)\)这两块。这样子,predict_data_and_write_to_eterms函数对应的源码就好理解了。需要注意的一点是,源码中的Data数据结构实际上是CSC格式的稀疏矩阵,因此C++源码计算过程中是转置后按行取值计算的。理解这一点后,再用python或java改写该部分时,便可直接使用矩阵计算的方式,避免for循环带来的开销。

  6. 135 |
  7. draw_all函数稍微复杂点。先假设没有meta文件(特征的分组信息),前面我们已经得到了v,w参数的梯度,mcmc的做法是计算梯度并更新之后,对结果分别加入了正态分布和gamma分布的采样过程,与此同时,作者边更新v和w,根据新旧v和w的变化程度同步更新error(这个部分理解得不是很透彻,一般而言会在v和w完全更新后再更新e)。最后,对训练集上的目标值做一个截断高斯分布采样,利用采样值对error进一步更新。

  8. 136 |
  9. meta信息的加入,相当于对每类特征内部多了一个正则项,在我使用的过程中,加不加正则项对结果的影响还是蛮大的。主要原因可能是我不同特征组的scale差异有点大,如果采用全局的正则项,难以精确描述不同特征组之间的差异。

  10. 137 |
138 | 139 |

改写成python和java版本

140 | 141 |

由于比赛平台是java的,需要将C++代码改写成java,前期验证功能性的时候,为了快速分析中间变量,先用python写了一遍。整体框架都一致,核心是实现predict_data_and_write_to_eterms和draw_all这两个函数。其中predict_data_and_write_to_eterms的实现比较简单,分别借用python下的scipy中的csc矩阵和java下matrix-toolkits-java包中的FlexCompColMatrix可以将C++源码中的for循环大大简化。但是对于draw_all函数却不太轻松,最核心的问题是前面提到的,C++源码中在更新w和v的同时还在更新error项,导致无法一次通过矩阵运算后再更新error(事实上我一开始这么干过,但是跑出来的效果差太远),无奈,只能和C++一样用循环迭代更新,效率上慢了将近一个数量级。

142 | 143 | 144 | 145 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /resources/unpublished-md/All About Zipper.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | 3 |
DataStructure,OCaml,Clojure
4 | 5 | # 1. 什么是Zipper 6 | 7 | 根据Wikipedia上的[解释](https://en.wikipedia.org/wiki/Zipper_(data_structure)),zipper函数式编程语言中的一种数据结构,能够很方便地遍历和更新list, tree以及一些抽象的循环定义的结构。这个概念的特殊之处在于,函数式编程语言中对数据结构中某个元素的改变不会影响已有的变量,zipper要解决的就是在此条件下如何高效地查找和更新。 8 | 9 | # 2. 如何理解Zipper 10 | 11 | 最早关于zipper的介绍就是[FUNCTIONAL PEARL The Zipper][]这篇paper。文章里用OCaml语言详细描述了两种数据结构,一种是list zipper,另一种是binary tree zipper(自己第一遍读这篇paper的时候,对第一个例子想了很久也没理解清楚,后来忽然发现其实就是list zipper的实现)。 12 | 13 | 个人感觉一开始直接去理解zipper的概念并不太容易,先用Church number这个简单的例子来加深下对$\lambda$演算的认识。 14 | 15 | 根据Wikipedia上的[解释](https://en.wikipedia.org/wiki/Lambda_calculus),一个有效的$\lambda$算子包含以下3条规则: 16 | 17 | 1. 变量$x$,就是最基础的一个$\lambda$算子 18 | 2. $(\lambda x.t)$,(即输入为x,输出为t的匿名函数) 19 | 3. $(ts)$,其中t和s都是$\lambda$算子,将t应用于s,得到的仍然是$\lambda$算子 20 | 21 | 22 | ``` 23 | 0 => λf.λx.x 24 | 1 => λf.λx.f x 25 | 2 => λf.λx.f (f x) 26 | ... 27 | ``` 28 | 29 | 上面几行是wiki里常见的表示Church number的一种方式。看着有点晕?根据前面的定义,我们把上面的表达式加点括号,然后变换下符号就好理解了: 30 | 31 | ``` 32 | 0 => (λt.(λx.x)) ;; 记作 C0 33 | 1 => (λt.(λx.(tx))) ;; 记作 C1 34 | 2 => (λt.(λx.(t(tx)))) ;; 记作 C2 35 | ``` 36 | 37 | 从上面可以看出,其实就是一个两层的匿名函数,上面的Church number可以看做是``Ck t x``经过Currying之后的表示,其中Ck表示对应整数k的Church数, t表示某种transform(可以理解为某种函数变换),x表示变量。假设t为``lambda x: x + 1``,x为0,那么Church number就与整数一一对应了。 38 | 39 | 本文并不打算对$\lambda$演算进行深入探讨,在这里引入Church number的概念是为了说明,在函数式编程的概念中即使是整数n这样的基础数据类型也是通过递归来表示的。需要注意的是,递归的过程是有一个起点的,对于Church number的例子起点就是C0,如果想要得到Ck,则需要递归调用k次运算得到。 40 | 41 | # 3. List Zipper 42 | 43 | [FUNCTIONAL PEARL The Zipper][]中的第一个例子如下: 44 | 45 | ```ocaml 46 | type tree = Item of item | Section of tree list;; 47 | type path = Top | Node of tree list * path * tree list;; 48 | ``` 49 | 理解前面的Church number之后,再加上一点点OCaml语法,就很好理解上面这两行代码,第一行首先递归定义了一个tree类型,其中``Item``是递归定义的起点,假设``item``的类型是int,下面几个例子都是tree的一些实例: 50 | 51 | ```ocaml 52 | let t0 = Item (0) (* 最基本的一个item *) 53 | let t1 = [] (* 空的tree *) 54 | let t2 = Section [Item(1);Item(2);Item(3);Item(4)] 55 | (* 一个Section, 其中每个元素都是一个Item *) 56 | 57 | let t3 = Section [ 58 | Section [Item(1); 59 | Section [Item(1)]]; 60 | Section [Item(2);Item(2)]; 61 | Section [Item(3);Item(3)]] 62 | (* 一个Section中包含了3个Section,每个Section都是可以嵌套的 *) 63 | ``` 64 | 65 | 第二行代码中的``path``类型就是一个tupple,包含3个元素,第一个元素是以tree为元素的列表,第二个元素是递归的path(起点是Top),第三个元素仍然是以tree为元素的列表。下面仍然给出几个实例: 66 | 67 | ```ocaml 68 | let p0 = Node([], Top, []) (* 最基础的一个path,左边和右边的元素都是空列表,中间是起点元素Top *) 69 | let p1 = Node (t0, p0, t0) 70 | let p2 = Node (t1, p1, t2) 71 | ``` 72 | 73 | 有了``tree``和``path``两个类型之后,就可以得到一个list zipper的类型了: 74 | 75 | ```ocaml 76 | type location = Loc of tree * path 77 | ``` 78 | 79 | 这里location是一个长度为2的tupple,包含``tree``和``path``。一个location本质是很自由的,其中的``tree``和``path``都可以是任意实现,通过对二者做一些限制,即可得到一个list zipper的实现。 80 | 81 | 从最简单的开始,一个最小list zipper的定义就是``Loc (Item(0), Top)``,从本质上讲,list zipper 是一种状态描述的结构,这里用paper中的一个例子来描述状态的变化过程。表达式``((a * b) + (c * d))``用前面定义的tree类型来表示就是 82 | 83 | ``` 84 | let t0 = Section [ 85 | Section [Item(a); Item(*); Item(b)]; 86 | Item(+); 87 | Section [Item(c); Item(*); Item(d)];] 88 | ``` 89 | 90 | 此时,t0是最顶层的tree,定义此时的path为``Top``,便得到了t0对应的location为``let l0 = Loc (t0, Top)``。list zipper 定义了如下遍历规则: 91 | 92 | - ``go_down``,就是指从当前location往更深一层走,location的第一个元素tree如果是一个Item的话会报错(没有更深的一层了),否则就是一个list,对其解构取出其元素``[t_first; t_rest]``,其中t_first构成新location中的tree元素,``let p_new = Node ([],p,t_rest)``则构成新location中的path元素。这里p表示上一层location中的path,左边的空list``[]``表示当前Section中t_first左边的tree(显然为空),t_rest则表示当前Section中t_first右侧的tree。相关的OCaml代码表示可以从paper中找到。对``l0``执行``go_down``操作后,可以得到``let l1 = Loc (Section [Item(a), Item(*), Item(b)], Node([], Top, [Item(+); Section [Item(c); Item(*); Item(d)]))`` 93 | - ``go_up``则是``go_down``的逆操作。对当前location中的path元素解构得到``(left, p_up, right)``,取出left和right与当前location中的tree元素拼接得到新的tree_up(left需要翻转)。 94 | - ``go_right``,把当前location中path解构后,得到``(left, p, r::r_rest)``,取出其中的r作为新location的tree,然后当前location中的tree拼接到left上,这样得到了新的location ``Node(r, (tree::left, p, r_rest))``。对前面的``l1``应用``go_right``后得到``let l2 = Loc (Item(+), Node([Section [Item(a), Item(*), Item(b)], Top, Section [Item(c); Item(*); Item(d)]))`` 95 | - ``go_left``也就是`go_right``的逆操作。需要注意处理边界情况。 96 | 97 | 可视化的描述可以看下图: 98 | 99 | ![zipper_0](../public/zipper_0.svg) 100 | 101 | # 4. Clojure中的zipper 102 | 103 | Clojure中的提供了一个[clojure.zip][]模块。与OCaml中的实现稍微有些不同,由于Clojure是动态类型的,并没有类似循环定义的结构类型,因此在[clojure.zip][]库中默认提供了3类zipper(seq-zip, vec-zip, xml-zip),实际上前面说了zipper的思想可以在任何递归的结构中应用,后面会提到如何在clojure中扩充数据结构类型。 104 | 105 | 在Clojure中,一个zipper的基本形式如下: 106 | 107 | ```clojure 108 | [node ;; 这里node可以是seq, vec 或者是其它递归的结构 109 | {:l left_nodes ;; 当前层级下当前node左侧的nodes 110 | :r right_nodes ;; 同理,当前层级下当前node右侧的nodes 111 | :pnodes nodes_from_root_to_up ;; 从root node开始,一直到当前node的父node的列表 112 | :ppath ;; 递归定义,与前面的思想一致 113 | }] 114 | ``` 115 | 116 | [clojure.zip]: https://clojuredocs.org/clojure.zip 117 | 118 | ## 4.1 Clojure中提供的zipper类型与方法 119 | 120 | [clojure.zip][]中包含了以下几类操作zipper的方法: 121 | 122 | 1. 遍历:next, prev, up, down, left, right, children, lefts, rights, rightmost, leftmost 123 | 2. 查询:node, path, 124 | 3. 更新:edit, replace 125 | 4. 插入/删除: insert-child, insert-left, insert-right, remove 126 | 5. 判断: branch?, end? 127 | 128 | 比较有意思的一个实现是``end?``,用来判断``next``的结果是否到达终点。可以先看个例子: 129 | 130 | ```clojure-repl 131 | user=> (zip/vector-zip []) 132 | [[] nil] 133 | user=> (zip/next (zip/vector-zip [])) 134 | [[] :end] 135 | ``` 136 | 137 | 其实就是在``next``的时候加入了一些判断,因为``next``的实现是深度优先的,最后会回到root,然后把path设为``:end``这样每次next的时候用``end?``判断下遍历是否结束。另外需要注意的一个是``remove``,默认会返回根据深度优先排序当前location的前一个location。 138 | 139 | ## 4.2 如何扩展zippe到其它类型? 140 | 141 | zipper的思想可以扩展到很多其他类型的数据结构。在[clojure.zip][]中提供了[zipper][]函数来构建新的zipper类型,这样就可以使用各种遍历更新的函数了。其中,需要提供一下几个函数: 142 | 143 | 1. ``branch? ``,判断一个node是不是还能有children(可以为空) 144 | 2. ``children``,给定一个可以branch类型的node,返回seq类型的children 145 | 3. ``make-node``,给定一个node和children,返回新的node 146 | 4. ``root``,即root node 147 | 148 | 在[hickory][]中有一个``hiccup-zip``的实现,可以学习下如何进行扩展的。 149 | 150 | [zipper]:https://github.com/clojure/clojure/blob/59b65669860a1f33825775494809e5d500c19c63/src/clj/clojure/zip.clj#L18 151 | [hickory]:https://github.com/davidsantiago/hickory/blob/master/src/hickory/zip.cljx 152 | 153 | ## 4.3 更高效的访问方法 154 | 155 | 尽管[clojure.zip][]库中提供了很丰富的遍历和更新函数,但是,想要优雅地修改递归类型的结构仍然是件很繁琐的事,很多时候都需要动手写``loop ... recur ...``,通常认为这样的操作是比较底层的。 156 | 157 | [specter][]就是用来提供高效访问的,其中对zipper类型的数据也提供了很好的接口,能够写出更简洁高效的代码。 158 | 159 | [specter]:https://github.com/nathanmarz/specter 160 | [FUNCTIONAL PEARL The Zipper]:https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf 161 | 162 | # 5 参考 163 | 164 | - [Tree visitors in Clojure](http://www.ibm.com/developerworks/library/j-treevisit/) -------------------------------------------------------------------------------- /resources/unpublished-md/Deep Learning 相关库简介.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | # Deep Learning 相关库简介 3 |
2016-03-17 22:15:40
4 |
2016-03-17 13:34:39
5 |
102
6 |
DeepLearning
7 | 本文将从deep learning 相关工具库的使用者角度来介绍下github上stars数排在前面的几个库(tensorflow, keras, torch, theano, skflow, lasagne, blocks)。由于我的主要研究内容为文本相关的工作,所以各个库的分析带有一定主观因素,以RNN模型为主,CNN相关的内容了解得不是特别深入(本文没有比较caffe和mxnet,其实主要原因还是自己C++太久没用了......)。 8 | 9 | 阅读本文你会了解: 10 | 11 | 1. 各个库是如何对神经网络中的结构和计算单元进行抽象的; 12 | 2. 如何用每个库跑RNN相关的模型; 13 | 3. 各个库学习和使用的难以程度对比; 14 | 4. 在各个库基础之上进一步改进和开发的难易程度; 15 | 16 | 本文**不会**涉及: 17 | 18 | 1. 各个库运行时间效率的对比(我没有自己做过相关的对比实验,但是网上有很多数据可以查); 19 | 2. CNN相关模型的构建(前面提到了自己最近对这块了解得不多); 20 | 3. RNN相关模型的原理和解释(网上很多资料,可以先学习后再进一步阅读); 21 | 22 | ##先说说这几个库之间的大致关系 23 | 24 | > 对于一个优秀的深度学习系统,或者更广来说优秀的科学计算系统,最重要的是编程接口的设计。他们都采用将一个领域特定语言(domain specific language)嵌入到一个主语言中。例如numpy将矩阵运算嵌入到python中。这类嵌入一般分为两种,其中一种嵌入的较浅,其中每个语句都按原来的意思执行,且通常采用命令式编程(imperative programming),其中numpy和Torch就是属于这种。而另一种则用一种深的嵌入方式,提供一整套针对具体应用的迷你语言。这一种通常使用声明式语言(declarative programing),既用户只需要声明要做什么,而具体执行则由系统完成。这类系统包括Caffe,theano和刚公布的TensorFlow。 25 | 26 | 以上是摘自[MXNet设计和实现](http://mxnet.readthedocs.org/en/latest/overview_zh.html)中的一段话。理解了这段话后,对后面各个库的进一步理解很有帮助。MXNet的设计者表示融合了这两种编程模式,我们先抛开mxnet,如上所述torch是采用命令式编程,然后theano和tensorflow是采用声明式编程,skflow对常用的tensorflow的封装,lasagne是对theano的封装,blocks除了对theano进行封装之外还提供了额外的处理机制,keras则是用一套接口同时封装了theano和tensorflow。 27 | 28 | ##从theano说起 29 | 30 | 前面说theano是声明式语言,其基本过程可以描述为以下几步: 31 | 32 | 1. 定义输入变量(x,y),输出变量(z); 33 | 2. 描述变量之间的计算关系(z = x + y); 34 | 3. 编译(f = theano.function([x, y], z); 35 | 4. 求值(f(1,2)); 36 | 37 | 那么,如果我想用theano写一个lstm呢?(具体参见[这里](http://deeplearning.net/tutorial/lstm.html)) 38 | 39 | 1. 准备输入变量x及x_mask(维度为 batch_size * sentence_length * vector_size),目标变量target(维度为batch_size) 40 | 2. **定义**并**初始化**lstm**结构单元**中的**参数**(i, f, o, c) 41 | 3. 定义好一个scan函数,在scan函数中完成每个结构单元的计算,根据lstm网络的性质,将结构单元的输出导入到下一步的输入。在这里,theano中的scan就是一个加强版的for**循环** 42 | 4. 计算loss,采用某种算法更新参数 43 | 5. 编译,f = theano.function([x, x_mask, target], loss) 44 | 6. 对每个batch求值 45 | 46 | 注意前面加黑的几个关键词,在后我们将反复看到每个库的设计者对这几个概念的不同理解。 47 | 48 | ##接着说tensorflow 49 | 50 | tensorflow的设计思想和theano很接近。但是我感觉,tensorflow似乎更强调整体性和结构型。二者的明显区别在于: 51 | 52 | 1. tensorflow默认有一个Graph的结构,所有添加的结点都是在这个图结构上,但是theano中并没有这个概念。我的理解是这个Graph结构对于变量的管理会方便点。 53 | 2. tensorflow目前能够在单机上多卡并行处理,其机制是通过指定gpu来分配计算过程的。因此,可以说其并行机制是数据层面的。而theano需要在载入包之前就指定gpu, 54 | 55 | 其余的很多地方都是相似的,只是换了名字而已,比如: 56 | tensorflow中的variable对应theano下的共享变量shared variables, 57 | tensorflow中的placeholder对应theano中的tensor变量, 58 | 另外tensorflow中为了避免变量的来回复制,其所用的tensor的概念和theano中不太一样 59 | 60 | 然后来看看tensorflow是怎么实现一个LSTM网络的,与theano不同,tensorflow已经对rnn_cell和lstm_cell做了封装,因此写起来容易了很多。 61 | 1. 定义好输入及中间变量 62 | 2. 采用**for循环**将每个lstm_cell的输入输出组装起来 63 | 3. 剩下的也就没有新意了,计算loss,更新state 64 | 65 | 我们后面再讨论这种封装方式与其他库的对比。 66 | 67 | ##再说torch 68 | torch的代码写起来有点像写matlab,本身torch是一个科学计算的大集合(我感觉只是借了lua这个语言一个外壳,方便和c及c++交互罢了,从这个角度来看,我觉得julia这门语言似乎大有潜力),这里我们主要是讨论其中的nn和rnn模块。 69 | 70 | 我自己对lua及torch都不是很熟,但是好在语法不复杂,基本都能看懂,建议大家也能花点时间学习下,这样下次看到哪篇paper里实验部分用torch写的时候,不至于完全看不懂。尽管我对torch的了解不深,但是不得不说torch对剩下其它几个库的设计影响非常大! 71 | 72 | 在torch的nn模块里,首先对整个网络结构做了分类抽象。首先是最顶层的抽象Model,这个里面最基础的就是output和grad_output,记忆中和caffe的抽象是类似的,将计算结果和偏导结果用一个抽象类来表示了。然后是Sequential, Parallel 及 Concat这三个容器。Sequential用于描述网络中一层层的序列关系,典型的就是MLP,Parallel可以用于分别处理输入的不同维度,而Concat则可以用于合并操作。一般来说,我们现在的网络结构都可以通过这三种结构拼接得到。 73 | 74 | 然后再看看torch的rnn模块中如何构建lstm网络的: 75 | 76 | 和tensorflow一样,首先是继承自AbstractRecurrent的一个抽象,然后是参数配置和初始化,不同之处在于,其对LSTM中的门结构也做了进一步抽象,最后,用Sequence代替了for循环的功能,从而提供一个完整的网络结构。 77 | 78 | ##小结 79 | 80 | 通过上面的简单描述,我们对这三个基本库有了些大致的印象。 81 | 82 | - torch是最庞大的库,如果一开始就选择这个库作为工具的话,还算说得过去,否则学习的代价有点大,因为平常做实验涉及的往往不只是跑跑模型这么简单,还涉及到数据的预处理与分析,相关图表的绘制,对比实验等等。这样一来要学习的东西就比较多了。 83 | - theano由于借用了numpy,scipy等python下科学计算的库,相对torch来说要轻松一些。不过,theano本身并没有像其它两个库一样提供cnn,rnn等模型的抽象类,因此往往不会直接使用theano去写模型。 84 | - tensorflow则更像是为神经网络相关模型而定制的。从顶层设计上就以graph为依托,通过不同的Session来控制计算流。 85 | 86 | 从库的使用者角度来说,tensorflow和torch都还不错。但是,如果涉及网络结构(这里特指RNN相关的网络)修改,那么torch要相对容易一些,主要是多了一个Gate的抽象,中间参数的处理上不需要太操心,而tensorflow中LSTM和RNN抽象类的耦合比较紧,如果涉及内部结构的修改会稍稍麻烦点,需要重写的方法比较多。 87 | 88 | tensorflow开源时间不久,先抛开不计。由于theano缺少对神经网络结构的抽象,而torch中nn模块又设计得很合理,于是后面涌现的基于theano的库多多少少都有所参照。 89 | 90 | ##Keras 91 | 92 | keras设计的level有点高,其设想的就是底层的计算模块是可拔插的。这个功能当然看起来很炫酷,我想用tensorflow就用tensorflow,想用theano就用theano作为计算内核,然而代价也是有的,如果你想改其中的结构,复杂程度立马上去了。我觉得这个库的目标用户仅限于使用现有神经网络单元结构的人。如果你发现某个结构keras里没有?没事,这个项目开发者众多,发起个issue,马上就有人填坑(比如highway network, 我在其它几个库里还没发现,这里居然就有了,虽然并不复杂)。如果你想构造个自己的结构单元?那得了,您还是看看后面几个库吧。 93 | 94 | 综上所述,keras最大的亮点就是,简洁而全面。正所谓人多力量大嘛! 由于其底层处于兼容性做了一套自己的封装,想改的话稍显麻烦。 95 | 96 | 如果你觉得看完keras还不知道怎么用?想来点更简单的?有!sklearn用过把,fit, predict两步即可,傻瓜式操作,人人都是机器学习大神。skflow就是类似的,看名字就知道了。不过是基于tensorflow开发的。我反正是没用过这个库...... 97 | 98 | ##Lasagne 99 | 100 | lasagne对网络结构的抽象和上面的几个库有很大的不同,在lasagne中基本抽象单元是Layer,对应到整个神经网络中的一层结构。这个layer可以是cnn也可以是rnn结构,除此之外还有一个MergeLayer,就是多输入多输出的结构。和torch一样,也对Gate门结构做了抽象。 101 | 102 | 这样设计有好处也有麻烦的地方: 103 | 104 | - 好处是,写出来的网络结构很简洁,网络结构的初始化和配置都包含在layer初始化参数里; 105 | - 不方便的地方是,只引入了layer结构抽象层,是在是有些单一,如果能再加一个类似torch中的Sequential结构就perfect了,因为一旦涉及到循环,就不得不回头去使用theano中的scan函数,说实话,scan函数设计得太复杂了点。(当然,作者是不这么认为的,设计者认为lasagne并不是要将theano完全隔离开,相反,lasagne中的所有变量都应该相对theano是透明的Transparency)。 106 | 107 | 此外,lasagne中LSTM网络的实现与前面的大致相同,实现细节上,lasagne中的lstm类直接继承自MergeLayer,然后内部采用``scan``函数实现,像其它库由于有循环结构的抽象实现,因此每个``lstm_cell``类只需要完成单个结点内部的运算(也即只实现``scan``函数中的``_step``辅助函数) 108 | 109 | 总的感觉就是,lasagne在类的抽象上并没有过度设计,整个网络中的参数采用自顶向下的宽度优先算法获取,因此,只要你愿意,你可以任意在这个网络上“凿”个洞,得到该局部网络的关系。有一定的扩展性优势。 110 | 111 | ##Blocks 112 | 113 | 这个库是我最看好的一个库,作者应该从torch中借鉴了很多思想。 114 | 115 | 在这个库中,每一个运算都看做是一块砖,这块砖涉及配置、分配、应用和初始化这四步。更重要的一点是,brick与brick之间可以嵌套,从而得到更抽象的brick。这下子解决了我们前面一直碰到的一个问题,:**抽象层次**的把握!前面大多都是根据抽象层次的不同设计各自的类,而blocks通过嵌套将类统一起来,这样我们在设计自己的模块时,可以很方便地选取合适尺寸的brick。 116 | 117 | 相比lasagne中直接根据layer.get_output来获取参数,blocks采用了ComputationGraph来管理整个网络结构,我的理解是,lasagne的那种方式还是有点野蛮......采用ComputationGraph之后,可以同时制定输入和输出对象,这样即使网络结构变得更复杂了,我们也可以随心所欲指定其中某个部分来更新。 118 | 119 | 下面用blocks[文档中](http://blocks.readthedocs.org/en/latest/rnn.html)关于rnn的一个模型来说明blocks在个性化定制方面是多么优雅。先看图: 120 | 121 | ![upload/blocks_rnn.png_7f55310225850de150599f22799cad5d](http://ontheroad.qiniudn.com/upload/blocks_rnn.png_7f55310225850de150599f22799cad5d) 122 | 123 | 如果要设计这样一个stack的模型,就需要make your hands dirty 了。在lasagne中,这个整体会被看做一个layer,所以需要自己重写一个layer,那跟直接用theano写无异了......keras中也没有提供现有的模型,所以......对于tensorflow和torch来说,需要重写AbstractRecurrent类,从而可以让其接受两个输入。 124 | 125 | 相比之下Keras提供的解决方案就比较优雅,通过iterate将simplerecurrent在time_step维度上缩短到了1步,然后再将整个连接结构封装起来。(也许其它几个库也有类似的控制,暂时没发现) 126 | 127 | 当然,除了上面这些抽象层面的易用性,blocks还提供了丰富的可视化调试和控制接口,以及数据预处理的fuel模块。这些功能在整个工程变得越来越臃肿的时候还是很实用的。 128 | 129 | 130 | ##总结 131 | 132 | 在了解theano的基础上,如果只是想跑一下现有的模型不关注底层的实现,可以直接用Keras,上手快,模块清晰。如果打算持续投入,涉及大量网络结构改进,推荐使用bricks,其对训练过程的控制有着独特的优势。 133 | 134 | 另外需要注意的一点是,同样的数据,用不同库的同一个算法时,结果很可能会出现几个点的差异,这往往是不同库中对默认参数的初始化方式不同引起的,需要仔细检查下。 -------------------------------------------------------------------------------- /resources/published-html/Data Visualization and Analysis in Recsys Challenge.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Data Visualization and Analysis in Recsys Challenge 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 |
  • 17 | Data Visualization and Analysis in Recsys Challenge 18 |
      19 |
    • 20 | 1. 加载数据 21 |
    • 22 |
    • 23 | 2. 准备基础数据 24 |
    • 25 |
    • 26 | 3. 数据规模初探 27 |
    • 28 |
    • 29 | 4. 加入时间序列分析 30 |
    • 31 |
    • 32 | 5. Category和Price信息 33 |
    • 34 |
    35 |
  • 36 |
37 | 38 | 39 |

Data Visualization and Analysis in Recsys Challenge

40 | 41 |
DataVisualization
42 | 43 |

本来想写点关于pandas的,写了点后感觉,毕竟只是个工具,怎么写都只会变成技术文档。还是来点实际的In Acion之类的东西,所以本文就结合pandas和Recsys Challenge 2015的数据来做一些简单分析。侧重点在如何理解和分析数据。

44 | 45 |
    46 |
  • 数据的下载和描述见这里

  • 47 |
  • 下文中绘图代码在这里,(不建议在低于16G内存的机器上直接跑该脚本,中间变量很吃内存。某些不需要的中间变量可以del后释放掉。)

  • 48 |
  • 原始网页的Leaderboard 太丑了...我做了个更Fancy的,有兴趣看看这里

  • 49 |
50 | 51 |

另外本文参考了以下内容:

52 | 53 |
54 |
    55 |
  1. http://aloneindecember.com/words/recsys-challenge-part-i/

  2. 56 |
  3. http://aloneindecember.com/words/recsys-challenge-part-ii/

  4. 57 |
  5. Can we approximate the upper bound score for the 2015 recsys challenge? | Recommended

  6. 58 |
  7. 土人之NLP日志: How to build a naive (very naive) system scored over 30,000 in RecSysChallenge 2015?

  8. 59 |
60 |
61 | 62 |

1. 加载数据

63 | 64 |

首先加载数据,pandas(以下简称pd)的read_csv函数相当丰富,但基本参数和np的loadtxt比较接近,主要差别在于,pd对于二维数据采用了类似SQL的处理方式,读入数据的时候会涉及到Index列的处理。另外结合数据描述,在读入数据的时候需要将TimeStamp设置为datetime.datetime类型。

65 | 66 |

2. 准备基础数据

67 | 68 |

pd可以对数据做一些类似SQL的处理,本文最常用的一个函数便是groupby。由于click和buy数据分别由两个文件存放,为了便于分析,首先将click和buy中相同的字段通过concat连接在一起得到total。提取buy中所有sessID,然后在total中增加一列IsBuy来区分每一条record所属的sessID最后是否有购买行为。分别对click,buy,total按照SessionID分组,得到clicksesIDgroup,buysesIDgroup和totalsesIDgroup。针对click中的category信息,构建items2category的字典。

69 | 70 |

3. 数据规模初探

71 | 72 |

对分组后的数据简单的总数统计可以发现,最后购买了东西的session的平均点击次数是8.74次,而没买东西的session所对应的平均点击次数却只有3.2次。显然这合乎常理,在确认购买东西之前,人们会反复查看和对比。如果只是随便点击两下似乎直接购买的可能不大。

73 | 74 |

通常,为了更可视化地描述人们在每个session中的点击次数,会画出如下的直方图来。

75 | 76 |

a.png_a9b6f1920752df9ac5ec07ab83a06369

77 | 78 |

看到这张图的第一时间,你可能会说,“噢,典型的泊松分布”,然后,没有然后了。其实这样的表示带来的信息量很有限。前面提到点击和购买session的平均次数差异很大,为了可视化地表示出这种差异,不妨将两部分数据拆分开来,选取[0,25]这一密集的区间,将二者的数据做对比,用图来说话。

79 | 80 |

b.png_bae06f391292f2407b24beface0acaaa

81 | 82 |

这里做了一个归一化处理。这样看起来就清楚多了。对于click数据,大部分session都只点了两三次,而且随着点击次数的增加,session个数衰减得非常快。而buy数据(最后产生了购买行为的session)则显示,其衰减的速度要缓慢得多。如果一个session的点击次数超过了10,那么极大可能会产生购买行为。

83 | 84 |

4. 加入时间序列分析

85 | 86 |

我们把session作为一个整体来看待,在这里选取每个session的第一次点击记录来代表。将totalsesIDgroup中的每个小组按照时间排序后,用first()函数得到每个小组的第一条记录。用apply函数将datetime类型的TimeStamp转换成月,然后绘制直方图,同时记录购买点击比例。

87 | 88 |
 89 | 
90 | 91 |

c.png_91ca2e0846a89cd147e3533244ac5e30

92 | 93 |

虽然点击量和购买量在7月8月有一定的起伏,但是比例整体保持不变。再将TimeStamp转换成星期,分析更细致的变化趋势。

94 | 95 |

d.png_b8c026947f2c28889731b0ede52192e5

96 | 97 |

从每周的成交量来看,大致还算比较稳定,在均值0.055附近抖动,而且第26周附近,点击量和购买量虽然同时跳水,但比例几乎保持不变。需要注意的是,由于测试集是从与训练集相同时间段中抽出来的一部分session,这样的稳定性有利于数据的一致性分析。

98 | 99 |

继续细化,将时间细化成天,得到下图。

100 | 101 |

e.png_3b1b190ad55cb7818b3c5d8a35703090

102 | 103 |

规律性相当强,几乎每到周二就跌到谷底,在周日达到峰值。有点意思。那再具体看看一个星期里,每天的点击量购买量究竟差别多大。

104 | 105 |

f.png_5948456e64dfab1166e9b9fdc6c17525

106 | 107 |

周二的点击量降到了高峰时期的1/3,购买点击比降到了一半。继续细化。绘制24小时流量图如下。

108 | 109 |

g.png_a6b1bbdc7972a1b185e137ad0a1321ae

110 | 111 |

9~10点附近一个高峰,18~19点附近一个高峰。

112 | 113 |

继续细化。(细化无止境...)

114 | 115 |

考虑这样一个问题,最后产生购买的session和没有购买的session之间在点击时间上会有怎样的差别呢?会不会最后产生购买的用户在一个页面上倾向于花更多的时间?带着这个问题,分别将点击session和购买session相邻两次点击之间的时间差求平均后表示出来。(横轴表示第x个点击,纵轴表示该次点击与距离上一次点击的平均时间。

116 | 117 |

h.png_3057f6bac63838a0e44ba4bacc8ead9d

118 | 119 |

两条线的交汇点大概是(10,120)附近。一定程度上验证了前面的设想。但随着点击次数的增加,购买的曲线和点击的曲线差不多杂糅在一起。可能点击次数太大后,单从时间因素无法区分用户是否会购买。(这张图的绘制含义描述起来稍微有点绕,有兴趣可以去看源码如何画出来的,就更好理解了)个人比较疑惑的一点是,为什么头两次点击之间的时间会相差3到4分钟这么长......

120 | 121 |

5. Category和Price信息

122 | 123 |

由于Category和price(quantity)信息都有很多缺失值,这一点需要格外注意。先说Category信息,用面积来表示每一类的每天的点击量,选取主要的几类绘制如下:

124 | 125 |

i.png_c1847099bcd3e8a287e422412cb82ecf

126 | 127 |

可以看到,Category的缺失信息集中在上半年(category为0表示类别信息缺失),下半年打折类别的点击量很多(类别为S表示打折)。因此在后面分析的时候需要格外注意该分界线。

128 | 129 |

同样的方法分析Price信息。

130 | 131 |

j.png_c869d85b15b2346775ce97c8b1d034b9

132 | 133 |

有意思的是缺失值占了一半,但却集中在中间那几个月。如果要利用price和quantity信息的化,恐怕需要作分区处理了。

134 | 135 |

最后看一下价格的波动区间吧。其实下图用kde更合适,而不是直方图。 136 | k.png_4d8865499475d03c8cfd97131711e7de

137 | 138 | 139 | 140 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /test/blog_clj/redis_io_test.clj: -------------------------------------------------------------------------------- 1 | (ns blog-clj.redis-io-test 2 | (:require [taoensso.carmine :as car :refer (wcar)] 3 | [clj-time.coerce :as c]) 4 | (:use midje.sweet) 5 | (:use blog-clj.redis-io)) 6 | 7 | (def test-site "test.tianjun.me:") 8 | (defn clean-test-keys! [] (when-let [ks (seq (wcar* (car/keys (str test-site "*"))))] 9 | (wcar* (apply car/del ks)))) 10 | 11 | (facts "about redis read and write" 12 | (against-background [(before :contents (clean-test-keys!)) 13 | (after :contents (clean-test-keys!)) 14 | (around :facts (binding [site test-site] ?form))] 15 | (fact "check create new blog process" 16 | (new-blog {:title "[1]This is a test title!" 17 | :tags #{"tag1" "tag2" "tag3"} 18 | :body "[1]blog body: this is the blog body" 19 | :update-time "2016-08-25 07:07:07"}) 20 | => truthy 21 | 22 | (new-blog {:title "[2]This is a test title!" 23 | :tags #{"tag1" "tag4"} 24 | :body "[2]blog body: this is the blog body" 25 | :update-time "2016-08-25 08:08:08"}) 26 | => truthy 27 | 28 | (new-blog {:title "[3]This is a test title!" 29 | :tags #{"tag3" "tag4" "tag5"} 30 | :body "[3]blog body: this is the blog body" 31 | :update-time "2016-08-25 09:09:09"}) 32 | => truthy) 33 | (fact "check the newly created blog" 34 | (get-blog 1) => (contains [[:title "[1]This is a test title!"] 35 | [:tags #{"tag1" "tag2" "tag3"}] 36 | [:body "[1]blog body: this is the blog body"] 37 | [:update-time "2016-08-25 07:07:07"] 38 | [:create-time "2016-08-25 07:07:07"]]) 39 | (get-blog 2) => (contains [[:title "[2]This is a test title!"] 40 | [:tags #{"tag1" "tag4"}] 41 | [:body "[2]blog body: this is the blog body"] 42 | [:update-time "2016-08-25 08:08:08"] 43 | [:create-time "2016-08-25 08:08:08"]]) 44 | (get-blog 3 :title :update-time) => (contains [[:title "[3]This is a test title!"] 45 | [:update-time "2016-08-25 09:09:09"]])) 46 | (fact "check all blog titles" 47 | (get-all-blogs-titles) => (just (just ["[3]This is a test title!" 48 | "3" 49 | (str (c/to-long "2016-08-25 09:09:09"))]) 50 | (just ["[2]This is a test title!" "2" 51 | (str (c/to-long "2016-08-25 08:08:08"))]) 52 | (just ["[1]This is a test title!" 53 | "1" 54 | (str (c/to-long "2016-08-25 07:07:07"))]))) 55 | (fact "check tags " 56 | (get-blogids-with-tag "tag1") => (just #{"1" "2"}) 57 | (get-blogids-with-tag "tag2") => (just #{"1"}) 58 | (get-blogids-with-tag "tag3") => (just #{"1" "3"}) 59 | (get-blogids-with-tag "tag4") => (just #{"2" "3"}) 60 | (get-blogids-with-tag "tag5") => (just #{"3"})) 61 | (fact "check updates" 62 | (update-blog {:body "Body 1 changed!" 63 | :blog-id "1" 64 | :update-time "2016-08-25 11:11:11"}) => truthy 65 | (get-blog 1) => (contains [[:body "Body 1 changed!"] 66 | [:update-time "2016-08-25 11:11:11"]]) 67 | (update-blog {:body "Body 2 changed!" 68 | :blog-id "2" 69 | :update-time "2016-08-25 12:12:12"}) => truthy 70 | (get-blog 2) => (contains [[:body "Body 2 changed!"] 71 | [:update-time "2016-08-25 12:12:12"]]) 72 | (update-blog {:tags #{"tag1" "tag2" "tag4"} 73 | :blog-id "3" 74 | :update-time "2016-08-25 13:13:13"}) => truthy 75 | 76 | (get-blog 3) => (contains [[:tags #{"tag1" "tag2" "tag4"}]]) 77 | (get-blogids-with-tag "tag1") => (just #{"1" "2" "3"}) 78 | (get-blogids-with-tag "tag2") => (just #{"1" "3"}) 79 | (get-blogids-with-tag "tag3") => (just #{"1"}) 80 | (get-blogids-with-tag "tag4") => (just #{"2" "3"}) 81 | (get-blogids-with-tag "tag5") => (just []) 82 | (get-all-blogs-titles) => (just (just ["[3]This is a test title!" 83 | "3" 84 | (str (c/to-long "2016-08-25 13:13:13"))]) 85 | (just ["[2]This is a test title!" 86 | "2" 87 | (str (c/to-long "2016-08-25 12:12:12"))]) 88 | (just ["[1]This is a test title!" 89 | "1" 90 | (str (c/to-long "2016-08-25 11:11:11"))]))) 91 | (fact "check deletes" 92 | (delete-blog 1) => truthy 93 | (get-blog 1) => (just {}) 94 | (get-blogids-with-tag "tag1") =not=> (contains "1") 95 | (get-blogids-with-tag "tag2") =not=> (contains "1") 96 | (get-blogids-with-tag "tag3") =not=> (contains "1")))) 97 | -------------------------------------------------------------------------------- /resources/unpublished-md/Machine Learning A Bayesian and Optimization Perspective.md: -------------------------------------------------------------------------------- 1 | 6 | [toc] 7 | 8 |
Book
9 | 10 | # Machine Learning A Bayesian and Optimization Perspective 读书笔记 11 | 12 | 个人感觉这本书的覆盖面有点广,更适合有一定基础之后在回过头来读。暂时先看完了2,3,4章,打个卡,后面有机会再继续读这本书。 13 | 14 | ## CH02 15 | 16 | 这一章先回顾了些基础知识,主要是概率和信息论里的基础,顺带复习了下《概率统计》那本书。2.4节的随机过程没太看懂,这块要补一补相关的基础,以后需要的时候再回头来看看。 17 | 18 | 下面是我写的部分习题的解答。 19 | 20 | ### P2.1 21 | 22 | 23 | $$\begin{equation} 24 | E(X) = \sum_{i=1}^n E(X_i) = np 25 | \end{equation}$$ 26 | 27 | $$\begin{equation} 28 | \begin{split} 29 | Var(X) &= \sum_{i=1}^n Var(X_i) \\ 30 | &= n\,Var(X_i) \\ 31 | &= n \left( E(X_i^2) - (E(X_i))^2\right) \\ 32 | &= n(p - p^2) 33 | \end{split} 34 | \end{equation}$$ 35 | 36 | ### P2.2 37 | 38 | $$\begin{equation} 39 | E(X) = \int_a^b x \frac{1}{b-a} \, dx = \frac{a+b}{2} 40 | \end{equation}$$ 41 | 42 | $$\begin{equation} 43 | \begin{split} 44 | Var(X) &= E(X^2) - (E(X))^2 \\ 45 | &= \int_a^b x^2 \frac{1}{b-a} \, dx - \left( \frac{a+b}{2}\right) ^ 2\\ 46 | &= \frac{a^2 + ab + b^2}{3} - \frac{a^2 + 2ab + b^2}{4} \\ 47 | &= \frac{a^2 -2ab + b^2}{12} 48 | \end{split} 49 | \end{equation}$$ 50 | 51 | ### P2.3 52 | 53 | See the Appendix A.1 in [http://cs229.stanford.edu/section/gaussians.pdf](http://cs229.stanford.edu/section/gaussians.pdf) 54 | 55 | ### P2.4 56 | 57 | The definition of beta function is given by: 58 | 59 | $$\begin{equation} 60 | B(\alpha, \beta) = \int_0^1 x^{\alpha-1} (1-x)^{\beta - 1}\, dx 61 | \label{beta_eq} 62 | \end{equation}$$ 63 | 64 | And we have: 65 | 66 | $$\begin{equation} 67 | B(\alpha, \beta) = \frac{\Gamma(\alpha) \Gamma(\beta)}{\Gamma(\alpha + \beta)} 68 | \label{beta_eq_gamma} 69 | \end{equation}$$ 70 | 71 | The Beta distribution is: 72 | 73 | $$\begin{equation} 74 | f(x \mid \alpha, \beta) = \left\{\begin{matrix} 75 | \frac{\Gamma(\alpha + \beta)}{\Gamma(\alpha) \Gamma(\beta)} x^{\alpha -1}(1-x)^{\beta - 1} & for \; 0\lt x \lt1 \\ 76 | 0 & otherwise. 77 | \end{matrix}\right. 78 | \end{equation} 79 | $$ 80 | 81 | So we get: 82 | 83 | $$\begin{equation} 84 | \begin{split} 85 | E(X^k) &= \int_0^1 x^k f(x \mid \alpha, \beta) \, dx \\ 86 | &= \frac{\Gamma(\alpha + \beta)}{\Gamma(\alpha) \Gamma(\beta)} \int_0^1 x^{\alpha + k -1}(1-x)^{\beta - 1} \, dx \\ 87 | &= \frac{\Gamma(\alpha + \beta)}{\Gamma(\alpha)\Gamma(\beta)} \cdot \frac{\Gamma(\alpha + k) \Gamma(\beta)}{\Gamma(\alpha + k + \beta)} 88 | \end{split} 89 | \label{beta_moment} 90 | \end{equation}$$ 91 | 92 | And the Gamma function has a property that: 93 | 94 | $$\begin{equation} 95 | \begin{matrix} 96 | \Gamma(\alpha) = (\alpha - 1)\Gamma(\alpha-1) & if \alpha \gt 1 97 | \end{matrix} 98 | \label{gamma_property} 99 | \end{equation}$$ 100 | 101 | 102 | Combine $\eqref{beta_moment}$ and $\eqref{gamma_property}$, we have that: 103 | 104 | $$\begin{equation} 105 | E(X) = \frac{\alpha}{\alpha + \beta} 106 | \end{equation}$$ 107 | 108 | $$\begin{equation} 109 | E(X^2) = \frac{\alpha (\alpha + 1)}{(\alpha + \beta)(\alpha + \beta + 1)} 110 | \end{equation}$$ 111 | 112 | So we get: 113 | $$\begin{equation} 114 | Var(X) = E(X^2) -(E(X))^2 =\frac{\alpha \beta}{(\alpha + \beta)^2(\alpha + \beta + 1)} 115 | \end{equation}$$ 116 | 117 | ### P2.5 118 | 119 | Bellow I will show that $\eqref{beta_eq_gamma}$ and $\eqref{beta_eq}$ are equal: 120 | $$ 121 | \begin{split} 122 | \Gamma(a)\Gamma(b) 123 | &= \int_0^\infty e^{-x} x^{a-1} \, dx \int_0^{\infty} e^{-y} y^{b-1} \, dy \\ 124 | &= \int_0^\infty \int_0^\infty e^{-(x+y)} x^{a-1} y^{b-1} \, dx \,dy \\ 125 | \end{split}$$ 126 | 127 | change $t = x + y$, naturally $t \ge x$ we have: 128 | 129 | $$\begin{split} 130 | \int_0^\infty \int_0^\infty e^{-(x+y)} x^{a-1} y^{b-1} \, dx \,dy 131 | &= \int_0^\infty e^{-t} \left(\int_0^\infty x^{a-1}(t-x)^{b-1} \, dx\right)\, dt \\ 132 | & \stackrel{x=\mu t}{=}\int_0^\infty e^{-t} \left( t^{a+b-1 } \int_0^{1} \mu^{a-1} (1-\mu)^{b-1} \, d\mu\right) dt \\ 133 | & = \int_0^{1} \mu^{a-1} (1-\mu)^{b-1} \, d\mu \int_0^{\infty}e^{-t} t^{a+b-1} \, dt \\ 134 | &= \int_0^{1} \mu^{a-1} (1-\mu)^{b-1} \, d\mu \cdot \Gamma(a+b) 135 | \end{split}$$ 136 | 137 | ### P2.6 138 | 139 | $$\begin{equation} 140 | \begin{split} 141 | E(X^k) &= \int_0^{\infty} x^k \frac{b^a}{\Gamma(a)} x^{a-1} e^{-bx} \, dx \\ 142 | &\stackrel{z=bx}{=}\int_0^{\infty} \frac{b^a}{\Gamma(a)} \left(\frac{z}{b}\right)^{a-1+k}e^{z} \frac{1}{b}\, dz \\ 143 | &= b^{-k} \frac{\Gamma(a+k)}{\Gamma(a)} \\ 144 | \end{split} 145 | \end{equation}$$ 146 | 147 | Then we have: 148 | $$\begin{align} 149 | E(X) &= \frac{a}{b} \\ 150 | E(X^2) &= \frac{(a+1)a}{b^2} \\ 151 | Var(X) &=E(X^2)-(E(X))^2 = \frac{a}{b^2} 152 | \end{align}$$ 153 | 154 | ### P2.7 155 | 156 | See [http://www.mas.ncl.ac.uk/~nmf16/teaching/mas3301/week6.pdf](http://www.mas.ncl.ac.uk/~nmf16/teaching/mas3301/week6.pdf) for details. 157 | 158 | ### P2.8 159 | 160 | Suppose $Var(X_i) = \sigma^2$, so we have $Var(\bar{X})=\sigma^2/n$, when $n \to \infty$, $Var(\bar{X}) \to 0$. 161 | 162 | ### P2.11 163 | 164 | Let $f(x) = \ln x -x + 1$, the first order is $f'(x) = 1/x - 1$, so we have $f(x) \ge f(1) = 0$. 165 | 166 | ### P2.12 167 | 168 | $$\begin{equation} 169 | \begin{split} 170 | I(x,y) &= \sum_{x \in \mathcal{X}} \sum_{y \in \mathcal{Y}}P(x,y) \log {\frac{P(x,y)}{P(x)P(y)}} \\ 171 | &= - \sum_{x \in \mathcal{X}} \sum_{y \in \mathcal{Y}}P(x,y) \log {\frac{P(x)P(y)}{P(x,y)}} \\ 172 | & \ge - \sum_{x \in \mathcal{X}} \sum_{y \in \mathcal{Y}} P(x,y)\left(\frac{P(x)P(y)}{P(x,y)} -1\right) \\ 173 | \end{split} 174 | \end{equation}$$ 175 | 176 | ### P2.13 177 | 178 | $$ 179 | \begin{equation} 180 | \begin{split} 181 | -\sum_{i \in I} p_i \log \left( \frac {q_i} {p_i} \right) & \ge - \sum_{i \in I} p_i \left( \frac {q_i - p_i} {p_i}\right) \\ 182 | &= -\sum_{i \in I} (q_i - p_i) \\ 183 | &= 1 - \sum_{i \in I} q_i \\ 184 | &\ge 0 185 | \end{split} 186 | \end{equation} 187 | $$ 188 | 189 | ### P2.15 190 | 191 | See [https://stats.stackexchange.com/questions/66108/](https://stats.stackexchange.com/questions/66108/why-is-entropy-maximised-when-the-probability-distribution-is-uniform) for details. 192 | 193 | ## CH03 194 | 195 | 这一章先从参数估计入手,介绍了参数视角下的线性回归和分类问题,作者提到了本书重点关注有监督的问题,无监督的问题没有涉及。 196 | 197 | 下面是我写的部分习题的解答。 198 | 199 | ### P3.1 200 | 201 | $$\begin{equation} 202 | \begin{split} 203 | \sigma_c^2 &= Var(\hat{\theta}) \\ 204 | &= Var(\frac{1}{m} \sum_{i=1}^{m} \hat{\theta}_i) \\ 205 | &= \frac{1}{m^2} \sum_{i=1}^{m} Var(\hat{\theta}_i) \\ 206 | &= \frac{1}{m} \sigma^2 207 | \end{split} 208 | \end{equation}$$ 209 | 210 | ### P3.2 & P3.3 211 | 212 | See [http://willett.ece.wisc.edu/wp-uploads/2016/01/15-MVUE.pdf](http://willett.ece.wisc.edu/wp-uploads/2016/01/15-MVUE.pdf) for details. 213 | 214 | ### P3.4 215 | 216 | According to the quadratic formula, we can easily get the inequation. 217 | 218 | ### P3.5 219 | 220 | By taking the derivative of $MSE(\hat{\theta}_b)$ with respect to $\alpha$, we let: 221 | 222 | $$\begin{equation} 223 | 2(1+\alpha)MSE(\hat{\theta}_{MVU}) + 2\hat{\theta}_o^2 \alpha = 0 224 | \end{equation}$$ 225 | 226 | and then we get: 227 | 228 | $$\begin{equation} 229 | \alpha_* = - \frac{MSE(\hat{\theta}_{MVU})}{MSE(\hat{\theta}_{MVU}) + \hat{\theta}_o^2} = - \frac{1}{1+\frac{\hat{\theta}_o^2}{MSE(\hat{\theta}_{MVU})}} 230 | \end{equation}$$ 231 | 232 | ### P3.6 233 | 234 | Since the expectation in Eq 3.26 is taken with respect to $p(\mathcal{X};\theta)$, if the integration and differentiation can be interchanged, we can first take the integration of $p(\mathcal{X};\theta)$, resulting Eq 3.26 --------------------------------------------------------------------------------