niedziela, 18 lutego 2024

Stopa zwrotu - ważone i roczna stopa zwrotu portfela

Ten post jest kontynuacją: https://kamilkondrat.blogspot.com/2024/02/stopa-zwrotu-zwrot-portfela.html

Ta praca odzwierciedla zainteresowanie autora analizą finansową i wykorzystaniem narzędzi informatycznych w jej realizacji. Niniejszy blog nie ma na celu dostarczania porad finansowych, lecz stanowi konstruktywne podejście autora do dzielenia się wiedzą. Autor widzi w tym sposobność do rozwijania swoich umiejętności i zachęcania do wzajemnej dyskusji. Należy pamiętać, że wnioski wynikające z analiz prezentowanych na tym blogu nie powinny być bezpośrednio stosowane do podejmowania decyzji finansowych. Nawet jeśli analizy są dokładne, stanowią one tylko fragment potrzebnych informacji do podjęcia w pełni świadomej decyzji. Brak szerszego kontekstu, w którym znajduje się potencjalny inwestor lub decydent, może prowadzić do niepełnych wniosków.

W poprzednim wpisie nadałem wagi poszczególnym spółkom: "Pozwolę sobie zdecydować, że 25% kapitału zostanie zainwestowane w Alphabet, 25% w Microsoft i 50% Nvidia." Tworząc przy tym jednowymiarową tablicę "weights."

Następnie pomnożymy wszystkie stopy zwrotu dla każdej spółki z każdego dnia przez ustalone wagi "weights", aby następnie zsumować wyniki trzech spółek uzyskując jeden konkretny wynik, który wyrazi dany dzień. Krok po kroku, jak to działa...

Mam przed sobą tablicę ukazującą znormalizowane stopy zwrotu (obliczoną na podstawie kodu, który pojawił się wcześniej na tym blogu):


GOOGL MSFT NVDA
Date


2019-02-19 NaN NaN NaN
2019-02-20 -0.005255 -0.005199 0.012193
2019-02-21 -0.014617 0.021092 -0.017534
2019-02-22 0.011184 0.014258 0.021955
2019-02-25 0.000690 0.005587 -0.003141
... ... ... ...
2024-02-12 -0.009866 -0.012579 0.001594
2024-02-13 -0.016200 -0.021529 -0.001661
2024-02-14 0.005512 0.009665 0.024567
2024-02-15 -0.021721 -0.007155 -0.016806
2024-02-16 -0.015760 -0.006149 -0.000619


Jeśli chcę obliczyć ważoną stopę zwrotu, to dla każdego poszczególnego wiersza należy wykonać analogiczną operację: (-0.015760 * 25%) + (-0.006149 * 25%) + (-0.000619 * 50%) =
-0.00578687. Wynik powyższego równania analogicznie należy zastosować do każdego z wierszy, ten wyżej odpowiada ostatniemu, tj. z 2024-02-16.

Oczywiście do obliczeń wykorzystamy odpowiednią funkcję. To jest ważnym elementem naszej pracy, uczymy się korzystać z programowania i komputera, aby on wykonywał za nas złożone obliczenia oraz powtarzalne rzeczy pozwalające się zautomatyzować, tak abyśmy mogli skupić się na esencji, a jest nią wyciąganie wniosków rozwijając naszą inteligencję i zdobywać nową wiedzę z danych.

Zastosujmy odpowiednią metodę, która obliczy ważone stopy zwrotu z każdego dnia dla trzech spółek i stworzy tablicę danych z wynikami. Przypominam, każda wartość w tej tablicy będzie łączną sumą stóp zwrotu z trzech spółek dla każdego dnia pomnożona uprzednio przez jej wagi:

weighted_returns = np.dot(returns, weights)

"weighted_returns" przyjmuje i zapisuje obiekt zwrócony przez funkcje "np.dot()" i jest to tablica danych.

Teraz przejdziemy do rocznej stopy zwrotu z portfela inwestycyjnego, bez uwzględnienia ważonych.

annual_returns = returns.mean() * 252

Słowo "annual" to angielskie coroczny/rocznik. Kod tak jak ten i każdy inny najlepiej jeśli jest samo tłumaczący się. Zmienna "returns" przechowuje stopy zwrotu obliczone na podstawie znormalizowanych ceny zamknięcia dla każdego dnia i dla każdej spółki oddzielnie. Używając na niej funkcji "mean()" obliczamy średnią stopę zwrotu dla jednego dnia i mnożymy to przez 252 (ta wartość bierze się stąd, że są to dni handlowe, pozostałe dni w roku to weekendy i święta, które nie uznaje się jako dni handlowe). 

Pamiętajmy, że "returns" zostaje wyliczony z całych danych, które zostały pobrane na początku skryptu, jeśli wybraliście to co było zaproponowane w prezentowanym przeze mnie przykładzie, to jest to okres 5 lat. Średnia dla jednego dnia jest wyliczona z 5 lat i następnie pomnożona przez 252 odpowiadające jednemu rokowi finansowemu, nie dwóm lub więcej.

W moim przypadku obliczone "annual_returns" prezentuje się następująco:

GOOGL: 0,233875
MSFT: 0,320815
NVDA: 0,716708

