Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00680 007764 18990354 na godz. na dobę w sumie
Data science od podstaw. Analiza danych w Pythonie. Wydanie II - książka
Data science od podstaw. Analiza danych w Pythonie. Wydanie II - książka
Autor: Liczba stron: 352
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-283-6154-6 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> python - programowanie
Porównaj ceny (książka, ebook (-30%), audiobook).

Analityka danych jest uważana za wyjątkowo obiecującą dziedzinę wiedzy. Rozwija się błyskawicznie i znajduje coraz to nowsze zastosowania. Profesjonaliści biegli w eksploracji danych i wydobywaniu z nich pożytecznych informacji mogą liczyć na interesującą pracę i bardzo atrakcyjne warunki zatrudnienia. Jednak aby zostać analitykiem danych, trzeba znać matematykę i statystykę, a także nauczyć się programowania. Umiejętności w zakresie uczenia maszynowego i uczenia głębokiego również są ważne. W przypadku tak specyficznej dziedziny, jaką jest nauka o danych, szczególnie istotne jest zdobycie gruntownych podstaw i dogłębne ich zrozumienie.

W tym przewodniku opisano zagadnienia związane z podstawami nauki o danych. Wyjaśniono niezbędne elementy matematyki i statystyki. Przedstawiono także techniki budowy potrzebnych narzędzi i sposoby działania najistotniejszych algorytmów. Książka została skonstruowana tak, aby poszczególne implementacje były jak najbardziej przejrzyste i zrozumiałe. Zamieszczone tu przykłady napisano w Pythonie: jest to język dość łatwy do nauki, a pracę na danych ułatwia szereg przydatnych bibliotek Pythona. W drugim wydaniu znalazły się nowe tematy, takie jak uczenie głębokie, statystyka i przetwarzanie języka naturalnego, a także działania na ogromnych zbiorach danych. Zagadnienia te często pojawiają się w pracy współczesnego analityka danych.

W książce między innymi:

Nauka o danych: bazuj na solidnych podstawach!

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

