Обработка естественного языка (NLP) - действительно очень интересная и обширная область искусственного интеллекта. Здесь я собираюсь использовать его для обработки текстовых записей и дам вам ускоренный курс по парсингу и анализу настроений. Для парсинга я использовал Selenium и tweepy, а для анализа настроений я использовал классы и методы NLTK и наивную байесовскую модель. Я изо всех сил старался охватить большинство шагов, которые следует выполнять при работе с набором текстовых данных, и позвольте мне убедиться, что это будет стоить вашего времени.

Так что же такое парсинг и анализ настроений?

Очистка - процесс получения небольших фрагментов чего-либо. В нашем случае это веб-скрапинг, поэтому здесь мы берем фрагменты информации, доступной на веб-сайте.

Анализ настроений. Из самого термина можно сделать вывод, что это процесс анализа взглядов или мнений людей по любому вопросу. Теперь темой может быть что угодно: продукт, фильм, политическая или социальная проблема, технология, любое событие или какой-либо тренд.

Люди обычно предпочитают социальные сети для выражения своих взглядов или мнений, это может быть Facebook, Twitter, Quora или любой другой блог-сайт. В этом уроке я собираюсь рассматривать Твиттер как источник информации, и тему, которую я хотел бы выбрать, - «ИИ и глубокое обучение», хотя код, которым я поделюсь, будет полностью общим, так что вы можете выбрать любой и другая интересная тема.

Из заголовка и приведенного выше описания вы, должно быть, определили, что текстовые данные, необходимые для выполнения анализа настроений, необходимо удалить из Twitter. Итак, ниже приведены основные операции, которые я собираюсь выполнить:

1. Очистка твитов

2. Определение настроений

3. Предварительная обработка текста

4. Извлечение признаков

5. Построение модели

  1. Очистка твитов

Если вы раньше выполняли парсинг на Python, значит, вы, должно быть, использовали «Запросы» и «Красивый суп»; для тех, кто не слышал об этом раньше, Request - это HTTP-библиотека Python для отправки HTTP-запросов, а Beautiful Soup - HTML-парсер для синтаксического анализа DOM и получения желаемой информации. из этого. Но мы не можем использовать эти библиотеки для удаления твитов из твиттера из-за динамической и прогрессивной генерации твитов. Осталось два варианта:

а). Селен

б). тонкая библиотека Python

Я покажу вам реализацию обоих. Между прочим, Selenium - это инструмент имитации браузера, обычно используемый для тестирования веб-страниц, и tweepy, как я уже упоминал, библиотека python, которая предоставляет доступ к различным API твиттера.

а). Утилизация с использованием селена:

Предположим, вы уже импортировали numpy и pandas. Ниже представлен класс SeleniumClient, который будет выполнять парсинг:

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
class SeleniumClient(object):
    def __init__(self):
        #Initialization method. 
        self.chrome_options = webdriver.ChromeOptions()
        self.chrome_options.add_argument('--headless')
        self.chrome_options.add_argument('--no-sandbox')
        self.chrome_options.add_argument('--disable-setuid-sandbox')

        # you need to provide the path of chromdriver in your system
        self.browser = webdriver.Chrome('D:/chromedriver_win32/chromedriver', options=self.chrome_options)

        self.base_url = 'https://twitter.com/search?q='

    def get_tweets(self, query):
        ''' 
        Function to fetch tweets. 
        '''
        try: 
            self.browser.get(self.base_url+query)
            time.sleep(2)

            body = self.browser.find_element_by_tag_name('body')

            for _ in range(3000):
                body.send_keys(Keys.PAGE_DOWN)
                time.sleep(0.3)

            timeline = self.browser.find_element_by_id('timeline')
            tweet_nodes = timeline.find_elements_by_css_selector('.tweet-text')

            return pd.DataFrame({'tweets': [tweet_node.text for tweet_node in tweet_nodes]})

        except: 
            print("Selenium - An error occured while fetching tweets.")