Weźmy pod uwagę Nvidię. Mnożąc tę wartość razy 100%, mamy 71,6708%. Oznacza to, że zysk z rocznej stopy zwrotu (innymi słowy skalowany do tego jednego roku) oparty o uśrednione dane z 5 lat wyniósł (w zaokrągleniu) 72%. To bardzo dobry wynik. Nie jest to oczywiście jeszcze predykcją, nie jest to jeszcze przepowiadaniem określonej przyszłości z przypisanym do tego prawdopodobieństwem, jednak mając te dane uśrednione z okresu ostatnich 5 lat możemy przepuszczać, że w przybliżeniu i kolejny przyniesie podobną korzyść z inwestycji. Jednak póki co zostawiamy to, z pewnością szeregi czasowe i predykcją pojawią się na blogu, ponieważ leży to w moich zainteresowaniach.

Użyjmy jeszcze raz wag, tym razem na rocznej stopie zwrotów, aby ostatecznie obliczyć zwrot całego portfela uzyskując końcowy wynik. Wiemy już jak przebiega obliczanie wektora/produktu macierzy. Teraz obliczymy ten produkt korzystając z rocznej stopy zwrotu trzymając się tych samych wartości wag: 25%, 25%, 50%. To będzie zwrotem portfela.

portfolio_return = np.dot(annual_returns, weights) * 100

Powyższy kod da nam procentowy zysk całego naszego omawianego portfela i na moim komputerze rezultatem jest wynik, który w zaokrągleniu daje 49,7% zysku. To jest zwrot inwestycji w trzy spółki, Alphabet, Microsoft i Nividia. 

Rozważając to w kontekście danych historycznych (bez robienia predykcji), można powiedzieć, że inwestując w te trzy spółki 1zł, za każdą złotówkę mielibyśmy w przybliżeniu zarobione dodatkowe 50 groszy zysku. Trzeba tylko pamiętać, że są to wnioski wyciągnięte z analizy przeszłości poprzez normalizowanie, uśrednianie i relatywizowanie i nie są to predykcje. 

Z racji tego jak przez te ostatnie 5 lat radzą sobie te spółki, możemy przepuszczać, że kolejny rok również przyniesie owoce, jednak trzeba pamiętać, że nie jest to tożsame z predykcją w stylu: "Roczna stopa zwrotu z kolejnego roku przyniesie 51,24% zysku, z prawdopodobieństwem 84%."

czwartek, 15 lutego 2024

Stopa zwrotu - zwrot portfela

Ten post jest kontynuacją: https://kamilkondrat.blogspot.com/2024/02/stopa-zwrotu-zwrot-portfolio.html

Ta praca odzwierciedla zainteresowanie autora analizą finansową i wykorzystaniem narzędzi informatycznych w jej realizacji. Niniejszy blog nie ma na celu dostarczania porad finansowych, lecz stanowi konstruktywne podejście autora do dzielenia się wiedzą. Autor widzi w tym sposobność do rozwijania swoich umiejętności i zachęcania do wzajemnej dyskusji. Należy pamiętać, że wnioski wynikające z analiz prezentowanych na tym blogu nie powinny być bezpośrednio stosowane do podejmowania decyzji finansowych. Nawet jeśli analizy są dokładne, stanowią one tylko fragment potrzebnych informacji do podjęcia w pełni świadomej decyzji. Brak szerszego kontekstu, w którym znajduje się potencjalny inwestor lub decydent, może prowadzić do niepełnych wniosków.

Dobrze, a więc kontynuujmy. Przejdę od razu bezpośrednio do policzenia zwrotu portfela. Metoda obliczania zwrotu portfela jest podobna do metody używanej dla pojedynczej spółki, z tą różnicą, że stosujemy znormalizowane ceny zamknięcia dla wszystkich spółek w portfelu. A więc zamiast wykorzystać bezpośrednio ceny zamknięcia, to wykorzystamy znormalizowane ceny zamknięcia, które zostały już policzone w poprzednim poście.

returns = (df_adj_close_norm / df_adj_close_norm.shift(1))-1

Tworzę zmienną "returns", która przechowa wynik, to po prostu angielski odpowiednik dla "zwroty." "shift(1)" pobiera wartość z poprzedniego wiersza i korzysta z niego w aktualnym (przesuwa wartość o jeden). W kontekście naszych danych i powyższego kodu oznacza to tyle, że znormalizowana cena z dnia poprzedniego zostaje użyta jako mianownik, który dzieli znormalizowaną cenę danego dnia, który w kontekście dnia poprzedniego jest dniem następującym po nim.

Od tego odejmujemy 1, co daje wartość, którą można przedstawić w formie procentów kiedy przemnożymy to sobie przez 100. Sugeruję wydrukować sobie tę ramkę danych. Aby to zrobić wystarczy, że uruchomicie zmienną "returns" w komórce naciskając shift + enter.

U mnie wygląda to tak:


Jak to interpretować? Weźmy pod uwagę 14 i 15 lutego. -0.032548 pomnożone przez 100% pozwoli nam uzyskać informację, która określi nam ile procent spadła cena akcji względem dnia 14 lutego. W tym przypadku, tj. 15 lutego, odnotowujemy spadek 3,2548% względem dnia poprzedniego.

returns.plot(figsize=(18,10))

A to z kolei, dzięki funkcji "plot" z biblioteki "matplotlib" wyświetla wykres. Ciekawie jest obserwować Nvidię. Można zobaczyć mocno odstające wzrosty i spadki. Liczby "18" i "10" to wartości parametrów określające wielkość wykresu, dobrze się "pobawić" wielkością tych parametrów żeby dostosować wygląd wykresu do swoich preferencji lub pod kątem ewentualnych odbiorców.

