NLP、NN、時系列:Google Trendsのデータを使用して石油価格を予測することは可能ですか?

『NLP、NN、時系列解析:Google Trendsデータを活用した石油価格の予測は可能か?』

まず、Word2Vecを使用して、その後、Google TrendsからGoogle検索の頻度をスクレイピングし、時間系列(フーリエ分解を介した)とKerasを使用したニューラルネットワークを組み合わせて、将来の石油価格を予測しようと試みます。

多くの出版物では、特定のタスクを実行するためにアルゴリズムが実装されているように見えますが、実際には、データ分析/科学の作業全体は、有用な結果を得るためにそれぞれが中核となる分析タスクとモデルを持つ異なるステップの組み合わせが必要な複雑な取り組みです。

このプロジェクトは、3つのアルゴリズム(NLP(word2vec)、フーリエ解析後の時系列分解(各時間効果を個別に予測するため)、Kerasを使用したニューラルネットワーク)に基づいてガソリン価格を予測する非常に野心的な試みです。Googleでの単語検索の頻度を使用して、石油価格のランダムな変動を予測します。

この作業でのアプローチの概要は次のとおりです:

最初のステップは、必要なライブラリをロードし、Gensimから事前学習済みのNLPモデルをダウンロードすることです。私は2つのWord2Vecモデルを使用しました。1つはWikipediaのデータでトレーニングされ、もう1つはTwitterのデータでトレーニングされたものです。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.tsa.seasonal import MSTL
import klib
from scipy import spatial
import gensim.downloader as api
from statsmodels.tsa.seasonal import MSTL
from nltk.stem import WordNetLemmatizer
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from datetime import timedelta
import tensorflow as tf
import math
import nltk
from scipy import optimize
from sklearn.preprocessing import MinMaxScaler
nltk.download('wordnet')
from pmdarima import auto_arima

model1 = api.load("glove-wiki-gigaword-200")
model2 = api.load("glove-twitter-100")

NLP部分の分析にはWord2Vecが適切な選択肢です。なぜなら:

  1. 類似度-距離を用いたトレーニングされるため、通常、マスキングなどの異なる下流タスク用にトレーニングされるBERTとは異なるからです。
  2. 単語の埋め込みが生成され、それが私たちが得ようとしているものであり、比較するものです。

この場合、前処理済みのモデルを使用する理由は、Googleデータを使用するため、クリーンなデータであると想定されているためです。データがクリーンでない場合や、使用言語があまりにも特殊な場合は、事前学習済みモデルの使用を推奨しません。

次に、両モデルから最も類似した単語を取得し、それらの平均を求める関数を定義します。

def close_words(wrd): 
    tbl1 = pd.DataFrame(model1.most_similar(wrd, topn=10), columns=['word','siml']).set_index('word') 
    tbl2 = pd.DataFrame(model2.most_similar(wrd, topn=10), columns=['word','siml']).set_index('word') 
    tbl = pd.concat((tbl1, tbl2), axis=1).mean(axis=1) 
    tbl = pd.DataFrame(tbl).sort_values(0, ascending=False) 
    return tbl

dfs = []

ここでは、石油または石油価格に関連すると思われるいくつかのキーワードで検索を行います。最初に「oil」という単語から始め、次にリストから派生する他の単語を探します。このステップの後、「検索結果」をデータフレームのリストに保存します。

同時に、類似した用語を使用しないように、Lemmatizationを適用しています。ステムではなく、レンマを選択することに注意してください。なぜなら、レンマはGoogle検索に表示される実際の単語であり、ステムはそうではないからです。

search = 'barrels'
wordsim = close_words(search)
lemmatizer = WordNetLemmatizer()
wordsim['Lemma'] = wordsim.index
wordsim['Lemma'] = wordsim['Lemma'].apply(lambda x: lemmatizer.lemmatize(x))
wordsim

結果が気に入った場合(それが理解できるものである場合)、それらをリストに追加します。次に、まともなサイズのリストができるまで、このプロセスを繰り返します。ただし、注意してください。この演習では、Google Trendsに対して実行するために使用するAPIには無料ライセンスの制限があります。

最後に、スクレーパーで使用できるファイルに結果を保存します。

dfs.append(wordsim)finaldf = pd.concat(dfs, ignore_index=True).groupby('Lemma').mean()finaldf.to_csv("finaldf.csv")

今度は、Google Trendsで検索するために使用するリストをすべて保存します。これには、Google Trends Scraperと呼ばれるサードパーティのAPIを使用します(https://apify.com/emastra/google-trends-scraper

免責事項:この記事は教育用のみです。私たちはウェブサイトのスクレープを奨励しません、特にそのような行動に対して条件や規約を持っているウェブプロパティに対しては特にそうです。

スクレーパーの実行には時間がかかる場合があります。スクレーパーの実行が完了したら、結果を保存してインポートします。それらは転置形式になっているため、次に考慮し、時系列データと数値の形式を整えます。

workdf = pd.read_csv("dataset_google-trends-scraper_2023-10-17_07-39-06-381.csv")workdf = workdf.Tdf2 = workdf.iloc[[-1]]workdf = workdf[:-1]workdf.columns = list(df2.values[0])workdf['Timestamp'] = workdf.indexworkdf['Timestamp'] = pd.to_datetime(workdf['Timestamp'], format='%b %d, %Y')workdf = workdf.sort_values('Timestamp')workdf = workdf.set_index('Timestamp')workdf = workdf.apply(pd.to_numeric, errors='coerce', axis=1)workdf

予測変数にも季節性があることがわかったため、52週と104週(1年と2年)のスケジュールで分解します。分解後の残差を使用します。

この作業全体の確定的な改善点は、応答変数に対して予測変数と同じアプローチを使用することですが、時間の都合でこの場合は行っていません。

a = []for col in workdf.columns: sea = MSTL(workdf[col], periods=(52, 104)).fit() res = pd.DataFrame(sea.resid) res.columns = [col] a.append(res)xres = pd.concat(a, axis=1)xres

この作業の次のセクションでは、応答変数の準備に取り組みます。

まず、政府データからガソリン価格をインポートし、適切な形式に適用します。

ydf = pd.read_csv("GASREGW.csv")from datetime import timedeltaydf['DATE'] = pd.to_datetime(ydf['DATE'], format='%Y-%m-%d')  - timedelta(days=1)ydf = ydf.sort_values('DATE')ydf = ydf.set_index('DATE')ydf = ydf.apply(pd.to_numeric, errors='coerce', axis=1)ydf

さて、石油価格の季節性についてさらに探ってみたいと思います。そのためには、系列データにフーリエ変換を行い、周波数を求めます。その逆数が可能な成分の時間期間(週単位)になります。

次に、その振幅が大きい成分を抽出し、季節性の分解に入力します。

from scipy.fft import fft, fftfreq
import numpy as np

# フーリエ変換
yf = fft(ydf['GASREGW'].values)
N = len(ydf)
xf = 1 / (fftfreq(N, d=1.0))
nyf = np.abs(yf)
four = pd.DataFrame({'Period': xf, 'Amp': nyf})
four = four[(four['Period'] > 0) & (four['Period'] <= 200)]
four = four.sort_values(['Period'], ascending=True)
four['Period'] = four['Period'].apply(lambda x: math.floor(x))
four = four.groupby('Period').max().reset_index(drop=False)
four = four.sort_values('Amp', ascending=False).head(5)

# 時系列分解
seas = MSTL(ydf['GASREGW'], periods=(32, 37, 52, 65, 104)).fit()
seas.plot()
plt.tight_layout()
plt.show()

# データセット整形
ydf['seasonal_32'] = seas.seasonal['seasonal_32']
ydf['seasonal_37'] = seas.seasonal['seasonal_37']
ydf['seasonal_52'] = seas.seasonal['seasonal_52']
ydf['seasonal_65'] = seas.seasonal['seasonal_65']
ydf['seasonal_104'] = seas.seasonal['seasonal_104']
ydf['Trend'] = seas.trend
ydf['Resid'] = seas.resid
ydf['Diff'] = ydf['Resid'].diff(-1)

# 正規化
def proper_scaler(simio):
    scaler = StandardScaler()
    results = scaler.fit_transform(simio)
    results = pd.DataFrame(results)
    results.index = simio.index
    results.columns = simio.columns
    return results

xresidualsS = proper_scaler(xres)

# 導関数を用いて滑らかにする
DF = pd.merge(xresidualsS, ydf, left_index=True, right_index=True)
DF['Diff'] = DF['Diff'].apply(lambda x: math.tanh(x))
DF.index = workdf.index
DF = DF.dropna()
DFf = DF.drop(columns=['GASREGW', 'seasonal_32', 'seasonal_37', 'seasonal_52', 'seasonal_65', 'seasonal_104', 'Trend', 'Resid'])
DFf

記述的な分析はこの作業の目標ではないため、このドキュメントは既に長くなっていますので、相関行列の形でクイックな探索を行います。

corr = DFf.corr(method = 'pearson')sns.heatmap(corr, cmap=sns.color_palette("vlag", as_cmap=True))

最後に、モデリングの段階に進みます。通常のように、最初のステップはデータをトレーニングセットと評価セットに分割することです。

finaleval=DFf[-12:]subset=DFf[:-12]x_subset = subset.drop(columns=["Diff"]).to_numpy()y_subset = subset['Diff'].to_numpy()x_finaleval = finaleval.drop(columns=["Diff"]).to_numpy()y_finaleval = finaleval[['Diff']].to_numpy()

次に、Kerasライブラリからニューラルネットワーク回帰戦略を使用します。前述のように、この演習に最適な活性化関数はTanhであることが試行錯誤の結果わかりました。

#initializeneur = tf.keras.models.Sequential()#layersneur.add(tf.keras.layers.Dense(units=1000, activation='tanh'))neur.add(tf.keras.layers.Dense(units=5000, activation='tanh'))neur.add(tf.keras.layers.Dense(units=7000, activation='tanh'))#output layerneur.add(tf.keras.layers.Dense(units=1))from keras import backend as Kdef custom_metric(y_true, y_pred):    SS_res =  K.sum(K.square( y_true-y_pred ))    SS_tot = K.sum(K.square( y_true - K.mean(y_true) ) )    return ( 1 - SS_res/(SS_tot + K.epsilon()) )#using mse for regression. Simple and clearneur.compile(optimizer='Adam', loss='mean_squared_error', metrics=[custom_metric])#trainneur.fit(x_subset, y_subset, batch_size=220, epochs=2000)

それでは、モデルができました。今度は見えないデータを評価します。R2はあまり悪くなく、最高ではありませんが、悪くもありません。

test_out = neur.predict(x_finaleval)output = finaleval[['Diff']]output['predicted'] = test_outoutput['actual'] = y_finalevalfrom sklearn.metrics import mean_absolute_error, mean_squared_error, r2_scoreprint("R2: ", r2_score(output['actual'], output['predicted']))print("MeanSqError: ",np.sqrt(mean_squared_error(output['actual'], output['predicted'])))print("MeanAbsError: ", mean_absolute_error(output['actual'],output['predicted']))output = output[['predicted','actual']]output

注意してください。今予測されたのは残差のTanhです。次のステップは、これらの変換を元に戻すことです。

output = output[['predicted']]output['predictedArcTanh'] = output['predicted'].apply(lambda x: math.atanh(x))output['PredResid'] = np.nanstart = 0prevval = 0for index, row in output.iterrows(): if start == 0:  prevval = -0.037122 - row['predictedArcTanh'] else:  prevval = prevval - row['predictedArcTanh'] output.at[index, 'PredResid'] = prevvaloutput = output[['PredResid']]output

それから、これらの週の残差の予測です。

まだ終わっていません。分解のトレンド成分では、線形回帰を適用して外挿します。

ydf['rown'] = range(len(ydf))
setreg = ydf[ydf.index <'2023-07-23']
setreg = setreg[['Trend','rown']]
mod = LinearRegression().fit(setreg[['rown']], setreg['Trend'])
setreg['PredTrend'] = mod.predict(setreg[['rown']])
plt.scatter(setreg['rown'], setreg['Trend'], color="black")
plt.plot(setreg['rown'], setreg['PredTrend'], color="blue", linewidth=3)
plt.xticks(())plt.yticks(())plt.show()

これがトレンドの予測です。

outcome = ydf[ydf.index >='2023-07-23']
outcome = outcome[['GASREGW','rown']]
outcome['PredTrend'] = mod.predict(outcome[['rown']])
outcome

季節の値に対しては、予測に適切な余弦関数を適用するために、カスタムのnumpy関数を作成し、パラメータ(振幅、周波数、位相角、オフセット)に「良い」シードを与えました。結果はシードに非常に敏感だったため、何度も実行しました。これがseasonal_32の予測です。

from scipy import optimize
from sklearn.preprocessing import MinMaxScaler
setreg = ydf[ydf.index <'2023-07-23']
setreg = setreg[['seasonal_32','rown']]
def fit_func(x, a, b, c, d):
    return a*np.cos(b*x+c) + d
params, params_covariance = optimize.curve_fit(fit_func, setreg['rown'], setreg['seasonal_32'], p0=(10,.19,0,0))
setreg['Predseasonal_32'] = setreg['rown'].apply(lambda x: fit_func(x, *params))
plt.scatter(setreg['rown'], setreg['seasonal_32'], color="black")
plt.plot(setreg['rown'], setreg['Predseasonal_32'], color="blue", linewidth=3)
plt.xticks(())plt.yticks(())plt.show()

フィットは十分に正確です。結果をアウトカムデータセットに保存します。

#assign to outcome
outcome['Predseasonal_32'] = outcome['rown'].apply(lambda x: fit_func(x, *params))

他の季節成分についても同様の手順で進めます。

  • Seasonal 37 weeks
setreg = ydf[ydf.index <'2023-07-23']
setreg = setreg[['seasonal_37','rown']]
def fit_func(x, a, b, c, d):
    return a*np.cos(b*x+c) + d
params, params_covariance = optimize.curve_fit(fit_func, setreg['rown'], setreg['seasonal_37'], p0=(10,.17,0,0))
setreg['Predseasonal_37'] = setreg['rown'].apply(lambda x: fit_func(x, *params))
plt.scatter(setreg['rown'], setreg['seasonal_37'], color="black")
plt.plot(setreg['rown'], setreg['Predseasonal_37'], color="blue", linewidth=3)
plt.xticks(())plt.yticks(())plt.show()

#assign to outcome
outcome['Predseasonal_37'] = outcome['rown'].apply(lambda x: fit_func(x, *params))
  • 季節ごとの52週
from scipy import optimize
from sklearn.preprocessing import MinMaxScaler

setreg = ydf[ydf.index < '2023-07-23']
setreg = setreg[['seasonal_52', 'rown']]

def fit_func(x, a, b, c, d):
    return a*np.cos(b*x+c) + d

params, params_covariance = optimize.curve_fit(fit_func, setreg['rown'], setreg['seasonal_52'], p0=(10, .13, 0, 0))

setreg['Predseasonal_52'] = setreg['rown'].apply(lambda x: fit_func(x, *params))

plt.scatter(setreg['rown'], setreg['seasonal_52'], color="black")
plt.plot(setreg['rown'], setreg['Predseasonal_52'], color="blue", linewidth=3)
plt.xticks(())
plt.yticks(())
plt.show()

#結果に割り当てる
outcome["Predseasonal_52"] = outcome["rown"].apply(lambda x: fit_func(x, *params))
  • 季節ごとの65週
setreg = ydf[ydf.index < '2023-07-23']
setreg = setreg[['seasonal_65', 'rown']]

def fit_func(x, a, b, c, d):
    return a*np.cos(b*x+c) + d

params, params_covariance = optimize.curve_fit(fit_func, setreg['rown'], setreg['seasonal_65'], p0=(10, .1, 0, 0))

setreg['Predseasonal_65'] = setreg['rown'].apply(lambda x: fit_func(x, *params))

plt.scatter(setreg['rown'], setreg['seasonal_65'], color="black")
plt.plot(setreg['rown'], setreg['Predseasonal_65'], color="blue", linewidth=3)
plt.xticks(())
plt.yticks(())
plt.show()

#結果に割り当てる
outcome["Predseasonal_65"] = outcome["rown"].apply(lambda x: fit_func(x, *params))
  • 季節ごとの104週
setreg = ydf[ydf.index < '2023-07-23']
setreg = setreg[['seasonal_104', 'rown']]

def fit_func(x, a, b, c, d):
    return a*np.cos(b*x+c) + d

params, params_covariance = optimize.curve_fit(fit_func, setreg['rown'], setreg['seasonal_104'], p0=(10, .05, 0, 0))

setreg['Predseasonal_104'] = setreg['rown'].apply(lambda x: fit_func(x, *params))

plt.scatter(setreg['rown'], setreg['seasonal_104'], color="black")
plt.plot(setreg['rown'], setreg['Predseasonal_104'], color="blue", linewidth=3)
plt.xticks(())
plt.yticks(())
plt.show()

最後に、outcomeデータセット全体の構造は以下のようになります:

#結果に割り当てる
outcome["Predseasonal_104"] = outcome["rown"].apply(lambda x: fit_func(x, *params))outcome

すべての成分の合計が予測された価格になります。

final = pd.merge(outcome, output, left_index=True, right_index=True)
final['GASREGW_prep'] = final['PredTrend'] + final['Predseasonal_32'] + final['Predseasonal_37'] + final['Predseasonal_52'] + final['Predseasonal_65'] + final['Predseasonal_104'] + final['PredResid']
final

そして、私たちの成果を見てみましょう!

sns.lineplot(x='index', y='value', hue='variable', data=pd.melt(final[['GASREGW','GASREGW_prep']].reset_index(drop=False), ['index']))plt.ylim(3,4.5)

価格変動に非常に保守的ですが、モデルは一般的な傾向を把握しています。

Google Collab Notebooksを利用して石油価格を予測できたなら、私はここにあなたのためにこのプロジェクトを書いているわけではありません。しかし、現実は、Googleトレンド検索トピックにはより強力なツールで探求できる予測力があるということです。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more