В приведенном выше коде вам нужно указать путь к веб-драйверу желаемого браузера, или мы можем просто установить переменную среды и не передавать какие-либо параметры внутри webdriver.Chrome ().

Вы можете использовать этот класс:

selenium_client = SeleniumClient()

tweets_df = selenium_client.get_tweets('AI and Deep learning')

В tweets_df вы получите фрейм данных, содержащий все отмененные твиты.

б). Получайте твиты с помощью tweepy:

Мы можем создать класс TwitterClient:

import tweepy
from tweepy import OAuthHandler
class TwitterClient(object): 
    def __init__(self):
        # Access Credentials 
        consumer_key = 'XXXX'
        consumer_secret = 'XXXX'
        access_token = 'XXXX'
        access_token_secret = 'XXXX'
try: 
            # OAuthHandler object 
            auth = OAuthHandler(consumer_key, consumer_secret) 
            # set access token and secret 
            auth.set_access_token(access_token, access_token_secret) 
            # create tweepy API object to fetch tweets 
            self.api = tweepy.API(auth, wait_on_rate_limit=True, wait_on_rate_limit_notify=True)
            
        except tweepy.TweepError as e:
            print(f"Error: Twitter Authentication Failed - \n{str(e)}") 

    # Function to fetch tweets
    def get_tweets(self, query, maxTweets = 1000): 
        # empty list to store parsed tweets 
        tweets = [] 
        sinceId = None
        max_id = -1
        tweetCount = 0
        tweetsPerQry = 100
        
        while tweetCount < maxTweets:
            try:
                if (max_id <= 0):
                    if (not sinceId):
                        new_tweets = self.api.search(q=query, count=tweetsPerQry)
                    else:
                        new_tweets = self.api.search(q=query, count=tweetsPerQry,
                                                since_id=sinceId)
                else:
                    if (not sinceId):
                        new_tweets = self.api.search(q=query, count=tweetsPerQry,
                                                max_id=str(max_id - 1))
                    else:
                        new_tweets = self.api.search(q=query, count=tweetsPerQry,
                                                max_id=str(max_id - 1),
                                                since_id=sinceId)
                if not new_tweets:
                    print("No more tweets found")
                    break
                    
                for tweet in new_tweets:
                    parsed_tweet = {} 
                    parsed_tweet['tweets'] = tweet.text 

                    # appending parsed tweet to tweets list 
                    if tweet.retweet_count > 0: 
                        # if tweet has retweets, ensure that it is appended only once 
                        if parsed_tweet not in tweets: 
                            tweets.append(parsed_tweet) 
                    else: 
                        tweets.append(parsed_tweet) 
                        
                tweetCount += len(new_tweets)
                print("Downloaded {0} tweets".format(tweetCount))
                max_id = new_tweets[-1].id

            except tweepy.TweepError as e:
                print("Tweepy error : " + str(e))
                break
        
        return pd.DataFrame(tweets)

В приведенном выше коде нам нужны «Учетные данные для доступа» для выполнения вызовов API, их можно получить из консоли разработчика Twitter, вам просто нужно зарегистрировать свое приложение и указать все веские причины для получения доступа. Этот вызов можно использовать так же, как мы использовали SeleniumClient. В ответ мы получим фрейм данных, содержащий все выбранные твиты.

Какой из них использовать?

Да, это очевидный вопрос. Ответ - крутой, потому что он быстрее и надежнее. Однако, если у вас нет учетных данных для доступа к Twiter API и вы не хотите ждать одобрения Twitter, вы можете использовать SeleniumClient. Всегда полезно знать различные подходы к выполнению любой задачи.

2. Определение типа настроения

Итак, тип настроения - это не что иное, как общая реакция, она может быть положительной, отрицательной или нейтральной. В нашем случае мы будем рассматривать только положительные (включая нейтральные) и отрицательные.

В. Почему мы должны определять тип настроения?

Потому что в конечном итоге мы будем обучать модель, которая должна уметь классифицировать негативные и позитивные настроения в твитах. Для этой классификации мы будем использовать некоторую модель обучения с учителем, поэтому нам нужна целевая переменная. Тип настроения будет нашей целевой переменной.

Я выделил два способа идентифицировать:

а. Использование NLTK's SentimentIntensityAnalyzer (мы будем называть SIA)
b. Использование TextBlob

import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer
from textblob import TextBlob
def fetch_sentiment_using_SIA(text):
    sid = SentimentIntensityAnalyzer()
    polarity_scores = sid.polarity_scores(text)
    if polarity_scores['neg'] > polarity_scores['pos']:
        return 'negative'
    else:
        return 'positive'

def fetch_sentiment_using_textblob(text):
    analysis = TextBlob(text)
    # set sentiment 
    if analysis.sentiment.polarity >= 0:
        return 'positive'
    else: 
        return 'negative'

Мы можем выбрать любой из них, я лично предпочитаю TextBlob, это дает лучшую категоризацию.

3. Предварительная обработка текста

Текст, полученный из твитов, недостаточно чистый, чтобы его можно было использовать для обучения модели, поэтому его необходимо предварительно обработать. Возможно, мы не сможем полностью очистить его, но мы должны постараться сделать все возможное, чтобы предварительно обработать как можно больше.

а. Удаление ‘@names’:

Все «@anyname» бесполезны, поскольку не передают никакого значения.

def remove_pattern(text, pattern_regex):
    r = re.findall(pattern_regex, text)
    for i in r:
        text = re.sub(i, '', text)
    
    return text
# We are keeping cleaned tweets in a new column called 'tidy_tweets'
tweets_df['tidy_tweets'] = np.vectorize(remove_pattern)(tweets_df['tweets'], "@[\w]*: | *RT*")

б. Удаление ссылок (http | https)

Ссылки в тексте бесполезны, потому что они также не несут никакой полезной информации.

cleaned_tweets = []

for index, row in tweets_df.iterrows():
    # Here we are filtering out all the words that contains link
    words_without_links = [word for word in row.tidy_tweets.split()        if 'http' not in word]
    cleaned_tweets.append(' '.join(words_without_links))

tweets_df['tidy_tweets'] = cleaned_tweets

c. Удаление повторяющихся строк

У нас могут быть повторяющиеся твиты в нашем фрейме данных, об этом нужно позаботиться:

tweets_df.drop_duplicates(subset=['tidy_tweets'], keep=False)

d. Удаление знаков препинания, цифр и специальных символов

tweets_df['absolute_tidy_tweets'] = tweets_df['tidy_tweets'].str.replace("[^a-zA-Z# ]", "")

Этот шаг не следует выполнять, если мы также хотим провести анализ тональности __key phrases__, потому что в предложении должно присутствовать семантическое значение. Итак, здесь мы создадим один дополнительный столбец «absolute_tidy_tweets», который будет содержать абсолютные аккуратные слова, которые в дальнейшем можно будет использовать для анализа тональности __key words__.

е. Удаление стоп-слов

Стоп-слова - это слова, которые используются только для правильного построения предложений. Они не имеют никакого значения для полной информации. Поэтому его нужно удалить, чтобы сделать нашу текстовую запись чище.

from nltk.corpus import stopwords
nltk.download('stopwords')
stopwords_set = set(stopwords.words("english"))
cleaned_tweets = []

for index, row in tweets_df.iterrows():
    
    # filerting out all the stopwords 
    words_without_stopwords = [word for word in row.absolute_tidy_tweets.split() if not word in stopwords_set]
    
    # finally creating tweets list of tuples containing stopwords(list) and sentimentType 
    cleaned_tweets.append(' '.join(words_without_stopwords))

tweets_df['absolute_tidy_tweets'] = cleaned_tweets

