Оглавление:

  1. Вступление.
  2. Предпосылки.
  3. Сбор данных.
  4. Понимание данных.
  5. EDA (Исследовательский анализ данных).
  6. Feature Engineering.
  7. Построение модели.
  8. Оценка.
  9. Заключение.
  10. Использованная литература.

1. Введение

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

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

Типы графиков

В теории графов есть два типа графов.

  1. Ненаправленный (над одним).
  2. Режиссер.

2. Предпосылка

Я предполагаю, что у вас есть базовые знания о машинном обучении и библиотеках, таких как pandas, numpy, matplotlib, seaborn и sklearn.

3. Сбор данных

Для решения этой проблемы доступно множество наборов данных с открытым исходным кодом, таких как Kaggle, Аналитика Vidhya и т. Д.

Я скачал форму набора данных Kaggle. Этот конкурс проводится Facebook для приема на работу.

4. Анализ данных

Если вы загрузили данные по ссылке, которую я предоставил, вы увидите, что набор данных для поездов и набор тестовых данных даны отдельно.

  • Учебный набор - 9437519 строк, 2 столбца.
  • Набор тестов - 262588 строк, 1 столбец.

Видя форму этого набора данных, получается очень огромный набор данных.

Что мы должны предсказать?

  • Для данного узла источника и узла назначения мы должны предсказать, есть ли вероятность соединения между ними.

Как мы относим эту проблему к проблеме машинного обучения?

В нашем наборе данных есть только два столбца: source_node и destination_node.

  • Тогда вопрос в том, какими будут целевые данные (помеченные данные) и каковы данные обучения. Потому что в машинном обучении нам нужны обучающие данные (X) и целевые данные (y). А здесь у нас всего две колонки.
  • Чтобы справиться с этой проблемой, нам нужно создать новые функции. Так что это будет полезно для модели.
  • Также нам нужно создать атрибут label (0/1) в качестве целевого столбца, 0 = не подключен и 1 = подключен.
  • Когда все это сделано, как добавление функций и меток, мы можем сказать, что это преобразовано в проблему машинного обучения.

Чтение train.csv

df=pd.read_csv('train.csv')
df.head(10)

Наблюдение:

  • Здесь вы видите два столбца: исходный_узел и целевой узел.

Рассмотрим 1-ю строку, где исходный узел равен 1, а целевой узел - 690569. Итак, просмотрев данные, мы можем сказать, что наши данные представляют собой ориентированный граф.

5. EDA (исследовательский анализ данных)

df=pd.read_csv('train.csv')
df.head(10)

После загрузки train.csv мы собираемся сохранить csv без заголовка (source_node и destination_node), чтобы мы могли выполнить EDA (исследовательский анализ данных) с помощью библиотеки networkx на правильных данных.

Сохранение CSV без заголовка

df.to_csv('data/train_woheader.csv',header=False,index=False)

Теперь мы собираемся читать узлы с помощью библиотеки networkx.

g=nx.read_edgelist('data/train_woheader.csv',delimiter=',',create_using=nx.DiGraph(),nodetype=int)

Наблюдение

  • Networkx по умолчанию создает неориентированный граф. Но у нас есть данные направленного графа, поэтому мы передаем параметр «create_using = nx.Digraph ()», т.е. теперь он будет создавать ориентированный граф и «nodetype = int», поскольку наши данные содержат только числовые значения.

Печать основной информации графика.

  • Основная информация о графике
print(nx.info(g))

Наблюдение:

  • Количество узлов: 1862220. Это означает, что в нашем наборе данных есть такое количество уникальных данных о людях.
  • Кол-во ребер: 9437519. Количество ребер в наличии.

Но есть ребра, которые не связаны между собой, как мы можем их вычислить?

  • Поскольку нет. узлов составляет 18 лакхов, тогда общее количество возможных соединений / ребер / звеньев будет 18лакС2 или 18 лакх кв.
  • Теперь у нас есть 18lakhC2 ссылок, из которых около 94 Lakh фактически подключены, они представлены как положительные (+ ve метка), то есть 1.
  • Несуществующие ссылки: 18lakhC2 - 94 Lakhs. Они представлены как отрицательные (метка -ve), т.е. 0.

Создание образцов обучающего набора данных из 50 строк для визуализации:

pd.read_csv('train.csv',nrows=50).to_csv('data/train_woheader_sample.csv',header=False,index=False)
subgraph=nx.read_edgelist('data/train_woheader_sample.csv',
delimiter=',',create_using=nx.DiGraph(),nodetype=int)

Визуализация

pos=nx.spring_layout(subgraph) # spring = circle layout
nx.draw(subgraph,pos,node_color='#A0CBE2',edge_color='#00bb5e',
width=1,edge_cmap=plt.cm.Blues,with_labels=True)
plt.savefig("graph_sample.pdf")
print(nx.info(subgraph))

В networkx мы также можем визуализировать график с помощью nx.spring_layout (). Используя spring_layout, он покажет график в виде кругового маннара.

Наблюдение:

  • В графе spring_layout он представляет узел таким образом, что внутренние узлы являются исходным узлом, а внешние узлы - адресатом.

Построение графика выборки данных.

Из приведенной выше диаграммы мы видим, что узел, который следует за узлом назначения, называется «последователем», то есть узел-источник является ведомым узлом-адресатом, а узел-адресат называется «последователем» узла-источника.

Количество подписчиков и количество людей:

indegree_dist = list(dict(g.in_degree()).values())
indegree_dist.sort()
plt.figure(figsize=(10,6))
plt.plot(indegree_dist)
plt.xlabel('Index No')
plt.ylabel('No Of Followers')
plt.show()

Мы просто визуализировали выбранные данные, построив график.

Наблюдение:

  • Ось Y показывает количество последователей, а ось X - количество людей.
  • Из приведенного выше графика мы говорим, что у нас меньше людей, у которых больше подписчиков, то есть наш набор данных содержит большее количество людей, у которых есть подписчики около 20–30, что означает тех людей, которые редко активны в социальных сетях.
  • Это довольно очевидно, потому что давать данные о популярных людях очень рискованно. Это может создать для них проблемы.

Просмотр значения процентиля на графике выше:

### 90-100 percentile
for i in range(0,11):
    print(90+i,'percentile value is',np.percentile(indegree_dist,90+i))

### 99-100 percentile
for i in range(10,110,10):
    print(99+(i/100),'percentile value is',np.percentile(indegree_dist,99+(i/100)))

Наблюдение:

  • От 90 до 99 человек с меньшим количеством подписчиков (до 40).
  • С 99,1 до 99,9 количество последователей увеличивается (очень меньше людей).

Также существует вероятность, что люди ни на кого не подписываются или никого не отслеживают.

# Person who are not following to anyone
print('No of persons those are not following anyone are' ,sum(np.array(outdegree_dist)==0),'and % is',
        sum(np.array(outdegree_dist)==0)*100/len(outdegree_dist))

# Person who have not followers
print('No of persons who have no followers' ,sum(np.array(indegree_dist)==0),'and % is',
        sum(np.array(indegree_dist)==0)*100/len(indegree_dist))

Наблюдение:

  • В нашем наборе данных 14% людей, которые ни на кого не подписываются.
  • 10% людей не имеют подписчиков.
  • Приведенные выше данные указывают на то, что эти многие люди не используют или не активны в социальных сетях.

Также существует вероятность, что люди подписываются на другого человека, а этот человек подписывается на них.

  • Построение того же графика для количества последователей + последователей в зависимости от количества людей
from collections import Counter
dict_in = dict(g.in_degree())
dict_out = dict(g.out_degree())
d = Counter(dict_in) + Counter(dict_out)
in_out_degree = np.array(list(d.values()))
in_out_degree_sort = sorted(in_out_degree)
plt.figure(figsize=(10,6))
plt.plot(in_out_degree_sort)
plt.xlabel('Index No')
plt.ylabel('No Of people each person is following + followers')
plt.show()

Наблюдение:

  • Из приведенного выше графика мы говорим, что у нас меньше людей, которые следят за нами и подписываются на него в ответ.

Расчет процентиля подписчика + подписчика.

### 90-100 percentile
for i in range(0,11):
    print(90+i,'percentile value is',np.percentile(in_out_degree_sort,90+i))

### 99-100 percentile
for i in range(10,110,10):
    print(99+(i/100),'percentile value is',np.percentile(in_out_degree_sort,99+(i/100)))

Наблюдение:

  • От 90 до 99 люди, имеющие меньшее количество подписчиков + подписчиков (до 79).
  • С 99,1 до 99,9 увеличивается количество последователей + подписчиков (очень меньше людей).

