Skip to main content

Numerai Signals 시작하기: 감정 분석(Sentiment Analysis)

이 보고서는 Numerai Signals 토너먼트에 Stock News API 및 FinBERT를 사용하는 방법을 보여줍니다.
Created on March 5|Last edited on May 6
이는 여기에서 볼 수 있는 영어 기사를 번역한 것이다.
Numerai Signals

이 블로그 게시물에서, 저희는 Numerai Signals 토너먼트를 소개하고 감정 분석(sentiment analysis) 예시를 사용하여 시작하는 법을 보여 드리겠습니다. 또한 감정 스코어에 따른 주식 순위 예측을 위해 뉴스 헤드라인을 살펴보겠습니다. Numerai에 대한 전반적인 소개는 여기의 다른 블로그 게시물을 확인해 보시기 바랍니다.

Numerai Signal이란 무엇인가요?

Numerai는 전 세계 데이터 과학자들의 예측에 기반하여 운영되는 크라우드소싱 AI 헤지 펀드입니다. 고전 Numerai 토너먼트는 머신러닝을 사용하여 모델링할 수 있는 익명화된 데이터를 여러분께 제공합니다. Numerai Signals는 이 아이디어를 일반화하고 여러분들이 원하는 데이터의 사용을 허용합니다. 이를 통해 데이터 과학자들은 데이터 소스 및 모델링 테크닉을 창의적으로 활용할 수 있습니다.


Numerai Signals 이면의 비전에 대한 아이디어를 살펴보시려면 이 비디오를 확인해보시기 바랍니다:

Numerai Signals


감정 분석을(sentiment analysis) 사용하여 Numerai Signals에 대하여 의견을 개진하기 위해 전체 파이프라인을 살펴보겠습니다! 전체 코드는 Kaggle Notebook에서도 구현됩니다. 우선, 데이터 과학 파이프라인의 가장 중요한 부분인 좋은 데이터를 얻는 것에 대해서부터 시작해보겠습니다! 이를 위해 저희는 Stock 뉴스 기사의 클린 API (애플리케이션 프로그래밍 인터페이스, Application Programming Interface)를 사용해보겠습니다.

 전체 코드 →

Stock News API (stocknewsapi.com)

Stock News API (stocknewsapi.com)는 광범위한 주식을 기반으로 하는 뉴스 기사를 쉽게 검색할 수 있는 온라인 서비스입니다. 약 10여 개의 뉴스 소스에서 기사를 수집하여 쉬운 데이터 분석을 위한 추가 정보를 제공합니다. 이를 통해 JSON 포맷으로 매주 최신 가사를 얻을 수 있으며, 데이터 결합(data joining)을 위해 이를 CSV로 분석해보겠습니다. Stock News API에는 100개의 API call을 포함한 14일 무료 체험 프로그램이 있습니다. 이후, 월 20달러에 20,000개의 API call에 가입하실 수 있습니다.
All sources from which we can retrieve news articles with stocknewsapi.com