е. Токенизация и лемматизация:

from nltk.stem import WordNetLemmatizer
# Tokenization
tokenized_tweet = tweets_df['absolute_tidy_tweets'].apply(lambda x: x.split())
# Finding Lemma for each word
word_lemmatizer = WordNetLemmatizer()
tokenized_tweet = tokenized_tweet.apply(lambda x: [word_lemmatizer.lemmatize(i) for i in x])
#joining words into sentences (from where they came from)
for i, tokens in enumerate(tokenized_tweet):
    tokenized_tweet[i] = ' '.join(tokens)

tweets_df['absolute_tidy_tweets'] = tokenized_tweet

4. Извлечение функций

Нам нужно преобразовать текстовое представление в виде числовых функций. У нас есть два популярных метода извлечения признаков:

  1. Пакет слов (простая векторизация)
  2. TF-IDF (частота термина - обратная частота документа)

Мы будем использовать извлеченные функции из обоих по очереди для анализа настроений и, наконец, сравним результат.

Ознакомьтесь с приведенным ниже ядром, чтобы правильно понять интуицию методов извлечения функций с примерами:
https://www.kaggle.com/amar09/text-pre-processing-and-feature-extraction

from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
# BOW features
bow_word_vectorizer = CountVectorizer(max_df=0.90, min_df=2, stop_words='english')
# bag-of-words feature matrix
bow_word_feature = bow_word_vectorizer.fit_transform(tweets_df['absolute_tidy_tweets'])

# TF-IDF features
tfidf_word_vectorizer = TfidfVectorizer(max_df=0.90, min_df=2, stop_words='english')
# TF-IDF feature matrix
tfidf_word_feature = tfidf_word_vectorizer.fit_transform(tweets_df['absolute_tidy_tweets'])

5. Построение модели

Давайте сначала сопоставим целевую переменную с {0,1}.

target_variable = tweets_df['sentiment'].apply(lambda x: 0 if x=='negative' else 1 )

Мы собираемся использовать наивную байесовскую модель для классификации настроений, потому что я также пробовал SVM, логистическую регрессию и дерево решений, но получил наилучшие результаты, используя только наивный байесовский метод.

from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
def naive_model(X_train, X_test, y_train, y_test):
    naive_classifier = GaussianNB()
    naive_classifier.fit(X_train.toarray(), y_train)

    # predictions over test set
    predictions = naive_classifier.predict(X_test.toarray())
    
    # calculating f1 score
    print(f'F1 Score - {f1_score(y_test, predictions)}')

Обучение функциям, извлеченным с помощью пакета слов:

X_train, X_test, y_train, y_test = train_test_split(bow_word_feature, target_variable, test_size=0.3, random_state=870)
naive_model(X_train, X_test, y_train, y_test)

Он дает показатель F1 - 0,9387254901960784

Теперь давайте обучимся функциям, извлеченным из TF-IDF:

X_train, X_test, y_train, y_test = train_test_split(tfidf_word_feature, target_variable, test_size=0.3, random_state=870)
naive_model(X_train, X_test, y_train, y_test)

Я получил результат F1 - 0,9400244798041616

Функции TF-IDF явно дают лучший результат.

Вывод. Здесь для анализа настроений мы использовали только «ключевые слова», мы также можем использовать «ключевые фразы». Есть много других шагов, которые мы должны выполнить, чтобы узнать о них подробно, ознакомьтесь с моим полным ядром.



В этом посте я предположил, что у вас уже есть базовые знания об обработке текста с помощью NLTK. Если у вас его нет, я предлагаю вам просмотреть мое ядро ​​ниже, которое содержит объяснение всех основных текстовых операций и подробное объяснение извлечения функций с использованием BOW и TF-IDF.



Прокомментируйте, если вам нужны дополнительные объяснения по чему-либо. Все предложения и отзывы всегда приветствуются.

Спасибо за чтение, удачного обучения;)