(2021~2022) Shipping Sentiment Index : ๋ด์ค๋ฐ์ดํฐ ๊ธฐ๋ฐ ํด์ด์
๊ฒฝ๊ธฐ ์์ธก ์ง์
Update: 2022-04-28
- ํ๋ก์ ํธ ์ด๋ฆ: ๋ด์ค๋ฐ์ดํฐ๋ฅผ ํ์ฉํ ํด์ด์ ๊ฒฝ๊ธฐ ๋น๊ธฐ ์์ธก ์ง์ ๊ฐ๋ฐ
- ํ๋ก์ ํธ ์งํ ๋ชฉ์ : 2021 ๊ณต๊ณต๋น
๋ฐ์ดํฐ ์ธํด์ญ ์๋ จ ํ๋
- ํ๊ตญํด์์์ฐ๊ฐ๋ฐ์(KMI): https://www.kmi.re.kr/
- ํ๋ก์ ํธ ์งํ ๊ธฐ๊ฐ: 2021๋ 9์ ~ 2022๋ 2์
- ํ๋ก์ ํธ ์ฐธ์ฌ ์ธ์: 1๋ช
- (๋ชฉ์ ) ๋ด์ค๋ฐ์ดํฐ๋ฅผ ํ์ฉํ ํ ์คํธ๋ง์ด๋ ๊ธฐ๋ฒ๊ณผ ๊ฐ์ฑ๋ถ์์ ํตํด ๋ด์ค๋ฐ์ดํฐ ์ง์๋ฅผ ์ฐ์ถํ๊ณ ๊ฒฝ๊ธฐ ์์ธก์ ์ฌ์ฉํ๊ธฐ ์ํจ.
- (ํ์์ฑ) ๋ค์ ์์ํ ํด์ด์ ๋ถ์ผ์ ์ค๋ฌผ๊ฒฝ๊ธฐ ํ ์ํฉ๊ณผ ๋ณํ ๋ฐฉํฅ์ ์ ์ํ๊ฒ ํ์ ํ๋ ๊ฒ๊ณผ ๊ฒฝ์ ์ฃผ์ฒด๋ค์ ๋ฏผ์ฒฉํ ๋์์ฑ ๋ง๋ จํ๊ธฐ ์ํจ.
(1) Crawling and Merging
ํ์ผ ์์น: Developing-CurrentForecastIndex-for-ShippingIndustry/1. Analysis Sentimental/(1) Crawling & Merging/
- ๋ชจ๋ธ ํ์ต ๋ฐ์ดํฐ ๊ตฌ์ถ์ ์ํด ๋ค์ด๋ฒ, ๋ค์์์ ๋ค์์ ๋ฐ์ดํฐ๋ฅผ ํฌ๋กค๋งํจ.
๊ฒ์ ๋ด์ค: ๋ง์ด ๋ณธ ๋ด์ค/ ๋๊ธ ๋ง์ ๋ด์ค (2021๋ 3์ ~ 2021๋ 11์)- ๋ด์ค ๋ ์ง
- ๋ด์ค ์ ๋ชฉ
- ๋ด์ค ๋ณธ๋ฌธ
- ๋ด์ค URL
- ๋ด์ค ์๋ ๊ฐ์ฑ rating ์์น
- ์ข์์
- ๊ฐ๋์ด์์
- ์ฌํผ์
- ํ๊ฐ ๋์.
- ๊ฐ ๊ธฐ์ฌ์ ๊ฐ์ฑ์ง์๋ ๋ค์์ ์์ ํตํด ์ฐ์ถํจ.
- ๊ธ์ rating(์ข์์, ๊ฐ๋์ด์์) - ๋ถ์ rating (์ฌํผ์, ํ๊ฐ ๋์)
- ์์์ด๋ฉด 1(๊ธ์ ) tag, ์์์ด๋ฉด 0(๋ถ์ ) tag
- ํฌ๋กค๋ง ํ ์ ์ฒด ๊ธฐ์ฌ Merge, ์๋ณ๋ก Merge
(2) Modeling
์ ์ฒ๋ฆฌ
def common_word_list(common_num,neg,pos):
negative_word=[]; positive_word=[]
n_list=neg.most_common(common_num); p_list=pos.most_common(common_num)
for i in range(common_num):
negative_word.append(n_list[i][0])
positive_word.append(p_list[i][0])
common_list=list(set(negative_word) & set(positive_word))
print(common_list)
print('common_list ๊ธธ์ด', len(common_list))
return common_list
# #tokenized๋ฅผ list๋ก ๋ณ๊ฒฝ
mecab=Mecab()
stopwords = ['ํ','์','์ผ๋ก','๋ก','๊ฒ','์จ','๋ง','๋', '๋', '๋ค', '์', '๊ฐ', '์ด', '์','์','์์','ํ', '์', 'ํ', '๊ณ ', '์', '๋ฅผ', '์ธ', '๋ฏ', '๊ณผ', '์', '๋ค', '๋ค', '๋ฏ', '์ง', '์', '๊ฒ', '๋ง', '๊ฒ', '๋', '์', '๋ฉด']
train_data['tokenized']=train_data['Sentence'].apply(mecab.morphs) #Sentence ๋ด์ฉ์ morphs๋ก ํํ์ ๋ถ์(type: list)
train_data['tokenized'] = train_data['tokenized'].apply(lambda x: [item for item in x if item not in stopwords]) #ํด๋น ์ด์ ๊ฐ ์ค stopword์ ํด๋นํ๋ ๊ฐ ์ง์ฐ๊ธฐ
train_data['tokenized'] = train_data['tokenized'].apply(lambda x: [item for item in x if len(item)>1]) #๊ธธ์ด 2์ด์๋ง ์ ์ฅ
train_data['tokenized'] = train_data['tokenized'].apply(lambda x: [item for item in x if item not in common_list]) #ํด๋น ์ด์ ๊ฐ ์ค stopword์ ํด๋นํ๋ ๊ฐ ์ง์ฐ๊ธฐ
- ๋ค์์ ๊ธฐ์ค์ผ๋ก ๊ธฐ์ฌ ๋ณธ๋ฌธ ๋ฐ์ดํฐ์ ๋ํด ์ ์ฒ๋ฆฌ๋ฅผ ์งํํจ.
- (1) ์กฐ์ฌ, ์ด๋ฏธ ๋ฑ์ผ๋ก ๊ตฌ์ฑ๋ stopword ์ ๊ฑฐ
- (2) ๋จ์ด์ ๊ธธ์ด๊ฐ 2๋ณด๋ค ์์ ๊ฒฝ์ฐ ์ ๊ฑฐ
- (3) common word list๋ฅผ ์์ฑํ๊ณ (ํจ์ common_word_list), ๊ทธ์ ํด๋นํ๋ ๋จ์ด ์ ๊ฑฐ
์ ์ ์ธ์ฝ๋ฉ ๋ฐ ํจ๋ฉ
### ์ ์ ์ธ์ฝ๋ฉ ###
tokenizer = Tokenizer()
tokenizer.fit_on_texts(X_train) #๋ฌธ์๋ฐ์ดํฐ๋ฅผ ์
๋ ฅ๋ฐ์ ๋ฆฌ์คํธ ํํ๋ก ๋ณํ, ๊ฐ ๋จ์ด์ index ๋ถ์ฌ
vocab_size = total_cnt - rare_cnt + 2 # ์ฌ์ฉ๋๋ ๋จ์ด ์งํฉ์ ํฌ๊ธฐ
tokenizer = Tokenizer(vocab_size, oov_token = 'OOV') #์ vocab_size๋ก tokenizer ์๋ก ์ค์
tokenizer.fit_on_texts(X_train)
#X_train, X_test์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ ์ธ์ฝ๋ฉ
X_train = tokenizer.texts_to_sequences(X_train)
### ํจ๋ฉ ###
def below_threshold_len(max_len, nested_list):
# ํฌ๊ท ๋จ์ด์ ๊ฐ์๋งํผ ์ ๊ฑฐํ๋ ํจ์, max_len์ ๋ฆฌ๋ทฐ์ ์ต๋ ๋ฐ ํ๊ท ๊ธธ์ด๋ฅผ ๋ณด๊ณ ๋น๊ตํด์ ์ค์
count = 0
for sentence in nested_list:
if(len(sentence) <= max_len):
count = count + 1
print('์ ์ฒด ์ํ ์ค ๊ธธ์ด๊ฐ %s ์ดํ์ธ ์ํ์ ๋น์จ: %s'%(max_len, (count / len(nested_list))*100))
max_len = 1000
below_threshold_len(max_len, X_train)
X_train = pad_sequences(X_train, maxlen = max_len)
- ์ ์ ์ธ์ฝ๋ฉ ๋ฒ์ ์ค์ ํจ.
- ์ ์ฒด ๋จ์ด์ ๊ฐ์(total cnt)์ ์๊ณ์น(threshold)๋ณด๋ค ์์ ๊ฒฝ์ฐ์ ํด๋นํ๋ ํฌ๊ท ๋จ์ด ์(rare cnt)๋ฅผ ๊ณ์ฐ
- ํจ๋ฉ
- max_len์ ๊ฐ์ ์์๋ก ๋ณ๊ฒฝํ๋ฉฐ ์ํ ๋น์จ์ ํ์ธํ๊ณ (ํจ์ below_threshold_len), pad_sequence ์ค์
๋ชจ๋ธ ์์ฑ
embedding_dim = 100
hidden_units = 128
model = Sequential()
model.add(Embedding(vocab_size, embedding_dim))
model.add(Bidirectional(LSTM(hidden_units)))
model.add(Dense(1, activation='sigmoid'))
es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=4)
mc = ModelCheckpoint('best_model.h5', monitor='val_acc', mode='max', verbose=1, save_best_only=True)
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(X_train, y_train, epochs=15, callbacks=[es, mc], batch_size=256, validation_split=0.2)
loaded_model = load_model('best_model.h5')
print("ํ
์คํธ ์ ํ๋: %.4f" % (loaded_model.evaluate(X_test, y_test)[1]))
- ๋ค์ ํจํค์ง๋ฅผ ์ค์นํ๊ณ ๋ชจ๋ธ๋ง ์ค์
- from tensorflow.keras.layers import Embedding, Dense, LSTM, Bidirectional
- from tensorflow.keras.models import Sequential, load_model
- from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
- Bidirectional-LSTM ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ํ์ตํจ.
- ์์ค์จ: 0.4597 // ์ ํ๋: 0.8141
(1) Crawling
ํ์ผ ์์น: Developing-CurrentForecastIndex-for-ShippingIndustry/2. Handling Shipping News/Crawling_๋ด์ค๋ฐ์ดํฐ_Shipping.ipynb
- ๊ฐ์ฑ ๋ถ๋ฅ๊ธฐ ๋ชจ๋ธ์ input์ผ๋ก ๋ค์ด๊ฐ ๋ฐ์ดํฐ ๊ตฌ์ถ์ ์ํด bigkinds ์ฌ์ดํธ์์ ๋ด์ค๋ฐ์ดํฐ๋ฅผ ํฌ๋กค๋งํจ.
- ํฌ๋กค๋งํ ๋ฐ์ดํฐ๋ ๋ค์๊ณผ ๊ฐ์.
- ๊ฒ์ ํค์๋: ํด์ด์ ,ํด์ด์ฐ์ ,ํด์ด๊ฒฝ๊ธฐ,ํด์ด์ ๊ณ
- ๊ฒ์ ๊ธฐ๊ฐ: 2000๋ 1์ ~ 2021๋ 11์
- ๋ด์ค ์ ๋ชฉ
- ๋ด์ค ๋ ์ง
- ๋ด์ค ๋ณธ๋ฌธ
- ๋ด์ค url
(2) Topic Modeling
๊ฐ 80๊ฐ ํ ํฝ์ ์์ 25๊ฐ ์ฐ๊ด์ด๋ฅผ ์ถ์ถ ํ ์ ํฉ์ฑ ๊ฒ์ฆ ํ NMF ํ ํฝ์ ์ฌ์ฉํ์์.
LDA Topic Modeling
# ์ค์น ํจํค์ง
from gensim import corpora, models
from gensim.models.coherencemodel import CoherenceModel
from gensim.models.ldamodel import LdaModel
from gensim.corpora.dictionary import Dictionary
from gensim.test.utils import common_texts
from gensim.test.utils import datapath
# common_texts์์ dictionary ์์ฑ
common_dictionary = Dictionary(common_texts)
common_corpus = [common_dictionary.doc2bow(text) for text in common_texts]
# corpus๋ฅผ ํ์ฉํ์ฌ LdaModel ์์ฑ
lda = LdaModel(common_corpus, num_topics=80)
#document(๋ด์ค๋ฐ์ดํฐ)์์ word ์ถ์ถ (๋ง๋ญ์น ์์ฑ)
data_word=[[word for word in x.split(' ')] for x in document]
id2word=corpora.Dictionary(data_word)
texts=data_word
corpus=[id2word.doc2bow(text) for text in texts]
print("Corpus Ready")
#์์ฑํ ๋ง๋ญ์น๋ก lda ์์
lda = LdaModel(corpus=corpus, id2word=id2word, num_topics=80)
print("lda done, please wait")
#์ถ๋ ฅ๋ถ
for i in range(num_topics):
words = model.show_topic(i, topn=num_words); #๋ฐํํ๋ ํ ํฝ ์ฐ๊ด์ด ๊ฐ์
word_dict['Topic # ' + '{:02d}'.format(i+1)] = [i[0] for i in words]
print("Result_out")
- gensim ํจํค์ง ํ์ฉํ์ฌ LDA Topic Modeling
- 80๊ฐ ํ ํฝ์ผ๋ก ๋๋์ด ๋ถ๋ฅ
NMF Topic Modeling
from sklearn.feature_extraction.text import CountVectorizer,TfidfTransformer
from sklearn.decomposition import NMF
from sklearn.preprocessing import normalize
#Count Vector ์์ฑ
vectorizer=CountVectorizer(analyzer='word')
x_counts=vectorizer.fit_transform(text)
transformer=TfidfTransformer(smooth_idf=False)
x_tfidf=transformer.fit_transform(x_counts)
xtfidf_norm=normalize(x_tfidf,norm='l2',axis=1)
print("xtfidf_norm Ready")
model=NMF(n_components=80,init='nndsvd')
model.fit(xtfidf_norm) # xtidf ๋ฐ์ดํฐ๋ฅผ fitํจ
print("Model Ready")
for topic in range(components_df.shape[0]):
tmp = components_df.iloc[topic]
print(f'For topic {topic+1} the words with the highest value are:')
print(tmp.nlargest(25))
#์ถ๋ ฅ๋ถ
- sklearn์ ํ์ฉํ์ฌ NMF Topic Modeling
- ๋ง์ฐฌ๊ฐ์ง๋ก 80๊ฐ ํ ํฝ์ ๋ถ๋ฅ
(1) Topic Count
- 2000๋ 1์๋ถํฐ 2021๋ 10์๊น์ง์ ๋ด์ค๋ฐ์ดํฐ์์ NMF ๋ฐฉ์์ผ๋ก ์ถ์ถํ ๊ฐ ํ ํฝ๋ณ ์ฐ๊ด์ด์ ๊ฐ์ ์ง๊ณ
- ์๋ณ ์ง์ ์ฐ์ถ์ ์ํด ๊ฐ ๋ด์ค๋ฐ์ดํฐ์ ์ผ๋ณ ํ ํฝ ๋จ์ด ์๋ฅผ ์ง๊ณํจ
(2) Sentimental Index Daily Sentimental
# ๊ฐ์ฑ์ง์๋ฅผ ๋ถ์ํ๋ ํจ์
def sentiment_predict(new_sentence):
encoded = tokenizer.texts_to_sequences([new_sentence]) # ์ ์ ์ธ์ฝ๋ฉ
pad_new = pad_sequences(encoded, maxlen = max_len) # ํจ๋ฉ
score = float(loaded_model.predict(pad_new)) # ์์ธก
return score
- ์ ํจ์๋ฅผ ํตํด ๊ฐ ๋ด์ค๋ฐ์ดํฐ์ ๊ธ์ , ๋ถ์ ์ง์๋ฅผ predictํจ.
- ์์ธก ํ์๋ ๋จ์ ๊ฐ์ฑ์ง์์ ํด๋นํ๋ ๊ธ์ -๋ถ์ ๊ฐ์ ๋ง๋ถ์ฌ์ฃผ์์.
Monthly Sentimental
for i in LCount: #์๋ณ ๋ด์ค ๊ฐ์
index=LCount.index(i)
df_tmp=df_Sentimental[pre:pre+i]
#์ผ๋ณ ๊ฐ์ฑ์ง์์ ํ๊ท ๊ฐ์ ๊ฐ์ฑ์ง์
pos=df_tmp['Pos'].tolist(); MeanPos=np.mean(pos);
neg=df_tmp['Neg'].tolist(); MeanNeg=np.mean(neg)
SentiIndex=(MeanPos-MeanNeg)*100
LSenti.append(round(SentiIndex,1))
pre=i
- ๋ค์์ ์ ์ฐจ๋ฅผ ํตํด ์ผ๋ณ๋ก ์์ธกํ ๊ฐ์ฑ์ง์๋ฅผ ์๋ณ ์ง์๋ก ๋ณํํ์์.
- (1) ์๋ณ ๋ด์ค ๊ฐ์ ๋งํผ ๊ธ์ ์์น์ ๋ถ์ ์์น์ ํ๊ท ์ ๊ตฌํจ.
- (2) (ํ๊ท ๊ธ์ - ํ๊ท ๋ถ์ )*100 ์ผ๋ก ๊ฐ์ฑ์ง์๋ฅผ ์ฐ์ถ
(3) Index
- ๋ด์ค๋ฐ์ดํฐ์ง์ ์ฐ์ถ์ ๊ฒฝ์ฐ ์ ํ์ฐ๊ตฌ๋ฅผ ๋ฐ๋ผ ์์ ์ค๊ณํ์์.
- ๊ฒฐํฉ์ง์ 1: ๊ฐ์ฑ์ง์ * ํ ํฝ ๋น์ค ์์ 20๊ฐ ํ ํฝ์ 10๊ฐ ์ฐ๊ด์ด ๋น์ค (%)
- ๊ฒฐํฉ์ง์ 2: ๊ฐ์ฑ์ง์ * ํ ํฝ ๊ฐ ์๊ด ์์ 20๊ฐ ํ ํฝ ๋จ์ด ๋น์ค (%)
- ๊ฒฐํฉ์ง์ 3: ๊ฐ์ฑ์ง์*ํ ํฝ-์์ฐ ์๊ด ์์ 20๊ฐ ํ ํฝ ๋จ์ด ๋น์ค (%)
์ฌ๊ธฐ์ ํด์ด์ ์์ฐ ์๊ด์ฑ์ ๋น๊ตํ๊ธฐ ์ํด ์์์ด์ก์ ์์ฐ์ง์๋ฅผ ์ฐธ๊ณ ํ์์. (ํต๊ณ์ฒญ)
- 3๊ฐ์ ์ง์์ ์ค์ ์งํ ๊ฐ ๋์ ์๊ด์ฑ์ ๋๋ ๊ฒฐํฉ์ง์ 3์ ๋ด์ค๋ฐ์ดํฐ ์ง์๋ก ์ ์ ํ์์.
- ์ค์ ์งํ: OECD์์ ๋ฐํํ ์ฐ๋ฆฌ๋๋ผ์ ์ฐ์ ์์ฐ์ง์
- ์ง์์ ์ค์ ์งํ ๊ฐ ์๊ด๊ณ์
๊ฒฐํฉ์ง์(1) ๊ฒฐํฉ์ง์(2) ๊ฒฐํฉ์ง์(3) -0.295 -0.343 -0.493
(1) Data Set Ready
- ๋ชจํ์ ๋ง๋ค๊ธฐ ์ ๋ด์ค๋ฐ์ดํฐ ์ด์ธ์ ๋น๊ธฐ์์ธก๋ชจํ์ ์ ์ฉ๋ ํด์ด์ ์ค๋ฌผ ๋ฐ์ดํฐ์ ์ ๊ตฌ์ถํจ.
- Stopford(2008), Chen et al(2015), Choi,Kim and Han(2018)์ ์ฐธ๊ณ ํ์ฌ ํด์ด์์ฅ์ ๊ณต๊ธ๋ถ์ผ, ์์๋ถ์ผ, ์ด์ ๋ฐ ๊ฐ๊ฒฉ ๋ถ์ผ, ๊ฒฝ์ ์ํฉ ๋ถ์ผ๋ก ๋๋์ด ์์ง, ์ฐ๋ฆฌ๋๋ผ ํด์ด์ ์์ฐ์ง์๋ฅผ ์์ธกํ๋ ๊ฒ์ ๋ชฉํ๋ก ํ๊ธฐ์ KOSPI, CLI(KOREA) ๋ฑ์ ์ถ๊ฐํ์ฌ 26๊ฐ์ ์ค๋ฌผ ์งํ๋ฅผ ์ ์ ํจ.
- ์๋ณ ์๊ณ์ด ๋ฐ์ดํฐ์ ํด๋นํ๋ฏ๋ก ์์ ๋ ์๊ณ์ด์ฑ์ ๋๊ธฐ ์ํด Bpanel(library tseries)๋ฅผ ํ์ฉํ์ฌ ์์ ํํจ.
- trans code๋ 3์ผ๋ก ์ ๋ ๋๋น ์ฆ๊ฐ์จ์ ํด๋น
(2) Modling
- Domenico Giannone(2008)์ด ์ ์ํ ๋น๊ธฐ์์ธก๋ชจํ์ ํ์ฉํ์ฌ ํด์ด์ ๊ฒฝ๊ธฐ ์์ธก์ ์๋ํ์์.
- ์์ธก๋ ฅ ํ๊ฐ๋ฅผ ์ํด 3๊ฐ์ ๋ชจ๋ธ์ ๋ง๋ค์์.
- ์๊ธฐํ๊ท๋ชจํ
- ๋ํ์์ธ๋ชจํ : ์ค์ ์งํ๋ง ์ฌ์ฉ
- ๋น๊ธฐ์์ธก๋ชจํ : ์ค์ ์งํ + ๋ด์ค๋ฐ์ดํฐ ์ง์ ์ฌ์ฉ
- ๊ฐ ๋ชจํ์ RMSE์ MAE ๋น๊ต
๋ถ๋ฅ ์๊ธฐํ๊ท๋ชจํ ๋ํ์์ธ๋ชจํ ๋น๊ธฐ์์ธก๋ชจํ RMSE 0.06661 0.03762 0.03754 MAE 0.04841 0.02794 0.02784 - ๋น๊ต ๊ฒฐ๊ณผ ๋น๊ธฐ์์ธก๋ชจํ(์ค์ ์งํ + ๋ด์ค๋ฐ์ดํฐ ์ง์)์ ์ฑ๋ฅ์ด ๊ฐ์ฅ ์ข์์.
- Python (3.7.3)
- R (4,1.2)
- JupyterNotebook