Edited at

GraphLassoを用いた株式市場の構造学習

More than 1 year has passed since last update.


1. 初めに

こちらのページを参考に,株価の構造を直接相関に着目して浮き上がらせ,可視化的にも計算的にも取り扱いやすい疎なグラフへ変換します.

最終的には次のような特定のエッジが強調された対マルコフグラフを描写します.

sphx_glr_plot_stock_market_001.png

Kaggleの記事でも紹介しましたが,このような変数間の相関関係を教師なし学習で把握することは,機械学習の予測精度を向上させる変数を新たに作成するため,非常に役立つ手段となりえます.

また,今回は一般的なピアソン相関ではなく,GraphLassoを用いて対マルコフグラフを描写します.通常,変数間の相関関係は直接相関と間接相関の2種類が含まれています.間接相関とは,本来では因果関係が成立しないはずの変数間で第三因子の存在により相関が発生する,見かけ上の相関を意味します.SklearnのGraphLassoは,各変数の確率密度関数がガウス分布に基づくという前提のもと,精度行列を算出することで,この間接相関を削ぎ落とした疎な行列として求める関数です.

詳細はこちらの書籍にまとめられています.数式を丁寧に説明してくれているので,非常にわかりやすいです.

異常検知と変化検知


2. 方針

株式市場の構造を抽出するため,教師なし学習を行います.

今回使用するデータは一日の価格変動量です.


2.1. グラフ構造を解析する

スパースな精度行列を予測することで,どの株価が相関を持つか発見します.特に,疎な精度行列はグラフとして株価間のつながりを与えてくれます.


2.2. クラスタリング

挙動が似ている株価をグルーピングします.sklearnでは多くのクラスタリング手法が提供されていますが,ここではAffinity Propagationを用います.この手法は,同じサイズのクラスタが強調されることなく,恣意的にクラスタ数を選択する必要がないためです.

注目したいのは,この手法はグラフとは異なる表記がなされるということです.グラフは1対1の変数間の性質を,クラスタリング結果は周辺的な性質を反映しています.同様のクラスタに所属している企業は,株式市場全体への類似したレベルの影響力を持っていると言えます.


2.3. 2Dスパースな圧縮

2次元で可視化するため,何かしらの描写方法が必要となります.ここでは,2次元へ圧縮することで各変数のノード位置を決定します.

使用している圧縮方法はLLEです.その他の圧縮方法についてはこちらを参照ください.


2.4. 可視化

先述した3つのモデルを組み合わせて2次元グラフへ描写します.ノードとエッジは次の3つを表現しています.


  • クラスタのラベルはノードの色で表現

  • スパースな共相関モデルはエッジの強さ

  • 株価を変数ごとに2次元へ圧縮し,ノードの位置を決定する


3. 株式市場の構造学習


3.1. データの取得

import datetime

import numpy as np
import matplotlib.pyplot as plt
try:
from matplotlib.finance import quotes_historical_yahoo_ochl
except ImportError:
# quotes_historical_yahoo_ochl was named quotes_historical_yahoo before matplotlib 1.4
from matplotlib.finance import quotes_historical_yahoo as quotes_historical_yahoo_ochl
from matplotlib.collections import LineCollection
from sklearn import cluster, covariance, manifold

matplotlibのファイナンスデータをインストールします.

# Choose a time period reasonably calm (not too long ago so that we get

# high-tech firms, and before the 2008 crash)
d1 = datetime.datetime(2003, 1, 1)
d2 = datetime.datetime(2008, 1, 1)

# kraft symbol has now changed from KFT to MDLZ in yahoo
symbol_dict = {
'TOT': 'Total',
'XOM': 'Exxon',
'CVX': 'Chevron',
'COP': 'ConocoPhillips',
'VLO': 'Valero Energy',
'MSFT': 'Microsoft',
'IBM': 'IBM',
'TWX': 'Time Warner',
'CMCSA': 'Comcast',
'CVC': 'Cablevision',
'YHOO': 'Yahoo',
'DELL': 'Dell',
'HPQ': 'HP',
'AMZN': 'Amazon',
'TM': 'Toyota',
'CAJ': 'Canon',
'MTU': 'Mitsubishi',
'SNE': 'Sony',
'F': 'Ford',
'HMC': 'Honda',
'NAV': 'Navistar',
'NOC': 'Northrop Grumman',
'BA': 'Boeing',
'KO': 'Coca Cola',
'MMM': '3M',
'MCD': 'Mc Donalds',
'PEP': 'Pepsi',
'MDLZ': 'Kraft Foods',
'K': 'Kellogg',
'UN': 'Unilever',
'MAR': 'Marriott',
'PG': 'Procter Gamble',
'CL': 'Colgate-Palmolive',
'GE': 'General Electrics',
'WFC': 'Wells Fargo',
'JPM': 'JPMorgan Chase',
'AIG': 'AIG',
'AXP': 'American express',
'BAC': 'Bank of America',
'GS': 'Goldman Sachs',
'AAPL': 'Apple',
'SAP': 'SAP',
'CSCO': 'Cisco',
'TXN': 'Texas instruments',
'XRX': 'Xerox',
'LMT': 'Lookheed Martin',
'WMT': 'Wal-Mart',
'WBA': 'Walgreen',
'HD': 'Home Depot',
'GSK': 'GlaxoSmithKline',
'PFE': 'Pfizer',
'SNY': 'Sanofi-Aventis',
'NVS': 'Novartis',
'KMB': 'Kimberly-Clark',
'R': 'Ryder',
'GD': 'General Dynamics',
'RTN': 'Raytheon',
'CVS': 'CVS',
'CAT': 'Caterpillar',
'DD': 'DuPont de Nemours'}

symbols, names = np.array(list(symbol_dict.items())).T

quotes = [quotes_historical_yahoo_ochl(symbol, d1, d2, asobject=True)
for symbol in symbols]

open = np.array([q.open for q in quotes]).astype(np.float)
close = np.array([q.close for q in quotes]).astype(np.float)

# The daily variations of the quotes are what carry most information
variation = close - open

一日の変動量をvariationとして求めます.

これが今回取り扱う入力値となります.


3.2. GraphLassoで構造学習

SklearnではGraphLassoCVを活用することで簡単に構造学習が可能です.

パラメータとしてalphaを選択する必要がありますが,CVということでalphaへリストを渡すことでグリッドサーチが可能となります.デフォルトではalpha=4です.

edge_model = covariance.GraphLassoCV()

# standardize the time series: using correlations rather than covariance
# is more efficient for structure recovery
X = variation.copy().T
X /= X.std(axis=0)
edge_model.fit(X)

GraphLassoでは,変数は事前に標準化されていることが前提となるため,ここでは分散をゼロとしています.

計算した結果は[共相関行列:edge_model.convariance_, 精度行列:edge_model.precision_]で確認が可能です.変数の数をNとするとNxNの三角行列として共相関を見ることができます.

_, labels = cluster.affinity_propagation(edge_model.covariance_)

n_labels = labels.max()

for i in range(n_labels + 1):
print('Cluster %i: %s' % ((i + 1), ', '.join(names[labels == i])))


3.3. 計算結果の処理と対マルコフグラフの描写

算出した共相関行列をaffinity_propagationでクラスタリングしています.

クラスタ結果は次の通り.

Cluster 1: Pepsi, Coca Cola, Kellogg

Cluster 2: Apple, Amazon, Yahoo
Cluster 3: GlaxoSmithKline, Novartis, Sanofi-Aventis
Cluster 4: Comcast, Time Warner, Cablevision
Cluster 5: ConocoPhillips, Chevron, Total, Valero Energy, Exxon
Cluster 6: CVS, Walgreen
Cluster 7: Navistar, Sony, Marriott, Caterpillar, Canon, Toyota, Honda, Mitsubishi, Xerox, Unilever
Cluster 8: Kimberly-Clark, Colgate-Palmolive, Procter Gamble
Cluster 9: American express, Ryder, Goldman Sachs, Wal-Mart, General Electrics, Pfizer, Wells Fargo, DuPont de Nemours, Bank of America, AIG, Home Depot, Ford, JPMorgan Chase, Mc Donalds
Cluster 10: Microsoft, SAP, 3M, IBM, Texas instruments, HP, Dell, Cisco
Cluster 11: Raytheon, Boeing, Lookheed Martin, General Dynamics, Northrop Grumman
Cluster 12: Kraft Foods

