۱. مرور کلی
پردازش زبان طبیعی (NLP) مطالعهی استخراج بینش و انجام تجزیه و تحلیل بر روی دادههای متنی است. با افزایش میزان نوشتار تولید شده در اینترنت، اکنون بیش از هر زمان دیگری، سازمانها به دنبال استفاده از متن خود برای کسب اطلاعات مرتبط با کسبوکارشان هستند.
پردازش زبان طبیعی (NLP) میتواند برای همه چیز، از ترجمه زبانها گرفته تا تحلیل احساسات و تولید جملات از ابتدا و موارد دیگر، مورد استفاده قرار گیرد. این یک حوزه تحقیقاتی فعال است که نحوه کار ما با متن را متحول میکند.
ما نحوه استفاده از پردازش زبان طبیعی (NLP) را روی حجم زیادی از دادههای متنی در مقیاس بزرگ بررسی خواهیم کرد. این قطعاً میتواند یک کار دلهرهآور باشد! خوشبختانه، ما از کتابخانههایی مانند Spark MLlib و spark-nlp برای آسانتر کردن این کار استفاده خواهیم کرد.
۲. مورد استفاده ما
دانشمند ارشد داده سازمان (خیالی) ما، "FoodCorp" علاقهمند به کسب اطلاعات بیشتر در مورد روندهای صنعت غذا است. ما به مجموعهای از دادههای متنی در قالب پستهایی از subreddit Reddit r/food دسترسی داریم که از آنها برای بررسی آنچه مردم در مورد آن صحبت میکنند، استفاده خواهیم کرد.
یک رویکرد برای انجام این کار از طریق یک روش NLP است که به عنوان "مدلسازی موضوعی" شناخته میشود. مدلسازی موضوعی یک روش آماری است که میتواند روندها را در معانی معنایی گروهی از اسناد شناسایی کند. به عبارت دیگر، میتوانیم یک مدل موضوعی بر روی مجموعه "پستهای" Reddit خود بسازیم که فهرستی از "موضوعات" یا گروههایی از کلمات را که یک روند را توصیف میکنند، تولید میکند.
برای ساخت مدل خود، از الگوریتمی به نام تخصیص پنهان دیریکله (LDA) استفاده خواهیم کرد که اغلب برای خوشهبندی متن استفاده میشود. مقدمهای عالی برای LDA را میتوانید اینجا بیابید.
۳. ایجاد یک پروژه
اگر از قبل حساب گوگل (جیمیل یا برنامههای گوگل) ندارید، باید یکی ایجاد کنید . وارد کنسول پلتفرم ابری گوگل ( console.cloud.google.com ) شوید و یک پروژه جدید ایجاد کنید:



در مرحله بعد، برای استفاده از منابع گوگل کلود، باید صورتحساب را در کنسول کلود فعال کنید .
اجرای این آزمایشگاه کد نباید بیش از چند دلار برای شما هزینه داشته باشد، اما اگر تصمیم به استفاده از منابع بیشتر بگیرید یا اگر آنها را در حال اجرا رها کنید، میتواند بیشتر هم بشود. آزمایشگاههای کد PySpark-BigQuery و Spark-NLP هر کدام در پایان «پاکسازی» را توضیح میدهند.
کاربران جدید پلتفرم ابری گوگل واجد شرایط دریافت یک دوره آزمایشی رایگان ۳۰۰ دلاری هستند.
۴. آمادهسازی محیط
ابتدا باید Dataproc و APIهای Compute Engine را فعال کنیم.
روی آیکون منو در سمت چپ بالای صفحه کلیک کنید.

از منوی کشویی، گزینه API Manager را انتخاب کنید.

روی فعال کردن APIها و خدمات کلیک کنید.

در کادر جستجو عبارت "Compute Engine" را جستجو کنید. در لیست نتایج ظاهر شده، روی "Google Compute Engine API" کلیک کنید.

در صفحه Google Compute Engine روی Enable کلیک کنید.

پس از فعال شدن، برای بازگشت، روی فلش به سمت چپ کلیک کنید.
حالا عبارت «Google Dataproc API» را جستجو کنید و آن را نیز فعال کنید.

در مرحله بعد، با کلیک روی دکمهای که در گوشه سمت راست بالای کنسول ابری قرار دارد، Cloud Shell را باز کنید:

ما قصد داریم برخی متغیرهای محیطی را تنظیم کنیم که بتوانیم در ادامهی کار با codelab به آنها ارجاع دهیم. ابتدا، نامی برای کلاستر Dataproc که قرار است ایجاد کنیم، مانند "my-cluster" انتخاب کنید و آن را در محیط خود تنظیم کنید. میتوانید از هر نامی که دوست دارید استفاده کنید.
CLUSTER_NAME=my-cluster
سپس، یکی از مناطق موجود در اینجا را انتخاب کنید. به عنوان مثال، میتوان به us-east1-b.
REGION=us-east1
در نهایت، باید سطل منبعی را که قرار است کار ما از آن دادهها را بخواند، تنظیم کنیم. ما دادههای نمونهای را در سطل bm_reddit موجود داریم، اما اگر قبل از این، پیشپردازش دادههای BigQuery را از PySpark تولید کردهاید، میتوانید از دادههایی که تولید کردهاید استفاده کنید.
BUCKET_NAME=bm_reddit
با پیکربندی متغیرهای محیطی، دستور زیر را برای ایجاد خوشه 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
بیایید هر یک از این دستورات را قدم به قدم بررسی کنیم:
gcloud beta dataproc clusters create ${CLUSTER_NAME} : ایجاد یک خوشه Dataproc با نامی که قبلاً ارائه دادید را آغاز میکند. ما در اینجا beta را اضافه میکنیم تا ویژگیهای بتای Dataproc مانند Component Gateway را فعال کنیم که در ادامه در مورد آنها بحث خواهیم کرد.
--zone=${ZONE} : این مکان خوشه را تعیین میکند.
--worker-machine-type n1-standard-8 : این نوع دستگاهی است که برای کارگران ما استفاده میشود.
--num-workers 4 : ما چهار worker در کلاستر خود خواهیم داشت.
--image-version 1.4-debian9 : این نشان دهنده نسخه تصویری Dataproc است که ما استفاده خواهیم کرد.
--initialization-actions ... : اقدامات اولیه، اسکریپتهای سفارشی هستند که هنگام ایجاد کلاسترها و workerها اجرا میشوند. آنها میتوانند توسط کاربر ایجاد شده و در یک سطل GCS ذخیره شوند یا از سطل عمومی dataproc-initialization-actions ارجاع داده شوند. اقدام اولیهسازی که در اینجا گنجانده شده است، نصب بستههای پایتون را با استفاده از Pip، همانطور که با پرچم --metadata ارائه شده است، امکانپذیر میکند.
--metadata 'PIP_PACKAGES=google-cloud-storage spark-nlp' : این لیستی از بستههایی است که با فاصله از هم جدا شدهاند تا در Dataproc نصب شوند. در این مورد، ما کتابخانه کلاینت پایتون google-cloud-storage و spark-nlp را نصب خواهیم کرد.
--optional-components=ANACONDA : کامپوننتهای اختیاری ، بستههای رایجی هستند که با Dataproc استفاده میشوند و به طور خودکار در طول ایجاد، روی کلاسترهای Dataproc نصب میشوند. مزایای استفاده از کامپوننتهای اختیاری نسبت به اقدامات اولیه شامل زمان راهاندازی سریعتر و آزمایش شدن برای نسخههای خاص Dataproc است. در کل، آنها قابل اعتمادتر هستند.
--enable-component-gateway : این فلگ به ما امکان میدهد از Component Gateway مربوط به Dataproc برای مشاهده رابطهای کاربری رایج مانند Zeppelin، Jupyter یا Spark History استفاده کنیم. توجه: برخی از این موارد به Optional Component مربوطه نیاز دارند.
برای آشنایی عمیقتر با Dataproc، لطفاً این codelab را بررسی کنید.
در مرحله بعد، دستورات زیر را در Cloud Shell خود اجرا کنید تا مخزن را به همراه کد نمونه کلون کنید و با دستور cd به دایرکتوری صحیح بروید:
cd
git clone https://github.com/GoogleCloudPlatform/cloud-dataproc
cd cloud-dataproc/codelabs/spark-nlp
۵. اسپارک اماللیب
Spark MLlib یک کتابخانه یادگیری ماشین مقیاسپذیر است که با استفاده از Apache Spark نوشته شده است. MLlib با بهرهگیری از کارایی Spark و مجموعهای از الگوریتمهای یادگیری ماشین تنظیمشده، میتواند حجم زیادی از دادهها را تجزیه و تحلیل کند. این کتابخانه دارای APIهایی در جاوا، اسکالا، پایتون و R است. در این آزمایشگاه کد، ما بهطور خاص بر روی پایتون تمرکز خواهیم کرد.
MLlib شامل مجموعه بزرگی از مبدلها و تخمینگرها است. مبدل ابزاری است که میتواند دادههای شما را تغییر دهد یا دگرگون کند، معمولاً با تابع transform() در حالی که تخمینگر یک الگوریتم از پیش ساخته شده است که میتوانید دادههای خود را با آن آموزش دهید، معمولاً با تابع fit() .
نمونههایی از ترانسفورماتورها عبارتند از:
- توکنسازی (ایجاد برداری از اعداد از رشتهای از کلمات)
- کدگذاری وان-هات (ایجاد یک بردار پراکنده از اعداد که نشاندهنده کلمات موجود در یک رشته است)
- حذفکنندهی کلمات متوقفکننده (حذف کلماتی که ارزش معنایی به یک رشته اضافه نمیکنند)
نمونههایی از تخمینگرها عبارتند از:
- طبقهبندی (این سیب است یا پرتقال؟)
- رگرسیون (قیمت این سیب چقدر باید باشد؟)
- خوشهبندی (همه سیبها چقدر به یکدیگر شبیه هستند؟)
- درختهای تصمیمگیری (اگر رنگ == نارنجی باشد، آنگاه پرتقال است. در غیر این صورت سیب است)
- کاهش ابعاد (آیا میتوانیم ویژگیها را از مجموعه داده خود حذف کنیم و همچنان بین سیب و پرتقال تمایز قائل شویم؟)
MLlib همچنین شامل ابزارهایی برای سایر روشهای رایج در یادگیری ماشین مانند تنظیم و انتخاب ابرپارامتر و همچنین اعتبارسنجی متقابل است.
علاوه بر این، MLlib شامل Pipelines API است که به شما امکان میدهد با استفاده از مبدلهای مختلفی که میتوانند دوباره روی آنها اجرا شوند، خطوط لوله تبدیل داده بسازید.
۶. اسپارک-NLP
Spark-nlp کتابخانهای است که توسط آزمایشگاههای جان اسنو برای انجام وظایف پردازش زبان طبیعی کارآمد با استفاده از اسپارک ایجاد شده است. این کتابخانه شامل ابزارهای داخلی به نام حاشیهنویسها برای وظایف رایجی مانند موارد زیر است:
- توکنسازی (ایجاد برداری از اعداد از رشتهای از کلمات)
- ایجاد جاسازی کلمات (تعریف رابطه بین کلمات از طریق بردارها)
- برچسبهای مربوط به اجزای کلام (کدام کلمات اسم هستند؟ کدامها فعل هستند؟)
اگرچه خارج از محدوده این آزمایشگاه کد است، spark-nlp به خوبی با TensorFlow ادغام میشود.
شاید از همه مهمتر، Spark-NLP با ارائه اجزایی که به راحتی در خطوط لوله MLlib قرار میگیرند، قابلیتهای Spark MLlib را گسترش میدهد.
۷. بهترین شیوهها برای پردازش زبان طبیعی
قبل از اینکه بتوانیم اطلاعات مفیدی را از دادههای خود استخراج کنیم، باید برخی اقدامات اولیه را انجام دهیم. مراحل پیشپردازش به شرح زیر است:
توکنسازی
اولین کاری که به طور سنتی میخواهیم انجام دهیم، «توکنگذاری» دادهها است. این شامل گرفتن دادهها و تقسیم آنها بر اساس «توکنها» یا کلمات است. به طور کلی، در این مرحله علائم نگارشی را حذف میکنیم و همه کلمات را به حروف کوچک تبدیل میکنیم. برای مثال، فرض کنید رشته زیر را داریم: What time is it? پس از توکنگذاری، این جمله شامل چهار نشانه خواهد بود: « what" , "time", "is", "it". ما نمیخواهیم مدل با کلمه what به عنوان دو کلمه متفاوت با دو نوع بزرگنویسی متفاوت رفتار کند. علاوه بر این، علائم نگارشی معمولاً به ما کمک نمیکند تا استنباط از کلمات را بهتر یاد بگیریم، بنابراین آن را نیز حذف میکنیم.
عادیسازی
ما اغلب میخواهیم دادهها را «نرمالسازی» کنیم. این کار کلمات با معنای مشابه را با همان معنی جایگزین میکند. برای مثال، اگر کلمات «fought»، «battled» و «dueled» در متن شناسایی شوند، در نرمالسازی ممکن است «battled» و «dueled» با کلمه «fought» جایگزین شوند.
ریشه یابی
ریشهیابی، کلمات را با معنی ریشهای آنها جایگزین میکند. برای مثال، کلمات "car"، "cars'" و "car's" همگی با کلمه "car" جایگزین میشوند، زیرا همه این کلمات در ریشه خود به یک چیز اشاره دارند.
حذف کلمات توقف
کلمات توقف کلماتی مانند "و" و "آن" هستند که معمولاً ارزشی به معنای جمله اضافه نمیکنند. ما معمولاً میخواهیم این کلمات را به عنوان وسیلهای برای کاهش نویز در مجموعه دادههای متنی خود حذف کنیم.
۸. دویدن در طول کار
بیایید نگاهی به کاری که قرار است اجرا کنیم بیندازیم. کد را میتوانید در cloud-dataproc/codelabs/spark-nlp/topic_model.py پیدا کنید. حداقل چند دقیقه را صرف خواندن آن و توضیحات مربوطه کنید تا بفهمید چه اتفاقی میافتد. همچنین برخی از بخشهای زیر را برجسته خواهیم کرد:
# 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}")
اجرای کار
حالا بیایید کارمان را اجرا کنیم. دستور زیر را اجرا کنید:
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}
این دستور به ما امکان میدهد از API مربوط به Dataproc Jobs استفاده کنیم. با اضافه کردن دستور pyspark به کلاستر اعلام میکنیم که این یک کار PySpark است. ما نام کلاستر، پارامترهای اختیاری از موارد موجود در اینجا و نام فایل حاوی کار را ارائه میدهیم. در مورد ما، پارامتر --properties را ارائه میدهیم که به ما امکان میدهد ویژگیهای مختلف Spark، Yarn یا Dataproc را تغییر دهیم. ما packages ویژگی Spark را تغییر میدهیم که به ما امکان میدهد به Spark اطلاع دهیم که میخواهیم spark-nlp به عنوان بستهبندی شده با کار خود اضافه کنیم. ما همچنین پارامترهای --driver-log-levels root=FATAL را ارائه میدهیم که بیشتر خروجیهای گزارش PySpark را به جز خطاها سرکوب میکند. به طور کلی، گزارشهای Spark معمولاً نویزی هستند.
در نهایت، -- ${BUCKET} یک آرگومان خط فرمان برای خود اسکریپت پایتون است که نام سطل را ارائه میدهد. به فاصله بین -- و ${BUCKET} توجه کنید.
پس از چند دقیقه اجرای کار، باید خروجی حاوی مدلهای خود را ببینیم:

عالیه!! آیا میتوانید با نگاه کردن به خروجی مدل خودتان، روندها را استنباط کنید؟ مدل ما چطور؟
از خروجی بالا، میتوان روند مربوط به غذای صبحانه در مبحث ۸ و دسرها در مبحث ۹ را استنباط کرد.
۹. پاکسازی
برای جلوگیری از تحمیل هزینههای غیرضروری به حساب GCP خود پس از تکمیل این راهنمای سریع:
اگر فقط برای این codelab پروژهای ایجاد کردهاید، میتوانید به صورت اختیاری پروژه را حذف کنید:
- در کنسول GCP، به صفحه پروژهها بروید.
- در لیست پروژهها، پروژهای را که میخواهید حذف کنید انتخاب کرده و روی حذف کلیک کنید.
- در کادر، شناسه پروژه را تایپ کنید و سپس برای حذف پروژه، روی خاموش کردن کلیک کنید.
مجوز
این اثر تحت مجوز عمومی Creative Commons Attribution 3.0 و مجوز Apache 2.0 منتشر شده است.