2022.07.06
知識グラフから株価に相関のありそうな企業を見つける
はじめに
こんにちは、グループ研究開発本部 AI研究開発室のS.Sです。
前回のブログでは知識グラフ上での推論について調査をしたので、今回はその延長線上で企業同士の関係を表現する知識グラフを構築し、似た企業のグループを見つけられるかどうかを検証してみたいと思います。一般の知識グラフは人や組織、場所などの名前がついたエンティティどうしの関係性をグラフに落とし込んだものですが、今回は企業のつながりをエンコードしたグラフを構築して分析することで、似たような企業を見つけられるかどうかを調べてみたいと思います。
2つの企業が似ている度合いというものは、知識グラフの近傍ノードから直接読み取ることができる一方で、株価の相関から定量的にも程度判断できます。
2つの観点から得られる似ている企業のリストを比べてみることで、株価データを使わなくても似た性質を持つ企業の集まりを見つけられるかどうかを評価してみることにします。
知識グラフの観点から企業の相関を見直すことで、株価の長期的な相関関係から判断できる企業のつながりに加えて、ある時期にイベントが起こり強い相関が生じた企業のペアについても見つけられる可能性があります。
知識グラフの構築
長期間にわたって株価が取得可能な企業の集まりとして今回はS&P 500にリストアップされている企業を対象にWikipediaの記事を取得し、記事の中からNamed Entity Recognition(NER)で組織名を抽出し、知識グラフを構築することにします。続く図はAllen NLPのNERモジュール(https://demo.allennlp.org/named-entity-recognition/named-entity-recognition)で組織名を抽出した例です。

構築する知識グラフではwikipediaの記事タイトルであるS&P500に属するある企業(例ではApple)から記事中に含まれる組織名(例ではMicrosoft)に辺をはります。


まずはwikipediaから記事データを抽出します。
wikipediaapiというライブラリを用いることでS&P 500のリストからリンクをたどって企業の記事を抽出することができます。
import pandas as pd
import wikipediaapi
from tqdm import tqdm
wiki_wiki = wikipediaapi.Wikipedia('en')
page_py = wiki_wiki.page('List of S&P 500 companies')
def print_links(page):
links = page.links
page_links = []
for title in sorted(links.keys()):
print("%s: %s" % (title, links[title]))
page_links.append(links[title])
return page_links
page_links = print_links(page_py)
pages = dict()
for p in page_links:
pages[p] = p.text
df_page = pd.DataFrame({"title": [x.title for x in page_links if x in pages],
"text": [pages[x] for x in page_links if x in pages]})
#df_page.to_csv("sp500_article.csv")
続いて抽出した記事タイトルと記事中に含まれる組織名からグラフを構築します。
NERにかける前に記事本文を文に区切る必要があるので、sentence splitterを使います。
判定結果のtagsの中にはNERの出力結果があり、組織名と判断されたもので単語の長さが1つのものはU-ORGが割り当てられ、2以上のものはB-ORGからL-ORGまでが組織名となります。
from allennlp.data.tokenizers.sentence_splitter import SpacySentenceSplitter
from allennlp.predictors.predictor import Predictor
import allennlp_models.tagging
predictor = Predictor.from_path("https://storage.googleapis.com/allennlp-public-models/ner-elmo.2021-02-12.tar.gz")
def get_ent(df_org):
df_ent = []
for _, row in pd.DataFrame({"beg": df_org.loc[lambda x: x["tags"] == "B-ORG"].index.values,
"end": df_org.loc[lambda x: x["tags"] == "L-ORG"].index.values + 1}).iterrows():
df_ent.append(df_org.iloc[row["beg"]:row["end"]]["words"].str.cat(sep=" "))
df_ent.extend(df_org.loc[lambda x: x["tags"] == "U-ORG"]["words"].to_list())
return df_ent
df_edges = []
for _, row in tqdm(list(df_page.iterrows())):
if pd.Series([row["title"]]).isin(pd.concat(df_edges)["src"]).all():
continue
df_org = []
sents = SpacySentenceSplitter().split_sentences(row["text"])
for i, s in list(enumerate(sents)):
res = predictor.predict(
sentence=s
)
df_org.append(pd.DataFrame({"tags": res["tags"], "words": res["words"], "page_title": row["title"]})\
.loc[lambda x: x["tags"].str.contains("ORG")])
df_org = pd.concat(df_org).reset_index(drop=True)
df_ent = get_ent(df_org)
df_edges.append(pd.DataFrame({"src": row["title"], "dst": df_ent}))
df_org = pd.concat(df_edges)
#pd.concat(df_edges).to_csv("sp500_org.csv")
entity embeddingの獲得
今回は知識グラフから似ている企業が近くに集まるようなembedding表現を得るのにDeepWalkという手法を使ってみます。
DeepWalkではグラフ上でrandom walkを行ってノードの列を生成し、その列を擬似的な単語列でみなしてSkipgramによるノードの分散表現を学習します。
得られた分散表現ではグラフ上での近さを反映したものとなります。(下図の右)

DeepWalk論文より引用
では前のステップで作成した知識グラフをDeepWalkに入力して、似ている企業が近くに集まるような組織名のembedding表現を計算してみます。
!git clone https://github.com/phanein/deepwalk.git
import numpy as np
df_org_uni = pd.concat((df_org, df_org.rename(columns={"dst": "src", "src": "dst"}))).drop_duplicates()
corps = pd.concat((df_org_uni["src"], df_org_uni["dst"])).drop_duplicates().reset_index(drop=True).values
df_corps = pd.Series(1+np.arange(len(corps)), index=corps)
df_edge_list = df_org_uni.assign(src=lambda x: df_corps.reindex(x["src"]).values, dst=lambda x: df_corps.reindex(x["dst"]).values)\
df_edge_list.to_csv("./edge_list.csv", header=None, index=False, sep=" ")
%cd deepwalk
!pip install -r requirements.txt
!python setup.py install
!deepwalk --input ../edge_list.csv --output sp500_embedding.csv
!tail -n +2 sp500_embedding.csv > sp500_embedding_mod.csv
トレーニングが終わったら得られた組織名のembeddingの中身を見てみます。
外部から取得した株価データもdataframeに読み込んでおきます。
今回は2000-01~2017-11の間(約4500日間)におけるS&P500の企業のdaily returnデータを使いました。
df_emb = pd.read_csv("sp500_embedding_mod.csv", sep=" ", header=None)
df_emb = df_emb.set_index([0]).sort_index()
df_emb.index = df_corps.index
df_stock_sel = read_stock_dataframe()
df_map = df_sp.assign(Symbol=lambda x: x["Symbol"].str.lower()).groupby("Security")["Symbol"].first()

株価の同時刻の相関と知識グラフから得られた企業名のembeddingからそれぞれ類似度の行列が得られます。


知識グラフから得られた類似度の確認
2つの類似度行列から得られるある企業に類似している企業の並びにどの程度相関があるのかを確認してみます。相関の計算には値の大小ではなく順序を重視するkendall correlationを使います。
df_corr = []
for tgt_col in tqdm(df_sp["Security"].loc[lambda x: x.isin(df_corps.index)]):
try:
df_rk = df_emb.reindex(df_sp["Security"].values).dot(df_emb.loc[tgt_col].values).sort_values().dropna()\
.rename("rank").reset_index().assign(index=lambda x: x["index"].map(df_map))\
.set_index("index")
corr = pd.concat((df_stock_sel.corr().loc[df_map.loc[tgt_col]],
df_rk), axis=1).corr(method="kendall")
df_corr.append({"tgt_col": tgt_col, "corr": corr.iloc[0, 1]})
except:
pass
df_corr = pd.DataFrame(df_corr)
df_corr["corr"].agg(["mean", "std", "count"]).to_frame("corr")
相関は弱いですが一定程度あるという結果になりました。
S&P 500のうち実際にデータを取得した企業は約半分の200ほどになります。

知識グラフ中で次数が高くさまざまな文脈で現れる企業のほうが若干並びが正確という傾向もみて取れます。
df_bucket = df_org_uni["src"].value_counts().reindex(df_corr["tgt_col"])\ .pipe(lambda x: pd.qcut(x, 5, duplicates="drop")) df_corr.assign(q_cnt=lambda x: df_bucket.reindex(x["tgt_col"]).values)\ .groupby(["q_cnt"])["corr"].agg(["mean", "std", "count"])

次数が高い企業について、知識グラフから得られた似ている企業の集まりが感覚的にも正しいかどうか実際の例をみてみます。

American Expressに似ている企業として当然現れるであろうVisaやMastercardはもちろん金融系という括りで関連のありそうな会社も上位に登場しています。


他にもいくつかの企業をみてもトップの部分に関してはある程度関連がありそうな企業が並んでいます。
まとめ
S&P500に含まれる企業についてwikipediaの記事から知識グラフを構築し、さらにそこからDeepWalkを使って企業の相関関係を抽出してみました。
得られた相関関係は株価の相関とも一定程度整合していることがわかりました。
また実際にいくつかの企業に類似する企業を抽出してみると、一時期のイベントで相関があった可能性がある企業や記事上で同じ文脈に現れる企業など株価の相関とは異なる観点でも似ている企業を抽出することができています。
最後に
グループ研究開発本部 AI研究開発室では、データサイエンティスト/機械学習エンジニアを募集しています。ビッグデータの解析業務などAI研究開発室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。皆さんのご応募をお待ちしています。
参考URL
- Wikipedia-API https://github.com/martin-majlis/Wikipedia-API
- AllenNLP – Named Entity Recognition https://demo.allennlp.org/named-entity-recognition/named-entity-recognition
- DeepWalk https://github.com/phanein/deepwalk
グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。
Follow @GMO_RD

