機器學習_用樹迴歸方法畫股票趨勢線

xieyan0811發表於2017-11-20

 本篇的主題是分段線性擬合,也叫回歸樹,是一種整合演算法,它同時使用了決策和線性迴歸的原理,其中有兩點不太容易理解,一個是決策樹中熵的概念,一個是線性擬合時求引數的公式為什麼是由矩陣乘法實現的。如需詳解,請見前篇:

《機器學習_決策樹與資訊熵》
《機器學習_最小二乘法,線性迴歸與邏輯迴歸》

1. 畫出股票的趨勢線

 我們常在股票節目裡看到這樣的驅勢線:



 比如說平臺突破就可以買入了,幾千支股票,能不能用程式的方式篩選哪支突破了呢?需要解決的主要問題是:怎麼判斷一段時間內股票的漲/跌/橫盤,以及一段趨勢的起止點和角度呢?



 這裡我們使用分段線性擬合,圖中藍色的點是某支股票每日的收盤價,紅色的直線為程式畫出的趨勢線。稍做修改,還可以輕鬆地畫出每段趨勢所在的箱體,阻力線和支撐線,以及判斷此前一般時間的趨勢。下面我們就來看看原理和具體演算法。

2. 線性迴歸

 先看看線性迴歸(Linear regression),線性迴歸是利用數理統計中迴歸分析,來確定兩種或兩種以上變數間相互依賴的定量關係的一種統計分析方法。簡單地說,二維中就是畫一條直線,讓它離所有點都儘量地近(距離之和最小),用線抽象地表達這些點。具體請見《機器學習_最小二乘法,線性迴歸與邏輯迴歸》。


3. 決策樹

 我們再看看決策樹,決策樹(Decision Tree)決策樹是一個預測模型;它是通過一系列的判斷達到決策的方法。具體請見《機器學習_決策樹與資訊熵》。


4. 樹迴歸

 樹迴歸把決策樹和線性迴歸整合在一起,先決策樹,在每個葉節點上構建一個線性方程。比如說資料的最佳擬合是一條折線,那就把它切成幾段用線性擬合,每段切多長呢?我們定義一個步長(以忽略小的波動,更好地控制周斯),在整個區域上遍歷,找最合適的點(樹的分叉點),用該點切分成兩段後,分別線性擬合,取整體誤差和最小的點,以此類擬,再分到三段,四段……,為避免過擬合,具體實現一般同時使用前剪枝和後剪枝。

5. 程式碼

# -*- coding: utf-8 -*-

import tushare as ts
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 用feature把dataSet按value分成兩個子集
def binSplitDataSet(dataSet, feature, value):
    mat0 = dataSet[np.nonzero(dataSet[:,feature] > value)[0],:]
    mat1 = dataSet[np.nonzero(dataSet[:,feature] <= value)[0],:]
    return mat0,mat1

# 求給定資料集的線性方程
def linearSolve(dataSet):
    m,n = np.shape(dataSet)
    X = np.mat(np.ones((m,n))); # 第一行補1,線性擬合要求
    Y = np.mat(np.ones((m,1)))
    X[:,1:n] = dataSet[:,0:n-1];
    Y = dataSet[:,-1] # 資料最後一列是y
    xTx = X.T*X
    if np.linalg.det(xTx) == 0.0:
        raise NameError('This matrix is singular, cannot do inverse,\n\
        try increasing dur')
    ws = xTx.I * (X.T * Y) # 公式推導較難理解
    return ws,X,Y

# 求線性方程的引數
def modelLeaf(dataSet):
    ws,X,Y = linearSolve(dataSet)
    return ws

# 預測值和y的方差
def modelErr(dataSet):
    ws,X,Y = linearSolve(dataSet)
    yHat = X * ws
    return sum(np.power(Y - yHat,2))