Теперь мы собираемся сгенерировать недостающие ребра из заданного графа (обучающих данных).

%%time
###generating missing edges from given graph.
import random
#getting all set of edges.
r = csv.reader(open('data/train_woheader.csv','r'))
#the dict will contain a tuple of 2 nodes as key and the value will be 1 is the nodes are connected else -1.
edges = dict()
# for present edges.
for edge in r: # i.e. edge is present in train data.
	edges[(edge[0], edge[1])] = 1 # if edge is present in r then 1.

# for missing edges.
missing_edges = set([])
while (len(missing_edges)<9437519):
	a=random.randint(1, 1862220) # no. of nodes
	b=random.randint(1, 1862220) # no. of nodes
	tmp = edges.get((a,b),-1) # marked -1 for all edges which are missing.
	if tmp == -1 and a!=b: # if edge is missing and a and b are not same.
		try:
            # adding points who less likely to be friends
			if nx.shortest_path_length(g,source=a,target=b) > 2: # greater than 2 coz more dist. low prob. to become a frd. That is what we want as a data/edge to add or join.

				missing_edges.add((a,b))
			else:
				continue  
		except:  
				missing_edges.add((a,b))              
	else:
		continue

Подход:

  • Сначала мы читаем данные из train (без заголовка) .csv.
  • Затем мы создаем словарь, который содержит кортеж из двух узлов в качестве ключа и значение, которое будет либо 1, либо -1. где 1 = подключено и -1 = не подключено.

  • Проверка наличия ребра (то есть ребер в r) в обучающих данных, и если он присутствует, он пометит его как 1.
  • После того, как все существующие ребра помечены как 1, мы создаем пустой список для недостающих ребер, чтобы пропущенные ребра не повторялись. Затем мы случайным образом генерируем узлы, размер которых совпадает с количеством узлов, присутствующих в обучающих данных (1862220), и помечаем их как -1 (т.е. отсутствует).
  • После того, как мы отметили все отсутствующие значения как -1, теперь мы должны добавить узлы в набор отсутствующих ребер, для этого мы проверяем, что если ребро отсутствует и исходный узел не равен целевому узлу, означает отсутствие край присутствует, а источник-пункт назначения не является одним узлом, тогда мы должны их добавить. Но есть еще одно условие: мы добавляем те узлы в missing_edges, кратчайший путь которых больше 2.

если nx.shortest_path_length (g, source = a, target = b) ›2:

Интуиция

  • Считайте, что A, B, C, D - народы. A и B уже подключены. Отсутствующие ребра - это A-C и A-D, если A является исходным узлом.
  • Теперь вероятность того, что A соединяется с C, больше, поскольку их кратчайший путь равен 2, чем вероятность того, что A соединяется с D, у которого кратчайший путь равен 3.
  • Это означает, что A будет соединен с C в будущем, поэтому они не могут сказать, что это недостающие ребра, или нет смысла называть их отсутствующими ребрами (это мое предположение). Эти данные (высокая вероятность подключения / кратчайшего пути) не помогут моделировать для лучшего прогнозирования. Нам нужны те ребра, у которых меньше вероятность соединения (как у A-D, у них кратчайший путь 3, т.е. у них меньше вероятность соединения).
  • Вот почему мы добавляем в список те ребра, у которых кратчайший путь больше 2.

Разделение только положительных (имеющихся ребер) данных поездного теста:

#reading total data df
df_pos = pd.read_csv('train.csv')
df_neg = pd.DataFrame(list(missing_edges), columns=['source_node', 'destination_node'])

print("Number of nodes in the graph with edges", df_pos.shape[0])
print("Number of nodes in the graph without edges", df_neg.shape[0])

#Trian test split 
#Spiltted data into 80-20 
#positive links and negative links seperatly because we need positive training data only for creating graph 
#and for feature generation
X_train_pos, X_test_pos, y_train_pos, y_test_pos  = train_test_split(df_pos,np.ones(len(df_pos)),test_size=0.2, random_state=9)
X_train_neg, X_test_neg, y_train_neg, y_test_neg  = train_test_split(df_neg,np.zeros(len(df_neg)),test_size=0.2, random_state=9)

