時間序列理論專題之二 時間序列的表達
-
-
但凡你面對著某個復雜的問題的時候,請將自己的思維集中且僅僅集中在這個問題上,不要考慮與這個問題不相干的東西,不要考慮到這個問題更抽象層面的東西。顯然,我們無論需要對某個序列分段,還是要在某個序列中搜索相似序列,這些都只是序列本身的問題,這個序列的業務含義怎樣,有無其他業務屬性,那是此刻不用關注的問題,你實現了相應的組件,之后在現有的基礎上,在業務對象中使用就行了,那個時候你的關注點則是業務系統本身,你無需再考慮時間序列如何搜索之類的問題。
因此,如何簡單的表達一個時間序列,相當的關鍵。這有助于你降低思維的維度,并提高代碼的適用范圍。 此處聲明一下,本系列中的代碼使用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,我幫你壓縮它、我幫你搜索相似序列”。
-

浙公網安備 33010602011771號