Node.js是干啥的
之前一直以为Node就是一个可以在shell里面执行js的工具,现在看好像也不止于此。我现在的理解就是这个Node是一种新的网络Server开发方式,尤其是为了Web开发而设立的。其特点就是事件驱动,单进程非阻塞IO方式。这样据说可以在IO密集的场景,大幅提高应用的性能。
想在自己的Hexo博客上加个评论功能。Hexo是个基于node.js静态博客系统,可以利用github page发布个人博客。这类博客系统添加评论系统,都是通过增加一个js代码,将评论信息提交到后台存储起来。(怎么调动回来的??)
网上搜了一下,发现有很多的备选方案,选择了最简单的Valine。还有gitcomment是基于github issue系统保存评论数据,但需要github账号登录。(github issue怎么用??Oauth是什么?)
Valine需要在LeanCloud上注册一个账号,所有的数据会保存在LeanCloud上。
在什么地方添加Valine的js代码呢?网上很多文章是说新版的NeXT已经添加了Valine的功能,只要修改配置就可以了,但是想多了解一些Hexo和Next主题的内部机制,所以想自己动手修改。代码看的很费劲,Hexo基于node.js用到了很多Node提供的全局对象,而且又做了很多的概念抽象,代码看起来一点都不容易。主题的代码也是懵逼,里面全是.swig结尾的文件,后来才知道是一个模板引擎的文件后缀。
最后终于找到一篇文章说,主题渲染的入口是_layout.swig文件,Hexo会根据source目录下的所有md文件,使用特定的模板进行渲染,默认使用post.swig文件进行渲染。所谓渲染,我的理解就是进行各种生成和替换,最终生成一个静态HTML。
1 | new Valine({ |
就是把这样一短代码加入到post.swig中,就可以展示评论输入框了。调试的时候差了一点,还需要去LeanCloud上建一个Comment类,这时Valine就可以提交代码了。
最近在看NLP,其中的基础就是word embedding,我也看了cs224n的课,也看了那个关于word2vec的论文,但我看到的就是优化这么一个目标函数
$$ \log{\sigma(v_{w_{O}}^{'T}v_{w_{I}})} + \sum_{i=1}^{n}E_{w_{i}\sim P_{n}(w)}[\log\sigma(-v_{w_{i}}^{'T}v_{w_{I}})] $$
这是什么?原论文基本没有写细节。我特别想知道,这样的函数怎么做back propagation。于是总算找到这个:《word2vec Parameter Learning Explained》。人家确认牛,不仅深入浅出的给出数学推导,还能给出一个直观的,说人话的解释,让人更加能够明白word2vec到底在干什么。我想尝试整理一下,看看都学到了哪些,所以总结如下:
训练word embedding的网络结构是这样的,他的隐藏层后面并没有任何非线性函数。
为了说明计算过程,模型简化为1对1 的预测,类似bigram。
模型的输入到hidden的计算是
$$h=W^{T}X = W_{(k,.)}^{T}$$
X是one-hot向量,是V*1的列向量,W是V*N的矩阵,就是word embed,每行代表词表中的一个词。h是N*1的列向量。
h向量就是W的第k行,也就是词表中第k个词的向量。$$W^{'}$$ 是N*V的矩阵,可理解为另外一组word embed。 从h预测output时,相当于是输入词的embed和输出词embed做内积,得出一个score u
$$ u= W^{'T}h$$
u是V*1的向量,通过softmax,得出预测的每个词的概率y $$ y_{i} = \frac{exp(u_{i})}{\sum_{k=1}^{V} exp(u_{k})} $$
有了预测概率,有了true target,就可以通过交叉熵来计算损失函数了,经过基本变形就得到了
$$
\begin{align*}
E &= - log \space y_{j^{}} \ &= -u_{j^{}} + \log \sum_{j’=1}^{V} exp(u_{j’})
\end{align*}
$$
我真正的困惑是从下面开始,不知道怎么去做导数反向传递,好在《word2vec Parameter Learning Explained》给出的推导过程特别详细,我才能勉强看懂。
就是这几个公式依次求导。可是反向求导为啥难理解呢,我觉得主要是因为,前向过程都是用矩阵或向量计算的,求导时需要很多变换,还需要考虑转置的问题,行列的问题,转换步骤一多,思维就乱掉了。
首先求关于$$u_{j}$$ 的导数 $$ \frac{\partial E}{\partial u_{j}} = y_{j} - t_{j} := e_{j} \qquad j\in [1,V] $$ 然后求关于$$W_{i,j}^{‘}$$ 的导数 $$ \begin{align*} \frac{\partial E}{\partial W_{ij}^{’}} &= \frac{\partial E}{\partial u_{j}} . \frac{\partial u_{j}}{\partial W_{ij}^{‘}} \ \&=e {j}.h{i} \qquad\qquad j\in [1,V]\quad i \in [1,N] \end{align*} $$ 这个要理解我觉得最好还是把矩阵画出来,然后一步步去推导比较容易理解。其实最后$$\frac{\partial E}{\partial W^{’}} $$ 会最终变为一个矩阵,参数更新也都是通过矩阵运算的方式。这个公式在原论文中给出了一个直观理解,就是对于输出参数矩阵的每个词,根据预测的概率误差,相应的远离输入词。相当于这次word vector在他们的高维空间,不停的移动,已获得最佳的位置。当训练样本足够多了,每个word vector也就基本稳定不会移动了,这时候就可以把参数矩阵拿出来直接当做word embedding使用了。这些word embedding中包含了很多语义特征。
其他的推导懒得写了,如果以后忘记了,就回看论文好了。
以前总听别人说做广告CTR需要用到LR,经常会在log中提取各种特种,一般是从Hadoop中跑出特征,然后再放到LR的并行系统上,进行计算,然后通过划分实验流量,做A/B Test。如果效果好,则新特征上线。
而我之前由于没有ML的基础,所以直接学NN,感觉全世界就只有NN,回头想想好像LR还是能够在其他很多场景下应用。
LR和NN的区别主要在于,LR是把一个线性函数 $ f(x) = Wx + b $ 的结果,通过sigmoid函数映射到(0,1)上,然后用概率来解释这个结果。映射函数如下: $$ \sigma(z)=\frac{1}{1+e^{-(Wx+b)}} $$ 对于线性函数,他的能力有限,比较直来直去,无非就是直线或者平面,来区分所有样本。这样对于一些比较复杂的样本分布,就会产生很多误差。
第一个图是逻辑回归对复杂数据分布的分类情况,第二个图是神经网络hidden layer有4个节点的分类情况(参考:Planar data classification with one hidden layer)。明显具有hidden layer的NN表现更好。科学家们好像能够证明NN可以表示任何复杂的函数,我没必要深究其中的数学证明了。
我目前的理解就是,神经网络把多个神经单元(线性函数+激活函数)整合在一起,然后在隐含层通过一种无法描述的魔力自行提取特征,然后在最后一层,仍然用线性分类函数对结果进行分类。后面的Loss function、SGD就都是一样的了。神经网络多了一点就是BP算法,主要的原理就是复合函数求导。前一篇(http://wliang.me/2018/02/05/20180205_DL学习笔记_逻辑回归/)的时候我也总结过。
我想总结时没比较加入太多数学公示,还是把直观的理解通过文字图表表达清楚最好,毕竟我又不去做research,我只是想做application。对自己放松点要求。
Python这个语言在语法上,数据结构上提供了很多遍历,使得开发代码量上了很多。
每次看别人的Python代码总能学到一些新的用法,以下几个小收获记录下来,算是一点总结:
1,tuple
1 | x = 'wangliang' |
2,zip函数可以把两个序列,按位置先后,两两组成一个tuple pair,很方便形成dict
1 | x = 'wangliang' |
3,sorted函数可以对序列类型进行排序
4,dict按值排序
1 | #https://stackoverflow.com/questions/613183/how-do-i-sort-a-dictionary-by-value |
这些方便的数据处理方式,可以应用到机器学习的数据预处理上。机器学习模型部分实现代码量不大,而且有很多框架可以直接调用。但是数据预处理部分就要自己来了。
提到ML,很郁闷。最近一直在看NLP,但是发现看论文好难找到通透的感觉,而且数学基础不够,也导致问题多多。这个会不会又是一个大坑呢。反正,在不为钱发愁的情况,尽量做自己能做且喜欢的事情吧。
$$ \hat{y}=\sigma(w^{T}X+ b), \quad where\ \sigma(z)=\frac{1}{1+e^{-z}} $$
$$
Loss= - (y\log{\hat{y}} + (1-y)\log{(1-\hat{y})})
$$
Cost Function:
$$
J(w,b) = \frac{1}{m} \sum_{i=0}^m{L(\hat{y}, y)}
$$
梯度下降:根据LostFunc,给出dw,dx,db 的计算式,得到值。每个变量,沿梯度方向变化一点,整个loss就会变化,不停的反复迭代,就会找到最佳的参数。
我一直有个错误的认识,就是dw, db和Loss值有关,dw,db需要使用Loss值进行计算,其实不是,dw只和LostFunc的表达式有关,只和其他参数当前值有关,有没有Loss值都不重要,只是每次迭代之后,需要看看Loss是不是在减少了,而不需要通过Loss计算梯度。
简单的路由绑定就像这样
1 | @app.route('/') |
在Flask内部可以这样做
1 | def index(): |
在add_url_rule函数中核心代码主要是这几行
1 | rule = self.url_rule_class(rule, methods=methods, **options) |
其中url_rule_class和url_map都是利用的werkzeug.routing的代码,核心的类就是Rule, Map, MapAdapter,代码看的我头疼,一方面现在智力下滑严重,很多看不懂,另外一方面,感觉怎么这么麻烦不就是简单的从url到具体函数的匹配么?可是深入看的话,发现人家的功能确实强大,比如可以进行变量转换,还能生成url。
最简单的Flask web程序就是这么几行
1 | from flask import Flask |
其实这里是创建了一个Flask实例app, app这个对象有符合WSGI规范的__call__
接口,外部进程会把app当做一个模块进行调用。
在Flask内部,wsgi_app调用dispatch_request函数,最终调用self.view_functions[rule.endpoint](**req.view_args)
这里view_function就是在应用中通过@app.route
关联的视图函数。视图函数进行业务处理,有的MVC框架管这个函数也叫controller。
Flask框架得到视图函数的返回值后,会把返回值按WSGI规定,处理为response对象。最后通过底层werkzeug的Response返回相应的结果。
流程上其实也不复杂,只是和其他框架一样,太多地方绕来绕去,不知道是不是OO功底太差,两三次类的替换,我就晕了。不过暂时也没必要扣细节了,先把整体流程明白了就好了
《WSGI: The Server-Application Interface for Python 》这篇文章是我喜欢的那种套路,按时间进程交代技术眼花的过程。他讲到1993年,web开始兴起,但是都是静态页面。随后大牛在NCSA HTTPd(apache 前身)实现了CGI。从那之后,网上的内容都是动态网站了,互联网用户也大幅增长。
但CGI有很多问题,随后大牛们又实现了FastCGI(1996)和mod_python(2000)。在2003年,又提出了PEP-3333实现WSGI,是个高层抽象,统一的接口。
WSGI对应用的要求就是要有一个接口能接受environ
和start_response
两个参数。而且能够返回一个iterable的body。
另外一篇是《https://jeffknupp.com/blog/2014/03/03/what-is-a-web-framework/》 中文版在这
比较清晰解释了WebFramework的主要功能,现在web框架这么多,其实核心功能也就是这些了。
首先要处理好HTTP协议的请求与应答。HTTP协议中主要的请求类型包括GET, POST, ,应答也是有固定套路的。这些都可以由框架完成。
然后框架要处理好URL路由(routing)和模板(template)的问题,这样可以比较从容的对不同URL生成不同的页面。同时在配合一个ORM,这样就能写很多web应用了。大体上,都是使用MVC这个总的模式。
web框架是不是要做好这几件事情:
也就说其实理论就那么一点,只是很繁琐,框架的目的就是为了提高开发效率,但是如果要使用好框架,首先非常清楚工作原理,最好能够理解框架实现细节。
Flask是个很轻便的框架,如果网站简单,就用一个app.py就把所有业务逻辑都写完了。但是如果业务逻辑多,需要多人开发,写一个文件,就很头疼。
所以Flask用蓝图(Blueprint)的概念,使得代码可以进行拆分。Blueprint的工作方式和Flask对象很接近,但两者又不一样。
1 | from flask import Blueprint, render_template, abort |
这是官网上的一个例子。其实就是创建一个Blueprint对象,指定路由,绑定视图函数。这样就可以把相关的业务逻辑,页面模板,静态文件组成一个package。通过这种方式把一个工程,拆解成不用的模块,减少耦合,并行开发。
Blueprint需要在Flask对象中注册,这样才能确定最终的URL。
1 | from flask import Flask |
通过url_prefix就可以确定蓝图的url路由。同时蓝图还能提供子域名的方式进行路由。使用subdomain='www.lutieg.com’参数可以实现指定动态子域名的方式。还可以通过传入参数的方式指定URL
参考链接:
1, http://dormousehole.readthedocs.io/en/latest/blueprints.html
2, https://spacewander.github.io/explore-flask-zh/7-blueprints.html