پردازش زبان طبیعی (NLP) با پایتون — راهنمای جامع

۲۸۸۴ بازدید
آخرین به‌روزرسانی: ۰۸ مهر ۱۴۰۲
زمان مطالعه: ۳۳ دقیقه
پردازش زبان طبیعی (NLP) با پایتون — راهنمای جامع

در این راهنما از راهبردهای تست شده و کارآمد برای بررسی مسائل پردازش زبان طبیعی (NLP) بهره گرفته شده است. تلاش شده است تا یادگیری NLP برای همه افراد آسان‌تر شده و همچنین برای افراد علاقه‌مند به این حوزه چشم‌اندازهای نوینی عرضه شود.

مقدمه

داده‌های ساخت‌نیافته و به طور خاص متن، تصاویر و ویدیوها حاوی حجم بالایی از اطلاعات هستند. با این حال به دلیل پیچیدگی ذ‌اتی پردازش و تجزیه و تحلیل این داده‌ها، افراد غالباً از صرف زمان و تلاش زیاد روی مجموعه داده‌های ساخت‌نیافته که در حکم کاوش معدن طلا هستند اجتناب می‌کنند.

پردازش زبان طبیعی (NLP) به بهره‌گیری از ابزارها، تکنیک‌ها و الگوریتم‌ها برای پردازش و درک داده‌های طبیعی مبتنی بر زبان مربوط است که معمولاً در قالب‌های ساخت‌نیافته‌ای مانند متن، سخنرانی و غیره وجود دارند. در این مقاله به برخی راهبردها، تکنیک‌ها و گردش کارهای آزموده شده نگاه خواهیم کرد که می‌توانند از سوی متخصصان این رشته، دانشمندان داده برای استخراج بینش‌های مفیدی از داده‌های متنی مورد استفاده قرار گیرند. همچنین موارد استفاده مفید و جذاب NLP را با هم مرور خواهیم کرد. هدف کلی این مقاله آموزش پردازش و درک داده‌های متنی به کمک خودآموزها و مثال‌های ساده است.

بخش‌های مقاله

ماهیت این مقاله آمیزه‌ای از مفاهیم نظری است؛ اما روی تکنیک‌ها و راهبردهای کارآمدی که مسائل مختلف NLP را پوشش می‌دهند نیز متمرکز شده‌ایم. برخی از حوزه‌های عمده‌ای که در این مقاله مورد بررسی قرار گرفته‌اند شامل موارد زیر هستند:

  1. پردازش و درک متن
  2. مهندسی ویژگی و بازنمایی متن
  3. مدل‌های یادگیری نظارت‌شده برای داده‌های متنی
  4. مدل‌های یادگیری نظارت‌نشده برای داده‌های متنی
  5. موضوعات پیشرفته

اغلب مواردی که در این مقاله ارائه می‌شوند، نکات و راهبردهایی هستند که در مسائل دنیای واقعی به طور کامل مورد استفاده قرار می‌گیرند.

جنبه‌های تحت پوشش این مقاله

در این مقاله جنبه‌های زیر از NLP را به همراه مثال‌های عملی به تفصیل بررسی خواهیم کرد:

  1. بازیابی داده با اسکراپینگ وب
  2. استخراج و گردآوری متون (Text wrangling) و پیش‌پردازش آن‌ها
  3. تگ گذاری اجزای گفتار
  4. تجزیه سطحی
  5. تجزیه وابستگی و تجزیه سازه‌ای
  6. بازشناسی نهاد دارای نام
  7. تحلیل عاطفی و احساسات

فهرست فوق ایده خوبی به شما ارائه می‌کند که کار خود را در مورد تجزیه و تحلیل دستور زبان و معناشناسی متون از کجا آغاز خواهیم کرد.

انگیزه

در نهایت، NLP حوزه‌ای تخصصی در علوم رایانه و هوش مصنوعی محسوب می‌شود که ریشه در زبانشناسی محاسباتی دارد.

دغدغه اصلی این حوزه از علوم، طراحی و ساخت برنامه و سیستم‌هایی است که امکان تعامل بین ماشین‌ها و زبان‌های طبیعی را فراهم سازند و در طی زمان برای استفاده انسان تکامل پیدا کنند. از این رو در اغلب موارد این حوزه علمی به عنوان یک زمینه کم‌عمق و سطحی برای تحقیق نگریسته می‌شود و افراد تمایل دارند که بیشتر روی یادگیری ماشین و یادگیری آماری تمرکز کنند.

اکثر کسانی که وارد دنیای علم داده می‌شوند، در نهایت در چالش‌های آنالیز و مدل‌سازی داده‌های متنی غرق می‌شوند. با این حال پس از کار چندین ساله به عنوان دانشمند داده روی چندین مسئله چالش‌برانگیز در زمینه NLP متوجه می‌شوند که این حوزه جنبه‌های جذابی مانند تکنیک‌ها، راهبردها و گردش کارهای خاص خود را دارد که می‌توان از آن برای حل طیف متنوعی از مسائل بهره گرفت.

هدف نهایی این مقاله آن است که آموزش‌های پردازش زبان طبیعی در یک راهنمای سریع و فشرده برای افرادی که فرصت مطالعه کتاب‌های قطور را ندارند عرضه شود.

سرآغاز

زمانی که مشغول ساخت محتوا و مثال‌های این آموزش بودیم بین دو راه برای انتخاب مجموعه داده ساختگی برای بررسی بهتر مسائل و یا تمرکز روی مجموعه داده‌های موجود از یکی از منابع برای علوم داده دچار شک و تردید بودیم. در نهایت تصمیم گرفتیم به اسکراپ کردن وب و گردآوری برخی متون جهت ایجاد مثال‌های کاربردی بر اساس آن بپردازیم.

داده‌های منبع که روی آن‌ها کار خواهیم کرد، مقالات خبری هستند که از وب سایت inshorts گردآوری شده‌اند. این وب سایت مقالات خبری کوتاه 60 کلمه‌ای را در حوزه‌های متنوعی ارائه می‌کند و بدین منظور حتی اپلیکیشنی نیز طراحی کرده است.

در این مقاله روی داده‌های متنی از مقالات خبری در حوزه‌های فناوری، ورزش و اخبار دنیا کار خواهیم کرد. در ابتدا برخی مبانی اسکراپ کردن و بازیابی این مقالات از وب‌سایت‌هایشان در بخش بعدی بررسی شده است.

گردش کار NLP استاندارد

در این بخش فرض می‌کنیم که شما از مدل CRISP-DM که معمولاً یک استاندارد صنعتی برای اجرای هر پروژه علم داده محسوب می‌شود، آگاهی دارید. به طور معمول هر مسئله مبتنی بر NLP را می‌توان به وسیله گردش کاری روش‌شناختی (methodical) که یک توالی از گام‌ها دارد، حل نمود. گام‌های اصلی در تصویر زیر مشخص شده‌اند.

گردش کاری با استاندارد بالا برای هر پروژه NLP

ما معمولاً با توده‌ای از اسناد کار خود را آغاز می‌کنیم و با پیروی از فرایندهای استاندارد برای گردآوری متون و پیش-پردازش، تجزیه و آنالیز مقدماتی کاوشی داده کار خود را ادامه می‌دهیم. بر اساس بینش‌های اولیه، معمولاً متن را با استفاده از تکنیک‌های مهندسی ویژگی، بازنمایی می‌کنیم. سپس بسته به مسئله‌ای که در دست داریم یا روی ساخت مدل‌های نظارت‌شده پیش‌بین و یا مدل‌های نظارت‌نشده متمرکز می‌شویم که معمولاً بیشتر تمرکز روی کاوش الگو و گروه‌بندی است. در نهایت به ارزیابی مدل و تعیین معیار کلی موفقیت بر اساس نظر مشتریان یا ذینفعان مرتبط پرداخته و مدل نهایی را برای استفاده‌های بعدی انتشار می‌دهیم.

اسکراپ کردن مقالات خبری برای بازیابی داده

ما به اسکراپ کردن وب سایت inshorts از طریق بهره‌گیری از پایتون برای بازیابی مقالات خبری خواهیم پرداخت.

در این بخش روی مقالاتی در حوزه‌های فناوری، ورزش و اخبار جهانی متمرکز شده‌ایم. از هر دسته مقالاتی به اندازه یک صفحه انتخاب می‌کنیم. یک صفحه فرود (Landing page) دسته خبری معمولی در تصویر زیر نمایش یافته است که بخش‌های HTML برای محتوای متنی هر مقاله نیز مشخص شده‌اند:

صفحه فرود (Landing page) برای مقالات خبری در حوزه فناوری و ساختارهای HTML مربوط به آن

از این رو می‌توانیم ‌بینیم که تگ‌های HTML خاص شامل محتوای متنی از هر مقاله خبری در صفحه فرود فوق‌الذکر است. ما از این اطلاعات برای استخراج مقالات خبری به کمک کتابخانه‌های BeautifulSoup و requests استفاده می‌کنیم. در ابتدا وابستگی‌های زیر را بارگذاری می‌کنیم.

import requests
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import os

%matplotlib inline

سپس تابعی می‌سازیم که از requests (درخواست‌ها) برای دسترسی و دریافت محتوای HTML از صفحه‌های فرود سه دسته خبری وب سایت استفاده می‌کند. در این زمان از BeautifulSoup برای تجزیه و استخراج عناوین خبری و محتوای متنی مقالات برای همه مقالات خبری در هر دسته بهره می‌گیریم. ما محتوای اخبار را با دسترسی به تگ‌ها و کلاس‌های خاص HTML به دست آوردیم. نمونه‌ای از این اخبار در تصویر قبل ارائه شده است.

seed_urls = ['https://inshorts.com/en/read/technology',
             'https://inshorts.com/en/read/sports',
             'https://inshorts.com/en/read/world']

def build_dataset(seed_urls):
    news_data = []
    for url in seed_urls:
        news_category = url.split('/')[-1]
        data = requests.get(url)
        soup = BeautifulSoup(data.content, 'html.parser')
        
        news_articles = [{'news_headline': headline.find('span', 
                                                         attrs={"itemprop": "headline"}).string,
                          'news_article': article.find('div', 
                                                       attrs={"itemprop": "articleBody"}).string,
                          'news_category': news_category}
                         
                            for headline, article in 
                             zip(soup.find_all('div', 
                                               class_=["news-card-title news-right-box"]),
                                 soup.find_all('div', 
                                               class_=["news-card-content news-right-box"]))
                        ]
        news_data.extend(news_articles)
        
    df =  pd.DataFrame(news_data)
    df = df[['news_headline', 'news_article', 'news_category']]
return df

کاملاً مشخص است که عناوین خبری، متن مقالات و دسته‌ها را استخراج و یک چارچوب داده‌ای ایجاد کرده‌ایم که در آن هر ردیف متناظر با مقالات خبری خاصی است. سپس این تابع را فراخوانی کرده و مجموعه داده خود را می‌سازیم.

news_df = build_dataset(seed_urls)
news_df.head(10)

اینک یک مجموعه داده کاملاً قالب‌بندی شده از مقالات خبری داریم و می‌توانیم به سرعت تعداد کل مقاله‌های خبری را با کد زیر بررسی کنیم.

news_df.news_category.value_counts()

Output:
-------
world 25
sports 25
technology 24
Name: news_category, dtype: int64

استخراج متون و پیش-پردازش

معمولاً چند مرحله در زمینه پاک‌سازی و پیش-پردازش داده‌های متنی وجود دارد. مراحل پیش-پردازش متن به تفصیل در این لینک ارائه شده است. با این حال در این بخش نیز برخی از مهم‌ترین گام‌هایی که به طور مکرر در پردازش زبان طبیعی (NLP) مورد استفاده قرار می‌گیرند را بررسی کرده‌ایم. این گام‌ها به وفور در پروژه‌های NLP مورد بهره‌برداری قرار می‌گیرند. ما اندکی از nltk و spacy استفاده می‌کنیم که هر دو کتابخانه‌های تثبیت‌شده‌ای در حوزه NLP هستند. به طور معمول pip install <library> یا یک conda install <library> بدین منظور کفایت می‌کند. با این وجود، در حالتی که با مشکلی در بارگذاری مدل‌های زبان spacy مواجه شدید، می‌توانید از مراحل زیر برای رفع مشکل استفاده کنید.

# OPTIONAL: ONLY USE IF SPACY FAILS TO LOAD LANGUAGE MODEL
# Use the following command to install spaCy
> pip install -U spacy

OR

> conda install -c conda-forge spacy

# Download the following language model and store it in disk
https://github.com/explosion/spacy-models/releases/tag/en_core_web_md-2.0.0

# Link the same to spacy
> python -m spacy link./spacymodels/en_core_web_md-2.0.0/en_core_web_md en_core

Linking successful
./spacymodels/en_core_web_md-2.0.0/en_core_web_md -->./Anaconda3/lib/site-packages/spacy/data/en_core

You can now load the model via spacy.load('en_core')

اینک وابستگی‌های ضروری برای پیش-پردازش متن را بارگذاری می‌کنیم. واژه‌های خنثی را از میان کلمات اضافه حذف خواهیم کرد، زیرا می‌خواهیم فقط کلمات مفید را به خصوص در طی تحلیل معناشناختی حفظ کنیم.

نکته مهم: بسیاری از افراد اعلام کرده‌اند که نمی‌توانند ماژول contractions را بارگذاری کنند. چون ماژول استاندارد پایتون نیست. ما از مجموعه استاندارد contractions که در فایل contractions.py در my repository وجود دارد استفاده می‌کنیم. بنابراین باید آن را در همان دایرکتوری که کد را اجرا می‌کنید قرار دهید، چون در غیر این صورت کار نمی‌کند.

import spacy
import pandas as pd
import numpy as np
import nltk
from nltk.tokenize.toktok import ToktokTokenizer
import re
from bs4 import BeautifulSoup
from contractions import CONTRACTION_MAP
import unicodedata

nlp = spacy.load('en_core', parse=True, tag=True, entity=True)
#nlp_vec = spacy.load('en_vecs', parse = True, tag=True, #entity=True)
tokenizer = ToktokTokenizer()
stopword_list = nltk.corpus.stopwords.words('english')
stopword_list.remove('no')
stopword_list.remove('not')

حذف کردن تگ‌های HTML

متن‌های ساخت‌نیافته غالباً شامل مقدار زیادی نویز هستند، به خصوص اگر از تکنیک‌هایی مانند اسکراپ کردن وب یا صفحه استفاده کنید. تگ‌های HTML به طور معمول یکی از مؤلفه‌هایی هستند که ارزش زیادی در جهت درک و آنالیز متن اضافه نمی‌کنند.

def strip_html_tags(text):
    soup = BeautifulSoup(text, "html.parser")
    stripped_text = soup.get_text()
    return stripped_text

strip_html_tags('<html><h2>Some important text</h2></html>')

 

'Some important text'

از روی خروجی فوق کاملاً مشخص است که می‌توانیم تگ‌های HTML غیر ضروری را حذف و اطلاعات متنی مفید را در همه اسناد حفظ کنیم.

حذف کاراکترهای آکسان دار

معمولاً در همه اسناد متنی با کاراکترها/حروف آکسان دار مواجه می‌شویم به خصوص اگر بخواهید زبان انگلیسی را آنالیز کنید. از این رو باید مطمئن شویم که این کاراکترها به صورت کاراکترهای ASCII تبدیل و استاندارد شده‌اند. یک نمونه ساده تبدیل é به e است.

def remove_accented_chars(text):
    text = unicodedata.normalize('NFKD', text).encode('ascii', 'ignore').decode('utf-8', 'ignore')
    return text

remove_accented_chars('Sómě Áccěntěd těxt')
'Some Accented text'

تابع قبلی به ما نشان می‌دهد که چگونه می‌توانیم به راحتی کاراکترهای آکسان دار را به کاراکترهای نرمال انگلیسی تبدیل کنیم که واژه‌ها را در اسناد متنی به صورت استاندارد در می‌آورد.

حالت گسترده اختصارات

اختصارها نسخه خلاصه‌شده‌ای از کلمات یا هجاها هستند. آن‌ها معمولاً به شکل‌های مکتوب یا شفاهی در زبان انگلیسی وجود دارند. نسخه‌های خلاصه شده یا اختصاری کلمات با حذف برخی حروف و صداهای خاص تولید می‌شوند. در مورد اختصارات انگلیسی در اغلب موارد از طریق حذف یک یا چند مصوت از کلمه پدید می‌آیند. نمونه‌هایی از اختصارات به صورت تبدیل do not به don’t و تبدیل I would به I’d است. تبدیل نسخه اختصاری هر عبارت به حالت اصلی‌اش به استانداردسازی متن کمک می‌کند.

برای استفاده از مجموعه استانداردی از اختصارات می‌توانید به فایل contractions.py در مخزن سورس کد (Repository) این مطلب مراجعه کنید.

def expand_contractions(text, contraction_mapping=CONTRACTION_MAP):
    
    contractions_pattern = re.compile('({})'.format('|'.join(contraction_mapping.keys())), 
                                      flags=re.IGNORECASE|re.DOTALL)
    def expand_match(contraction):
        match = contraction.group(0)
        first_char = match[0]
        expanded_contraction = contraction_mapping.get(match)\
                                if contraction_mapping.get(match)\
                                else contraction_mapping.get(match.lower())                       
        expanded_contraction = first_char+expanded_contraction[1:]
        return expanded_contraction
        
    expanded_text = contractions_pattern.sub(expand_match, text)
    expanded_text = re.sub("'", "", expanded_text)
    return expanded_text

expand_contractions("Y'all can't expand contractions I'd think")
'You all cannot expand contractions I would think'

در خروجی فوق می‌بینیم که این تابع تا چه مقدار به بسط دادن اختصارات کمک می‌کند. اما آیا روش بهتری برای این کار وجود دارد؟ مسلماً وجود دارد، اگر ما نمونه‌های کافی داشته باشیم، می‌توانیم یک مدل یادگیری عمیق برای عملکرد بهتر تمرین بدهیم.

حذف کاراکترهای ویژه

کاراکترهای ویژه و نمادها معمولاً کاراکترهای عددی-حرفی یا حتی در مواردی کاراکترهای عددی (بسته به مسئله) هستند که باعث افزایش نویز در متون ساخت‌نیافته می‌شوند. به طور معمول می‌توان از عبارت‌های قاعده‌مند (regex ها) برای حذف آن‌ها استفاده کرد.

def remove_special_characters(text, remove_digits=False):
    pattern = r'[^a-zA-z0-9\s]' if not remove_digits else r'[^a-zA-z\s]'
    text = re.sub(pattern, '', text)
    return text

remove_special_characters("Well this was fun! What do you think? 123#@!", 
remove_digits=True)
'Well this was fun What do you think '

حذف ارقام به صورت اختیاری است، زیرا در اغلب موارد ممکن است در مرحله پیش-پردازش متن نیاز به حفظ آن‌ها وجود داشته باشد.

ریشه‌یابی لغوی (Stemming)

برای درک Stemming می‌بایست درکی از ریشه کلمه داشته باشید. ریشه‌های کلمات که به نام حالت پایه واژه نیز شناخته می‌شوند مواردی هستند که پسوندهای مختلف در فرایندی به نام تصریف به آن می‌چسبند و واژه‌های جدیدی می‌سازند. برای مثال واژه JUMP را در نظر بگیرید. می‌توان به آن پسوندهایی اضافه کرد و کلمات جدیدی مانند JUMPS، JUMPED، و JUMPING از آن ساخت. در این مورد واژه پایه JUMP همان ریشه کلمه است.

ریشه کلمه و تصریف‌های آن

تصویر فوق چگونگی حضور ریشه کلمه در همه تصریف‌هایش را نشان می‌دهد، چون ریشه کلمه پایه‌ای برای همه تصریف‌هایی است که به وسیله پسوند ساخته شده‌اند. فرایند معکوس به دست آوردن ریشه از یک کلمه تصریف‌شده به نام stemming نامیده می‌شود. Stemming به ما کمک می‌کند که کلمات را به صورت ریشه کلمه یا حالت پایه استانداردسازی بکنیم. در این حالت تصریف‌ها را نادیده می‌گیریم و این وضعیت کاربردهای مفیدی مانند طبقه‌بندی یا خوشه‌بندی متن و حتی بازیابی اطلاعات دارد. در ادامه یک ریشه‌یاب رایج به نام Porter را می‌بینید:

def simple_stemmer(text):
    ps = nltk.porter.PorterStemmer()
    text = ' '.join([ps.stem(word) for word in text.split()])
    return text

simple_stemmer("My system keeps crashing his crashed yesterday, ours crashes daily")
'My system keep crash hi crash yesterday, our crash daili'