Tytuł oryginału: Data Science from Scratch: First Principles with Python, 2nd Edition Tłumaczenie: Wojciech Bombik, z wykorzystaniem fragmentów książki „Data science od podstaw. Analiza danych w Pythonie” w przekładzie Konrada Matuka ISBN: 978-83-283-6154-6 © 2020 Helion SA Authorized Polish translation of the English edition of Data Science from Scratch, 2nd Edition ISBN 9781492041139 © 2019 Joel Grus. This translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls all rights to publish and sell the same. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Helion SA dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Helion SA nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Helion SA ul. Kościuszki 1c, 44-100 Gliwice tel. 32 231 22 19, 32 230 98 63 e-mail: helion@helion.pl WWW: http://helion.pl (księgarnia internetowa, katalog książek) Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/dascp2.zip Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/dascp2 Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Printed in Poland. • Kup książkę • Poleć książkę • Oceń książkę • Księgarnia internetowa • Lubię to! » Nasza społeczność Spis treści Przedmowa do drugiego wydania ............................................................................. 11 Przedmowa do pierwszego wydania .......................................................................... 14 Znaczenie danych Czym jest analiza danych? Hipotetyczna motywacja 1. Wprowadzenie .......................................................................................................... 17 17 17 18 19 21 23 25 26 27 Określanie najważniejszych węzłów Analitycy, których możesz znać Wynagrodzenie i doświadczenie Płatne konta Tematy interesujące użytkowników Co dalej? 2. Błyskawiczny kurs Pythona ........................................................................................ 29 29 30 30 31 32 33 33 34 35 35 36 37 38 Zasady tworzenia kodu Pythona Skąd wziąć interpreter Pythona? Środowiska wirtualne Formatowanie za pomocą białych znaków Moduły Polskie znaki diakrytyczne Funkcje Łańcuchy Wyjątki Listy Krotki Słowniki defaultdict 3 Poleć książkęKup książkę Counter Zbiory Przepływ sterowania Wartości logiczne Sortowanie Składanie list Testy automatyczne i instrukcja assert Programowanie obiektowe Obiekty iterowalne i generatory Losowość Wyrażenia regularne Narzędzia funkcyjne Funkcja zip i rozpakowywanie argumentów Argumenty nazwane i nienazwane Adnotacje typów Jak pisać adnotacje typów Witaj w firmie DataSciencester! Dalsza eksploracja 39 39 40 41 42 42 43 43 45 46 47 48 48 49 50 52 53 53 3. Wizualizacja danych .................................................................................................. 55 55 57 60 60 63 Pakiet matplotlib Wykres słupkowy Wykresy liniowe Wykresy punktowe Dalsza eksploracja 4. Algebra liniowa ......................................................................................................... 65 65 69 71 Wektory Macierze Dalsza eksploracja Opis pojedynczego zbioru danych Tendencje centralne Dyspersja 5. Statystyka ................................................................................................................. 73 73 74 76 78 80 81 81 82 Korelacja Paradoks Simpsona Inne pułapki związane z korelacją Korelacja i przyczynowość Dalsza eksploracja 4  Spis treści Poleć książkęKup książkę 6. Prawdopodobieństwo ............................................................................................... 83 83 84 85 87 87 89 91 93 Zależność i niezależność Prawdopodobieństwo warunkowe Twierdzenie Bayesa Zmienne losowe Ciągły rozkład prawdopodobieństwa Rozkład normalny Centralne twierdzenie graniczne Dalsza eksploracja 7. Hipotezy i wnioski ..................................................................................................... 95 95 95 98 99 100 101 102 105 Sprawdzanie hipotez Przykład: rzut monetą Wartości p Przedziały ufności Hakowanie wartości p Przykład: przeprowadzanie testu A-B Wnioskowanie bayesowskie Dalsza eksploracja 8. Metoda gradientu prostego ......................................................................................107 107 108 111 111 112 113 114 Podstawy metody gradientu prostego Szacowanie gradientu Korzystanie z gradientu Dobór właściwego rozmiaru kroku Używanie metody gradientu do dopasowywania modeli Metody gradientu prostego: stochastyczna i minibatch Dalsza eksploracja Strumienie stdin i stdout Wczytywanie plików Podstawowe zagadnienia dotyczące plików tekstowych Pliki zawierające dane rozdzielone separatorem 9. Uzyskiwanie danych .................................................................................................117 117 119 119 120 122 122 124 126 126 127 128 Format JSON (i XML) Korzystanie z interfejsu programistycznego bez uwierzytelniania Poszukiwanie interfejsów programistycznych Pobieranie danych ze stron internetowych HTML i parsowanie Przykład: wypowiedzi kongresmenów Korzystanie z interfejsów programistycznych Spis treści  5 Poleć książkęKup książkę Przykład: korzystanie z interfejsów programistycznych serwisu Twitter Uzyskiwanie danych uwierzytelniających Dalsza eksploracja 128 129 132 Eksploracja danych Eksploracja danych jednowymiarowych Dwa wymiary Wiele wymiarów 10. Praca z danymi ........................................................................................................ 133 133 133 135 136 137 139 140 141 144 145 146 151 Wykorzystanie klasy NamedTuple Dekorator dataclass Oczyszczanie i wstępne przetwarzanie danych Przetwarzanie danych Przeskalowanie Dygresja: tqdm Redukcja liczby wymiarów Dalsza eksploracja 11. Uczenie maszynowe ................................................................................................ 153 153 154 154 157 159 161 162 Modelowanie Czym jest uczenie maszynowe? Nadmierne i zbyt małe dopasowanie Poprawność Kompromis pomiędzy wartością progową a wariancją Ekstrakcja i selekcja cech Dalsza eksploracja 12. Algorytm k najbliższych sąsiadów ............................................................................ 163 163 165 168 171 Model Przykład: dane dotyczące irysów Przekleństwo wymiarowości Dalsza eksploracja 13. Naiwny klasyfikator bayesowski .............................................................................. 173 173 174 175 177 178 180 Bardzo prosty filtr antyspamowy Bardziej zaawansowany filtr antyspamowy Implementacja Testowanie modelu Używanie modelu Dalsza eksploracja 6  Spis treści Poleć książkęKup książkę 14. Prosta regresja liniowa .............................................................................................181 181 184 185 185 Model Korzystanie z algorytmu spadku gradientowego Szacowanie maksymalnego prawdopodobieństwa Dalsza eksploracja 15. Regresja wieloraka ..................................................................................................187 187 188 189 190 191 192 193 194 196 Model Dalsze założenia dotyczące modelu najmniejszych kwadratów Dopasowywanie modelu Interpretacja modelu Poprawność dopasowania Dygresja: ładowanie wstępne Błędy standardowe współczynników regresji Regularyzacja Dalsza eksploracja 16. Regresja logistyczna .................................................................................................197 197 199 201 202 203 206 Problem Funkcja logistyczna Stosowanie modelu Poprawność dopasowania Maszyny wektorów nośnych Dalsza eksploracja 17. Drzewa decyzyjne ....................................................................................................207 207 209 211 211 214 216 217 Czym jest drzewo decyzyjne? Entropia Entropia podziału Tworzenie drzewa decyzyjnego Łączenie wszystkiego w całość Lasy losowe Dalsza eksploracja 18. Sztuczne sieci neuronowe .........................................................................................219 219 221 224 226 228 Perceptrony Jednokierunkowe sieci neuronowe Propagacja wsteczna Przykład: Fizz Buzz Dalsza eksploracja Spis treści  7 Poleć książkęKup książkę 19. Uczenie głębokie ..................................................................................................... 229 229 231 233 235 235 237 238 239 240 242 243 246 247 Tensor Abstrakcja Layer Warstwa Linear Sieci neuronowe jako sekwencje warstw Abstrakcja Loss i optymalizacja Przykład: kolejne podejście do bramki XOR Inne funkcje aktywacji Przykład: kolejne podejście do gry Fizz Buzz Funkcja softmax i entropia krzyżowa Dropout Przykład: MNIST Zapisywanie i wczytywanie modeli Dalsza eksploracja 20. Grupowanie ............................................................................................................ 249 249 250 252 253 255 257 261 Idea Model Przykład: spotkania Wybór wartości parametru k Przykład: grupowanie kolorów Grupowanie hierarchiczne z podejściem aglomeracyjnym Dalsza eksploracja 21. Przetwarzanie języka naturalnego ........................................................................... 263 Chmury wyrazowe 263 Modele n-gram 264 Gramatyka 267 Na marginesie: próbkowanie Gibbsa 269 Modelowanie tematu 271 Wektory słów 275 Rekurencyjne sieci neuronowe 283 Przykład: używanie rekurencyjnej sieci neuronowej na poziomie pojedynczych znaków 285 Dalsza eksploracja 288 Pośrednictwo Centralność wektorów własnych Mnożenie macierzy Centralność 22. Analiza sieci społecznościowych ............................................................................... 289 289 294 294 295 297 299 Grafy skierowane i metoda PageRank Dalsza eksploracja 8  Spis treści Poleć książkęKup książkę 23. Systemy rekomendujące ..........................................................................................301 301 302 303 305 307 311 Ręczne rozwiązywanie problemu Rekomendowanie tego, co jest popularne Filtrowanie kolaboratywne oparte na użytkownikach Filtrowanie kolaboratywne oparte na zainteresowaniach Faktoryzacja macierzy Dalsza eksploracja 24. Bazy danych i SQL .....................................................................................................313 313 316 316 317 319 321 322 324 324 325 326 326 Polecenia CREATE TABLE i INSERT Polecenie UPDATE Polecenie DELETE Polecenie SELECT Polecenie GROUP BY Polecenie ORDER BY Polecenie JOIN Zapytania składowe Indeksy Optymalizacja zapytań Bazy danych NoSQL Dalsza eksploracja 25. Algorytm MapReduce ...............................................................................................327 327 329 330 331 332 334 334 Przykład: liczenie słów Dlaczego warto korzystać z algorytmu MapReduce? Algorytm MapReduce w ujęciu bardziej ogólnym Przykład: analiza treści statusów Przykład: mnożenie macierzy Dodatkowe informacje: zespalanie Dalsza eksploracja 26. Etyka przetwarzania danych .....................................................................................335 335 336 336 337 337 339 339 340 340 Czym jest etyka danych? Ale tak naprawdę to czym jest etyka danych? Czy powinienem przejmować się etyką danych? Tworzenie złych produktów wykorzystujących dane Kompromis między dokładnością a uczciwością Współpraca Interpretowalność Rekomendacje Tendencyjne dane Spis treści  9 Poleć książkęKup książkę Ochrona danych Podsumowanie Dalsza eksploracja 341 342 342 NumPy pandas scikit-learn Wizualizacja R Uczenie głębokie Szukanie danych Zabierz się za analizę IPython Matematyka Korzystanie z gotowych rozwiązań 27. Praktyka czyni mistrza ............................................................................................. 343 343 343 344 344 344 344 345 345 346 346 346 347 347 347 348 348 Hacker News Wozy straży pożarnej Koszulki Tweety na kuli ziemskiej A Ty? 10  Spis treści Poleć książkęKup książkę ROZDZIAŁ 21. Przetwarzanie języka naturalnego Byli na wielkiej uczcie języków i pokradli okruszyny. — William Shakespeare Przetwarzanie języka naturalnego (NLP — ang. natural language processing) jest określeniem technik przetwarzania danych związanych z językiem naturalnym. To bardzo szerokie pole, ale przyj- rzymy się kilku technikom — zaczniemy od prostszych, aby później przejść do bardziej skompli- kowanych. Chmury wyrazowe W rozdziale 1. obliczaliśmy liczbę wystąpień słów w zainteresowaniach użytkowników. Częstotliwość pojawiania się słów można zilustrować za pomocą chmury wyrazowej, w której słowa mają roz- miar zależny od liczby ich wystąpień w analizowanym tekście. Ogólnie rzecz biorąc, analitycy danych rzadko korzystają z chmur słów między innymi dlatego, że punkty umieszczenia tych słów zwykle nie mają żadnego znaczenia, liczy się tylko ich wielkość. Jeżeli kiedykolwiek spotkasz się z koniecznością utworzenia chmury wyrazowej, zastanów się nad tym, czy osie, wzdłuż których umieszczane są słowa, mogą mieć jakieś znaczenie. Załóżmy, że dyspo- nujemy zbiorem słów związanych z nauką o danych, które są opisane dwoma wartościami znaj- dującymi się w zakresie od 0 do 100. Pierwsza z nich określa częstotliwość pojawiania się słowa w ofertach pracy, a druga w życiorysach zawodowych: data = [ ( big data , 100, 15), ( Hadoop , 95, 25), ( Python , 75, 50), ( R , 50, 40), ( machine learning , 80, 20), ( statistics , 20, 60), ( data science , 60, 70), ( analytics , 90, 3), ( team player , 85, 85), ( dynamic , 2, 90), ( synergies , 70, 0), ( actionable insights , 40, 30), ( think out of the box , 45, 10), ( self-starter , 30, 50), ( customer focus , 65, 15), ( thought leadership , 35, 35)] Utworzenie chmury wyrazowej polega tylko na ułożeniu słów w dostępnej przestrzeni w intere- sujący sposób (zobacz rysunek 21.1). 263 Poleć książkęKup książkę Rysunek 21.1. Chmura wyrazowa Chmura wyrazowa wygląda ładnie, ale tak naprawdę przekazuje mało informacji. Ciekawszym rozwiązaniem byłoby umieszczenie wyrazów tak, aby położenie wyrazów w płaszczyźnie pozio- mej było zależne od częstotliwości ich występowania w ofertach pracy, a położenie w płaszczyźnie pionowej było zależne od częstotliwości ich występowania w życiorysach zawodowych. Z takiej wizualizacji można wyciągnąć więcej wniosków (zobacz rysunek 21.2): from matplotlib import pyplot as plt def text_size(total: int) - float: Wynosi 8, jeżeli liczba wystąpień jest równa 0 lub 28, jeżeli liczba wystąpień jest równa 200. return 8 + total / 200 * 20 for word, job_popularity, resume_popularity in data: plt.text(job_popularity, resume_popularity, word, ha= center , va= center , size=text_size(job_popularity + resume_popularity)) plt.xlabel( Popularnosc w ofertach pracy ) plt.ylabel( Popularnosc w CV ) plt.axis([0, 100, 0, 100]) plt.xticks([]) plt.yticks([]) plt.show() Modele n-gram Szefowa działu marketingu chce utworzyć tysiące stron internetowych związanych z nauką o danych w celu przesunięcia serwisu DataSciencester wyżej w wynikach wyszukiwania słów kluczowych związanych z analizą danych. Pomimo usilnych prób przekonania jej, że algorytmy wyszukiwarek są na tyle sprytne, że to rozwiązanie nic nie da, nie udało Ci się jej przekonać. 264  Rozdział 21. Przetwarzanie języka naturalnego Poleć książkęKup książkę Rysunek 21.2. Chmura wyrazowa, która jest mniej atrakcyjna wizualnie, ale przekazuje więcej informacji Oczywiście szefowa działu marketingu nie chce tworzyć samodzielnie tysiąca stron internetowych ani nie chce wynajmować hordy osób do tworzenia treści. Zdecydowała się poprosić Ciebie o pro- gramistyczne wygenerowanie takich stron. Aby to zrobić, musisz dysponować jakimś sposobem modelowania języka. Jednym z możliwych rozwiązań jest zebranie korpusu dokumentów i użycie go w celu nauczenia modelu statystycznego. Zaczniemy od napisanego przez Mike’a Loukidesa tekstu What is data science? (http://oreil.ly/1Cd6ykN), wyjaśniającego, czym jest nauka o danych. Podobnie jak robiliśmy w rozdziale 9., będziemy korzystać z pakietów requests i BeautifulSoup w celu uzyskania danych, ale tym razem musimy zwrócić uwagę na kilka problemów. Pierwszym z nich jest to, że apostrofy znajdujące się w tekście są tak naprawdę znakiem Unicode u \u2019 . W związku z tym potrzebujemy funkcji pomocniczej zastępującej je standardowymi apostrofami: def fix_unicode(text: str) - str: return text.replace(u \u2019 , ) Drugim problemem jest to, że po pobraniu tekstu ze strony internetowej musimy podzielić go na sekwencję słów i kropek (kropki wskazują koniec zdania). Operacje te możemy wykonać za po- mocą funkcji re.findall(): import re from bs4 import BeautifulSoup import requests url = https://www.oreilly.com/ideas/what-is-data-science/ Modele n-gram 265  Poleć książkęKup książkę html = requests.get(url).text soup = BeautifulSoup(html, html5lib ) content = soup.find( div , article-body ) # Znajdź element div oznaczony etykietą article-body. regex = r [\w ]+|[\.] # Wybiera słowa i kropki. document = [] for paragraph in content( p ): words = re.findall(regex, fix_unicode(paragraph.text)) document.extend(words) Oczywiście moglibyśmy (a nawet powinniśmy) oczyścić dane w większym stopniu. Zawierają one wciąż zbędne fragmenty tekstu (pierwszym słowem dokumentu jest np. Section), a dodatkowo dokonaliśmy podziału na kropkach znajdujących się w środku zdania (dotyczy to np. wyrażenia Web 2.0). Nasze dane zawierają ponadto sporo przypisów i list. Pomimo tego będziemy pracować z nimi w niezmienionej formie. Podzieliliśmy tekst na sekwencje słów, a więc możemy modelować język w następujący sposób: zakładając jakieś słowo początkowe (takie jak np. book — z ang. książka), szukamy w dokumencie wszystkich słów znajdujących się za nim. Wybieramy losowo jedno z nich i powtarzamy ten pro- ces aż do uzyskania kropki sygnalizującej koniec zdania. Model taki określamy mianem bigramu, ponieważ zależy on w pełni od częstotliwości pojawiania się bigramów (par słów) w danych, na których model był uczony. Co z pierwszym słowem? Możemy je wybrać losowo na podstawie słów występujących po kropce. Na początek zacznijmy od utworzenia możliwych przejść składających się ze słów. Przypominam, że funkcja zip przerywa działanie po zakończeniu przetwarzania jednego z obiektów wejściowych, a więc polecenie zip(document, document[1:]) utworzy pary kolejnych elementów dokumentu document: from collections import defaultdict transitions = defaultdict(list) for prev, current in zip(document, document[1:]): transitions[prev].append(current) Teraz możemy przystąpić do generowania zdań: def generate_using_bigrams() - str: current = . # Kolejne słowo rozpoczyna nowe zdanie. result = [] while True: next_word_candidates = transitions[current] # bigramy (current, _) current = random.choice(next_word_candidates) # Wylosuj. result.append(current) # Dołącz do wyniku. if current == . : return .join(result) # Zakończ pracę w przypadku kropki. Wygenerowane zdania są bełkotem, ale bełkot ten można umieścić na stronie internetowej mają- cej zawierać treści, które mają brzmieć tak, jakby dotyczyły nauki o danych. Oto przykład wyge- nerowanego zdania: If you may know which are you want to data sort the data feeds web friend someone on trend- ing topics as the data in Hadoop is the data science requires a book demonstrates why visuali- zations are but we do massive correlations across many commercial disk drives in Python lan- guage and creates more tractable form making connections then use and uses it to solve a data. — model bigram 266  Rozdział 21. Przetwarzanie języka naturalnego Poleć książkęKup książkę Jeżeli skorzystamy z trigramów (trzech występujących po sobie słów), to generowany tekst będzie mniej bełkotliwy. Ogólnie rzecz biorąc, możesz korzystać z n-gramów — n występujących po so- bie słów, ale w naszym przypadku wystarczy, że będziemy korzystać z trzech takich słów. Teraz przejście będzie zależeć od dwóch poprzednich słów: trigram_transitions = defaultdict(list) starts = [] for prev, current, next in zip(document, document[1:], document[2:]): if prev == . : # Jeżeli poprzednim „słowem” była kropka, starts.append(current) # to zacznij od tego słowa trigram_transitions[(prev, current)].append(next) Zwróć uwagę na to, że teraz musimy dysponować oddzielnym mechanizmem śledzenia słów po- czątkowych. Zdania generowane są w bardzo podobny sposób: def generate_using_trigrams() - str: current = random.choice(starts) # Wylosuj pierwsze słowo. prev = . # Umieść przed nim kropkę. result = [current] while True: next_word_candidates = trigram_transitions[(prev, current)] next_word = random.choice(next_word_candidates) prev, current = current, next_word result.append(current) if current == . : return .join(result) W ten sposób możemy wygenerować sensowniejsze zdania. Oto przykład takiego zdania: In hindsight MapReduce seems like an epidemic and if so does that give us new insights into how economies work That’s not a question we could even have asked a few years there has been instrumented. — model trigram Zdania brzmią lepiej, ponieważ ograniczyliśmy liczbę opcji dostępnych na każdym etapie ich ge- nerowania, a w wielu sytuacjach algorytm nie miał żadnych dodatkowych opcji, spośród których mógłby dokonać wyboru. W związku z tym często generowane będą zdania (lub przynajmniej długie frazy), które dokładnie w takiej samej formie występowały w oryginalnych danych. Problem ten rozwiązałoby rozszerzenie zbioru danych — zbiór ten warto byłoby rozszerzyć o n-gramy po- chodzące z różnych prac naukowych dotyczących analizy danych. Gramatyka Język może być modelowany również na podstawie zasad gramatyki — reguł tworzenia akcepto- walnych zdań. W szkole podstawowej uczono Cię, czym są części mowy i jak należy je ze sobą łączyć. Jeżeli miałeś kiepskiego nauczyciela od angielskiego, to z pewnością słyszałeś twierdzenie, że zda- nie musi się składać z rzeczownika, po którym ma znaleźć się czasownik. Z reguły tej moglibyśmy skorzystać podczas generowania zdań, gdybyśmy dysponowali listą rzeczowników i czasowników. Gramatyka 267  Poleć książkęKup książkę Zdefiniujemy nieco bardziej złożone zasady gramatyczne: from typing import List, Dict # alias typu Grammar = Dict[str, List[str]] grammar = { _S : [ _NP _VP ], _NP : [ _N , _A _NP _P _A _N ], _VP : [ _V , _V _NP ], _N : [ data science , Python , regression ], _A : [ big , linear , logistic ], _P : [ about , near ], _V : [ learns , trains , tests , is ] } Przyjąłem konwencję, według której nazwy rozpoczynające się od znaków podkreślenia odwołują się do reguł wymagających dalszego rozszerzenia (reguły o innych nazwach nie wymagają tego). Reguła _S jest regułą „zdania”, które wymaga skorzystania z reguły _NP (umieszczenia frazy rzeczowni- kowej) i reguły _VP (umieszczenia frazy czasownikowej po frazie rzeczownikowej). Reguła frazy rzeczownikowej może prowadzić do reguły _V (rzeczownika) lub reguły rzeczownika, po której zostanie umieszczona reguła frazy rzeczownikowej. Zauważ, że reguła _NP zawiera samą siebie na liście własnych reguł składowych. Zasady gramatyki mogą mieć charakter rekurencyjny, co pozwala na generowanie nieskończenie wielu różnych zdań. Jak można skorzystać z tych zasad w celu wygenerowania zdań? Zaczniemy od utworzenia listy zawierającej regułę zdania [ _S ], a następnie będziemy tworzyć kolejne listy, rozbudowując ją o reguły losowo wybrane ze zbioru dozwolonych reguł. Proces rozszerzania budowy list zakończymy po uzyskaniu listy zawierającej same węzły końcowe. Oto przykład progresywnej rozbudowy takich list: [ _S ] [ _NP , _VP ] [ _N , _VP ] [ Python , _VP ] [ Python , _V , _NP ] [ Python , trains , _NP ] [ Python , trains , _A , _NP , _P , _A , _N ] [ Python , trains , logistic , _NP , _P , _A , _N ] [ Python , trains , logistic , _N , _P , _A , _N ] [ Python , trains , logistic , data science , _P , _A , _N ] [ Python , trains , logistic , data science , about , _A , _N ] [ Python , trains , logistic , data science , about , logistic , _N ] [ Python , trains , logistic , data science , about , logistic , Python ] Jak możemy to zaimplementować? Zaczniemy od utworzenia prostej funkcji identyfikującej reguły, które można rozbudować: def is_terminal(token: str) - bool: return token[0] != _ Potrzebujemy jeszcze funkcji zwracającej listę znaczników token do zdania. Będziemy szukać pierw- szego niedającego się rozbudować znacznika. Znalezienie go oznacza zbudowanie całego zdania. 268  Rozdział 21. Przetwarzanie języka naturalnego Poleć książkęKup książkę W przypadku znalezienia znacznika niekońcowego należy wylosować jeden z jego produktów. Je- żeli znacznik jest końcowy, to należy zamiast niego umieścić słowo. W innym przypadku mamy do czynienia z sekwencją rozdzielonych spacjami znaczników niekońcowych, które musimy rozdzie- lić, a następnie zastąpić bieżącymi znacznikami. Tak czy inaczej proces jest powtarzany na no- wym zestawie znaczników. Implementując te wszystkie mechanizmy, uzyskujemy następujący kod: def expand(grammar: Grammar, tokens: List[str]) - List[str]: for i, token in enumerate(tokens): # Ignoruj węzły końcowe. if is_terminal(token): continue # Wylosuj element zastępujący (znaleziono znacznik niekońcowy). replacement = random.choice(grammar[token]) if is_terminal(replacement): tokens[i] = replacement else: # Replacement może być np. _NP _VP , więc musimy podzielić po spacjach tokens = tokens[:i] + replacement.split() + tokens[(i+1):] # Wywołaj rozszerzanie nowej listy znaczników. return expand(grammar, tokens) # W tym momencie wszystkie węzły zostały obsłużone. return tokens Teraz możemy rozpocząć proces generowania zdań: def generate_sentence(grammar: Grammar) - List[str]: return expand(grammar, [ _S ]) Poeksperymentuj — dodaj więcej słów, utwórz więcej reguł, wprowadź kolejne części mowy i wy- generuj tyle stron internetowych, ilu potrzebuje Twoja firma. Zasady gramatyczne są o wiele bardziej interesujące, gdy używa się ich do wykonywania odwrot- nej operacji — parsowania zdań. Wówczas zasady gramatyczne pozwalają na identyfikację pod- miotów i czasowników, a także określenie sensu zdania. Generowanie tekstu za pomocą technik analitycznych to dość ciekawe zagadnienie, ale próba zro- zumienia tekstu jest czymś jeszcze bardziej interesującym (informacje o bibliotekach przeznaczo- nych do analizy tekstu znajdziesz w podrozdziale „Dalsza eksploracja” znajdującym się na końcu tego rozdziału). Na marginesie: próbkowanie Gibbsa Generowanie próbek z niektórych rozkładów jest łatwe. W celu wygenerowania losowych zmien- nych z rozkładu jednostajnego wystarczy użyć funkcji: random.random() W celu wygenerowania losowych wartości zmiennych z rozkładu normalnego wystarczy użyć kodu: inverse_normal_cdf(random.random()) Na marginesie: próbkowanie Gibbsa 269  Poleć książkęKup książkę Niestety generowanie próbek z niektórych rozkładów jest o wiele trudniejsze. Próbkowanie Gibbsa (ang. Gibbs sampling) jest techniką generowania próbek z wielowymiarowych rozkładów wtedy, gdy znamy tylko niektóre uwarunkowania takich rozkładów. Załóżmy, że np. rzucamy dwoma kostkami. Niech x oznacza liczbę oczek wyrzuconą na pierwszej kostce, a y oznacza sumę liczby oczek wyrzuconych na obu kostkach. Załóżmy, że chcemy wyge- nerować dużo par wartości (x, y). W takim przypadku bezpośrednie generowanie par nie stanowi trudności: from typing import Tuple import random def roll_a_die() - int: return random.choice([1,2,3,4,5,6]) def direct_sample() - Tuple[int, int]: d1 = roll_a_die() d2 = roll_a_die() return d1, d1 + d2 A co, gdybyśmy znali tylko rozkłady warunkowe? Rozkład y jest uwarunkowany od x. Znając wartość x, można stwierdzić, że prawdopodobieństwo tego, że y jest równe x + 1, x + 2, x + 3, x + 4, x + 5 lub x + 6, jest w każdym przypadku takie samo: def random_y_given_x(x: int) - int: Może wynosić x + 1, x + 2, ... , x + 6 return x + roll_a_die() Pójście w drugą stronę nie byłoby już takie łatwe. Gdybyśmy wiedzieli, że y = 2, to x musi być równe 1 (obie wartości muszą dać w sumie 2, a na obu kostkach musi wypaść po jednym oczku). Jeżeli wiemy, że y = 3, to x może równie dobrze być równe 1, jak i 2. Z taką samą sytuacją mamy do czynienia, jeżeli y = 11 (wtedy x może być równe 5 lub 6): def random_x_given_y(y: int) - int: if y = 7: # Jeżeli suma jest równa 7 lub mniej, to na pierwszej kostce mogły wypaść wartości 1, 2, ..., (suma - 1). return random.randrange(1, y) else: # Jeżeli suma jest większa od 7, to na pierwszej kostce # mogły równie prawdopodobnie wypaść wartości (suma - 6), (suma - 5), ..., 6. return random.randrange(y - 6, 7) Próbkowanie Gibbsa działa tak, że zaczynamy od pierwszej (poprawnej) wartości dla x i y, a na- stępnie powtarzamy zastępowanie x losową wartością wybraną pod warunkiem y i zastępowanie y losową wartością wybraną pod warunkiem x. Po kilku iteracjach uzyskane wartości x i y będą re- prezentowały próbkę z rozkładu bezwarunkowo połączonego: def gibbs_sample(num_iters: int = 100) - Tuple[int, int]: x, y = 1, 2 # To tak naprawdę nie ma znaczenia. for _ in range(num_iters): x = random_x_given_y(y) y = random_y_given_x(x) return x, y Możemy sprawdzić, że uzyskamy wynik podobny do bezpośredniego próbkowania: def compare_distributions(num_samples: int = 1000) - Dict[int, List[int]]: counts = defaultdict(lambda: [0, 0]) for _ in range(num_samples): 270  Rozdział 21. Przetwarzanie języka naturalnego Poleć książkęKup książkę counts[gibbs_sample()][0] += 1 counts[direct_sample()][1] += 1 return counts Z techniki tej skorzystamy w kolejnym podrozdziale. Modelowanie tematu Tworząc mechanizm rekomendacyjny w rozdziale 1., szukaliśmy dokładnie pasujących do siebie elementów na listach zainteresowań deklarowanych przez użytkowników. Bardziej zaawansowanym sposobem na zrozumienie zainteresowań użytkowników może być próba zidentyfikowania tematów z nimi związanych. Często w tego typu przypadkach korzysta się z analizy LDA (ang. Latent Dirichlet Analysis). Analiza ta jest stosowana w celu określania tematów zbioru dokumentów. Użyjemy jej do przetworzenia dokumentów składających się z zainteresowań każdego z użytkowników. Analiza LDA przypomina nieco naiwny klasyfikator bayesowski, który zbudowaliśmy w rozdziale 13. — zakłada model probabilistyczny dokumentów. Nie będziemy zagłębiać się zbyt głęboko w szcze- góły matematyczne, ale możemy przyjąć, że model zakłada, iż:  Istnieje określona liczba K tematów.  Istnieje zmienna losowa przypisująca każdemu tematowi rozkład probabilistyczny słów. Roz- kład ten należy traktować jako rozkład prawdopodobieństwa wystąpienia słowa w w temacie k.  Istnieje inna zmienna losowa przypisująca każdemu dokumentowi rozkład prawdopodobień- stwa tematów. Rozkład ten należy traktować jako mieszaninę tematów dokumentu d.  Każde słowo w dokumencie zostało wygenerowane poprzez losowy wybór tematu (z rozkładu tematów dokumentu), a następnie losowy wyraz słowa (z rozkładu słów określonego wcześniej tematu). Dysponujemy zbiorem dokumentów (documents). Każdy z tych dokumentów jest listą słów. Dys- ponujemy również odpowiadającym temu zbiorowi zbiorem tematów (documents_topics), który przypisuje temat (w tym przypadku liczbę z zakresu od 0 do K – 1) do każdego słowa znajdującego się w każdym dokumencie. W związku z tym piąte słowo w czwartym dokumencie można odczytać za pomocą kodu: documents[3][4] Temat, z którego to słowo zostało wybrane, można określić za pomocą kodu: document_topics[3][4] Mamy więc do czynienia z bardzo jawną definicją rozkładu dokumentów według tematów i nie- jawną definicją rozkładu tematów według słów. Możemy oszacować prawdopodobieństwo tego, że temat 1 zwróci określone słowo, porównując liczbę określającą, ile razy to słowo zostało wygenerowane przez ten temat, z liczbą wszystkich operacji generowania słów z tego tematu. (Podobne rozwiązanie stosowaliśmy w rozdziale 13. — porównywa- liśmy liczbę wystąpień danego słowa w spamie z całkowitą liczbą słów pojawiających się w spamie). Modelowanie tematu 271  Poleć książkęKup książkę Tematy są tylko liczbami, ale możemy nadać im również nazwy opisowe poprzez analizę słów, do których przywiązują one największą wagę. Potrzebujemy sposobu wygenerowania zbioru docu- ment_topics. Do tego przyda się nam właśnie próbkowanie Gibbsa. Zaczynamy od losowego przypisania tematu do wszystkich słów znajdujących się we wszystkich dokumentach. Na razie będziemy przetwarzać każdy dokument słowo po słowie. Dla danego słowa występującego w danym dokumencie będziemy konstruować wagi każdego tematu, który zależy od (bieżącego) rozkładu tematów w tym dokumencie i (bieżącego) rozkładu słów tego tematu. Następnie używamy tych wag do próbkowania nowego tematu dla danego słowa. Jeżeli powtó- rzymy ten proces wielokrotnie, to uzyskamy łączną próbkę z rozkładu temat-słowo i rozkładu dokument-temat. Na początek musimy utworzyć funkcję losującą indeks na podstawie dowolnego zestawu wag: def sample_from(weights: List[float]) - int: zwraca i z prawdopodobieństwem weights[i] / sum(weights) total = sum(weights) rnd = total * random.random() # Rozkład jednostajny od 0 do sumy. for i, w in enumerate(weights): rnd -= w # Zwraca najmniejszą wartość i spełniającą warunek: if rnd = 0: return i # weights[0] + … + weights[i] = rnd. Po przekazaniu do tej funkcji wag [1, 1, 3] będzie ona zwracała przez jedną piątą czasu 0, przez jedną piątą czasu 1, a przez trzy piąte czasu będzie zwracała 2. Możemy to sprawdzić: from collections import Counter # wylosuj 1000 razy i policz draws = Counter(sample_from([0.1, 0.1, 0.8]) for _ in range(1000)) assert 10 draws[0] 190 # powinno być ~10 assert 10 draws[1] 190 # powinno być ~10 assert 650 draws[2] 950 # powinno być ~80 assert draws[0] + draws[1] + draws[2] == 1000 Naszymi dokumentami są zainteresowania użytkowników. Mają one następującą formę: documents = [ [ Hadoop , Big Data , HBase , Java , Spark , Storm , Cassandra ], [ NoSQL , MongoDB , Cassandra , HBase , Postgres ], [ Python , scikit-learn , scipy , numpy , statsmodels , pandas ], [ R , Python , statistics , regression , probability ], [ machine learning , regression , decision trees , libsvm ], [ Python , R , Java , C++ , Haskell , programming languages ], [ statistics , probability , mathematics , theory ], [ machine learning , scikit-learn , Mahout , neural networks ], [ neural networks , deep learning , Big Data , artificial intelligence ], [ Hadoop , Java , MapReduce , Big Data ], [ statistics , R , statsmodels ], [ C++ , deep learning , artificial intelligence , probability ], [ pandas , R , Python ], [ databases , HBase , Postgres , MySQL , MongoDB ], [ libsvm , regression , support vector machines ] ] Spróbujemy znaleźć K = 4 tematów. 272  Rozdział 21. Przetwarzanie języka naturalnego Poleć książkęKup książkę W celu obliczenia wag próbkowania musimy śledzić kilka wartości. Musimy utworzyć strukturę da- nych, w której je umieścimy.  Ile razy każdy temat jest przypisywany do każdego dokumentu: #Lista liczników; po jednym dla każdego dokumentu. document_topic_counts = [Counter() for _ in documents]  Ile razy każde słowo jest przypisywane do każdego tematu: #Lista liczników; po jednym dla każdego tematu. topic_word_counts = [Counter() for _ in range(K)]  Suma słów przypisanych do każdego tematu: #Lista liczb; po jednej dla każdego tematu. topic_counts = [0 for _ in range(K)]  Całkowita liczba słów znajdujących się w każdym dokumencie: #Lista liczb; po jednej dla każdego dokumentu. document_lengths = [len(document) for document in documents]  Liczba unikalnych słów: distinct_words = set(word for document in documents for word in document) W = len(distinct_words)  Liczba dokumentów: D = len(documents) Po określeniu tych wartości możemy ustalić np. liczbę słów występujących w dokumencie documents[3] dotyczących tematu nr 1: document_topic_counts[3][1] Ponadto możemy określić, ile razy słowo nlp pojawiło się w związku z tematem nr 2: topic_word_counts[2][ nlp ] Teraz możemy zdefiniować nasze funkcje prawdopodobieństwa warunkowego. Każda z nich, po- dobnie jak w rozdziale 13., będzie miała czynnik wygładzający, zapewniający to, że każdy temat ma niezerową szansę na bycie wybranym w dowolnym dokumencie, oraz to, że każde słowo ma niezerową szansę na bycie wybranym w dowolnym temacie: def p_topic_given_document(topic: int, d: int, alpha: float = 0.1) - float: Ułamek słów w dokumencie _d_, które są przypisane do tematu _topic_ plus wartość wygładzająca. return ((document_topic_counts[d][topic] + alpha) / (document_lengths[d] + K * alpha)) def p_word_given_topic(word: str, topic: int, beta: float = 0.1) - float: Ułamek słów _word_ przypisanych do tematu _topic_, plus wartość wygładzająca. return ((topic_word_counts[topic][word] + beta) / (topic_counts[topic] + W * beta)) Z funkcji tych będziemy korzystać w celu utworzenia wag do aktualizacji tematów: def topic_weight(d: int, word: str, k: int) - float: Zwróć wagę k-tego tematu na podstawie dokumentu i występującego w nim słowa return p_word_given_topic(word, k) * p_topic_given_document(k, d) Modelowanie tematu 273  Poleć książkęKup książkę def choose_new_topic(d: int, word: str) - int: return sample_from([topic_weight(d, word, k) for k in range(K)]) Sposób definicji funkcji topic weight obliczającej wagi tematów wynika z pewnych zasad mate- matycznych, ale niestety jest to zbyt krótka książka, aby wyjaśnić w niej szczegóły matematyczne tej implementacji. Na szczęście działanie tej funkcji jest intuicyjne — prawdopodobieństwo wy- brania dowolnego tematu na podstawie słowa i dokumentu zależy od tego, jakie jest prawdopodobień- stwo wystąpienia wybranego tematu w danym dokumencie, a także od prawdopodobieństwa wy- stąpienia danego słowa w wybranym temacie. Dysponujemy już wszystkimi niezbędnymi mechanizmami. Zaczniemy od przypisania każdego słowa do wybranego losowo tematu, a następnie wygenerujemy odpowiednie wartości liczników: random.seed(0) document_topics = [[random.randrange(K) for word in document] for document in documents] for d in range(D): for word, topic in zip(documents[d], document_topics[d]): document_topic_counts[d][topic] += 1 topic_word_counts[topic][word] += 1 topic_counts[topic] += 1 Naszym celem jest uzyskanie połączonej próbki rozkładu tematy-słowa i rozkładu dokumenty- tematy. Zrobimy to za pomocą próbkowania Gibbsa korzystającego ze zdefiniowanych wcześniej prawdopodobieństw warunkowych: import tqdm for iter in tqdm.trange(1000): for d in range(D): for i, (word, topic) in enumerate(zip(documents[d], document_topics[d])): # Odejmij parę słowo-temat od sumy, aby nie wpływała ona na wagi. document_topic_counts[d][topic] -= 1 topic_word_counts[topic][word] -= 1 topic_counts[topic] -= 1 document_lengths[d] -= 1 # Wybierz nowy temat na podstawie wag. new_topic = choose_new_topic(d, word) document_topics[d][i] = new_topic # Dodaj go z powrotem do sumy. document_topic_counts[d][new_topic] += 1 topic_word_counts[new_topic][word] += 1 topic_counts[new_topic] += 1 document_lengths[d] += 1 Czym są tematy? Są one liczbami 0, 1, 2 i 3. Jeżeli chcemy nadać im nazwy, to musimy je określić samodzielnie. Przyjrzyjmy się pięciu słowom, które uzyskały największe wagi w każdym z tematów (zobacz tabelę 21.1): for k, word_counts in enumerate(topic_word_counts): for word, count in word_counts.most_common(): if count 0: print(k, word, count) 274  Rozdział 21. Przetwarzanie języka naturalnego Poleć książkęKup książkę Tabela 21.1. Najpopularniejsze słowa występujące w poszczególnych tematach Temat 0 Java Big Data Hadoop deep learning artificial intelligence Temat 1 R statistics Python probability pandas Temat 2 HBase Postgres MongoDB Cassandra NoSQL Temat 3 regression libsvm scikit-learn machine learning neural networks Przyglądając się słowom przypisanym do tematów, przypisałbym im następujące nazwy: topic_names = [ Big Data and programming languages , # Duże zbiory danych i języki programowania Python and statistics , # Python i statystyka databases , # Bazy danych machine learning ] # Uczenie maszynowe Teraz możemy sprawdzić, jak model przypisuje tematy do zainteresowań użytkowników: for document, topic_counts in zip(documents, document_topic_counts): print(document) for topic, count in topic_counts.most_common(): if count 0: print(topic_names[topic], count) print() Po uruchomieniu tego kodu uzyskamy następujące dane: [ Hadoop , Big Data , HBase , Java , Spark , Storm , Cassandra ] Big Data and programming languages 4 databases 3 [ NoSQL , MongoDB , Cassandra , HBase , Postgres ] databases 5 [ Python , scikit-learn , scipy , numpy , statsmodels , pandas ] Python and statistics 5 machine learning 1 Nazwy tematów zawierają spójnik „i”, a więc prawdopodobnie można wydzielić większą liczbę tematów, ale niestety dysponujemy zbyt małym treningowym zbiorem danych, aby wytrenować model w celu rozpoznawania większej liczby tematów. Wektory słów Duża część ostatnich postępów w przetwarzaniu języka naturalnego ma związek z uczeniem głę- bokim. W dalszej części tego rozdziału przyjrzymy się kilku z nich, używając narzędzi, które napi- saliśmy w rozdziale 19. Jedną z ważnych innowacji jest przedstawianie słów jako kilkuwymiarowych wektorów. Można z nimi zrobić wiele rzeczy — porównywać, dodawać do siebie lub wykorzystywać w modelach uczenia maszynowego. Wektory takie mają kilka przydatnych cech. Na przykład podobne słowa są zazwyczaj przedstawiane podobnymi wektorami. Często więc wektor słowa „duży” będzie zbli- żony do wektora słowa „wielki”, dzięki czemu modele działające na takich wektorach będą mogły (do pewnego stopnia) radzić sobie z synonimami niskim kosztem. Wektory wykazują też ciekawe właściwości arytmetyczne. Na przykład, jeżeli w niektórych modelach od wektora słowa „król” odejmiemy wektor słowa „mężczyzna” i dodamy wektor słowa „kobieta”, to Wektory słów 275  Poleć książkęKup książkę możemy uzyskać wektor zbliżony do słowa „królowa”. Rozważania na temat tego, co to dokładnie oznacza, mogą być interesujące, ale nie będziemy się nimi teraz zajmować. Stworzenie takich wektorów dla dużego słownika jest trudnym zadaniem, więc zazwyczaj bę- dziemy korzystać z korpusów językowych. Jest kilka sposobów działania, ale z grubsza wszystkie wyglądają tak: 1. Pobierz tekst. 2. Utwórz zbiór danych tak, aby spróbować przewidywać słowa na podstawie słów sąsiadują- cych (lub na odwrót — na podstawie słowa przewidywać jego sąsiadów). 3. Wytrenuj sieć neuronową, aby potrafiła poradzić sobie z tym zadaniem. 4. Utwórz wektory słów z wewnętrznych stanów wytrenowanych neuronów. Naszym celem jest przewidywanie słów na podstawie wyrazów znajdujących się w pobliżu. Słowa, które pojawiają się w podobnych kontekstach (a więc również w towarzystwie podobnych wyrazów), powinny mieć podobne stany wewnętrzne, więc ich wektory również będą zbliżone do siebie. Podobieństwo wektorów będziemy mierzyć przy użyciu tzw. podobieństwa kosinusowego, które przyjmuje wartości z przedziału od –1 do 1 i określa stopień, w jakim dwa wektory wskazują ten sam kierunek: from scratch.linear_algebra import dot, Vector import math def cosine_similarity(v1: Vector, v2: Vector) - float: return dot(v1, v2) / math.sqrt(dot(v1, v1) * dot(v2, v2)) assert cosine_similarity([1., 1, 1], [2., 2, 2]) == 1, ten sam kierunek assert cosine_similarity([-1., -1], [2., 2]) == -1, przeciwny kierunek assert cosine_similarity([1., 0], [0., 1]) == 0, ortogonalne Wytrenujmy kilka wektorów, aby zobaczyć, jak to działa. Na początek będziemy potrzebować przykładowego zbioru danych. Zazwyczaj wektory słów są uzy- skiwane poprzez modele trenowane na milionach lub nawet miliardach słów. Ponieważ nasze pro- gramy nie poradzą sobie z tak dużymi ilościami danych, stworzymy sztuczny zbiór danych o określo- nej strukturze: colors = [ red , green , blue , yellow , black , ] nouns = [ bed , car , boat , cat ] verbs = [ is , was , seems ] adverbs = [ very , quite , extremely , ] adjectives = [ slow , fast , soft , hard ] def make_sentence() - str: return .join([ The , random.choice(colors), random.choice(nouns), random.choice(verbs), random.choice(adverbs), random.choice(adjectives), . ]) 276  Rozdział 21. Przetwarzanie języka naturalnego Poleć książkęKup książkę NUM_SENTENCES = 50 random.seed(0) sentences = [make_sentence() for _ in range(NUM_SENTENCES)] W ten sposób wygenerujemy wiele zdań o podobnej strukturze, ale składających się z różnych słów, takich jak na przykład: „The green boat seems quite slow”. W utworzonych zdaniach kolory będą zazwyczaj pojawiać się w podobnych kontekstach, podobnie jak rzeczowniki i pozostałe czę- ści mowy. Jeżeli więc przypiszemy wektory słów w odpowiedni sposób, słowa z podobnych kate- gorii powinny uzyskać podobne wektory. Podczas prawdziwej analizy wektorów prawdopodobnie korzystalibyśmy ze zbioru mającego miliony zdań. W takim przypadku uzyskalibyśmy określone konteksty z samego tekstu. Analizując jedynie 50 zdań, musimy niestety narzucić konteksty w nieco sztuczny sposób. Do dalszej analizy zamienimy słowa na identyfikatory. Wykorzystamy do tego celu klasę Vocabulary: from scratch.deep_learning import Tensor class Vocabulary: def __init__(self, words: List[str] = None) - None: self.w2i: Dict[str, int] = {} # mapowanie słowa na jego identyfikator self.i2w: Dict[int, str] = {} # mapowanie identyfikatora na słowo for word in (words or []): # jeżeli na wejściu były jakieś słowa, to je dodajemy. self.add(word) @property def size(self) - int: ile słów jest w słowniku return len(self.w2i) def add(self, word: str) - None: if word not in self.w2i: # jeżeli słowo jest nowe, word_id = len(self.w2i) # znajdź kolejny identyfikator. self.w2i[word] = word_id # dodaj do mapowania słów na identyfikatory self.i2w[word_id] = word # dodaj do mapowania identyfikatorów na słowa def get_id(self, word: str) - int: zwraca identyfikator słowa (lub None) return self.w2i.get(word) def get_word(self, word_id: int) - str: zwraca słowo o określonym identyfikatorze (lub None) return self.i2w.get(word_id) def one_hot_encode(self, word: str) - Tensor: word_id = self.get_id(word) assert word_id is not None, f nieznane słowo {word} return [1.0 if i == word_id else 0.0 for i in range(self.size)] Wszystkie te rzeczy można by zrobić ręcznie, ale wygodniej jest je mieć zebrane w jednej klasie. Możemy ją teraz przetestować: vocab = Vocabulary([ a , b , c ]) assert vocab.size == 3, w słowniku są 3 słowa assert vocab.get_id( b ) == 1, b powinno mieć identyfikator 1 assert vocab.one_hot_encode( b ) == [0, 1, 0] Wektory słów 277  Poleć książkęKup książkę assert vocab.get_id( z ) is None, w słowniku nie ma z assert vocab.get_word(2) == c , identyfikator 2 powinien oznaczać c vocab.add( z ) assert vocab.size == 4, teraz w słowniku są 4 słowa assert vocab.get_id( z ) == 3, teraz z powinno mieć identyfikator 3 assert vocab.one_hot_encode( z ) == [0, 0, 0, 1] Przydałaby się również pomocnicza funkcja do zapisywania i odczytywania słownika, podobnie jak robiliśmy w przypadku modeli uczenia głębokiego: import json def save_vocab(vocab: Vocabulary, filename: str) - None: with open(filename, w ) as f: json.dump(vocab.w2i, f) # potrzebujemy zapisać jedynie w2i def load_vocab(filename: str) - Vocabulary: vocab = Vocabulary() with open(filename) as f: # odczytaj w2i i na jego podstawie wygeneruj i2w vocab.w2i = json.load(f) vocab.i2w = {id: word for word, id in vocab.w2i.items()} return vocab Będziemy korzystać z modelu wektorów słów, nazywanego skip-gram, który otrzymuje na wejściu słowo, a następnie określa prawdopodobieństwo, z jakim inne słowa mogą znaleźć się w jego są- siedztwie. Dostarczymy mu pary treningowe (word, nearby_word) i spróbujemy zminimalizować funkcję straty SoftmaxCrossEntropy. Inny popularny model, tzw. CBOW (ang. continuous bag-of-words), na podstawie sąsiadujących słów przewiduje konkretne słowo. Zajmiemy się teraz zaprojektowaniem sieci neuronowej. W jej sercu będzie znajdować się warstwa Em- bedding przekształcająca identyfikator słowa na jego wektor. Możemy w tym celu użyć zwykłej ta- beli słownikowej. Następnie prześlemy wektor słów do warstwy Linear, która ma tyle samo wyjść, ile jest słów w słowni- ku. Podobnie jak wcześniej, użyjemy funkcji softmax do przekonwertowania wartości wyjścio- wych na prawdopodobieństwa wystąpień słów sąsiadujących. Używając metody gradientu, bę- dziemy aktualizować wektory w tabeli słownikowej. Po wytrenowaniu modelu tabela ta będzie zawierała odpowiednie wektory słów. Na początek stwórzmy warstwę Embedding. Możemy jej użyć również do obiektów innych niż słowa, więc napiszemy ogólną klasę, do której później dopiszemy podklasę TextEmbedding przeznaczoną jedynie dla wektorów słów. W konstruktorze klasy określimy liczbę i wymiary wektorów, które mają zostać utworzone (począt- kowo będą to losowe wektory o rozkładzie normalnym). from typing import Iterable from scratch.deep_learning import Layer, Tensor, random_tensor, zeros_like class Embedding(Layer): def __init__(self, num_embeddings: int, embedding_dim: int) - None: self.num_embeddings = num_embeddings 278  Rozdział 21. Przetwarzanie języka naturalnego Poleć książkęKup książkę self.embedding_dim = embedding_dim # jeden wektor o rozmiarze size embedding_dim dla każdego przekształcenia self.embeddings = random_tensor(num_embeddings, embedding_dim) self.grad = zeros_like(self.embeddings) # zapisz ostatni input id self.last_input_id = None W naszym przypadku warstwa ta będzie przetwarzała jedno słowo w danym momencie. Istnieją modele, w których można przetwarzać sekwencje słów i tworzyć z nich sekwencje wektorów (na przykład w opisanym wcześniej modelu CBOW), jednak dla uproszczenia pozostaniemy przy przetwarzaniu pojedynczych słów. def forward(self, input_id: int) - Tensor: Wybiera wektor odpowiadający wejściowemu identyfikatorowi self.input_id = input_id # zapamiętaj, aby użyć przy propagacji wstecznej return self.embeddings[input_id] Podczas propagacji wstecznej będziemy potrzebować gradientu dla wybranego wektora, więc skonstruujemy odpowiedni gradient dla self.embeddings, który wynosi zero dla każdego wektora z wyjątkiem wybranego: def backward(self, gradient: Tensor) - None: # korzystamy z gradientu z poprzedniego przetwarzania # jest to znacznie szybsze niż tworzenie zerowego tensora za każdym razem. if self.last_input_id is not None: zero_row = [0 for _ in range(self.embedding_dim)] self.grad[self.last_input_id] = zero_row self.last_input_id = self.input_id self.grad[self.input_id] = gradient Musimy jeszcze nadpisać odpowiednie metody dla parametrów i gradientów: def params(self) - Iterable[Tensor]: return [self.embeddings] def grads(self) - Iterable[Tensor]: return [self.grad] Jak wspomnieliśmy wcześniej, napiszemy jeszcze podklasę obsługującą tylko wektory słów. W tym przypadku liczba wektorów jest określona przez słownik, więc możemy wykorzystać go jako pa- rametr wejściowy: class TextEmbedding(Embedding): def __init__(self, vocab: Vocabulary, embedding_dim: int) - None: # wywołaj konstruktor klasy Embedding super().__init__(vocab.size, embedding_dim) # umieść słownik w obiekcie klasy self.vocab = vocab Pozostałe funkcje klasy Embedding będą działały bez zmian, ale dodamy jeszcze kilka nowych, spe- cyficznych dla pracy z tekstem. Chcielibyśmy na przykład mieć możliwość uzyskiwania wektora dla konkretnego słowa (to nie jest część interfejsu Layer, ale zawsze możemy dodawać nowe funk- cje do konkretnych warstw według naszych potrzeb). Wektory słów 279  Poleć książkęKup książkę def __getitem__(self, word: str) - Tensor: word_id = self.vocab.get_id(word) if word_id is not None: return self.embeddings[word_id] else: return None Dzięki tej funkcji specjalnej będziemy mogli uzyskiwać wektory przy użyciu indeksów: word_vector = embedding[ black ] Chcielibyśmy mieć również możliwość uzyskiwania informacji na temat słowa będącego najbliżej danego wyrazu: def closest(self, word: str, n: int = 5) - List[Tuple[float, str]]: Zwraca n najbliższych słów na podstawie podobieństwa kosinusowego vector = self[word] # wyznacz pary (similarity, other_word) i posortuj według największego podobieństwa scores = [(cosine_similarity(vector, self.embeddings[i]), other_word) for other_word, i in self.vocab.w2i.items()] scores.sort(reverse=True) return scores[:n] Nasza warstwa Embedding zwraca wektory, które możemy przekazać do warstwy Linear. Teraz możemy już przygotować dane treningowe. Dla każdego słowa wejściowego wybierzemy jako wynik dwa słowa po jego lewej i dwa po jego prawej stronie. Zacznijmy od zamiany wszystkich liter na małe i rozbicia zdań na słowa: import re # To wyrażenie regularne nie jest szczególnie zaawansowane, ale w naszym przypadku wystarczy. tokenized_sentences = [re.findall( [a-z]+|[.] , sentence.lower()) for sentence in sentences] a następnie zbudujmy słownik: # Tworzenie słownika (czyli mapowania słowo - identyfikator słowa) na podstawie tekstu. vocab = Vocabulary(word for sentence_words in tokenized_sentences for word in sentence_words) Możemy teraz wygenerować dane treningowe: from scratch.deep_learning import Tensor, one_hot_encode inputs: List[int] = [] targets: List[Tensor] = [] for sentence in tokenized_sentences: for i, word in enumerate(sentence): # dla każdego słowa for j in [i - 2, i - 1, i + 1, i + 2]: # weź najbliższe otoczenie, if 0 = j len(sentence): # które znajduje się w tekście, nearby_word = sentence[j] # i pobierz z niego słowa. # dodaje input, czyli identyfikator word_id pierwotnego słowa. inputs.append(vocab.get_id(word)) # dodaje target, czyli identyfikatory najbliższych słów. targets.append(vocab.one_hot_encode(nearby_word)) 280  Rozdział 21. Przetwarzanie języka naturalnego Poleć książkęKup książkę Używając stworzonych przez nas narzędzi, możemy zbudować model: from scratch.deep_learning import Sequential, Linear random.seed(0) EMBEDDING_DIM = 5 # wydaje się być dobrą wielkością # tworzymy warstwę embedding osobno. embedding = TextEmbedding(vocab=vocab, embedding_dim=EMBEDDING_DIM) model = Sequential([ # Mając słowo na wejściu (jako wektor identyfikatorów word_ids), dołącz jego wektor. embedding, # użyj warstwy linear do obliczenia wartości dla najbliższych słów. Linear(input_dim=EMBEDDING_DIM, output_dim=vocab.size) ]) Do wytrenowania modelu możemy użyć narzędzi napisanych w rozdziale 19.: from scratch.deep_learning import SoftmaxCrossEntropy, Momentum, GradientDescent loss = SoftmaxCrossEntropy() optimizer = GradientDescent(learning_rate=0.01) for epoch in range(100): epoch_loss = 0.0 for input, target in zip(inputs, targets): predicted = model.forward(input) epoch_loss += loss.loss(predicted, target) gradient = loss.gradient(predicted, target) model.backward(gradient) optimizer.step(model) print(epoch, epoch_loss) # wyświetl wartość straty print(embedding.closest( black )) # oraz kilka najbliższych słów print(embedding.closest( slow )) # aby było widać print(embedding.closest( car )) # jak przebiega trenowanie modelu. W miarę trenowania modelu możesz zobaczyć, że kolory coraz bardziej zbliżają się do siebie, po- dobnie przymiotniki i rzeczowniki. Gdy model jest już gotowy, warto popatrzyć na najbliższe sobie słowa: pairs = [(cosine_similarity(embedding[w1], embedding[w2]), w1, w2) for w1 in vocab.w2i for w2 in vocab.w2i if w1 w2] pairs.sort(reverse=True) print(pairs[:5]) U mnie wyniki były takie: [(0.9980283554864815, boat , car ), (0.9975147744587706, bed , cat ), (0.9953153441218054, seems , was ), (0.9927107440377975, extremely , quite ), (0.9836183658415987, bed , car )] Możemy także wyznaczyć pierwsze dwie główne składowe i przedstawić je na wykresie: from scratch.working_with_data import pca, transform import matplotlib.pyplot as plt # Wyznacz pierwsze dwie główne składowe i przetransformuj wektory słów. components = pca(embedding.embeddings, 2) Wektory słów 281  Poleć książkęKup książkę transformed = transform(embedding.embeddings, components) # Narysuj punkty na wykresie i pokoloruj je na biało, aby były niewidoczne. fig, ax = plt.subplots() ax.scatter(*zip(*transformed), marker= . , color= w ) # Dodaj opis do każdego punktu. for word, idx in vocab.w2i.items(): ax.annotate(word, transformed[idx]) # Ukryj osie. ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) plt.show() Widać, że podobne słowa faktycznie znajdują się blisko siebie (zobacz rysunek 21.3). Rysunek 21.3. Wektory słów Jeżeli jesteś zainteresowany wytrenowaniem modelu CBOW, to wiedz, że nie jest to takie trudne. Na początek należy zmodyfikować nieco warstwę Embedding, tak by na wejściu przyjmowała listę identyfikatorów i zwracała listę wektorów. Następnie należy stworzyć nową warstwę (Sum?), która pobiera listę wektorów i zwraca ich sumę. Każde słowo reprezentuje przykład treningowy, gdzie na wejściu mamy identyfikatory otaczają- cych słów, a wartością wyjściową jest identyfikator samego słowa. Zmodyfikowana warstwa Embedding przetwarza otaczające słowa na listę wektorów, nowa warstwa Sum przekształca listę w pojedynczy wektor, a warstwa Linear wylicza wyniki, które mogą być prze- kształcone przy użyciu funkcji softmax, aby uzyskać rozkład oznaczający „najbardziej prawdopo- dobne słowa w tym kontekście”. Wydaje mi się, że model CBOW jest trudniejszy do trenowania niż skip-gram, ale zachęcam, byś spróbował. 282  Rozdział 21. Przetwarzanie języka naturalnego Poleć książkęKup książkę Rekurencyjne sieci neuronowe Wektory słów, które tworzyliśmy w poprzednim podrozdziale, często są używane na wejściu sieci neuronowych. Jednym z problemów, jakie pojawiają się w tej sytuacji, jest to, że zdania mają róż- ną liczbę słów. Zdanie składające się z trzech słów można przedstawić w formie tensora [3, embed- ding_dim], a zdanie zawierające dziesięć słów jako tensor [10, embedding_dim]. Aby przekazać takie zdania na przykład do warstwy Linear, musimy zrobić coś z tą zmienną. Można na przykład wykorzystać warstwę Sum (lub jej wariant, używający średniej), jednak kolej- ność słów w zdaniu ma zazwyczaj duże znaczenie. Prostym przykładem mogą być zdania „Dzieci zjadają kurczaki” i „Kurczaki zjadają dzieci”. Innym sposobem jest wykorzystanie rekurencyjnych sieci neuronowych (RNN — ang. recurrent neural network), które przechowują ukryte stany pomiędzy kolejnymi przetwarzanymi warto- ściami. W najprostszym przypadku każda wartość wejściowa jest przetwarzana razem z ukrytym stanem, aby wygenerować wartość wyjściową, która jest następnie używana jako nowy ukryty stan. Pozwala to sieciom „zapamiętywać” dotychczas przetworzone wartości i generować wynik, który zależy od wszystkich wartości na wejściu oraz od ich kolejności. Stworzymy najprostszą możliwą warstwę RNN, która przyjmuje pojedyncze wartości na wejściu (na przykład pojedyncze słowa ze zdania lub pojedyncze znaki ze słowa) i przechowuje ich ukryty stan pomiędzy wywołaniami. Jak pewnie pamiętasz, nasza warstwa Linear wykorzystuje pewne wagi w oraz wartość progową (bias) b. Na wejściu p
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Data science od podstaw. Analiza danych w Pythonie. Wydanie II
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: