1. نظرة عامة
معالجة اللغات الطبيعية هي دراسة استخلاص الإحصاءات وإجراء التحليلات على البيانات النصية. مع استمرار زيادة حجم الكتابة على الإنترنت، تسعى المؤسسات الآن أكثر من أي وقت مضى إلى الاستفادة من نصوصها للحصول على معلومات ذات صلة بأنشطتها التجارية.
يمكن استخدام معالجة اللغة الطبيعية في كل شيء، بدءًا من ترجمة اللغات وتحليل المشاعر وإنشاء جمل من الصفر وغير ذلك الكثير. وهو مجال بحثي نشط يغيّر طريقة عملنا مع النصوص.
سنستكشف كيفية استخدام معالجة اللغة الطبيعية على كميات كبيرة من البيانات النصية على نطاق واسع. قد تكون هذه مهمة شاقّة بالتأكيد. لحسن الحظ، سنستفيد من مكتبات مثل Spark MLlib وspark-nlp لتسهيل ذلك.
2. حالة الاستخدام
يهتم رئيس علماء البيانات في مؤسستنا (الخيالية) "FoodCorp" بمعرفة المزيد عن المؤشرات في مجال الأغذية. يمكننا الوصول إلى مجموعة من البيانات النصية في شكل مشاركات من منتدى Reddit الفرعي r/food، وسنستخدمها لاستكشاف المواضيع التي يتحدث عنها المستخدمون.
تتمثل إحدى طرق إجراء ذلك في استخدام طريقة معالجة اللغة الطبيعية المعروفة باسم "وضع النماذج للمواضيع". وضع النماذج للموضوعات هو طريقة إحصائية يمكنها تحديد المؤشرات في المعاني الدلالية لمجموعة من المستندات. بعبارة أخرى، يمكننا إنشاء نموذج مواضيع استنادًا إلى مجموعة "المشاركات" على Reddit، ما سيؤدي إلى إنشاء قائمة "بالمواضيع" أو مجموعات من الكلمات التي تصف مؤشرًا.
لإنشاء النموذج، سنستخدم خوارزمية تُعرف باسم Latent Dirichlet Allocation (LDA)، والتي تُستخدَم غالبًا لتجميع النصوص. يمكنك الاطّلاع على مقدمة ممتازة عن تحليل الدلالات اللامحدودة هنا.
3- إنشاء مشروع
إذا لم يكن لديك حساب على Google (Gmail أو Google Apps)، عليك إنشاء حساب. سجِّل الدخول إلى وحدة تحكّم Google Cloud Platform ( console.cloud.google.com) وأنشِئ مشروعًا جديدًا:
بعد ذلك، عليك تفعيل الفوترة في Cloud Console لاستخدام موارد Google Cloud.
من المفترض ألا تتجاوز تكلفة استخدام هذا الدليل التعليمي بضعة دولارات، ولكن قد تزيد هذه التكلفة إذا قرّرت استخدام المزيد من الموارد أو إذا تركت الدليل التعليمي قيد التشغيل. يشرح كلّ من PySpark-BigQuery وSpark-NLP في نهاية ورشة التعلم البرمجي "عمليات التنظيف".
المستخدمون الجدد في Google Cloud Platform مؤهّلون للاستفادة من فترة تجريبية مجانية بقيمة 300 دولار أمريكي.
4. إعداد البيئة
أولاً، علينا تفعيل Dataproc وCompute Engine API.
انقر على رمز القائمة في أعلى يمين الشاشة.
اختَر "مدير واجهة برمجة التطبيقات" من القائمة المنسدلة.
انقر على تفعيل واجهات برمجة التطبيقات والخدمات.
ابحث عن "Compute Engine" في مربّع البحث. انقر على "Google Compute Engine API" في قائمة النتائج التي تظهر.
في صفحة Google Compute Engine، انقر على تفعيل.
بعد تفعيل هذه الميزة، انقر على السهم المتّجه لليسار للرجوع.
الآن، ابحث عن "Google Dataproc API" وفعِّلها أيضًا.
بعد ذلك، افتح Cloud Shell بالنقر على الزر في أعلى يسار وحدة تحكّم السحابة الإلكترونية:
سنضبط بعض متغيّرات البيئة التي يمكننا الرجوع إليها أثناء المتابعة في ورشة رموز البرامج. أولاً، اختَر اسمًا لمجموعة Dataproc التي سننشئها، مثل "my-cluster"، واضبطه في بيئتك. يمكنك استخدام أي اسم تريده.
CLUSTER_NAME=my-cluster
بعد ذلك، اختَر منطقة من إحدى المناطق المتاحة هنا. يمكن أن يكون أحد الأمثلة على ذلك us-east1-b.
REGION=us-east1
أخيرًا، علينا ضبط حزمة المصدر التي ستقرأ مهمتنا البيانات منها. تتوفّر لدينا عيّنات بيانات في الحزمة bm_reddit
، ولكن يمكنك استخدام البيانات التي أنشأتها من PySpark لمعالجة البيانات الأولية في BigQuery إذا كنت قد أكملت هذا الإجراء قبل هذا الإجراء.
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، مثل بوابة المكوّنات، والتي سنناقشها أدناه.
--zone=${ZONE}
: يحدِّد هذا العنصر موقع المجموعة.
--worker-machine-type n1-standard-8
: هذا هو نوع الآلة التي سيتم استخدامها للعمال.
--num-workers 4
: سيكون لدينا أربعة عاملي معالجة في مجموعتنا.
--image-version 1.4-debian9
: يشير ذلك إلى إصدار الصورة من Dataproc الذي سنستخدمه.
--initialization-actions ...
: إجراءات الإعداد هي نصوص برمجية مخصّصة يتم تنفيذها عند إنشاء المجموعات والعمال. ويمكن أن ينشئها المستخدم ويخزّنها في حزمة GCS أو يشير إليها من الحزمة العامة dataproc-initialization-actions
. سيسمح إجراء الإعداد المُدرَج هنا بتثبيت حِزم Python باستخدام Pip، كما هو موضح في العلامة --metadata
.
--metadata 'PIP_PACKAGES=google-cloud-storage spark-nlp'
: هذه قائمة مفصولة بفواصل بالحِزم المطلوب تثبيتها في Dataproc. في هذه الحالة، سنثبّت google-cloud-storage
مكتبة برامج Python وspark-nlp
.
--optional-components=ANACONDA
: المكونات الاختيارية هي حِزم شائعة تُستخدَم مع Dataproc ويتم تثبيتها تلقائيًا على مجموعات Dataproc أثناء الإنشاء. تشمل مزايا استخدام المكوّنات الاختيارية بدلاً من إجراءات الإعداد أوقات بدء أسرع واختبارها لإصدارات معيّنة من Dataproc. بشكل عام، تكون هذه المراجع أكثر موثوقية.
--enable-component-gateway
: تسمح لنا هذه العلامة بالاستفادة من Dataproc's Component Gateway لعرض واجهات المستخدم الشائعة، مثل Zeppelin أو Jupyter أو Spark History. ملاحظة: تتطلّب بعض هذه المكونات المكوّن الاختياري المرتبط بها.
للحصول على مقدمة أكثر تفصيلاً حول Dataproc، يُرجى الاطّلاع على هذا الدرس التطبيقي.
بعد ذلك، نفِّذ الأوامر التالية في Cloud Shell لنسخ مستودع البيانات باستخدام نموذج الرمز وcd إلى الدليل الصحيح:
cd
git clone https://github.com/GoogleCloudPlatform/cloud-dataproc
cd cloud-dataproc/codelabs/spark-nlp
5- Spark MLlib
Spark MLlib هي مكتبة قابلة للتطوير للتعلم الآلي مكتوبة بلغة Apache Spark. من خلال الاستفادة من كفاءة Spark مع مجموعة من خوارزميات تعلُّم الآلة المحسّنة، يمكن لمجموعة MLlib تحليل كميات كبيرة من البيانات. وتتوفّر واجهات برمجة التطبيقات باللغات البرمجية Java وScala وPython وR. في هذا الدليل التعليمي حول رموز البرمجة، سنركّز على لغة بايثون تحديدًا.
تحتوي حزمة MLlib على مجموعة كبيرة من المحوِّلات والمقدّرات. أداة التحويل هي أداة يمكنها تغيير بياناتك أو تعديلها، عادةً باستخدام دالة transform()
، في حين أنّ أداة التقدير هي خوارزمية مُعدّة مسبقًا يمكنك تدريب بياناتك عليها، عادةً باستخدام دالة fit()
.
تشمل أمثلة المحوِّلات ما يلي:
- تقسيم المحتوى إلى وحدات (إنشاء متجه من الأرقام من سلسلة من الكلمات)
- الترميز الأحادي (إنشاء متجه متفرق من الأرقام التي تمثّل الكلمات المتوفّرة في سلسلة)
- أداة إزالة الكلمات المحظورة (إزالة الكلمات التي لا تضيف قيمة دلالية إلى سلسلة)
تشمل أمثلة التقديرات ما يلي:
- التصنيف (هل هذا تفاح أم برتقال؟)
- الانحدار (كم يجب أن يكلف هذا التفاح؟)
- التجميع (ما مدى تشابه كل التفاحات مع بعضها؟)
- أشجار القرار (إذا كان اللون = برتقالي، هذا يعني أنّه برتقالي). بخلاف ذلك، تكون تفاحة).
- تقليل الأبعاد (هل يمكننا إزالة ميزات من مجموعة البيانات مع مواصلة التمييز بين تفاحة وبرتقان؟)
تحتوي حزمة MLlib أيضًا على أدوات للأساليب الشائعة الأخرى في تعلُّم الآلة، مثل ضبط المَعلمات الفائقة واختيارها، بالإضافة إلى التحقّق التبادلي.
بالإضافة إلى ذلك، يحتوي MLlib على Pipelines API، التي تتيح لك إنشاء قنوات تحويل البيانات باستخدام محوِّلات مختلفة يمكن إعادة تنفيذها.
6- Spark-NLP
Spark-nlp هي مكتبة أنشأتها John Snow Labs لتنفيذ مهام معالجة فعّالة للغة الطبيعية باستخدام Spark. يحتوي على أدوات مدمجة تُعرف باسم "أدوات التعليق التوضيحي" للمهام الشائعة، مثل:
- تقسيم المحتوى إلى وحدات (إنشاء متجه من الأرقام من سلسلة من الكلمات)
- إنشاء نماذج إدراج الكلمات (تحديد العلاقة بين الكلمات من خلال المتجهات)
- علامات أقسام الكلام (أي الكلمات هي أسماء؟ ما هي الأفعال؟)
على الرغم من أنّه خارج نطاق هذا الدرس التطبيقي حول الترميز، يمكن دمج حزمة spark-nlp أيضًا مع TensorFlow.
من بين الميزات الأكثر أهمية، توسّع Spark-NLP إمكانات Spark MLlib من خلال توفير مكوّنات يمكن دمجها بسهولة في مسارات MLlib.
7- أفضل الممارسات المتعلّقة بمعالجة اللغات الطبيعية
قبل أن نتمكّن من استخراج معلومات مفيدة من بياناتنا، علينا الاهتمام ببعض الأمور الإدارية. في ما يلي خطوات المعالجة المُسبَقة التي سننفذها:
إنشاء الرموز المميّزة
أول إجراء نريد اتّخاذه عادةً هو "تجزئة" البيانات. ويشمل ذلك أخذ البيانات وتقسيمها استنادًا إلى "الرموز" أو الكلمات. بشكل عام، نزيل علامات الترقيم ونضبط جميع الكلمات على الأحرف الصغيرة في هذه الخطوة. على سبيل المثال، لنفترض أنّ لدينا السلسلة التالية: What time is it?
بعد تقسيمها إلى وحدات، ستتألف هذه الجملة من أربع وحدات: "what" , "time", "is", "it".
لا نريد أن يتعامل النموذج مع الكلمة what
ككلمتَين مختلفتَين بطريقتَي كتابة مختلفتَين. بالإضافة إلى ذلك، لا تساعدنا عادةً علامات الترقيم في التعرّف بشكل أفضل على الاستنتاجات المستندة إلى الكلمات، لذا نزيل هذه العلامات أيضًا.
تسوية
غالبًا ما نريد "تطبيع" البيانات. سيؤدي ذلك إلى استبدال الكلمات ذات المعنى المشابه بالكلمة نفسها. على سبيل المثال، إذا تم التعرّف على الكلمات "fought" و"battled" و "dueled" في النص، قد تستبدل عملية التسويف كلمة "battled" و "dueled" بكلمة "fought".
تقسيم الكلمات إلى جذور
ستؤدي إزالة البادئات واللواحق إلى استبدال الكلمات بمعناها الأساسي. على سبيل المثال، سيتم استبدال الكلمات "سيارة" و"سيارات" و"سيارة" بكلمة "سيارة"، لأنّ كل هذه الكلمات تشير إلى الشيء نفسه في الأساس.
إزالة الكلمات غير القابلة للاستخدام
الكلمات غير القابلة للاستخدام هي كلمات مثل "و" و "ال" التي لا تضيف عادةً قيمة إلى المعنى الدلالي للجملة. نريد عادةً إزالة هذه العناصر كإجراء للحدّ من التشويش في مجموعات البيانات النصية.
8. تنفيذ المهمة
لنلقِ نظرة على المهمة التي سننفّذها. يمكن العثور على الرمز البرمجي على 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}
يتيح لنا هذا الأمر الاستفادة من Dataproc Jobs API. من خلال تضمين الأمر pyspark
، نشير إلى المجموعة بأنّ هذه مهمة PySpark. نوفّر اسم المجموعة والمَعلمات الاختيارية من بين تلك المتاحة هنا واسم الملف الذي يحتوي على المهمة. في حالتنا، نوفّر المَعلمة --properties
التي تسمح لنا بتغيير خصائص مختلفة لـ Spark أو Yarn أو Dataproc. نحن بصدد تغيير سمة Spark packages
التي تتيح لنا إبلاغ Spark بأنّنا نريد تضمين spark-nlp
في الحزمة مع مهمتنا. نقدّم أيضًا المَعلمات --driver-log-levels root=FATAL
التي ستؤدي إلى إخفاء معظم نتائج السجلّ من PySpark باستثناء الأخطاء. بشكل عام، تميل سجلات Spark إلى أن تكون صاخبة.
أخيرًا، -- ${BUCKET}
هي وسيطة سطر الأوامر لنص Python البرمجي نفسه الذي يقدّم اسم الحزمة. يُرجى ملاحظة المسافة بين --
و${BUCKET}
.
بعد بضع دقائق من تشغيل المهمة، من المفترض أن يظهر لنا ناتج يحتوي على نماذجنا:
رائع. هل يمكنك استنتاج المؤشرات من خلال الاطّلاع على النتائج التي يقدّمها النموذج؟ ماذا عن عروضنا؟
من خلال النتيجة أعلاه، يمكن استنتاج مؤشر من الموضوع 8 يتعلّق بطعام الإفطار والحلويات من الموضوع 9.
9. تنظيف
لتجنُّب تحصيل رسوم غير ضرورية من حسابك على Google Cloud Platform بعد إكمال هذا الدليل السريع:
- حذف حزمة Cloud Storage التي أنشأتها للبيئة
- احذِف بيئة Dataproc.
إذا أنشأت مشروعًا لهذا الدليل التعليمي فقط، يمكنك أيضًا حذف المشروع اختياريًا:
- في وحدة تحكّم Google Cloud Platform، انتقِل إلى صفحة المشاريع.
- في قائمة المشاريع، اختَر المشروع الذي تريد حذفه وانقر على حذف.
- في المربّع، اكتب رقم تعريف المشروع، ثم انقر على إيقاف لحذف المشروع.
الترخيص
يخضع هذا العمل لترخيص عام بموجب رخصة المشاع الإبداعي 3.0 مع نسب العمل إلى مؤلفه، وترخيص Apache 2.0.