Profilo di Wei琛凡FotoBlogElenchiAltro Strumenti Guida

Blog


18/10/2009

球面分布

      球面分布这个东东学起来挺有意思,可是目前我还没有完全想通它的直观意义。上个礼拜五以为自己想明白了,还在TA session上大肆发挥了一通(我真的很适合当老师),其实是片面的。我当时说,球面分布就是沿各个维度按同样的一维分布采样,这样得到的采样点就是球面分布的。于是,球面分布便有了许多很好的性质,比如说:
      1)球面分布可以看成两个独立分布的乘积,一个是在单位球面上均匀采样,另一个则是对长度采样;
      2)球面分布向量的长度与归一化球面分布向量的联合分布其实就是长度随机变量与单位球面均匀分布向量的联合分布;
      3)如果有一个尺度函数能够抹掉所有向量的长度信息,也就是说T(aX)=T(X),那么这个尺度函数作用于任意球面分布向量,便与T(S)同分布,S就是单位球面均匀分布向量。
      按照第一段提到的直观意义,这三点性质都是显而易见的,只要用数学语言表述出来并且证明一下就好了。可上述直观意义的片面性在于:如果一个向量服从球面分布,那么每个维度服从同样的分布;但如果仅仅是每个维度按同样的一维分布采样,并不能推出整个向量就是球面分布的,甚至每个维度独立同分布都不行。于是,问题就来了,什么样的各维度同分布采样能够导致球面分布呢?什么样的不行呢?当然我知道,从特征函数可以知道这一点,但在现实生活中,除了多维正态分布,还有哪些分布可以归为球面分布而又有实际用途呢?多维正态分布有非常好的性质,这是它得到广泛应用的原因。很多结论都可以由多维正态分布的性质直接推出,并不需仰仗球面分布。
      不过不管怎样,至少我看到了上面的3)价值。对于任意一个球面分布向量X来说,T(X)=sqrt(d)*avg(X)/S(avg(X)表示所有维度的均值,S即标准差)是抹掉了向量长度信息的一个函数,于是对任何球面分布向量,这个T(X)便于T(S)同分布,而我们知道标准多维正态分布N_d与S同分布,于是T(X)便与T(N_d)同分布,即为t(d-1)。这个结论非常爽,也是t分布这么广泛应用的原因之一吧。
08/10/2009

旷日持久的讨论

      今天终于有了比较实质性的结果。值得记上一笔。
      我工作的项目是参与开发一个server。这个server是数据库之上的一个layer,从数据库取出数据进行cache,并将数据返回给其它的应用程序。server用java开发,通讯层使用MINA框架。同时要提供java和c++的Client Library(CientLib),除了给其它应用程序提供访问数据的接口,还要隐藏实现static load balancing和fail over的机制。
      相当多的business objects要处理。这些objects有不小的可能性会在将来需要改变。这迫使我们为objects引入版本,从程序角度简单看就是创建与版本对应的package(java)。需要提供不同语言的ClientLib,则迫使我们寻求code generation的方案。Code Generator(CodeGen)主要要做两件事,一是按照一种更高层的object定义格式,生成不同语言下的class定义;二是读写objects。Google protocol不错,但由于编译器不兼容,最后被迫放弃。只好自己写CodeGen。
      不同版本的objects可能在某段时间内存在,也就是说server必须在某段时间内支持同一个object的不同版本。这产生出很多问题。怎么保证后向兼容,即老版本的ClientLib仍然能够和新版本server沟通?怎么实现serielization和deserielization?无数种方案被提出,无数次讨论,终于有了一个比较好的方案。
      我们把objects的改变分成两类。一类是较大规模的改动,包括删除field,在原有两个field之间添加新field等,而另一类则是简单的添加field。前一类我们会升主版本,比如说从1.3到2.0,这时需要创建新的package(java)和namespace(c++)。server另开一个端口serve这个新版本。也就是说,没有一个端口会serve两个主版本的objects,或者说每一个主版本都对应一个独特的端口及一条独特的socket。这样可以实现主版本间的后向兼容,读写也不存在什么问题。对后一类,我们升级副版本,比如说从2.5到2.6。不需创建新package,不需开辟新端口,但需要在CodeGen上做足文章。
      本来我们在CodeGen中是用java序列化的,但如此一来,老版本的ClientLib如何读?讨论到最后我们决定放弃序列化,而采用我们自定义的消息格式,其实很简单,就是对每个object,用4个bytes写object长度,然后紧跟该object的binary序列。注意每个object均如此,因此会有嵌套。在读object函数中,按field的顺序一直往下读,读完之后直接跳到该object binary序列的末尾,然后紧接着读下一个object。注意仍然有嵌套在这里面,不过每个object的读函数做好自己的事,便没什么问题了。
      关键在写object函数。怎么写object的长度呢?其实也不难。每个object的写函数做完自己的事情之后将长度往上一层汇报一下,上一层的写函数求一下和再往上汇报,仅此而已。这些logic都要在CodeGen的代码中实现。
      于是,一个那么复杂的问题最后竟然简化到这样的地步,不得不佩服组里这些经验老道的程序员们。狠狠的崇拜了他们一下,也狠狠的学了一招,过瘾!
05/10/2009

浪费了两天

      最近看上了Flex,简单好用有够酷。但是一个小破问题却让我头疼了两天,至今无解。
      用HttpService发送post请求,不过是flex 101的东东。post请求中带参数,可以算102吧。可是,我就连这102都搞不定,塞。。。
      我想将下面的字符串“<reportedby>huwei</reportedby>”作为参数“bug”的值传到server端。这里参数值是一个xml string,flex会将它自动url-encoded一下,于是传到server端就变成了%3Creportedby%3E...之类的了。已经试过将contentType改成“application/xml”,会导致其它的麻烦,下面会说到。
<mx:request>
    <bug>
        <reportedby>huwei</reportedby>
    </bug>
</mx:request>
      似乎PHP、JSP、Perl对传过来的xml string都可以正常解析,没问题。但讨厌的是我的project中,我们用MINA框架来写一个简单的Java Http Server接口,用socket连接,只不过包上http的codec而已。不知道是不是因为这个“非主流”Http Server的缘故,flex的HttpService必须用contentType=“application/x-www-form-urlencoded”、server端必须有crossdomain.xml,浏览器才能够与server沟通不出fault。这是题外话。
      xml string作为参数值传到我的server,都变成了上面说的url-encoded字符串。当然可以用java的URLDecoder将其还原。但心里总是不爽,按道理说,flex应该提供这样的便利,将xml string原原本本传递而不进行任何处理的啊。于是使劲google,使劲试各种方案,结果两天下来还是没找到。哪位达人要是知道怎么搞麻烦告知一下,谢了先啊。
04/06/2009

期权策略从数学上来说应该是挺简单的啊

    期权的四个building blocks及其简单。下面考虑它们的收益曲线。
 
    buy call。可以看成是射线 y = x<0 ? 0 : x (1)平移到(s,-p)。其中s是strike price,p是premium,下同。
    write call。可以看成是射线 y = x<0 ? 0 : x (1)先乘上-1然后再平移到(s,p)。
    buy put。可以看成是射线 y = x<0 ? -x : 0 (2) 平移到(s,-p)。
    write put。可以看成是射线 y = x<0 ? -x : 0 (2)先乘上-1然后再平移到(s,p)。
 
    由于underlying assets的价格不可能到无穷大,所以可以假设我们所考虑到空间仅限于[-N,N]*[-N,N](N是一个大数),记为S。每一个期权策略可以看成是S中的一个函数,那么所有策略组成的函数空间的基是什么呢?{(1),(2)}当然是首选。因为(1)(2)相互正交且(1)、(2)的二泛均为有限值可归一。
    (1)、(2)再简单不过了,只是它们都不连续。不连续会有什么影响么,忘了。。。策略空间的运算也很简单,就是最常见的加法和乘法。这样一来,如果不连续没什么影响的话,策略空间应该是极为简单的。
 
    还没有学期权定价,不知道可不可以用上函数空间的概念。
20/02/2009

原来你在这里

        在Kepler Asset Management干了四个月活,收获不小。零碎的活不说,干下了两个比较大型的项目,让我颇有成就感。遗憾的是,在我离开公司之前,第二个项目的bug没能查出,这让我寝食难安。我真的不相信,我如此精心的设计、严谨的逻辑和漂亮的程序怎么会出错!幸而有蔡蓉接手我这项目,我们这个星期的努力终有成效。啊,虫虫啊,原来你在这里!
        这个项目要把每天交易的symbol聚团,即对每一个symbol来说,要把一天中所有和这个symbol相关的交易信息放到一起构成一个大block,压缩这个block,并把压缩结果顺序写到一个最终文件里。这个文件一般来说要超过2G。为了提高写文件的效率,我用了一个2G的写buffer。为了节省打开关闭文件的次数,我在程序开头fopen,在最后fclose,所以我程序的输出部分很象下面的片段1。
 
char* buf = new char[0x7ffffff];
memset(buf, 'a', 2000000000)
FILE* fh = fopen("OutputFile.bin", "ab+");
setbuf(fh, buf);
fwrite(&buf[0], 2000000000, 1, fh); // output about 2G byte into the OutputFile.bin
memset(buf, 'b', 2000000000);
fwrite(&buf[0], 200000000, 1, fh); // output about 200M byte into the OutputFile.bin
fclose(fh);
 
        你能看出有什么问题吗?再看看片段2。
 
char* buf = new char[0x7ffffff];
memset(buf, 'a', 2000000000)
FILE* fh = fopen("OutputFile1.bin", "ab+");
setbuf(fh, buf);
fwrite(&buf[0], 2000000000, 1, fh); // output about 2G byte into the OutputFile1.bin
fclose(fh);
memset(buf, 'b', 2000000000);
fh = fopen("OutputFile1.bin", "ab+");
setbuf(fh, buf);
fwrite(&buf[0], 200000000, 1, fh); // output about 200M byte into the OutputFile1.bin
fclose(fh);
 
        不同之处在于片段1最开始打开文件,输出了大于2G的数据后才关闭。而片段2每次输出都打开并关闭文件。貌似这两个片段应该得到同样的输出文件,但事实上却不一样。这种底层的IO不一致性使得查bug极端困难。项目那么大,我总在花时间检查其他方面,谁能想到最后虫虫竟然在这里!
        似乎用fflush可以解决这个问题。如果待输出的Memory Block与写buffer不是同一个block的话,是需要fflush。但我让这两者合二为一啦,还需要fflush么?底层的实现没法深究,只能记住fflush的重要性。
        以上的结果都在VS2005下,在VS2008下上述两个片段的输出结果一致。可能是微软已经发现了这种底层IO实现的不一致性而做了修改。
13/12/2006

实现了一个跟踪算法Ensemble Tracking

    跟踪(tracking)是计算机视觉里一个很重要也是很热的研究方向。我之前读过许多相关论文,但没有实现过任何算法。于是这学期计算机视觉课的大作业,我就决定实现一个跟踪算法。CVPR05中有一篇题为“Ensemble Tracking”的文章,核心思想比较简单,就是利用上一帧训练所得的Adaboost强分类器,对当前帧的像素进行分类,分为前景和背景两类。之后根据分类置信图(confidence map)来确定当前帧的感兴趣区域,进而训练出新的Adaboost强分类器。
    本来想自己一点一点写的,但由于时间紧张,后来就用了openCV这个类库。openCV是专为计算机视觉研究者开发的一套平台,里面包含计算机视觉领域从底层到高层的一系列方法实现。底层图象操作有如边缘检测、角点检测,中层结构恢复有三维重建、立体视觉等,高层语义判断有人脸检测等。但机器学习这一块的方法实现相对来说就太少了。这几年将机器学习引入计算机视觉的研究趋势方兴未艾,openCV的开发团队应该紧跟这一潮流才好。
    作者在文中所使用的特征是局部方向直方图(local orientation histogram)加颜色。局部方向直方图的量化维度可以随意设定,但一般取为8。颜色就是RGB或者其它的颜色空间,维度为3。因此总共的特征维度为11。但我在实验中发现局部方向直方图其实对跟踪准确度的贡献并不是很大,作者应该就是为了赶潮流而选用这一特征的。局部方向直方图被用于SIFT等特征点提取的算法中,作为特征点的描述子,其目的是使描述子具有旋转、尺度不变等优良特性。强行将其作为每一像素点的特征描述,感觉有点似是而非了。不过在分类的框架下,跟踪的确没有什么好用的特征,有一个新的特征描述就拿来试一下,也正是做研究的套路。
    许多细节还没有实现,只实现了大框架。基本上可以做到实时跟踪,只要跟踪物体的移动速度不要太快就好了。跟踪的研究离实用真是差得太远了,最简单的idea都只能勉强达到实时,就别提那些用到EM啊MCMC啊之类的算法了。Level set倒有可能会比较快,下次实现个level set算法看看。
09/12/2006

贡献一个用于获取增广图象的函数

    openCV里面似乎没有求增广图象的函数,于是自己写了一个。“增广图象”就是指在原图象的边界外围扩充数行数列,使得滤波、FFT等在图象的边界处也能够正常进行。扩充的图象值可以简单的设为零,但为了保证边界的连续性,一般是采用对称填充的策略。也即:左右扩充列是将原图象相应列沿y轴翻转所得;上下扩充行沿x轴;四角扩充块分别以四角点为中心点对称翻转所的。
    函数为CvMat* cvAugmentedImageMatrix( IplImage *grayImage, int add_rows, int add_cols )。非常裸的一个程序,没有做复杂的出错判断,将就用吧。如果是彩色图,补充上不同的信道就好了。调用和测试可以如下进行:
CvMat* AugmentedImageMatrix = cvAugmentedImageMatrix(grayimage, add_rows, add_cols );
IplImage *test = cvCreateImage(cvSize(AugmentedImageMatrix ->height, AugmentedImageMatrix ->width),8,1);
cvGetImage( AugmentedImageMatrix , test );
cvSaveImage("g:/test.bmp",test);
   下面是源码  
CvMat* cvAugmentedImageMatrix( IplImage *grayImage, int add_rows, int add_cols )
{
 int i,j;
 CvMat *AugmentedImageMatrix = cvCreateMat(grayImage->height + 2*add_rows, grayImage->width + 2*add_cols, CV_8UC1);
   
 for(i=add_cols; i<=AugmentedImageMatrix->width-1 - add_cols; i++)     // central, copy the original image
  for(j=add_rows; j<=AugmentedImageMatrix->height-1 - add_rows; j++)
   AugmentedImageMatrix->data.ptr[j*AugmentedImageMatrix->width+i] =  CV_IMAGE_ELEM( grayImage, uchar, j-add_rows, i-add_cols);
       
 for(i=0; i<add_cols; i++)               // left, symmetry along y axis
  for(j=add_rows; j<=AugmentedImageMatrix->height-1 - add_rows; j++)
   AugmentedImageMatrix->data.ptr[j*AugmentedImageMatrix->width+i] = AugmentedImageMatrix->data.ptr[j*AugmentedImageMatrix->width+(2*add_cols-i-1)];
   
 int tmpint = add_cols + grayImage->width;
 for(i=tmpint; i<AugmentedImageMatrix->width; i++)         // right, symmetry along y axis
  for(j=add_rows; j<=AugmentedImageMatrix->height-1 - add_rows; j++)
   AugmentedImageMatrix->data.ptr[j*AugmentedImageMatrix->width+i] = AugmentedImageMatrix->data.ptr[j*AugmentedImageMatrix->width+(2*tmpint-i-1)];
 for(i=add_cols; i<=AugmentedImageMatrix->width-1 - add_cols; i++)     // down, symmetry along x axis
  for(j=0; j<add_rows; j++)
   AugmentedImageMatrix->data.ptr[j*AugmentedImageMatrix->width+i] = AugmentedImageMatrix->data.ptr[(2*add_rows-j-1)*AugmentedImageMatrix->width+i];
 tmpint = add_rows + grayImage->height;
 for(i=add_cols; i<=AugmentedImageMatrix->width-1 - add_cols; i++)     // up, symmetry along x axis
  for(j=tmpint; j<AugmentedImageMatrix->height; j++)
   AugmentedImageMatrix->data.ptr[j*AugmentedImageMatrix->width+i] = AugmentedImageMatrix->data.ptr[(2*tmpint-j-1)*AugmentedImageMatrix->width+i];
 tmpint = add_cols + add_rows - 1;
 for(i=0; i<add_cols; i++)               // downleft, symmetry along origin
  for(j=0; j<add_rows; j++)
   AugmentedImageMatrix->data.ptr[j*AugmentedImageMatrix->width+i] = AugmentedImageMatrix->data.ptr[(tmpint-i)*AugmentedImageMatrix->width+(tmpint-j)];
 tmpint = add_rows + grayImage->height;
 for(i=0; i<add_cols; i++)               // upleft, symmetry along the upleftmost pixel
  for(j=tmpint; j<AugmentedImageMatrix->height; j++)
   AugmentedImageMatrix->data.ptr[j*AugmentedImageMatrix->width+i] = AugmentedImageMatrix->data.ptr[(tmpint-add_cols+i)*AugmentedImageMatrix->width+(add_cols+j-tmpint)];
 tmpint = add_cols + grayImage->width;
 for(i=tmpint; i<AugmentedImageMatrix->width; i++)         // downright, symmetry along the downrightmost pixel
  for(j=0; j<=add_rows; j++)
   AugmentedImageMatrix->data.ptr[j*AugmentedImageMatrix->width+i] = AugmentedImageMatrix->data.ptr[(add_rows+i-tmpint)*AugmentedImageMatrix->width+(tmpint-add_rows+j)];
 tmpint = add_cols + grayImage->width + add_rows + grayImage->height - 1;
 for(i=add_cols + grayImage->width; i<AugmentedImageMatrix->width; i++)    // upright, symmetry along the uprightmost pixel
  for(j=add_rows + grayImage->height; j<AugmentedImageMatrix->height; j++)
   AugmentedImageMatrix->data.ptr[j*AugmentedImageMatrix->width+i] = AugmentedImageMatrix->data.ptr[(tmpint-i)*AugmentedImageMatrix->width+(tmpint-j)];
 return AugmentedImageMatrix;
}
05/12/2006

OpenCV学习——关于坐标原点

    整了一个上午才整明白,如果image->origin!=0的话,坐标原点被设为图象的左下角点。包括ROI和CvRect等,都是从左下角。横x右正、竖y上正,整个和我们通常用的笛卡尔坐标系一样。这一点应该补充到中文手册当中去。 
    大家可以试试camshift例程。用鼠标画一个矩形框,这个矩形框被称为selection. 我一开始想当然的认为selection.x和selection.y指的是所画矩形框左上角像素点的(x,y)值,因为一般图象研究中的坐标系设置就是原点在左上角、横x右正、竖y下正。后来编着编着出问题了。仔细研究才发现,selection.x和selection.
y原来指的左下角。那么CvRect和ROI的“增长”方式就是从左下到右上,和我们平时拖动鼠标的习惯(从左上到右下)截然相反,这一点让人非常郁闷啊!
11/11/2006

解脱!Halfedge最终斗胜

     困扰了我许久的程序今天总算该告一个段落了!翻来覆去试了很多种情况,程序都能够正常运行。当然还有可能有一些小bug,但主体框架我可以保证没有问题了,因为所有可能的情况我觉得我都已经考虑到了。历时一个半月,可谓呕心沥血,现在倒没有太多的成就感,更多的是欣慰吧。以前编程太少,编程基础太差,这次作业对我来说是一个巨大的困难和挑战,现在回过头看,它不仅仅使我增加了编程的经验,更让我调整了对一类实际问题的分析方法。这种方法和我所擅的理论分析所用的方法是有很大不同的。
    简单介绍一下Halfedge数据结构吧。这是在图形学当中很常用的一种数据结构。一个三维空间中的曲面可以用很多三角形拼接而成的网格来逼近,因此对三维空间曲面的分析和显示都可以由在三角形网格(triangular mesh)上的操作来完成。在这样的三角形网格中,有三个非常基本的元素:顶点(vertices)、边(edges)和面(faces,即三角形构成的面)。但如何在计算机中表达这些元素以及它们之间的关系呢?人们引入了另一个概念——halfedge。halfedge实际上是带方向的边,每一条边都与两条halfedge相关,分别指向这条边的两个端点。正是由于halfedge的方向性,使得我们的曲面(准确的说是三角形网格)也具有了“方向”。
    整个Halfedge数据结构非常简单。首先,每个顶点都有一条与之相关的out_halfedge,每条边都有两条与之相关的halfedge,每个面都有一条与之相关的halfedge。然后每条halfedge都有一个所指向的顶点、有一条所指向的halfedge(next_halfedge)、有一条指向它的halfedge(prev_halfedge)以及其所在的face。halfedge就像一座沟通顶点、边、面的桥梁,我们可以根据网格中许多halfedge之间强烈的关系,遍历或者操纵整个网格。
    概念是再简单不过了,但是实现起来困难重重。当从网格文件(mesh file)中依次读入面时,必须把当前的面加入到已形成的网格中,并保持halfedge数据结构不变,而其中最关键的是要在任意一个时刻保持边界上的consistency,也就是说,要使处于边界处的halfedge能够始终保持可遍历性。文件中面的顺序是随机的,因此程序必须得覆盖非常非常多的情况。
    一开始,我甚至不知道应该如何去分析这个问题,我总觉得可能的情况太多太多了,简直无从入手,这反应了我分析实际问题的能力尚有欠缺。如何能够从看似极不相似的纷繁复杂的过程中看到某些共性从而对这些过程展开分类,这是我今后应该着重培养自己的一个方面。
    编程的细节在这里无法细述,但我在程序中写了详尽的注释。哪位朋友如果感兴趣的话,我很愿意将程序分享,顺便也可以帮我找找bug。
27/10/2006

品 计算理论 之 函数疑云

    计算理论是一门极其抽象、复杂的学科。当我们翻开各种计算理论的教材,我们不难发现这门学科所研究的实体不外乎以下这些:Grammar,Language, Function, Turing Machine, Set(string set 和 natual number set)。Grammar,Language,Turing Machine自不必说,是计算理论特有的研究实体。而Function和Set在其它的学科中也有研究,比如说数学分析当中要研究Function的连续、可微等性质,泛函里要研究函数空间所具备的开闭覆盖等性质。那么我们就要问了,计算理论到底研究Function和Set的哪方面性质呢?
    让我先宕开一笔,下这么个结论:Set的性质可以由Function而描述。比如说,最基本的“in”就可以用一个“characristic function”来描述:f(x)= (x in S) ? 1 : 0。再比如说,“A intersect B”意味着{x | x in A and x in B}。其中“and”也可以用一个函数来描述。于是,我们可以把火力集中在Function上。这就是“品计算理论”系列要从Function谈起的原因。
    言归正传。计算理论主要要研究Function的Computability(可计算性)和Complexity(计算复杂度)这两个重要性质。Computability是要考虑一个函数到底能不能被我们的机器“算出来”,而Complexity要考虑一个函数到底能够在什么时候被我们的机器算出来。显然,能算的函数才值得我们去研究其算出的时间,所以,研究computability是研究complexity的前提。complexity貌似不在Quals考试范围之内,因此若时间空余,我再专开一集讨论complexity。
    在computability的学习当中,我想没有人会不被下面几个概念所惑: primitive recursive function,partial recursive function,total partial recursive function。它们之间有什么区别和联系呢?这是一个极其关键的问题,是理解可计算性的一把钥匙。然而遗憾的是,在这一问题上,常常是疑云笼罩,迷雾重重。
    让我首先说清一个问题,上述几个函数都可以是从string set到string set的映射,也可以是natual number set到natual number set的映射。也就是说,乍一看来,每一函数所处理的实体都有两种可能。但使用与进制转换类似的技术(e.g.二进制转十进制,八进制转六十进制),我们可以将每一string转换成一个十进制的natual number。因此,从本质上说,string set和natual number set没有任何区别(当然,要格外小心的是,这一结论的前提是我们的alphabet是有限集)。那么,下面我们就可以专心讨论从natual number set到natual number set函数。
    我们注意到,光从上述几个概念本身出发是很难讲清楚它们之间的区别和联系的。我们又注意到,这几个概念都是为computability服务的,而说computability无法绕过的一个实体就是我们的机器Turing Machine。所以,当我们考察这几个概念的时候,不妨转换一下角度,设想自己就是那些如芸芸众生一般的活跃在计算理论中的Turing Machine们。
    我们厌恶冗长的工作,不希望一项工作没完没了,始终做不到头。其实Turing Machine们也是一样,他们最希望的就是能够对每一输入,都能有输出并且停止工作。这是一种天性,于是当我们横竖睡不着,仔细把教材看了半个学期后,终于从字缝中看出字来,满本都写着两个字——“停机”!
    停机怎么个停法?这就变成了理解上述几个概念的关键。第一种停法,就是对于任一个输入,可以隐式的或者显示的获得停机时刻或者停机时刻的上界。这种Turing Machine可是清闲得很啊,一点压力都没有,因为他们知道他们的生活就是这样的了,一切都完全可以预料。他们安享这种生活, 他们做得最多的事情就是碰到一个新任务之后,直接把以前的任务得到结果拿过来用一下。于是他们可以应付所有的任务,对于任何新任务都可以在“确切”的时间内完成。这就是说,对于任一输入x,停机时刻的上界是x的一个函数(实际上是primitive recursive function),可以借助其它同类Turing Machine计算出来;并且可以利用的资源是输入为x'(x'<x)时的输出。这一类Turing Machine所能计算的函数就是primitive recursive function。我们可以将它们叫做“公务员函数”,它们对应Turing Machine的性质可以称为“自信预测”。
    第二种停法。对于任一输入,没法预测停机时刻上界。“噢,天!这个任务我没法确定在什么时候能够做完!”一个Turing Machine在跟作为老板的你抱怨道,“不过不幸中的万幸,我可以向您保证,老板,我一定可以在将来的某时搞定它!”这样的伙计虽然不像前面那一类那样让我们省心,但至少,他们的前途是光明的,他们总能做成事情。我们看到,这一类Turing Machine的心理压力应该是蛮大的。他们的工作方法倒是和第一种差不多,稍有不同的是他们每过一天,都要看看在新的一天里,以前任务所获得的结果的各种组合是否符合某些“条件”。如果符合了条件,他们就会说:“啊哈!搞定!终于可以睡个好觉了!”;如果不符合条件,好觉是不用想了,明天接着试吧。这就是说,对于任一输入x,停机时刻的上界与x无关,不是“确切”的时间,但我们总可以预测在一个小于无穷的时刻t,条件g(x, t)(本身是一个公务员函数,可以自信预测)能够成立。这一类Turing Machine所能计算的函数就是total recursive function。我们可以将它们叫做“小职员函数”,它们对应Turing Machine的性质可以称为“勉强预测”。
    第三种停法。对于一部分输入,勉强预测;而对于另一部分输入,无法预测。也就是说,对于某些输入,根本没法停机。显然这一类Turing Machine的压力是最大的,很有可能永无休止的工作下去。但他们的工作方式和第二类Turing Machine是完全一致的,就是每天不断的重复检查是否符合某些条件。对于某些输入,这些条件永远永远无法满足。唉,挺同情他们的,可谁叫他们挑了有可能永远无法满足的条件呢?这一类Turing Machine所能计算的函数就是partial recursive function。我们可以将它们叫做“可怜的小职员函数”,或者叫做“胡卫函数”。因为我的编程能力太差,要让我编个排序的程序我还可以保证某一天能完成,但让我编个Halfedge Data Structure,我估计是永远达不到“perfect”这一条件的了。于是,这类函数对应Turing Machine的性质可以称为“半预测”或者“编程能力差”:)
    最后一种停法。对于所有输入,永不停机。那个能够把r.e. set打印在tape 2的所谓“枚举机”应该属于此列。这类Turing Machine所对应的函数可以称为“工作狂函数”,性质便是“放弃预测”或“永不休止”。实际上,这一类函数也是partial recursive function,是其中的极端情况,即,可以勉强预测的那部分输入的集合是空集。
    总结一下,对于任意输入都能预测停机时间的是primitive recursive function和total recursive funtion,两者都是total function,只是预测能力不一样。无法对任意输入预测停机时间的是partial recursive function,它是partial function。而这三者最大的相同点是什么呢?就是它们都是computable(可计算)的,也就是“任务可完成的”(枚举机这样的特例对于任务“空集”是可完成的)。
    细心的读者看到这里该问了,照此说来,所以的函数都是partial recursive function咯?哈,其实不然。上面说明的前提都是在Turing Machine世界里,而正如太阳系之外还有其它星系一样,Turing Machine世界之外应该还有好多机器,只不过这样的机器目前还不存在,我们可以称之为“虚拟机”。那些虚拟机实现的函数就是non partial recursive的。纯随机函数就是一个很好的例子。再举一个例子,我们知道K={x | x in Wx}是r.e. set,而K补是non-r.e. set,而且必然存在某一个函数f,f的值域是K补。于是,这个函数f就是无法用Turing Machine计算的函数,就不是partial recursive function。更有甚者,这样non partial recursive function的集合本身就是nor-r.e. set。换句话说,这样的函数要比我们目前能计算的函数多得多,正如同实数集与自然数集的关系一样。
    开头我们提到过,Set的性质可以由Function来描述。那么,关于Set的一堆概念之间又有怎样的区别和联系呢?请看下集——三分集合!
09/10/2006

经典就是经典

    《Introduction of Algorithm》的确是一本经典教材。通俗易懂、深入浅出、层次清晰、举例恰当,看得我是气血翻涌,竟然生出一口气读完的冲动!(这真的是一本理工科教材吗?怎么和小说一个性质。。。)
    以前没上过算法课,但是读过两本中文的算法教材。与这本经典教材相比,简直就是O(1)和O(n^n)的差别,“完全”的无穷次方不在一个量级上。所以建议所有中科院的同学们,别看那本破书啦,赶紧弃暗投明吧!(不知道是不是还是那位看上去很和善的老师教,他对学生不错,但是他写的书。。。)
    老李不知道有没有同感。