Контролируемое машинное обучение и анализ настроений

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

Логитическая регрессия — это алгоритм обучения с учителем. У нас есть ввод X, который входит в вашу функцию прогнозирования, чтобы получить Y^. Затем мы можем сравнить его с меткой, фактическим значением Y. Этот процесс дает вам стоимость, которую вы используете для обновления θ. Изображение с Coursera Обработка естественного языка с классификацией и векторными пространствами хорошо описывает процесс.

Текст, используемый в анализе настроений, необходимо преобразовать в вектор признаков, другими словами, текст необходимо преобразовать в вектор с нулями и единицами, представляющими слова.

Цель проекта

Цель проекта — классифицировать твиты с помощью логистической регрессии. Учитывая твит, мы должны решить, имеет ли текст положительное или отрицательное настроение. В частности, мы узнаем:

  • как извлечь черты из текста
  • как реализовать логистическую регрессию с нуля
  • применять логистическую регрессию для задачи обработки естественного языка
  • протестировать модель и выполнить анализ ошибок

Этап подготовки

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

import nltk
from os import getcwd
import numpy as np
import pandas as pd
from nltk.corpus import twitter_samples
from utils import process_tweet, build_freqs

twitter_samples содержит 10000 примеров. 5000 положительных примеров и 5000 отрицательных примеров.

all_positive_tweets = twitter_samples.strings('positive_tweets.json')
all_negative_tweets = twitter_samples.strings('negative_tweets.json')
test_pos = all_positive_tweets[4000:]
train_pos = all_positive_tweets[:4000]
test_neg = all_negative_tweets[4000:]
train_neg = all_negative_tweets[:4000]
train_x = train_pos + train_neg 
test_x = test_pos + test_neg

Прежде чем мы начнем обучать модель, мы должны разделить данные на набор для обучения и тестирования. Пропорция такова: 20% на тестирование и 80% на обучение. Когда мы закончим с разбиением, нам нужно создать частотный словарь, который подсчитывает, сколько раз встречается заданное слово. Мы можем создать функцию build_freqs().

for y,tweet in zip(ys, tweets):
        for word in process_tweet(tweet):
            pair = (word, y)
            if pair in freqs:
                freqs[pair] += 1
            else:
                freqs[pair] = 1

Теперь мы можем использовать его для всего набора данных.

# create frequency dictionary
freqs = build_freqs(train_x, train_y)
# check the output
print("type(freqs) = " + str(type(freqs)))
print("len(freqs) = " + str(len(freqs.keys())))

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

print('This is an example of a positive tweet: \n', train_x[0])
print('\nThis is an example of the processed version of the tweet: \n', process_tweet(train_x[0]))

Логистическая регрессия

Сигмовидная функция

Во-первых, мы должны реализовать сигмовидную функцию. Функция выглядит следующим образом:

График представляет собой забавную форму буквы «S» между 0 и 1. Как вы, возможно, помните, логистическая регрессия — это алгоритм классификации, поэтому прогнозы будут находиться в этом диапазоне. 0 — отрицательное настроение, 1 — положительное.

Реализация проста. Мы должны реализовать уравнение.

def sigmoid(z): 

    h = 1/(1+np.exp(-z))
    return hCost function and gradient descent 

Градиентный спуск

Теперь займемся реализацией градиентного спуска. Для каждой итерации алгоритма мы вычисляем функцию стоимости, используя все обучающие примеры. Мы обновляем веса 𝜃𝑖 в векторе-столбце, а не по одному значению за раз. 𝜃 имеет размерность (n+1, 1). N обозначает все функции, а 1 – дополнительный член смещения 𝜃0. «Логиты», «z», рассчитываются путем умножения матрицы признаков «x» на весовой вектор «тета». 𝑧=𝐱𝜃. Предсказание h вычисляется путем применения сигмовидной функции к каждому элементу в z. Стоимость J рассчитывается путем скалярного произведения векторов y и log(h). Поскольку оба они являются векторами-столбцами, мы должны транспонировать векторы влево, чтобы матричное умножение вектора-строки на вектор-столбец выполняло скалярное произведение. Обновленная тета также векторизована. Поскольку размеры 𝐱 равны (m, n+1), а размеры 𝐡 и 𝐲 равны (m, 1), нам нужно переставьте 𝐱 и поместите его слева, чтобы выполнить умножение матриц.