print('='*60)
print("Number of nodes in the train data graph with edges", X_train_pos.shape[0],"=",y_train_pos.shape[0])
print("Number of nodes in the train data graph without edges", X_train_neg.shape[0],"=", y_train_neg.shape[0])
print('='*60)
print("Number of nodes in the test data graph with edges", X_test_pos.shape[0],"=",y_test_pos.shape[0])
print("Number of nodes in the test data graph without edges", X_test_neg.shape[0],"=",y_test_neg.shape[0])

#removing header and saving
X_train_pos.to_csv('data/train_pos_after_eda.csv',header=False, index=False)
X_test_pos.to_csv('data/test_pos_after_eda.csv',header=False, index=False)
X_train_neg.to_csv('data/train_neg_after_eda.csv',header=False, index=False)
X_test_neg.to_csv('data/test_neg_after_eda.csv',header=False, index=False)

X_train_pos и X_test_pos:

X_train_pos.head()

  • Он содержит все данные о существующих ребрах.
X_test_pos.head()

y_train_pos & y_test_pos:

y_train_pos

y_test_pos

  • Он указывает на положительные данные (есть ребра (1) или нет (-1)).
  • Здесь мы рассматриваем только положительные данные, поэтому у нас есть массив всех единиц.

Что такое проблема холодного запуска?

  • Если узлы присутствуют в данных поезда, но не в тестовых данных, или наоборот.
  • Из диаграммы мы видим, что узел 2 отсутствует в обучающих данных, но присутствует в тестовых данных.
  • Точно так же узлы 4, 6 присутствуют в тестовых данных, но отсутствуют в тренировочных данных.

До сих пор у нас есть исходный узел, целевой узел и атрибут target / label (0: край отсутствует в нашем наборе данных, а 1: край присутствует в нашем наборе данных).

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

6. Разработка функций.

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

Мы собираемся создать следующие функции:

  1. Расстояние Жаккара.
  2. Косинусное расстояние.
  3. Рейтинг страницы.
  4. Кратчайший путь.
  5. Слабосвязный компонент (WCC).
  6. Индекс Адара.
  7. Следует назад.
  8. Кац.
  9. HITS (поиск тем по гиперссылкам).

1. Расстояние Жаккара

Коэффициент сходства Жаккара - это статистика, используемая для измерения сходства и разнообразия наборов выборок.

Жаккар Дист. для подписчиков

  • Узлы, следующие за X, или мы можем сказать, что наследники X.
  • В наборе «A» рассмотрим все входящие ребра X, т. Е. Последователи X. A = {A, D}
  • В наборе «B» учитывайте все входящие ребра E. B = {B}

Жаккар Дист. для подписчиков

  • В наборе «A» учтите все исходящие ребра X. A = {C, D}
  • Узлы, за которыми следует X, т.е. предшественник X.
  • В наборе «B» учтите все исходящие ребра E. B = {B}

Примечание.

  • Расстояние Жаккара обычно используется для нормализации характеристик.
  • Если расстояние Жаккара больше, то вероятность соединения двух человек больше.

2. Косинусное расстояние

Косинусное сходство - это мера сходства между двумя ненулевыми векторами внутреннего пространства продукта, измеряющая косинус угла между ними. Косинус 0 ° равен 1, и он меньше 1 для любого угла в интервале 0, π] радиан.

  • Если косинусный угол между исходным узлом и целевым узлом равен 0 ° или близок к нему, то вероятность соединения этих узлов высока.

3. Рейтинг страницы.

PageRank вычисляет рейтинг узлов в графе G на основе структуры входящих ссылок. Первоначально он был разработан как алгоритм ранжирования веб-страниц.

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

4. Кратчайший путь.

Вычислите длину кратчайшего пути на графике.

  • Сначала он проверит, есть ли какое-либо прямое ребро между исходным узлом и целевым узлом, если оно есть, то удалит это ребро, потому что прямой граничный узел имеет кратчайший путь 1, который бесполезен. Вот почему мы удаляем прямое ребро, а затем вычисляем кратчайший путь между исходным узлом и целевым узлом.

5. Слабосвязный компонент (WCC).

  • Чтобы понять слабосвязанные компоненты, сначала мы должны понять, что такое сильносвязанные компоненты.

Сильно связанные компоненты: -

  • В компонентах сильной связности, если каждая вершина (узел) достижима из любой другой вершины (узла), то мы говорим, что это компонент сильной связности. Это сформирует цикл в своем регионе.
  • Рассмотрим регион 1, поскольку мы видим, что «узел а» может посещать другой узел в своем регионе (регион 1). Цикл имеет форму (узел a-узел b-узел e). Аналогично, в области 2 и области 3 каждый узел может посещать другой узел в своих регионах.

