|
|
# Лабораторная работа 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()
|
|
|
```
|
|
|
|
|
|
Из-за большого количества запросов не совсем удобно смотреть таблицы и хотелось бы больше интерактивности для понимания. Поэтому сделаем графики взаимного расположения запросов друг относительного друга. Сначала необходимо вычислить расстояние между векторами. Для этого можно применить косинусное расстояние.
|
|
|
|
|
|
Так как графики будут двух- и трехмерные, а исходная матрица расстояний n-мерная, то придется применять алгоритмы снижения размерности. На выбор есть много алгоритмов (MDS, PCA, t-SNE), но остановим выбор на Incremental PCA. Этот выбор сделан вследствие выигрыша данного алгоритма в ресурсоемкости.
|
|
|
|
|
|
Алгоритм Incremental PCA используется в качестве замены метода главных компонентов (PCA), когда набор данных, подлежащий разложению, слишком велик, чтобы разместиться в оперативной памяти. IPCA создает низкоуровневое приближение для входных данных, используя объем памяти, который не зависит от количества входных выборок данных.
|
|
|
|
|
|
```python
|
|
|
from sklearn.metrics.pairwise import cosine_similarity
|
|
|
dist = 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()
|
|
|
```
|