Jako dodatek rozdzielę wagi inwestycji. Pozwolę sobie zdecydować, że 25% kapitału zostanie zainwestowane w Alphabet, 25% w Microsoft i 50% Nvidia. Wyraźnie faworyzuję Nvidia. Oczywiście nie traktujcie tego jako rady inwestycyjnej i miejcie też świadomość, że nie jest to jednoznacznym odzwierciedleniem moich przekonań.

Aby to wyrazić przy pomocy abstrakcji jaką jest kod, napiszmy:

weights = np.array([0.25,0.25,0.5])

W następnym wpisie, którym zamknę tę pod-serię "zwrot portfela" zacznę od obliczenia iloczynu macierzy...

środa, 14 lutego 2024

Stopa zwrotu - zwrot portfolio (normalizacja)

Ten post jest kontynuacją serii o stopie zwrotu: https://kamilkondrat.blogspot.com/2024/02/stopa-zwrotu-logarytmiczna-stopa-zwrotu.html

Ta praca odzwierciedla zainteresowanie autora analizą finansową i wykorzystaniem narzędzi informatycznych w jej realizacji. Niniejszy blog nie ma na celu dostarczania porad finansowych, lecz stanowi konstruktywne podejście autora do dzielenia się wiedzą. Autor widzi w tym sposobność do rozwijania swoich umiejętności i zachęcania do wzajemnej dyskusji. Należy pamiętać, że wnioski wynikające z analiz prezentowanych na tym blogu nie powinny być bezpośrednio stosowane do podejmowania decyzji finansowych. Nawet jeśli analizy są dokładne, stanowią one tylko fragment potrzebnych informacji do podjęcia w pełni świadomej decyzji. Brak szerszego kontekstu, w którym znajduje się potencjalny inwestor lub decydent, może prowadzić do niepełnych wniosków.

Zacznijmy od importu interesujących nas modułów. To wszystko już miało okazję pojawić się w poprzednich postach.

import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt

Uruchomienie tej komórki bez żadnych błędów oznacza, że mamy już zainstalowane te moduły, a ich z importowanie przebiegło pomyślnie.

tickers = ['MSFT','GOOGL','NVDA']

Tworzę listę z trzema spółkami. Angielskie "tickers", w kontekście tego co robimy, znaczy to samo co unikalne symbole giełdowe. Te powyżej to Microsoft, holding Alphabet (właściciel Google), NVidia, a tak sobie je wybrałem.

df = yf.download(tickers, period = "5y")

Pobieramy dane giełdowe z ostatnich 5 lat dla tych trzech spółek.

Zastosujemy normalizację do 100. Proces ten polega na skalowaniu serii danych cenowych w taki sposób, by punkt startowy każdej serii był równy 100, co umożliwia łatwe porównywanie procentowych zmian wartości różnych aktywów, niezależnie od ich początkowej wartości.

Przeprowadzenie tego zaczyna się od ustalenia punktu startowego, który jest punktem w czasie, od którego rozpoczyna się analiza i ustawia się dla niego wartość normalizowaną na 100. Następnie, dla każdego kolejnego punktu w czasie, oblicza się wartość normalizowaną, dzieląc bieżącą cenę przez cenę w punkcie startowym i mnożąc wynik przez 100.

Oczywiście normalizację można zastosować dla wartości 14, 57, 256, albo 1570. Tylko takie obliczanie dają w rezultacie mało intuicyjny kontekst i trudniej odnieść je do zmiany procentowej.

Przejdźmy do Pythona. Python w Jupyter to moje standardowe środowisko pracy. Potrzebujemy cen aktywów. Wykorzystamy do tego cenę zamknięcia z naszej ramki danych.

df_adj_close = df['Adj Close']

Stworzyliśmy sobie zmienną "df_adj_close", która zawiera wszystkie wiersze z kolumny z cenami zamknięcia. Punktem startowym dla przeprowadzenia naszej normalizacji będzie pierwszy wiersz naszych danych. Wybierzemy ją korzystając z funkcji "iloc[0]"; iloc czyli "index location", innymi słowy: "Daj mi wartość wiersza o numerze indeksu równym zero." Korzystamy przy tym z biblioteki "pandas" dlatego też korzystamy z gotowych funkcji dostosowanych do pracy z obiektami pandas jak ramka danych, którym jest nasza własna ramka danych i należące do niej kolumny.

df_adj_close_norm = (df_adj_close / df_adj_close.iloc[0]) * 100

I dzięki powyższej linijce kodu mamy wyliczone znormalizowane ceny zamknięcia do 100 dla każdego dnia. Proponuję wyświetlić zawartość naszych obliczeń używając kodu poniżej. Można m.in. odczytać, że wartość akcji Nvidii w ciągu 5 lat wzrosły 19-krotnie!

df_adj_close_norm

To jeszcze nie jest finalny rezultat naszej analizy, którym ma być zwrot całego portfela. Niech to będzie wisienką na naszym ciasteczku, którą zafundujemy sobie w następnym wpisie. Przy okazji rozwinę to o kilka niuansów.

poniedziałek, 12 lutego 2024

Stopa zwrotu - logarytmiczna stopa zwrotu

Kontynuacja wpisu: https://kamilkondrat.blogspot.com/2024/02/stopa-zwrotu-zaczynamy.html