Слабосвязанные компоненты: -

  • В слабосвязных компонентах узел не может посещать каждый узел, т. Е. Нет образования цикла.
  • Здесь «узел A» не может посещать «узел D» или «узел C».
  • Сначала мы проверяем, что это Сильно связанный компонент. Если это не Сильносвязный компонент, мы удаляем направленные ребра и смотрим, есть ли еще хотя бы один путь, тогда мы говорим, что это слабосвязные компоненты.

6. Индекс Адара.

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

  • Если N (u) мало, то два узла очень близки друг к другу. Но мы хотим, чтобы два узла находились подальше друг от друга.

7. Следуй назад

В Follow Back, если исходный узел следует за целевым узлом, а целевой узел также следует за исходным узлом, этот узел мы сохраняем в функции Follow Back.

8. Кац

Он используется для измерения относительной степени влияния субъекта (или узла) в социальной сети. В отличие от типичных мер центральности, которые рассматривают только кратчайший путь (геодезическую) между парой акторов, центральность Каца измеряет влияние, принимая во внимание общее количество проходов между парой акторов. Он похож на рейтинг страницы Google и центральность собственного вектора.

9. HITS (поиск тем по гиперссылкам)

Он также известен как центры и органы . Это алгоритм анализа ссылок, который оценивает веб-страницы. Хороший центр представляет страницу, которая указывает на множество других страниц, а хороший авторитет представляет страницу, на которую ссылается множество различных узлов.

Выборка данных обучения и тестирования: -

# Sampling training 
filename = "data/train_after_eda.csv"
n_train = sum(1 for line in open(filename)) #number of records in file (excludes header)
s = 100000 #desired sample size
skip_train = sorted(random.sample(range(1,n_train+1),n_train-s))
# Sampling testing data
filename = "data/test_after_eda.csv"
n_test = sum(1 for line in open(filename)) #number of records in file (excludes header)
s = 50000 #desired sample size
skip_test = sorted(random.sample(range(1,n_test+1),n_test-s))
  • Мы производим выборку данных поездов в 100 000 точек и тестовых данных в 50 000 точек.
  • И мы сохраняем данные, которые не были выбраны в skip_train и skip_test.

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

  1. Применение функций Жаккара и косинуса.
start_time = time()
#mapping jaccard followers to train and test data
df_final_train['jaccard_followers'] = df_final_train.apply(lambda row:jaccard_for_followers(row['source_node'],row['destination_node']),axis=1)
df_final_test['jaccard_followers'] = df_final_test.apply(lambda row:										jaccard_for_followers(row['source_node'],row['destination_node']),axis=1)

#mapping jaccard followees to train and test data df_final_train['jaccard_followees'] = df_final_train.apply(lambda row:jaccard_for_followees(row['source_node'],row['destination_node']),axis=1)
df_final_test['jaccard_followees'] = df_final_test.apply(lambda row:jaccard_for_followees(row['source_node'],row['destination_node']),axis=1)

#mapping cosine followers to train and test data
df_final_train['cosine_followers'] = df_final_train.apply(lambda row:cosine_for_followers(row['source_node'],row['destination_node']),axis=1)
df_final_test['cosine_followers'] = df_final_test.apply(lambda row:
cosine_for_followers(row['source_node'],row['destination_node']),axis=1)

#mapping cosine followees to train and test data
df_final_train['cosine_followees'] = df_final_train.apply(lambda row:cosine_for_followees(row['source_node'],row['destination_node']),axis=1)
df_final_test['cosine_followees'] = df_final_test.apply(lambda row:cosine_for_followees(row['source_node'],row['destination_node']),axis=1)
print("--- %s seconds ---" % (time() - start_time))

2. Расчет количества подписчиков и подписчиков, а также вычисление пересечения между ними.

def compute_features_stage1(df_final):
    #calculating no of followers followees for source and destination
    #calculating intersection of followers and followees for source and destination
    num_followers_s=[]
    num_followees_s=[]
    num_followers_d=[]
    num_followees_d=[]
    inter_followers=[]
    inter_followees=[]
    for i,row in df_final.iterrows():
        try:
            s1=set(train_graph.predecessors(row['source_node']))
            s2=set(train_graph.successors(row['source_node']))
        except:
            s1 = set()
            s2 = set()
        try:
            d1=set(train_graph.predecessors(row['destination_node']))
            d2=set(train_graph.successors(row['destination_node']))
        except:
            d1 = set()
            d2 = set()
        num_followers_s.append(len(s1))
        num_followees_s.append(len(s2))

        num_followers_d.append(len(d1))
        num_followees_d.append(len(d2))

        inter_followers.append(len(s1.intersection(d1)))
        inter_followees.append(len(s2.intersection(d2)))
    
    return num_followers_s, num_followers_d, num_followees_s, num_followees_d, inter_followers, inter_followees
Applying on train data
df_final_train['num_followers_s'],df_final_train['num_followers_d'],df_final_train['num_followees_s'],df_final_train['num_followees_d'],df_final_train['inter_followers'],df_final_train['inter_followees']= compute_features_stage1(df_final_train)
Applying on test data
df_final_test['num_followers_s'],df_final_test['num_followers_d'],df_final_test['num_followees_s'],df_final_test['num_followees_d'],df_final_test['inter_followers'],df_final_test['inter_followees']= compute_features_stage1(df_final_test)
#Storing in h5 file
hdf = HDFStore('data/fea_sample/storage_sample_stage1.h5')
hdf.put('train_df',df_final_train, format='table', data_columns=True)
hdf.put('test_df',df_final_test, format='table', data_columns=True)
hdf.close()

3 . Применение индекса Adar, wcc (слабосвязные компоненты), кратчайшего пути и обратного пути

#mapping adar index on train
df_final_train['adar_index'] = df_final_train.apply(lambda row: calc_adar_in(row['source_node'],row['destination_node']),axis=1)
#mapping adar index on test
df_final_test['adar_index'] = df_final_test.apply(lambda row: calc_adar_in(row['source_node'],row['destination_node']),axis=1)

#--------------------------------------------------------------------------------------------------------
#mapping followback or not on train
df_final_train['follows_back'] = df_final_train.apply(lambda row: follows_back(row['source_node'],row['destination_node']),axis=1)

#mapping followback or not on test
df_final_test['follows_back'] = df_final_test.apply(lambda row: follows_back(row['source_node'],row['destination_node']),axis=1)

#--------------------------------------------------------------------------------------------------------
#mapping same component of wcc or not on train
df_final_train['same_comp'] = df_final_train.apply(lambda row: belongs_to_same_wcc(row['source_node'],row['destination_node']),axis=1)

##mapping same component of wcc or not on train
df_final_test['same_comp'] = df_final_test.apply(lambda row: belongs_to_same_wcc(row['source_node'],row['destination_node']),axis=1)

#--------------------------------------------------------------------------------------------------------
#mapping shortest path on train 
df_final_train['shortest_path'] = df_final_train.apply(lambda row: compute_shortest_path_length(row['source_node'],row['destination_node']),axis=1)
#mapping shortest path on test
df_final_test['shortest_path'] = df_final_test.apply(lambda row: compute_shortest_path_length(row['source_node'],row['destination_node']),axis=1)
# Storing file in h5 format
hdf = HDFStore('data/fea_sample/storage_sample_stage2.h5')
hdf.put('train_df',df_final_train, format='table', data_columns=True)
hdf.put('test_df',df_final_test, format='table', data_columns=True)
hdf.close()

4. Применение рейтинга страниц, Каца и Хита.

start_time = time()
#page rank for source and destination in Train and Test
#if anything not there in train graph then adding mean page rank 
df_final_train['page_rank_s'] = df_final_train.source_node.apply(lambda x:pr.get(x,mean_pr))
df_final_train['page_rank_d'] = df_final_train.destination_node.apply(lambda x:pr.get(x,mean_pr))

df_final_test['page_rank_s'] = df_final_test.source_node.apply(lambda x:pr.get(x,mean_pr))
df_final_test['page_rank_d'] = df_final_test.destination_node.apply(lambda x:pr.get(x,mean_pr))
#================================================================================

#Katz centrality score for source and destination in Train and test
#if anything not there in train graph then adding mean katz score
df_final_train['katz_s'] = df_final_train.source_node.apply(lambda x: katz.get(x,mean_katz))
df_final_train['katz_d'] = df_final_train.destination_node.apply(lambda x: katz.get(x,mean_katz))

df_final_test['katz_s'] = df_final_test.source_node.apply(lambda x: katz.get(x,mean_katz))
df_final_test['katz_d'] = df_final_test.destination_node.apply(lambda x: katz.get(x,mean_katz))
#================================================================================

#Hits algorithm score for source and destination in Train and test
#if anything not there in train graph then adding 0
df_final_train['hubs_s'] = df_final_train.source_node.apply(lambda x: hits[0].get(x,0))
df_final_train['hubs_d'] = df_final_train.destination_node.apply(lambda x: hits[0].get(x,0))

df_final_test['hubs_s'] = df_final_test.source_node.apply(lambda x: hits[0].get(x,0))
df_final_test['hubs_d'] = df_final_test.destination_node.apply(lambda x: hits[0].get(x,0))
#================================================================================

#Hits algorithm score for source and destination in Train and Test
#if anything not there in train graph then adding 0
df_final_train['authorities_s'] = df_final_train.source_node.apply(lambda x: hits[1].get(x,0))
df_final_train['authorities_d'] = df_final_train.destination_node.apply(lambda x: hits[1].get(x,0))

df_final_test['authorities_s'] = df_final_test.source_node.apply(lambda x: hits[1].get(x,0))
df_final_test['authorities_d'] = df_final_test.destination_node.apply(lambda x: hits[1].get(x,0))
#================================================================================
# Storing file in h5 format
hdf = HDFStore('data/fea_sample/storage_sample_stage3.h5')
hdf.put('train_df',df_final_train, format='table', data_columns=True)
hdf.put('test_df',df_final_test, format='table', data_columns=True)
hdf.close()
print("--- %s seconds ---" % (time() - start_time))

7. Построение модели

Загрузка файла .h5, который мы сохранили после Благодаря разработке

#Loading training data
df_final_train = read_hdf('data/fea_sample/storage_sample_stage3.h5', 'train_df',mode='r')
#Loading testing data
df_final_test = read_hdf('data/fea_sample/storage_sample_stage3.h5', 'test_df',mode='r')
#seeing the columns labels of training data
df_final_train.columns

Удаление «source_node», «destination_node» и целевой переменной («indicator_link») из данных обучения и тестирования

#
df_final_train.drop(['source_node', 'destination_node','indicator_link'],axis=1,inplace=True)
df_final_test.drop(['source_node', 'destination_node','indicator_link'],axis=1,inplace=True)

Разделение целевой переменной на набор для обучения и тестирования

#Train data
y_train = df_final_train.indicator_link
#Test data
y_test = df_final_test.indicator_link

Модель: классификация XGBoost

#Building Model
#Finding the best parameter
tuned_params = {'max_depth': [1, 2, 3, 4, 5], 'learning_rate': [0.01, 0.05, 0.1], 'n_estimators': [100, 200, 300, 400, 500], 'reg_lambda': [0.001, 0.1, 1.0, 10.0, 100.0]}
model = RandomizedSearchCV(XGBClassifier(), tuned_params, n_iter=15, scoring = 'roc_auc', n_jobs=-1)
model.fit(df_final_train,y_train) # actual data and actual 
prediction
#Printing the best parameter
model.best_estimator_

Построение модели с использованием наилучшего параметра:

#Building Model using best parameter
clf = GridSearchCV(XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,colsample_bytree=1, gamma=0, learning_rate=0.05, max_delta_step=0,max_depth=4, min_child_weight=1, missing=None, n_estimators=500,n_jobs=1, nthread=None,objective='binary:logistic', random_state=0,reg_alpha=0, reg_lambda=1.0, scale_pos_weight=1, seed=None,silent=True, subsample=1), tuned_params, scoring = 'roc_auc', n_jobs=-1)

clf.fit(df_final_train,y_train) # actual data and actual prediction

Прогнозирование поездов и тестовых данных

y_train_pred = clf.predict(df_final_train)
y_test_pred = clf.predict(df_final_test)

8. Оценка

Оценка F1

Оценка F1 - это средневзвешенная точность и отзывчивость. Таким образом, эта оценка учитывает как ложноположительные, так и ложноотрицательные результаты.

from sklearn.metrics import f1_score
#f1 score of Train data
print('Train f1 score',f1_score(y_train,y_train_pred))
#f1 score of test data
print('Test f1 score',f1_score(y_test,y_test_pred))

Матрица неточностей

