diff --git a/.gitignore b/.gitignore index 79e102c..8bdddb2 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,9 @@ notebooks/unfinished gklearn/kernels/else/ gklearn/kernels/unfinished/ gklearn/kernels/.tags + +# pyenv +.python-version + +# docker travis debug. +ci.sh diff --git a/.travis.yml b/.travis.yml index cb9369c..8703d37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ python: - '3.5' - '3.6' - '3.7' -# - '3.8' +- '3.8' before_install: - python --version - pip install -U pip @@ -13,10 +13,7 @@ before_install: - pip install pytest-cov install: -- if [ $TRAVIS_PYTHON_VERSION == 3.8 ]; - then pip install -r gklearn/tests/requirements.txt; - else pip install -r requirements.txt; - fi +- pip install -r requirements.txt - pip install wheel script: diff --git a/gklearn/kernels/path_up_to_h.py b/gklearn/kernels/path_up_to_h.py new file mode 100644 index 0000000..4b35463 --- /dev/null +++ b/gklearn/kernels/path_up_to_h.py @@ -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]) diff --git a/gklearn/kernels/shortest_path.py b/gklearn/kernels/shortest_path.py index eabe0a8..c11c2e5 100644 --- a/gklearn/kernels/shortest_path.py +++ b/gklearn/kernels/shortest_path.py @@ -4,6 +4,11 @@ Created on Tue Apr 7 15:24:58 2020 @author: ljia + +@references: + + [1] Borgwardt KM, Kriegel HP. Shortest-path kernels on graphs. InData + Mining, Fifth IEEE International Conference on 2005 Nov 27 (pp. 8-pp). IEEE. """ import sys diff --git a/gklearn/kernels/structural_sp.py b/gklearn/kernels/structural_sp.py index 9f10b39..4b9fb26 100644 --- a/gklearn/kernels/structural_sp.py +++ b/gklearn/kernels/structural_sp.py @@ -4,6 +4,11 @@ Created on Mon Mar 30 11:59:57 2020 @author: ljia + +@references: + + [1] Suard F, Rakotomamonjy A, Bensrhair A. Kernel on Bag of Paths For + Measuring Similarity of Shapes. InESANN 2007 Apr 25 (pp. 355-360). """ import sys from itertools import product diff --git a/setup.py b/setup.py index c24493b..0d7fbba 100644 --- a/setup.py +++ b/setup.py @@ -3,9 +3,12 @@ import setuptools with open("README.md", "r") as fh: long_description = fh.read() +with open('requirements.txt') as fp: + install_requires = fp.read() + setuptools.setup( name="graphkit-learn", - version="0.1b2", + version="0.2b1", author="Linlin Jia", author_email="linlin.jia@insa-rouen.fr", description="A Python library for graph kernels based on linear patterns", @@ -18,4 +21,5 @@ setuptools.setup( "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: OS Independent", ], + install_requires=install_requires, )