# Лабораторная работа 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()
```
