|
|
@@ -0,0 +1,265 @@ |
|
|
|
#!/usr/bin/env python3 |
|
|
|
# -*- coding: utf-8 -*- |
|
|
|
""" |
|
|
|
Created on Fri Apr 10 18:33:13 2020 |
|
|
|
|
|
|
|
@author: ljia |
|
|
|
|
|
|
|
@references: |
|
|
|
|
|
|
|
[1] Liva Ralaivola, Sanjay J Swamidass, Hiroto Saigo, and Pierre |
|
|
|
Baldi. Graph kernels for chemical informatics. Neural networks, |
|
|
|
18(8):1093–1110, 2005. |
|
|
|
""" |
|
|
|
import sys |
|
|
|
from itertools import product |
|
|
|
# from functools import partial |
|
|
|
from multiprocessing import Pool |
|
|
|
from tqdm import tqdm |
|
|
|
import numpy as np |
|
|
|
from gklearn.utils.parallel import parallel_gm, parallel_me |
|
|
|
from gklearn.utils.utils import getSPGraph |
|
|
|
from gklearn.kernels import GraphKernel |
|
|
|
|
|
|
|
|
|
|
|
class PathUpToH(GraphKernel): |
|
|
|
|
|
|
|
def __init__(self, **kwargs): |
|
|
|
GraphKernel.__init__(self) |
|
|
|
self.__node_labels = kwargs.get('node_labels', []) |
|
|
|
self.__edge_labels = kwargs.get('edge_labels', []) |
|
|
|
self.__depth = int(kwargs.get('depth', 10)) |
|
|
|
self.__k_func = kwargs.get('k_func', 'MinMax') |
|
|
|
self.__compute_method = kwargs.get('compute_method', 'trie') |
|
|
|
self.__ds_infos = kwargs.get('ds_infos', {}) |
|
|
|
|
|
|
|
|
|
|
|
def _compute_gm_series(self): |
|
|
|
# get shortest path graph of each graph. |
|
|
|
if self._verbose >= 2: |
|
|
|
iterator = tqdm(self._graphs, desc='getting sp graphs', file=sys.stdout) |
|
|
|
else: |
|
|
|
iterator = self._graphs |
|
|
|
self._graphs = [getSPGraph(g, edge_weight=self.__edge_weight) for g in iterator] |
|
|
|
|
|
|
|
# compute Gram matrix. |
|
|
|
gram_matrix = np.zeros((len(self._graphs), len(self._graphs))) |
|
|
|
|
|
|
|
from itertools import combinations_with_replacement |
|
|
|
itr = combinations_with_replacement(range(0, len(self._graphs)), 2) |
|
|
|
if self._verbose >= 2: |
|
|
|
iterator = tqdm(itr, desc='calculating kernels', file=sys.stdout) |
|
|
|
else: |
|
|
|
iterator = itr |
|
|
|
for i, j in iterator: |
|
|
|
kernel = self.__sp_do_(self._graphs[i], self._graphs[j]) |
|
|
|
gram_matrix[i][j] = kernel |
|
|
|
gram_matrix[j][i] = kernel |
|
|
|
|
|
|
|
return gram_matrix |
|
|
|
|
|
|
|
|
|
|
|
def _compute_gm_imap_unordered(self): |
|
|
|
# get shortest path graph of each graph. |
|
|
|
pool = Pool(self._n_jobs) |
|
|
|
get_sp_graphs_fun = self._wrapper_get_sp_graphs |
|
|
|
itr = zip(self._graphs, range(0, len(self._graphs))) |
|
|
|
if len(self._graphs) < 100 * self._n_jobs: |
|
|
|
chunksize = int(len(self._graphs) / self._n_jobs) + 1 |
|
|
|
else: |
|
|
|
chunksize = 100 |
|
|
|
if self._verbose >= 2: |
|
|
|
iterator = tqdm(pool.imap_unordered(get_sp_graphs_fun, itr, chunksize), |
|
|
|
desc='getting sp graphs', file=sys.stdout) |
|
|
|
else: |
|
|
|
iterator = pool.imap_unordered(get_sp_graphs_fun, itr, chunksize) |
|
|
|
for i, g in iterator: |
|
|
|
self._graphs[i] = g |
|
|
|
pool.close() |
|
|
|
pool.join() |
|
|
|
|
|
|
|
# compute Gram matrix. |
|
|
|
gram_matrix = np.zeros((len(self._graphs), len(self._graphs))) |
|
|
|
|
|
|
|
def init_worker(gs_toshare): |
|
|
|
global G_gs |
|
|
|
G_gs = gs_toshare |
|
|
|
do_fun = self._wrapper_sp_do |
|
|
|
parallel_gm(do_fun, gram_matrix, self._graphs, init_worker=init_worker, |
|
|
|
glbv=(self._graphs,), n_jobs=self._n_jobs, verbose=self._verbose) |
|
|
|
|
|
|
|
return gram_matrix |
|
|
|
|
|
|
|
|
|
|
|
def _compute_kernel_list_series(self, g1, g_list): |
|
|
|
# get shortest path graphs of g1 and each graph in g_list. |
|
|
|
g1 = getSPGraph(g1, edge_weight=self.__edge_weight) |
|
|
|
if self._verbose >= 2: |
|
|
|
iterator = tqdm(g_list, desc='getting sp graphs', file=sys.stdout) |
|
|
|
else: |
|
|
|
iterator = g_list |
|
|
|
g_list = [getSPGraph(g, edge_weight=self.__edge_weight) for g in iterator] |
|
|
|
|
|
|
|
# compute kernel list. |
|
|
|
kernel_list = [None] * len(g_list) |
|
|
|
if self._verbose >= 2: |
|
|
|
iterator = tqdm(range(len(g_list)), desc='calculating kernels', file=sys.stdout) |
|
|
|
else: |
|
|
|
iterator = range(len(g_list)) |
|
|
|
for i in iterator: |
|
|
|
kernel = self.__sp_do(g1, g_list[i]) |
|
|
|
kernel_list[i] = kernel |
|
|
|
|
|
|
|
return kernel_list |
|
|
|
|
|
|
|
|
|
|
|
def _compute_kernel_list_imap_unordered(self, g1, g_list): |
|
|
|
# get shortest path graphs of g1 and each graph in g_list. |
|
|
|
g1 = getSPGraph(g1, edge_weight=self.__edge_weight) |
|
|
|
pool = Pool(self._n_jobs) |
|
|
|
get_sp_graphs_fun = self._wrapper_get_sp_graphs |
|
|
|
itr = zip(g_list, range(0, len(g_list))) |
|
|
|
if len(g_list) < 100 * self._n_jobs: |
|
|
|
chunksize = int(len(g_list) / self._n_jobs) + 1 |
|
|
|
else: |
|
|
|
chunksize = 100 |
|
|
|
if self._verbose >= 2: |
|
|
|
iterator = tqdm(pool.imap_unordered(get_sp_graphs_fun, itr, chunksize), |
|
|
|
desc='getting sp graphs', file=sys.stdout) |
|
|
|
else: |
|
|
|
iterator = pool.imap_unordered(get_sp_graphs_fun, itr, chunksize) |
|
|
|
for i, g in iterator: |
|
|
|
g_list[i] = g |
|
|
|
pool.close() |
|
|
|
pool.join() |
|
|
|
|
|
|
|
# compute Gram matrix. |
|
|
|
kernel_list = [None] * len(g_list) |
|
|
|
|
|
|
|
def init_worker(g1_toshare, gl_toshare): |
|
|
|
global G_g1, G_gl |
|
|
|
G_g1 = g1_toshare |
|
|
|
G_gl = gl_toshare |
|
|
|
do_fun = self._wrapper_kernel_list_do |
|
|
|
def func_assign(result, var_to_assign): |
|
|
|
var_to_assign[result[0]] = result[1] |
|
|
|
itr = range(len(g_list)) |
|
|
|
len_itr = len(g_list) |
|
|
|
parallel_me(do_fun, func_assign, kernel_list, itr, len_itr=len_itr, |
|
|
|
init_worker=init_worker, glbv=(g1, g_list), method='imap_unordered', n_jobs=self._n_jobs, itr_desc='calculating kernels', verbose=self._verbose) |
|
|
|
|
|
|
|
return kernel_list |
|
|
|
|
|
|
|
|
|
|
|
def _wrapper_kernel_list_do(self, itr): |
|
|
|
return itr, self.__sp_do(G_g1, G_gl[itr]) |
|
|
|
|
|
|
|
|
|
|
|
def _compute_single_kernel_series(self, g1, g2): |
|
|
|
g1 = getSPGraph(g1, edge_weight=self.__edge_weight) |
|
|
|
g2 = getSPGraph(g2, edge_weight=self.__edge_weight) |
|
|
|
kernel = self.__sp_do(g1, g2) |
|
|
|
return kernel |
|
|
|
|
|
|
|
|
|
|
|
def _wrapper_get_sp_graphs(self, itr_item): |
|
|
|
g = itr_item[0] |
|
|
|
i = itr_item[1] |
|
|
|
return i, getSPGraph(g, edge_weight=self.__edge_weight) |
|
|
|
|
|
|
|
|
|
|
|
def __sp_do(self, g1, g2): |
|
|
|
|
|
|
|
kernel = 0 |
|
|
|
|
|
|
|
# compute shortest path matrices first, method borrowed from FCSP. |
|
|
|
vk_dict = {} # shortest path matrices dict |
|
|
|
if len(self.__node_labels) > 0: |
|
|
|
# node symb and non-synb labeled |
|
|
|
if len(self.__node_attrs) > 0: |
|
|
|
kn = self.__node_kernels['mix'] |
|
|
|
for n1, n2 in product( |
|
|
|
g1.nodes(data=True), g2.nodes(data=True)): |
|
|
|
n1_labels = [n1[1][nl] for nl in self.__node_labels] |
|
|
|
n2_labels = [n2[1][nl] for nl in self.__node_labels] |
|
|
|
n1_attrs = [n1[1][na] for na in self.__node_attrs] |
|
|
|
n2_attrs = [n2[1][na] for na in self.__node_attrs] |
|
|
|
vk_dict[(n1[0], n2[0])] = kn(n1_labels, n2_labels, n1_attrs, n2_attrs) |
|
|
|
# node symb labeled |
|
|
|
else: |
|
|
|
kn = self.__node_kernels['symb'] |
|
|
|
for n1 in g1.nodes(data=True): |
|
|
|
for n2 in g2.nodes(data=True): |
|
|
|
n1_labels = [n1[1][nl] for nl in self.__node_labels] |
|
|
|
n2_labels = [n2[1][nl] for nl in self.__node_labels] |
|
|
|
vk_dict[(n1[0], n2[0])] = kn(n1_labels, n2_labels) |
|
|
|
else: |
|
|
|
# node non-synb labeled |
|
|
|
if len(self.__node_attrs) > 0: |
|
|
|
kn = self.__node_kernels['nsymb'] |
|
|
|
for n1 in g1.nodes(data=True): |
|
|
|
for n2 in g2.nodes(data=True): |
|
|
|
n1_attrs = [n1[1][na] for na in self.__node_attrs] |
|
|
|
n2_attrs = [n2[1][na] for na in self.__node_attrs] |
|
|
|
vk_dict[(n1[0], n2[0])] = kn(n1_attrs, n2_attrs) |
|
|
|
# node unlabeled |
|
|
|
else: |
|
|
|
for e1, e2 in product( |
|
|
|
g1.edges(data=True), g2.edges(data=True)): |
|
|
|
if e1[2]['cost'] == e2[2]['cost']: |
|
|
|
kernel += 1 |
|
|
|
return kernel |
|
|
|
|
|
|
|
# compute graph kernels |
|
|
|
if self.__ds_infos['directed']: |
|
|
|
for e1, e2 in product(g1.edges(data=True), g2.edges(data=True)): |
|
|
|
if e1[2]['cost'] == e2[2]['cost']: |
|
|
|
nk11, nk22 = vk_dict[(e1[0], e2[0])], vk_dict[(e1[1], e2[1])] |
|
|
|
kn1 = nk11 * nk22 |
|
|
|
kernel += kn1 |
|
|
|
else: |
|
|
|
for e1, e2 in product(g1.edges(data=True), g2.edges(data=True)): |
|
|
|
if e1[2]['cost'] == e2[2]['cost']: |
|
|
|
# each edge walk is counted twice, starting from both its extreme nodes. |
|
|
|
nk11, nk12, nk21, nk22 = vk_dict[(e1[0], e2[0])], vk_dict[( |
|
|
|
e1[0], e2[1])], vk_dict[(e1[1], e2[0])], vk_dict[(e1[1], e2[1])] |
|
|
|
kn1 = nk11 * nk22 |
|
|
|
kn2 = nk12 * nk21 |
|
|
|
kernel += kn1 + kn2 |
|
|
|
|
|
|
|
# # ---- exact implementation of the Fast Computation of Shortest Path Kernel (FCSP), reference [2], sadly it is slower than the current implementation |
|
|
|
# # compute vertex kernels |
|
|
|
# try: |
|
|
|
# vk_mat = np.zeros((nx.number_of_nodes(g1), |
|
|
|
# nx.number_of_nodes(g2))) |
|
|
|
# g1nl = enumerate(g1.nodes(data=True)) |
|
|
|
# g2nl = enumerate(g2.nodes(data=True)) |
|
|
|
# for i1, n1 in g1nl: |
|
|
|
# for i2, n2 in g2nl: |
|
|
|
# vk_mat[i1][i2] = kn( |
|
|
|
# n1[1][node_label], n2[1][node_label], |
|
|
|
# [n1[1]['attributes']], [n2[1]['attributes']]) |
|
|
|
|
|
|
|
# range1 = range(0, len(edge_w_g[i])) |
|
|
|
# range2 = range(0, len(edge_w_g[j])) |
|
|
|
# for i1 in range1: |
|
|
|
# x1 = edge_x_g[i][i1] |
|
|
|
# y1 = edge_y_g[i][i1] |
|
|
|
# w1 = edge_w_g[i][i1] |
|
|
|
# for i2 in range2: |
|
|
|
# x2 = edge_x_g[j][i2] |
|
|
|
# y2 = edge_y_g[j][i2] |
|
|
|
# w2 = edge_w_g[j][i2] |
|
|
|
# ke = (w1 == w2) |
|
|
|
# if ke > 0: |
|
|
|
# kn1 = vk_mat[x1][x2] * vk_mat[y1][y2] |
|
|
|
# kn2 = vk_mat[x1][y2] * vk_mat[y1][x2] |
|
|
|
# kernel += kn1 + kn2 |
|
|
|
|
|
|
|
return kernel |
|
|
|
|
|
|
|
|
|
|
|
def _wrapper_sp_do(self, itr): |
|
|
|
i = itr[0] |
|
|
|
j = itr[1] |
|
|
|
return i, j, self.__sp_do(G_gs[i], G_gs[j]) |