ریشه‌یاب پورتر بر اساس الگوریتم توسعه یافته از سوی مبدع آن دکتر مارتین پورتر (Martin Porter) ساخته شده است. این الگوریتم در ابتدا دارای 5 مرحله برای کاهش تصریف‌ها به ریشه‌ها داشت که در هر مرحله مجموعه قواعد خاصی وجود دارد.

توجه داشته باشید که Stemming معمولاً مجموعه قواعد ثابتی دارد و از این رو ریشه‌های کلمات ممکن است از نظر لغوی صحیح نباشند. این بدان معنی است که واژه‌های ریشه‌یابی شده ممکن است از نظر معناشناختی صحیح نباشند و همچنین ممکن است حتی در لغت‌نامه وجود نداشته باشند (چنان که در خروجی فوق مشاهده می‌شود).

ریشه‌یابی معنایی (Lemmatization)

Lemmatization تا حدود زیادی شبیه stemming است و در طی این فرایند پسوندهای کلمه حذف می‌شوند تا به حالت پایه کلمه برسیم. با این وجود حالت پایه در این وضعیت به نام کلمه ریشه شناخته می‌شود و نه stem ریشه. تفاوت در این جاست که کلمه ریشه همواره از نظر فرهنگ واژگانی یک کلمه صحیح است (در لغت‌نامه وجود دارد)؛ اما stem ریشه ممکن است چنین نباشد. از این رو کلمه ریشه به نام lemma نیز نامیده می‌شود که همواره در دیکشنری وجود دارد. هر دو کتابخانه nltk و spacy الگوریتم‌های lemmitizer عالی دارند. ما در این نوشته از spacy استفاده خواهیم کرد.

def lemmatize_text(text):
    text = nlp(text)
    text = ' '.join([word.lemma_ if word.lemma_ != '-PRON-' else word.text for word in text])
    return text

lemmatize_text("My system keeps crashing! his crashed yesterday, ours crashes daily")

حذف کلمات اضافی (Stopwords)

کلماتی که بی معنی هستند یا معنای خاصی ندارند، به خصوص وقتی ویژگی‌های معنایی از متن استخراج می‌شود به نام Stopword نامیده می‌شوند. این موارد معمولاً کلماتی هستند که فراوانی زیادی در متن دارند. به طور معمول این کلمات شامل حروف تعریف، ربط، اضافه و مواردی از این دست هستند. برخی نمونه‌های stopword ها شامل a، an، the، and و موارد مشابه را شامل می‌شوند.

def remove_stopwords(text, is_lower_case=False):
    tokens = tokenizer.tokenize(text)
    tokens = [token.strip() for token in tokens]
    if is_lower_case:
        filtered_tokens = [token for token in tokens if token not in stopword_list]
    else:
        filtered_tokens = [token for token in tokens if token.lower() not in stopword_list]
    filtered_text = ' '.join(filtered_tokens)    
    return filtered_text

remove_stopwords("The, and, if are stopwords, computer is not")
',, stopwords, computer not'

یک فهرست سراسری از stopword ها وجود ندارد؛ اما می‌توانید در کتابخانه nltk یک فهرست از stopword های استاندارد زبان انگلیسی بیابید. همچنین می‌توانید stopword های خاص کاربری خودتان را در صورت لزوم به آن اضافه کنید.

جمع‌بندی همه موارد – ساخت یک نرمالایزر متن

با این که ما می‌توانیم همچنان به معرفی تکنیک‌های بیشتری مانند خطایاب املایی، خطایاب گرامری و موارد دیگر ادامه دهیم؛ اما اجازه دهید آن چه تاکنون معرفی کرده‌ایم را جمع‌بندی کنیم و این عملیات‌ها را به هم مرتبط سازیم تا یک نرمالایزر متن برای پیش–پردازش داده‌های متنی بسازیم.

def normalize_corpus(corpus, html_stripping=True, contraction_expansion=True,
                     accented_char_removal=True, text_lower_case=True, 
                     text_lemmatization=True, special_char_removal=True, 
                     stopword_removal=True, remove_digits=True):
    
    normalized_corpus = []
    # normalize each document in the corpus
    for doc in corpus:
        # strip HTML
        if html_stripping:
            doc = strip_html_tags(doc)
        # remove accented characters
        if accented_char_removal:
            doc = remove_accented_chars(doc)
        # expand contractions    
        if contraction_expansion:
            doc = expand_contractions(doc)
        # lowercase the text    
        if text_lower_case:
            doc = doc.lower()
        # remove extra newlines
        doc = re.sub(r'[\r|\n|\r\n]+', ' ',doc)
        # lemmatize text
        if text_lemmatization:
            doc = lemmatize_text(doc)
        # remove special characters and\or digits    
        if special_char_removal:
            # insert spaces between special characters to isolate them    
            special_char_pattern = re.compile(r'([{.(-)!}])')
            doc = special_char_pattern.sub(" \\1 ", doc)
            doc = remove_special_characters(doc, remove_digits=remove_digits)  
        # remove extra whitespace
        doc = re.sub(' +', ' ', doc)
        # remove stopwords
        if stopword_removal:
            doc = remove_stopwords(doc, is_lower_case=text_lower_case)
            
        normalized_corpus.append(doc)
        
return normalized_corpus

اینک این تابع را مورد بهره‌برداری عملی قرار می‌دهیم. نخست عناوین اخبار و متن مقالات خبری را با هم ترکیب می‌کنیم تا سندی برای هر خبر تشکیل دهیم. سپس آن‌ها را مورد پیش-پردازش قرار می‌دهیم.

# combining headline and article text
news_df['full_text'] = news_df["news_headline"].map(str)+ '. ' + news_df["news_article"]

# pre-process text and store the same
news_df['clean_text'] = normalize_corpus(news_df['full_text'])
norm_corpus = list(news_df['clean_text'])

# show a sample news article
news_df.iloc[1][['full_text', 'clean_text']].to_dict()
{'clean_text': 'us unveils world powerful supercomputer beat china us unveil world powerful supercomputer call summit beat previous record holder china sunway taihulight peak performance trillion calculation per second twice fast sunway taihulight capable trillion calculation per second summit server reportedly take size two tennis court',

'full_text': "US unveils world's most powerful supercomputer, beats China. The US has unveiled the world's most powerful supercomputer called 'Summit', beating the previous record-holder China's Sunway TaihuLight. With a peak performance of 200,000 trillion calculations per second, it is over twice as fast as Sunway TaihuLight, which is capable of 93,000 trillion calculations per second. Summit has 4,608 servers, which reportedly take up the size of two tennis courts."}

بدین ترتیب می‌بینید که پیش-پردازشگر متن ما در زمینه پیش-پردازش مقالات خبری به درستی عمل می‌کند. پس از این شما می‌توانید این مجموعه داده را در صورت نیاز روی دیسک ذخیره کنید به طوری که همواره در موارد بعدی برای تحلیل بیشتر آن را بارگذاری کنید.

news_df.to_csv('news.csv', index=False, encoding='utf-8')

درک ساختار و دستور زبان

در هر زبانی برخی ساختارها و دستور زبان وجود دارند که همراه با هم حرکت می‌کنند و در آن مجموعه قواعد خاص، موارد عرفی و مفاهیمی وجود دارند که شیوه ترکیب کلمات برای تشکیل عبارت‌ها را تعیین می‌کنند. عبارت‌ها با هم ترکیب می‌شوند و اجزای جمله (clauses) را تشکیل می‌دهند و این اجزا نیز با ترکیب خود جمله‌ها را می‌سازند. ما در این راهنما به طور خاص در مورد دستور زبان انگلیسی و ساختار آن صحبت می‌کنیم. در زبان انگلیسی واژه‌ها معمولاً با هم ترکیب می‌شوند تا واحدهای سازنده دیگری را تشکیل دهند. این واحدهای سازنده شامل کلمات، عبارت‌ها، اجزای جمله و جمله‌ها هستند. برای مثال جمله «The brown fox is quick and he is jumping over the lazy dog» را در نظر بگیرید که از ترکیبی از کلمات تشکیل شده است. وقتی به کلمات نگاه می‌کنیم تصور می‌کنیم که هر یک به تنهایی معنی چندانی ندارند.

دسته‌ای از کلمات بدون نظم که معنای چندانی به ذهن متبادر نمی‌کنند

دانش ما در خصوص ساختار و دستور زبان هر زبانی در بسیاری از زمینه‌ها مانند پردازش، تفسیر و تجزیه متون برای عملیات‌های دیگر مانند طبقه‌بندی متون و خلاصه‌سازی مفید است. تکنیک‌های معمول تجزیه متن برای درک دستور زبان متن در ادامه فهرست شده‌اند:

  • تگ گذاری اجزای گفتار (POS)
  • تجزیه سطحی یا خُردکردن متن
  • تجزیه سازه‌ای
  • تجزیه وابستگی

ما در بخش‌های بعدی همه این تکنیک‌ها را بررسی خواهیم کرد. اگر بخواهیم جمله نمونه قبلی خودمان «The brown fox is quick and he is jumping over the lazy dog» را با استفاده از تگ‌های POS حاشیه‌نویسی کنیم، چیزی شبیه تصویر زیر خواهد بود:

تگ گذاری POS برای یک جمله

از این رو یک جمله به طور معمول از ساختار سلسله‌مراتبی تبعیت می‌کند که شامل مؤلفه‌های زیر است:

sentence → clauses → phrases → words

تگ گذاری اجزای گفتار

اجزای گفتار (POS) دسته‌بندی‌های واژگانی خاصی هستند که در آن واژه‌ها بر اساس زمینه و نقش معنایی‌شان به دسته‌های مختلف انتساب می‌یابند. به طور معمول واژه‌ها می‌توانند در یکی از دسته‌های اصلی زیر قرار گیرند:

  • اسم (Noun): این جزء معمولاً واژه‌هایی را شامل می‌شود که یک شیء یا نهاد را معرفی می‌کنند که می‌تواند زنده یا غیر زنده باشد. برخی نمونه‌ها شامل روباه، سگ، کتاب و غیره هستند. نماد تگ POS برای اسم‌ها به صورت N است.
  • فعل (Verb): افعال همان واژه‌هایی هستند که اعمال، وضعیت‌ها یا رخدادهای خاصی را توصیف می‌کنند. طیف متنوعی از زیر دسته‌های افعال مانند افعال کمکی، افعال انعکاسی و افعال گذرا (و موارد بسیار دیگر) هستند. برخی نمونه‌های معمول افعال به صورت دویدن، پریدن، خواندن و نوشتن هستند. نماد تگ POS برای افعال به صورت V است.
  • صفت (Adjective): صفت‌ها، اسم‌هایی هستند که برای توصیف یا تعیین کیفیت واژه‌های دیگر که معمولاً اسم یا عبارت‌های اسمی هستند، به کار می‌روند. عبارت گل زیبا یک اسم (N) دارد که با استفاده از یک صفت (ADJ ) به صورت زیبا توصیف شده است. نماد تگ POS برای صفت‌ها به صورت ADJ است.
  • قید (Adverb): قیدها معمولاً به صورت اصلاح‌کننده (Modifier) برای واژه‌های دیگر شامل اسم، صفت، فعل و یا دیگر قیدها استفاده می‌شوند. عبارت گل بسیار زیبا دارای قید (Adv) «بسیار» است که صفت زیبا را تعدیل کرده و میزان زیبایی آن را تعیین می‌کند. نماد تگ POS برای قیدها به صورت ADV است.

علاوه بر این چهار دسته اصلی اجزای گفتار، دسته‌های دیگری نیز وجود دارند که به طور مکرر در زبان انگلیسی ظاهر می‌شوند. این موارد شامل ضمایر، حروف اضافه، حروف ندا، حروف ربط، ضمایر اشاره و موارد بسیار دیگر هستند. به علاوه، هر تگ POS مانند اسم (N) را می‌توان به زیر دسته‌هایی مانند اسامی مفرد (NN)، اسامی خاص مفرد (NNP) و اسامی جمع (NNS) تقسیم کرد.

این فرایند طبقه‌بندی و برچسب‌گذاری تگ‌های POS برای کلمات به نام تگ گذاری اجزای گفتار نامیده می‌شود. تگ‌های POS برای حاشیه‌نویسی کلمات و تعیین POS آن‌ها استفاده می‌شوند که در جهت اجرای آنالیزهای خاص مانند خلاصه‌سازی اسامی و تعیین رایج‌ترین اسم‌ها، ابهام‌زدایی از معنی کلمه و آنالیز گرامری کاملاً مفید هستند. ما در این راهنما از کتابخانه‌های nltk و spacy استفاده می‌کنیم که معمولاً از Penn Treebank notation برای تگ گذاری POS بهره می‌گیرند.

# create a basic pre-processed corpus, don't lowercase to get POS context
corpus = normalize_corpus(news_df['full_text'], text_lower_case=False, 
                          text_lemmatization=False, special_char_removal=False)

# demo for POS tagging for sample news headline
sentence = str(news_df.iloc[1].news_headline)
sentence_nlp = nlp(sentence)

# POS tagging with Spacy 
spacy_pos_tagged = [(word, word.tag_, word.pos_) for word in sentence_nlp]
pd.DataFrame(spacy_pos_tagged, columns=['Word', 'POS tag', 'Tag type'])

# POS tagging with nltk
nltk_pos_tagged = nltk.pos_tag(sentence.split())
pd.DataFrame(nltk_pos_tagged, columns=['Word', 'POS tag'])
تگ گذاری POS یک عنوان خبری

 

می‌توانید ببینید که هر یک از این کتابخانه‌ها به روش خاص خود با توکن‌ها برخورد می‌کنند و تگ‌های خاصی را به آن‌ها انتساب می‌دهند. بر اساس آنچه مشاهده می‌شود، به نظر می‌رسد که spacy اندکی بهتر از nltk عمل می‌کند.

تجزیه سطحی یا خرد کردن متن

بر اساس سلسله‌مراتبی که قبلاً توصیف کردیم، گروه‌های کلمات عبارت‌ها را می‌سازند، پنج دسته از عبارت‌ها وجود دارند.

  • عبارت‌های اسمی (NP): این عبارت‌ها آن‌هایی هستند که در آن یک اسم به عنوان نهاد عمل می‌کند. عبارت‌های اسمی به عنوان مفعول یا فاعل برای یک فعل عمل می‌کنند.
  • عبارت‌های فعلی (VP): این عبارت‌ها واحدهای واژگانی هستند که در آن یک فعل به عنوان نهاد عمل کند. به طور معمول دو شکل از عبارت‌های فعلی وجود دارند. یک شکل از مؤلفه‌های فعلی مانند دیگر نهادها است که شامل اسم، صفت یا قید به عنوان اجزای فاعلی هستند.
  • عبارت‌های توصیفی (ADJP): این عبارت‌ها دارای یک صفت به عنوان کلمه نهاد هستند. نقش اصلی آن‌ها توصیف یا تعیین کیفیت اسم‌ها و ضمایر در یک جمله است و معمولاً پیش یا پس از یک اسم یا ضمیر قرار می‌گیرند.
  • عبارت‌های قیدی (ADVP): این عبارت‌ها مانند قیدها عمل می‌کنند، زیرا قید در یک عبارت قیدی به عنوان نهاد عمل می‌کند. عبارت‌های قیدی به عنوان اصلاح‌کننده‌هایی برای اسم، فعل یا خود قیدها عمل می‌کنند و جزییات بیشتری ارائه می‌کنند که آن‌ها را توصیف کرده و کیفیتشان را بیان می‌کند.
  • عبارت‌های اضافی (PP): این عبارت‌ها معمولاً شامل یک حرف اضافه به عنوان نهاد هستند و اجزای واژگان دیگری مانند اسم، ضمیر و موارد دیگر را نیز شامل می‌شوند. این عبارت‌ها به عنوان یک قید یا صفت عمل کرده و کلمات یا عبارت‌های دیگر را توصیف می‌کنند.

تجزیه سطحی که به نام تجزیه سبک یا خرد کردن متن نیز نامیده می‌شود، یک تکنیک رایج در پردازش زبان طبیعی برای آنالیز ساختار یک جمله و تقسیم آن به اجزای سازنده‌اش (که توکن‌هایی مانند کلمات است) محسوب می‌شود. از گروه‌بندی این توکن‌ها در واحدهای بزرگ‌تر به صورت عبارت‌های سطح بالا استفاده می‌شود. این فرایند شامل تگ‌های POS و همچنین عبارت‌هایی از یک جمله است.

نمونه‌ای از تجزیه سطحی که به توصیف حاشیه‌نویسی عبارت‌های سطوح بالاتر می‌پردازد

ما از منبع conll2000 برای تمرین دادن مدل تجزیه سطحی خود استفاده کرده‌ایم. این منبع در کتابخانه nltk به همراه حاشیه‌نویسی موجود است و ما از حدود 10 هزار رکورد آن برای تمرین دادن مدل خود استفاده خواهیم کرد. یک جمله حاشیه‌نویسی شده ساده در ادامه نمایش یافته است.

from nltk.corpus import conll2000

data = conll2000.chunked_sents()
train_data = data[:10900]
test_data = data[10900:] 

