机器学习 – 第三周:逻辑回归

分类(Classification)问题

逻辑回归(Logistic Regression)主要是用于分类问题的。分类问题,即对数据集中的数据进行分类,区分出两个或多个类别。

例如,之前提到的邮箱判定是否为垃圾邮件的算法,即一类分类算法:区分一封邮件是垃圾邮件,或是非垃圾邮件。

处理这种问题,其实和线性回归的问题如出一辙:只需判定其属于某个分类的概率——例如某封邮件经过算法计算出为垃圾邮件的概率为 0.9 ,我们则有充分的理由认为这是一封垃圾邮件。

模型表达

继续用 $$h_\theta(x)$$ 来表示我们的假设函数。由于我们计算出的是分类的概率,则由概率论(常识),应有 $$0 \le {{\rm{h}}_\theta }(x) \le 1$$ 。但线性回归函数计算出的 $$\theta^T$$ 并不是在此范围内,我们需要选择一个函数 $$g(z)$$ ,使得 $$ 0 \le h_\theta(x) = g(\theta^T) \le 1 $$ 。

我们取下列的 $$g(z)$$ :

$$g(z) = {1 \over {1 + {e^{ – z}}}}$$

则我们的假设函数 $$h(\theta)$$ 为:

$${h_\theta }(x) = {1 \over {1 + {e^{ – {\theta ^T}x}}}}$$

函数$$g(z)$$单调递增,且$$g(0)=0.5$$、$$0 \le g(z) \le 1$$,函数图形类似积分符号“$$\int$$”,比较适合作为一个分类的假设函数。

此时 $$h_\theta(x)$$ 表示的则是“输入x时,y=1的概率”。用概率论中的条件概率表示,则是$$P(y = 1|x;\theta )$$。

决策边界(Decision boundary)

(啊突然发现真的有点不得不配图了……我还要再坚持一下!)

考虑只有两个参数:$$x_1$$、$$x_2$$的情况。将不同分类的数据集以不同符号标注在平面直角坐标系上;再将预测函数 $$h_\theta$$ 亦画出在同一个坐标系中。此时,预测函数 $$h_\theta$$ 将不同的数据集分开——这就是决策边界。

决策边境亦可以是非线性的——只需在预测函数中加入高次项。

成本函数

$$J(\theta ) = – {1 \over m}[\sum\limits_{i = 1}^m {{y^{(i)}}\log {h_\theta }({x^{(i)}}) + (1 – {y^{(i)}})\log (1 – {h_\theta }({x^{(i)}}))]} $$

总之大概就是以上这坨玩意。

当然,要用梯度下降法求它的最小值,我们需要知道它的偏导数……如下:

$${\partial \over {\partial {\theta _j}}}J(\theta ) = {1 \over m}\sum\limits_{i = 1}^m {({h_\theta }({x^{(i)}}) - {y^{(i)}})x_j^{(i)}}$$

高级优化算法(Advanced optimization)

还有一些比梯度下降法牛逼得多的算法,例如:

  • Conjugate gradient
  • BFGS
  • L-BFGS

这些算法在 MATLAB 或者 octave 里都有,我们要做的只是传入成本函数和偏导数,然后可以由内置的算法自动计算出最合适的 $$\theta$$ 值。

多类别的逻辑回归算法

在目标数据集有多个分类的情况,我们使用一种“一对多”(one vs all)的方式来计算。原理很简单,例如我们有$$n$$个分类,我们依次挑选其中的1个分类,将剩下的$$n-1$$个分类作为另一个分类来运行逻辑回归算法。

机器学习 – 第二周:多变量线性回归

好吧……我成功的把 coursera 的 Machine Learning 课完完全全的坑掉了…… Courera 都发信告诉我你快去 check 你的 Review questions 啦否则要不及格了!我才发现我已经完完全全的跟不上进度了哭瞎…… 估计我要跟着 ml-007 班才可能上完这门课了 QAQ 留级什么的……

于是这是多变量线性回归的总结。

多变量线性回归,其实亦是线性回归:其区别主要在于,参与回归的变量变多了,也就是要用多个因素来共同预测同一个结果。

模型表达

$${{\rm{h}}_\theta }(x) = {\theta _0} + {\theta _1}{x_1} + {\theta _2}{x_2} + … + {\theta _n}{x_n}$$

——其实有 $$x_0$$ 的,只是它等于1,于是就没有 $$x_0$$ 了。

当然它可以用简洁大气的向量形式表示,如下:

$${{\rm{h}}_\theta }(x) = {\theta ^T}x$$

多变量的梯度下降算法

成本函数

$$J(\theta ) = {1 \over {2m}}\sum\limits_{i = 1}^m {{{({h_\theta }({x^{(i)}}) – {y^{(i)}})}^2}} $$

成本函数和单变量的并无本质区别,唯需注意:其中的 $$\theta$$ 和 $$x$$ 已经是向量。

梯度下降

梯度下降也并无区别;唯将同步更新的 $$\theta_0$$ 和 $$\theta_1$$ 变成了 $$\theta_0$$ 到 $$\theta_j$$ 而已。

特征缩放(Feature Scaling)

Google 上随便搜了下,没找着合适的中文翻译,暂且叫他特征缩放吧。其主要目的,则是让各个特征——即参与预测的参数,保持在同一个数量级上,以减少迭代的次数。

参与预测的各特征由于种种原因,其大小并不在同一个数量级上;也就是说,特征的单位往往不统一。而这会造成迭代上的问题——有的次要特征无法快速减小,以至于消耗大量的时间;而主要特征有可能无法快速上升从而达到最佳解。

而特征缩放,将所有的特征都限定在 $$ – 1 \le x \le 1$$ 的范围内,使特征都保持在同一个数量级。这样一来,梯度下降算法将会提升其效率,运行得更加快速干净。

一般我们采用如下算法:记特征的平均值为 $$\mu $$ ,而特征的范围(最大值减去最小值)为 $$s$$ ,则将以下的变量代替特征 $$x$$ 参与迭代:

$${{x – \mu } \over s}$$

学习速率(Learning rate, $$\alpha$$)

提到学习速率之前,需要先了解我们是如何处理梯度下降算法中的问题的——即,我们如何知道梯度下降算法是否正在朝我们想要的结果发展?

最简单明了的方式,就是画出迭代次数-成本函数的直角坐标系图。在每次迭代时,该图线持续下降——亦即,Jmin(t)是一个单调递减的函数。

那学习速率 $$\alpha$$ 又会以怎样的方式影响梯度下降算法呢?回到之前我们的梯度下降算法公式:

$$\theta_{j}:=\theta_{j}-\alpha\frac{\partial}{\partial\theta_{j}}J(\theta_{0},\theta_{1})$$

观察到,每次迭代都将下降$${\partial \over {\partial {\theta _j}}}J({\theta _0},{\theta _1})$$的$$\alpha$$倍。

显然,若是$$\alpha$$取值太小,则每次下降的步幅太小,则效率太低;若$$\alpha$$的取值太大,则将错过最低值而走向离最低值越来越远的道路——穿过最低值后,其导数值可能比上一次迭代更高,于是一次比一次跳跃得更远,也就无法取到最小值了。

选择学习速率$$\alpha$$的方法是凭经验:依次取0.001、0.003、0.01……1、3、10、……如此尝试。在尝试的过程中,可以首先将迭代次数降低;然后试着画出 Jmin-t 的图线;观察图线,然后根据图线调整 $$\alpha$$ 的值。

线性回归中的多次式

之前我们提到的线性回归预测,多是一次项比较多;但某些特征,对结果的影响可能不是一次的——可能是二次、三次甚至更甚。于是,可以用 $$x^2_i$$ 、 $$x^3_i$$ 来代替 $$x_i$$ 参与线性回归。

一般等式

可用下列等式直接求出线性回归时我们所求的 $$\theta$$ 值:

$$\theta = {({X^T}X)^{ – 1}}{X^T}{\rm{y}}$$

适用于样本数量 n 比较小的情况——因为 n 大了会比较慢。

当 $${X^T}X$$ 没有逆矩阵时,可以用 octave 的 pinv 方法求得伪逆——主要是特征太多的情况下会导致这种情况的发生。

根据天气和时间动态更换的博客背景图

之前博客的背景图是一个永恒的晴天……
因为一开始就是当成一个可以根据时间和天气轮换的设计,但是懒得去写 js ,于是就是如大家所见的那样。

刚刚闲得无聊,把整个逻辑补完了。用到的是新浪的天气 js 接口,然后用一组图片搞定了他们。

核心代码如下:

var t = Math.floor(((new Date()).getHours() + 6) / 5),
    f = 'bright' + t;
if (w.indexOf('雨')>=0 || w.indexOf('雪') >= 0) {
    f = 'rainy';
} else if (w.indexOf('云')>=0 || w.indexOf('阴') >= 0) {
    f = 'cloudy' + t;
}
if (document.body.clientWidth > 600) {
    document.body.style.backgroundImage = 'url("' + f + '.jpg")';
}

具体的效果,可以参考本博客背景,也可以移步演示页面

相应的几张背景图为原创,谢绝扒图。如需自用,请联系我。

机器学习 – 第一周:单变量线性回归

模型表达

单变量线性回归是一种最简单、最典型的监督式学习算法。我们将用于训练的数据称为“数据集”(dataset),则有以下几个定义:

  • $$m$$ 代表训练数据集的大小(即有多少个训练样本);
  • $$x$$ 代表模型的输入(即模型的输入特征数量);
  • $$y$$ 代表模型的输出(即最终预测的结果);
  • 我们用上标 $${^{(i)}}$$代表数据集中的第 i 组数据,如 $${x^{(i)}}$$ 代表数据集中第二个输出;
  • $$h$$ 则是我们用于预测的函数,具体表述如下:
    将学习算法通过训练数据集训练之后得出函数 $$h$$ 中的不确定参数项,此时 $$h$$ 为一确定的函数;
    将任意的输入 $$x$$ 代入 $$h$$ 中,即可得到对应的输出 $$y$$。
    在单变量线性回归中,我们使用的 $${h_\theta}(x)$$$${h_\theta }(x) = {\theta _0} + {\theta _1}x$$

成本函数

线性回归要达到的目标,即将上述 $${h_\theta}(x)$$ 函数中的 $$\theta$$ 求出。如何选择 $$\theta$$ 们就成了要解决的问题。

给出成本函数 $$J({\theta _0},{\theta _1}) = {1 \over {2m}}\sum\limits_{i = 1}^m {({h_\theta }({x^{(i)}}) - {y^{(i)}})} $$
此函数是线性预测函数 $$h$$ 与实际数据 $$y$$ 的误差的方差值。

则我们的问题就转化为了,如何求成本函数 $$J({\theta _0},{\theta _1})$$ 的最小值。

梯度下降法

选用一种算法,使 $$\theta$$ 从任意位置开始,都能下降到一个局部最小值。

梯度下降法的具体做法是,每次迭代都将 $$\theta$$ 减少一个值,这个值是学习速率 $$\alpha$$ 乘以成本函数 $$J$$ 对 $$\theta_i$$ 的偏导数。具体公式如下:

repeat until convergence {
$$\theta_{j}:=\theta_{j}-\alpha\frac{\partial}{\partial\theta_{j}}J(\theta_{0},\theta_{1})$$ (for j=0 and j=1)
}

** 值得注意的是,此处的 $$\theta$$ 的更新是同步的,亦即计算$$\theta_2$$时使用的$$\theta_1$$是本次更新前的值。**

由于每次更新变量 $$\theta$$ 都是减去一个和梯度线性相关的值,故此算法将会在迭代的过程中自行调整每次下降的幅度,
从而减小迭代次数。如当初始值很高,函数图像很陡峭时,此时梯度足够大,故下降的速率也足够大,
从而在单次的迭代中下降足够多的幅度。
而当 $$\theta$$ 已经位于最小值附近时,由于函数图像的缓和,梯度下降,故下降速率会自行变小。
这就是梯度下降算法的优越之处。

在单变量线性回归算法中,梯度下降算法中的偏导数部分可以求解如下:

$$${\theta _0}: = {\theta _0} - \alpha {1 \over m}\sum\limits_{i = 1}^m {({h_\theta }({x^{(i)}}) - {y^{(i)}})} $$$
$$${\theta _1}: = {\theta _1} - \alpha {1 \over m}\sum\limits_{i = 1}^m {({h_\theta }({x^{(i)}}) - {y^{(i)}})} \cdot {x^{(i)}}$$$

机器学习 – 简介

最近在 Coursera 上参加 Andrew Ng 的 Machine Learning 课程,感觉颇为良好, AN 的课讲得很是生动,也比较深入浅出,适合我这种低智商人群学习。

于是,这里是对其中的“Introduction”亦即“介绍”部分,做一个中文版的提纲,以便自己查阅。接下来的学习时间里,我也会整理出相应的提纲资料,一方面是为了自己查询方便(读中文还是比英文舒坦),另一方面也算是给后来者留下一个参考。想要阅读相关的其它文章的话,请点击 ml-006 这个标签。

注意,以下内容均为本人理解(除引用外),本人不对以下信息的准确性负任何责任。转载请参考右边 CC 协议。

定义

A computer program is said to learn from experience E with respect to some task T and some performance measure P, if its performance on T, as measured by P, improves with experience E.
—- Tom Mitchell (1998)

Google 找了一番,没有找到确切的翻译,我就随便翻译一下吧。

所谓机器学习,即能够完成任务 T 的程序,其自行从 E 中学习经验,用 P 来衡量其表现;且在 T 任务上的表现 P 能够由经验 E 来提升。

举个例子:

比如我们平常所用的 GMail 的 SPAM 判定,就是一个典型的机器学习程序:
任务T:判断一封邮件是否为 SPAM
经验E:用户对 SPAM 邮件的标记(“这是垃圾邮件”)
表现P:对垃圾邮件的识别率

模型形态

广为人知的,机器学习分为两种形态,即监督式学习和非监督式学习。两种模型形态的区别主要在于训练数据集。

监督式学习(Supervised learning)

Supervised learning is the machine learning task of inferring a function from labeled training data (via wikipeia).

监督式学习的数据集中的每个点都是“right answers”,即所有的测试数据都是已知的且有着正确的标记。例如预测房价的学习算法,其每个点都是明确已知的:500 ㎡ 的房子卖 $ 100,000 美元。

而监督式学习主要用于回归分析(Regression,即输出是连续的)和数据分类(Classification,输出是离散的)。

非监督式学习(Unsupervised learning)

In machine learning, the problem of unsupervised learning is that of trying to find hidden structure in unlabeled data (via wikipedia).

相对的,非监督式学习即一组没有经过标记的数据——我们只知道这些数据在那里,并不知道这些数据之间的联系、这些数据是什么。例如 Google News 的“相关新闻”聚类:我们只有海量的新闻数据,而并不知道其意味着什么。

非监督式学习主要应用于聚类分析(Cluster analysis)。

gulp.js – stream 驱动的自动构建工具

最近心情不太好,开始有点自暴自弃。所幸能够写点代码分分心……

开始接触一些前端的项目,就尝试着用一些看起来很 cool 的技术/工具。初步选定的工具包括 jade 、 stylus 和 coffee 。

可是做预编译实在是一个很麻烦的事情,直到我遇见了 gulp.js

gulp.js 是一个简洁明快的自动构建工具,官方文档也异常简洁清晰。主打“stream”式构建,配置文件写起来也是不太复杂。这里针对自己用到的一些插件做简单的介绍。

gulp-concat 是一个将多个文件合并到一起的插件。

gulp-clean 用来清理目标目录中的文件。

gulp-stylusgulp-coffeegulp-jade用来预编译我的代码。

想要达到的效果是:

  1. 将一些静态文件复制到发布目录;
  2. 将框架的 css 与自己的 stylus 编译成的 css 合并;
  3. 将框架的 js 与自己的 coffee 合并;
  4. 将 jade 编译为 html 文件。

目录结构如下:

public/ 存放发布文件
src/ 存放源文件

实现思路:

清理发布目录

var clean = require('gulp-clean');
gulp.task('clean', function () {
    return gulp.src('public').pipe(clean());
});

几乎是最简单的任务。将发布目录整个的删掉。

gulp.src用于读取源文件,这里读取的是public目录;然后将其 pipe 到 gulp-clean 插件,将其清理掉。

处理静态文件

gulp.task('static', function () {
    gulp.src('src/img/**')
        .pipe(gulp.dest('public/img/'));
    gulp.src('src/fonts/**')
        .pipe(gulp.dest('public/fonts/'));
});

和上面的范例类似,只不过用的是gulp.dest,用于将 pipe 过来的文件发布到 public 目录。

注意这里的路径是 src/img/** ,表示对 img 目录下的文件进行递归处理。

构建 jade

var jade = require('gulp-jade');
gulp.task('html', function () {
    return gulp.src(['src/jade/**/*.jade', '!src/jade/partial/**'])
        .pipe(jade())
        .pipe(gulp.dest('public/'));
});

这里只是换用了gulp-jade插件而已。

需要注意的是,这次的路径传入进来的是个数组;数组会如同你想象的那样,将数组中的元素挨个匹配;!开头的表示去除src/jade/partial目录下的所有 *.jade 文件;**/*.jade则表示该目录下递归中所有的 jade 文件。

这里将 gulp 链 return 回去,是为了在处理依赖的时候,能够正确确定函数处理进度,以保证运行顺序。

合并 js 和 css

var concat = require('gulp-concat');
//合并 css
gulp.task('css', function () {
    return gulp.src('src/css/*.css')
        .pipe(concat('0.css'))
        .pipe(gulp.dest('.tmp/css/'));
});
//处理 js
gulp.task('js', function () {
    //加载jquery、modernizr、foundation、vue
    return gulp.src([
        'src/js/vendor/jquery.js',
        'src/js/vendor/modernizr.js',
        'src/js/foundation.min.js',
        'src/js/vendor/vue.min.js'
    ])
        .pipe(concat('0.js'))
        .pipe(gulp.dest('.tmp/js/'));
});

这里用到的是gulp-concat插件。它能把多个文件合并成为同一个。

这里将输出送到了 .tmp 目录下,是为了后续能够处理它。

编译 stylus 和 coffee

var stylus = require('gulp-stylus');
var coffee = require('gulp-coffee');
//stylus 编译
gulp.task('stylus', function () {
    return gulp.src('src/styl/**/*.styl')
        .pipe(stylus())
        .pipe(concat('1.css'))
        .pipe(gulp.dest('.tmp/css/'));
})
//处理 coffee
gulp.task('coffee', function () {
    return gulp.src('src/coffee/**/*.coffee')
        .pipe(coffee({
            bare: true
        }).on('error', gutil.log))
        .pipe(concat('1.js'))
        .pipe(gulp.dest('.tmp/js'));
});

这里依然将输出送到了 .tmp 下。接下来要将 .tmp 下的文件合并为真正能够使用的 css 和 js 文件。

合并临时 css 与 js 文件

gulp.task('concat-css', ['css', 'stylus'], function () {
    return gulp.src('.tmp/css/*')
        .pipe(concat('common.css'))
        .pipe(gulp.dest('public/css'));
});
gulp.task('concat-js', ['js', 'coffee'], function () {
    return gulp.src('.tmp/js/*')
        .pipe(concat('common.js'))
        .pipe(gulp.dest('public/js'));
});

这里用到的是 deps 也就是“依赖”语法。task 的第二个参数是一个数组。在执行这个任务时,会自动执行数组中的任务;并且,当且仅当数组中所有任务执行完毕后,才会执行后面的 function 中的任务。

这里的应用就是,在先合并好了框架的 css 和自己写的 stylus 编译完成之后,再将临时文件中的两组文件合并起来。

清理临时文件

gulp.task('concat-and-clean', ['concat-css', 'concat-js'], function () {
    return gulp.src('.tmp').pipe(clean());
});

这里则是用到了刚刚提到的 gulp=clean 插件和 deps 语法。

默认动作

gulp.task('default', ['concat-and-clean', 'html', 'static']);

在命令行下,执行 gulp 时,则会自动执行 default 任务。这里定义的 default 任务没有事实上的动作;它只是同时执行上面所述的几个任务,然后自己退出。

监视改动

gulp.task('watch', ['default'], function () {
    //js
    gulp.watch('src/coffee/**', ['concat-js']);
    //css
    gulp.watch('src/styl/**', ['concat-css']);
    //html
    gulp.watch('src/jade/**', ['html']);
    //静态
    gulp.watch('src/img/**', ['static']);
})

gulp.watch 能够监视指定文件的改动,并启动任务;也就是说,当src/coffee/中的文件改动时,gulp 会自动执行 concat-js 任务。

然后我们只需要在命令行中输入 gulp watch ,gulp 将会自动跑一轮 default 任务,然后监视我们的源文件;当发现文件变动时,将自动执行编译。

至此,我的 gulpfile.js 就编写完毕了。Enjoy it!

CentOS 6 + Apache 环境下,PHP 5.2 与 5.4 共存

别问我为什么还要用 5.2 ,我会告诉你是因为 ZendOPT 么。

言归正传。在 Linux 下,要装两个不同版本的 php 可不是下载两个 exe 那么简单。这里采用的方法,是手工编译 php 5.2 并使用 epel 源中的 php 5.4 。前者采用 fastcgi 模式运行,后者采用传统的 mod_php 来运行。

现在我们假设,环境中已经装好了 Apache 2.2 + MySQL 5.5 + PHP 5.4 ,并且希望加装一个 PHP 5.2.17 ,并且能够直接使用 MySQL 5.5。

首先需要安装 epel 。

rpm -ivh http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm

然后安装依赖:

yum install httpd-devel libxml-delvel openssl-devel bzip2-devel libcurl-devel libjpeg-devel libpng-devel freetype-devel libmcrypt-devel libmhash-devel libtool-ltdl-devel

然后需要自己编译安装 libiconv,如下。

wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz
tar -xvf libiconv-1.14.tar.gz
cd libiconv-1.14
./configure
make
make install

然后编译安装 PHP ,记得打开 cgi 选项。PHP 源码包请到官网下载。

./configure --prefix=/usr/local/php52 --with-config-file-path=/usr/local/php52/etc --with-config-file-scan-dir=/usr/local/php52/etc/php.d --with-openssl --with-zlib --with-bz2 --with-gettext --with-mhash --with-mcrypt --with-iconv=/usr/local/libiconv --with-curl --with-gd --with-jpeg-dir --with-png-dir --with-freetype-dir --with-mysql=/usr/bin/mysql --with-pdo-mysql=/usr/bin/mysql --enable-gd-native-ttf --enable-bcmath --enable-mbstring --enable-zip --enable-soap --enable-sockets --enable-ftp --without-pear --enable-fastcgi --enable-force-cgi-redirect
make
make install

这样 PHP 5.2.17 就被安装到了 /usr/local/php52 里了。

/usr/loca/php52/bin/php -v

然后配置 fastcgi 。 找到 ~/public_html/cgi-bin/ 目录,创建 php52.fcgi 文件如下:

#!/bin/sh
export PHP_FCGI_CHILDREN=4
export PHP_FCGI_MAX_REQUESTS=200
export PHPRC="/home/oott123/conf/php.ini"
exec /usr/local/php52/bin/php-cgi

其中 PHPRC 为用户定义的 php.ini 文件。再创建 .htaccess 文件(此处需要 AllowOverrideAll )如下:

<FilesMatch \.php$>
    SetHandler application/x-httpd-php-52
</FilesMatch>
Action application/x-httpd-php-52 /cgi-bin/php52.fcgi

如果需要再加入 php 5.4 的支持,再添加如下代码:

<FilesMatch \.php54$>
    SetHandler application/x-httpd-php
</FilesMatch>

至此已经完成了 PHP 多版本的配置。

Goodbye , LastPass . Hello , KeePass !

一直以来我都高度依赖 LastPass ,由于自己的分级密码策略,我实在是记不住哪些网站用了哪些密码。好在有了 LastPass ,让我的生命中少了一大堆烦恼。

随着审美观的提高,我不断发现 LastPass 的致命软肋。如,密码数据库保存在服务器上;又如,连接经常不够稳定;再如,保存密码流程真真是二逼兮兮的。直到我发现了 KeePass ,这一切都不再烦恼。

KeePass 是一个优秀的开源密码管理方案。由于其并不把数据存放在服务器上,因此一切关于网站会不会一直存活下去的担心皆可避免;另外由于其开源的特性,让你的选择不只有一种——光是 iOS 客户端就有长长的一串。

这里我选用的是官方的 KeePass 2.25 。在 KeePass 里很容易把 LastPass 的数据导入进来,只需点击文件->导入->LastPass CSV,即可导入 LastPass 导出的 csv 文件了。

KeePass丰富的导入功能

在这张图里也可以看到,KeePass 能够导入各种各样的格式。

导入之后,就可以开始使用 KeePass 了。这里顺便介绍一些实用的插件。

首先是配合 FireFox 使用的 KeeFox 。在 Mozilla 扩展中心下载 KeeFox 之后,关闭 KeePass ,根据提示选择安装路径,即可安装 KeePass 插件。这时启动 KeePass ,右键点击你存放密码的目录,选择 Set as KeeFox start group ,就算配置完成了。之后直接在 Firefox 中就能享受到 KeePass 的贴心了。

然后是 KeeOtp 。这款插件支持将 Google 二次验证导入 KeePass 中,这样就不需要去找手机了,同时手机丢失了也不怕, KeePass 里还有备份。插件的用法很简单,右键点击条目选择 Timed One Time Password ,复制粘贴网页中的密钥,就能成功设置了。

另外给出几个小提示。

其一是自动登录 KeePass 的方法。 KeePass 确实够好用,可是问题是每次都输入长长的密码实在是很不环保啊!KeePass 提供了-pw:123456这样的命令行参数,但是把密码写到命令行里恐怕和没有密码没区别。解决的方法,是使用内置的{PASSWORD_ENC}功能和-pw-enc命令行。根据官方说明,{PASSWORD_ENC}是一种只限当前用户使用的加密功能,也只有 KeePass 能够解密,所以可以安心使用。

具体的方法是,创建一个条目,用户名随意,密码写成你的 KeePass 的密码,在编辑->自动输入中选择“替代默认规则”并写入规则:{PASSWORD_ENC}确定保存。然后在 KeePass 目录下新建一个文件,叫 start.bat ,用记事本打开,输入:start KeePass.exe "(KeePass数据库目录)" -pw-enc:。此时将光标放到最后,切换到 KeePass 里,右键刚刚的条目,选择“自动输入”。然后……你就可以看到你的记事本里的字符在疯狂的跳动,大概十多秒的样子输入了一大堆 Base64 编码的玩意。现在,可以保存关闭了,下次双击 start.bat 就可以自动登录了。至于安全性嘛……总之不能和便利性同在。

KeePass密码加密

其二是自动保存的触发器。点击“工具->触发器”,左下角的“工具”中粘贴下面的脚本即可:

<?xml version="1.0" encoding="utf-8"?>
<TriggerCollection xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Triggers>
        <Trigger>
            <Guid>EXkzOtjQEEqLvtvmzbDD+Q==</Guid>
            <Name>自动存盘</Name>
            <Comments>via http://best33.com</Comments>
            <Events>
                <Event>
                    <TypeGuid>jRLUmvLLT/eo78/arGJomQ==</TypeGuid>
                    <Parameters />
                </Event>
            </Events>
            <Conditions>
                <Condition>
                    <TypeGuid>08r67ygqRkqZkNhl/OAW7Q==</TypeGuid>
                    <Parameters />
                    <Negate>false</Negate>
                </Condition>
            </Conditions>
            <Actions>
                <Action>
                    <TypeGuid>9VdhS/hMQV2pE3o5zRDwvQ==</TypeGuid>
                    <Parameters />
                </Action>
            </Actions>
        </Trigger>
    </Triggers>
