共有38篇文章被收藏推荐
收录于2007-08-16
认领
报错
推荐
很久很久没有写 blog 了,MSN 、GTalk 也很少上,有些朋友问我现在的状态,甚至还有问我是否还活着。连 emfox 都写了 blog 声明自己还活着,我也一定要来冒个泡,记一篇流水帐吧,描述一下我现在的状态,还要多多感谢关心我的朋友们。
简单地说,现在生活很忙碌,虽然我一直都是比较忙碌的,但是现在确实忙得连 blog 都很少有时间来写,当然,另一个原因大概是自己近来也没有什么可以写的吧,不想写太多生活琐事在 blog 上,而自己最近在弄的东西,自己都还没有琢磨清楚,也不好意思胡乱写了。人家都说保研了之后过的是猪一样的生活,我想大概也只是开开玩笑吧。生活上突然彻底地变成另外一种 style ,似乎并没有带来什么特别不习惯的地方,不过我一直也就比较喜欢充实一些的生活吧,所谓转变其实也只是在忙着折腾不同的东西而已。
一方面是实验室,因为导师特别牛,而且人很好,自己也不好意思也不想蒙混了事吧,所以绝大部分时间是花在实验室相关的事情上。实际上,这么辛苦的一个原因也还是自己太弱了吧,导师主要做 Machine Learning 相关的研究,自己接触 Research 并不是很深,现在的状态还是帮忙实现程序、跑一下实验之类的,但是我也希望能逐渐有更深入一些的理解,然后能有一些自己的 idea 吧,说实话,到目前为止我都还是非常 enjoy 这个过程的:
- Matlab coding:不出所料,很容易上手的。以前我一直以为大家用 Matlab 是因为它带的数学库高效,现在明白,虽然并不一定是最高效的,但是肯定是非常方便的。不可否认,Matlab 在许多方面都做得非常方便,而且在矩阵运算等方面从底层库自动地支持并行,在多核的机器上跑起来非常爽。但是如果要让我说一下对 Matlab 那个脚本语言的感觉的话,真的是觉得设计得不太好,或者根本没有什么设计——完全是 ad hoc 的产物。不过,即便如此,似乎这是目前最好的选择了。说起 Matlab ,众所周知将程序 vectorize 能够提高运行效率,但是很多时候都是用空间换时间来利用底层矩阵运算的并行支持吧,对内存要求就很大,一不小心就 Out of memory 了,后来我一怒之下给分了一个 40 GB 的 swap 空间,总算不报内存不足了,不过后来发现其实没有什么大的用处,因为如果程序自己要频繁使用的数据都不能在内存中放下而是一直要去读写 swap 的话,速度实在是太慢了,最后还是需要改程序的。另外,通常要跑许多实验的话,特别是一个实验要跑很久的情况,只用自己的机器就显得有些不够,大家抢公共 server 来跑程序还是比较好玩的,不过礼貌一点的话,可以调整 Matlab 让它只占用一两个核,给别人留些资源。发现 Windows 如果 cpu 占用达到 100% 的话,基本上就什么事情都干不成了,而 Linux 下似乎同时还听歌、写程序都不会感觉到卡,莫非是 Ubuntu 下的进程调度对 X 程序做过调整,优先级比较高?
- Learning:说的是我自己的学习,而不是 Machine Learning
主要是看一些相关的书吧,比如《Pattern Classification》和《Pattern Recognition and Machine Learning》之类的,也算是作为入门吧,另外就是数学了,强烈地感觉到数学需要补,碰到什么 Manifold 之类的都直接弄不明白了,可是好像感觉好补的东西太多了,倒是无从下手了。另外就是看 paper ,这个事情有时候没完没了——如果 paper 看不懂,通常会找 paper 的参考文献里引用的 paper 来看,然后 paper 数就指数级增长了。总之呢,最近是读了很多很多的东西,感觉很好,因为大学以来除了复习考试之外几乎都没有能完全静下来认真读过什么书吧。
还有一点就是实验室现在在 ZJG ,而我又搬到了 YQ ,现在几乎每天往返,倒成了天然邮递员,时不时帮人带点东西。不过每天坐车其实是相当痛苦的,特别是等车的时候,而且车上看书又太坏眼睛了,晚上车上还不开灯。最后我干脆买了个魔方开始玩,我一直很不擅长玩这种很考智力(或者短期记忆力?)的东西吧,现在也还只能拼出一面一层来,不过我也不急吧,时间倒是有的是,只要我没有失去耐心就行了。
另外一个让我很高兴去投入精力去做点事情的就是俱乐部了。一个是前一阵子组织起来的读书会,来源于Human Instrumentality Project 和 Ghost in the Shell ,我给生造了一个名字:Ghost Instrumentality Program (GIP) ,目前在看《Introduction to Information Retrieval》一书,我觉得效果挺好的。读书的效果就不用说了,又给大家制造了另一个经常聚在一起交流的机会,而且每次有人主讲的形式,虽然听众不多,但是其实也是一个不错的锻炼机会。
秋学期的时候俱乐部组织了一次比较大的纳新,吸收了不少新鲜血液,之后有微软校园行,几乎是在同时还承办了学校的 C 语言比赛,也算够忙活了。而冬学期 Technology Group 推出的技术活动主要是小课堂——搜索引擎相关的四场系列讲座。上周六刚做完第二场,由我讲了正则表达式以及中文分词相关的东西,也算是做过不少小课堂讲座了,这次总算时间控制得比较好,也没有从头到尾都特别紧张,希望是真的得到锻炼了,而不是一时运气好吧!
这种密集的活动频率其实对于俱乐部的 staff 来说还是相当忙的,特别是低年级的 dd mm 们课程还是相当重的。不过,似乎我现在一说起做活动就是看到各种各样的锻炼机会:组织、沟通、统筹、执行力等等,而且,从活动整个的情况也能从一定程度上看出来哪些人是否擅长做哪些事吧。正好也可以好好考验一下俱乐部现行制度(家文化+Project Manager)吧。
其实转变的不止是我吧,和 cerror 完成俱乐部主席的正式交接之后,houshui 似乎开始实验室忙碌的生活了;而 MSRA 归来的 cerror 现在竟然是满口 SVM 、KDD 云云;sleepyworm 也在为理想而忙碌吧,似乎寒假里有相当充实的安排;而 moonykily 的寒假大概会在 MSRA 度过吧,他走后现在我们寝室就只剩下两个人了。说起来,我们寝室也算圆满了:四个人,一个准备出国中,还趁机溜出去实习了;一个人在准备考研,已经搬出去很久了;一个人保研了,那就是我;最后一个是直接找工作,而且似乎也拿到了阿里巴巴的 offer 吧,现在寝室里大部分时间就几乎只有他一个人了。只是我自己的寒假还没有想好怎么安排,反正我是决定不回家了,虽然很小就离家在外读书吧,但是还是第一次过年不回家吧。也许过年本来就对我没有什么特别多的意义吧,现在还记得去年春晚的时候我抱一个本在旁边写程序的情景;也或许是为了避开一些东西吧;或者说得好听一点,不能回家颓废了,要好好学习。
不管怎么说,在任何情况下,好好学习总是错不了的吧!
标题取得似乎有点大,不过在介绍 PPM 之前还是看一下我做这个简单的实验的效果吧,我收集了一些各种语言的源代码,大致如下:
1.7M c 1.4M cpp 1.4M elisp 1.4M erlang 1.8M java 1.8M python 1.4M ruby
通过这些数据训练出一些模型出来,然后对类型未知的源代码进行分类,看它是哪种语言的代码,测试了 310 个文件,有三个出错了,效果应该还算不错吧。
那么,关于整个故事,还是让我们从头开始说吧。
其实这篇文章在至少一个月以前就构思好了,只是要用到的那个 pyppm 一直都没有做完,所以一直拖到现在。实际上 pyppm 的核心的编码和解码都早就用 C++ 写完了,只是一直没有把那个易用的 Python 接口加进来,今天也正巧有空,便实现了一下,顺便做了一下实验。
PPM 是 Prediction by Partial Matching 的缩写。这是一个预测算法,简单地说,就是根据当前的 context 来预测下一个会出现的 symbol 是什么。context 就是之前出现的 symbol 的序列,例如,之前已经出现的序列是 aaab ,对下一个 symbol 进行预测,就是给出这样的结果(例如):紧接着会出现 a 的概率是 0.6 ,出现 b 的概率是 0.3 ,出现 c 的概率是 0.1 。我们需要一个合适的模型来生成“合理”的预测,预测得越准就越有用(有什么用后面再说)。PPM 采用的方法非常简单:通过对输入序列进行统计,使用频率来估计概率。
例如,当看到输入序列 aaab 之后,大致可以得到这样一个统计信息:
- aaa: (b, 1)
- aa: (a, 1); (b, 1)
- a: (a, 2); (b, 1)
可以看到,比如在前一个字符是 a 的情况下,下一个字符出现 a 的概率比出现 b 的概率要大,而这些信息完全是根据输入序列统计出来的,并且是在不断更新的。一般维护向前的 1 到 N (比如 6)的序列作为 context ,也有不固定 N ,而是自适应性地调整的情况。总之,在正常情况下,当输入的序列“足够多”的时候,模型会被训练得越来越好,进而能够做越来越精确地预测。而且,训练好的模型,应用在同类的问题上,预测的效果会很好,但是用在不相关的东西上,反而会适得其反——就好像费力教会一个人精通了英语,然后给他一段中文让他看一样。这样明显的区别用在模式分类上正好非常合适,我们在后面还会再说。
那么,能够做预测了,比如,我很确信,下一个字符 99.9% 的是 c ,可是这又什么用呢?我们知道,事物越混乱它的熵就越大,相反,越有规律熵就越小,这里 99.9% 的预测正是我们掌握了 symbol 出现的规律的结果。而根据香农的理论,熵被作为信息编码需要的 bit 数的下界而存在。因此,概率越大,熵就越小,我们就有肯能用越少的 bit 来编码相应的信息。
当然,仅仅是“有可能”还不行,于是 Arithmetic Coding 又出场了。在上《多媒体技术》的课上讲到过这种巧妙的编码方法:它将信息编码成一个实数——或者说是一个实数区间里的任意一个实数。详细的方法我就不在这里介绍了,可以直接看 Wikipedia 上的介绍,非常有趣。不过,计算机并不能方便地处理任意精度的实数,因此再转换为二进制,一个完整的编码系统就完成了:通过 PPM 做概率预测,然后将结果用 Arithmetic Coding 编码方法进行编码。而且不需要传递诸如辞典之类的,解码器也是用同样的方法,通过将算数编码的概率解码开,让 PPM 模型从头开始学习,到最后得到一个和编码器最终完全相同的模型,这时原来编码过的数据也就完全解开了!
一个最直接的应用就是用来做数据压缩,我在 pyppm 中实现了编码和解码器,可以参考里面的 test.c 。我大致比较了一下,压缩比比普通的 gzip 要高,但是不如 bzip2 ,而且在数据量很大的时候有些慢,占用内存也比较大。不过我想一般实际的压缩程序也不会直接采用某一种单纯的算法,而是各种方法混合再加上许多优化的吧。另外,PPM 还被经常用到的地方就是 Pattern Classification 。
究竟要如何来做呢?让我们来用 pyppm 做一个简单的实验就知道了。我今天终于为 pyppm 做了 Python 的包装代码(要不然怎么能叫 pyppm 呢?
),不过和我最开始的设想的完整的接口已经偷懒了很多了,但是至少还是可以方便地做实验。首先将 pyppm 模块加载进来,新建一个空的模型:
>>> import pyppm >>> model = pyppm.Model()
然后用一个 input.txt 来训练这个模型:
>>> model.train("input.txt") 118
返回的数字是编码这个文件所需要花的字节数,这个文件原来是 139 字节的,似乎没有什么特别明显的改进啊。于是再训练一次:
>>> model.train("input.txt") 22
这次数字瞬间就降到了 22 ,不断地重复,最后数值大约稳定在 6 个字节了:
>>> model.train("input.txt") 14 >>> model.train("input.txt") 11 >>> model.train("input.txt") 9 >>> model.train("input.txt") 8 >>> model.train("input.txt") 8 >>> model.train("input.txt") 7 >>> model.train("input.txt") 7 >>> model.train("input.txt") 6 >>> model.train("input.txt") 6 >>> model.train("input.txt") 6 >>> model.train("input.txt") 6 >>> model.train("input.txt") 6 >>> model.train("input.txt") 6
可是当我们再将这个 model 用于另一个相同大小的 input2.txt 的文件的时候,又得到了比较大的数值:
>>> model.train("input2.txt") 126
那么总结一下:
- 未训练过的模型会得到比较大的数值。
- 训练过后模型逐渐成熟,得到的数值会越来越小。
- 训练过的模型如果用在不相同的数据上,不仅不能得到好的结果,反而还有可能比初始情况更差(126 > 118)。
最后一条将“不相同”改为“不相关”或者类似的概念就能推广了。简单地说,现在我要进行一个 N 类的分类任务,首先我从 N 个类别中选出一些 case 来作为训练数据,为每个类型训练出一个 PPM 模型出来,在对未知数据做分类预测的时候,我们只要分别用这 N 个模型来对数据进行编码,编码长度最短的说明相关性最高,即定为预测的分类,就这么简单!
既然 pyppm 已经有了 Python 的接口,我们不妨来实践一番:来根据源代码的内容预测源代码是由什么语言写成的。做这个实验的主要原因是数据集相对好找,也不用手工标注来做训练集
。首先我在系统上找来了一些代码,主要有以下几类:
- C: 取自 Linux kernel 的驱动程序部分。
- C++: 取自 Rubinius 的新的 C++ VM 。
- Ruby: 取自 Rubinius 。
- Python: 取自 Python 标准库。
- ELisp: 取自 Emacs 的源代码。
- Erlang: 取自 Erlang 的源代码。
- Java: 取自 solr 的源代码。
由于源代码大小不一,因此我没有按照文件个数来统计,而是大约每个类别留下了 2MB 左右的代码量,作为训练集,并从中随机抽取出一些代码组成一个大约 1MB 的测试数据集(测试数据已从训练集中删除)。另外,我还从 skime 的代码库里取了所有的 Python 代码,以及一些 hbase 代码库里的 Java 代码和自己 Emacs 插件目录下的一些 Elisp 文件加到测试集中组成了一个总共 4MB 左右的测试集,包含了 310 个大小不一的源文件。
首先是为各个语言训练模型,代码非常简单,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #!/usr/bin/python from sys import stdout from os import listdir from os.path import join import pyppm models = ["c", "cpp", "elisp", "erlang", "java", "python", "ruby"] def train(model_name): print "Training PPM model for %s..." % model_name model = pyppm.Model() for f in listdir(model_name): model.train(join(model_name, f)) stdout.write(".") stdout.flush() print "\nTraining done, saving model..." model.dump("models/%s" % model_name) print "Done!" for model in models: train(model) |
然后下一步是测试,首先将各个保存好的 PPM 模型加载进来,然后对每个测试文件,分别用各个模型来做 predict ,选择最小的作为预测分类,并统计错误率:
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | #!/usr/bin/python import re from sys import stdout from os import listdir from os.path import join from operator import itemgetter import pyppm model_names = ["c", "cpp", "elisp", "erlang", "java", "python", "ruby"] models = {} for name in model_names: stdout.write("Loading %s model..." % name) stdout.flush() models[name] = pyppm.Model("models/%s" % name) stdout.write("done!\n") def real_type(path): type_mapping = { 'el': 'elisp', 'erl': 'erlang', 'py': 'python', 'rb': 'ruby' } m = re.search(r'\.(.+)$', path) ext = m.group(1) type = type_mapping.get(ext) if type is None: type = ext return type def test(path): scores = [(models[name].predict(path), name) for name in model_names] scores.sort(key=itemgetter(0)) predicted_lbl = scores[0][1] real_lbl = real_type(path) if predicted_lbl == real_lbl: return True else: print "Failed to predict %s" % path print "=" * 40 print "Real type: %s" % real_lbl print "Predict type: %s" % predicted_lbl print "Predict scores:" for score, lbl in scores: print "%6d <-- %s" % (score, lbl) print return False total = 0 error = 0 for f in listdir("test"): total += 1 if not test(join("test", f)): error += 1 print "=-"*30 print "%d tested, %d predict failed" % (total, error) |
下面是程序的输出中三个被判错的详细情况:
Failed to predict test/fore200e_mkfirm.c ======================================== Real type: c Predict type: cpp Predict scores: 1828 <-- cpp 1935 <-- c 2188 <-- ruby 2398 <-- python 2419 <-- java 2614 <-- erlang 2848 <-- elisp Failed to predict test/strlcat.cpp ======================================== Real type: cpp Predict type: c Predict scores: 767 <-- c 930 <-- python 1053 <-- cpp 1202 <-- elisp 1247 <-- java 1265 <-- ruby 1450 <-- erlang Failed to predict test/strlcpy.cpp ======================================== Real type: cpp Predict type: c Predict scores: 691 <-- c 835 <-- python 990 <-- cpp 1110 <-- elisp 1149 <-- ruby 1156 <-- java 1362 <-- erlang =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 310 tested, 3 predict failed
可以看到主要是 C 和 C++ 之间没有分清楚,似乎也是可以原谅的,不过从具体分值排名来看,似乎 Python 这个分类也蠢蠢欲动。不过我还是决定检查一下判错的文件的内容,因为“real type”事实上是根据文件的扩展名来直接推断的。
比如这个 test/strlcpy.cpp 的实际内容如下:
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | /* * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef HAVE_STRLCPY #include <sys/types.h> #include <string.h> /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ size_t strlcpy (char *dst, const char *src, size_t siz) { register char *d = dst; register const char *s = src; register size_t n = siz; /* Copy as many bytes as will fit */ if (n && --n) { do { if (!(*d++ = *s++)) break; } while (--n); } /* Not enough room in dst, add NUL and traverse rest of src */ if (!n) { if (siz) *d = '\0'; /* NUL-terminate dst */ while(*s++) ; } return (s - src - 1); /* count does not include NUL */ } #endif |
本来就没有一点 C++ 的特征嘛,让人来判断估计也会判错,或者说这个问题本身就是模棱两可的,另外一个类似的 test/strlcat.cpp 也是差不多的。不过 test/fore200e_mkfirm.c 判断错误确实是没什么可说的了。
总的来说,这个试验还是挺成功的,预测的准确率挺高,其中两个“预测错误”其实是帮忙发现了标注错误,而另外一个错误其实是在允许的范围之内的——实际上这个准确率远远超过我的预期啊。
不过,PPM 生成的模型都是非常大的,如下:
total 160M -rw-r--r-- 1 pluskid pluskid 30M 2008-11-03 15:37 c -rw-r--r-- 1 pluskid pluskid 20M 2008-11-03 15:37 cpp -rw-r--r-- 1 pluskid pluskid 22M 2008-11-03 15:37 elisp -rw-r--r-- 1 pluskid pluskid 23M 2008-11-03 15:38 erlang -rw-r--r-- 1 pluskid pluskid 20M 2008-11-03 15:38 java -rw-r--r-- 1 pluskid pluskid 30M 2008-11-03 15:39 python -rw-r--r-- 1 pluskid pluskid 17M 2008-11-03 15:39 ruby
运行测试的时候内存占用大约在 380 MB 左右,如果用在更大量的数据上,可能就要考虑改进算法或者添置硬件了。不过原来在垃圾邮件过滤的项目里那个占用内存很大又比较慢的(大概因为是用纯 Java 实现的原因吧) PPM 也能在比较大的邮件 Corpus 上跑起来的!
另外,这种采取压缩的方法来进行分类其实也在不少地方都有用到,看起来似乎是很巧妙的,好像不用做特征提取了,似乎是把整个数据原封不动地作为输入,这样也许能抓住所有的特征。其实不是这样,就好象 kernel method 把特征提取的过程隐藏到了 kernel 中一样,这类的算法其实是把特征提取推迟到了编码方法当中去,就比如 PPM 算法,大致看起来就好像是在做 n-gram 这样的特征提取来做预测一样,所以还是万变不离其宗的呀。
新的空间正在着落中,到时候应该还可以继续用这个域名,如果没什么问题的话,http://blog.pluskid.org 也可以同时用起来,慢慢切换吧。不过这个 blog 竟然到现在还没有挂,今天犯这个错误太低级了,于是还是忍不住上来把它记到 Bug Archive 中去。
事情是这样的,原来是一个循环:
for (int i = sizeof(int)-1; i >= 0; --i) { n |= buf[i]; n <<= 8; }
编译的时候有一个 warning 说将 signed 和 unsigned 变量进行比较,因为 sizeof 返回的结果 size_t 是无符号的,于是我顺手就把 int i 改成了 unsigned int i 。然后这个 unsigned int i 就在那里永远轮回了。事情就是这样,想找什么借口都找不出来了…… -.-bb
事情确实发生得有点突然,昨天晚上登上 Google Talk ,Jack 突然告诉我托管的服务器在 11 月份就要拿回来了。那代表所有 lifegoo.com 上的应用都会下线吧。这个 blog 是去年五月份的时候我请求帮忙开通的,非常好用,真的要感谢 Jack 和 sishen 了!
不过,现在得到这个消息还真有些伤心,毕竟是用了这么长时间的 blog ,incoming link 也是有不少的。但是总之还是先把数据备份下来吧。我想先用 wordpress 的方式备份一份数据库,再用普通网页抓取的方式把网站爬下来,不过好像 quark 昨天晚上已经帮我爬过了,还做成了一个 chm ,待会找他要去。
备份下来之后还要考虑今后的去处呢。一时之间都没有想到哪个地方比较好用又稳定的。也许是该去注册一个域名了,不知道现在注册域名费用如何。可是如果单单是为了放一个 blog 去租一个虚拟空间的话,似乎太浪费了,而且这些价格啊、在哪里租比较好啊之类的也都还很不了解。时间比较仓促,似乎现在 blogger 还没有被封掉,于是暂时用 blogger 上的那个吧: http://pluskid.blogspot.com ,如果我找到了新的地方,会在那里贴出来。lifegoo 这里也不清楚具体哪天会下线,所以我待会把这篇文章也贴过去。
我想 blog 我还是希望继续写的,虽然现在频率比以前已经低了很多了吧。moonykily 曾经跟我说他觉得写技术 blog 的人都是脑子进水了,blog 本来就是用来抒发自己心情的。当然我是不会同意他的这个观点的,且不说写技术 blog 能够让知识得到分享这样的话吧,更自私的观点就是:如果你能把自己知道的东西给别人描述清楚的话,你会掌握得更加牢靠。而有些东西当你准备要写下来的时候,你才会发现原来自己根本没有弄清楚。我写 blog 一般会花掉半天到两天不等的时间,但是大部分时候我还是觉得是有收获的。
所以,我还是先去备份数据了,之后怎么迁移还真是个麻烦事,一时也想不清楚,不知道大家有什么好的主意没有?
用 LaTeX 可以生成漂亮的公式,这是众所周知的。但是并不是总是会用 LaTeX 来做整个文档。例如,虽然我也用 beamer 做过 slides ,但是我还是觉得用 PowerPoint 或者类似的工具可以更方便地做出漂亮的幻灯片来。又比如我在写 blog 的时候想要插入一个公式,等等。这个时候我通常会临时建一个 TeX 文档,输入这个公式,生成出 PDF ,用阅读器打开,然后截图。虽然已经这样做了许多许多次了,但是其实这个过程非常无聊,而且我喜欢透明的背景,截图是白色的背景的话,有时候不能很好地融入到场景之中。
但是其实输出为 PDF 格式只是 LaTeX 文档的一种渲染方式,要得到一个透明背景的 png 其实也是非常方便的事情。我以前在用 Muse 做笔记的时候就有一个像 MediaWiki 那样的 latex 标签可以直接书写 LaTeX 公式,结果会自动转化为图片嵌入到生成的文档中去,效果就像这个页面显示的那样。
虽然不会像 Muse 那么方便,不过我想至少可以写一个简单的脚本来简化这个过程。首先是要新建一个空的 LaTeX 模板,并且需要把框架写好(例如,将页面布局设置为 empty ,没有页眉页脚之类的,因为只需要一个公式所占的那部分内容),引用一些常用的宏包:
\documentclass{article} \usepackage{amsmath} \usepackage{fullpage} \usepackage{amssymb} \usepackage[usenames]{color} \usepackage{latexsym} \usepackage[mathscr]{eucal} \pagestyle{empty} \begin{document} \end{document}
然后是用 latex 命令将其编译为 dvi 文件,最后用 dvipng 命令转化为 png 格式的图片即可。整个脚本如下:
#!/bin/bash TMPFILE=/tmp/tex2png.tmp.tex if ! [[ -e $TMPFILE ]]; then cat <<EOF > $TMPFILE \documentclass{article} \usepackage{amsmath} \usepackage{fullpage} \usepackage{amssymb} \usepackage[usenames]{color} \usepackage{latexsym} \usepackage[mathscr]{eucal} \pagestyle{empty} \begin{document} \end{document} EOF fi vim $TMPFILE +11 cd /tmp latex $TMPFILE dvipng ${TMPFILE%.tex}.dvi -T tight -x 5000 -bg transparent -o eq.png echo "Equation generated to /tmp/eq.png"
不过这其实是一个相当 ad hoc 的脚本了,虽然没有立即删掉上一次编辑的公式的 TeX 源码,但是如果下次做的时候会覆盖前一次的代码,之后再要想做一点小修改也是需要整个公式重新写一遍的。应该最方便的还是像 Muse 那样直接嵌入 LaTeX 标签的 WordPress 插件,不过一来我没有精力去做那样的插件,二来就算有现成的插件,我也不想再服务器上瞎折腾什么 LaTeX 之类的。总之能用就行吧!
最后,其实还可以用一下这里的 ImageMagick 脚本给公式加个阴影什么的,就像本文一开头的那个 LaTeX 的字样一样。


