1. Omówienie
Przetwarzanie języka naturalnego (NLP) to badanie sposobów uzyskiwania informacji i analizowania danych tekstowych. Ilość tekstów tworzonych w internecie stale rośnie, dlatego organizacje bardziej niż kiedykolwiek wcześniej szukają sposobów na wykorzystanie tekstów, aby zdobywać informacje istotne dla ich działalności.
NLP można używać do różnych celów, od tłumaczenia języków po analizę nastawienia i generowanie zdań od podstaw. To aktywny obszar badań, który zmienia sposób pracy z tekstem.
Dowiesz się, jak stosować NLP do dużych ilości danych tekstowych. Może to być przytłaczające zadanie. Na szczęście użyjemy bibliotek takich jak Spark MLlib i spark-nlp, aby ułatwić Ci to zadanie.
2. Nasz przypadek użycia
Główny naukowiec zajmujący się danymi w naszej (fikcyjnej) organizacji „FoodCorp” chce dowiedzieć się więcej o trendach w branży spożywczej. Mamy dostęp do zbioru danych tekstowych w postach na subreddicie r/food na Reddicie, który posłuży nam do zbadania, o czym rozmawiają użytkownicy.
Jednym ze sposobów jest zastosowanie metody NLP znanej jako „modelowanie tematów”. Modelowanie tematów to metoda statystyczna, która umożliwia wykrywanie trendów w znaczeniach semantycznych grupy dokumentów. Inaczej mówiąc, możemy zbudować model tematów na podstawie zbioru „postów” na Reddicie, który wygeneruje listę „tematów” lub grup słów opisujących trend.
Do tworzenia modelu użyjemy algorytmu zwanego Latent Dirichlet Allocation (LDA), który jest często używany do grupowania tekstu. Doskonałe wprowadzenie do LDA znajdziesz tutaj.
3. Tworzenie projektu
Jeśli nie masz jeszcze konta Google (Gmail lub Google Apps), utwórz je. Zaloguj się w konsoli Google Cloud Platform ( console.cloud.google.com) i utwórz nowy projekt:
Następnie, aby korzystać z zasobów Google Cloud, musisz włączyć płatności w Cloud Console.
Przeprowadzenie tego ćwiczenia nie powinno kosztować więcej niż kilka dolarów, ale może okazać się droższe, jeśli zdecydujesz się wykorzystać więcej zasobów lub pozostawisz je uruchomione. W ramach laboratoriów kodu PySpark-BigQuery i Spark-NLP na końcu znajdziesz instrukcje dotyczące „Sprzątania”.
Nowi użytkownicy Google Cloud Platform mogą skorzystać z bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.
4. Konfigurowanie środowiska
Najpierw musimy włączyć interfejsy Dataproc i Compute Engine API.
Kliknij ikonę menu w lewym górnym rogu ekranu.
W menu kliknij Menedżer interfejsu API.
Kliknij Włącz interfejsy API i usługi.
W polu wyszukiwania wyszukaj „Compute Engine”. Na wyświetlonej liście wyników kliknij „Google Compute Engine API”.
Na stronie Google Compute Engine kliknij Włącz.
Gdy to zrobisz, kliknij strzałkę w lewo, aby wrócić.
Teraz wyszukaj „Google Dataproc API” i także go włącz.
Następnie otwórz Cloud Shell, klikając przycisk w prawym górnym rogu konsoli w chmurze:
Skonfigurujemy kilka zmiennych środowiskowych, do których będziemy się odwoływać w trakcie pracy w tym laboratorium. Najpierw wybierz nazwę klastra Dataproc, który utworzymy, np. „mój-klaster”, i skonfiguruj go w swoim środowisku. Możesz użyć dowolnej nazwy.
CLUSTER_NAME=my-cluster
Następnie wybierz strefę z dostępnych tutaj. Przykład: us-east1-b.
REGION=us-east1
Na koniec musimy ustawić zasób źródłowy, z którego zadanie będzie odczytywać dane. Przykładowe dane są dostępne w zasośniku bm_reddit
, ale możesz też użyć danych wygenerowanych w ramach tego kursu, jeśli ukończyłeś już wcześniej kurs PySpark do wstępnego przetwarzania danych BigQuery.
BUCKET_NAME=bm_reddit
Po skonfigurowaniu zmiennych środowiskowych uruchom polecenie, aby utworzyć klaster Dataproc:
gcloud beta dataproc clusters create ${CLUSTER_NAME} \
--region ${REGION} \
--metadata 'PIP_PACKAGES=google-cloud-storage spark-nlp==2.7.2' \
--worker-machine-type n1-standard-8 \
--num-workers 4 \
--image-version 1.4-debian10 \
--initialization-actions gs://dataproc-initialization-actions/python/pip-install.sh \
--optional-components=JUPYTER,ANACONDA \
--enable-component-gateway
Przyjrzyjmy się każdemu z tych poleceń:
gcloud beta dataproc clusters create ${CLUSTER_NAME}
: inicjuje tworzenie klastra Dataproc o podanej wcześniej nazwie. Dołączamy tutaj beta
, aby umożliwić korzystanie z funkcji beta Dataproc, takich jak bramka komponentów, o której piszemy poniżej.
--zone=${ZONE}
: określa lokalizację klastra.
--worker-machine-type n1-standard-8
: typ maszyny, której mają używać pracownicy.
--num-workers 4
: w naszym klastrze będą 4 procesy.
--image-version 1.4-debian9
: oznacza wersję obrazu Dataproc, której użyjemy.
--initialization-actions ...
: działania inicjowania to niestandardowe skrypty, które są wykonywane podczas tworzenia klastrów i elementów roboczych. Mogą być tworzone przez użytkowników i przechowywane w zasobniku GCS lub mogą być dostępne w zasobniku publicznym dataproc-initialization-actions
. Zawarte tutaj działanie inicjowania umożliwi instalację pakietów Pythona za pomocą narzędzia Pip, zgodnie z flagą --metadata
.
--metadata 'PIP_PACKAGES=google-cloud-storage spark-nlp'
: lista oddzielonych spacjami pakietów do zainstalowania w Dataproc. W tym przypadku zainstalujemy bibliotekę klienta google-cloud-storage
w Pythonie i spark-nlp
.
--optional-components=ANACONDA
: Opcjonalne komponenty to popularne pakiety używane w Dataproc, które są automatycznie instalowane w klastrach Dataproc podczas ich tworzenia. Zalety korzystania z komponentów opcjonalnych w porównaniu z działaniami inicjującymi to krótszy czas uruchamiania i testowanie w przypadku konkretnych wersji Dataproc. Ogólnie są one bardziej wiarygodne.
--enable-component-gateway
: ten parametr pozwala nam korzystać z bramki komponentów Dataproc do wyświetlania typowych interfejsów użytkownika, takich jak Zeppelin, Jupyter czy historia Spark. Uwaga: niektóre z nich wymagają powiązanego komponentu opcjonalnego.
Aby uzyskać bardziej szczegółowe informacje o usłudze Dataproc, zapoznaj się z tym ćwiczeniem w Codelab.
Następnie uruchom w Cloud Shell te polecenia, aby sklonować repozytorium z przykładowym kodem i przejść do odpowiedniego katalogu:
cd
git clone https://github.com/GoogleCloudPlatform/cloud-dataproc
cd cloud-dataproc/codelabs/spark-nlp
5. Spark MLlib
Spark MLlib to skalowalna biblioteka uczenia maszynowego napisana w języku Apache Spark. Dzięki wykorzystaniu wydajności Sparka z zestawem dopracowanych algorytmów uczenia maszynowego biblioteka MLlib może analizować duże ilości danych. Interfejsy API są dostępne w językach: Java, Scala, Python i R. W tym ćwiczeniu skupimy się na Pythonie.
MLlib zawiera duży zestaw przekształcaczy i szacowników. Transformer to narzędzie, które może zmodyfikować lub zmienić Twoje dane, zwykle za pomocą funkcji transform()
. Estimator to gotowy algorytm, na którym możesz trenować dane, zwykle za pomocą funkcji fit()
.
Przykłady takich treści:
- tokenizacji (tworzenie wektora liczb z ciągu słów);
- kodowanie one-hot (tworzenie rzadkiego wektora liczb reprezentujących słowa w ciągu);
- usuwanie słów stop (usuwanie słów, które nie zwiększają wartości semantycznej ciągu);
Przykłady szacowników:
- klasyfikacja (czy to jabłko czy pomarańcza?)
- regresja (ile powinien kosztować ten owoc?)
- grupowanie (jak podobne są do siebie wszystkie jabłka?)
- drzewa decyzyjne (jeśli kolor = pomarańczowy, to jest pomarańczowy. W przeciwnym razie jest to jabłko)
- redukcja wymiarów (czy możemy usunąć z danych zbioru atrybuty, a nadal odróżniać jabłko od pomarańczy?).
MLlib zawiera też narzędzia do innych popularnych metod uczenia maszynowego, takich jak dobór i dostrajanie parametrów oraz walidacja krzyżowa.
Dodatkowo MLlib zawiera interfejs Pipelines API, który umożliwia tworzenie potoku przekształcania danych za pomocą różnych przekształceń, które można ponownie wykonać.
6. Spark-NLP
Spark-nlp to biblioteka stworzona przez John Snow Labs do wydajnego wykonywania zadań przetwarzania języka naturalnego przy użyciu Sparka. Zawiera wbudowane narzędzia, tzw. adnotatory, do wykonywania typowych czynności, takich jak:
- tokenizacji (tworzenie wektora liczb z ciągu słów);
- tworzenie wektorów dystrybucyjnych słów (definiowanie relacji między słowami za pomocą wektorów);
- tagi części mowy (które słowa są rzeczownikami? Które z nich są czasownikami?)
Chociaż nie jest to objęte tym ćwiczeniem z programowania, biblioteka spark-nlp dobrze integruje się z TensorFlow.
Najważniejsze jest to, że Spark-NLP rozszerza możliwości Spark MLlib, udostępniając komponenty, które łatwo wstawiać do potoków MLlib.
7. Sprawdzone metody przetwarzania języka naturalnego
Zanim będziemy mogli wyodrębnić z tych danych przydatne informacje, musimy zadbać o porządki. Wykonujemy te kroki wstępnej obróbki:
Tokenizacja
Pierwszą rzeczą, którą tradycyjnie się zajmujemy, jest „tokowanie” danych. Polega to na pobraniu danych i podziale ich na „tokeny” lub słowa. Na tym etapie zwykle usuwamy znaki interpunkcyjne i zamieniamy wszystkie słowa na małe litery. Załóżmy na przykład, że mamy taki ciąg znaków: What time is it?
Po podziałie na tokeny to zdanie będzie się składać z 4 tokenów: „what" , "time", "is", "it".
Nie chcemy, aby model traktował słowo what
jako 2 różne słowa z 2 różnymi wielkościami liter. Ponadto znaki interpunkcyjne zwykle nie pomagają w lepszym wywnioskowaniu znaczenia słów, więc również je usuwamy.
Normalizacja
Często chcemy „unormować” dane. Spowoduje to zastąpienie słów o podobnym znaczeniu tym samym słowem. Jeśli na przykład w tekście występują słowa „fought”, „battled” i „dueled”, normalizacja może zastąpić „battled” i „dueled” słowem „fought”.
Stemming
Stemming zastąpi słowa ich rdzeniem znaczeniowym. Na przykład słowa „samochód”, „samochody” i „samochody” zostaną zastąpione słowem „samochód”, ponieważ wszystkie te słowa mają ten sam korzeń.
Usuwanie wyrazów stopowych
Słowa kluczowe to słowa takie jak „i” czy „to”, które zwykle nie zwiększają wartości semantycznej zdania. Zwykle chcemy je usunąć, aby ograniczyć szum w naszych zbiorach danych tekstowych.
8. Przeprowadzanie zadania
Przyjrzyjmy się zadaniu, które chcemy wykonać. Kod znajdziesz w pliku cloud-dataproc/codelabs/spark-nlp/topic_model.py. Poświęć co najmniej kilka minut na przeczytanie tego dokumentu i powiązanych z nim komentarzy, aby zrozumieć, co się dzieje. Podkreślmy też kilka sekcji:
# Python imports
import sys
# spark-nlp components. Each one is incorporated into our pipeline.
from sparknlp.annotator import Lemmatizer, Stemmer, Tokenizer, Normalizer
from sparknlp.base import DocumentAssembler, Finisher
# A Spark Session is how we interact with Spark SQL to create Dataframes
from pyspark.sql import SparkSession
# These allow us to create a schema for our data
from pyspark.sql.types import StructField, StructType, StringType, LongType
# Spark Pipelines allow us to sequentially add components such as transformers
from pyspark.ml import Pipeline
# These are components we will incorporate into our pipeline.
from pyspark.ml.feature import StopWordsRemover, CountVectorizer, IDF
# LDA is our model of choice for topic modeling
from pyspark.ml.clustering import LDA
# Some transformers require the usage of other Spark ML functions. We import them here
from pyspark.sql.functions import col, lit, concat
# This will help catch some PySpark errors
from pyspark.sql.utils import AnalysisException
# Assign bucket where the data lives
try:
bucket = sys.argv[1]
except IndexError:
print("Please provide a bucket name")
sys.exit(1)
# Create a SparkSession under the name "reddit". Viewable via the Spark UI
spark = SparkSession.builder.appName("reddit topic model").getOrCreate()
# Create a three column schema consisting of two strings and a long integer
fields = [StructField("title", StringType(), True),
StructField("body", StringType(), True),
StructField("created_at", LongType(), True)]
schema = StructType(fields)
# We'll attempt to process every year / month combination below.
years = ['2016', '2017', '2018', '2019']
months = ['01', '02', '03', '04', '05', '06',
'07', '08', '09', '10', '11', '12']
# This is the subreddit we're working with.
subreddit = "food"
# Create a base dataframe.
reddit_data = spark.createDataFrame([], schema)
# Keep a running list of all files that will be processed
files_read = []
for year in years:
for month in months:
# In the form of <project-id>.<dataset>.<table>
gs_uri = f"gs://{bucket}/reddit_posts/{year}/{month}/{subreddit}.csv.gz"
# If the table doesn't exist we will simply continue and not
# log it into our "tables_read" list
try:
reddit_data = (
spark.read.format('csv')
.options(codec="org.apache.hadoop.io.compress.GzipCodec")
.load(gs_uri, schema=schema)
.union(reddit_data)
)
files_read.append(gs_uri)
except AnalysisException:
continue
if len(files_read) == 0:
print('No files read')
sys.exit(1)
# Replacing null values with their respective typed-equivalent is usually
# easier to work with. In this case, we'll replace nulls with empty strings.
# Since some of our data doesn't have a body, we can combine all of the text
# for the titles and bodies so that every row has useful data.
df_train = (
reddit_data
# Replace null values with an empty string
.fillna("")
.select(
# Combine columns
concat(
# First column to concatenate. col() is used to specify that we're referencing a column
col("title"),
# Literal character that will be between the concatenated columns.
lit(" "),
# Second column to concatenate.
col("body")
# Change the name of the new column
).alias("text")
)
)
# Now, we begin assembling our pipeline. Each component here is used to some transformation to the data.
# The Document Assembler takes the raw text data and convert it into a format that can
# be tokenized. It becomes one of spark-nlp native object types, the "Document".
document_assembler = DocumentAssembler().setInputCol("text").setOutputCol("document")
# The Tokenizer takes data that is of the "Document" type and tokenizes it.
# While slightly more involved than this, this is effectively taking a string and splitting
# it along ths spaces, so each word is its own string. The data then becomes the
# spark-nlp native type "Token".
tokenizer = Tokenizer().setInputCols(["document"]).setOutputCol("token")
# The Normalizer will group words together based on similar semantic meaning.
normalizer = Normalizer().setInputCols(["token"]).setOutputCol("normalizer")
# The Stemmer takes objects of class "Token" and converts the words into their
# root meaning. For instance, the words "cars", "cars'" and "car's" would all be replaced
# with the word "car".
stemmer = Stemmer().setInputCols(["normalizer"]).setOutputCol("stem")
# The Finisher signals to spark-nlp allows us to access the data outside of spark-nlp
# components. For instance, we can now feed the data into components from Spark MLlib.
finisher = Finisher().setInputCols(["stem"]).setOutputCols(["to_spark"]).setValueSplitSymbol(" ")
# Stopwords are common words that generally don't add much detail to the meaning
# of a body of text. In English, these are mostly "articles" such as the words "the"
# and "of".
stopword_remover = StopWordsRemover(inputCol="to_spark", outputCol="filtered")
# Here we implement TF-IDF as an input to our LDA model. CountVectorizer (TF) keeps track
# of the vocabulary that's being created so we can map our topics back to their
# corresponding words.
# TF (term frequency) creates a matrix that counts how many times each word in the
# vocabulary appears in each body of text. This then gives each word a weight based
# on its frequency.
tf = CountVectorizer(inputCol="filtered", outputCol="raw_features")
# Here we implement the IDF portion. IDF (Inverse document frequency) reduces
# the weights of commonly-appearing words.
idf = IDF(inputCol="raw_features", outputCol="features")
# LDA creates a statistical representation of how frequently words appear
# together in order to create "topics" or groups of commonly appearing words.
lda = LDA(k=10, maxIter=10)
# We add all of the transformers into a Pipeline object. Each transformer
# will execute in the ordered provided to the "stages" parameter
pipeline = Pipeline(
stages = [
document_assembler,
tokenizer,
normalizer,
stemmer,
finisher,
stopword_remover,
tf,
idf,
lda
]
)
# We fit the data to the model.
model = pipeline.fit(df_train)
# Now that we have completed a pipeline, we want to output the topics as human-readable.
# To do this, we need to grab the vocabulary generated from our pipeline, grab the topic
# model and do the appropriate mapping. The output from each individual component lives
# in the model object. We can access them by referring to them by their position in
# the pipeline via model.stages[<ind>]
# Let's create a reference our vocabulary.
vocab = model.stages[-3].vocabulary
# Next, let's grab the topics generated by our LDA model via describeTopics(). Using collect(),
# we load the output into a Python array.
raw_topics = model.stages[-1].describeTopics().collect()
# Lastly, let's get the indices of the vocabulary terms from our topics
topic_inds = [ind.termIndices for ind in raw_topics]
# The indices we just grab directly map to the term at position <ind> from our vocabulary.
# Using the below code, we can generate the mappings from our topic indices to our vocabulary.
topics = []
for topic in topic_inds:
_topic = []
for ind in topic:
_topic.append(vocab[ind])
topics.append(_topic)
# Let's see our topics!
for i, topic in enumerate(topics, start=1):
print(f"topic {i}: {topic}")
Uruchamianie zadania
Uruchommy teraz zadanie. Uruchom to polecenie:
gcloud dataproc jobs submit pyspark --cluster ${CLUSTER_NAME}\
--region ${REGION}\
--properties=spark.jars.packages=com.johnsnowlabs.nlp:spark-nlp_2.11:2.7.2\
--driver-log-levels root=FATAL \
topic_model.py \
-- ${BUCKET_NAME}
To polecenie pozwala nam korzystać z interfejsu Dataproc Jobs API. Dodając polecenie pyspark
, wskazujemy klastrowi, że jest to zadanie PySpark. Podajemy nazwę klastra, opcjonalne parametry spośród dostępnych tutaj oraz nazwę pliku zawierającego zadanie. W naszym przypadku udostępniamy parametr --properties
, który pozwala nam zmieniać różne właściwości Spark, Yarn lub Dataproc. Zmieniamy właściwość Spark packages
, która pozwala nam poinformować Spark, że chcemy uwzględnić spark-nlp
jako pakiet z naszym zadaniem. Udostępniamy też parametry --driver-log-levels root=FATAL
, które wytłumią większość danych wyjściowych z PySpark, z wyjątkiem błędów. Zasadniczo dzienniki Spark są zazwyczaj zbyt obszerne.
Na koniec -- ${BUCKET}
to argument wiersza poleceń dla samego skryptu Pythona, który podaje nazwę zasobnika. Zwróć uwagę na spację między --
a ${BUCKET}
.
Po kilku minutach działania zadania powinny pojawić się dane wyjściowe zawierające nasze modele:
Świetnie! Czy możesz określić trendy, analizując dane wyjściowe modelu? A nasz?
Z wyników przedstawionych powyżej można wywnioskować, że temat 8 dotyczy śniadań, a temat 9 – deserów.
9. Czyszczenie
Aby uniknąć niepotrzebnych opłat na koncie Google Cloud Platform po zakończeniu tego samouczka:
- Usuń zasobnik Cloud Storage utworzony w ramach środowiska.
- Usuń środowisko Dataproc.
Jeśli projekt został utworzony tylko na potrzeby tego samouczka, możesz go opcjonalnie usunąć:
- W konsoli GCP otwórz stronę Projekty.
- Na liście projektów wybierz projekt, który chcesz usunąć, i kliknij Usuń.
- Wpisz w polu identyfikator projektu, a następnie kliknij Wyłącz, aby usunąć projekt.
Licencja
To zadanie jest licencjonowane na podstawie ogólnej licencji Creative Commons Attribution 3.0 i licencji Apache 2.0.