Введение

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

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

Эта статья выглядит следующим образом.

  1. Понимание набора данных реального мира.
  2. Импорт набора данных в фреймворк pandas
  3. Исследовательский анализ данных в наборе данных
  4. Предварительная обработка данных
  5. Featurization
  6. Краткая теория СВД и усеченной СВД
  7. Применение TruncatedSVD к набору данных

Понимание набора данных

Набор данных состоит из обзоров прекрасных продуктов от Amazon. Данные охватывают период более 10 лет, включая все ~ 500 000 отзывов до октября 2012 года. Обзоры включают информацию о продуктах и ​​пользователях, рейтинги и обзор в виде простого текста. Он также включает обзоры из всех других категорий Amazon.

Количество отзывов: 568 454
Количество пользователей: 256 059
Количество продуктов: 74 258
Сроки: октябрь 1999 - октябрь 2012
Количество атрибутов / столбцов в данных: 10

Информация об атрибутах:

  1. Id
  2. ProductId - уникальный идентификатор товара
  3. UserId - уникальный идентификатор пользователя
  4. Имя профиля
  5. HelpfulnessNumerator - количество пользователей, которые сочли отзыв полезным.
  6. HelpfulnessDenominator - количество пользователей, которые указали, считают ли они отзыв полезным или нет.
  7. Оценка - оценка от 1 до 5
  8. Время - отметка времени для обзора
  9. Сводка - краткое изложение обзора
  10. Текст - текст отзыва

Импорт набора данных

Набор данных доступен в двух формах:

  1. .csv файл
  2. База данных SQLite

Чтобы загрузить данные, мы использовали набор данных SQLITE, так как это проще запрашивать данные и эффективно визуализировать данные.

Поскольку мы хотим получить только общее мнение о рекомендациях (положительное или отрицательное), мы намеренно игнорируем все баллы, равные 3. Если оценка выше 3, то рекомендация будет установлена ​​на «положительную». В противном случае он будет установлен на «отрицательный».

# using SQLite Table to read data.
con = sqlite3.connect('database.sqlite')
# filtering only positive and negative reviews i.e. 
# not taking into consideration those reviews with Score=3
# SELECT * FROM Reviews WHERE Score != 3 LIMIT 500000, will give top 500000 data points
# you can change the number to any other number based on your computing power
# filtered_data = pd.read_sql_query(""" SELECT * FROM Reviews WHERE Score != 3 LIMIT 500000""", con) 
# for tsne assignment you can take 5k data points
filtered_data = pd.read_sql_query(""" SELECT * FROM Reviews WHERE Score != 3 LIMIT 100000  """, con)
# Give reviews with Score>3 a positive rating(1), and reviews with a score<3 a negative rating(0).
def partition(x):
    if x < 3:
        return 0
    return 1
#changing reviews with score less than 3 to be positive and vice-versa
actualScore = filtered_data['Score']
positiveNegative = actualScore.map(partition) 
filtered_data['Score'] = positiveNegative
print("Number of data points in our data", filtered_data.shape)
filtered_data.head(3)

В приведенном выше коде мы читаем набор данных в формате sqlite, где оценка не равна 3, с помощью запроса sql в фрейм данных pandas. После прочтения мы заменяем столбец оценки фрейма данных отрицательным, если оценка меньше 3, в противном случае - положительной.

Исследовательский анализ данных

После считывания данных в фрейм данных pandas, затем мы выполнили EDA для набора данных. Сначала мы проверили наличие повторяющихся записей в наборе данных. Было замечено (как показано в таблице ниже), что в данных обзоров было много повторяющихся записей. Следовательно, необходимо было удалить дубликаты, чтобы получить объективные результаты для анализа данных. Ниже приведен пример:

display= pd.read_sql_query("""
SELECT *
FROM Reviews
WHERE Score != 3 AND UserId="AR5J8UI46CURR"
ORDER BY ProductID
""", con)
display.head()

Из приведенной выше таблицы видно, что у одного и того же пользователя есть несколько отзывов с одинаковыми значениями для HelpfulnessNumerator, HelpfulnessDenominator, Score, Time, Summary и Text, и при проведении анализа было обнаружено, что ProductId = B000HDOPZG было Loacker Quadratini Vanilla Wafer Cookies, 8.82- Пакеты унций (упаковка из 8) ProductId = B000HDL1RQ - это печенье Loacker Quadratini с лимонными вафлями, пакеты на 8,82 унции (упаковка из 8) и так далее.

После анализа был сделан вывод, что обзоры с одинаковыми параметрами, отличными от ProductId, относятся к одному и тому же продукту, только с разным вкусом или количеством. Следовательно, чтобы уменьшить избыточность, было решено исключить строки с одинаковыми параметрами.

Для этого использовался метод: сначала мы сортируем данные в соответствии с ProductId, а затем просто сохраняем первый аналогичный обзор продукта и удаляем остальные. для напр. выше остается только обзор для ProductId = B000HDL1RQ. Этот метод гарантирует, что для каждого продукта будет только один представитель. Дедупликация без сортировки может привести к тому, что разные представители по-прежнему будут существовать для одного и того же продукта.

#Sorting data according to ProductId in ascending order
sorted_data=filtered_data.sort_values('ProductId', axis=0, ascending=True, inplace=False, kind='quicksort', na_position='last')
#Deduplication of entries
final=sorted_data.drop_duplicates(subset={"UserId","ProfileName","Time","Text"}, keep='first', inplace=False)
final.shape

Код, показанный выше, сначала сортирует данные в соответствии с ProductID, а затем удаляет все повторяющиеся копии, сохраняя при этом первую копию.

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

display= pd.read_sql_query("""
SELECT *
FROM Reviews
WHERE Score != 3 AND Id=44737 OR Id=64422
ORDER BY ProductID
""", con)
display.head()

Код, показанный ниже, удаляет все строки, в которых HelpfulnessNumerator больше, чем HelpfulnessDenominator.

final=final[final.HelpfulnessNumerator<=final.HelpfulnessDenominator]

В следующем разделе мы выполним предварительную обработку набора данных.

Предварительная обработка данных

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

На этапе предварительной обработки мы сделали следующее, как показано ниже: -

  1. Удаление тегов html
  2. Удалите знаки препинания или ограниченный набор специальных символов, например, или. или # и т. д.
  3. Проверьте, состоит ли слово из английских букв и не является ли оно буквенно-цифровым.
  4. Убедитесь, что длина слова больше 2 (так как было исследовано, что нет прилагательного, состоящего из двух букв)
  5. Преобразуйте слово в нижний регистр
  6. Удалить стоп-слова
  7. Наконец, слово Snowball Stemming (было замечено, что оно лучше, чем Porter Stemming)

После чего мы собираем слова, используемые для описания положительных и отрицательных отзывов.

Когда мы сталкиваемся с проблемой текста в Обработке естественного языка, процесс удаления стоп-слов является одним из важных шагов для улучшения входных данных для любых моделей. Стоп-слова означают, что это очень распространенные слова в языке (например, a, an, the в английском языке. Это не помогает в большинстве проблем НЛП, таких как семантический анализ, классификация и т. Д., Поэтому мы удалим все стоп-слова в нашем обзоры.

В приведенном ниже коде мы определяем набор всех стоп-слов на английском языке.

# https://gist.github.com/sebleier/554280
# we are removing the words from the stop words list: 'no', 'nor', 'not'
# <br /><br /> ==> after the above steps, we are getting "br br"
# we are including them into stop words list
# instead of <br /> if we have <br/> these tags would have revmoved in the 1st step
stopwords= set(['br', 'the', 'i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', "you're", "you've",\
            "you'll", "you'd", 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', \
            'she', "she's", 'her', 'hers', 'herself', 'it', "it's", 'its', 'itself', 'they', 'them', 'their',\
            'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', "that'll", 'these', 'those', \
            'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', \
            'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', \
            'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after',\
            'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further',\
            'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more',\
            'most', 'other', 'some', 'such', 'only', 'own', 'same', 'so', 'than', 'too', 'very', \
            's', 't', 'can', 'will', 'just', 'don', "don't", 'should', "should've", 'now', 'd', 'll', 'm', 'o', 're', \
            've', 'y', 'ain', 'aren', "aren't", 'couldn', "couldn't", 'didn', "didn't", 'doesn', "doesn't", 'hadn',\
            "hadn't", 'hasn', "hasn't", 'haven', "haven't", 'isn', "isn't", 'ma', 'mightn', "mightn't", 'mustn',\
            "mustn't", 'needn', "needn't", 'shan', "shan't", 'shouldn', "shouldn't", 'wasn', "wasn't", 'weren', "weren't", \
            'won', "won't", 'wouldn', "wouldn't"])

Приведенный ниже код выполняет все шаги, упомянутые выше.

# https://stackoverflow.com/a/47091490/4084039
import re
def decontracted(phrase):
    # specific
    phrase = re.sub(r"won't", "will not", phrase)
    phrase = re.sub(r"can\'t", "can not", phrase)
# general
    phrase = re.sub(r"n\'t", " not", phrase)
    phrase = re.sub(r"\'re", " are", phrase)
    phrase = re.sub(r"\'s", " is", phrase)
    phrase = re.sub(r"\'d", " would", phrase)
    phrase = re.sub(r"\'ll", " will", phrase)
    phrase = re.sub(r"\'t", " not", phrase)
    phrase = re.sub(r"\'ve", " have", phrase)
    phrase = re.sub(r"\'m", " am", phrase)
    return phrase

from tqdm import tqdm
preprocessed_reviews = []
# tqdm is for printing the status bar
for sentence in tqdm(final['Text'].values):
sentence = re.sub(r"http\S+", "", sentence) # regular expression to remove http or https from reviews
    
    sentence = BeautifulSoup(sentence, 'lxml').get_text() # removal of html tags using BeautifulSoup library
    
    sentence = decontracted(sentence) #  Remove any punctuations or limited set of special characters like , or . or # etc.
    
    sentence = re.sub("\S*\d\S*", "", sentence).strip() #remove words with numbers python: https://stackoverflow.com/a/18082370/4084039
    
    sentence = re.sub('[^A-Za-z]+', ' ', sentence) # regular expression to check whether text is english or alphanumeric
    # https://gist.github.com/sebleier/554280
    
    sentence = ' '.join(e.lower() for e in sentence.split() if e.lower() not in stopwords) # converting words to lowercase, removing words with length less than equal to two and removing stopwords.
    preprocessed_reviews.append(sentence.strip()) # appending the processed reviews into a list.

После предварительной обработки текста рецензии следующий шаг - определение характеристик.

Featurization

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

  1. Мешок слов
  2. Частота термина - обратная периодичность документа (TFIDF)
  3. Word2Vec

Чтобы продемонстрировать использование усеченного SVD, мы использовали метод TFIDF. Можно попробовать и другими методами, порядок действий останется прежним. Поскольку мы использовали TFIDF, мы немного углубимся в работу TFIDF.

TF-IDF расшифровывается как Term Frequency-Inverse Document Frequency, что в основном говорит нам о важности слова в корпусе или наборе данных. TF-IDF содержит два понятия «частота термина» (TF) и частота обратного документа (IDF).

Частота запросов

Частота термина определяется как часто слово появляется в документе или корпусе. Поскольку каждое предложение имеет разную длину, возможно, что слово в длинном предложении может встречаться большее количество раз по сравнению со словом в более коротком предложении. Частота сроков определяется как:

Частота обратного документа

Частота обратного документа - еще одно понятие, которое используется для определения важности слова. Он основан на том, что менее частые слова более информативны и важны. IDF представлен формулой:

Ниже приведен простой пример, чтобы понять, как работает TFIDF.

Пример:

Рассмотрим документ, содержащий 100 слов, в котором слово кошка встречается 3 раза. Тогда частота термина (т. Е. Tf) для cat равна (3/100) = 0,03. Теперь предположим, что у нас есть 10 миллионов документов, и слово cat встречается в одной тысяче из них. Затем обратная частота документа (т.е. idf) вычисляется как log (10,000,000 / 1,000) = 4. Таким образом, вес Tf-idf является произведением этих величин: 0,03 * 4 = 0,12.

Приведенный выше пример взят отсюда.

TFIDF можно легко реализовать с помощью класса TFIDFVectorizer в sklearn. TfidfVectorizer будет токенизировать документы, изучать словарный запас и обратную частоту взвешивания документов, а также позволит вам кодировать новые документы.

Ниже приведен код, который мы использовали для получения векторов TFIDF для текстовых обзоров с помощью TfidfVectorizer.

tf_idf_vect = TfidfVectorizer(ngram_range=(1,1), min_df=10,use_idf= True)
tf_idf_vect.fit(preprocessed_reviews)
print("some sample features(unique words in the corpus)",tf_idf_vect.get_feature_names()[0:10])
print('='*50)
final_tf_idf = tf_idf_vect.transform(preprocessed_reviews)

В TfidfVectorizer ngram_range = (1,1) указывает только в словах униграммы для получения векторов.

При построении словаря мы можем игнорировать слова, частота которых в документе строго ниже заданного порога. Установка min_df = 10 указывает, что слова с частотой документа менее 10 игнорируются.

Use_idf = True, разрешить обратное изменение веса документа.

После инициализации TfidfVectorizer мы подогнали и преобразовали предварительно обработанные обзоры, полученные на этапе предварительной обработки, как показано в приведенном выше коде.

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

some sample features(unique words in the corpus) ['aa', 'aafco', 'aback', 'abandon', 'abandoned', 'abdominal', 'ability', 'able', 'abroad', 'absence']

Форма нашего текстового обзора после выполнения TfidfVectorier была (87773, 11524). Итак, у нас было 87773 текстовых обзора в наборе данных, и каждый обзор векторизован с размером 11524.

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

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

В следующем разделе мы обсудим, как мы добились уменьшения размерности в нашем случае.

СВД и усеченная СВД

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

А = U. Сигма. V ^ T

Где A - это вещественная матрица размера m x n, которую мы хотим разложить, U - матрица размера m x m, Sigma - диагональная матрица размера m x n, а V ^ T - транспонированная матрица размера n x n, где T - верхний индекс.

«Разложение по сингулярным значениям - изюминка линейной алгебры».

- Стр. 371, Введение в линейную алгебру, пятое издание, 2016 г.

Диагональные значения в матрице сигма известны как сингулярные значения исходной матрицы A. Столбцы матрицы U называются лево-сингулярными векторами матрицы A, а столбцы матрицы V называются сингулярными векторами справа матрицы A.

СВД для уменьшения размерности

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

Для этого мы можем выполнить операцию SVD с исходными данными и выбрать k самых больших сингулярных значений в Sigma. Эти столбцы можно выбрать из Sigma, а строки - из V ^ T. Затем можно восстановить приближенное значение B исходного вектора A.

B = U * Sigmak * V ^ Tk

При обработке естественного языка SVD может применяться к матрицам встречаемости слов.

Усеченный СВД

До сих пор мы видели, что разложение по сингулярным числам разбивает любую матрицу A так, чтобы A = U * Sigmal * V ^ T.

Рассмотрим подробнее матрицу Sigma.

Помните, что Sigma - это матрица формы, показанной ниже.

куда

- особые значения матрицы A ранга r.

Мы можем найти усеченный SVD в A, установив все, кроме первых k наибольших сингулярных значений, равными нулю и используя только первые k столбцов U и V.

Выбор значения k

Итак, какое k мы должны выбрать? Если мы выберем более высокое k, мы получим более близкое приближение к A. С другой стороны, выбор меньшего k сэкономит нам больше времени обработки. По сути, мы должны выбирать между временем и точностью. Как вы думаете, что важнее? Как мы можем найти хороший баланс между жертвой времени и данными?

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

Уменьшение размерности с помощью усеченного SVD

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

В качестве примера совпадения слов рассмотрим корпус, состоящий из следующих документов:

Мудрый пенни и глупый фунт

сэкономленный пенни - это заработанный пенни

Если позволить count (w (next) | w (current)) представить, сколько раз слово w (next) следует за словом w (current) , мы можем суммировать статистику совместной встречаемости слов «а» и «пенни» как:

В приведенной выше таблице показано, что за буквой «а» дважды следует слово «пенни», в то время как слова «заработал», «сэкономлен» и «мудрый» следует за словом «пенни» один раз в нашем корпусе. Таким образом, слово «заработано» в одном случае из трех может появиться после «пенни». Показанный выше счетчик называется частотой биграмм; он просматривает только следующее слово текущего слова. Учитывая корпус из N слов, нам нужна таблица размером N x N для представления биграммных частот всех возможных пар слов. Такая таблица очень разреженная, так как большинство частот равны нулю.

Ниже приведен код генерации матрицы совпадения для нашего текстового обзора. Общее количество уникальных слов в нашем корпусе мы получили из TfidfVectorizer с помощью метода .get_feature_names ()

# to obtain the co-occureence matrix using Top 6000 features of TFIDF vectorizer
cooccurrenceMatrix = np.zeros((6000,6000)) # co-occurance matrix
context_window = 4 # context window for co-occurance matrix

После того, как наша матрица совпадений построена, мы применили Truncated SVD для уменьшения размерности.

Как мы видели, для Truncated SVD нам нужно определить значение k. Мы выделяем значение k или количество компонентов на основе объясненной дисперсии для каждого из особых значений.

Анализ этого показан ниже в коде.

# Program to find the optimal number of components for Truncated SVD
n_comp = [4,10,15,20,50,100,150,200,500,700,800,900,1000,1500,2000,2500,3000,3500] # list containing different values of components
explained = [] # explained variance ratio for each component of Truncated SVD
for x in n_comp:
    svd = TruncatedSVD(n_components=x)
    svd.fit(cooccurrenceMatrix)
    explained.append(svd.explained_variance_ratio_.sum())
    print("Number of components = %r and explained variance = %r"%(x,svd.explained_variance_ratio_.sum()))
plt.plot(n_comp, explained)
plt.xlabel('Number of components')
plt.ylabel("Explained Variance")
plt.title("Plot of Number of components v/s explained variance")
plt.show()

Из приведенного выше графика мы видим, что при k = 3000 мы получаем объясненную дисперсию более 97%. Таким образом, 3000 функций или слов объясняют 97% наших данных.

Таким образом, каждый обзор вместо того, чтобы представлять его с 11k старыми функциями, мы можем представить каждый обзор с топ-3000 компонентов, полученных из усеченного SVD.

Вот как мы можем реализовать уменьшение размерности с помощью усеченного SVD.

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

Заключение

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

Мы изучили и практически реализовали технику уменьшения размерности текстовых данных с помощью Truncated SVD и увидели, как оптимально выбрать количество компонентов для Truncated SVD.

Обратитесь к моему профилю на github за подробным кодом.