|
|
# Лабораторная работа 2. «Предобработка и классификация данных социальных сетей»
|
|
|
|
|
|
[назад](README.md)
|
|
|
|
|
|
## 1. Предварительная обработка данных
|
|
|
Лабораторная работа выполняется с использованием языка программирования Python. В случае отсутствия данного программного обеспечения на компьютере необходимо перейти на официальный сайт и скачать последнюю версию Python. Далее установить Python на компьютер следуя указаниям мастера установки.
|
|
|
|
|
|
Первый этапом данной лабораторной работы является удаление всех сторонних полей с оставлением лишь полезной информации.
|
|
|
|
|
|
Для этого необходимо загрузить скаченный в предыдущей лабораторной работе файл данных из социальной сети VK и извлечь необходимое поле text.
|
|
|
|
|
|
```python
|
|
|
all_wall = []
|
|
|
file = open(r" wall_asp.txt")
|
|
|
for line in file.readlines():
|
|
|
string = line
|
|
|
wall = json.loads(string)
|
|
|
json_decode = json.JSONDecoder()
|
|
|
parsed_response = json_decode.decode(json.dumps(wall))
|
|
|
nodes = parsed_response.get('items')
|
|
|
|
|
|
for node in nodes:
|
|
|
all_wall.append(node.get("text"))
|
|
|
```
|
|
|
|
|
|
## 2. Подсчет слов в собранных текстовых данных
|
|
|
|
|
|
Задание включает в себя реализацию алгоритма WordCount. Это одна из самых простых и тривиальных задач машинного обучения. Она заключается в разбиении текста на слова, а также подсчёт их количества.
|
|
|
|
|
|
Добавим в имеющийся код следующие строки
|
|
|
|
|
|
```python
|
|
|
wordcount={}
|
|
|
|
|
|
for wall in all_wall:
|
|
|
for word in wall.split():
|
|
|
if word not in wordcount:
|
|
|
wordcount[word] = 1
|
|
|
else:
|
|
|
wordcount[word] += 1
|
|
|
try:
|
|
|
for wc in wordcount:
|
|
|
print(wc, wordcount[wc])
|
|
|
except Exception:
|
|
|
k=1
|
|
|
```
|
|
|
|
|
|
В результате перезапуска программы можно увидеть в консоли множество сочетаний вида «значение, ключ».
|
|
|
|
|
|
## 3. Кластеризация текстовых данных
|
|
|
|
|
|
Следующей задачей является кластеризация текстовых данных.
|
|
|
|
|
|
В области анализа данных широко распространена задача разделения множества объектов на подмножества таким образом, чтобы все объекты каждого подмножества имели больше сходства друг с другом, чем с объектами других подмножеств.
|
|
|
|
|
|
Данная задача находит широкое практическое применение. Например, в области медицины алгоритм кластеризации может помочь идентифицировать центры клеток на изображении группы клеток. Используя GPS-данные мобильного устройства, можно определить наиболее посещаемые пользователем места в пределах определенной территории.
|
|
|
|
|
|
В рамках текущей задачи создадим новый файл и импортируем необходимые библиотеки.
|
|
|
|
|
|
```python
|
|
|
import numpy as np
|
|
|
import pandas as pd
|
|
|
import nltk
|
|
|
import re
|
|
|
import os
|
|
|
import codecs
|
|
|
import matplotlib.pyplot as plt
|
|
|
import matplotlib as mpl
|
|
|
```
|
|
|
|
|
|
Необходимо считать данные в массив, а далее приступить к нормализации – приведению слова к начальной форме. Это можно сделать несколькими способами, используя стеммер Портера, стеммер `MyStem` и `PyMorphy2`. Стоит отметить – `MyStem` работает через wrapper, поэтому скорость выполнения операций очень медленная. Предлагается использовать стеммер Портера, хотя можно использовать другие и комбинировать их с друг другом (например, сначала пройтись `PyMorphy2`, а после стеммером Портера).
|
|
|
|
|
|
```python
|
|
|
print(str(len(all_wall)) + ' запросов считано')
|
|
|
from nltk.stem.snowball import SnowballStemmer
|
|
|
stemmer = SnowballStemmer("russian")
|
|
|
#nltk.download()
|
|
|
|
|
|
def token_and_stem(text):
|
|
|
tokens = [word for sent in nltk.sent_tokenize(text) for word in nltk.word_tokenize(sent)]
|
|
|
filtered_tokens = []
|
|
|
for token in tokens:
|
|
|
if re.search('[а-яА-Я]', token):
|
|
|
filtered_tokens.append(token)
|
|
|
stems = [stemmer.stem(t) for t in
|
|
|
filtered_tokens]
|
|
|
return stems
|
|
|
|
|
|
def token_only(text):
|
|
|
tokens = [word.lower() for sent in nltk.sent_tokenize(text) for word in nltk.word_tokenize(sent)]
|
|
|
filtered_tokens = []
|
|
|
for token in tokens:
|
|
|
if re.search('[а-яА-Я]', token):
|
|
|
filtered_tokens.append(token)
|
|
|
return filtered_tokens
|
|
|
|
|
|
#Создаем словари (массивы) из полученных основ
|
|
|
totalvocab_stem = []
|
|
|
totalvocab_token = []
|
|
|
for i in all_wall:
|
|
|
allwords_stemmed = token_and_stem(i)
|
|
|
#print(allwords_stemmed)
|
|
|
totalvocab_stem.extend(allwords_stemmed)
|
|
|
allwords_tokenized = token_only(i)
|
|
|
totalvocab_token.extend(allwords_tokenized)
|
|
|
```
|
|
|
|
|
|
Создадим матрицу весов TF-IDF. Будем считать каждый поисковой запрос за документ (так делают при анализе постов в Twitter, где каждый твит – это документ). `tfidf_vectorizer` возьмем из пакета `sklearn`, а стоп-слова выберем из корпуса `ntlk` (изначально придется скачать через `nltk.download()`). Параметры можно подстроить от верхней и нижней границы до количества `n-gram` (в данном случае возьмем 3).
|
|
|
|
|
|
```python
|
|
|
stopwords = nltk.corpus.stopwords.words('russian')
|
|
|
|
|
|
#можно расширить список стоп-слов
|
|
|
stopwords.extend(['что', 'это', 'так', 'вот',
|
|
|
'быть', 'как', 'в', 'к', 'на'])
|
|
|
|
|
|
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
|
|
|
|
|
|
n_featur=200000
|
|
|
tfidf_vectorizer = TfidfVectorizer(max_df=0.8,
|
|
|
max_features=10000,
|
|
|
stop_words=stopwords,
|
|
|
min_df=0.01,
|
|
|
use_idf=True,
|
|
|
tokenizer=token_and_stem,
|
|
|
ngram_range=(1,3)
|
|
|
)
|
|
|
tfidf_matrix = tfidf_vectorizer.fit_transform(all_wall)
|
|
|
|
|
|
print(tfidf_matrix.shape)
|
|
|
```
|
|
|
|
|
|
Над полученной матрицей начинаем применять различные методы кластеризации.
|
|
|
|
|
|
```python
|
|
|
num_clusters = 5
|
|
|
|
|
|
# Метод к-средних - KMeans
|
|
|
from sklearn.cluster import KMeans
|
|
|
km = KMeans(n_clusters=num_clusters)
|
|
|
km.fit(tfidf_matrix)
|
|
|
idx = km.fit(tfidf_matrix)
|
|
|
clusters = km.labels_.tolist()
|
|
|
print(clusters)
|
|
|
print (km.labels_)
|
|
|
|
|
|
# MiniBatchKMeans
|
|
|
from sklearn.cluster import MiniBatchKMeans
|
|
|
mbk = MiniBatchKMeans(init='random', n_clusters=num_clusters) #(init='k-means++',‘random’ or an ndarray)
|
|
|
mbk.fit_transform(tfidf_matrix)
|
|
|
mbk.fit(tfidf_matrix)
|
|
|
miniclusters = mbk.labels_.tolist()
|
|
|
print (mbk.labels_)
|
|
|
|
|
|
# DBSCAN
|
|
|
from sklearn.cluster import DBSCAN
|
|
|
db = DBSCAN(eps=0.3,
|
|
|
min_samples=10).fit(tfidf_matrix)
|
|
|
labels = db.labels_
|
|
|
labels.shape
|
|
|
print(labels)
|
|
|
|
|
|
# Аггломеративная класстеризация
|
|
|
from sklearn.cluster import AgglomerativeClustering
|
|
|
agglo1 = AgglomerativeClustering(
|
|
|
n_clusters=num_clusters,
|
|
|
affinity='euclidean') #affinity можно выбрать любое или попробовать все по очереди: cosine, l1, l2, manhattan
|
|
|
answer = agglo1.fit_predict(tfidf_matrix.toarray())
|
|
|
answer.shape
|
|
|
```
|
|
|
|
|
|
Полученные данные можно сгруппировать в `dataframe` и посчитать количество запросов, попавших в каждый кластер.
|
|
|
|
|
|
```python
|
|
|
#k-means
|
|
|
clusterkm = km.labels_.tolist()
|
|
|
#minikmeans
|
|
|
clustermbk = mbk.labels_.tolist()
|
|
|
#dbscan
|
|
|
clusters3 = labels
|
|
|
#agglo
|
|
|
#clusters4 = answer.tolist()
|
|
|
frame = pd.DataFrame(all_wall, index = [clusterkm])
|
|
|
#k-means
|
|
|
out = { 'title': all_wall, 'cluster': clusterkm }
|
|
|
frame1 = pd.DataFrame(out, index = [clusterkm],
|
|
|
columns = ['title', 'cluster'])
|
|
|
#mini
|
|
|
out = { 'title': all_wall, 'cluster': clustermbk }
|
|
|
frame_minik = pd.DataFrame(out, index =
|
|
|
[clustermbk], columns = ['title', 'cluster'])
|
|
|
frame1['cluster'].value_counts()
|
|
|
frame_minik['cluster'].value_counts()
|
|
|
```
|
|
|
|
|
|
Из-за большого количества запросов не совсем удобно смотреть таблицы и хотелось бы больше интерактивности для понимания. Поэтому сделаем графики взаимного расположения запросов друг относительного друга. Сначала необходимо вычислить расстояние между векторами. Для этого можно применить косинусное расстояние. При этом можно использовать вычитание из единицы, чтобы не было отрицательных значений и находиться в пределах от $0$ до $1$.
|
|
|
|
|
|
Так как графики будут двух- и трехмерные, а исходная матрица расстояний n-мерная, то придется применять алгоритмы снижения размерности. На выбор есть много алгоритмов (MDS, PCA, t-SNE), но остановим выбор на Incremental PCA. Этот выбор сделан вследствие выигрыша данного алгоритма в ресурсоемкости.
|
|
|
|
|
|
Алгоритм Incremental PCA используется в качестве замены метода главных компонентов (PCA), когда набор данных, подлежащий разложению, слишком велик, чтобы разместиться в оперативной памяти. IPCA создает низкоуровневое приближение для входных данных, используя объем памяти, который не зависит от количества входных выборок данных.
|
|
|
|
|
|
```python
|
|
|
from sklearn.metrics.pairwise import
|
|
|
cosine_similarity
|
|
|
dist = 1 - cosine_similarity(tfidf_matrix)
|
|
|
dist.shape
|
|
|
|
|
|
# Метод главных компонент - PCA
|
|
|
from sklearn.decomposition import IncrementalPCA
|
|
|
icpa = IncrementalPCA(n_components=2, batch_size=16)
|
|
|
icpa.fit(dist)
|
|
|
demo2 = icpa.transform(dist)
|
|
|
xs, ys = demo2[:, 0], demo2[:, 1]
|
|
|
|
|
|
# PCA 3D
|
|
|
from sklearn.decomposition import IncrementalPCA
|
|
|
icpa = IncrementalPCA(n_components=3,batch_size=16)
|
|
|
icpa.fit(dist)
|
|
|
ddd = icpa.transform(dist)
|
|
|
xs, ys, zs = ddd[:, 0], ddd[:, 1], ddd[:, 2]
|
|
|
|
|
|
#Можно сразу примерно посмотреть, что получится в итоге
|
|
|
from mpl_toolkits.mplot3d import Axes3D
|
|
|
fig = plt.figure()
|
|
|
ax = fig.add_subplot(111, projection='3d')
|
|
|
ax.scatter(xs, ys, zs)
|
|
|
ax.set_xlabel('X')
|
|
|
ax.set_ylabel('Y')
|
|
|
ax.set_zlabel('Z')
|
|
|
plt.show()
|
|
|
```
|