1:本文主要討論將神經網路的理念運用在股價預測或估值上;
2:本文主要為理念的講解,模型也是筆者自建,因此不涉及任何主流機器學習框架,如果有讀者是為了學習使用thenserflow,sklearn或者PyBrain這些模塊就不必往下看了;
3:出于個人原因筆者暫不打算公開具體模型,函式及引數,僅使用部分簡單代碼及回測結果以作展示;
4:本文主要資料均通過Tushare(ID:444829)金融大資料平臺介面獲取;
5:筆者希望搭建出一套交易體系,原則是只做干貨的分享,后續將更新更多模塊,但作業學習之余的閑暇時間有限,更新速度慢還請諒解;
6:文中假設與觀點是基于筆者對模型及資料的一孔之見,若有不同見解歡迎隨時留言交流;
7:模型實作基于python3.8;
目錄
1. 論玄學
2. 什么是神經網路
2.1 認識世界的程序
2.2 神經網路正向傳播
2.2.1 權值的作用
2.2.2 激活函式存在的必要性
2.3 反向演算法
2.3.1 損失函式
2.4 梯度下降演算法
2.4.1 學習率的調整
2.4.2 全域最優解和區域最優解問題
2.5 神經網路偏置項
3. 賦予神經網路金融思維
4. 模型實作
4.1 資料準備
4.2 構建網路
4.2.1 前向運算
4.2.2 反向演算法
4.3 回測
5. 寫在后面
1. 論玄學
相信不少朋友在看了筆者上期發布的股價隨機游走的文章后紛紛表示玄學,前兩天筆者看見了更為玄學的東西,直接上圖(關鍵部位打碼,感興趣的朋友去搜索都能搜到):

以筆者的經歷,隔壁老纏師日夜“說纏論道”,樓上黑盤子團隊打著燈籠在韭菜面前用陰陽八卦圖找買賣點都沒能嚇退我,看到這幅圖的瞬間虎軀一震, 這,,這莫非就是“天干地支擇時,五行相生相克選股”大法,而且還是出自某券商首席,
不知和它比起來筆者上期經過數學推演的隨機游走還算不算得玄學? 如果您的答案還是玄學,那么本期筆者將帶來經過數學邏輯嚴密推演的神經網路模型,在基本面和技術面的加持下徹底摘下玄學的帽子,
2. 什么是神經網路
2.1 認識世界的程序
對于神經網路,度娘給出的解釋是介樣的:

看了半天跟沒看一樣,,
對于神經網路筆者有自己的理解,它其實是仿生學,即通程序式來模仿生物認識世界的程序,使得程式能自己“思考”,
筆者記得本科時候數學老師是位密碼專家,一次上課時他語重心長的說:“萬物皆可函式”, 當時筆者上課只感覺:

后來筆者在學習神經網路的時候明白了這句話的深意, 神經網路網路就是將現實存在的事或物以函式的形式抽象表達出來,下面筆者舉一個例子:
這是一只橘貓

但我們為何知道這是一只橘貓?
這就涉及到特征的辨識,身體形狀,顏色,眼睛大小,鼻子,胡子形狀,總之這只動物有著橘貓的所有特征,因此大腦將它分類成腦中存在的一個標簽——橘貓,
事實上,對一個初生小嬰兒來說,大腦就是一片白紙,橘貓所有的特征都是我們人類通過后天學習得到的,第一次給小嬰兒看這張橘貓圖并且告訴他這是一種叫橘貓的動物, 在腦中形成一個橘貓的印象, 比如耳朵尖尖的,腦袋圓圓的樣子,第二次再看一模一樣的圖,橘貓這個標簽就自然而然地與這張圖對應上了,
那么再看這張圖:

盡管耳朵被遮住了,我們還是能分辨出它還是一只橘貓,這是因為我們已經不是單純靠耳朵來辨認了,可我們的小baby只認識上面那張橘貓圖,當把這張圖給他看的時候我們可以誤導他說這是一只英短或者橘狗,如圖一,于是就有了趙高指鹿為馬的典故,,