def gradientDescent(x, y, theta, alpha, num_iters):
   
    m = x.shape[0]
    
    for i in range(0, num_iters):
        
        # get z, the dot product of x and theta
        z = np.dot(x, theta)
        
        # get the sigmoid of z
        h = sigmoid(z)
        
        # calculate the cost function
        J = -1./m * (np.dot(y.transpose(), np.log(h)) + np.dot((1-y).transpose(),np.log(1-h)))
# update the weights theta
        theta = theta = theta = theta - (alpha/m) * np.dot(x.transpose(),(h-y))
        
    J = float(J)
    return J, theta

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

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

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

def extract_features(tweet, freqs):
    # process_tweet tokenizes, stems, and removes stopwords
    word_l = process_tweet(tweet)
    
    # 3 elements in the form of a 1 x 3 vector
    x = np.zeros((1, 3)) 
    
    #bias term is set to 1
    x[0,0] = 1 
    
    # loop through each word in the list of words
    for word in word_l:
        
        # increment the word count for the positive label 1
        x[0,1] += freqs.get((word, 1.0), 0)
        
        # increment the word count for the negative label 0
        x[0,2] += freqs.get((word, 0.0), 0)
        
    assert(x.shape == (1, 3))
    return x

Обучение вашей модели

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

# collect the features 'x' and stack them into a matrix 'X'
X = np.zeros((len(train_x), 3))
for i in range(len(train_x)):
    X[i, :]= extract_features(train_x[i], freqs)
# training labels corresponding to X
Y = train_y
# Apply gradient descent
J, theta = gradientDescent(X, Y, np.zeros((3, 1)), 1e-9, 1500)
print(f"The cost after training is {J:.8f}.")
print(f"The resulting vector of weights is {[round(t, 8) for t in np.squeeze(theta)]}")

Протестируйте свою модель логистической регрессии

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

def predict_tweet(tweet, freqs, theta):
    
    # extract the features of the tweet and store it into x
    x = extract_features(tweet, freqs)
    
    # make the prediction using x and theta
    y_pred = sigmoid(np.dot(x,theta))
    
    return y_pred

Мы видели прогнозы для некоторых твитов.

for tweet in ['I am happy', 'I am bad', 'this movie should have been great.', 'great', 'great great', 'great great great', 'great great great great']:
    print( '%s -> %f' % (tweet, predict_tweet(tweet, freqs, theta)))

Теперь посчитаем меру точности для тестового набора. Мы берем предыдущую функцию predict_tweet и проверяем предсказание каждого твита. Если прогноз больше 0,5, значение устанавливается равным 1, в противном случае — 0. Прогноз является точным, если y_hat и test_y равны. Мы должны суммировать все эти экземпляры и разделить на m.

def test_logistic_regression(test_x, test_y, freqs, theta):
    
    # the list for storing predictions
    y_hat = []
    
    for tweet in test_x:
        # get the label prediction for the tweet
        y_pred = predict_tweet(tweet, freqs, theta)
        
        if y_pred > 0.5:
            # append 1.0 to the list
            y_hat.append(1)
        else:
            # append 0 to the list
            y_hat.append(0)
# With the above implementation, y_hat is a list, but test_y is (m,1) array
    # convert both to one-dimensional arrays in order to compare them using the '==' operator
    accuracy = (y_hat==np.squeeze(test_y)).sum()/len(test_x)   
    return accuracy

Точность всего тестового набора:

tmp_accuracy = test_logistic_regression(test_x, test_y, freqs, theta)
print(f"Logistic regression model's accuracy = {tmp_accuracy:.4f}")

Точность модели логистической регрессии = 0,9950.