電力現貨預測精度高不等于交易收益好:改造決策樹
枯藤老樹昏鴉,小橋流水人家。語文老師給我講這首詩的時候,說這個只是名詞的堆砌,但給人一種直指內心的感覺。
——以上是這篇技術文章的核心思想。
(來源:微信公眾號“蘭木達電力現貨”作者:王超一)
一.兩種決策樹的差異對比
在講正題之前,先簡要講一下 scikit-learn(以下簡稱sklearn)決策樹的主要實現邏輯。另外,這一塊代碼位于 sklearn/tree 目錄下,而 ensemble 里的隨機森林依賴 tree 目錄代碼。
首先,構造一棵決策樹有兩種邏輯,一種是深度優先搜索DepthFirstTreeBuilder,即每次找到最優的分裂節點,然后在左右子樹中按同樣邏輯構建;另一種是類似寬度優先搜索的貪心策略BestFirstTreeBuilder,之所以說是類似寬度優先搜索,是因為每次都找到當前的最優分裂,而不是按層分裂,即把寬搜用的隊列換成優先級隊列。用哪一種方式建樹取決于是否指定了最大葉子節點數T,如果指定了就用BestFirstTreeBuilder,因為這樣即使無腦搜索也是O(T2)復雜度,當然sklearn用了基于堆的優先級隊列可以將復雜度降低為O(TlogT)。
其次,除了構建樹的方法外,剩下的邏輯核心在于 Splitter, Splitter也保存了原始的X、y、 sample_weight,Splitter主要有4個方法:
1)init 方法,保存原始X、y、sample_weight,生成一個對樣本(行)的索引表,并利用X和行索引表和一個特征的緩存數組生成一個Partitioner 對象;
2)node_reset方法,利用 y,sample weight和行索引構造 Criterion 對象,分類樹的熵、基尼系數,回歸樹的MSE,MAE都是在這個Criterion的實現;
3)node_impurity方法,調用self.criterion.node_impurity計算一個節點的純度,這個數越小越好;
4)node_split 方法,本方法是核心,有兩個更基礎的方法,分別為 node_split_best 和 node_split_random,這兩個方法都調用了 Splitter、Partitioner、Criterion 3個對象,同時有很多性能上的優化,如在行索引上操作,對快速排序的優化,對常量特征的優化等。
上邊這一段看不懂沒有關系,大致就是說,sklearn用cython寫了個樹的構建,并把api暴露給python,然后每個關鍵點都有幾種選擇,這些一般都傳入字符串參數,然后sklearn有內置的字典來找到對應的類,這些類用 Cython 實現來確保效率。
二.電力市場交易的優化目標是收益而不是偏差
問題在于,拿電力交易為例,我們最終的目標是收益最大化,一般來說這個收益取決于當前價格和未來價格之差,后文簡稱價差,如果我們構造一個回歸樹,y為價差,那么可以選擇 MAE、MSE或其變種作為一個純度的度量,這樣就是把收益最大化問題轉換為價差的回歸了。這個數學建模的問題在于,雖然邏輯上更優的價差預測會帶來更好的策略收益,但實踐中也不絕對,有可能把MAE降低了,策略收益沒有提高反而降低了。
能否給出一個直面目標函數(策略收益)的決策樹呢?
首先,回過頭再看一下決策樹為我們提供了什么,決策樹給出了一些條件下的樣本集合,這些條件是分層的,可以定義這個樣本集合的一個指標,代表這個分層好不好,即決策樹是否恰當劃分。那么這個指標就可以直接定義為這個集合下按照某種交易策略下的利潤。
比如,我們有一種策略,只看預測價差的符號,不論價差大小,都申報同樣多的電量,那么這個集合的利潤就是所有價差之和的絕對值。
其次,還得重新實現proxy_impurity_improvement和children_impurity這兩個方法,限于篇幅原因不再贅述。這里面有個坑,這個純度即node_impurity 越小越好,那么我們利潤最大化得乘以-1,然而在sklearn中,默認純度為正數,我們開頭講的樹構建過程中,如果純度小于一個極小正數時認為當前節點已經是葉子節點了,所以得改樹構建的邏輯,具體而言就是加上3個#注釋掉3行即可。
經過這樣的改造,在山東日前交易中,回測利潤是MSE的3倍或更多。
接下來,我們更進一步,注意到我們這里要用-1倍的利潤作為節點純度,對新能源電站,計算利潤需要更多的數據,比如裝機容量、實際發電和預測發電,而實際發電在sklearn框架中是一個尷尬的存在。
我們不能把這個數值當做X,因為這個是個未來數據。然而在回歸中,如果把這個數當做y,在代碼邏輯上勉強能跑通,但會在代碼中把真正的y與輔助性的數據混在一起,代碼結構化變差,比如在predict會得到一個二維矩陣。所以我們嘗試了修改 sklearn 的 api,分別給 BaseDecisionTree.fit、TreeBuilder.build、Splitter.init, Criterion.init 中增加相同的輔助計算節點純度的參數,加上這個機制,就可以任意修改(節點純度)目標函數了。
自定義的目標函數讓我們可以直面交易利潤,把價差預測的學習直接升級到交易策略的學習,然而,這也給我們提出了更高的決策樹后處理要求,對策略的過擬合也得有更多的控制手段。
最后吐槽一下,Pycharm 對 Cython 的支持太差了,右邊一堆紅(提示代碼錯誤),代碼折疊也有問題,可能逼著我們去用 vs code 了。
責任編輯:葉雨田