圖一:圖片單個特征
但如果我們依然告訴小baby這是一只橘貓,而且可以隨機給他看很多橘貓耳朵被遮住和不被遮住的圖,小baby或許會明白耳朵是不是尖尖的貓耳朵不重要,關鍵是這只貓要萌,

好吧,關鍵是鼻子,嘴巴,顏色,體態也很重要, 如圖二:

圖二:多個特征
想象一下,當重復成百上千張不同的橘貓圖,通過不斷提取特征資訊,下次再給小baby看一張陌生的橘貓圖時,他自然而然就把橘貓這個標簽匹配上了,我們小時候看的很多識字卡,看圖說話的小游戲其實一直在重復這個程序, 可以說世上本沒有橘貓,只是人們給他打了個標簽一樣的名字便成了橘貓,
2.2 神經網路正向傳播
上一個小標題只是解釋了特征量對辨識準確度的影響,但大腦內部是如何作業的?它就像一只black box, 我們依然無法建立模型,
如圖三,生物的神經網路結構大家或許見怪不怪,一個細胞周邊被無數絲線一樣的東西連接到其它細胞, 一個成人的大腦中有上千億個神經細胞相連,

圖三:生物的神經網路
人工神經網路是一門仿生學, 心理學家McCulloch和數學家Pitts在1943年參考了生物神經元的結構,發表了抽象的神經元模型MP, 既然自然界中的生物神經網路長成這樣, 我們何不人工造一個?反正一個圓圓的細胞加一些突觸,為了簡化,就畫上兩個突觸, 一個負責輸入信號,另一個輸出信號,由于神經元是要進行“思考”的,筆者將神經元分成兩個半圓,白色表示輸入,橙色表示經過處理后的輸出,于是我們有圖四:

圖四:一個神經元
正如筆者老師所言,“萬物皆可函式”,輸入到輸出的程序即是神經元處理資料的程序,可以用函式y = f(x)表示, 例如:
橘貓 = f(耳朵,鼻子,顏色)
我們也稱這個函式叫激活函式, 可以簡單理解為將這一堆資料激活得到橘貓的效果,
通過輸入耳朵,鼻子和顏色的資料,經過這個函式計算得到橘貓這個結果,但這顯然也太粗糙了,補上幾個規定:
1:輸入資料——藍色圓表示
2:神經元命名(輸入):

3:神經元命名(輸出):

3:輸入資料為輸入層;中間神經元為隱藏層
4:最后一層為輸出層,以白藍色圓形表示
于是可以豐滿一下網路,如圖四所示:

圖四:兩個神經元
完善到這里,這個神經網路還只有形狀,沒有靈魂, 神經元之間的連接僅僅只是一條“虛”線線,只有賦值才能化虛為實,
2.2.1 權值的作用
生物神經元之間傳遞信號是通過神經信號, 這是一種電信號,度娘給出的解釋是:

好吧,我們不是研究生物,筆者的理解是,當電信號越強烈,刺激越大,說人話就是被100度開水燙和60度水燙的區別, 如果用數學語言表達就是權重, 當被100度開水燙,傳遞一個非常大的權重值,
那么我們給剛剛圖四種中的神經網路加上權重,再豐滿一下,多加一層隱藏層,約定神經元權重表示如下:

把代號也標上,于是有圖五:

圖五:一個兩層的全連接網路
由于神經網路特點,中間層與層之間的神經元都是依靠權重連接起來的,因此我們也稱之為全連接層,
至此,整個網路就算構建完善了, 計算也變為可能, 整個網路的運算規則是:下一層某個神經元的輸入值等于上層所有神經元輸出值乘以各自的權重,
第一層L1:
![]()
簡寫出來如下,后面統一這樣簡寫表示,:

第二層L2就會稍微復雜一些,因為L2層每一個神經元都會接收L1神經元傳入的值, 神經元的值變成多層復合函式如下:

最后一層輸出層如上類推,嵌套更多一層函式而已,寫出來太長,就用上層輸出直接簡寫:

至此,神經網路前向計算全部完成,但這樣依舊有很大問題,如何得知計算出的結果就是橘貓呢,規定好權重,帶真實數值進去算一通說不定算出個哈士奇, 這樣算出的結果完全沒有意義,下一部分將通過反向運算解決誤差的問題, 也是模擬思考程序的關鍵步驟,
2.2.2 激活函式存在的必要性
神經網路運算規則是:下一層某個神經元的輸入值等于上層所有神經元輸出值乘以各自的權重, 先說結論,如果沒有激活函式,整個神經網路其實只相當于一個線性函式,多層及多個神經元存在的意義就沒有了,
一個很簡單的例子: 某層神經元計算如下f(x) = 2x + 1, 下層神經元z(x) = 2 f(x) + 1, 由于線性函式的性質,其實z(x)就等價于4x + 3, 也就是說完全可以用一個線性模型代替這個所謂的神經網路, 于是這樣的神經網路就算構建100層也依舊只相當于一個線性模型,
因此,一般情況下構建神經網路的激活函式一定要是非線性的, 筆者后面將用老掉牙的sigmoid函式做演示,不過前人也設計出許多不同型別的激活函式,例如tanh, relu函式, 這些函式被廣泛驗證并運用在各種模型中, 但筆者認為沒有什么one size fits all的最優函式,很多時候需要根據實際問題自行構建屬于這個模型的激活函式,

2.3 反向演算法
如圖一情景所示,當小baby看見一只從來沒見過的貓耳朵時,他其實很難判斷出是不是一只橘貓, 當大人告訴他這依舊是一只橘貓后會發生什么? 顯然小baby的關注點不再局限于耳朵上,他會看顏色,體態,花紋等等其它特征, 根據這些特征判斷是不是橘貓,這一程序中神奇的事情發生了——不依靠耳朵識別,轉化為數學語言不就是耳朵的權重沒有了嗎? 而顏色,體態等其它特征形成更大的權重,
上述這一程序正是反向演算法的精意所在, 即:正向計算——對比真實資料——得到誤差——反向思考什么地方出了問題——進行權值調整,
為了知道是哪個神經元權值造成了多少的誤差,我們借助偏導數的手段來處理, 這個視頻講解了梯度的幾何意義,可以幫助理解后面的梯度下降演算法,
5分鐘理解偏導數和梯度 - 數學知識的影片決議https://m5.baidu.com/video/page?pd=video_page&nid=3337295596859546898&sign=6619531904245224999&word=%E5%81%8F%E5%AF%BC%E6%95%B0&oword=%E5%81%8F%E5%AF%BC%E6%95%B0&atn=index&frsrcid=4185&ext=%7B%22jsy%22%3A1%7D&top=%7B%22sfhs%22%3A1%2C%22_hold%22%3A2%7D&sl=2&fr0=B&fr1=B&ms=1&lid=6820456397095973891&_t=1641313540826
2.3.1 損失函式
計算誤差的程序其實有很多種方式,最簡單的真實資料于神經網路跑出的資料做差, 也可以求平均絕對離差;由于絕對值不好進行求導,也可以平方一下,轉化為方差;如果多對資料也可以使用協方差, 亦或者根據實際模型需要構建函式,下面以方差進行簡單演示:
先把最后一個輸出的神經元拎出來展示細節, 如圖六所示, 為了量化出某個權值對最后結果造成了多少誤差,這里采用偏導數,由于誤差值是權值經過了激活函式, 又過了一個損失函式計算得到,因此誤差對權值的偏導數需要采用鏈式求導的方式,
圖六: 損失函式對權值求偏導
2.3.1.1 首先是輸出層神經元對隱藏層神經元權重的偏導:
如圖六, 對l2層第一個權重求偏導有:

其中:


上面這步其實就是對sigmoid函式求導

最后其實就是對上層加和計算下一個神經元輸入值的那個線性函式求導,有點繞, 其實就算:

將以上拆分的幾個式子相乘即可得到誤差對權重的鏈式求導結果,但這個結果只是第一個權重的,還要如法炮制計算第二個權重, 計算都是一樣的,換換角標而已,這里就不做過多展示,
2.3.1.2 隱藏層權重的偏導
即計算l1層w1和w2, 這里其實就算套用上面2.3.1.1里面的鏈式求導,只是再多加一層函式求導,

這個式子其實依舊是上面的鏈式求導后面多加一個神經元的運算, 唯一不同的是式子第三項不再是對權重求導,而是對神經元輸出值求導,原因依舊是正向演算法的線性運算:

這個式子對神經元求偏導就只剩下權值了, 而對權重求偏導則剩下神經元輸入值,
剛剛那個長式子后面兩項依舊是熟悉的權值求偏導,
如法炮制可以分別求出誤差對剩下三個權重的偏導,
2.3.1.3 隱藏層神經元的偏導
上面一個式子筆者提到對權重和對神經元求偏導是不一樣的,這里筆者還要加一個神經元的偏導,尤其是前面幾層隱藏層的,與剛剛的不同之處在于前面幾層隱藏層所有神經元都是全連接的形態,因此 計算偏導時下層所有神經元所傳遞的誤差都要全部計算, 筆者依然把神經網路一部分拆出來展示, 如圖七,

圖七: 圖五神經網路的一部分
可以看到L1層的輸出o其實是與下一層所有的兩個神經元連接起來的,因此o的誤差其實來自于l2層神經元a1和a2, 因此計算偏導就不能像輸出——隱藏那樣只計算一個連接,


這個式子比較長,前半部分走的路徑是圖七中:L3層o1——L2層o1——L2層a1——L1層a1
后半部分則是:L3層o1——L2層o2——L2層a2——L1層a1
兩條路徑殊途同歸,都指向L1層a1,
同樣的,如法炮制可計算L1層a2的誤差貢獻,
如果一層有多個神經元,都需要用上面的幾個演算法依次計算,如果有很多層,很多個神經元,對計算機算力就逐漸有更高要求了,至此,整個反向計算就介紹完了, 但還有個小尾巴,我們只知道誤差是多少,如何根據誤差將社交網路中的權重調整到最優呢?
2.4 梯度下降演算法
上一節已經知道了偏導數,即每個權重對誤差的貢獻, 由于神經網路最終目的是準確的計算出真實世界存在的值,我們有了已經誤差,和每個權重對誤差的貢獻, 那么下一個不做自然而然就算把誤差貢獻減小,可以這樣說,當沒有誤差時神經網路所計算的值 = 真實世界的值, 既然已經得到權重對誤差貢獻,只要反向的減去這個貢獻,讓權重往更接近真實值的那個方向調整, 數學處理上即是給偏導加個負號再減去權重, 但光加個負號其實就是默認以1為步長進行反向調整,這顯然太粗糙, 于是人們在符號后面再加上個系數,例如-0.1, -0.01或者-0.05來限制或放大每次進行調整的大小,
神經網路在尋解的程序其實就是函式在尋解的程序,只有當權重取到某個值時,誤差才是最小的,偏離這個值則會使誤差變大, 因此可以用一個拋物線來刻畫這一狀態,

圖八:梯度下降演算法簡圖
如圖八,橫軸代表網路中權重與最佳擬合權重的差值,縱軸代表神經網路計算出的值與真實值的誤差
權重設定得越離譜, 真實值與神經網路計算出的輸出值差距也就越大, 我們的目標即是根據上節求出的偏導值調整權重, 不斷縮小誤差, 如圖中紅色階梯所示,
2.4.1 學習率的調整
既然我們是根據真實值進行調整,那么下面該解決一次該調整多少的問題,上節中提到,在求出的偏導值前加上一個系數便能控制每次調整的步長, 我們稱這個步長為學習率 , 如果系數設定過大,就是我們俗稱的步子邁大了,就會一步跨太遠距離,如果這個距離導致輸出結果與真實值差距呈現擴散的結果,即誤差越來越大,則稱為梯度爆炸, 如圖:

圖九:輸出誤差越來越大
圖八的梯度下降就比圖九好很多,但設定過小的步長又太謹慎,如果是一個很大的網路就需要學習很多次才能逼近真實值, 大大降低了網路運行效率,筆者認為,對一個比較的的神經網路而言,最優的梯度尋解演算法是自適應調整,當距離真實值較遠時設定一個較大的學習率,逐漸逼近真實值的程序中,慢慢降低的值,不過對于一個小型網路,設計這樣的演算法其實可有可無,因為小網路對算力要求較低,
有了學習率后,我們在把學習率帶入上一節的公式中,計算更新的權重:

展開得:

依舊如法炮制計算每一個權重連接,更新完整個網路的權重后再用資料跑一個神經網路輸出值,將這個值與真實值比較,得到誤差,根據誤差再次進行反向運算, 然后再正向運算,得到誤差,再反向運算, 如此回圈,直到真實值與輸出值一致,這個程序要重復多少次往往是神經網路大小,尋解演算法決定的, 少則幾十次,多則成千上萬次,
2.4.2 全域最優解和區域最優解問題
在復雜的網路中,尋解演算法所得的最優解或許站在全域來看并不是最優, 之前圖八只是個很簡單的單谷形態,實際問題中可能會遇上多谷形態,或許神經網路下降到某個谷底逼近了最優解,但其實比這個谷底還要低的地方可能還有一個谷底, 這時需要判斷究竟哪個谷底才是全域最優解, 也并非是越低越好,看圖說話, 如圖十,一般只有復雜的網路演算法才會遇到這樣的問題,筆者暫不做討論,

圖十:區域最優解與全域最優解
2.5 神經網路偏置項
之前介紹正向演算法筆者沒有談偏置項的問題,大家喜聞樂見的線性模型往往都是f(x) = ax + b, 由于神經網路用到了線性模型,這個b自然而然就被帶進了神經網路里, 在線性模型中我們叫b截距, 神經網路中稱為偏置, 線性模型加上截距后給予線性問題更大的可操作空間,因為如果沒有b, f(x) = ax 一定是一條過原點的直線,現實情況下很大問題的解都不是一條過原點的直線所能描述的,往往需要向上或向下平移,將偏置項帶入之前的網路結構中則如圖十一:

圖十一:帶偏置項的神經網路
隱藏層的輸入是幾個線性模型相加,因此每個神經元其實只要帶一個偏置項就可以了,計算也非常簡單,只要在神經元偏導的基礎上把最后一項改成帶偏導的線性函式,筆者就不做推導了,
3. 賦予神經網路金融思維
之前說了一通亂七八糟的公式和堆倒只為證明一件事,神經網路的運算是非常講究邏輯的一套方法, 筆者將本期題目取名為全能大明星正是由于神經網路可以思考的這一特性, 通過不斷的學習新資料,神經網路不斷自我更新,自我進步,可以這樣說,通過不斷學習新資料,你的網路將不會落后于時代,(前提是網路結構,演算法也能適應上資料的發展哈)
得到這樣一套理論我們可以賦予它金融的思維, 通過學習市場中的資料幫助我們進行交易,擇股等等,有人或許不這樣想, 筆者以前聽過一個百度的演算法工程師說:“神經網路其實對股票市場的很多資料會感到很困惑”, 舉一個很淺顯的例子,市場爆炒ST股, 白馬藍籌統統躺下,這用基本面分析的神經網路去算收益恐怕,,股票市場是很復雜的一個整體,受到影響因素太多,很多時候不是業績好公司股價才上去, 而市場中一些東西用量化手段極難解釋,例如預期,短期情緒波動,這時候神經網路會感到很困惑,明明各項指標都非常不錯,但股價卻是下跌的,
對于這點,筆者想說的是神經網路進行的思考至少是理性的, 誰沒因為情緒追漲殺跌過,但機器是沒有感情的, 它所遵循的只是一套演算法,其次,筆者認為近幾年的a股正往好的方向發展,在如今4k+公司,全面注冊制不斷深入的大環境下,僧多粥少的格局依舊是未來的主旋律, 即是大盤連陽也不見得所有板塊能萬箭齊發,千股漲停,筆者的老師說過,股票市場短期是投票機,長期是稱重機,偶爾的垃圾股爆炒注定只能曇花一現,投資者往往需要很強的投研能力才能獲得長期超額回報,筆者認為神經網路正是一個具備理性投研能力的理性“投資者”, 通過學習基本面類指標,不斷完善演算法,提高勝率,基本面模型將成為投研中的行業挖掘機幫助人們做決策,
模型設計思路:構建資料集,匯入神經網路模型訓練,得到結果, 下面筆者將用少量資料構建一個3層的簡單網路做展示,
4. 模型實作
匯入必要模塊
import tushare as ts
import random
import numpy as np
import pandas as pd
4.1 資料準備
首先準備資料, 本文采用Tushare金融大資料介面,非常方便,給筆者省下大把寫爬蟲的時間,筆者就隨便挑三個指標吧:ROE,PE, PB, 實體化介面,需要輸入自己的密鑰,將相關資料請求放在靜態方法下面方便重復呼叫,
pro = ts.pro_api("token") # 填自己的token
class report:
def __init__(self, ROE, PE, PB, pct_chg):
self.ROE = ROE
self.PE = PE
self.PB = PB
self.pct_chg = pct_chg
@staticmethod
def daily(code, start_date, end_date):
daily = pro.daily_basic(ts_code=code, start_date=start_date, end_date=end_date,
fields='ts_code,trade_date,close,total_mv,pe,pb')
return daily
@staticmethod
def roe(code, year):
indicator = pro.fina_indicator(ts_code=code, period=str(year) + "1231", fields='roe') # 最后一天表示年報, ocfps
roe = float(indicator[:1]["roe"])
return roe
@staticmethod
def trading_data(code, start_date, end_date):
trading_data = pro.daily(ts_code=code, start_date=start_date, end_date=end_date)
return trading_data
如果要請求其它財務型別指標也可以請求介面,例如資產負債表, 損益表, 通過輸入引數可以回傳相關指標, 非常方便,
BS = pro.balancesheet(ts_code=code, start_date=start_date, end_date=end_date,
fields='ts_code,ann_date,f_ann_date,end_date,report_type,total_assets,undistr_porfit,total_hldr_eqy_exc_min_int,'
'total_cur_assets,total_cur_liab, total_liab,total_hldr_eqy_exc_min_int, surplus_rese', end_type = 4)
IS = pro.income(ts_code=code, start_date=start_date, end_date=end_date,
fields='ts_code,ann_date,f_ann_date,end_date,report_type,'
'total_profit,revenue,n_income, fin_exp_int_exp', end_type = 4)
接下來定義下請求資料的時間點,需要注意的是財務資料和交易資料是不同步的, 財報公布是滯后的,年報于下一年4月底之前全部披露,Tushare period= “20210431”代表年報, 因為是做預測,這里請求的交易資料年份加一,
def data_craw(year, code, data_set):
# ___________________規定日期節點___________________
period = str(year)
end_date = str(year + 1) + "0431"
start_date = str(year) + "0101" # 財務資料時間點
start_trading = str(year+1) + "0101"
end_trading = str(year+1) + "1231" # 交易資料時間點
實體化交易資料,計算年漲幅;獲取ROE;獲取年均pe, pb, 最后輸入年份,公司代碼,實體化函式就好了,
trading_data = report.trading_data(code, start_trading, end_trading)
price_beg = trading_data["pre_close"][-1:].values
price_end = trading_data["close"][:1].values
pct_chg = (price_end - price_beg) / price_beg
roe = float(report.roe(code, year-1)) / 100
market = report.daily(code, start_trading, end_trading)
pe = market["pe"].mean()
pb = market["pb"].mean()
data = report(period, float(roe), pe, pb, float(pct_chg))
return data
data_collect = []
data_craw(years, code, data_collect)
4.2 構建網路
這里筆者沒有采用矩陣,而是定義物件的方式算, 這是一個悲傷的故事,不說了,,
4.2.1 前向運算
定義神經元類物件,這里權值筆者采用Xavier權值初始化方法初始權重,Xavier是個人,也不多說了,感興趣自行百度吧,
class Neural:
def __init__(self, neural, bias, weight, data): # 第幾個神經元, 偏置, 權重,資料集
self.neural = neural
self.bias = bias
self.weight = weight
self.data = data
self.in_value = sum(np.multiply(self.weight, self.data)) + bias
self.out_value = self.sigmoid(float(self.in_value))
def sigmoid(self, x):
sig = 1 / (1 + np.exp(-x))
return sig
class Neu_layer:
def __init__(self, layer):
self.layer = layer # 第幾層編號
def xavier_init(self, in_num, out_num): # xavier 權值初始化方法
A = - (6 / (in_num + out_num + 5)) ** 0.5
B = (6 / (in_num + out_num + 5)) ** 0.5
std_div = (((B - A) ** 2) / 12) ** 0.5
return std_div
def lay_data(self, numbers, data, input_neur_num, output_neur_num):
neural_list = [] # 放置每層神經元的資料物件
for i in range(numbers): # numbers-多少個神經元,給每個神經元創一個物件
w = [] # 跑完一個神經元初始化權重
std_div = self.xavier_init(input_neur_num, output_neur_num)
for a in range(len(data)):
w_value = np.random.normal(loc=0, scale=std_div, size=1)
w.append(float(w_value)) # 多個權重一一對應
bias = float(random.uniform(-0.1, 0.1)) # 為該層每個神經元設定一個偏置引數
a = Neural(i + 1, bias, w, data) # 第幾個, bias, weight, 單個樣本的一個特征值
neural_list.append(a)
self.neur_objs = neural_list
只要在lay_data函式的第二引數放每層構建幾個神經元就好, 后面幾個引數筆者定義的,大家可以自行改, 關鍵是還要讓本層神經元的運算結果要當作下一層的輸入, 筆者就不放這部分代碼了,寫的有些亂,大家可自行增加,
4.2.2 反向演算法
class backward:
def __init__(self, forward_obj, actu, η): # 傳入構建的神經元物件串列
self.forward_obj = forward_obj
self.o_h(actu, η)
def o_h(self, actu, η): # 輸出層傳遞到隱藏層,傳入實際值
result = self.forward_obj[-1].neur_objs[0].out_value # 輸出結果
δerror = 2 * (actu - result) # 初始化成本函式偏導
new_layers = []
layer_number = 1
for layer in reversed(self.forward_obj):
new_neur_obj = []
neur_num = 1 # 記錄第幾個神經元
for neur in layer.neur_objs:
weight_count = 0 # 記第幾個權重,避免乘錯
δlast_out = [] # 上個神經元對下層某個神經元的偏導
new_weight = []
δb = δerror * (neur.out_value - neur.out_value ** 2) # 更新的偏置, δerror 是神經元的輸出偏導
new_bias = neur.bias - η * δb
for data in neur.data:
if len(self.forward_obj) <= 2 or len(neur.data) <= 2: # 三層網路去執行else
δw = δerror * (0.5 - (neur.out_value ** 2)/8) * data
new_weight.append(float(
neur.weight[neur.data.index(data)] - η * δw)) # 更新的權重
out_error = δerror * (neur.out_value - neur.out_value ** 2)* neur.weight[weight_count]
δlast_out.append(out_error)
weight_count += 1
else:
δerror = new_layers[-1].δlast_neur[neur_num - 1]
δw = δerror * (neur.out_value - neur.out_value ** 2) * data
new_weight.append(float(neur.weight[neur.data.index(data)] - η * δw)) # 更新的權重
# print("out_error =", δerror, "乘以",neur.out_value -neur.out_value**2," 乘以", neur.weight[weight_count] )
out_error = δerror * (neur.out_value - neur.out_value ** 2) * neur.weight[weight_count]
δlast_out.append(out_error)
weight_count += 1
new_neu = new_neural(neur_num, new_weight, δlast_out, new_bias) # 本個神經元物件中,δlast_out 存的是下層神經元各自的偏導
new_neur_obj.append(new_neu)
neur_num += 1
δlast_neur_list = [0] * len(new_neur_obj[-1].δlast_out) # 生成一個串列用來加各自分別求的偏導,算下層神經元的總偏差
for i in new_neur_obj:
δlast_neur_list = [float(m) + float(n) for m, n in zip(δlast_neur_list, i.δlast_out)]
layer_number += 1
δlast_neur = δlast_neur_list
a = new_neu_layer(layer.layer, δlast_neur, new_neur_obj) # neur_error 原來data的偏導值
new_layers.append(a)
self.new_layers = new_layers
接下來反復呼叫正向演算法和反向演算法就可以了,定義一下訓練多少次,學習率多少,再加上隨機資料集進行投喂,不過要注意break條件,即梯度下降到某個閾值停止, 這些引數都要自己慢慢試才能找到最優的,
if a > actu + 0.00001 or a < actu - 0.00001: # 洗腦回圈到閾值結束, actu代表正向運算跑出的值
back = backward(layer_list, actu, η)
new_layers = back.new_layers
counter += 1
print("正在學習......神經網路誤差修正第{}次".format(counter), actu, a)
forward(new_layers, data, actu, η, counter)
最后給自己的網路加上個高大上的進度條吧!轉義字符單行顯示,process_bar可以在代碼里定義個可加的變數,當作運行進度傳進進去, 如果訓練次數很多就要修改前面“[\033[32m%-100s\033[0m]\033[32m%.2f%%\033[0m” 這一串里100那個位置的引數,
print('\r學習進度: [\033[32m%-100s\033[0m]\033[32m%.2f%%\033[0m ' % ('>' * process_bar, process_bar + 1), end='')
4.3 回測
筆者以自建模型運行,策略也很簡單,每年審計報告公告后用模型掃一遍全市場資料,取模型結果最優的前二十家公司無腦做多,每年調倉一次,價格加權,不考慮手續費,以下使用近兩年回測結果略作展示,

2021年后三季度回測

2019年4月底至2020年4月底回測
20只股票從組合的角度來看已經屬于比較分散化的組合了,但這是只看模型結果無腦買入,如果再結合一定的投研能力,收益率或許有可能再拔高一層,
5. 寫在后面
目前根據近十年回測結果,平均年化收益率46%以上,但這意味這筆者能拳打巴菲特,腳踢暴龍獸?筆者也不這樣認為,從目前回測結果看2017, 2016年筆者的模型收益率只有個位數,而且在某些年份出現較大的波動,例如19年回測結果,筆者算都不用算,眼睛掃一眼就知道夏普比率肯定不如21年,衡量一個好的組合有很多方面, 回測控制,波動率的控制都很重要,最后,筆者分析收益率較低的年份,懷疑是市場風格導致的,因為筆者目前所訓練出的模型偏向成長型公司,一些年份,例如價值猛飆,st爆炒則會使模型表現非常一般,總之投資者的投研能力是模型能成功的保護傘,大勢的研判也是非常重要的,
筆者還想說的一點是投資者教育,別一天到晚聽網上什么炒股大神,什么金牌投顧,什么好朋友,尤其還有什么內幕訊息!敲黑板畫重點, 能賺錢的模型輪落不到白給的地步,做股票越是厲害越會自己做,而不是把收益無私的和別人分享, 市場是有反伸性的,人性如此,

本來這篇文章是打算元旦發出去的,不過有些事給耽擱了, 雖然晚了幾天但筆者還是想祝大家22年長虹, 您若不棄,我們風雨共濟,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/404317.html
標籤:AI