def chooseBestSplit(dataSet, rate, dur):
    # 判斷所有樣本是否為同一分類
    if len(set(dataSet[:,-1].T.tolist()[0])) == 1:
        return None, modelLeaf(dataSet)
    m,n = np.shape(dataSet)
    S = modelErr(dataSet) # 整體誤差
    bestS = np.inf
    bestIndex = 0
    bestValue = 0
    for featIndex in range(n-1): # 遍歷所有特徵, 此處只有一個
        # 遍歷特徵中每種取值
        for splitVal in set(dataSet[:,featIndex].T.tolist()[0]):
            mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal) 
            if (np.shape(mat0)[0] < dur) or (np.shape(mat1)[0] < dur): 
                continue # 樣本數太少, 前剪枝
            newS = modelErr(mat0) + modelErr(mat1) # 計算整體誤差
            if newS < bestS: 
                bestIndex = featIndex
                bestValue = splitVal
                bestS = newS
    if (S - bestS) < rate: # 如差誤差下降得太少,則不切分 
        return None, modelLeaf(dataSet)
    mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
    return bestIndex,bestValue

def isTree(obj):
    return (type(obj).__name__=='dict')

# 預測函式,資料乘模型,模型是斜率和截距的矩陣
def modelTreeEval(model, inDat):
    n = np.shape(inDat)[1]
    X = np.mat(np.ones((1,n+1)))
    X[:,1:n+1]=inDat
    return float(X*model)

# 預測函式
def treeForeCast(tree, inData):
    if not isTree(tree):
        return modelTreeEval(tree, inData)
    if inData[tree['spInd']] > tree['spVal']:
        if isTree(tree['left']):
            return treeForeCast(tree['left'], inData)
        else:
            return modelTreeEval(tree['left'], inData)
    else:
        if isTree(tree['right']):
            return treeForeCast(tree['right'], inData)
        else:
            return modelTreeEval(tree['right'], inData)

# 對測試資料集預測一系列結果, 用於做圖
def createForeCast(tree, testData):
    m=len(testData)
    yHat = np.mat(np.zeros((m,1)))
    for i in range(m): # m是item個數
        yHat[i,0] = treeForeCast(tree, np.mat(testData[i]))
    return yHat

# 繪圖
def draw(dataSet, tree):
    plt.scatter(dataSet[:,0], dataSet[:,1], s=5) # 在圖中以點畫收盤價
    yHat = createForeCast(tree, dataSet[:,0])
    plt.plot(dataSet[:,0], yHat, linewidth=2.0, color='red')
    plt.show()

# 生成迴歸樹, dataSet是資料, rate是誤差下降, dur是葉節點的最小樣本數
def createTree(dataSet, rate, dur):
    # 尋找最佳劃分點, feat為切分點, val為值
    feat, val = chooseBestSplit(dataSet, rate, dur)
    if feat == None:
        return val # 不再可分
    retTree = {}
    retTree['spInd'] = feat
    retTree['spVal'] = val 
    lSet, rSet = binSplitDataSet(dataSet, feat, val) # 把資料切給左右兩樹
    retTree['left'] = createTree(lSet, rate, dur)
    retTree['right'] = createTree(rSet, rate, dur)
    return retTree

if __name__ == '__main__':
    df = ts.get_k_data(code = '002230', start = '2017-01-01') # 科大訊飛今年的股票資料
    e = pd.DataFrame()
    e['idx'] = df.index # 用索引號保證順序X軸
    e['close'] = df['close'] # 用收盤價作為分類標準Y軸, 以Y軸高低劃分X成段,並分段擬合
    arr = np.array(e)
    tree = createTree(np.mat(arr), 100, 10)
draw(arr, tree)


6. 分析:

 演算法的擬合度和複雜度是使用步長,誤差下降和最小樣本數控制的,計算的時間跨度(一月/一年)也影響著程式執行時間。例程中計算的是“科大訊飛”近一年來的股價趨勢,計算用時約十秒左右(我的機器速度還可以)。
 要計算所有的股票,也需要不少時間。所以具體實現時,一方面可以利用當前價格和移動均線的相對位置過濾掉一些股票,另一方面,也可以將計算結果儲存下來,以避免對之前資料的重複計算。


技術文章定時推送
請關注公眾號:演算法學習分享

相關文章