Этот проект предоставлен IBM skills network.
Откройте для себя мир безграничных возможностей обучения! В этом проекте вы погрузитесь в захватывающую сферу обработки естественного языка (NLP) и машинного обучения (ML), чтобы найти похожие курсы, как никогда раньше. От предварительной обработки текста до его векторизации с помощью передовых моделей НЛП, таких как BERT, вы овладеете искусством подготовки текста для анализа. Получите практический опыт работы с алгоритмами кластеризации и найдите оптимальное количество кластеров, используя научные методы, такие как оценка силуэта, метод Дэвиса Боулдина, метод Калински-Харабаса и X-средних. Откройте для себя красоту визуализации данных при построении похожих курсов в интерактивном 2D и 3D. И, наконец, ищите и рекомендуйте кластеры с учетом ваших конкретных интересов — и все это в одном проекте!
➜ Совет для профессионалов: если вы хотите учиться быстрее и запускать (или скачивать) этот проект в Jupyter Notebook бесплатно, посетите CognitiveClass.ai.
После завершения этого проекта вы сможете:
1. Подготовить набор данных и текст для анализа на различных этапах
2. Векторизовать текст для задач машинного обучения с использованием современных моделей НЛП, таких как модель BERT для встраивания
3. Определить оптимальную количество кластеров
4. Используйте алгоритм K-средних для кластеризации
5. Визуализируйте похожие курсы на 2D- и 3D-графиках
6. Рекомендуйте кластер курса, исходя из ваших интересов
Загрузка библиотек
# if you have not installed the following libraries, un-comment this code: #pip install -U 'skillsnetwork' 'yellowbrick' 'sentence-transformers' 'seaborn' 'texthero' 'pyclustering' 'nltk' 'nmslib' import string import re import nltk import numpy as np import pandas as pd import matplotlib.pyplot as plt %matplotlib inline from sklearn.cluster import KMeans from nltk.stem import PorterStemmer
1. Подготовка набора данных
Данные включают данные каталога курсов для разных курсов по разным предметам. Наша цель — найти и порекомендовать курсы с похожими описаниями.
Набор данных создан университетом Иллинойс. Вы можете скачать и использовать его в образовательных целях. Вы также можете скачать его здесь.
# Load the daset data = pd.read_csv('course-catalog.csv') print('==> List of all columns: \n\n',list(data),'\n') # sort data by names and subset data data = data.sort_values(by='Name') # lets drop the duplicated data point in the description data = data.drop_duplicates(subset='Description', keep="first") # select columns the we are interested data = data[['Subject','Name','Description']] data.head()
Этот проект будет сосредоточен на наборе данных, содержащем 190 предметов, некоторые из которых имеют ограниченное количество точек данных. Чтобы обеспечить надежный анализ, мы выберем и сохраним только 15 наиболее часто встречающихся «субъектов» в данных. Затем мы исследуем, насколько эффективно кластеризация может дифференцировать эти 15 тематических групп на основе их описаний.
# counting the datapoints for each group of subject keep=data.groupby('Subject').count().sort_values(by='Name',ascending=False)['Name'] print(' ==> list of Subject and their counts:', keep) # keep only first 15 most frequent keep=keep[:15].index print('\n ==> List of Subject to keep: \n\n', keep) data = data[data['Subject'].isin(keep)] data = data.reset_index(drop=True) data.shape
1. 1. Предварительная обработка текста для кластеризации
Шаги для предварительной обработки текста следующие:
1. Преобразуйте весь текст в нижний регистр для единообразия и простоты обработки.
2. Удалите все знаки препинания и стоп-слова, поскольку они не влияют на смысл текста.
3. Используйте корень, чтобы сократить слова до их значения. базовая форма для лучшей группировки похожих слов.
4. Применить лемматизацию для дальнейшей нормализации слов, рассматривая варианты слова как одно и то же.
5. Преобразовать обработанный текст в числовые векторы для математического анализа и обработки .
# selecting the column that has text text = data['Description'] # removing null values text = text[~pd.isnull(text)] #removing missing values text=text.tolist() # make it lower case text = [t.lower() for t in text] text[:3] # Strip all punctuation from each article # This uses str.translate to map all punctuation to the empty string table = str.maketrans('', '', string.punctuation) text = [t.translate(table) for t in text] # Convert all numbers in the article to the word 'num' using regular expressions text = [re.sub(r'\d+', 'num', t) for t in text] # Print the first article as a running example text[:2]
Выделение корней и лемматизация – это два метода предварительной обработки текста, которые используются при анализе текста для приведения слов к их базовой форме. Стемминг использует эвристику, чтобы отсечь суффиксы слов, чтобы получить основу (например, доступ, доступ => основа к => доступу), в то время как лемматизация использует более продвинутые алгоритмы для приведения слова к его базовой форме на основе его предполагаемого значения. Цель как стемминга, так и лемматизации состоит в том, чтобы сгруппировать вместе различные формы слова, чтобы их можно было проанализировать как единый элемент (например, было, были, я => лемматизировать => быть)
# creating stemmer model and fit evey sentence to it stemmer = PorterStemmer() text_stem = [stemmer.stem(t) for t in text] # for lemmatization, you need to download "wordnet" repository which contains family of words nltk.download("wordnet") # create lemmatizer and apply it for every sentence in the text lem = nltk.stem.wordnet.WordNetLemmatizer() text_lem = [lem.lemmatize(t) for t in text_stem]
Важно отметить, что применение этих шагов предварительной обработки может улучшить или не улучшить результаты в зависимости от конкретной задачи, данных и модели. Поэтому рекомендуется поэкспериментировать с различными этапами предварительной обработки и сравнить результаты, чтобы найти лучший подход для вашей конкретной проблемы.
2. Векторизация предложений
Здесь мы используем:
Sentence-BERT — это модификация платформы BERT, позволяющая проводить крупномасштабное семантическое сравнение, группировку и поиск информации.
SBERT имеет библиотеку Python под названием sentence_transformers, которые вы можете использовать для вычисления вложений предложений для более чем 100 языков. Затем эти вложения можно сравнить, например. с косинусным сходством или кластеризацией, чтобы найти предложения со схожими значениями. Это может быть полезно для семантического текстового сходства, семантического поиска или анализа парафраз.
В разделе Установка необходимых библиотек уже должны быть установлены предложения_трансформеров. Создадим список предложений.
Библиотека предлагает большую коллекцию предобученных моделей]() для различных задач. Например, модель all-mpnet-base-v2 обеспечивает наилучшее качество, тогда как all-MiniLM-L6-v2 работает в 5 раз быстрее и по-прежнему обеспечивает хорошее качество. Давайте загрузим модель all-MiniLM-L6-v2.
from sentence_transformers import SentenceTransformer from sentence_transformers import util model_SBERT = SentenceTransformer('all-MiniLM-L6-v2')
В этом разделе мы применим SBERT к описаниям наших курсов.
В целом, модели SentenceTransformer основаны на сложных архитектурах преобразователей, которые, как известно, представляют собой большие и глубокие нейронные сети. Создание вложений из этих моделей может быть дорогостоящим в вычислительном отношении и может занять больше времени по сравнению с более простыми моделями. Это связано с тем, что генерация встраивания требует значительного количества вычислительных ресурсов, таких как мощность процессора или графического процессора.
# this method encode the sentences into numpy vectors which can be used for clustering task # the vectorization may take time embeddings = model_SBERT.encode(text_lem, convert_to_numpy=True,show_progress_bar=True) # By default, convert_to_numpy = True embeddings.shape print(embeddings[0][:50])
Как видите, этапы предварительной обработки преобразовали наш текст в числовое представление, что является важным шагом в подготовке данных для моделей машинного обучения, таких как кластеризация и классификация. Это числовое представление позволяет нам выполнять математический анализ и обработку, делая данные готовыми для построения и обучения моделей машинного обучения.
➜ Совет: вы можете бесплатно скачать полную версию Jupyter Notebook с веб-сайта когнитивного класса.
3. Выбор количества кластеров (k) для кластеризации K-средних
Есть несколько других методов, которые можно использовать для поиска оптимального количества кластеров для кластеризации k-средних в Python, помимо метода локтя. Некоторые из этих методов включают в себя:
Оценка силуэта: оценка силуэта рассчитывается путем получения среднего силуэта всех точек данных, где силуэт отдельной точки данных определяется как разница между средним расстоянием этой точки до всех других точек в тот же кластер и среднее расстояние от этой точки до всех точек в ближайших других кластерах, деленное на большее из двух.
Индекс Калински-Харабаса. Этот метод вычисляет оценку для каждого значения k на основе отношения WCSS кластеров к сумме квадратов между кластерами. Оптимальное количество кластеров — это значение k, которое максимизирует индекс Калинского-Харабаша.
Индекс Дэвиса-Булдина: этот метод вычисляет оценку для каждого значения k на основе отношения WCSS кластеров к расстоянию между центрами кластеров. Оптимальное количество кластеров — это значение k, которое минимизирует индекс Дэвиса-Булдина.
Алгоритм X-средних. Этот метод является расширением алгоритма k-средних, который использует рекурсивный процесс разделения для автоматического определения оптимального количества кластеров.
# we append the best number of k into the list to reach our final desicion optimal_Ks=[] # Using Silhouette score from sklearn.metrics import silhouette_score # Create a list of possible numbers of clusters to try num_clusters = list(range(2, 20)) # Calculate the silhouette scores for each number of clusters silhouette_scores = [] for k in num_clusters: # run clustering for each k kmeans = KMeans(n_clusters=k,n_init=10) kmeans.fit(embeddings) labels = kmeans.predict(embeddings) #calculating the sihouetter score sil_score=silhouette_score(embeddings,labels) print("cluster:",k, "score -> ", sil_score) silhouette_scores.append(sil_score) # displaying the optimum number of clusters optimal_num_clusters = num_clusters[silhouette_scores.index(max(silhouette_scores))] print("The highest s-score of the k-clusters: " , optimal_num_clusters) #appending the result into the opmimum list optimal_Ks.append(optimal_num_clusters)
Вы также можете построить кластеры в этом методе. Критерии выбора лучшего номера кластера на основе визуализации следующие:
– Наличие большего количества кластеров с оценкой силуэта выше среднего (обозначено пунктирной линией)
– Меньшие колебания размера графиков силуэта (что указывает на более однородные кластеры)
– Меньше отрицательных моментов, которые представляют потенциальная неправильная кластеризация.
Индекс Дэвиса Боулдинга:
# Davies Bouldin index from sklearn.cluster import KMeans from sklearn.metrics import davies_bouldin_score # Create a list of possible numbers of clusters to try num_clusters = list(range(2, 21)) # Calculate the silhouette scores for each number of clusters dav_scores = [] for k in num_clusters: kmeans = KMeans(n_clusters=k) kmeans.fit(embeddings) labels = kmeans.predict(embeddings) # calculating Davies score for each k dav_score=davies_bouldin_score(embeddings, labels) print("cluster:",k, "value -> ", dav_score) dav_scores.append(dav_score) # displaying the optimum number of clusters optimal_num_clusters = num_clusters[dav_scores.index(min(dav_scores))] print("The optimum score of the k-clusters: " , optimal_num_clusters) #appending the result into the opmimum list optimal_Ks.append(optimal_num_clusters)
Х означает:
from pyclustering.cluster.xmeans import xmeans from pyclustering.cluster.center_initializer import kmeans_plusplus_initializer # Create an X-means object with the sample data xmeans_instance = xmeans(embeddings, kmeans_plusplus_initializer(embeddings, 2).initialize()) # Perform the X-means algorithm xmeans_instance.process() # Get the optimal number of clusters clusters = xmeans_instance.get_clusters() # print the clusters print('the identified optimum cluster ==> ',len(clusters)) #appending the result into the opmimum list optimal_Ks.append(len(clusters))
Для голосования за оптимальное количество кластеров мы используем медиану. Использование медианы может быть лучше, чем использование среднего, когда набор данных содержит выбросы или экстремальные значения.
# lets vote the median of the suggested clusters int(np.median(optimal_Ks))
4. Кластеризация данных
#'How many clusters do you want to use? use utilze median to vote # lets use median of the optimal number of clusters true_k = int(np.median(optimal_Ks)) k_model = KMeans(n_clusters=true_k, init='k-means++', max_iter=200, n_init=10) k_model.fit(embeddings) # the labels given by clusters labels=k_model.labels_ # adding the labels to the our original dataset data['labels'] = pd.Categorical(labels) data.head()
Давайте посмотрим, как даются «метки» по отношению к «Теме». Для этого мы создаем таблицу, которая включает перекрестные подсчеты групп «Тема» и «Ярлык».
# data.groupby(['labels','Subject']).size(): this groups the data by two columns 'labels' and 'Subject' and then it counts the number of occurances in each group. # .unstack() : this unstacks the DataFrame, which means it pivots the dataframe from long format to wide format. data_wide = data.groupby(['labels','Subject']).size().to_frame().unstack() data_wide
Каждая строка базы данных соответствует кластеру и каждому столбцу субъекта. Большинство столбцов равны нулю для каждого кластера, за исключением нескольких. Таким образом, алгоритм кластеризации берет выборки, относящиеся к определенной теме. Давайте визуализируем результаты, чтобы получить больше информации.
5. График результатов
Наши векторизованные данные имеют большие размеры (384 столбца), и для их построения нам нужно уменьшить размеры.
t-SNE — это метод уменьшения размерности, который сводит многомерные данные к двумерному или трехмерному пространству, упрощая визуализацию шаблонов или кластеров. Он сохраняет локальные отношения между точками данных и разделяет разные кластеры. Он обычно используется с кластеризацией и может быть чувствителен к начальным условиям и параметрам. Это полезно для визуализации многомерных наборов данных и часто используется с кластеризацией для интерпретации результатов.
В нашем наборе данных с 1205 точками данных, каждая из которых представлена набором из 384 функций, было бы трудно визуализировать все эти функции одновременно и понять отношения между точками данных. t-SNE можно использовать для проецирования точек данных в 2D- или 3D-пространство, где вы можете увидеть кластеры похожих точек данных и понять их отношения.
Мы преобразуем две точки данных в 2D-пространство:
#reduce the dimension of data into two and then plot it from sklearn.manifold import TSNE X_embedded_2d = TSNE(n_components=2).fit_transform(embeddings) # reduce the dimention to 3 for 3d plotting X_embedded_3d = TSNE(n_components=3).fit_transform(embeddings)
Кроме того, мы можем использовать библиотеку `textero` для уменьшения размерности и интерактивного построения графиков.
import texthero as hero # coverting the vectors to array data_hero = data.copy() data_hero['vectors'] = np.array(embeddings).tolist() # using TSNE method to reduce the dimensions data_hero['tsnemodel'] = hero.tsne(data_hero['vectors']) # plot the data hero.scatterplot(data_hero, col='tsnemodel' , color='labels' , title="Course categories" , hover_data = ['Subject','Name'])
Мы можем строить интерактивные трехмерные диаграммы, используя plotly.
import plotly.graph_objects as go #for 3D plot txt=[["Subject: " + data['Subject'][i], "Lables: " + str(data['labels'][i])] for i in range(len(data))] trace = go.Scatter3d(x=X_embedded_3d[:,0], y=X_embedded_3d[:,1], z=X_embedded_3d[:,2], mode='markers', text=txt, marker=dict(color = labels, size= 10, line=dict(color= 'black',width = 10))) layout = go.Layout(margin=dict(l=0,r=0),height = 800,width = 800) trace = [trace] fig = go.Figure(data = trace, layout = layout) fig.show()
6. Создание поискового индекса для рекомендации
Когда мы выполняем наш поиск на основе описания курсов, нам нужно иметь возможность найти векторы, наиболее близкие к нашему вектору поиска. Начнем с создания индекса для точек данных. Для этого мы можем использовать легкую и эффективную неметрическую пространственную библиотеку (NMSLIB). Там 3 шага, связанные с этим процессом:
- Инициализируйте новый индекс, передав «hnsw» в качестве метода и «cosinesimil» в качестве пробела.
- Добавьте вложения в индекс, используя метод
addDataPointBatch()
. - Создайте индекс с точками данных, используя
createIndex method()
.
import nmslib model_index = nmslib.init(method='hnsw', space='cosinesimil') model_index.addDataPointBatch(embeddings) model_index.createIndex({'post': 2})
Создание функции поиска для рекомендации
Наконец, мы создаем функцию поиска, чтобы найти лучшую бутылку, конечно, на основе индекса сходства между отзывами. Функция принимает два входа: DataFrame и UserQuery. Пользовательский запрос будет преобразован в вектор с помощью `model.encode()`, точно так же, как мы сделали для описаний курса. Затем можно использовать библиотеку *NMSLIB* для возврата k ближайших соседей (алгоритм классифицирует пользовательский ввод на основе меры подобия) вектора запроса пользователя. Мы можем установить k (количество соседей) равным 20.
def find_best_course(data, q_input): # Check if both inputs are not None if data is not None and q_input is not None: subset = data.copy() # Encode the q_input using the SBERT model query = model_SBERT.encode([q_input], convert_to_tensor=True) # Use the knnQuery function to find the 20 nearest neighbors to the encoded query ids, distances = model_index.knnQuery(query, k=20) # Initialize an empty list to store the best fits best_fits = [] # Iterate through the returned ids and distances for i, j in zip(ids, distances): # Create a dictionary for each best fit containing information from the original data # Subject, Name, Description, cluster and distance returned by the knnQuery function best_fits.append({'Subject':subset.Subject.values[i] , 'Name' : subset.Name.values[i] , 'Description': subset.Description.values[i] , 'cluster': subset.labels.values[i] , 'distance': j }) return pd.DataFrame(best_fits)
Мы тестируем нашу функцию, добавляя описание «Изучаем и пишем о компьютерных технологиях» и находим курсы, которые представляют собой наиболее похожий кластер.
most_similar=find_best_course(data, "exploring and writing about computer technologies") # exteract the most similar cluster most_similar[most_similar['cluster']==int(most_similar['cluster'].mode())]