print(len(train_data), len(test_data))
print(train_data[1]) 
10900 48
(S
Chancellor/NNP
(PP of/IN)
(NP the/DT Exchequer/NNP)
(NP Nigel/NNP Lawson/NNP)
(NP 's/POS restated/VBN commitment/NN)
(PP to/TO)
(NP a/DT firm/NN monetary/JJ policy/NN)
(VP has/VBZ helped/VBN to/TO prevent/VB)
(NP a/DT freefall/NN)
(PP in/IN)
(NP sterling/NN)
(PP over/IN)
(NP the/DT past/JJ week/NN)
./.)

از خروجی فوق مشخص است که نقاط داده‌ای ما جملاتی هستند که قبلاً با عبارت‌ها و فراداده‌های تگ‌های POS حاشیه‌نویسی شده‌اند و برای تمرین دادن مدل تجزیه سطحی مفید هستند. ما از تابع کاربردی chunking به نام tree2conlltags برای گرفتن کلمه، تگ و تگ‌های chunk برای هر توکن و از تابع conlltags2tree برای تولید یک درخت تجزیه از این توکن‌های سه‌گانه بهره می‌گیریم. ما از این تابع‌ها برای تمرین دادن تجزیه‌کننده خودمان استفاده می‌کنیم. نمونه‌ای از این الگوریتم در ادامه نمایش یافته است.

from nltk.chunk.util import tree2conlltags, conlltags2tree

wtc = tree2conlltags(train_data[1])
wtc
[('Chancellor', 'NNP', 'O'),
('of', 'IN', 'B-PP'),
('the', 'DT', 'B-NP'),
('Exchequer', 'NNP', 'I-NP'),
('Nigel', 'NNP', 'B-NP'),
('Lawson', 'NNP', 'I-NP'),
("'s", 'POS', 'B-NP'),
('restated', 'VBN', 'I-NP'),
('commitment', 'NN', 'I-NP'),
('to', 'TO', 'B-PP'),
('a', 'DT', 'B-NP'),
('firm', 'NN', 'I-NP'),
('monetary', 'JJ', 'I-NP'),
('policy', 'NN', 'I-NP'),
('has', 'VBZ', 'B-VP'),
('helped', 'VBN', 'I-VP'),
('to', 'TO', 'I-VP'),
('prevent', 'VB', 'I-VP'),
('a', 'DT', 'B-NP'),
('freefall', 'NN', 'I-NP'),
('in', 'IN', 'B-PP'),
('sterling', 'NN', 'B-NP'),
('over', 'IN', 'B-PP'),
('the', 'DT', 'B-NP'),
('past', 'JJ', 'I-NP'),
('week', 'NN', 'I-NP'),
('.', '.', 'O')]

تگ‌های chunk از فرمت IOB پیروی می‌کنند. این اختصاری برای عبارت‌های Inside, Outside, و Beginning به معنی درون، بیرون و آغاز است. پیشوند -B پیش از یک تگ نشان دهنده این است که آغاز یک chunk است و پیشوند -I نشان می‌دهد که درون chunk است. تگ O نیز نشان می‌دهد که توکن به هیچ chunk تعلق ندارد. تگ -B همواره زمانی استفاده می‌شود که تگ‌های بعدی از همان نوع و پس از آن بدون حضور تگ O وجود داشته باشند.

اینک تابع ()conll_tag_ chunks را برای استخراج تگ‌های POS و chunk از جمله‌های دارای حاشیه‌نویسی chunk و از تابعی به نام ()combined_taggers برای تمرین دادن چندین تگ کننده با تگ کننده‌های backoff (مانند تگ کننده‌های یونیگرام و بایگرام) استفاده می‌کنیم.

def conll_tag_chunks(chunk_sents):
    tagged_sents = [tree2conlltags(tree) for tree in chunk_sents]
    return [[(t, c) for (w, t, c) in sent] for sent in tagged_sents]


def combined_tagger(train_data, taggers, backoff=None):
    for tagger in taggers:
        backoff = tagger(train_data, backoff=backoff)
return backoff 

اینک کلاسی به نام NGramTagChunker تعریف می‌کنیم که جمله‌های تگ شده را به عنوان ورودی تمرین می‌گیرد و سه‌گانه WTG (کلمه، تگ PSO، تگ chunk) آن‌ها را گرفته و یک BigramTagger را با UnigramTagger به عنوان تگ کننده backoff تمرین می‌دهد. همچنین یک تابع ()parse تعریف می‌کنیم که تجزیه سطحی را روی جمله‌های جدید اجرا می‌کند.

UnigramTagger , BigramTagger , و TrigramTagger کلاس‌هایی هستند که از کلاس پایه NGramTagger به ارث می‌رسند، این کلاس خود از کلاس ContextTagger به ارث رسیده است که آن نیز از کلاس SequentialBackoffTagger ارث‌بری کرده است.

ما از این کلاس برای تمرین دادن روی conll2000 چانک شده train_data و ارزیابی عملکرد مدل روی test_data استفاده می‌کنیم.

from nltk.tag import UnigramTagger, BigramTagger
from nltk.chunk import ChunkParserI

# define the chunker class
class NGramTagChunker(ChunkParserI):
    
  def __init__(self, train_sentences, 
               tagger_classes=[UnigramTagger, BigramTagger]):
    train_sent_tags = conll_tag_chunks(train_sentences)
    self.chunk_tagger = combined_tagger(train_sent_tags, tagger_classes)

  def parse(self, tagged_sentence):
    if not tagged_sentence: 
        return None
    pos_tags = [tag for word, tag in tagged_sentence]
    chunk_pos_tags = self.chunk_tagger.tag(pos_tags)
    chunk_tags = [chunk_tag for (pos_tag, chunk_tag) in chunk_pos_tags]
    wpc_tags = [(word, pos_tag, chunk_tag) for ((word, pos_tag), chunk_tag)
                     in zip(tagged_sentence, chunk_tags)]
    return conlltags2tree(wpc_tags)
  
# train chunker model  
ntc = NGramTagChunker(train_data)

# evaluate chunker model performance
print(ntc.evaluate(test_data))
ChunkParse score:
    IOB Accuracy:  90.0%%
    Precision:     82.1%%
    Recall:        86.3%%
    F-Measure:     84.1%%

مدل chunking ما دقتی در حدود 90% دارد که کاملاً مناسب است. اینک از این مدل برای تجزیه سطحی و چانک کردن عنوان‌های مقالات خبری نمونه خودمان استفاده می‌کنیم:

«US unveils world’s most powerful supercomputer, beats China»

chunk_tree = ntc.parse(nltk_pos_tagged)
print(chunk_tree)

Output:
-------
(S
  (NP US/NNP)
  (VP unveils/VBZ world's/VBZ)
  (NP most/RBS powerful/JJ supercomputer,/JJ beats/NNS China/NNP))

بنابراین می‌توانید ببینید که دو عبارت اسمی (NP) و یک عبارت فعلی (VP) در عنوان خبر شناسایی کرده است. تگ‌های POS هر کلمه نیز قابل مشاهده هستند. این نتایج را به صورت یک درخت نیز در ادامه به تصویر کشیده‌ایم. در حالتی که nltk خطایی داشته باشد می‌بایست ghostscript را نصب کنید.

from IPython.display import display

## download and install ghostscript from https://www.ghostscript.com/download/gsdnld.html

# often need to add to the path manually (for windows)
os.environ['PATH'] = os.environ['PATH']+";C:\\Program Files\\gs\\gs9.09\\bin\\"

display(chunk_tree)
عنوان خبری با تجزیه سطحی

خروجی فوق درک خوبی از چگونگی تجزیه سطحی یک عنوان خبری به ما عرضه می‌کند.

تجزیه سازه‌ای

از گرامرهای مبتنی بر سازه برای آنالیز و تعیین سازه‌های یک جمله استفاده می‌شود. این گرامرها می‌توانند برای مدل‌سازی یا نمایش ساختار درونی جمله بر حسب ساختمان با ترتیب سلسله‌مراتبی از سازه‌های آن استفاده شوند. تک‌تک کلمات به طور معمول به یک دسته واژگان خاص تعلق دارند و واژه‌های نهاد عبارت‌های مختلف را تشکیل می‌دهند. این عبارت‌ها بر اساس قواعدی که به نام «قواعد ساختار عبارت» نامیده می‌شوند تشکیل می‌یابند.

قواعد ساختار عبارت هسته اصلی گرامرهای سازه‌ای را تشکیل می‌دهند، زیرا آن‌ها در مورد نحو و قواعدی صحبت می‌کنند که بر سلسله‌مراتب و ترتیب سازه‌های مختلف زبانی حاکم هستند. این قواعد به طور عمده به دو چیز اشاره دارند:

  • آن‌ها تعیین می‌کنند چه کلماتی برای ساخت عبارت‌ها یا سازه‌ها استفاده شوند.
  • آن‌ها چگونگی نیاز ما به مرتب‌سازی این سازه‌ها را مشخص می‌کنند.

بازنمایی معمول قاعده ساختار یک عبارت به صورت S → AB است که نشان می‌دهد ساختار S شامل سازنده‌های A و B است. و ترتیب آن ابتدا A و بعد B است. با این که چندین قاعده در این خصوص وجود دارند؛ اما مهم‌ترین قاعده به توصیف چگونگی تقسیم یک جمله یا جزء جمله می‌پردازد. قاعده ساختار عبارت یک تقسیم دودویی برای جمله یا جزء آن به صورت S → NP VP نشان می‌دهد که در آن S جمله یا جزئی از آن است و به مفعول که با عبارت اسمی (NP) نمایش می‌یابد و گزاره که با عبارت فعلی (VP) نمایش می‌یابد، تقسیم شده است.

یک الگوریتم تجزیه‌کننده سازه‌ای را می‌توان بر اساس چنین گرامرها/قواعدی ساخت که معمولاً به صورت جمعی به صورت گرامر مستقل از متن (CFG) یا گرامر ساخت-عبارتی وجود دارند. این الگوریتم تجزیه‌کننده جمله‌های ورودی را بر اساس این قواعد پردازش و به ساخت درخت تجزیه کمک می‌کند.

نمونه‌ای از تجزیه سازه‌ای که ساختار سلسله‌مراتبی تودرتو را نشان می‌دهد.

ما از کتابخانه‌های nltk و StanfordParser برای تولید درختان تجزیه استفاده می‌کنیم.

پیش‌نیازها: الگوریتم پارسر رسمی استنفورد را که به نظر می‌رسد کارکرد خوبی دارد می‌توانید از اینجا دانلود کنید. نسخه‌های بعدی آن را می‌توانید از این وب سایت و با مراجعه به بخش Release History دانلود کنید. پس از دانلود کردن آن را در مکان مشخصی در سیستم فایل خود از حالت فشرده خارج کنید. سپس می‌توانید از پارسر nltk استفاده کنید که در ادامه بررسی خواهیم کرد.

الگوریتم تجزیه‌گر (پارسر) استنفورد از روش PCFG (گرامر مستقل از متن و احتمالاتی) استفاده می‌کند. این الگوریتم به هر یک از قواعد احتمالاتی، یک مقدار احتمال نسبت می‌دهد. احتمال وقوع یک درخت تجزیه از PCFG به سادگی حاصل‌ضرب احتمال‌های منفرد مورد استفاده برای تولید آن است.

# set java path
import os
java_path = r'C:\Program Files\Java\jdk1.8.0_102\bin\java.exe'
os.environ['JAVAHOME'] = java_path

from nltk.parse.stanford import StanfordParser

scp = StanfordParser(path_to_jar='E:/stanford/stanford-parser-full-2015-04-20/stanford-parser.jar',
                     path_to_models_jar='E:/stanford/stanford-parser-full-2015-04-20/stanford-parser-3.5.2-models.jar')
                   
result = list(scp.raw_parse(sentence))
print(result[0])
(ROOT
  (SINV
    (S
      (NP (NNP US))
      (VP
        (VBZ unveils)
        (NP
          (NP (NN world) (POS 's))
          (ADJP (RBS most) (JJ powerful))
          (NN supercomputer))))
    (, ,)
    (VP (VBZ beats))
    (NP (NNP China))))

می‌توانیم ببینیم که درخت تجزیه سازه‌ای ما برای عنوان اخبار چگونه عمل کرده است. در ادامه آن را برای درک بهتر ساختار آن بصری‌سازی می‌کنیم.

(ROOT
from IPython.display import display
display(result[0])
تجزیه سازه‌ای عنوان اخبار

می‌بینیم که ساختار سلسله‌مراتبی تودرتوی سازه‌ها در خروجی قبلی در مقایسه با ساختار مسطح در تجزیه سطحی چه مقدار متفاوت است. اگر کنجکاو هستید که معنی SINV چیست، باید بگوییم که این اصطلاح اختصاری برای «Inverted declarative sentence» است یعنی جمله‌ای که در آن فاعل از یک فعل یا فعل وجهی زمان‌دار پیروی می‌کند. برای ملاحظه تگ‌های بیشتر به Penn Treebank reference مراجعه کنید.

تجزیه وابستگی

در زمینه تجزیه وابستگی سعی می‌کنیم از گرامرهای مبتنی بر وابستگی برای تحلیل و استنباط وابستگی‌های ساختاری و رابطه بین توکن‌ها در یک جمله بهره بگیریم. مفهوم اساسی تشکیل دهنده گرامر وابستگی این است که در هر جمله‌ای و در هر زبانی، همه واژه‌ها به جز یک مورد به نوعی رابطه یا وابستگی با کلمات دیگر جمله دارند. کلمه‌ای که هیچ وابستگی ندارد، ریشه جمله است. در اغلب موارد ریشه جمله همان فعل آن است. همه کلمات دیگر به طور مستقیم یا غیرمستقیم با استفاده از پیوندهایی که وابستگی نامیده می‌شوند به ریشه جمله متصل هستند.

برای مثال جمله «The brown fox is quick and he is jumping over the lazy dog» را در نظر بگیرید. اگر بخواهیم درخت وابستگی نحوی این جمله را ترسیم کنیم به ساختار زیر خواهیم رسید:

درخت تجزیه وابستگی برای یک جمله

هر یک از این رابطه‌های وابستگی معنای خاص خود را دارند و بخشی از یک فهرست جهانی انواع وابستگی محسوب می‌شوند. این مسئله در این مقاله بررسی شده است. همچنین می‌توانید فهرست بلند انواع وابستگی و معانی آن‌ها را در اینجا ببینید.

اگر برخی از این وابستگی‌ها را مشاهده کنیم، درک آن‌ها کار دشواری نخواهد بود.

  • تگ وابستگی det کاملاً شهودی است- این تگ رابطه تعیین کنده بین یک نهاد اسمی و گزاره آن را مشخص می‌سازد. به طور معمول کلمه‌ای که تگ POS به صورت DET دارد، دارای رابطه وابستگی تگ det است، برای مثال: fox → the و dog → the.
  • تگ وابستگی amod برای اشاره به اصلاح‌کننده توصیفی استفاده می‌شود و به معنی هر صفتی است که معنای یک اسم را تغییر می‌دهد، برای مثال:  fox → brown و dog → lazy.
  • تگ وابستگی nsubj برای یک نهاد استفاده می‌شود که به عنوان فاعل یا عامل در یک جزء جمله مطرح است، برای مثال: is → fox و jumping → he.
  • وابستگی‌های cc و conj بیشتر به پیوندهایی مرتبط هستند که واژه‌هایی را با استفاده از حروف ربط به هم متصل می‌کنند، برای مثال: is → and و is → jumping.
  • تگ وابستگی aux نشان دهنده افعال کمکی یا ثانویه در جزء جمله است، برای مثال: jumping → is.
  • تگ وابستگی acomp به معنی مکمل قیدی است و به عنوان یک مکمل یا مفعول برای یک فعل در جمله عمل می‌کند، برای مثال: is → quick.
  • تگ وابستگی prep نشان دهنده یک اصلاح‌کننده اضافی است که معمولاً معنای اسم، فعل، قید یا حرف اضافه را تغییر می‌دهد. به طور معمول این بازنمایی برای حروف اضافی که اسم یا مکمل اسمی دارند استفاده می‌شود، برای مثال: jumping → over.
    • تگ وابستگی pobj برای نشان دادن مفعول یک حرف اضافه استفاده می‌شود. این تگ معمولاً نهاد یک عبارت اسمی است و پس از یک حرف اضافه در جمله ظاهر می‌شود، برای مثال: over → dog.

Spacy دو نوع تجزیه‌کننده وابستگی در زبان انگلیسی دارد و از هر زبانی که برای مدل‌سازی استفاده کنید می‌توانید جزییات بیشتر را در این لینک مشاهده کنید. بر اساس مدل‌های زبانی می‌توانید از Universal Dependencies Scheme یا CLEAR Style Dependency Scheme که اینک در NLP4J نیز وجود دارد بهره بگیرید. ما در این نوشته از spoacy استفاده می‌کنیم و وابستگی‌ها را برای هر توکن در عنوان‌های خبری خود نمایش می‌دهیم.

dependency_pattern = '{left}<---{word}[{w_type}]--->{right}\n--------'
for token in sentence_nlp:
    print(dependency_pattern.format(word=token.orth_, 
                                  w_type=token.dep_,
                                  left=[t.orth_ 
                                            for t 
                                            in token.lefts],
                                  right=[t.orth_ 
                                             for t 
in token.rights]))
[]<---US[compound]--->[]
--------
['US']<---unveils[nsubj]--->['supercomputer', ',']
--------
[]<---world[poss]--->["'s"]
--------
[]<---'s[case]--->[]
--------
[]<---most[amod]--->[]
--------
[]<---powerful[compound]--->[]
--------
['world', 'most', 'powerful']<---supercomputer[appos]--->[]
--------
[]<---, [punct]--->[]
--------
['unveils']<---beats[ROOT]--->['China']
--------
[]<---China[dobj]--->[]
--------

مشخص است که فعل ریشه است، زیرا در مقایسه با دیگر توکن‌ها می‌بینیم که هیچ وابستگی دیگری ندارد. برای کسب اطلاعات بیشتر در خصوص حاشیه‌نویسی می‌توانید به CLEAR dependency scheme مراجعه کنید. همچنین می‌توانیم وابستگی‌های فوق را به روش بهتری بصری‌سازی کنیم.

from spacy import displacy

displacy.render(sentence_nlp, jupyter=True, 
                options={'distance': 110,
                         'arrow_stroke': 2,
'arrow_width': 8})
درخت جدید وابستگی عنوان از Spacy

شما همواره می‌توانید از کتابخانه nltk و StanfordDependencyParser برای بصری‌سازی و ساخت درخت وابستگی استفاده کنید. ما درخت وابستگی را هم به صورت خام و هم به شکل حاشیه‌نویسی شده زیر ترسیم کرده‌ایم:

from nltk.parse.stanford import StanfordDependencyParser
sdp = StanfordDependencyParser(path_to_jar='E:/stanford/stanford-parser-full-2015-04-20/stanford-parser.jar',
                               path_to_models_jar='E:/stanford/stanford-parser-full-2015-04-20/stanford-parser-3.5.2-models.jar')    

result = list(sdp.raw_parse(sentence))  

# print the dependency tree
dep_tree = [parse.tree() for parse in result][0]
print(dep_tree)

# visualize raw dependency tree
from IPython.display import display
display(dep_tree)

# visualize annotated dependency tree (needs graphviz)
from graphviz import Source
dep_tree_dot_repr = [parse for parse in result][0].to_dot()
source = Source(dep_tree_dot_repr, filename="dep_tree", format="png")
source
(beats (unveils US (supercomputer (world 's) (powerful most)))  
 China)
بصری‌سازی درخت وابستگی با استفاده از پارسر وابستگی استنفورد

ممکن است متوجه شده باشید که مشابهت‌هایی بین این درخت و درختی که قبلاً به دست آوردیم وجود دارد. حاشیه‌نویسی کمک می‌کند که درک بهتری از نوع وابستگی میان توکن‌های مختلف داشته باشیم.

بازشناسی نهادهای دارای نام (موجودیت‌های نامدار)

در هر سند متنی اصطلاح‌های خاصی وجود دارند که نماینده نهادهای ویژه‌ای هستند و دارای جنبه آگاهی بخشی بیشتری بوده و چارچوب منحصربه‌فردی دارند. این نهادها به نام نهادهای دارای نام یا موجودیت‌های نامدار نامیده می‌شوند. این اصطلاح به طور خاص به اشیایی در دنیای واقعی مانند افراد، مکان‌ها، سازمان‌ها و موارد دیگر اشاره می‌کند که غالباً دارای اسامی خاص هستند. یک رویکرد بسیط در این خصوص می‌تواند این باشد که در اسناد متنی به دنبال عبارت‌های اسمی بگردیم. بازشناسی نهادهای دارای نام (NER) همچنین به عنوان خردکردن/استخراج نهاد نیز نامیده می‌شود که تکنیکی رایج در زمینه استخراج اطلاعات برای شناسایی و قطعه‌بندی نهادهای دارای نام و طبقه‌بندی یا دسته‌بندی آن‌ها تحت دسته‌های از پیش تعریف شده مختلف محسوب می‌شود.

SpaCy قابلیت‌های خاصی در زمینه بازشناسی نهادهای دارای نام دارد. در ادامه از آن روی یکی از مقاله‌های خبری نمونه خود استفاده می‌کنیم.

sentence = str(news_df.iloc[1].full_text)
sentence_nlp = nlp(sentence)

# print named entities in article
print([(word, word.ent_type_) for word in sentence_nlp if word.ent_type_])

# visualize named entities
displacy.render(sentence_nlp, style='ent', jupyter=True)
[(US, 'GPE'), (China, 'GPE'), (US, 'GPE'), (China, 'GPE'),
(Sunway, 'ORG'), (TaihuLight, 'ORG'), (200,000, 'CARDINAL'),
(second, 'ORDINAL'), (Sunway, 'ORG'), (TaihuLight, 'ORG'),
(93,000, 'CARDINAL'), (4,608, 'CARDINAL'), (two, 'CARDINAL')]
بصری‌سازی نهادهای دارای نام در یک مقاله خبری با استفاده از SpaCy

می‌توانیم به وضوح ببینیم که عمده نهادهای دارای نام به وسیله spacy شناسایی شده‌اند. برای درک بهتر در مورد این که هر نهاد دارای نام چه معنی دارد می‌توانید از مستندات این کتابخانه استفاده و یا جدول زیر را بررسی کنید.

انواع نهادهای دارای نام

اینک می‌خواهیم نهادهای دارای نام با فراوانی بالا را در متون خبری خود بیابیم. بدین منظور یک چارچوب داده‌ای از همه نهادهای دارای نام و انواع آن‌ها با استفاده از کد زیر ایجاد می‌کنیم.

named_entities = []
for sentence in corpus:
    temp_entity_name = ''
    temp_named_entity = None
    sentence = nlp(sentence)
    for word in sentence:
        term = word.text 
        tag = word.ent_type_
        if tag:
            temp_entity_name = ' '.join([temp_entity_name, term]).strip()
            temp_named_entity = (temp_entity_name, tag)
        else:
            if temp_named_entity:
                named_entities.append(temp_named_entity)
                temp_entity_name = ''
                temp_named_entity = None

entity_frame = pd.DataFrame(named_entities, 
columns=['Entity Name', 'Entity Type'])

اینک می‌توانیم این چارچوب داده‌ای را برای یافتن نهادها و انواعی که بیشترین رخداد را دارند تبدیل و تجمیع کنیم.

# get the top named entities
top_entities = (entity_frame.groupby(by=['Entity Name', 'Entity Type'])
                           .size()
                           .sort_values(ascending=False)
                           .reset_index().rename(columns={0 : 'Frequency'}))
top_entities.T.iloc[:,:15]
نهادهای دارای نام و انواع با فراوانی بالا در مقالات خبری ما

آیا متوجه هیچ نکته جالبی شدید؟ بله منظور ما دیدار بین ترامپ و کیم جونگ است. همچنین می‌بینیم که کلمه messenger را نیز به عنوان یک محصول (فیسبوک) به درستی تشخیص داده است.

همچنین می‌توانیم انواع نهادها را برای تشکیل معنای کلی از انواع نهادهایی که در مقالات خبری بیشتر ظاهر می‌شوند گروه‌بندی کنیم.

# get the top named entity types
top_entities = (entity_frame.groupby(by=['Entity Type'])
                           .size()
                           .sort_values(ascending=False)
                           .reset_index().rename(columns={0 : 'Frequency'}))
top_entities.T.iloc[:,:15]
انواع نهادهای با فراوانی بالا در مقالات خبری

می‌توانیم ببینیم که افراد، مکان‌ها و سازمان‌ها بیشترین نهادهای مورد اشاره هستند و جالب اینجاست که انواع بسیار مختلف دیگری از نهادها را نیز شاهد هستیم.

یک تگ کننده زیبای دیگر NER به نام StanfordNERTagger است که در رابط nltk وجود دارد. برای استفاده از این تگ کننده باید جاوا روی سیستم نصب باشد و سپس Stanford NER resources را دانلود کنید. این منابع را در مکان مورد نظر خود (برای مثال مسیر E:\stanford) از حالت فشرده خارج کنید.

الگوریتم بازشناسی نهادهای دارای نام استنفورد (Stanford’s Named Entity Recognizer) بر مبنای یک پیاده‌سازی از مدل‌های توالی فیلد تصادفی شرطی زنجیره خطی (CRF) است. متأسفانه این مدل تنها روی نمونه‌هایی از نوع شخص (PERSON)، سازمان (ORGANIZATION) و مکان (LOCATION) تمرین یافته است. از کد زیر می‌توان به عنوان یک گردش کار استاندارد استفاده کرد که به استخراج نهادهای دارای نام با استفاده از این تگ کننده کمک می‌کند و نهادهای دارای نام و انواع آن‌ها را که دارای فراوانی بالاتری هستند به نمایش می‌گذارد (روش استخراج تا حدودی از spacy متفاوت است.)

from nltk.tag import StanfordNERTagger
import os

# set java path
java_path = r'C:\Program Files\Java\jdk1.8.0_102\bin\java.exe'
os.environ['JAVAHOME'] = java_path

# initialize NER tagger
sn = StanfordNERTagger('E:/stanford/stanford-ner-2014-08-27/classifiers/english.all.3class.distsim.crf.ser.gz',
                       path_to_jar='E:/stanford/stanford-ner-2014-08-27/stanford-ner.jar')

# tag named entities
ner_tagged_sentences = [sn.tag(sent.split()) for sent in corpus]

# extract all named entities
named_entities = []
for sentence in ner_tagged_sentences:
    temp_entity_name = ''
    temp_named_entity = None
    for term, tag in sentence:
        if tag != 'O':
            temp_entity_name = ' '.join([temp_entity_name, term]).strip()
            temp_named_entity = (temp_entity_name, tag)
        else:
            if temp_named_entity:
                named_entities.append(temp_named_entity)
                temp_entity_name = ''
                temp_named_entity = None

#named_entities = list(set(named_entities))
entity_frame = pd.DataFrame(named_entities, 
                            columns=['Entity Name', 'Entity Type'])
                            

# view top entities and types
top_entities = (entity_frame.groupby(by=['Entity Name', 'Entity Type'])
                           .size()
                           .sort_values(ascending=False)
                           .reset_index().rename(columns={0 : 'Frequency'}))
top_entities.head(15)


# view top entity types
top_entities = (entity_frame.groupby(by=['Entity Type'])
                           .size()
                           .sort_values(ascending=False)
                           .reset_index().rename(columns={0 : 'Frequency'}))
top_entities.head()
نهادهای دارای نام و انواع با فراوانی بالا از NER استنفورد روی مقالات خبری ما

متوجه شدیم که نتایج تا حدود زیادی مشابه هستند؛ گرچه تنها شناسایی به سه نوع نهاد محدود شده است. نکته جالب‌تر این که چند مورد از افراد مورد اشاره در رشته‌های ورزشی مختلف را نیز شاهد هستیم.

تحلیل عاطفی و احساسات (Sentiment Analysis)

تحلیل احساسات احتمالاً یکی از رایج‌ترین کاربردهای NLP است و خودآموزها، دوره‌های آموزشی و کاربردهای مختلفی وجود دارند که روی تحلیل احساسات مجموعه داده‌های مختلف از پیمایش‌های شرکتی تا بررسی‌های فیلم متمرکز شده‌اند. جنبه کلیدی تحلیل احساسات روی آنالیز یک متن برای درک نظرات بیان شده از طریق آن متمرکز است. به طور معمول، ما این احساسات را با مقادیر مثبت و منفی کمی سازی می‌کنیم که قطبیت (Polarity) نام دارند. احساس کلی از روی نشانه‌های امتیاز قطبیت غالباً به صورت مثبت، خنثی یا منفی استنباط می‌شود.

به طور معمول تحلیل احساسات روی متن‌هایی بهترین نتیجه را ارائه می‌کند که چارچوب ذهنی دارند و در مورد متونی با زمینه صرفاً عینی کارایی کمتری دارد. متون عینی معمولاً برخی عبارت‌های نرمال یا واقعیت‌ها را بدون بیان عواطف، احساسات یا لحن ارائه‌ی کنند. متون ذهنی شامل نوشته‌هایی هستند که معمولاً به وسیله انسانی با حالت روانی، عواطف و احساسات خاص خود ابراز شده‌اند. امروزه تحلیل احساسات به طور گسترده‌ای مورد استفاده قرار می‌گیرد و به طور خاص به عنوان بخشی از تحلیل رسانه‌های اجتماعی در حوزه‌های مختلف نگریسته می‌شود. این حوزه می‌تواند یک حوزه کسب‌وکار، تحلیل یک فیلم اخیراً نمایش یافته یا عرضه یک محصول باشد و هدف از آن درک پذیرش این محصول از سوی افراد و دریافت تفکرات آن‌ها بر اساس نظرات یا در واقع احساسات آن‌ها است.

تحلیل احساسات برای داده‌های متنی به طور معمول در چند سطح قابل محاسبه است که شامل سطح جمله‌های منفرد، پاراگراف یا کل سند می‌شود. در اغلب موارد تحلیل احساسات به صورت سطح کل یک سند پس از انجام تحلیل در سطح جملات منفرد محاسبه می‌شوند. دو رویکرد کلی در مورد تحلیل احساسات وجود دارد:

  • رویکردهای یادگیری ماشین نظارت‌شده یا یادگیری عمیق
  • رویکردهای نظارت‌نشده مبتنی بر فرهنگ واژگان

در خصوص رویکرد اول ما معمولاً به داده‌های از پیش برچسب‌گذاری شده نیازمند هستیم. از این رو روی رویکرد دوم متمرکز می‌شویم. برای پوشش جامع تحلیل احساسات به فصل هفتم کتاب یادگیری ماشین عملی با پایتون (2018) مراجعه کنید. در این سناریو متأسفانه به مجموعه داده تمرینی با برچسب‌گذاری مناسب دسترسی نداریم و از این رو به تکنیک‌های یادگیری نظارت‌نشده برای پیش‌بینی احساسات از طریق پایگاه دانش، معرفت‌شناسی، پایگاه‌های داده و فرهنگ واژگان که اطلاعات دقیق دارند نیازمندیم. این منابع باید به طور خاص برای تحلیل احساسات، گردآوری و آماده‌سازی شده باشند.

فرهنگ واژگان نوعی دیکشنری، لغت‌نامه و یا کتابچه‌ای از کلمات است. در این مورد فرهنگ‌های واژگانی دیکشنری‌های خاصی هستند که برای تحلیل احساسات تهیه شده‌اند. اغلب این فرهنگ‌های واژگانی فهرستی از واژه‌های با قطبیت مثبت و منفی در خود دارند که به هر یک امتیازی داده‌اند و از تکنیک‌های مختلفی مانند موقعیت‌یابی کلمه، واژگان پیرامونی، چارچوب متن، اجزای گفتاری، عبارت‌ها و موارد دیگر بهره می‌گیرند و امتیازهایی به سندهای متنی می‌دهند که از روی آن می‌توانیم در مورد احساسات استنباطی داوری کنیم. پس از تجمیع این امتیازها می‌توانیم برآیند کلی احساس را داشته باشیم.

فرهنگ‌های مختلف واژگانی برای تحلیل احساسات مورد استفاده قرار می‌گیرند که شامل موارد زیر هستند:

  • فرهنگ واژگان AFINN
  • فرهنگ واژگان Bing Liu
  • فرهنگ واژگان MPQA
  • SentiWordNet
  • فرهنگ واژگان VADER
  • فرهنگ واژگان TextBlob

این فهرست در خصوص ابزارهای قابل بهره داری در تحلیل احساسات فهرست جامعی محسوب نمی‌شود و چند واژه‌نامه دیگر نیز وجود دارند که به راحتی می‌توانید آن‌ها را در اینترنت یابید. ما در این بخش از دو تکنیک استفاده خواهیم کرد.

تحلیل احساسات با فرهنگ واژگان AFINN

فرهنگ واژگان AFINN احتمالاً یکی از ساده‌ترین و رایج‌ترین واژه‌نامه‌هایی است که می‌توان به طور گسترده برای تحلیل احساسات مورد استفاده قرار داد. این واژه‌نامه از سوی Finn Årup Nielsen تهیه شده است و جزییات بیشتر را در مورد آن می‌توانید در مقاله‌ای با عنوان «A new ANEW: evaluation of a word list for sentiment analysis in microblogs» مشاهده کنید. نسخه کنونی این واژه‌نامه «AFINN-en-165. txt» نام دارد و شامل بیش از 3300 کلمه است که هر یک دارای امتیاز قطبیت هستند. این واژه‌نامه را می‌توانید در مخزن گیت‌هاب رسمی آن به همراه نسخه‌های قبلی که شامل AFINN-111 است، مشاهده کنید. مؤلف این واژه‌نامه کتابخانه پوششی خوبی نیز در پایتون ایجاد کرده است که afinn نام دارد و ما برای تحلیل‌های خود از آن استفاده خواهیم کرد.

کد زیر احساسات را برای همه مقالات خبری ما محاسبه می‌کند و آمار خلاصه‌ای از احساسات کلی برای هر دسته خبری به دست می‌آورد.

# initialize afinn sentiment analyzer
from afinn import Afinn
af = Afinn()

# compute sentiment scores (polarity) and labels
sentiment_scores = [af.score(article) for article in corpus]
sentiment_category = ['positive' if score > 0 
                          else 'negative' if score < 0 
                              else 'neutral' 
                                  for score in sentiment_scores]
    
    
# sentiment statistics per news category
df = pd.DataFrame([list(news_df['news_category']), sentiment_scores, sentiment_category]).T
df.columns = ['news_category', 'sentiment_score', 'sentiment_category']
df['sentiment_score'] = df.sentiment_score.astype('float')
df.groupby(by=['news_category']).describe()

ما می‌توانیم ایده خوبی از تحلیل احساسات در میان دسته‌های خبری متفاوت به دست آوریم. به نظر می‌رسد که احساسات در خبرهای ورزشی بسیار مثبت و در حوزه فناوری به طور معقولی منفی است. در ادامه اقدام به بصری‌سازی این نتایج می‌کنیم.

f, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 4))
sp = sns.stripplot(x='news_category', y="sentiment_score", 
                   hue='news_category', data=df, ax=ax1)
bp = sns.boxplot(x='news_category', y="sentiment_score", 
                 hue='news_category', data=df, palette="Set2", ax=ax2)
t = f.suptitle('Visualizing News Sentiment', fontsize=14)
بصری‌سازی قطبیت احساسات اخبار

می‌توانید بینید که میزان قطبیت احساسات در دسته اخبار ورزشی و جهانی در مقایسه با حوزه فناوری بسیار بالاتر است. چون در زمینه فناوری قطبیت منفی زیادی وجود دارد. می‌توانیم فراوانی برچسب‌های احساسی را به صورت بصری ارائه کنیم.

fc = sns.factorplot(x="news_category", hue="sentiment_category", 
                    data=df, kind="count", 
                    palette={"negative": "#FE2020", 
                             "positive": "#BADD07", 
"neutral": "#68BFF5"})
بصری‌سازی دسته‌بندی‌های احساسی برای هر دسته خبری

جای شگفتی نیست که می‌بینیم حوزه فناوری بیشترین تعداد مقالات منفی و دسته اخبار جهانی بیشترین مقالات مثبت را دارد. دسته اخبار ورزشی به دلیل وجود مقالاتی که دارای ماهیتی عینی‌تر هستند (در مورد رویدادهای ورزشی و بدون هیچ گونه عواطف یا احساسات صحبت می‌کنند) بیشتر خنثی هستند. در ادامه به بررسی عمیق‌تر مقالات خبری دارای قطبیت منفی و مثبت احساسی در دسته اخبار فناوری می‌پردازیم.

pos_idx = df[(df.news_category=='technology') & (df.sentiment_score == 6)].index[0]
neg_idx = df[(df.news_category=='technology') & (df.sentiment_score == -15)].index[0]

print('Most Negative Tech News Article:', news_df.iloc[neg_idx][['news_article']][0])
print()
print('Most Positive Tech News Article:', news_df.iloc[pos_idx][['news_article']][0])

به نظر می‌رسد که اغلب مقالات منفی در مورد سوءاستفاده اخیر از گوشی‌های هوشمند در هند است و اغلب مقالات مثبت در مورد مسابقه‌ای برای برگزاری مراسم ازدواج در یک اتومبیل خودران بوده است. نتیجه جالبی است و در ادامه تحلیل مشابهی برای اخبار جهانی انجام می‌دهیم.

pos_idx = df[(df.news_category=='world') & (df.sentiment_score == 16)].index[0]
neg_idx = df[(df.news_category=='world') & (df.sentiment_score == -12)].index[0]

print('Most Negative World News Article:', news_df.iloc[neg_idx][['news_article']][0])
print()
print('Most Positive World News Article:', news_df.iloc[pos_idx][['news_article']][0])

بسیار جالب است که ترامپ در هر دو دسته مقالات خبری مثبت و منفی جهانی حضور دارد. مقالات را بخوانید تا در مورد دلیل این که چرا مدل یکی از آن‌ها را منفی‌تر و دیگری را مثبت‌تر تشخیص داده است، آگاهی بیشتری کسب کنید.

تحلیل احساسات با TextBlob

TextBlob کتابخانه متن-باز (Open source) عالی دیگری برای اجرای راحت وظایف NLP است که شامل تحلیل احساسات نیز می‌شود. این کتابخانه همچنین واژه‌نامه‌ای احساسی (به شکل فایل XML) دارد که از هر دو امتیاز قطبیت و ذهنیت بهره می‌گیرد. به طور معمول امتیازها یک مقیاس نرمال شده در مقایسه با afinn دارند. امتیاز قطبیت در محدوده [1.0 و 1.0-]. ذهنیت (subjectivity) در محدوده [0.0, 1.0] یکسان است و 0.0 کاملاً عینی و 1.0 کاملاً ذهنی است. اجازه بدهید اینک از این برای قطبیت احساسی و برچسب‌ها برای هر مقاله خبری استفاده کنیم و آمار خلاصه را برای هر دسته خبری تجمیع می‌کنیم.

from textblob import TextBlob

# compute sentiment scores (polarity) and labels
sentiment_scores_tb = [round(TextBlob(article).sentiment.polarity, 3) for article in news_df['clean_text']]
sentiment_category_tb = ['positive' if score > 0 
                             else 'negative' if score < 0 
                                 else 'neutral' 
                                     for score in sentiment_scores_tb]


# sentiment statistics per news category
df = pd.DataFrame([list(news_df['news_category']), sentiment_scores_tb, sentiment_category_tb]).T
df.columns = ['news_category', 'sentiment_score', 'sentiment_category']
df['sentiment_score'] = df.sentiment_score.astype('float')
df.groupby(by=['news_category']).describe()

به نظر می‌رسد که تحلیل احساسات در دسته اخبار جهانی کاملاً مثبت و در دسته فناوری کاملاً منفی است. با این حال این معیارها ممکن است نشان دهنده این باشند که مدل اغلب مقالات را به صورت مثبت پیش‌بینی کرده است. در ادامه نگاهی به توزیع فراوانی احساسات برای هر دسته خبری می‌اندازیم.

fc = sns.factorplot(x="news_category", hue="sentiment_category", 
                    data=df, kind="count", 
                    palette={"negative": "#FE2020", 
                             "positive": "#BADD07", 
"neutral": "#68BFF5"})
بصری‌سازی دسته‌های احساسی برای هر دسته خبری

کاملاً مشخص است که مقالات خبری مثبت‌تری در همه دسته‌های خبری در مقایسه با مدل قبلی وجود دارند. با این وجود، همچنان به نظر می‌رسد که دسته فناوری دارای منفی‌ترین مقالات خبری است و مثبت‌ترین مقالات خبری مانند تحلیل قبلی هستند. اینک تحلیل جامعی انجام می‌دهیم تا ببینیم آیا همچنان مقالات مشابه را در مثبت‌ترین و منفی‌ترین دسته‌ها برای اخبار جهانی داریم یا نه.

pos_idx = df[(df.news_category=='world') & (df.sentiment_score == 0.7)].index[0]
neg_idx = df[(df.news_category=='world') & (df.sentiment_score == -0.296)].index[0]

print('Most Negative World News Article:', news_df.iloc[neg_idx][['news_article']][0])
print()
print('Most Positive World News Article:', news_df.iloc[pos_idx][['news_article']][0])

به نظر می‌رسد که منفی‌ترین مقاله‌های خبری در دسته اخبار جهانی حتی مأیوس‌کننده‌تر از آن چیزی هستند که قبلاً دیدیم. مثبت‌ترین مقالات خبری همچنان به همان حالتی هستند که در مدل قبلی به دست آوردیم.

در نهایت، حتی می‌توانیم بین این دو مدل مقایسه کنیم و ببینیم که چه تعداد از تخمین‌ها با هم مطابقت دارند و چه تعداد ندارند. این کار از طریق بهره‌گیری از یک ماتریس درهم‌ریختگی که غالباً در طبقه‌بندی استفاده می‌شود میسر است. ما از ماژول model_evaluation_utils بدین منظور استفاده می‌کنیم:

import model_evaluation_utils as meu
meu.display_confusion_matrix_pretty(true_labels=sentiment_category, 
                                    predicted_labels=sentiment_category_tb, 
classes=['negative', 'neutral', 'positive'])
مقایسه پیش‌بینی‌های احساسی در میان مدل‌ها

در جدول قبلی، برچسب‌های Actual پیش‌بینی‌هایی از الگوریتم تحلیل احساسات Affin و برچسب‌های Predicted پیش‌بینی‌هایی از TextBlob هستند. به نظر می‌رسد که فرضیه‌های قبلی صحیح هستند. TextBlob به طور قطع چند مقاله مثبت و منفی را به صورت مثبت پیش‌بینی کرده است. در مجموع اغلب پیش‌بینی‌های احساسی با هم منطبق به نظر می‌رسند که مسئله خوبی است.

نتیجه‌گیری

این مقاله احتمالاً یکی از طولانی‌ترین مقالات در زمینه مبانی پردازش زبان طبیعی است. اگر مطالعه این مقاله را تا اینجا انجام داده‌اید پیشنهاد می‌کنیم در بخش‌های آتی که در آینده نزدیک در بلاگ فرادرس عرضه خواهند شد نیز با ما همراه باشید. این نمونه‌ها باید ایده خوبی در مورد چگونگی شروع به کار بار اسناد متنی و راهبردهای رایج برای بازیابی داده، پیش-پردازش، تجزیه، درک ساختار، نهاد و تحلیل احساسات متن به شما داده باشند. ما تکنیک‌های مهندسی و بازنمایی ویژگی را با مثال‌های کارآمد در مقاله بعدی از این سری آموزش‌ها ارائه خواهیم کرد.

همه کدها و مجموعه داده‌های مورد استفاده در این مقاله در این آدرس گیت‌هاب قابل دسترسی هستند. این کد در قالب نت بوک ژوپیتر در دسترس است.

اگر این نوشته مورد توجه شما قرار گرفته است، پیشنهاد می‌کنیم موارد زیر را نیز ملاحظه کنید:

==

بر اساس رای ۸ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
towardsdatascience
۵ دیدگاه برای «پردازش زبان طبیعی (NLP) با پایتون — راهنمای جامع»

بسیار بسیار ممنون
خدا خیرتون بده

سلام
بسیار عالی بود
زحمت کشیده بودید واقعا

تشکر فراوان

با سلام

خیلی عالی بود. به صورت گام به گام و مفصل همه چی رو شرح دادن

سلام.باتشکر از آموزش خوبتون میخواستم ببینم آیا nlp برای پردازش متون فارسی هم میتونه نتیجه خوبی داشته باشد؟

سلام و خسته نباشید خدمت شما.
مقاله بسیار عالی پربار و مفید بود. از این بابت خیلی تشکر میکنم از آقای لطفی. اما چند تا سوال داشتم از خدمتتون و میخواستم بدونم آیا راه ارتباطی هست که سوالات رو مطرح کنیم با ایشون؟؟

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *