时间序列理论专题之二 时间序列的表达

­

­

      但凡你面对着某个复杂的问题的时候,请将自己的思维集中且仅仅集中在这个问题上,不要考虑与这个问题不相干的东西,不要考虑到这个问题更抽象层面的东西。显然,我们无论需要对某个序列分段,还是要在某个序列中搜索相似序列,这些都只是序列本身的问题,这个序列的业务含义怎样,有无其他业务属性,那是此刻不用关注的问题,你实现了相应的组件,之后在现有的基础上,在业务对象中使用就行了,那个时候你的关注点则是业务系统本身,你无需再考虑时间序列如何搜索之类的问题。

       因此,如何简单的表达一个时间序列,相当的关键。这有助于你降低思维的维度,并提高代码的适用范围。 此处声明一下,本系列中的代码使用C#,基于.net 4.0,代码基本上是在文字中手写,仅为了说明概念,极度的不能保证编译调试通过。毕竟本系列的主要任务,是理解时间序列的基本概念、算法,而不是描述时间序列组件的开发过程。

       先说几句题外的话:

    在时间序列分段的领域,有很多涉及到如何在线分段的理论和算法,这个我认为是毫无意义的。所谓在线划分,是为了应付不断增长的数据,比如日线每个交易日都会增加一条,用户的访问记录每天都会增加许多。因此,一些算法专家,认为时间序列分段的算法,应该考虑将分段的结果记录在数据库中,增加数据仅对增量部分进行划分即可。这种想法,表面上看,确实能够提高运算效率,但实际上非常的迂腐。

    国外确实有很多专家,早期致力于这方面的研究,但请记住他们所处的时空。最近10年来,内存的增长和CPU性能的强劲、硬盘容量的大幅提高和廉价化,导致这种“提高性能”的做法基本上毫无意义。假设序列长度不多于一百万条,我们直接将整个序列读入内存,直接得到其分段线性表示,是非常快捷的。数据多于100万条的话,我们每次从数据库中按关键特征取出一组就行了,比如股票,你取出一只股票的全部日线,比如心电图,你取出一个人的全部数据。如果考虑这所谓的在线划分,既要考虑算法要支持在线划分、又要在编程中考虑如何存储问题,认为的增加了算法的复杂度和容易理解的程度,但并不能带来具体的有意义的收益,只是快1毫秒左右。

    第二点,则是如何为时间序列设定专用的索引结构,以方便在数据库中直接搜索相似记录,这个更为复杂,相关的论文、算法也非常的多,但是如同第一个问题所说,也是毫无意义的,仅能够快不到1毫秒,在最近的三年和今后,继续从事这个专题研究的人,我认为只能授予一个荣誉称号,“木乃伊”。

    第三点,是有很多专家研究多变量的时间序列,这实际上是一个抽象程度的问题,你如果有一个很好的算法处理一个一维的时间序列,运用这项工具,直接在外部处理不同的指标序列来实现多维分析就行了。所以做这方面研究的专家,大体上是缺乏抽象思维的人,这项工作也毫无意义。

    从我个人的角度,在自己的笔记本上,4G内存完全可以将中国股市最近二十年所有的股票日线数据一次载入内存,不到500M不是吗?如果数据量更大,分类载入即可。这样,时间序列分析的所有开发工作,实际上相当单纯。

    由此引申到,我们很多使用Asp.net、Java开发Web应用的,经常因为内存、数据库、保留中间结果之类的事情,将程序设计的分外复杂,很多的时候,也不过是如同上面所说的快了1毫秒而已,付出的代价是:1、程序变得复杂,以至于更容易出现Bug,难以持续维护。2、程序变得复杂,以至于和团队成员的合作增加沟通难度。

    完全没有必要,记住我们今天的硬件环境,教科书是十年前的老人编写的,即使是Web应用要处理几十万人并发访问,也无非在缓存方面注意一些就行了。

    我个人一直强调开发中的两个特性,最重要的一点是“简单”,尽最大的可能简单,你略微增加的一点复杂度很可能蔓延到项目中的其他领域、其他步骤,以致于最后的开发、团队协作、维护方面的代价远超想象。次重要的一点是“快”,多数情况下,你所有的工作在内存中做岂非更快?

­

    下面才说到正题:如何表达一条时间序列。

    我在许多不同的论文中看到不同的表达方法,最笨的是同时保留多个序列,次笨的是<时间,数值>构成一个对象这种。我们在表达一个序列的时候,要注意与具体的业务分离、与特定的时间分离。这样,在做时间序列相关的部分的开发时,你面对的只是一个单纯的按序排列的数字,思维会更简单。

    实际上,一个数组就能够表达一个时间序列,“时间”问题,比如在心电图中,假设每隔0.1秒采集一条数据,则此人的心电图序列的数据中就无需包括时间记录。而股票的日线,则需要包含一个日期字段,描述这个数组中的第一条、第二条分别是哪一天的数据,当然,这个数组本身也是按照日期排序的,后面的元素肯定比前面的元素晚。

    但我们多数情况下,仅仅关心时间序列中各个元素的顺序,而不关心具体的时间是哪一天哪一秒。同时,为了将关注点集中在时间序列的处理上,我们仅通过一个接口来提供一个时间序列:

    public interface ISeries

    {        

        double this[int i] { get; }

        int Count { get; }

    }

    使用这个接口,则时间序列的后续算法中,都无需考虑具体的业务对象。比如心电图的存储数据,假设为如下的结构:

    public class Heart

    {

         DateTime Time;

         double Value;

    }

    那么,我们实现一个类,用来保存某个人的心电图,这个类实现ISeries接口:

    public class HeartSeries:ISeries

    {

         //此人的姓名或其他与业务相关的信息

         public string Name {get;set;}

­

         //此人的心电图序列

         List<Heart> Hearts{get;set;}

         //实现ISiries接口

         public double this[i]

         {  get

            {

                return Hearts[i];

             }

          }

          public int Count

          { get

             {

                 return Hearts.Count;

             }

          }

    }

­

    现在就看到了,HeartSerial类就存放了一个人的心电图序列,相关的业务处理,包括如何存储到数据库、如何从数据库中找出某人的心电图、获取找出今年所有人的心电图,这个人的名字叫什么,这些业务信息,我们的时间序列处理的框架,都不关心,这交给具体应用程序去处理。

    我们仅仅在ISeries中存储了一个数组和一个长度信息,没有保存时间信息,若需要获取某点的时间,在外部使用下标访问原来的业务对象数组即可获取。因此,这样表达时间序列,我们仅仅面临着两个东西:这是按时间顺序保存的第几个元素?这个元素的值是什么?

­

       当然,对于一个业务对象要处理多个时间序列的情况,比如股票日线要处理收盘价、成交量、收盘价的5日移动平均值这些东西,使用这个接口就能够很方便的处理。

    我们当然会创建一个类似HeartSeries的QuoteSeries类,存储一只股票的全部日线,这个类实现ISeries接口,但同时设置一个参数,表示其返回何种指标,是收盘价还是成交量?在this属性中,判断这个参数,决定返回那一个指标,在Length属性中,判断是哪一类指标,返回相应的长度(比如要返回5日均价,则长度要比原始对象数组的长度少4,因为从第五天开始才存在5日均价)。这样,只要设置相应的属性,时间序列处理的框架,就会得到相应的时间序列。无需拷贝到另外一个数组中去,也不会涉及到具体的时间和具体的业务问题。

      到现在为止,未来的时间序列处理部分,就这么告诉我们“给我一个ISeries,我帮你压缩它、我帮你搜索相似序列”。

­

posted @ 2011-01-11 17:09  玄歌  阅读(4480)  评论(4编辑  收藏  举报