</TriggerCollection>

其三是自动同步。这个嘛比较简单,数据文件放到 OneDrive/DropBox/Google Drive 里就好了。为什么是这三个网盘呢?因为 KeePass2Android 只支持这三个网盘……

当然了,你有自己的 FTP/WebDav 服务器啥的也是可以直接用 KeePass 打开的,只不过存盘太慢了……所以还是用同步盘吧XD。

一种可能是高大上的防止SPAM的方法

前一阵子比特币火的一塌糊涂,然后猛然惊觉其实自己在几年前就买过这种东西,后来换电脑了钱包啥的都没有保留下来,扼腕叹息觉得我差一点点就是富豪了。事实证明我想多了,因为仔细一想才发现自己的持有量还不到1个单位,才多少钱啊,离富豪的距离还有几千倍呢。不过当年不明白比特币的原理,也想不了那么多,这个钱没这个福气赚,也是没办法的事情。

最近又去看了看比特币的实现原理,然后在想,如果说把类似的原理用在其它地方会发生什么。于是就有了这个防 SPAM 的思路。不过也不知道该用什么关键字去检索,也不知道自己是原创还是在重复造轮子,总之是写了一个 typecho 的插件叫 SpammersGoAway ,已经在自己博客上部署了,并且把其它的防垃圾策略都撤了,看看有什么后果。

Update : 由于对某些手机浏览器支持不能,暂时关闭了本站的 SpammersGoAway .

这个实现原理在 README.md 里已经详细的叙述了,这里就复制粘贴一下好了。

对于 Spammer 来说,效率是摆在第一位的。他们需要用极短的时间来发送大量的 SPAM ,这就是 Spammer和普通的用户最显著的区别。

利用这一点,我们可以使每位评论者都花上一点点的时间来计算一个特殊的 hash值,比如hash(time+i),然后循环使i自增,最后要求计算出来的 hash 满足一个特殊的条件如前3位相同,这样对于一个Spammer 来说,他发送一个 SPAM 所使用的资源提高了:需要更多的 CPU 和时间。而对于普通用户来说,浏览器计算这个 hash值的时间在 3 秒内,还不够用户输入一条完整的评论,对于用户来说几乎无影响。

在Linux下使用ftp.proxy搭建FTP反向代理/透明代理

由于某些原因,自己的FTP服务器访问起来相当不顺畅。好在手上有速度稍快的资源可以拿来当反代,Google了一大堆,最后终于找到了一个合适的项目,叫 ftp.proxy 。ftp.proxy 是一个提供 FTP 代理的程序,支持透明代理,也支持标准化的 FTP 代理。这里我们把它配置为透明(反向)代理来使用。

首先,到 ftp.proxy 的下载页面 查看最新的 ftp.proxy ,在服务器上下载它。写这篇文章的时候,最新的 ftp.proxy 是1.2.3。由于这个项目已经快10年没有更新了(The current stable version of ftp.proxy is 1.2.3 Released 30. July 2004),为了防止官方出现某些意外的错误,我把它放了一份到了百度网盘上,你可以考虑下载它。

wget http://www.ftpproxy.org/download/ftpproxy-current.tgz

然后就是编译安装。如同官网所说,这是一个“old story”。

tar -xvf ftpproxy-1.2.3.tgz #文件名请自行修改
cd ftpproxy-1.2.3/
make
mkdir /usr/local/man #如果出错是没关系的
sudo make install

安装的时候遇到了一个错误,是因为自己的 CentOS 并没有/usr/local/man/这个目录。修正的方法已经写在上面了。

然后就可以开始使用ftp.proxy了。如下:

ftp.proxy -D 12580 192.168.1.1:10086

其中12580是你要在反代服务器上监听的端口,而10086则是原服务器的端口。

在本地试着连接一下,Enjoy it~