Ta praca odzwierciedla zainteresowanie autora analizą finansową i wykorzystaniem narzędzi informatycznych w jej realizacji. Niniejszy blog nie ma na celu dostarczania porad finansowych, lecz stanowi konstruktywne podejście autora do dzielenia się wiedzą. Autor widzi w tym sposobność do rozwijania swoich umiejętności i zachęcania do wzajemnej dyskusji. Należy pamiętać, że wnioski wynikające z analiz prezentowanych na tym blogu nie powinny być bezpośrednio stosowane do podejmowania decyzji finansowych. Nawet jeśli analizy są dokładne, stanowią one tylko fragment potrzebnych informacji do podjęcia w pełni świadomej decyzji. Brak szerszego kontekstu, w którym znajduje się potencjalny inwestor lub decydent, może prowadzić do niepełnych wniosków.

Zajmiemy się logarytmicznym zwrotem z inwestycji. Pozwala on na obliczenie wartości naszej inwestycji po określonym czasie. Dzięki odjęciu wartości początkowej od wartości końcowej i ujęciu tego względem wartości początkowej uzyskujemy współczynnik zmiany wartości z inwestycji, a po pomnożeniu przez 100% można wyrazić procentowy zwrot z inwestycji.

Aby obliczyć to na danych na których pracujemy (jeśli ktoś nie ma zaczytanych danych, to sugeruję cofnąć się do poprzedniego wpisu po odpowiednią instrukcję która to zrobi). Jeśli aktualnie te dane macie już zaczytane, to przejdźmy do kolejnego kroku:

Potrzebny nam będzie moduł "numpy", który zawiera w sobie funkcje do obliczeń od tych prostszych i bardziej pospolitych po zaawansowane i naukowe. A w tym interesującą nas funkcję, obliczającą logarytm.

import numpy as np

Następnie użyjmy kodu do obliczenia logarytmicznego zwrotu.

df['log_return'] = np.log(df['Adj Close'] / df['Adj Close'].shift(1))

W tej sytuacji korzystamy z bliźniaczego kodu służącego do obliczenia zwykłej stopy zwrotu z tą różnicą, że wynik dzielenia ceny zamknięcia z wartością przesuniętą o jeden wiersz w dół będącą z poprzedniego dnia jeszcze dodatkowo logarytmujemy (a dokładnie: obliczamy logarytm naturalny) przy użyciu gotowej funkcji z biblioteki numpy:"np.log()".

Logarytmiczna stopa zwrotu pokazuje, tak samo jak zwykła stopa zwrotu, względne zmiany w wartości inwestycji. Różnicą jest to, że dzieje się to w ujęciu logarytmicznym, a to sprawia, że te współczynniki mogą być używane w innych kompatybilnych modelach matematycznych opartych o logarytm naturalny, a tego typu modele istnieją i pozwalają uzyskać zaawansowane rezultaty.

Jeszcze inną cechą logarytmicznej stopy zwrotu jest tzw. addytywność. Znaczy to tyle, że jeśli mielibyśmy obliczone dwie logarytmiczne stopy zwrotu z dwóch kolejnych lat oddzielnie, to moglibyśmy je do siebie dodać i mieć jeden wynik z dwóch lat trwania inwestycji łącznie. Dlatego też logarytmiczna stopa zwrotu ma dla nas znaczenie i zostaje odnotowana na tym blogu.

Addytywność jest istotną właściwością przy analizie szeregów czasowych, do której zamierzam się w przyszłości dokładniej przyjrzeć na tym blogu.

sobota, 10 lutego 2024

Stopa zwrotu - zaczynamy

Ta praca odzwierciedla zainteresowanie autora analizą finansową i wykorzystaniem narzędzi informatycznych w jej realizacji. Niniejszy blog nie ma na celu dostarczania porad finansowych, lecz stanowi konstruktywne podejście autora do dzielenia się wiedzą. Autor widzi w tym sposobność do rozwijania swoich umiejętności i zachęcania do wzajemnej dyskusji. Należy pamiętać, że wnioski wynikające z analiz prezentowanych na tym blogu nie powinny być bezpośrednio stosowane do podejmowania decyzji finansowych. Nawet jeśli analizy są dokładne, stanowią one tylko fragment potrzebnych informacji do podjęcia w pełni świadomej decyzji. Brak szerszego kontekstu, w którym znajduje się potencjalny inwestor lub decydent, może prowadzić do niepełnych wniosków.

Dziś rozpocznę nowy projekt. Będzie dotyczył obliczania Stopy Zwrotu. W poprzednim wpisie ( https://kamilkondrat.blogspot.com/2024/02/analiza-sentymentow-danych-giedowych_6.html ) zakończyłem opisywanie wszystkich elementów środowiska, które miały nam służyć do analizy sentymentów. Postanowiłem, że zrobię jednak mały krok w bok i na razie odpuszczę ten projekt, z tego względu, że opiera się o użycie sztucznej inteligencji. 

Generalnie bardzo mi się podoba obszar AI, czytając i zagłębiając się w te wszystkie biblioteki i działanie tych różnych warstw i różnych mechanizmów sterujących uczeniem maszynowym, trenowaniem modeli itp... natknąłem się na, w moim mniemaniu, bardzo wciągające i pomysłowe rzeczy. To jest naprawdę kierunek wart eksploracji i ja na pewno do tego wrócę, natomiast moim celem nie jest bycie stricte znawcą AI, a analitykiem danych w obszarze finansów dla którego AI będzie wsparciem.

Bycie kompetentnym analitykiem danych finansowych wymaga zrozumienia świata finansów i ekonomii oraz umiejętności analizy danych jako takich bez AI, a dopiero potem można w sensowny sposób rozszerzać to o stosowanie machine learningu, tak to widzę.

Dlatego też, zaczynamy:

import pandas as pd
import yfinance as yf

Miałem okazję omawiać te moduły na tym blogu, jednak uważam, że mimo wszystko warto się odnieść jeszcze raz w celu przypomnienia i/lub podkreślenia jakiejś szczególnej cechy.

"Pandas" - to bardzo popularna i bardzo bogata biblioteka obfitująca w różne narzędzia służące do obróbki danych, w analizie danych. Nie zawiera rozwiązań z obszaru sztucznej inteligencji. Natomiast pozwala na wiele "zwykłej" analizy danych i ewentualne przygotowanie danych pod dalszą analizę danych z z zastosowaniem modeli uczenia maszynowego. 

Na szczególną uwagę zasługują struktury danych takie jak "Series" i "DataFrame." "Series" jest jednowymiarową tablicą etykietowanych danych, a upraszczając tę definicję jest to prosta lista, gdzie po przecinku mamy jakieś wartości. Taki format danych odpowiada wektorowi numerycznemu, jeśli umieścimy to w kontekście pracy z AI.

DataFrame - to jest super ważny element. DataFrame, który możemy tłumaczyć jako Ramka Danych, to z kolei dwuwymiarowa struktura danych składająca się z wierszy i kolumn, gdzie każda z kolumn może być innym typem danych, a to odzwierciedla na przykład tabelę w bazie danych stworzoną w SQL.

"Yfinance" - jest biblioteką do pobierania danych finansowych ze strony Yahoo Finance...

df = yf.download("VWSYF", period="5y")

Ja wybrałem spółkę Vestas Wind Systems (o symbolu "VWSYF", co zostało użyte w kodzie), to Duńska firma specjalizująca się w produkcji, instalacji i serwisie turbin wiatrowych. Nie ma nic szczególnego w tym wyborze, możecie wybrać dane jakie chcecie lub podążać za przykładem, jeśli się uczycie.

Obliczmy podstawową stopę zwrotu:

df['simple_return'] = (df['Adj Close'] / df['Adj Close'].shift(1)) - 1

Tworzymy nową kolumnę "simple_return" i dzięki operatorowi przypisania "=" zapisujemy w niej wynik dzielenia ceny zamknięcia z danego dnia "Adj Close" przez cenę zamknięcia z dnia poprzedniego. 

Metoda "shift(1)" pozwala nam sięgnąć cenę zamknięcia o jedną wyżej w wierszach danej kolumny, to ta metoda właśnie sprawia, że faktycznie sięgamy po wartość z poprzedniego dnia lub też możemy powiedzieć, że wartość, która jest wyżej "spada" nam o jedną na dół - zależy od interpretacji...

"-1" pozwala nam odjąć to co mamy "przed przecinkiem" i zostaje nam wartość dziesiętna, którą możemy zamienić sobie na wskaźnik procentowej zmiany wartości względem dnia poprzedniego. 

Wszystko to będzie oczywiście działać kiedy dane będą posortowane rosnąco, czyli w naszej ramce danych daty wcześniejsze będą wierszami, które są wyżej. Warto zauważyć, że w taki sposób pandas sortuje dane automatycznie, dobra nasza.

W związku z tym, że nie chcę robić jakichś rozwiązłych wykładów, a w miarę prostym językiem zaciekawiać i uczyć małymi dawkami (tłumacząc przy tym rzeczy samemu sobie) kończę już ten wpis i do zobaczenia.

wtorek, 6 lutego 2024

Analiza sentymentów danych giełdowych - zamykamy opis używanego środowiska

Ten wpis jest kontynuacją: https://kamilkondrat.blogspot.com/2024/02/analiza-sentymentow-danych-giedowych_4.html

Ta praca odzwierciedla zainteresowanie autora analizą finansową i wykorzystaniem narzędzi informatycznych w jej realizacji. Niniejszy blog nie ma na celu dostarczania porad finansowych, lecz stanowi konstruktywne podejście autora do dzielenia się wiedzą. Autor widzi w tym sposobność do rozwijania swoich umiejętności i zachęcania do wzajemnej dyskusji. Należy pamiętać, że wnioski wynikające z analiz prezentowanych na tym blogu nie powinny być bezpośrednio stosowane do podejmowania decyzji finansowych. Nawet jeśli analizy są dokładne, stanowią one tylko fragment potrzebnych informacji do podjęcia w pełni świadomej decyzji. Brak szerszego kontekstu, w którym znajduje się potencjalny inwestor lub decydent, może prowadzić do niepełnych wniosków.

import tensorflow as tf
from tensorflow.keras.layers import Bidirectional, Conv1D, Dense, 
	Dropout, Embedding, Flatten, Input, LSTM, MaxPool1D
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.preprocessing.text import one_hot, Tokenizer
from tensorflow.keras.utils import to_categorical

"Model" i "Sequential" - to klasy pozwalające na budowę modelu sieci neuronowych. "Sequential" jest stworzony do tworzenia prostszych modeli, w których to każda warstwa będzie przyjmować dane wyłącznie z jednej poprzedzającej warstwy i przekazywać je do następnej warstwy, to przejście jest wyłącznie liniowe.

Natomiast "Model" umożliwia tworzenie bardziej złożonych architektur sieci neuronowych. Wchodzi w to między innymi: wielokrotne wejścia i wyjścia danych do i z modelu, pozwalające na równoległą pracę modelu na przykład nad tłumaczeniem i streszczaniem tekstu jednocześnie; kolejną możliwością "Model" jest na przykład możliwość wielokrotnego wykorzystywania warstw, w różnych częściach modelu.

"pad_sequences" - funkcja służąca do normalizacji sekwencji tekstu. Mając sekwencje (zdania) o różnej długości będziemy zainteresowani zrównaniem ich ze sobą długością, tak aby uzyskać jednorodne partie i następnie użyć ich jako dane wejściowe do naszej sieci neuronowej.

"one_hot" - funkcja "one hot" w TensorFlow tworzy indeksy dla każdego słowa. Myślę, że najprościej będzie mi pokazać to na przykładzie:

Weźmy sobie zdanie "Ala ma kota". Po tokenizacji mamy: ["Ala", "ma", "kota"]. Ustalamy rozmiar naszego słownika. Następnie funkcja "one_hot" nada indeksy: "Ala" -> 1, "ma" -> 2, "kota" -> 3. Całe zdanie będzie, technicznie, listą prezentującą się następująco: [1,2,3].

Aby przekształcić to na wektory "one_hot", które nie są bezpośrednio związane z funkcją "one_hot" w TensorFlow, przyjęłoby to następujący wyraz (słownik o rozmiarze 4, pierwsza wartość zostaje 0, z racji tego, że tak się zwykle liczy od 0, nie od 1):
Dla indeksu 1 "Ala" -> [0, 1, 0, 0]
Dla indeksu 2 "ma" -> [0, 0, 1, 0]
Dla indeksu 3 "kota" -> [0, 0, 0, 1]

"Tokenizer" - jest podobny w działaniu do funkcji "one_hot", tylko że bardziej rozbudowany. Różnica przede wszystkim jest taka, że Tokenizer najpierw analizuje cały tekst, a później przypisuje unikalny indeks unikalnemu słowu. "One_hot" działa inaczej używa do tego prostej funkcji hashującej, bez uprzedniego tworzenia globalnego słownika.

"to_categorical" -przekształca liczby całkowite (wektory klas) na wyżej w tym tekście opisany "one hot" (to ma też określenie: binarna macierz klas - binarna, wyrażona w zerach i jedynkach).

niedziela, 4 lutego 2024

Analiza sentymentów danych giełdowych - dalsze omawianie warstw

Ten wpis jest kontynuacją: https://kamilkondrat.blogspot.com/2024/02/analiza-sentymentow-danych-giedowych.html

Ta praca odzwierciedla zainteresowanie autora analizą finansową i wykorzystaniem narzędzi informatycznych w jej realizacji. Niniejszy blog nie ma na celu dostarczania porad finansowych, lecz stanowi konstruktywne podejście autora do dzielenia się wiedzą. Autor widzi w tym sposobność do rozwijania swoich umiejętności i zachęcania do wzajemnej dyskusji. Należy pamiętać, że wnioski wynikające z analiz prezentowanych na tym blogu nie powinny być bezpośrednio stosowane do podejmowania decyzji finansowych. Nawet jeśli analizy są dokładne, stanowią one tylko fragment potrzebnych informacji do podjęcia w pełni świadomej decyzji. Brak szerszego kontekstu, w którym znajduje się potencjalny inwestor lub decydent, może prowadzić do niepełnych wniosków.

import tensorflow as tf
from tensorflow.keras.layers import Bidirectional, Conv1D, Dense, 
	Dropout, Embedding, Flatten, Input, LSTM, MaxPool1D
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.preprocessing.text import one_hot, Tokenizer
from tensorflow.keras.utils import to_categorical

"Embedding" - służy do zamiany słów na wektory numeryczne. Prościej mówiąc tekst jest prezentowany jako liczba, to pozwala na wydajniejszą analizę danych. To jest bardzo ważny element, gdyż jak wiemy moc komputerów i sztucznej inteligencji łączy się przede wszystkim ze zdolnością obliczeniową. Przekształcanie tekstu na liczby otwiera nas na skorzystanie z tego dobrodziejstwa.

"Flatten" - przekształca dane wielowymiarowe na dane jednowymiarowe. A dokładnie tensory wielowymiarowe na przykład trójwymiarowe jak obrazy (3 wymiary obrazu: wysokość, szerokość, barwa piksela), na tensory jednowymiarowe, czyli wektory numeryczne. W Pythonie wektor numeryczny może być reprezentowany listą liczb całkowitych array z modułu numpy: vector = np.array([1, 2, 3, 4, 5]). To raczej nic skomplikowanego.

Celem jest uzyskanie takiej reprezentacji danych, która najlepiej wpasuje się jako dane wejściowe do trenowanego modelu, zapewniając najbardziej satysfakcjonujące wyniki przy wykorzystaniu struktury sieci neuronowych.

"Input" - obiekt pozwalający na definiowanie kształtu i typu danych wejściowych. W kontekście NLP przykładem będą trzy zdania o 3, 5 i 7 słowach. Optymalizacją przy użyciu "Input" będzie przełożenie tych trzech zdań na stałą strukturę sekwencji 7 słów.

LSTM - w kontekście NLP, warstwa "LSTM " umożliwia zapamiętywanie i analizę sekwencji danych, co jest kluczowe przy przetwarzaniu dłuższych tekstów. Dzięki "LSTM", sztuczna inteligencja może efektywnie przetwarzać informacje zdanie po zdaniu, zachowując przy tym kluczowe informacje z poprzednich fragmentów tekstu. 

To pozwala na głębsze zrozumienie kontekstu i wyłapywanie zależności między elementami tekstu, nawet jeśli są one od siebie oddalone. Takie możliwości LSTM sprawiają, że sieci te są wyjątkowo przydatne w analizie długich tekstów, gdzie konieczne jest uchwycenie szerszego kontekstu.

MaxPool1D - pozwala na ograniczenie liczby cezów/tokenów/słów w analizie sekwencji zdań do tych, które są najistotniejsze z punktu widzenia wyników analizy danych, dzięki czemu możemy zwiększyć generalizację i zapobiec przeuczeniu modelu.

Tym krótkim wpisem zamknę kwestię warstw. Kolejny wpis zacznę od odniesienia się do obiekty "Model."

sobota, 3 lutego 2024

Analiza sentymentów danych giełdowych - witaj Machine Learning

Ten post jest kontynuacją: https://kamilkondrat.blogspot.com/2024/01/analiza-sentymentow-danych-giedowych.html

Ta praca odzwierciedla zainteresowanie autora analizą finansową i wykorzystaniem narzędzi informatycznych w jej realizacji. Niniejszy blog nie ma na celu dostarczania porad finansowych, lecz stanowi konstruktywne podejście autora do dzielenia się wiedzą. Autor widzi w tym sposobność do rozwijania swoich umiejętności i zachęcania do wzajemnej dyskusji. Należy pamiętać, że wnioski wynikające z analiz prezentowanych na tym blogu nie powinny być bezpośrednio stosowane do podejmowania decyzji finansowych. Nawet jeśli analizy są dokładne, stanowią one tylko fragment potrzebnych informacji do podjęcia w pełni świadomej decyzji. Brak szerszego kontekstu, w którym znajduje się potencjalny inwestor lub decydent, może prowadzić do niepełnych wniosków.

Nasz projekt będzie zawierał w sobie budowę modelu NLP opartego o głębokie uczenie maszynowe i TensorFlow. Z importujmy wszystkie istotne elementy dla naszej pracy:

import tensorflow as tf
from tensorflow.keras.layers import Bidirectional, Conv1D, Dense, 
	Dropout, Embedding, Flatten, Input, LSTM, MaxPool1D
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.preprocessing.text import one_hot, Tokenizer
from tensorflow.keras.utils import to_categorical

 "tf" - to alias dla tensorflow. Takie zabiegi skracają i upraszczają kod, czyniąc go prostszym i czytelniejszym.

"layers" - oznacza warstwy w modelu sieci neuronowej. Po słowie kluczowym import znajdują się wymienione te wszystkie warstwy, które pozwolą nam stworzyć działający model.

"'Bidirectional" - w sieci neuronowej pozwala na dwukierunkową analizę tekstu. Przy przetwarzaniu zdania, bada słowa zarówno od lewej do prawej strony, jak i od prawej do lewej strony zdania jednocześnie. Warstwa "Bidirectional" łączy informacje z obu kierunków, tworząc pełny kontekst analizowanego tekstu.

"Conv1D" -  wykrywa wzorce w danych jednowymiarowych. Wykonuje operację konwolucji, która sprawia, że filtrowane dane tekstowe wykrywają określone wzorce, wedle wstępnych, skonfigurowanych, założeń.

Dense - jest to warstwa służąca do budowania głębokich sieci neuronowych. Naurony warstwy Dense są warstwą, która jest połączona z warstwą poprzedzającą, w taki sposób, że każdy neuron warstwy poprzedzającej (dowolnego rodzaju) łączy się z warstwą następującą (w tym przypadku Dense - na tej się teraz skupiamy) i jest połączony z każdym neuronem tej warstwy. A więc jeśli mamy załóżmy 5 poprzedzających neuronów, które przekazują informacje do warstwy Dense mającą 3 neurony, to każdy z tych 5 neuronów będzie połączony z każdym z tych 3 neuronów warstwy Dense. 

Mamy wtedy 3 * 5 = 15 połączeń i jest to 15 okazji do przyjęcia i później przekazania dalej unikalnych "paczek" informacji. Te unikalności w tej całej złożoności odkrywają szczególne sieci połączeń istotne dla rozwiązania danego problemu stając się też podstawą do bardziej zaawansowanych zadań w uczeniu maszynowym.

Dropout - ta warstwa umożliwia losowe wyłączanie pewnego odsetku neuronów (np. 15%) w celu uniknięcia przetrenowania modelu. 

Przetrenowany model to taki, który jest wrażliwy głównie albo tylko na dane treningowe. Na przykład jeśli będziemy uczyć go rozpoznawać zdjęcia pomarańczy korzystając z naszej własnej raczej mało wymyślnej kolekcji pomarańczy (załóżmy że tak jest), to gdy wytrenujemy model, zdjęcie pomarańczy naszego znajomego, który chciał się nam pochwalić fotką z wakacji robiąc zdjęcie pomarańczy w słonecznej Catanii może okazać się, że według naszego modelu nie mamy do czynienia z pomarańczą. 

W wyniku naturalnego słonecznego oświetlenia i innej naturalnej barwy świeżego pomarańcza obraz ten wymknie się naszemu modelowi i jasność i barwa itp. sprawią, że pomarańcz nie zostanie zidentyfikowany jako pomarańcz. 

Podobna sytuacja może być ze zdeformowaną pomarańczą, choć dla naszego oka po skórze owocu i kolorze wyraźnie będziemy widzieć, że to pomarańcz, jednak nasz przetrenowany model odrzuci to zdjęcie jako to które miałoby przedstawiać pomarańczę. 

Model musi trochę "stracić na ostrości," spojrzeć nieco z "przymrużeniem oka", by być w stanie uogólnić, co to znaczy dla niego "pomarańcz." To w ten sposób za kategoryzuje jako pomarańcze też te pomarańcze, które odbiegają od standardowego wyglądu pomarańczy. Żeby to było możliwe, wykorzystujemy do tego właśnie takie narzędzia jak Dropout.

Innym sposobem w jaki może zareagować przetrenowany model będzie zakwalifikowanie obiektu niebędącego pomarańczem, wtedy gdy ukażą się znaki szczególne dla pomarańczy na przykład w przypadku, gdy podamy mu rysunek pomarańczy. Przetrenowany model da się oszukać. Bierze się to z tego, że brak "przymrużenia oka", zdystansowania się i rozmycia sprawił, że model stał się wyjątkowo wrażliwy na znaki szczególne, jeśli by choć trochę stracił w tym rozeznanie, to na pewno odrzuciłby rysunek pomarańcza jako zdjęcie właściwego obiektu pomarańcza zrobionego gdzieś w trójwymiarowej przestrzeni, nie pokwapiłby się na znaki szczególne dla standardowej pomarańczy wyróżnione na rysunku.

Zdjęcia akurat nie mają związku z analizą sentymentów względem zawirowań giełdowych, jednak uważam, że przykłady z analizą obrazów są dobre dla ukazania ogólnej zasady. 

Jednak zaadaptujmy to sobie do analizy sentymentów. Jeśli przetrenowalibyśmy model dając mu zbyt wiele określonych danych, na przykład byłyby to jedynie pozytywne opinie i to jeszcze wyrażane przez ludzi w wąski podobny sposób, to moglibyśmy mieć problem z tym, że opinie negatywne i neutralne również mogłyby być zinterpretowane jako pozytywne, gdyż pewne elementy kategorii "pozytywne", zostały by odnalezione w opiniach obiektywnie negatywnych i neutralnych, które następnie zostałyby zakwalifikowane jako pozytywne przez model. Taka sytuacja analogicznie odpowiadałby sytuacji z rysunkiem pomarańczy, który omyłkowo byłby zakwalifikowany jako pomarańcz.

Jest też inna opcja, analogiczna do sytuacji, w której prawdziwa pomarańcza przez przeuczenie modelu została odrzucona jako nieprawdziwa pomarańcza, gdyż odstawała od standardowej wizji pomarańczy wytrenowanej na mało zróżnicowanych danych służących do wytrenowania modelu. 

W przypadku analizy sentymentów może to wyglądać tak, że z racji tego rodzaju przeuczenia modelu, faktycznie pozytywny sentyment (pozytywna opinia) zostanie odrzucony jako pozytywny i zakwalifikowany jako neutralny lub negatywny, gdyż nie będzie odpowiadał zakwalifikowaniu go do pozytywnego sentymentu, co wyniknie nie z tego, że nie będzie to pozytywna opinia z naszego ludzkiego punktu widzenia, a z tego że przetrenowany modelu będzie oczekiwał "wyidealizowanej" wersji pozytywnego sentymentu, bo tylko takie "wyidealizowane" wersje pozytywnie wyrażanych sentymentów nauczył się rozpoznawać jako pozytywne.

Uniknięciem przeuczenia byłoby wtedy zapobieżeniu temu na przykład przy pomocy omawianej warstwy "Dropout" i zyskaniu "mniejszej ostrości", aby ten mniej "wyidealizowany" pozytywny komentarz lub opinia i zarazem wyraz sentymentu był, mówiąc kolokwialnie, słusznie podciągnięty pod sentyment pozytywny, a bardziej fachowo, zostałby zgeneralizowany jako pozytywny sentyment.

Na zakończenie tego wpisy chcę tylko zaznaczyć, że należy pamiętać iż machine learning obfituje w swojej złożoności rozmaitością scenariuszy tego jakimi drogami potoczy się proces trenowania modelu i jak będzie on zachowywał się w przyszłości wykonując dla nas pracę. Tak samo rozmaite sposoby i techniki zapanowania nad całym proces i kierowania nim są różnorodne, na każdym etapie projektowania modelu uczenia maszynowego można stosować mnogość różnych technik, które chroniłyby by nas przed przetrenowaniem modelu lub innymi rzeczami związanymi z trenowaniem modelu, o tym i o innych rzeczach będę się uczył tak jak Wy i tutaj pisał.

Analiza sentymentów - wpisy na mediach społecznościowych (podział danych)

Ten wpis zaczniemy od stworzenia DataFrame z danymi treningowymi train_df = pd.read_csv('train.csv', encoding='ISO-8859-1')....