Матрица неточностей - это таблица, которая часто используется для описания эффективности модели классификации (или «классификатора») на наборе тестовых данных, для которых известны истинные значения.

confusion_matrix(y_test, y_test_pred).T

Наблюдение:

  • Мы видим, что наша модель классифицирует 24710 положительных точек как истинно положительные, а 21852 отрицательных - как истинно отрицательные.
  • Некоторые точки неправильно классифицируются нашей моделью, и это нормально, потому что, если наша модель классифицирует все точки как истинно положительные и истинно отрицательные, это означает, что наша модель переоборудована.
  • Итак, здесь 362 положительных точки классифицируются нашей моделью как отрицательные точки, а 3077 отрицательных точек классифицируются как положительные точки.
  • Обычно точки TN (истинно отрицательные) и TP (истинно положительные) следует классифицировать в большом количестве.

Необычная визуализация матрицы путаницы, включая матрицу точности и матрицу отзыва данных обучения и тестирования.

from sklearn.metrics import confusion_matrix
def plot_confusion_matrix(test_y, predict_y):
    C = confusion_matrix(test_y, predict_y)
    
    A =(((C.T)/(C.sum(axis=1))).T)
    
    B =(C/C.sum(axis=0))
    plt.figure(figsize=(20,4))
    
    labels = [0,1]
    # representing A in heatmap format
    cmap=sns.light_palette("blue")
    plt.subplot(1, 3, 1)
    sns.heatmap(C, annot=True, cmap=cmap, fmt=".3f", xticklabels=labels, yticklabels=labels)
    plt.xlabel('Predicted Class')
    plt.ylabel('Original Class')
    plt.title("Confusion matrix")
    
    plt.subplot(1, 3, 2)
    sns.heatmap(B, annot=True, cmap=cmap, fmt=".3f", xticklabels=labels, yticklabels=labels)
    plt.xlabel('Predicted Class')
    plt.ylabel('Original Class')
    plt.title("Precision matrix")
    
    plt.subplot(1, 3, 3)
    # representing B in heatmap format
    sns.heatmap(A, annot=True, cmap=cmap, fmt=".3f", xticklabels=labels, yticklabels=labels)
    plt.xlabel('Predicted Class')
    plt.ylabel('Original Class')
    plt.title("Recall matrix")
    
    plt.show()
print('Train confusion_matrix')
plot_confusion_matrix(y_train,y_train_pred)
print('Test confusion_matrix')
plot_confusion_matrix(y_test,y_test_pred)

Построение кривой ROC-AUC:

Он говорит о том, насколько модель способна различать классы. Чем выше AUC, тем лучше модель предсказывает 0 как 0 и 1 как 1.

Кривая ROC строится с отношением TPR (истинно положительной скорости) к FPR (ложноположительной скорости), где TPR находится на оси y, а FPR - на оси x.

from sklearn.metrics import roc_curve, auc
fpr,tpr,ths = roc_curve(y_test,y_test_pred)
auc_sc = auc(fpr, tpr)
plt.plot(fpr, tpr, color='navy',label='ROC curve (area = %0.2f)' % auc_sc)
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic with test data')
plt.legend()
plt.show()

Наблюдение:

  • Поскольку кривая ROC составляет 0,93, это означает, что существует 93% вероятность того, что модель сможет различать положительный класс и отрицательный класс.

Точность обучающих данных:

#By passing actual target data and target predicted data
acc=accuracy_score(y_train,y_train_pred,normalize=True)*float(100)
print(acc)

Точность данных тестирования:

#By passing actual target data and target predicted data
acc = accuracy_score(y_test,y_test_pred,normalize=True)*float(100)
print(acc)

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

8. Заключение

Большое спасибо за чтение этого блога. Надеюсь, вы получили некоторые базовые знания о том, как предложения друзей в социальных сетях, таких как facebook и instagram, работают с использованием машинного обучения. Помните, что это не единственный способ решить проблемы такого типа. Есть много способов.

Если вам было интересно, не забудьте похлопать в ладоши, поделиться и записать свои мысли ниже.

Благодарности:

Хочу поблагодарить тех, кто мне помог.

  1. Харшалл Ламба, ИТ-аналитик Tata Consultancy Services.

2. Дхирадж Амин, доцент инженерного колледжа Пиллай, Нью-Панвел.

9. Ссылки

PS: Для полной реализации обратитесь к моему репозиторию GitHub здесь