Python에서 requests 라이브러리를 통해 데이터를 가져올 수 있습니다. 액세스를 얻으려면 Stock News API에서 API 키/토큰이 필요합니다. 예를 들어, Tesla, Inc. (TSLA) 주식에 관한 최신 뉴스 기사를 얻는 방법은 다음과 같습니다:
import requests
api_key = "YOUR_API_KEY_HERE"
api_request = f"https://stocknewsapi.com/api/v1?tickers=TSLA&items=1&token={api_key}"
data = requests.get(api_request).json()['data']
print(data)
JSON 출력은 다음과 같습니다:
[
{
"news_url": "https://www.barrons.com/articles/fund-bought-tesla-apple-microsoft-stock-sold-att-51613063598",
"image_url": "https://cdn.snapi.dev/images/v1/a/7/im-298821.jpg",
"title": "A Huge Fund Bought Tesla, Apple, and Microsoft Stock. Here's What It Sold.",
"text": "Dutch pension fund PGGM initiated a position in EV-giant Tesla, bought more Apple and Microsoft stock, and sold AT&T stock in the fourth quarter.",
"source_name": "Barrons",
"date": "Sun, 14 Feb 2021 07:00:00 -0500",
"topics": ["paywall"],
"sentiment": "Neutral",
"type": "Article",
"tickers": [
"AAPL",
"MSFT",
"TSLA",
"T"
]
}
저희가 관심 있게 보는 문제는 다음과 같습니다:
  • 감정 예측을 위한 “title”
  • 데이터 결합에 대한 “date”. date(날짜)는 시간대(이 경우 GMT-5)를 포함하며 뉴스 소스에 따라 다를 수 있습니다.
  • 적합한 주식을 예측에 포함하기 위한 “tickers”
API는 자체 감정 분류(sentiment classification)를 제공하며, 여기에는 “Positive”(긍정), “Neutral”(중립), “Negative”(부정)가 있습니다. 문제를 좀 더 쉽게 다루기 위해, “Neutral”로 분류된 기사를 필터링하겠습니다. 여기서 저희는 양극화된 기사가 단기 시장 변동 예측에 가장 중요하다고 가정했습니다.

데이터 검색(Data Retrieval)

저희가 요청한 관련 데이터는 지난주 뉴스 기사로, API call에 &days=7을 지정하여 지난 7일을 필터링하고 &type=article을 통하여 기사를 필터링합니다.
또한, API는 단일 호출을 최대 50개의 기사로(&items=50) 제한합니다. 한 주에 단일 주식 시세 표시기(stock ticker)에 대한 50개 이상의 뉴스 기사가 있을 수 있으므로, 이 경우, 저희는 해당 주 동안 모든 기사를 얻을 때까지 또는 저희가 지정한 한도에 도달할 때까지 다른 call을 생성합니다. API call 낭비를 ��지하고자 이 제한을 설정합니다.


모든 Tesla Inc. (TSLA) 뉴스 기사를 가져오는 루프(loop)는 다음과 같으며, 앞에서 계산한 date(날짜)를 사용합니다:
import requests
import pandas as pd

page_cutoff_point = 10
dfs = []
i = 1
while i <= page_cutoff_point:
api_request = f"https://stocknewsapi.com/api/v1?tickers=TSLA&items=50&type=article&page={i}sortby=rank&days=7&token={api_key}"
data = requests.get(api_request).json()['data']
df = pd.DataFrame(data)
if df.empty:
break
dfs.append(df)
i += 1
tesla_df = pd.concat(dfs)
모든 관련 주식 시세 표시기(stock tickers)에 대하여 이 루프를 실행하고 이 주식 시세 표시기를 연결하여 최종 Pandas DataFrame을 얻을 수 있습니다.

 전체 코드 →

데이터 랭글링(Data Wrangling)

감정 분석 예측을 위한 데이터를 준비하기 위해서 저희는 일련의 단계를 거치게 됩니다. 높은 수준에서 다음의 사전 처리 작업(preprocessing operations)이 수행됩니다:
  1. 불필요한 열을 제거합니다. ('text', 'news_url', 'image_url', 'topics' and 'source_name')
  2. “Neutral” sentiment(감정)를 통해 모든 기사를 필터링합니다.
  3. 모든 행이 공통 datetime 포맷을 갖도록 모든 타임스탬프를 UTC(Coordinated Universal Time, 협정 세계시)로 변환합니다.
저희는 모든 뉴스 헤드라인을 포함하는 각 주식 시세 표시기(stock ticker)에 대한 하나의 고유한 행이 있었으면 합니다. 일부 뉴스 기사는 여러 주식 시세 표시기(stock ticker)를 참조할 수 있으므로 우선 set intersection을 수행하여 Numerai Signals에 대한 모든 관련 주식 시세 표시기를 찾습니다. 모든 2,090개의 중복된 표시기(overlapping ticker)는 이미 계산되어 pickle 파일로 여기에 저장되나 아래의 코드는 이를 직접 계산하는 법을 나타내고 있습니다. 새로운 주식은 언제나 상장 및 상장폐지되므로 정기적으로 교차하는 주식(intersecting stock)을 계산하는 것이 타당합니다.
relevant_tickers=stock_news_tickersnumerai_tickersrelevant\_tickers = stock\_news\_tickers \cap numerai\_tickers
import pickle
import requests
import pandas as pd

# Get all tickers available in stocknewsapi.com
ticker_request = f"https://stocknewsapi.com/api/v1/account/tickersdbv2?token={api_key}"
json_data = requests.get(ticker_request).json()['data']
stock_news_tickers = []
for row in json_data:
stock_news_tickers.append(row['ticker'])

# Get all Numerai Signals tickers
ticker_df = pd.read_csv("https://numerai-signals-public-data.s3-us-west-2.amazonaws.com/signals_ticker_map_w_bbg.csv")
numerai_tickers = set(ticker_df['ticker'])

# Compute intersection
relevant_tickers = list(set(stock_news_tickers).intersection(numerai_tickers))


표시기(ticker)를 참조하는 모든 뉴스 헤드라인을 집계하여 각 표시기에 대한 행과 [SEP][SEP] 으로 구분된 헤드라인을 얻게 되며, 이는 뉴스 헤드라인 입력을 배치(batch)하는 데 사용됩니다.
data.loc[:, 'title'] = data['title'] + " [SEP] "
마지막으로, Numerai 표시기(ticker)에 대한 새로운 DataFrame을 병합하여 나중에 제출(submission) 단계에서 필요한 Bloomberg 표시기(ticker) 형식을 검색합니다.
# Aggregate news headlines for each ticker
dfs = []
for ticker in relevant_tickers:
aggregated = data[data['tickers'].apply(lambda x: ticker in x)].resample("W-fri", on='date').sum()
aggregated = aggregated.drop("tickers", axis=1)
aggregated['ticker'] = ticker
aggregated = aggregated.drop_duplicates("ticker", keep='last')
if aggregated.empty:
continue
dfs.append(aggregated)
new_df = pd.concat(dfs)
new_df['title'] = new_df['title'].astype(str)
merged = new_df.merge(ticker_df, on='ticker')
merged = merged.drop("yahoo", axis=1).dropna()

 전체 코드 →

추론 (FinBERT)

드디어 머신러닝 파트를 다룰 준비가 되었습니다! FinBERT는 경제 기사에 대하여 충분히 훈련된 Transformer 모델입니다. Dogu Araci의 마스터 논문의 일부로 개발되었습니.다FinBERT는금융 뉴스에 대한 교육을 받았기 때문에 금융 영역에서 NLP (Natural Language Processing) 작업을처리하는 데 좋은 모델입니다. FinBERT는 2018년 Google이 개발한 BERT (Bidirectional Encoder Representations) 모델을 기반으로 한 언어 모델입니다. Transformer 모델에 대한 자세한 설명은 "Transformer Deep Dive" 기사를 참조하시기 바랍니다.
저희는 HuggingFace Transformers 라이브러리 및 PyTorch를 사용하여 모든 헤드라인에 대한 감정 예측(sentiment predictions)을 가져오겠습니다. 저희가 사용할 사전 훈련된 FinBERT 모델은 HuggingFace 웹사이트에서 이용하실 수 있습니다.


높은 수준에서, 파이프라인은 다음의 단계를 거칩니다:
  1. GPU 메모리가 부족하지 않도록 뉴스 헤드라인을 배치(batch)합니다.
def _chunks(lst, n):
""" Yield n-sized chunks from list. """
for i in range(0, len(lst), n):
yield lst[i:i + n]

batch_size = 8
for row in data:
ticker_headlines = row.split(" [SEP] ")[:-1]
.
for batch in _chunks(ticker_headlines, batch_size):
.
2. 각 batch에 대해 헤드라인을 토큰화합니다. input_idsattention_mask는 FinBERT 모델에 대한 입력이 됩니다.
import torch
from transformers import AutoTokenizer
batch = ["The stock will go up in the upcoming week!", "Earnings are down for Q3"]
tokenizer = AutoTokenizer.from_pretrained("ipuneetrathore/bert-base-cased-finetuned-finBERT")
encoded = tokenizer(batch, add_special_tokens=True, max_length=200,
padding='max_length',return_attention_mask=True,
return_tensors='pt', truncation=True)
input_ids = torch.cat([encoded['input_ids']], dim=0).to('cuda')
attention_mask = torch.cat([encoded['attention_mask']], dim=0).to('cuda')
3. 사전 훈련된 FinBERT 모델에서 softmax 활성화를 가져옵니다.
import torch.nn.functional as F
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained("ipuneetrathore/bert-base-cased-finetuned-finBERT").eval().to('cuda')
model_output = model(input_ids, token_type_ids=None, attention_mask=attention_mask)
logits = model_output[0]
softmax_output = F.softmax(logits, dim=1).cpu().detach().numpy()
4. 감정 스점수(sentiment score)를 계산하고 주식 시세 표시기(stock ticker)의 모든 예측에 대한 평균을 구합니다.
score(T)=1ni=1npositiveinegativeiscore(T) = \frac{1}{n} \sum_{i=1}^{n} positive_i - negative_i

여기서 TT 는 특정 주식 시세 표시기(stock ticker)를, nn 은 헤드라인의 전체 양을 나타냅니다.
단일 감정 점수의 경우 다음과 같습니다:
sentiment_score = softmax_output[:, 2] - softmax_output[:, 0]
주어진 표시기(ticker)에 대한 모든 감정 스코어(sentiment score)를 수집한 이후 다음을 수행합니다:
mean_score = np.array(sent_scores_ticker).ravel().mean()
5. 감정 점수(sentiment score)는 [1...1][-1...1]범위에 있으나 Numerai Signals는 오직 [0...1][0 ... 1] 범위의 예측만을 허용합니다. 따라서 MinMaxScaler를 사용하여 모든 주식 시세 표시기(stock ticker)에 대한 감정 점수 예측을 [0...1][0 ... 1] 범위로 조정합니다.
from sklearn.preprocessing import MinMaxScaler
def scale_sentiment(sentiments):
""" Scale sentiment scores from [-1...1] to [0...1] """
mm = MinMaxScaler()
sent_proc = np.array(sentiments).reshape(-1, 1)
return mm.fit_transform(sent_proc)

 전체 코드 →

제출

이제 예측을 얻었으므로 Numerai Signals에 제출할 준비가 거의 다 되었습니다. DataFrame 마무리하기 위해 예측이 “live” 데이터(예: 다음 주)에 대한 것임을 나타내는 열이 추가됩니다. 또한 다음 금요일을 나타내는 날짜 열을 생성합니다. 마지막으로 DataFrame은 CSV에 기록되며 Numerai의 API를 사용하여 업로드됩니다.
import numerapi
from datetime import datetime
from dateutil.relativedelta import relativedelta, FR

# API settings for submitting to Numerai
NMR_PUBLIC_ID = "YOUR PUBLIC KEY"
NMR_SECRET_KEY = "YOUR SECRET KEY"
MODEL_NAME = "YOUR MODEL NAME"
SUB_PATH = "finbert_submission.csv"

# Initialize API with API Keys and add data_type column
NAPI = numerapi.SignalsAPI(NMR_PUBLIC_ID, NMR_SECRET_KEY)
final_df.loc[:, "data_type"] = "live"
# Add date column denoting last Friday
friday = int(str((datetime.now() + relativedelta(weekday=FR(-1))).date()).replace("-", ""))
final_df["friday_date"] = friday

# Save final DataFrame to CSV and upload predictions
cols = ["bloomberg_ticker", "friday_date", "data_type", "signal"]
final_df[cols].reset_index(drop=True).to_csv(SUB_PATH, index=False)
model_id = NAPI.get_models()[MODEL_NAME]
NAPI.upload_predictions(SUB_PATH, model_id=model_id)
제출 후 모델 페이지 signals.numer.ai/tournament에서 제출 사항을 확인할 수 있습니다. 제출은 최소 10개의 주식 시세 표시기에 대한 것이어야 하며, 매주 제출을 할 수 있는 대략 총 5,300개의 국제 주식이 있습니다. 총 숫자는 새로운 주식 상장 및 상장 취소에 따라 매주 다를 수 있습니다.

signals.numer.ai/tournament 예시 제출 확인

 전체 코드 →

평가 및 보관

이 파이프라인에서의 예측은 저희가 지정한 모든 주식의 순위를 나타내는 것으로 추정됩니다. 0에 가까우면 주식이 다음 주에 하락할 것으로 예상하며, 1에 가까운 경우 주식이 오를 것으로 봅니다. Numerai는 주식 포트폴리오 구축을 위해 모든 사용자의 예측을 종합합니다. 신호 점수(signal score)가 0에 가까운 주식을 매도하고 1에 가까운 주식을 매수합니다. 사용자는 사용자의 예측이 다음 주에 얻게 될 Spearman Correlation score(스피어만 상관 스코어)를 기반으로 평가됩니다. 또한, 시그널의 고유성은 Meta Model Contribution (MMC) score(메타 모델 기여도 스코어 점수)로 평가됩니다. Numeraire (NMR) cryptocurrency(Numeraire (NMR) 암호화폐)를 사용하여 Numerai를 통해 사용자는 예측에 돈을 걸 수 있으며, 이러한 메트릭을 바탕으로 Numeraire를 얻거나 잃게 됩니다.
Numerai 참여에 대한 자세한 배경 및 안내는 제가 작성한 Numerai 기사를 참조하시기 바랍니다. 그리고 Numerai Signals 참여에 대한 상세 설명은 Numerai Signals 문서를 읽어보시기 바랍니다.
Numerai Signals에 대한 예측 예시


이제 이 기사를 마무리하겠습니다. 이 기사를 통해 여러분들이 즐겁게 Numerai Signals를 시작하실 수 있기를 바랍니다! 또한 이 리포트에서 새로운 개념에 대해 뭔가 얻어 가실 수 있기를 바랍니다. 좀 더 알아보고 싶으시다면 아래의 자료 섹션을 참조하시어 Numerai Signals에 대한 소개, 데이터 소스, 추천 논문 및 Numerai 커뮤니티에 대한 정보를 확인하시기 바랍니다.
문의 사항이나 피드백할 내용이 있으신 경우, 아래에 댓글을 남겨주시기 바랍니다. 또한 Twitter @carlolepelaars를 통해 연락하실 수 있습니다.

자료

학습 자료

데이터 소스

추천 논문

커뮤니티

Iterate on AI agents and models faster. Try Weights & Biases today.