これで各変数のラベリングが完了しました.

次に各変数の値を2つへ圧縮します.圧縮方法はLLEを使用しています.

# We use a dense eigen_solver to achieve reproducibility (arpack is

# initiated with random vectors that we don't control). In addition, we
# use a large number of neighbors to capture the large-scale structure.
node_position_model = manifold.LocallyLinearEmbedding(
n_components=2, eigen_solver='dense', n_neighbors=6)

embedding = node_position_model.fit_transform(X.T).T

これによりembedding.shape=(2, 60)の圧縮ができました.

そしてGraphLassoで計算した精度行列を取得します.

plt.figure(1, facecolor='w', figsize=(10, 8))

plt.clf()
ax = plt.axes([0., 0., 1., 1.])
plt.axis('off')

# Display a graph of the partial correlations
partial_correlations = edge_model.precision_.copy()
d = 1 / np.sqrt(np.diag(partial_correlations))
partial_correlations *= d
partial_correlations *= d[:, np.newaxis]
non_zero = (np.abs(np.triu(partial_correlations, k=1)) > 0.02)

精度行列を取得しました.

次から結果を対マルコフグラフで可視化します.

まずはグループごとにノードを作成していきます.

同じ色のノードがLLEで取得した同グループです.

# Plot the nodes using the coordinates of our embedding

plt.scatter(embedding[0], embedding[1], s=100 * d ** 2, c=labels,
cmap=plt.cm.spectral)

次にエッジを描写していきます.

GraphLassoで算出した精度行列をもとに,Lineの色とサイズを変化させます.

# Plot the edges

start_idx, end_idx = np.where(non_zero)
#a sequence of (*line0*, *line1*, *line2*), where::
# linen = (x0, y0), (x1, y1), ... (xm, ym)
segments = [[embedding[:, start], embedding[:, stop]]
for start, stop in zip(start_idx, end_idx)]
values = np.abs(partial_correlations[non_zero])
lc = LineCollection(segments,
zorder=0, cmap=plt.cm.hot_r,
norm=plt.Normalize(0, .7 * values.max()))
lc.set_array(values)
lc.set_linewidths(15 * values)
ax.add_collection(lc)

最後に細かいラベルづけをして完了です.

# Add a label to each node. The challenge here is that we want to

# position the labels to avoid overlap with other labels
for index, (name, label, (x, y)) in enumerate(
zip(names, labels, embedding.T)):

dx = x - embedding[0]
dx[index] = 1
dy = y - embedding[1]
dy[index] = 1
this_dx = dx[np.argmin(np.abs(dy))]
this_dy = dy[np.argmin(np.abs(dx))]
if this_dx > 0:
horizontalalignment = 'left'
x = x + .002
else:
horizontalalignment = 'right'
x = x - .002
if this_dy > 0:
verticalalignment = 'bottom'
y = y + .002
else:
verticalalignment = 'top'
y = y - .002
plt.text(x, y, name, size=10,
horizontalalignment=horizontalalignment,
verticalalignment=verticalalignment,
bbox=dict(facecolor='w',
edgecolor=plt.cm.spectral(label / float(n_labels)),
alpha=.6))

plt.xlim(embedding[0].min() - .15 * embedding[0].ptp(),
embedding[0].max() + .10 * embedding[0].ptp(),)
plt.ylim(embedding[1].min() - .03 * embedding[1].ptp(),
embedding[1].max() + .03 * embedding[1].ptp())

plt.show()

結果は最初に示した通り.

ノードの色は株価の変動から見た類似したグループを表現しており,エッジが濃く太いほど直接相関が大きいことがわかります.

同業種はノード間の距離が近いことがわかります.全体的に似通った挙動をしているようです.同様のクラスタ内では,特定の企業間で特に強い直接相関が見受けられます.

また,数は少ないですが,部分的に異なるクラスタに所属している特定企業間で直接相関が存在していることがわかります.

sphx_glr_plot_stock_market_001.png