0%

Node.js是干啥的

之前一直以为Node就是一个可以在shell里面执行js的工具,现在看好像也不止于此。我现在的理解就是这个Node是一种新的网络Server开发方式,尤其是为了Web开发而设立的。其特点就是事件驱动,单进程非阻塞IO方式。这样据说可以在IO密集的场景,大幅提高应用的性能。

Read more »

想在自己的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
2
3
4
5
6
7
8
9
10
new Valine({
el: '#comment' ,
notify:false,
verify:false,
appId: '这里填上面获得的appid',
appKey: '这里填上面获得的appkey',
placeholder: 'just go go',
path:window.location.pathname,
avatar:'mm'
});

就是把这样一短代码加入到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到底在干什么。我想尝试整理一下,看看都学到了哪些,所以总结如下:

1, Forward过程

训练word embedding的网络结构是这样的,他的隐藏层后面并没有任何非线性函数。

wordvec

为了说明计算过程,模型简化为1对1 的预测,类似bigram。

Input -> Hidden

模型的输入到hidden的计算是

$$h=W^{T}X = W_{(k,.)}^{T}$$

X是one-hot向量,是V*1的列向量,W是V*N的矩阵,就是word embed,每行代表词表中的一个词。h是N*1的列向量。

Hidden -> Output

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})} $$

Loss Func

有了预测概率,有了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》给出的推导过程特别详细,我才能勉强看懂。

2, Backward过程

  1. $$h=W^{T}X = W_{(k,.)}^{T}$$
  2. $$ u= W^{'T}h$$
  3. $$ y_{i} = \frac{exp(u_{i})}{\sum_{k=1}^{V} exp(u_{k})} $$
  4. $$ E = -u_{j^{*}} + \log \sum_{j{'}=1}{V} exp(u_{j^{'}}) $$

就是这几个公式依次求导。可是反向求导为啥难理解呢,我觉得主要是因为,前向过程都是用矩阵或向量计算的,求导时需要很多变换,还需要考虑转置的问题,行列的问题,转换步骤一多,思维就乱掉了。

首先求关于$$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个节点的分类情况

第一个图是逻辑回归对复杂数据分布的分类情况,第二个图是神经网络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
2
3
x = 'wangliang'
y = tuple(x) #这样可以得到字符级的tuple
z = set(x) #这样可以得到一个字符集合

2,zip函数可以把两个序列,按位置先后,两两组成一个tuple pair,很方便形成dict

1
2
3
4
x = 'wangliang'
y = dict(zip(x, range(10))) #y 就是一个 {ch, idx}的dict
s = list(zip(x, range(10))) #s 是一个(ch, idx)的list
a,b = zip(*s) #通过这样的方式,还能把含有tuple的list拆成两个tuple

3,sorted函数可以对序列类型进行排序

4,dict按值排序

1
2
3
4
#https://stackoverflow.com/questions/613183/how-do-i-sort-a-dictionary-by-value
import operator
x = {1: 2, 3: 4, 4: 3, 2: 1, 0: 0}
sorted_x = sorted(x.items(), key=operator.itemgetter(1))

这些方便的数据处理方式,可以应用到机器学习的数据预处理上。机器学习模型部分实现代码量不大,而且有很多框架可以直接调用。但是数据预处理部分就要自己来了。

提到ML,很郁闷。最近一直在看NLP,但是发现看论文好难找到通透的感觉,而且数学基础不够,也导致问题多多。这个会不会又是一个大坑呢。反正,在不为钱发愁的情况,尽量做自己能做且喜欢的事情吧。

Logistic Regression as a Neural Network

  1. 二元分类:比如判断一张图片是猫
  2. 逻辑回归:一个输入向量和一个参数向量做点积,得到的结果再用sigmoid函数求出一个值,得到的就是二元分类的概率

$$ \hat{y}=\sigma(w^{T}X+ b), \quad where\ \sigma(z)=\frac{1}{1+e^{-z}} $$

  1. Lost Function:

$$ 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)} $$

  1. 梯度下降:根据LostFunc,给出dw,dx,db 的计算式,得到值。每个变量,沿梯度方向变化一点,整个loss就会变化,不停的反复迭代,就会找到最佳的参数。

    我一直有个错误的认识,就是dw, db和Loss值有关,dw,db需要使用Loss值进行计算,其实不是,dw只和LostFunc的表达式有关,只和其他参数当前值有关,有没有Loss值都不重要,只是每次迭代之后,需要看看Loss是不是在减少了,而不需要通过Loss计算梯度。

简单的路由绑定就像这样

1
2
3
@app.route('/')
def index():
pass

在Flask内部可以这样做

1
2
3
def index():
pass
app.add_url_rule('/', 'index', index)

在add_url_rule函数中核心代码主要是这几行

1
2
3
4
5
6
rule = self.url_rule_class(rule, methods=methods, **options)

self.url_map.add(rule)
if view_func is not None:
...
self.view_functions[endpoint] = view_func

其中url_rule_class和url_map都是利用的werkzeug.routing的代码,核心的类就是Rule, Map, MapAdapter,代码看的我头疼,一方面现在智力下滑严重,很多看不懂,另外一方面,感觉怎么这么麻烦不就是简单的从url到具体函数的匹配么?可是深入看的话,发现人家的功能确实强大,比如可以进行变量转换,还能生成url。

最简单的Flask web程序就是这么几行

1
2
3
4
5
6
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
return 'Hello, World!'

其实这里是创建了一个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对应用的要求就是要有一个接口能接受environstart_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框架是不是要做好这几件事情:

  1. HTTP消息接受和发送
  2. URL的路由,根据url能够调用对应页面函数
  3. 模板引擎,能够高效,简介的动态生成页面
  4. ORM,能够方便的访问数据库
  5. 其他关于cookie, session, 表单验证, 都属于额外的附加功能。

也就说其实理论就那么一点,只是很繁琐,框架的目的就是为了提高开发效率,但是如果要使用好框架,首先非常清楚工作原理,最好能够理解框架实现细节。

Flask是个很轻便的框架,如果网站简单,就用一个app.py就把所有业务逻辑都写完了。但是如果业务逻辑多,需要多人开发,写一个文件,就很头疼。

所以Flask用蓝图(Blueprint)的概念,使得代码可以进行拆分。Blueprint的工作方式和Flask对象很接近,但两者又不一样。

创建Blueprint

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound

simple_page = Blueprint('simple_page', __name__,
template_folder='templates')

@simple_page.route('/', defaults={'page': 'index'})
@simple_page.route('/<page>')
def show(page):
try:
return render_template('pages/%s.html' % page)
except TemplateNotFound:
abort(404)

这是官网上的一个例子。其实就是创建一个Blueprint对象,指定路由,绑定视图函数。这样就可以把相关的业务逻辑,页面模板,静态文件组成一个package。通过这种方式把一个工程,拆解成不用的模块,减少耦合,并行开发。

注册Blueprint

Blueprint需要在Flask对象中注册,这样才能确定最终的URL。

1
2
3
4
5
from flask import Flask
from yourapplication.simple_page import simple_page

app = Flask(__name__)
app.register_blueprint(simple_page, url_prefix='/page')

通过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

3, https://www.zhihu.com/question/31748237