From d3c22dd0d275cd65cab00a184ce3440fa4f7a258 Mon Sep 17 00:00:00 2001 From: Fize Jacques <jacques.fize@cirad.fr> Date: Fri, 27 Nov 2020 11:35:28 +0100 Subject: [PATCH] Save Repo --- LICENSE | 21 + README.md | 194 ++++++++ desamb_eval.py | 66 +++ desamb_eval_runs.sh | 3 + documentation/imgs/LSTM_archv2.png | Bin 0 -> 187798 bytes generate_dataset.py | 149 ++++++ geocoder_app.py | 93 ++++ helpers.py | 201 ++++++++ lib/__init__.py | 0 lib/custom_layer.py | 31 ++ lib/data_generator.py | 347 ++++++++++++++ lib/data_generatorv3.py | 354 ++++++++++++++ lib/datageneratorv4.py | 65 +++ lib/geocoder/__init__.py | 0 lib/geocoder/bert_geocoder.py | 67 +++ lib/geocoder/heuristics.py | 55 +++ lib/geocoder/our_geocoder.py | 113 +++++ lib/geocoder/svm_geocoder.py | 36 ++ lib/metrics.py | 37 ++ lib/ngram_index.py | 229 +++++++++ lib/run.py | 223 +++++++++ lib/torch_generator.py | 50 ++ lib/utils.py | 220 +++++++++ lib/utils_geo.py | 444 ++++++++++++++++++ lib/word_index.py | 180 +++++++ .../toponym_combination_embedding.json | 20 + .../toponym_combination_embedding_v2.json | 20 + .../toponym_combination_embedding_v3.json | 20 + predict_toponym_coordinates.py | 133 ++++++ region_model.py | 199 ++++++++ requirements.txt | 27 ++ run_multiple_configurations.py | 22 + scripts/embeddingngram.py | 59 +++ scripts/generate_cooc_geocoding_dataset.py | 41 ++ scripts/get_all_adjacency_rel.py | 88 ++++ scripts/get_cooccurrence.py | 40 ++ scripts/gethealpix.py | 32 ++ scripts/randoludo.py | 50 ++ templates/pair_topo.html | 55 +++ templates/skeleton.html | 116 +++++ templates/text.html | 60 +++ train_bert_geocoder.py | 353 ++++++++++++++ train_geocoder.py | 401 ++++++++++++++++ train_geocoder_v2.py | 242 ++++++++++ train_test_split_cooccurrence_data.py | 68 +++ train_test_split_geonames.py | 66 +++ 46 files changed, 5290 insertions(+) create mode 100644 LICENSE create mode 100644 desamb_eval.py create mode 100644 desamb_eval_runs.sh create mode 100644 documentation/imgs/LSTM_archv2.png create mode 100644 generate_dataset.py create mode 100644 geocoder_app.py create mode 100644 helpers.py create mode 100644 lib/__init__.py create mode 100644 lib/custom_layer.py create mode 100644 lib/data_generator.py create mode 100644 lib/data_generatorv3.py create mode 100644 lib/datageneratorv4.py create mode 100644 lib/geocoder/__init__.py create mode 100644 lib/geocoder/bert_geocoder.py create mode 100644 lib/geocoder/heuristics.py create mode 100644 lib/geocoder/our_geocoder.py create mode 100644 lib/geocoder/svm_geocoder.py create mode 100644 lib/metrics.py create mode 100644 lib/ngram_index.py create mode 100644 lib/run.py create mode 100644 lib/torch_generator.py create mode 100644 lib/utils.py create mode 100644 lib/utils_geo.py create mode 100644 lib/word_index.py create mode 100644 parser_config/toponym_combination_embedding.json create mode 100644 parser_config/toponym_combination_embedding_v2.json create mode 100644 parser_config/toponym_combination_embedding_v3.json create mode 100644 predict_toponym_coordinates.py create mode 100644 region_model.py create mode 100644 requirements.txt create mode 100644 run_multiple_configurations.py create mode 100644 scripts/embeddingngram.py create mode 100644 scripts/generate_cooc_geocoding_dataset.py create mode 100644 scripts/get_all_adjacency_rel.py create mode 100644 scripts/get_cooccurrence.py create mode 100644 scripts/gethealpix.py create mode 100644 scripts/randoludo.py create mode 100644 templates/pair_topo.html create mode 100644 templates/skeleton.html create mode 100644 templates/text.html create mode 100644 train_bert_geocoder.py create mode 100644 train_geocoder.py create mode 100644 train_geocoder_v2.py create mode 100644 train_test_split_cooccurrence_data.py create mode 100644 train_test_split_geonames.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ff31b15 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Jacques Fize, Ludovic Moncla and Bruno Martins + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e69de29..f5c5288 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,194 @@ + + +This repository contains the code for *"Using a deep neural network for toponym geocoding based on co-occurrences and spatial relations"*. In a nutshell, we propose to geocode place names using the less information available (two place names, one to geocode and the second used as context) and rely on deep learning network architecture. + + + +<hr> + +# Model architecture + +The model is neural network. The first model is illustrated in the Figure 1. In a nutshell, the model aims to predict coordinates (output) from two place names. The first place name is the one we want to geocode and the second place name is used as context. + +In a experiment (presented [here](https://jacobe2169.github.io/mapthetoponymsim/)), we found and assume that specific toponym affixes (suffix or prefix for example) are bound to certain geographic area. Based on this assumption, we decide to use n-gram sequence representation of input toponyms. For example, Paris will be transformed to Par,ari,ris. + +<div style="text-align:center"> +<img src="documentation/imgs/LSTM_archv2.png"/> +<p><strong>Figure 1</strong> : General workflow</p> +</div> + +<hr> + + +# Setup environnement + +- Python3.6+ +- Os free** + +***It is strongly advised to used Anaconda in a Windows environnement!* + +## Install dependencies + + pip3 install -r requirements.txt + +For Anaconda users + + while read requirement; do conda install --yes $requirement; done < requirements.txt + + + +<hr> + +# Get Started + +## Get pre-trained model + +Pre-trained model are available : + +| Geographic Area | Description | URL | +|-----------|-------------------------------------------------------------------------|--------------------------------------------------------------------------| +| FR | Model trained on the France populated places and area | https://projet.liris.cnrs.fr/hextgeo/files/trained_models/FR_MODEL_2.zip | +| GB | Model trained on the England populated places and area | https://projet.liris.cnrs.fr/hextgeo/files/trained_models/GB_MODEL_2.zip | +| US | Model trained on the United States of America populated places and area | https://projet.liris.cnrs.fr/hextgeo/files/trained_models/US_MODEL_2.zip | + +## Load and use the model + +First thing is to import the dedicated module and load pre-trained model file. Here, we'll be using the France model. + +```python +from lib.geocoder.our_geocoder import Geocoder +g = Geocoder("FR_MODEL_2/FR.txt_100_4_100__A_C.h5","FR_MODEL_2/FR.txt_100_4_100__A_C_index") + +``` + +To geocode a pair of toponym use the `model.get_coord` method: +```python +print(g.get_coord("Paris","France")) +#(2.7003836631774902, 41.24913454055786) #lon,lat +``` + +To reduce computation time, use the `model.get_coords` to geocode multiple pairs of toponyms: + +```python +print(g.get_coords(["Paris","Paris"],["Cherbourg","Montpellier"])) +#(array([2.6039734, 3.480011 ], dtype=float32), +# array([48.27507 , 48.075943], dtype=float32)) + +``` + +<hr> + +# Train your own model + +We propose an implementation of the model illustrated in Figure 1 and a second based on the same input but using BERT pre-trained model. + + +## Prepare data + +The data preparation is divided into three steps. First, we retrieve required data from Geonames. Second, we retrieve place names co-occurrences from Wikipedia. Finally, we generate the datasets to train the model. + +### Geonames data + 1. Download the Geonames data use to train the network [here](download.geonames.org/export/dump/) + 2. download the hierarchy data [here](http://download.geonames.org/export/dump/hierarchy.zip) + 3. unzip both file in the directory of your choice + +### Cooccurence data + + 5. First, you must download the Wikipedia corpus from which you want to extract co-occurrences : [English Wikipedia Corpus](https://dumps.wikimedia.org/enwiki/20200201/enwiki-20200201-pages-articles.xml.bz2) + 6. Parse the corpus with Gensim script using the following command : `python3 -m gensim.scripts.segment_wiki -i -f <wikicorpus> -o <1stoutputname>.json.gz` + 7. Build a page of interest file that contains a list of Wikipedia pages. The file must be a csv with the following column : title,latitude,longitude.<br> You can find [here](https://projet.liris.cnrs.fr/hextgeo/files/place_en_fr_page_clean.csv) a page of interest file that contains places that appears in both FR and EN wikipedia. + 8. Then using and index that contains pages of interest run the command : `python3 script/get_cooccurrence.py <page_of_interest_file> <2noutputname> -c <1stoutputname>.json.gz` + +### Generate dataset + +Use the following command to generate the datasets for training your model. + + python3 generate_dataset.py <geonames_dataset> <wikipedia_dataset> <geonames_hierarchy_data> + + +| Parameter | Description | +|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| --cooc-sampling | Number of cooccurrence sampled for each place in the cooccurrence dataset | +| --adj-sampling | Number of adjacent relation extracted for each place in a Healpix cell | +| --adj-nside | Healpix resolution where places within are considered adjacent | +| --split-nside | Size of the zone where the train/test split are done | +| --split-method | [per_pair\|per_entity] Split each dataset based on places (place cannot exists in both train and test) or pairs(place can appears in train and test) | + +### If you're in a hurry + +French Geonames, French Wikipedia cooccurence data, and their train/test splits datasets can be found here : [https://projet.liris.cnrs.fr/hextgeo/files/](https://projet.liris.cnrs.fr/hextgeo/files/) + + +## Our model + +To train the first model use the following command : + + python3 train_geocoder_v2.py <dataset_name> <inclusion_dataset> <adjacent_dataset> <cooccurrence_dataset> [-i | -a | -w ]+ [optional args] + +| Parameter | Description | +|-----------------------|---------------------------------------------------------------------------------| +| -i,--inclusion | Use inclusion relationships to train the network | +| -a,--adjacency | Use adjacency relationships to train the network | +| -w,--wikipedia-coo | Use Wikipedia place co-occurrences to train the network | +| -n,--ngram-size | ngram size | +| -t,--tolerance-value | K-value in the computation of the accuracy@k (K unit is kilometer) | +| -e,--epochs | number of epochs | +| -d,--dimension | size of the ngram embeddings | +| --admin_code_1 | (Optional) If you wish to train the network on a specific region | + + +<hr> + +# [In Progress] BERT model +In the recent years, BERT architecture proposed by Google researches enables to outperform state-of-art methods for differents tasks in NLP (POS, NER, Classification). To verify if BERT embeddings would permit to increase the performance of our approach, we code a script to use bert with our data. In our previous model, the model returned two values each on between [0,1]. Using Bert, the task has shifted to classification (softmax) where each class correspond to a cell on the glob. We use the hierarchical projection model : Healpix. Other projections model like S2geometry can be considered : https://s2geometry.io/about/overview. + +In order, to run this model training, run the `train_bert_geocoder.py` script : + + python3 train_bert_geocoder.py \ + <train_dataset>\ + <test_dataset>\ + <output_dir>\ + [--batch_size BATCH_SIZE | --epochs EPOCHS] + +The train and test dataset are table data composed of two columns: sentence and label. + +### Pretrained-model + +Pretrained model can be found [here](https://projet.liris.cnrs.fr/hextgeo/files/trained_models/BERT_MODELS/) + +### Use BERT model + +```python +from lib.geocoder.bert_geocoder import BertGeocoder +geocoder = BertGeocoder(<bert_model_dir>,<label_healpix_file>) +geocoder.geocode(<toponyms>,<context,toponyms>) +``` + +<hr> + +# Train multiple model with different parameters + +We built a tiny module that allows to run the network training using different parameters. To do that use the GridSearchModel class in `lib.run`. You can find +an example in the following code: + +```python +from lib.run import GridSearchModel +from collections import OrderedDict + +grid = GridSearchModel(\ + "python3 train_geocoder_v2.py", + **OrderedDict({ # We use an OrderedDict since the order of parameters is important + "rel":["-i","-a","-c"], + "-n":[4], + "geoname_fn":"../data/geonamesData/US_FR.txt".split(), + "hierarchy_fn":"../data/geonamesData/hierarchy.txt".split(), + "store_true":["rel"] + }.items())) +grid.run() +``` + +# Authors and Acknowledgment + +Proposed by **Jacques Fize**, **Ludovic Moncla** and **Bruno Martins** + +This research is supported by an IDEXLYON project of the University of Lyon within the framework of the Investments for the Future Program (ANR-16-IDEX-0005). Bruno Martins was supported by the Fundação para a Ciência e a Tecnologia (FCT), through the project grants PTDC/CCI-CIF/32607/2017 CMIMU) and UIBD/50021/2020 (INESC-ID multi-annual funding). \ No newline at end of file diff --git a/desamb_eval.py b/desamb_eval.py new file mode 100644 index 0000000..8fcd5fe --- /dev/null +++ b/desamb_eval.py @@ -0,0 +1,66 @@ +from glob import glob +import json + +import argparse +import logging + +import pandas as pd + + + +parser = argparse.ArgumentParser() +parser.add_argument("eval_dataset") +parser.add_argument("models_directory") +parser.add_argument("-g","--gpu",action="store_true") +args = parser.parse_args()#("-g ../data/geocoding_evaluation/fr_cooc_test.csv outputs/FR_RESULT".split()) + + +if not args.gpu: + import os + os.environ['CUDA_VISIBLE_DEVICES'] = '-1' # No need for GPU + + +from predict_toponym_coordinates import Geocoder +from lib.utils_geo import haversine_pd + +logging.getLogger("tensorflow").setLevel(logging.CRITICAL) +logging.getLogger("tensorflow_hub").setLevel(logging.CRITICAL) + +EVAL_DATASET_FN= args.eval_dataset#"./test_dataset_ambiguity.csv" + + +def eval_model(eval_dataset_fn,model_fn,model_index_fn): + print("Dataset -- {0} -- Model -- {1}".format(\ + eval_dataset_fn.split("/")[-1], + model_fn.split("/")[-1])) + df = pd.read_csv(eval_dataset_fn) + geocoder = Geocoder(model_fn,model_index_fn) + lon,lat = geocoder.get_coords(df.name1.values,df.name2.values) + lon,lat = geocoder.wgs_coord(lon,lat) + + df["p_longitude"] = lon + df["p_latitude"] = lat + + df["dist"] = haversine_pd(df.longitude,df.latitude,df.p_longitude,df.p_latitude) + + print("100km",(df.dist<100).sum()/len(df)) + print("50km",(df.dist<50).sum()/len(df)) + print("20km",(df.dist<20).sum()/len(df)) + return df + +prefixes = [x.rstrip(".h5") for x in glob(args.models_directory+"/*.h5")] + +final_output = [] +for prefix in prefixes: + try: + df = eval_model(EVAL_DATASET_FN,prefix + ".h5",prefix + "_index") + data = json.load(open(prefix+".json")) + data["acccuracy@100km"] = (df.dist<100).sum()/len(df) + data["acccuracy@50km"] = (df.dist<50).sum()/len(df) + data["acccuracy@25km"] = (df.dist<25).sum()/len(df) + final_output.append(data) + except: + print("BUMP!") + + +pd.DataFrame(final_output).to_csv("{0}_RESULT.csv".format(EVAL_DATASET_FN.rstrip(".csv"))) \ No newline at end of file diff --git a/desamb_eval_runs.sh b/desamb_eval_runs.sh new file mode 100644 index 0000000..20eb682 --- /dev/null +++ b/desamb_eval_runs.sh @@ -0,0 +1,3 @@ +python3 desamb_eval.py -g ../data/geocoding_evaluation/fr_dataset_ambiguity_sample50percent.csv outputs/FR_RESULT +python3 desamb_eval.py -g ../data/geocoding_evaluation/us_fr_cooc_test.csv outputs/USFR_WORD +python3 desamb_eval.py -g ../data/geocoding_evaluation/us_fr_dataset_ambiguity.csv outputs/USFR_WORD diff --git a/documentation/imgs/LSTM_archv2.png b/documentation/imgs/LSTM_archv2.png new file mode 100644 index 0000000000000000000000000000000000000000..794909f8d568f9f153a774bba997d97f9d6a89be GIT binary patch literal 187798 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%0AgA(3Xp~NHKZ(S)Qp9V)fSweog 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 Ld<XSN`0xJ#fe_t* literal 0 HcmV?d00001 diff --git a/generate_dataset.py b/generate_dataset.py new file mode 100644 index 0000000..788fe37 --- /dev/null +++ b/generate_dataset.py @@ -0,0 +1,149 @@ +import argparse + +import pandas as pd +import numpy as np + +from helpers import read_geonames +from lib.utils_geo import latlon2healpix + +from tqdm import tqdm +from sklearn.model_selection import train_test_split + +parser = argparse.ArgumentParser() + +parser.add_argument("geonames_dataset") +parser.add_argument("wikipedia_dataset") +parser.add_argument("geonames_hierarchy_data") +parser.add_argument("--cooc-sampling", default=4, type=int) +parser.add_argument("--adj-sampling", default=4, type=int) +parser.add_argument("--adj-nside", default=128, type=int) +parser.add_argument("--split-nside", default=128, type=int) +parser.add_argument("--split-method", default="per_pair", type=str, choices="per_pair per_place".split()) + +args = parser.parse_args()#("../data/geonamesData/FR.txt ../data/wikipedia/cooccurrence_FR.txt ../data/geonamesData/hierarchy.txt".split()) + +PREFIX = args.geonames_dataset.split("/")[-1].split(".")[0] # Ouch ! +PREFIX = PREFIX + "_" + args.split_method + +#  LOAD DATA +geonames_data = read_geonames(args.geonames_dataset) +wikipedia_data = pd.read_csv(args.wikipedia_dataset, sep="\t") +geonames_hierarchy_data = pd.read_csv(args.geonames_hierarchy_data, sep="\t", header=None, + names="parentId,childId,type".split(",")).fillna("") + +# Add IDs for the Wikipedia Cooc Dataset +min_id = geonames_data.geonameid.max() + 1 +max_id = min_id + len(wikipedia_data) +wikipedia_data["geonameid"] = np.arange(min_id, max_id) + +#  Healpix cell id computation +geonames_data["adj_split"] = geonames_data.apply(lambda x: latlon2healpix(x.latitude, x.longitude, args.adj_nside), + axis=1) + + +def get_adjacent_pairs(dataframe, sampling_nb): + """ + Return pairs of place toponyms that are adjacent geographicaly. + Parameters + ---------- + dataframe : pandas.DataFrame + geonames data + sampling_nb : int + number of adjacent place drawn + + Returns + ------- + list of list + [[ID,place toponym,adjacent place toponym, latitude, longitude],...] + """ + new_pairs = [] + for ix, row in tqdm(dataframe.iterrows(), total=len(dataframe), desc="Get Adjacent Toponym Pairs"): + healpix_cell = row.adj_split + topo_prin = row["name"] + lat, lon = row.latitude, row.longitude + within_cell = dataframe[dataframe.adj_split == healpix_cell]["name"].values + selected = np.random.choice(within_cell, sampling_nb) + new_pairs.extend([[row.geonameid, topo_prin, sel, lat, lon] for sel in selected]) + return new_pairs + + +def get_cooccurrence_pairs(dataframe, sampling_nb): + """ + Return pairs of place toponyms where toponyms appears in a same wikipedia page + Parameters + ---------- + dataframe : pandas.DataFrame + wikipedia cooccurrence data + sampling_nb : int + number of adjacent place drawn + + Returns + ------- + list of list + [[ID,place toponym,adjacent place toponym, latitude, longitude],...] + """ + new_pairs = [] + dataframe["interlinks"] = dataframe.interlinks.apply(lambda x: np.random.choice(x.split("|"), sampling_nb)) + for ix, row in tqdm(dataframe.iterrows(), total=len(dataframe), desc="Get Cooccurrent Toponym Pairs"): + topo_prin = row.title + lat, lon = row.latitude, row.longitude + new_pairs.extend([[row.geonameid, topo_prin, sel, lat, lon] for sel in row["interlinks"]]) + return new_pairs + + +def get_inclusion_pairs(geoname_df, hierarchy_df): + """ + Return pairs of place toponyms that share an inclusion relationship. Ex. Paris, France (Paris geometry is included in France geometry) + Parameters + ---------- + dataframe : pandas.DataFrame + geonames data + hierarchy_df : pandas.DataFrame + geonames hierarchy data + + Returns + ------- + list of list + [[ID,place toponym,adjacent place toponym, latitude, longitude],...] + """ + geonamesIDS = set(geoname_df.geonameid.values) + id_label = dict(geonames_data["geonameid name".split()].values) + id_lat = dict(geonames_data["geonameid latitude".split()].values) + id_lon = dict(geonames_data["geonameid longitude".split()].values) + filter_mask = (hierarchy_df.childId.isin(geonamesIDS) & hierarchy_df.parentId.isin(geonamesIDS)) + pairs_id = hierarchy_df[filter_mask]["childId parentId".split()].values.tolist() + return [[p[0], id_label[p[0]], id_label[p[1]], id_lat[p[0]], id_lon[p[0]]] for p in pairs_id] + +# EXTRACT PAIRS FROM INPUT DATA +cooc_pairs = pd.DataFrame(get_cooccurrence_pairs(wikipedia_data, args.cooc_sampling), + columns="ID toponym toponym_context latitude longitude".split()) +adjacent_pairs = pd.DataFrame(get_adjacent_pairs(geonames_data, args.adj_sampling), + columns="ID toponym toponym_context latitude longitude".split()) +inclusion_pairs = pd.DataFrame(get_inclusion_pairs(geonames_data, geonames_hierarchy_data), + columns="ID toponym toponym_context latitude longitude".split()) + +# FOR EACH PAIR, COMPUTE THE HEALPIX CELL ID FOR EACH COORDINATES ASSOCIATED +cooc_pairs["hp_split"] = cooc_pairs.apply(lambda x: latlon2healpix(x.latitude, x.longitude, args.split_nside), axis=1) +adjacent_pairs["hp_split"] = adjacent_pairs.apply(lambda x: latlon2healpix(x.latitude, x.longitude, args.split_nside), + axis=1) +inclusion_pairs["hp_split"] = inclusion_pairs.apply(lambda x: latlon2healpix(x.latitude, x.longitude, args.split_nside), + axis=1) + +# SPLIT DATASETS BETWEEN TRAIN AND TEST GEOGRAPHICALY +field = "hp_split" +if args.split_method == "per_place": + field = "ID" + +for df in [cooc_pairs, adjacent_pairs]: + df_train, _ = train_test_split(df, stratify=df[field].values, test_size=0.33) + df["split"] = "test" + df.loc[df_train.index.values, "split"] = "train" + +inc_train, _ = train_test_split(inclusion_pairs, test_size=0.33) +inclusion_pairs["split"] = "test" +inclusion_pairs.loc[inc_train.index.values, "split"] = "train" + +# SAVE DATA +inclusion_pairs.to_csv("{0}_inclusion.csv".format(PREFIX), sep="\t") +adjacent_pairs.to_csv("{0}_adjacent.csv".format(PREFIX), sep="\t") +cooc_pairs.to_csv("{0}_cooc.csv".format(PREFIX), sep="\t") diff --git a/geocoder_app.py b/geocoder_app.py new file mode 100644 index 0000000..81d4a1e --- /dev/null +++ b/geocoder_app.py @@ -0,0 +1,93 @@ +from flask import Flask, escape, request, render_template,jsonify,Markup, redirect, url_for +from lib.geocoder.our_geocoder import Geocoder,TextGeocoder +from lib.geocoder.heuristics import * + +import spacy + +app = Flask(__name__) + +dict_model = { + "FR_AIC":("./outputs/FR_MODEL_2/FR.txt_100_4_100__A_I_C.h5","./outputs/FR_MODEL_2/FR.txt_100_4_100__A_I_C_index"), + "FR_C":("./outputs/FR_MODEL_2/FR.txt_100_4_100__C.h5","./outputs/FR_MODEL_2/FR.txt_100_4_100__C_index"), + "FR_AC":("./outputs/FR_MODEL_2/FR.txt_100_4_100__A_C.h5","./outputs/FR_MODEL_2/FR.txt_100_4_100__A_C_index"), + "FR_IC":("./outputs/FR_MODEL_2/FR.txt_100_4_100__I_C.h5","./outputs/FR_MODEL_2/FR.txt_100_4_100__I_C_index"), + + "GB_AIC":("./outputs/GB_MODEL_2/GB.txt_100_4_100__A_I_C.h5","./outputs/GB_MODEL_2/GB.txt_100_4_100__A_I_C_index"), + "GB_C":("./outputs/GB_MODEL_2/GB.txt_100_4_100__C.h5","./outputs/GB_MODEL_2/GB.txt_100_4_100__C_index"), + "GB_AC":("./outputs/GB_MODEL_2/GB.txt_100_4_100__A_C.h5","./outputs/GB_MODEL_2/GB.txt_100_4_100__A_C_index"), + "GB_IC":("./outputs/GB_MODEL_2/GB.txt_100_4_100__I_C.h5","./outputs/GB_MODEL_2/GB.txt_100_4_100__I_C_index") + ,"FR_IGN":("./outputs/IGN/onlyAdjac/IGN_4_100_A_C.h5","./outputs/IGN/onlyAdjac/IGN_4_100_A_C_index") +} + +MODEL = "FR_AC" +LANG = "fr" +NER = "spacy" + +heuristic_func = heuristic_cluster + +geocoder = Geocoder(*dict_model[MODEL]) +g_t = TextGeocoder(geocoder,NER,LANG,heuristic_func) + + +@app.route('/') +def home(): + toponym = request.args.get("top", "") + c_toponym = request.args.get("c_top", "") + msg = request.args.get("msg", "") + msg_code = request.args.get("msg_code", "info") + if toponym and c_toponym: + lon,lat = geocoder.get_coords([toponym],[c_toponym]) + lon,lat = lon[0],lat[0] + print(lon,lat) + return render_template("pair_topo.html",lat=lat,lon=lon,title="Toponyms Pair Geocoder",dict_model=dict_model,msg_code=msg_code) + else: + return render_template("pair_topo.html",title="Toponyms Pair Geocoder",dict_model=dict_model,msg_code=msg_code) + +@app.route('/text') +def text(): + return render_template("text.html",title="Text Geocoder",dict_model=dict_model) + +@app.route('/geocode', methods=['POST', 'GET']) +def geocode(): + if request.method == 'POST': + text = request.form["text"] + + results = g_t.geocode(g_t.extract_geo_entities(text)) + + html_, pos_ = "", 0 + for item in results: + start,end = item["start"], item["end"] + html_ = html_ + text[pos_:start] + "<span class=\"annotation place\">{0}</span>".format(text[start:end]) + pos_ = end + + place_coords = {} + for r in results: + if r["text"] in place_coords: + continue + place_coords[r["text"]]={"lat":float(r["coord"]["lat"]),"lon":float(r["coord"]["lon"])} + return render_template("text.html",title="Text Geocoder",data={"type":"success","output":Markup(html_),"place_coords":place_coords},dict_model=dict_model) + + +@app.route("/loadmodel/<model_id>") +def loadModel(model_id): + global geocoder,g_t,LANG + if not model_id in dict_model: + return redirect(url_for(".home",msg="An error happend when loading the model \"{0}\"!".format(model_id),msg_code="danger")) + else: + geocoder = Geocoder(*dict_model[model_id]) + g_t = TextGeocoder(geocoder,NER,LANG,heuristic_func) + return redirect(url_for(".home",msg="Model \"{0}\" was loaded successfuly!".format(model_id),msg_code="success")) + +@app.route("/loadlang/<lang>") +def loadLang(lang): + global geocoder,g_t,LANG + try: + g_t = TextGeocoder(geocoder,NER,lang,heuristic_func) + LANG = lang + return redirect(url_for(".home",msg="Language is now set to \"{0}\"!".format(LANG),msg_code="success")) + except: + return redirect(url_for(".home",msg="\"{}\" language is not available!".format(lang),msg_code="danger")) + + +if __name__ == "__main__": + app.run(host="0.0.0.0",debug=True) \ No newline at end of file diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..b093f62 --- /dev/null +++ b/helpers.py @@ -0,0 +1,201 @@ +import os +import time +import re + +import numpy as np +import pandas as pd + + +def read_geonames(file): + """ + Return a dataframe that contains Geonames data. + + Parameters + ---------- + file : str + path of the Geonames Csv file + + Returns + ------- + pd.DataFrame + geonames data + """ + dtypes_dict = { + 0: int, # geonameid + 1: str, # name + 2: str, # asciiname + 3: str, # alternatenames + 4: float, # latitude + 5: float, # longitude + 6: str, # feature class + 7: str, # feature code + 8: str, # country code + 9: str, # cc2 + 10: str, # admin1 code + 11: str, # admin2 code + 12: str, # admin3 code + 13: str, # admin4 code + 14: int, # population + 15: str, # elevation + 16: int, # dem (digital elevation model) + 17: str, # timezone + 18: str, # modification date yyyy-MM-dd + } + rename_cols = { + 0: "geonameid", # geonameid + 1: "name", # name + 2: "asciiname", # asciiname + 3: "alternatenames", # alternatenames + 4: "latitude", # latitude + 5: "longitude", # longitude + 6: "feature_class", # feature class + 7: "feature_code", # feature code + 8: "country_code", # country code + 9: "cc2", # cc2 + 10: "admin1_code", # admin1 code + 11: "admin2_code", # admin2 code + 12: "admin3_code", # admin3 code + 13: "admin4_code", # admin4 code + 14: "population", # population + 15: "elevation", # elevation + 16: "dem", # dem (digital elevation model) + 17: "timezone", # timezone + 18: "modification_date", # modification date yyyy-MM-dd + } + data = pd.read_csv( + file, + sep="\t", + header=None, + quoting=3, + dtype=dtypes_dict, + na_values="", + keep_default_na=False, + error_bad_lines=False, + ) + data.rename(columns=rename_cols, inplace=True) + return data + + +def parse_title_wiki(title_wiki): + """ + Parse Wikipedia title + + Parameters + ---------- + title_wiki : str + wikipedia title + + Returns + ------- + str + parsed wikipedia title + """ + return re.sub("\(.*\)", "", str(title_wiki)).strip().lower() + + +def _split(lst, n, complete_chunk_value): + """ + Split a list into chunk of n-size. + + Parameters + ---------- + lst : list + input list + n : int + chunk size + complete_chunk_value : object + if last chunk size not equal to n, this value is used to complete it + + Returns + ------- + list + chunked list + """ + chunks = [lst[i : i + n] for i in range(0, len(lst), n)] + if not chunks: + return chunks + if len(chunks[-1]) != n: + chunks[-1].extend([complete_chunk_value] * (n - len(chunks[-1]))) + return np.array(chunks) + + +class Chronometer: + def __init__(self): + self.__task_begin_timestamp = {} + + def start(self, task_name): + """ + Start a new task chronometer + + Parameters + ---------- + task_name : str + task id + + Raises + ------ + ValueError + if a running task already exists with that name + """ + if task_name in self.__task_begin_timestamp: + raise ValueError( + "A running task exists with the name {0}!".format(task_name) + ) + self.__task_begin_timestamp[task_name] = time.time() + + def stop(self, task_name): + """ + Stop and return the duration of the task + + Parameters + ---------- + task_name : str + task id + + Returns + ------- + float + duration of the task in seconds + + Raises + ------ + ValueError + if no task exist with the id `task_name` + """ + if not task_name in self.__task_begin_timestamp: + raise ValueError("The {0} task does not exist!".format(task_name)) + + duration = time.time() - self.__task_begin_timestamp[task_name] + del self.__task_begin_timestamp[task_name] + + return duration + + +from keras.callbacks import Callback +import time + +class EpochTimer(Callback): + def __init__(self,log_filename): + self.epoch = 0 + self.timer = time.time() + self.output = open(log_filename,'w') + self.output.write("{0},{1}\n".format("Epoch","Execution Time")) + self.output.flush() + + def on_epoch_begin(self,epoch, logs={}): + self.timer = time.time() + + def on_epoch_end(self, epoch, logs=None): + end_time = time.time() - self.timer + self.output.write("{0},{1}\n".format(self.epoch,end_time)) + self.output.flush() + self.epoch += 1 + +if __name__ == "__main__": + chrono = Chronometer() + chrono.start("test") + chrono.start("test2") + time.sleep(3) + print(chrono.stop("test")) + time.sleep(3) + print(chrono.stop("test2")) diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/custom_layer.py b/lib/custom_layer.py new file mode 100644 index 0000000..7204573 --- /dev/null +++ b/lib/custom_layer.py @@ -0,0 +1,31 @@ +# This Layer implementation comes from + +import keras +from keras import backend as K +from keras.engine.topology import Layer + + + +class Pentanh(Layer): + """ + Implementation for the "Penalized Tanh" activation function presented in : + Xu, Bing, Ruitong Huang, et Mu Li. « Revise saturated activation functions ». arXiv preprint arXiv:1602.05980, 2016. + + Code Author: Ana Bárbara Cardoso https://github.com/barbarainacioc/toponym-resolution/blob/master/system/nn_model.py + """ + + def __init__(self, **kwargs): + super(Pentanh, self).__init__(**kwargs) + self.supports_masking = True + self.__name__ = 'pentanh' + + def call(self, inputs): + return K.switch(K.greater(inputs,0), K.tanh(inputs), 0.25 * K.tanh(inputs)) + + def get_config(self): + return super(Pentanh, self).get_config() + + def compute_output_shape(self, input_shape): + return input_shape + +keras.utils.generic_utils.get_custom_objects().update({'pentanh': Pentanh()}) \ No newline at end of file diff --git a/lib/data_generator.py b/lib/data_generator.py new file mode 100644 index 0000000..7eae387 --- /dev/null +++ b/lib/data_generator.py @@ -0,0 +1,347 @@ +import os +from gzip import GzipFile + +import keras +from keras.utils import to_categorical +import numpy as np +import pandas as pd + +from .utils_geo import zero_one_encoding + +from helpers import parse_title_wiki,read_geonames +from gensim.models.keyedvectors import KeyedVectors + +from sklearn.preprocessing import LabelEncoder + + +def wc_l(filename,gzip=True): + lc = 0 + if not gzip: + f = open(filename) + if gzip: + f = GzipFile(filename) + while f.readline(): + lc += 1 + f.close() + return lc + +class SamplingProbabilities: + def __init__(self): + self.count = {} + + def get_probs(self,item): + if not item in self.count: + self.count[item] = 0 + self.count[item]+=1 + return 1/self.count[item] + def __call__(self,a): + return self.get_probs(a) + + +class DataSource(object): + def __init__(self,name,input_filename): + self.name = name + assert os.path.exists(input_filename) + self.input_filename = input_filename + self.len = 0 + + self.is_there_healpix = False + + def __next__(self): + raise NotImplementedError() + + def __iter__(self): + return self + + def __len__(self): + return self.len + + def __reset__(self): + raise NotImplementedError() + + def isOver(self): + raise NotImplementedError() + +class Adjacency(DataSource): + def __init__(self,filename,geonames_filename,sampling=3,len_=None,gzip=True): + DataSource.__init__(self,"Adjacency SRC",filename) + + assert os.path.exists(geonames_filename) + self.geonames_data_dict = {row.geonameid:row.name for row in read_geonames(geonames_filename).itertuples()} + + self.gzip = gzip + if not self.gzip: + self.data_src = open(self.input_filename,'rb') + else: + self.data_src = GzipFile(self.input_filename,'rb') + + if len_: + self.len = len_*sampling + else: + self.len = wc_l(filename,gzip=gzip) + + self.data_src.readline() # header line + + self.sampling = sampling + if self.sampling: + self.probs_storage = SamplingProbabilities() + + self.topo = None + self.context_topo_context = [] + self.curr_probs = None + self.lat, self.lon = None, None + + + self.i = 0 + self.is_over = False + + def __next__(self): + if self.i >= len(self.context_topo_context): + line = self.data_src.readline() + if not line: + self.is_over = True + raise StopIteration + line = line.decode("utf-8").rstrip("\n") + _,geonameid, adjacent_geoname_id,latitude,longitude = tuple(line.split(",")) + + self.topo = int(geonameid) + self.context_topo_context = [int(x) for x in adjacent_geoname_id.split("|")] + if self.sampling: + self.curr_probs = [self.probs_storage(x) for x in self.context_topo_context] + self.context_topo_context = np.random.choice(self.context_topo_context,self.sampling,self.curr_probs) + self.lat, self.lon = float(latitude),float(longitude) + + self.i = 0 + + self.i += 1 + return (self.geonames_data_dict[self.topo], + self.geonames_data_dict[self.context_topo_context[self.i-1]], + self.lat,self.lon) + + def __reset__(self): + if not self.gzip: + self.data_src = open(self.input_filename,'rb') + else: + self.data_src = GzipFile(self.input_filename,'rb') + + self.data_src.readline() # header line + self.is_over = False + + def isOver(self): + return self.is_over + + +class Inclusion(DataSource): + def __init__(self, geonames_filename,hierarchy_filename,mask_ids=None): + super().__init__("Inclusion SRC",hierarchy_filename) + assert os.path.exists(geonames_filename) + self.geonames_data_dict = {row.geonameid:(row.name,row.latitude,row.longitude) for row in read_geonames(geonames_filename).itertuples()} + + self.data_src = pd.read_csv(self.input_filename, + sep="\t", + header=None, + names="parentId,childId,type".split(",") + ).fillna("") + + if mask_ids: + self.data_src = self.data_src[self.data_src.childId.isin(mask_ids)] + self.data_src= self.data_src[self.data_src.childId.isin(self.geonames_data_dict)] + self.data_src= self.data_src[self.data_src.parentId.isin(self.geonames_data_dict)] + + self.data_src = self.data_src["childId parentId".split()].values.tolist() + self.len = len(self.data_src) + + self.i = 0 + + self.is_over = False + + def __next__(self): + if self.i+1 >= self.len: + self.eof = True + raise StopIteration + else: + self.i += 1 + tup_ = tuple(self.data_src[self.i-1]) + return (self.geonames_data_dict[tup_[0]][0], + self.geonames_data_dict[tup_[1]][0], + self.geonames_data_dict[tup_[0]][2], + self.geonames_data_dict[tup_[0]][1]) + + def __reset__(self): + self.i = 0 + self.is_over = False + + def isOver(self): + return (self.i == self.len) + + + + +class CoOccurrences(DataSource): + def __init__(self, filename, label_encoder,sampling=3,resolution = 256,use_healpix=False): + super().__init__("Co-Occurrence data",filename) + self.is_there_healpix = use_healpix + # LOAD DATA + + self.data_src = pd.read_csv(filename,sep="\t") + + # CHECK IF THE HEALPIX RESOLUTION DATA APPEARS IN THE DATA + if not "healpix_{0}".format(resolution) in self.data_src.columns: + raise KeyError("healpix_{0} column does not exists ! ".format(resolution)) + + # PARSE TOPONYMS + self.data_src["title"] = self.data_src.title.apply(parse_title_wiki) + try: + self.data_src["interlinks"] = self.data_src.interlinks.apply(parse_title_wiki) + except: + pass + + # LOOP parameter + self.sampling = sampling + if self.sampling: + self.probs_storage = SamplingProbabilities() + + # LOOP INDICES + self.i = 0 + self.j = 0 + self.is_over = False + self.len = len(self.data_src)*self.sampling + + + # BUFFER VARIABLE + self.topo = None + self.context_topo_context = [] + self.curr_probs = None + self.lat, self.lon = None, None + + + self.resolution = resolution + self.classes = self.data_src["healpix_{0}".format(self.resolution)].unique().tolist() + + self.class_encoder = label_encoder + self.class_encoder.fit(self.classes) + + self.healpix = None + + def __next__(self): + if self.isOver() or self.i*self.sampling == self.len: + self.is_over = True + raise StopIteration + + if self.j >= len(self.context_topo_context): + line = self.data_src.iloc[self.i] + + self.topo = line.title + self.context_topo_context = [x for x in str(line.interlinks).split("|")] + if self.sampling: + self.curr_probs = [self.probs_storage(x) for x in self.context_topo_context] + self.context_topo_context = np.random.choice(self.context_topo_context,self.sampling,self.curr_probs) + self.lat, self.lon = line.latitude,line.longitude + + self.healpix = line["healpix_{0}".format(self.resolution)] + + self.i += 1 + self.j = 0 + + self.j += 1 + return (self.topo, + self.context_topo_context[self.j-1], + self.lat,self.lon,self.class_encoder.transform([self.healpix])[0]) + + def __reset__(self): + self.i = 0 + self.is_over = False + + def isOver(self): + return self.is_over + +class DataGenerator(keras.utils.Sequence): + 'Generates data for Keras' + def __init__(self,data_sources,ngram_index,class_encoder,**kwargs): + 'Initialization' + self.data_src = data_sources + self.ngram_index = ngram_index + + self.batch_size = kwargs.get("batch_size",1000) + self.only_healpix = kwargs.get("only_healpix",False) + + self.len = sum([len(d) for d in self.data_src]) + self.datasrc_index = 0 + + self.num_classes = class_encoder.get_num_classes() + + self.is_there_healpix = self.data_src[self.datasrc_index].is_there_healpix + def __len__(self): + 'Denotes the number of batches per epoch' + return int(np.floor(self.len / self.batch_size)) + + def return_(self,X,y,y2=None): + if self.is_there_healpix and self.only_healpix: + return [X[:,0],X[:,1]],y2 + + elif self.is_there_healpix: + return [X[:,0],X[:,1]],[y,y2] + else: + return [X[:,0],X[:,1]],y + + def __getitem__(self, index): + 'Generate one batch of data' + X = np.empty((self.batch_size,2,self.ngram_index.max_len),dtype=np.int32) # toponym + y = np.empty((self.batch_size,2),dtype=float) #lat lon coord + + y2=None # For healpix + if self.is_there_healpix: + y2 = np.empty((self.batch_size,self.num_classes),dtype=float) # healpix class + + if self.data_src[self.datasrc_index].isOver(): + self.datasrc_index += 1 + self.is_there_healpix = self.data_src[self.datasrc_index].is_there_healpix + + if self.datasrc_index >= len(self.data_src): + self.return_(X,y,y2) + + for i in range(self.batch_size): + if self.data_src[self.datasrc_index].isOver(): + return self.return_(X,y,y2) + try: + topo, topo_context, latitude, longitude, healpix_class = self.data_src[self.datasrc_index].__next__() + except StopIteration as e: + return self.return_(X,y,y2) + + X[i] = [ self.ngram_index.encode(topo),self.ngram_index.encode(topo_context)] + y[i] = [*zero_one_encoding(longitude,latitude)] + if self.is_there_healpix: + y2[i] = to_categorical(healpix_class, num_classes=self.num_classes, dtype='int32' +) + + #y[i] = [longitude,latitude] + return self.return_(X,y,y2) + + def on_epoch_end(self): + 'Updates indexes after each epoch' + [d.__reset__() for d in self.data_src] + self.datasrc_index = 0 + + + +def load_embedding(model_fn,dim_vector=100): + model = KeyedVectors.load(model_fn) + N = len(model.wv.vocab) + M = np.zeros((N,dim_vector)) + for i in range(N): + try: + M[i] = model.wv[str(i)] + except KeyError: + pass + return M + +if __name__ == "__main__": + # All adj nb of line :7955000-1 + from lib.ngram_index import NgramIndex + from tqdm import tqdm + ng = NgramIndex.load("../data/embeddings/word2vec4gram/4gramWiki+geonames_index.json") + c= CoOccurrences("../data/wikipedia/cooccurrence_FR.txt_test.csv",sampling=3) + a = Adjacency("/home/jacques/sample_adjacency.txt",geonames_filename="../data/geonamesData/allCountries.txt",gzip=False,sampling=10) + i= Inclusion(geonames_filename="../data/geonamesData/allCountries.txt",hierarchy_filename="../data/geonamesData/hierarchy.txt") + d= DataGenerator([c,a,i],ng) + for x in tqdm(range(len(d))):d[i] diff --git a/lib/data_generatorv3.py b/lib/data_generatorv3.py new file mode 100644 index 0000000..9bd88db --- /dev/null +++ b/lib/data_generatorv3.py @@ -0,0 +1,354 @@ +import os +from gzip import GzipFile + +import keras +from keras.utils import to_categorical +import numpy as np +import pandas as pd + +from .utils_geo import zero_one_encoding + +from helpers import parse_title_wiki,read_geonames +from gensim.models.keyedvectors import KeyedVectors + +from sklearn.preprocessing import LabelEncoder + + +def wc_l(filename,gzip=True): + lc = 0 + if not gzip: + f = open(filename) + if gzip: + f = GzipFile(filename) + while f.readline(): + lc += 1 + f.close() + return lc + +class SamplingProbabilities: + def __init__(self): + self.count = {} + + def get_probs(self,item): + if not item in self.count: + self.count[item] = 0 + self.count[item]+=1 + return 1/self.count[item] + def __call__(self,a): + return self.get_probs(a) + + +class DataSource(object): + def __init__(self,name,input_filename): + self.name = name + assert os.path.exists(input_filename) + self.input_filename = input_filename + self.len = 0 + + self.is_there_healpix = False + + def __next__(self): + raise NotImplementedError() + + def __iter__(self): + return self + + def __len__(self): + return self.len + + def __reset__(self): + raise NotImplementedError() + + def isOver(self): + raise NotImplementedError() + +class Adjacency(DataSource): + def __init__(self,filename,geonames_filename,sampling=3,len_=None,gzip=True): + DataSource.__init__(self,"Adjacency SRC",filename) + + assert os.path.exists(geonames_filename) + self.geonames_data_dict = {row.geonameid:row.name for row in read_geonames(geonames_filename).itertuples()} + + self.gzip = gzip + if not self.gzip: + self.data_src = open(self.input_filename,'rb') + else: + self.data_src = GzipFile(self.input_filename,'rb') + + if len_: + self.len = len_*sampling + else: + self.len = wc_l(filename,gzip=gzip) + + self.data_src.readline() # header line + + self.sampling = sampling + if self.sampling: + self.probs_storage = SamplingProbabilities() + + self.topo = None + self.context_topo_context = [] + self.curr_probs = None + self.lat, self.lon = None, None + + + self.i = 0 + self.is_over = False + + def __next__(self): + if self.i >= len(self.context_topo_context): + line = self.data_src.readline() + if not line: + self.is_over = True + raise StopIteration + line = line.decode("utf-8").rstrip("\n") + _,geonameid, adjacent_geoname_id,latitude,longitude = tuple(line.split(",")) + + self.topo = int(geonameid) + self.context_topo_context = [int(x) for x in adjacent_geoname_id.split("|")] + if self.sampling: + self.curr_probs = [self.probs_storage(x) for x in self.context_topo_context] + self.context_topo_context = np.random.choice(self.context_topo_context,self.sampling,self.curr_probs) + self.lat, self.lon = float(latitude),float(longitude) + + self.i = 0 + + self.i += 1 + return (self.geonames_data_dict[self.topo], + self.geonames_data_dict[self.context_topo_context[self.i-1]], + self.lat,self.lon) + + def __reset__(self): + if not self.gzip: + self.data_src = open(self.input_filename,'rb') + else: + self.data_src = GzipFile(self.input_filename,'rb') + + self.data_src.readline() # header line + self.is_over = False + + def isOver(self): + return self.is_over + + +class Inclusion(DataSource): + def __init__(self, geonames_filename,hierarchy_filename,mask_ids=None): + super().__init__("Inclusion SRC",hierarchy_filename) + assert os.path.exists(geonames_filename) + self.geonames_data_dict = {row.geonameid:(row.name,row.latitude,row.longitude) for row in read_geonames(geonames_filename).itertuples()} + + self.data_src = pd.read_csv(self.input_filename, + sep="\t", + header=None, + names="parentId,childId,type".split(",") + ).fillna("") + + if mask_ids: + self.data_src = self.data_src[self.data_src.childId.isin(mask_ids)] + self.data_src= self.data_src[self.data_src.childId.isin(self.geonames_data_dict)] + self.data_src= self.data_src[self.data_src.parentId.isin(self.geonames_data_dict)] + + self.data_src = self.data_src["childId parentId".split()].values.tolist() + self.len = len(self.data_src) + + self.i = 0 + + self.is_over = False + + def __next__(self): + if self.i+1 >= self.len: + self.eof = True + raise StopIteration + else: + self.i += 1 + tup_ = tuple(self.data_src[self.i-1]) + return (self.geonames_data_dict[tup_[0]][0], + self.geonames_data_dict[tup_[1]][0], + self.geonames_data_dict[tup_[0]][2], + self.geonames_data_dict[tup_[0]][1]) + + def __reset__(self): + self.i = 0 + self.is_over = False + + def isOver(self): + return (self.i == self.len) + + + + +class CoOccurrences(DataSource): + def __init__(self, filename, label_encoder,sampling=3,resolution = 256,use_healpix=False): + super().__init__("Co-Occurrence data",filename) + self.is_there_healpix = use_healpix + # LOAD DATA + + self.data_src = pd.read_csv(filename,sep="\t") + + # CHECK IF THE HEALPIX RESOLUTION DATA APPEARS IN THE DATA + if not "healpix_{0}".format(resolution) in self.data_src.columns: + raise KeyError("healpix_{0} column does not exists ! ".format(resolution)) + + # PARSE TOPONYMS + self.data_src["title"] = self.data_src.title.apply(parse_title_wiki) + try: + self.data_src["interlinks"] = self.data_src.interlinks.apply(parse_title_wiki) + except: + pass + + # LOOP parameter + self.sampling = sampling + if self.sampling: + self.probs_storage = SamplingProbabilities() + + # LOOP INDICES + self.i = 0 + self.j = 0 + self.is_over = False + self.len = len(self.data_src)*(self.sampling-1) + + + # BUFFER VARIABLE + self.topo = None + self.context_topo_context = [] + self.curr_probs = None + self.lat, self.lon = None, None + + + self.resolution = resolution + self.classes = self.data_src["healpix_{0}".format(self.resolution)].unique().tolist() + + self.class_encoder = label_encoder + self.class_encoder.fit(self.classes) + + self.healpix = None + + def __next__(self): + if self.isOver() or self.i*self.sampling == self.len: + self.is_over = True + raise StopIteration + + if self.j >= len(self.context_topo_context): + line = self.data_src.iloc[self.i] + + self.topo = line.title + self.context_topo_context = [x for x in str(line.interlinks).split("|")] + N = len(self.context_topo_context) + triple = [] + for i in range(N): + if i+1 == N: + break + triple.append((self.context_topo_context[i],self.context_topo_context[i+1])) + + + self.context_topo_context = triple + np.random.shuffle(self.context_topo_context) + self.lat, self.lon = line.latitude,line.longitude + + self.healpix = line["healpix_{0}".format(self.resolution)] + + self.i += 1 + self.j = 0 + + self.j += 1 + return (self.topo, + *self.context_topo_context[self.j-1], + self.lat,self.lon,self.class_encoder.transform([self.healpix])[0]) + + def __reset__(self): + self.i = 0 + self.is_over = False + + def isOver(self): + return self.is_over + +class DataGenerator(keras.utils.Sequence): + 'Generates data for Keras' + def __init__(self,data_sources,ngram_index,class_encoder,**kwargs): + 'Initialization' + self.data_src = data_sources + self.ngram_index = ngram_index + + self.batch_size = kwargs.get("batch_size",1000) + self.only_healpix = kwargs.get("only_healpix",False) + + self.len = sum([len(d) for d in self.data_src]) + self.datasrc_index = 0 + + self.num_classes = class_encoder.get_num_classes() + + self.is_there_healpix = self.data_src[self.datasrc_index].is_there_healpix + def __len__(self): + 'Denotes the number of batches per epoch' + return int(np.floor(self.len / self.batch_size)) + + def return_(self,X,y,y2=None): + if self.is_there_healpix and self.only_healpix: + return [X[:,0],X[:,1],X[:,2]],y2 + + elif self.is_there_healpix: + return [X[:,0],X[:,1],X[:,2]],[y,y2] + else: + return [X[:,0],X[:,1],X[:,2]],y + + def __getitem__(self, index): + 'Generate one batch of data' + X = np.empty((self.batch_size,3,self.ngram_index.max_len),dtype=np.int32) # toponym + y = np.empty((self.batch_size,2),dtype=float) #lat lon coord + + y2=None # For healpix + if self.is_there_healpix: + y2 = np.empty((self.batch_size,self.num_classes),dtype=float) # healpix class + + if self.data_src[self.datasrc_index].isOver(): + self.datasrc_index += 1 + self.is_there_healpix = self.data_src[self.datasrc_index].is_there_healpix + + if self.datasrc_index >= len(self.data_src): + self.return_(X,y,y2) + + for i in range(self.batch_size): + if self.data_src[self.datasrc_index].isOver(): + return self.return_(X,y,y2) + try: + topo, topo_context_1,topo_context_2, latitude, longitude, healpix_class = self.data_src[self.datasrc_index].__next__() + except StopIteration as e: + return self.return_(X,y,y2) + + X[i] = [ self.ngram_index.encode(topo),self.ngram_index.encode(topo_context_1),self.ngram_index.encode(topo_context_2)] + y[i] = [*zero_one_encoding(longitude,latitude)] + if self.is_there_healpix: + y2[i] = to_categorical(healpix_class, num_classes=self.num_classes, dtype='int32' +) + + #y[i] = [longitude,latitude] + return self.return_(X,y,y2) + + def on_epoch_end(self): + 'Updates indexes after each epoch' + [d.__reset__() for d in self.data_src] + self.datasrc_index = 0 + + + +def load_embedding(model_fn,dim_vector=100): + model = KeyedVectors.load(model_fn) + N = len(model.wv.vocab) + M = np.zeros((N,dim_vector)) + for i in range(N): + try: + M[i] = model.wv[str(i)] + except KeyError: + pass + return M + +if __name__ == "__main__": + # All adj nb of line :7955000-1 + from lib.ngram_index import NgramIndex + from tqdm import tqdm + ng = NgramIndex.load("../data/embeddings/word2vec4gram/4gramWiki+geonames_index.json") + c= CoOccurrences("../data/wikipedia/cooccurrence_FR.txt_test.csv",sampling=3) + a = Adjacency("/home/jacques/sample_adjacency.txt",geonames_filename="../data/geonamesData/allCountries.txt",gzip=False,sampling=10) + i= Inclusion(geonames_filename="../data/geonamesData/allCountries.txt",hierarchy_filename="../data/geonamesData/hierarchy.txt") + d= DataGenerator([c,a,i],ng) + for x in tqdm(range(len(d))):d[i] diff --git a/lib/datageneratorv4.py b/lib/datageneratorv4.py new file mode 100644 index 0000000..e451035 --- /dev/null +++ b/lib/datageneratorv4.py @@ -0,0 +1,65 @@ +import os +from gzip import GzipFile + +import keras +from keras.utils import to_categorical +import numpy as np +import pandas as pd + +from lib.utils_geo import zero_one_encoding + +from helpers import parse_title_wiki,read_geonames +from gensim.models.keyedvectors import KeyedVectors + +from sklearn.preprocessing import LabelEncoder + +import numpy as np +import keras + +class DataGenerator(keras.utils.Sequence): + 'Generates data for Keras' + def __init__(self, pairs_of_toponyms,encoder, batch_size=32, shuffle=True): + 'Initialization' + self.data= pairs_of_toponyms + self.encoder = encoder + self.dim = self.encoder.max_len + self.shuffle = shuffle + + self.batch_size = batch_size + self.on_epoch_end() + + def __len__(self): + 'Denotes the number of batches per epoch' + return int(np.floor(len(self.data) / self.batch_size)) + + def __getitem__(self, index): + 'Generate one batch of data' + # Generate indexes of the batch + indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size] + + # Generate data + return self.__data_generation(indexes) + + + def on_epoch_end(self): + 'Updates indexes after each epoch' + self.indexes = np.arange(len(self.data)) + if self.shuffle == True: + np.random.shuffle(self.indexes) + + def __data_generation(self, list_ids): + 'Generates data containing batch_size samples' # X : (n_samples, *dim, n_channels) + # Initialization + X1 = np.empty((self.batch_size, self.dim)) + X2 = np.empty((self.batch_size, self.dim)) + y = np.zeros((self.batch_size,2), dtype=float) + + # Generate data + for ix,i in enumerate(list_ids): + # Store sample + X1[ix,] = self.encoder.encode(self.data.toponym.iloc[i]) + X2[ix,] = self.encoder.encode(self.data.toponym_context.iloc[i]) + # Store class + y[ix,] = list(zero_one_encoding(self.data.longitude.iloc[i],self.data.latitude.iloc[i])) + + return [X1,X2],y \ No newline at end of file diff --git a/lib/geocoder/__init__.py b/lib/geocoder/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/geocoder/bert_geocoder.py b/lib/geocoder/bert_geocoder.py new file mode 100644 index 0000000..037ddad --- /dev/null +++ b/lib/geocoder/bert_geocoder.py @@ -0,0 +1,67 @@ +import os +import sys +import time +import random +import argparse +import datetime + +import pandas as pd +import numpy as np + +import tensorflow as tf +import torch + +from tqdm import tqdm +tqdm.pandas() + +from torch.utils.data import TensorDataset, DataLoader, RandomSampler, SequentialSampler +from keras.preprocessing.sequence import pad_sequences +from transformers import BertTokenizer +from transformers import BertForSequenceClassification, AdamW, BertConfig +from transformers import get_linear_schedule_with_warmup + +from ..torch_generator import SentenceDataset +from ..utils_geo import latlon2healpix,healpix2latlon + +import pickle + +# If there's a GPU available... +if torch.cuda.is_available(): + + # Tell PyTorch to use the GPU. + device = torch.device("cuda") + print('There are %d GPU(s) available.' % torch.cuda.device_count()) + print('We will use the GPU:', torch.cuda.get_device_name(0)) +# If not... +else: + print('No GPU available, using the CPU instead.') + device = torch.device("cpu") + + +class BertGeocoder(): + def __init__(self,bert_model_dir,label_healpix_file,healpix_nside=128,batch_size=1): + self.bert_model = BertForSequenceClassification.from_pretrained(bert_model_dir) + self.bert_model.to(device) + self.tokenizer = BertTokenizer.from_pretrained(bert_model_dir,truncation=True) + self.label_healpix = {v:k for k, v in pickle.load(open(label_healpix_file,'rb')).items()} + + self.nside = healpix_nside + + self.batch_size = batch_size + + def geocode(self,toponyms, context_toponyms): + data = SentenceDataset(pd.DataFrame([[toponyms[i] + " " + context_toponyms[i],0] for i in range(len(toponyms))],columns=["sentence","label"]),self.tokenizer,batch_size=len(toponyms),shuffle=False) + dataloader = DataLoader(data, batch_size=self.batch_size) + results = [] + for step, batch in enumerate(dataloader): + b_input_ids = batch[0].to(device) + b_input_mask = batch[1].to(device) + with torch.no_grad(): + outputs = self.bert_model(b_input_ids, + token_type_ids=None, + attention_mask=b_input_mask) + results.append(outputs[0].detach().cpu().numpy()) + label = np.argmax(np.concatenate(results),axis=1) + healpix_label = [self.label_healpix[l] for l in label] + lat,lon = healpix2latlon(healpix_label,self.nside) + return np.concatenate((lat.reshape(-1,1),lon.reshape(-1,1)),axis=1) \ No newline at end of file diff --git a/lib/geocoder/heuristics.py b/lib/geocoder/heuristics.py new file mode 100644 index 0000000..82d7110 --- /dev/null +++ b/lib/geocoder/heuristics.py @@ -0,0 +1,55 @@ +import pandas as pd +import numpy as np + +from haversine import haversine_vector, Unit +from sklearn.cluster import DBSCAN + +def heuristic_mean(geocoder,toponyms): + input_ = np.asarray([[t1,t2] for t2 in toponyms for t1 in toponyms if t2 != t1]) + res_geocode = pd.DataFrame(input_,columns="t tc".split()) + lons,lats = geocoder.get_coords(input_[:,0],input_[:,1]) + res_geocode["lon"] = lons + res_geocode["lat"] = lats + results = {} + for tp in toponyms: + lat = res_geocode[res_geocode.t == tp].lat.mean() + lon = res_geocode[res_geocode.t == tp].lon.mean() + results[tp]={"lat":lat,"lon":lon} + return results + +def heuristic_no_context(geocoder,toponyms): + input_ = np.asarray([[t1,t1] for t2 in toponyms for t1 in toponyms if t2 != t1]) + res_geocode = pd.DataFrame(input_,columns="t tc".split()) + lons,lats = geocoder.get_coords(input_[:,0],input_[:,1]) + res_geocode["lon"] = lons + res_geocode["lat"] = lats + results = {} + for tp in toponyms: + lat = res_geocode[res_geocode.t == tp].lat.mean() + lon = res_geocode[res_geocode.t == tp].lon.mean() + results[tp]={"lat":lat,"lon":lon} + return results + +def heuristic_cluster(geocoder,toponyms,eps=100): + results = {} + input_ = np.asarray([[t1,t2] for t2 in toponyms for t1 in toponyms if t2 != t1]) + res_geocode = pd.DataFrame(input_,columns="t tc".split()) + lons,lats = geocoder.get_coords(input_[:,0],input_[:,1]) + res_geocode["lon"] = lons + res_geocode["lat"] = lats + + clf = DBSCAN(eps=eps) + for t in toponyms: + tp_df = res_geocode[res_geocode.tc == t].copy() + + coords = tp_df["lon lat".split()].values + clf.fit(haversine_vector(coords,coords,unit="km",comb=True)) + + tp_df["cluster"] = clf.labels_ + counts_ = dict(tp_df.cluster.value_counts()) + max_cluster = max(counts_, key=counts_.get) + tp_df = tp_df[tp_df.cluster == max_cluster] + lat = tp_df.lat.median() + lon = tp_df.lon.median() # + results[t]={"lat":lat,"lon":lon} + return results \ No newline at end of file diff --git a/lib/geocoder/our_geocoder.py b/lib/geocoder/our_geocoder.py new file mode 100644 index 0000000..0345ea4 --- /dev/null +++ b/lib/geocoder/our_geocoder.py @@ -0,0 +1,113 @@ +# NATIVE LIB +import os + +# DATA LIB +import numpy as np +import pandas as pd + +# DL LIB +import tensorflow as tf +import keras.backend as K +from keras.models import load_model +from tensorflow.python.keras.backend import set_session +from tensorflow.python.keras.models import load_model + +# CUSTOM LIB +from lib.word_index import WordIndex +from lib.ngram_index import NgramIndex +from lib.utils_geo import haversine_tf_1circle + + +import stanza +import spacy +import os +os.environ['CUDA_VISIBLE_DEVICES'] = '-1' + +class Geocoder(object): + """ + >>>geocoder = Geocoder("LSTM_FR.txt_20_4_0.002_None_A_I_C.h5","index_4gram_FR_backup.txt") + >>>lon,lat = geocoder.get_coord("Paris","New-York") + >>>lon,lat = geocoder.wgs_coord(lon,lat) + >>>geocoder.plot_coord("Paris,New-York",lat,lon) + + if you want an interactive map using leafletJS, set to True the `interactive_map` parameter of `Geocoder.plot_coord()` + """ + def __init__(self,keras_model_fn,ngram_index_file,word_index=False): + self.keras_model = load_model(keras_model_fn,custom_objects={"loss":haversine_tf_1circle},compile=False)#custom_objects={"accuracy_at_k_lat":lat_accuracy(),"accuracy_at_k_lon":lon_accuracy()}) + if not word_index: + self.ngram_encoder = NgramIndex.load(ngram_index_file) + else: + self.ngram_encoder = WordIndex.load(ngram_index_file) + + def get_coord(self,toponym,context_toponym): + global sess + global graph + p = self.ngram_encoder.complete(self.ngram_encoder.encode(toponym),self.ngram_encoder.max_len) + c = self.ngram_encoder.complete(self.ngram_encoder.encode(context_toponym),self.ngram_encoder.max_len) + p = np.array(p) + c = np.array(c) + coord = self.keras_model.predict([[p],[c]]) + return self.wgs_coord(coord[0][0],coord[0][1]) + + def get_coords(self,list_toponym,list_toponym_context): + p = [self.ngram_encoder.complete(self.ngram_encoder.encode(toponym),self.ngram_encoder.max_len) for toponym in list_toponym] + c = [self.ngram_encoder.complete(self.ngram_encoder.encode(toponym),self.ngram_encoder.max_len) for toponym in list_toponym_context] + + p = np.array(p) + c = np.array(c) + + coords = self.keras_model.predict([p,c]) + return self.wgs_coord(coords[:,0],coords[:,1]) #lon lat + + def wgs_coord(self,lon,lat): + return ((lon*360)-180),((lat*180)-90) + + def plot_coord(self,toponym,lat,lon,interactive_map=False,**kwargs): + if interactive_map: + import folium + import tempfile + import webbrowser + fp = tempfile.NamedTemporaryFile(delete=False) + m = folium.Map() + folium.Marker([lat, lon], popup=toponym).add_to(m) + m.save(fp.name) + webbrowser.open('file://' + fp.name) + else: + import matplotlib.pyplot as plt + import geopandas + fig, ax = plt.subplots(1,**kwargs) + world = geopandas.read_file(geopandas.datasets.get_path('naturalearth_lowres')) + world.plot(color='white', edgecolor='black',ax=ax) + ax.plot(lon,lat,marker='o', color='red', markersize=5) + plt.show() + + + +class TextGeocoder(): + def __init__(self,geocoder_model,ner_name,lang,heuristic_func,n_jobs=None): + self.geocoder_model = geocoder_model + self.ner_name = ner_name + self.ner_model = None + if self.ner_name == "stanza": + self.ner_model = stanza.Pipeline(lang) + else: + self.ner_model = spacy.load(lang) + self.heuristic_func = heuristic_func + def __call__(self,a): + pass + + def extract_geo_entities(self,text): + if self.ner_model == "stanza": + entities = [{"text":en.text,"type":en.type,"start":en.start_char,"end":en.end_char} for en in self.ner_model(text).entities if en.type == "LOC"] + else: + entities = [{"text":en.text,"type":en.label_,"start":en.start_char,"end":en.end_char} for en in self.ner_model(text).ents if en.label_ in "LOC GPE".split()] + return entities + + def geocode(self,entities): + df = pd.DataFrame(entities) + heuristic_results = self.heuristic_func(self.geocoder_model,df.text.values) + for e in range(len(entities)): + entities[e]["coord"] = heuristic_results[entities[e]["text"]] + return entities + + diff --git a/lib/geocoder/svm_geocoder.py b/lib/geocoder/svm_geocoder.py new file mode 100644 index 0000000..2997b29 --- /dev/null +++ b/lib/geocoder/svm_geocoder.py @@ -0,0 +1,36 @@ +import numpy as np + +from joblib import dump, load +from tensorflow.keras.utils import to_categorical + +from lib.utils_geo import latlon2healpix +from lib.ngram_index import NgramIndex + + +def parse_bow(x,index): + return np.sum(to_categorical(x,num_classes=index.cpt+1),axis=0) + +def is_in(lat,lon,hp_predicted,hp_nside): + hp_truth = latlon2healpix(lat,lon,hp_nside) + return hp_truth == hp_predicted + +class SVMGeocoder(object): + + def __init__(self,model_fn,ngram_index_filename): + self.model = load(model_fn) + self.ng_index = NgramIndex.load(ngram_index_filename) + + def geocode(self,phrase1,phrase2): + if not phrase1 or not phrase2: + return None + vec = parse_bow(np.array(self.ng_index.encode(phrase1)),self.ng_index)+\ + parse_bow(np.array(self.ng_index.encode(phrase2)),self.ng_index) + return self.model.predict([vec])[0] + + def geocode_multi(self,phrases1,phrases2): + vecs = np.array([ parse_bow(np.array(self.ng_index.encode(ph)),self.ng_index) for ph in phrases1 if ph]) + vecs += np.array([ parse_bow(np.array(self.ng_index.encode(ph)),self.ng_index) for ph in phrases2 if ph]) + return self.model.predict(vecs) + +hp = SVMGeocoder("SVMLINEAR_US_FR_AC.bin", "../../outputs/US_FR.txt_100_4_0.002__A_C_index") +hp.geocode("paris","montpellier") diff --git a/lib/metrics.py b/lib/metrics.py new file mode 100644 index 0000000..e82c548 --- /dev/null +++ b/lib/metrics.py @@ -0,0 +1,37 @@ +import tensorflow as tf + +def lat_accuracy(LAT_TOL =1/180.): + def accuracy_at_k_lat(y_true, y_pred): + """ + Metrics use to measure the accuracy of the coordinate prediction. But in comparison to the normal accuracy metrics, we add a tolerance threshold due to the (quasi) impossible + task for neural network to obtain the exact coordinate. + + Parameters + ---------- + y_true : tf.Tensor + truth data + y_pred : tf.Tensor + predicted output + """ + diff = tf.abs(y_true - y_pred) + fit = tf.dtypes.cast(tf.less(diff,LAT_TOL),tf.int64) + return tf.reduce_sum(fit)/tf.size(y_pred,out_type=tf.dtypes.int64) + return accuracy_at_k_lat + +def lon_accuracy(LON_TOL=1/360.): + def accuracy_at_k_lon(y_true, y_pred): + """ + Metrics use to measure the accuracy of the coordinate prediction. But in comparison to the normal accuracy metrics, we add a tolerance threshold due to the (quasi) impossible + task for neural network to obtain the exact coordinate. + + Parameters + ---------- + y_true : tf.Tensor + truth data + y_pred : tf.Tensor + predicted output + """ + diff = tf.abs(y_true - y_pred) + fit = tf.dtypes.cast(tf.less(diff,LON_TOL),tf.int64) + return tf.reduce_sum(fit)/tf.size(y_pred,out_type=tf.dtypes.int64) + return accuracy_at_k_lon \ No newline at end of file diff --git a/lib/ngram_index.py b/lib/ngram_index.py new file mode 100644 index 0000000..5f86220 --- /dev/null +++ b/lib/ngram_index.py @@ -0,0 +1,229 @@ +import json + +import numpy as np + +from ngram import NGram +from transformers import BertTokenizer + +# Machine learning +from gensim.models import Word2Vec + + +class bertTokenizer: + def __init__(self): + self.tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased',do_lower_case=False) + + def split(self,string): + return self.tokenizer.tokenize(string) + +class NgramIndex(): + """ + Class used for encoding words in ngram representation + """ + def __init__(self,n,bert_tokenization=False,loaded = False): + """ + Constructor + + Parameters + ---------- + n : int + ngram size + """ + self.ngram_gen = NGram(N=n) + self.empty_char = "$" + if bert_tokenization: + self.ngram_gen = bertTokenizer() + self.empty_char = "#" + + self.size = n + self.ngram_index = {"":0} + self.index_ngram = {0:""} + self.cpt = 0 + self.max_len = 0 + + self.loaded = loaded + + + def split_and_add(self,word): + """ + Split word in multiple ngram and add each one of them to the index + + Parameters + ---------- + word : str + a word + """ + ngrams = str(word).lower().replace(" ",self.empty_char) + ngrams = list(self.ngram_gen.split(ngrams)) + [self.add(ngram) for ngram in ngrams] + self.max_len = max(self.max_len,len(ngrams)) + + def add(self,ngram): + """ + Add a ngram to the index + + Parameters + ---------- + ngram : str + ngram + """ + if not ngram in self.ngram_index: + self.cpt+=1 + self.ngram_index[ngram]=self.cpt + self.index_ngram[self.cpt]=ngram + + + def encode(self,word): + """ + Return a ngram representation of a word + + Parameters + ---------- + word : str + a word + + Returns + ------- + list of int + listfrom shapely.geometry import Point,box + of ngram index + """ + ngrams = str(word).lower().replace(" ",self.empty_char) + ngrams = list(self.ngram_gen.split(ngrams)) + ngrams = [ng for ng in ngrams if ng.count(self.empty_char)<2] + if not self.loaded: + [self.add(ng) for ng in ngrams if not ng in self.ngram_index] + return self.complete([self.ngram_index[ng] for ng in ngrams if ng in self.ngram_index],self.max_len) + + def complete(self,ngram_encoding,MAX_LEN,filling_item=0): + """ + Complete a ngram encoded version of word with void ngram. It's necessary for neural network. + + Parameters + ---------- + ngram_encoding : list of int + first encoding of a word + MAX_LEN : int + desired length of the encoding + filling_item : int, optional + ngram index you wish to use, by default 0 + + Returns + ------- + list of int + list of ngram index + """ + if self.loaded and len(ngram_encoding) >=MAX_LEN: + return ngram_encoding[:MAX_LEN] + assert len(ngram_encoding) <= MAX_LEN + diff = MAX_LEN - len(ngram_encoding) + ngram_encoding.extend([filling_item]*diff) + return ngram_encoding + + def get_embedding_layer(self,texts,dim=100,**kwargs): + """ + Return an embedding matrix for each ngram using encoded texts. Using gensim.Word2vec model. + + Parameters + ---------- + texts : list of [list of int] + list of encoded word + dim : int, optional + embedding dimension, by default 100 + + Returns + ------- + np.array + embedding matrix + """ + model = Word2Vec([[str(w) for w in t] for t in texts], size=dim,window=5, min_count=1, workers=4,**kwargs) + N = len(self.ngram_index) + embedding_matrix = np.zeros((N,dim)) + for i in range(N): + if str(i) in model.wv: + embedding_matrix[i] = model.wv[str(i)] + return embedding_matrix + + def get_glove_embedding_layer(self,texts,dim=100,**kwargs): + """ + Return an embedding matrix for each ngram using encoded texts. Using gensim.Word2vec model. + + Parameters + ---------- + texts : list of [list of int] + list of encoded word + dim : int, optional + embedding dimension, by default 100 + + Returns + ------- + np.array + embedding matrix + """ + from glove import Corpus, Glove + corpus = Corpus() + corpus.fit([[str(w) for w in t] for t in texts], window=10) + glove = Glove(no_components=dim, learning_rate=0.05) + glove.fit(corpus.matrix, epochs=30, no_threads=4, verbose=True) + glove.add_dictionary(corpus.dictionary) + N = len(self.ngram_index) + embedding_matrix = np.zeros((N,dim)) + for i in range(N): + if str(i) in glove.dictionary: + embedding_matrix[i] = glove.word_vectors[glove.dictionary[str(i)]] + return embedding_matrix + + + def save(self,fn): + """ + + Save the NgramIndex + + Parameters + ---------- + fn : str + output filename + """ + data = { + "ngram_size": self.size, + "ngram_index": self.ngram_index, + "cpt_state": self.cpt, + "max_len_state": self.max_len + } + json.dump(data,open(fn,'w')) + + @staticmethod + def load(fn): + """ + + Load a NgramIndex state from a file. + + Parameters + ---------- + fn : str + input filename + + Returns + ------- + NgramIndex + ngram index + + Raises + ------ + KeyError + raised if a required field does not appear in the input file + """ + try: + data = json.load(open(fn)) + except json.JSONDecodeError: + print("Data file must be a JSON") + for key in ["ngram_size","ngram_index","cpt_state","max_len_state"]: + if not key in data: + raise KeyError("{0} field cannot be found in given file".format(key)) + new_obj = NgramIndex(data["ngram_size"],loaded=True) + new_obj.ngram_index = data["ngram_index"] + new_obj.index_ngram = {v:k for k,v in new_obj.ngram_index.items()} + new_obj.cpt = data["cpt_state"] + new_obj.max_len = data["max_len_state"] + return new_obj + diff --git a/lib/run.py b/lib/run.py new file mode 100644 index 0000000..f2a7b79 --- /dev/null +++ b/lib/run.py @@ -0,0 +1,223 @@ + +import subprocess +import time +import numpy as np + +class Chronometer: + """ + To be used for mesure time execution of a block of code + >>> import time + >>> chrono = Chronometer() + >>> chrono.start("task1") + >>> time.sleep(1) + >>> duration = chrono.stop("task1") + >>> print(duration) #Should display '1' + + """ + def __init__(self): + self.__task_begin_timestamp = {} + + def start(self, task_name): + """ + Start a new task chronometer + + Parameters + ---------- + task_name : str + task id + + Raises + ------ + ValueError + if a running task already exists with that name + """ + if task_name in self.__task_begin_timestamp: + raise ValueError( + "A running task exists with the name {0}!".format(task_name) + ) + self.__task_begin_timestamp[task_name] = time.time() + + def stop(self, task_name): + """ + Stop and return the duration of the task + + Parameters + ---------- + task_name : str + task id + + Returns + ------- + float + duration of the task in seconds + + Raises + ------ + ValueError + if no task exist with the id `task_name` + """ + if not task_name in self.__task_begin_timestamp: + raise ValueError("The {0} task does not exist!".format(task_name)) + + duration = time.time() - self.__task_begin_timestamp[task_name] + del self.__task_begin_timestamp[task_name] + + return duration + +class Run(object): + """ + Define a task to execute. A task here is associated to a command line. A task is defined by two entities : + * base_command : runnable + * kwargs : parameters associate to the command + + Parmeters formating follows `argparse` format: + * "-i" or "--input" : optional parameter + * "input" : required parameter + + >>> task1 = Run("task1","echo",text="hello word") + >>> task1.run() + + With optional parameter, we have to use a trick ;) + + >>> task1 = Run("task1","echo",**{"text":"hello word","-e":"args") + >>> task1.run() + + To save the output, indicate an output filename when the task is run : + >>> task1.run("output_file.txt") + """ + def __init__(self,task_name,base_command,**kwargs): + """ + Constructor + + Parameters + ---------- + command_base : str + command base + **kwargs : dict + parameters + """ + self.chrono = Chronometer() + self.task_name = task_name + self.base_command = base_command + + self.run_args = kwargs + + + def get_command(self): + """ + Return the shell command build on the task attributes (basic command and parameters) + + Returns + ------- + str + command + """ + command = self.base_command + for key,value in self.run_args.items(): + if "-" in key: + command = command + " {0} {1}".format(key,value) + else: + command = command + " {0}".format(value) + return command + + def add_parameter(self, key, value): + """ + Add a parameter to the task + + Parameters + ---------- + key : str + key + value : object + value + """ + self.run_args[key] = value + + def run(self,log_filename = None): + """ + Run the task + + Parameters + ---------- + log_filename : str, optional + log filename, by default None + """ + self.chrono.start(self.task_name) + + out_proc = subprocess.PIPE + if log_filename: + out_proc = open(log_filename,'a') + print(4) + process = subprocess.Popen(self.get_command().split(),stdout=out_proc) + _, _ = process.communicate() # We don't care of the output (if so, we use the log_filename argument) + + duration = self.chrono.stop(self.task_name) + print("RUN {0} finished in {1}seconds OR {2}minutes OR {3}hours".format(\ + self.task_name,duration,duration/60,(duration/60)/60 + )) + def __repr__(self): + return "; ".join(["{0}={1}".format(k,v) for k,v in self.run_args.items()]) + + +class GridSearchModel: + """ + Define a set of model executions based on a set of parameters and their values variations. + + For the parameters format, please check the `Run` documentations. + + >>> grid = GridSearchModel("ls",test=["-l", "-h","-lh"]) + >>> grid.run() + """ + def __init__(self,command_base,**kwargs): + """ + Constructor + + Parameters + ---------- + command_base : str + command base + **kwargs : dict + parameters + """ + self.parameters = kwargs + self.cpt = 0 + self.number_of_combination = np.prod([len(v) for _,v in self.parameters.items()]) + + + + self.tasks = [] + for cpt in range(self.number_of_combination): + new_task = Run(str(cpt),command_base) + self.tasks.append(new_task) + + for key,values in self.parameters.items(): + split_ = int(self.number_of_combination/len(values)) + i = 0 + for val in values: + for task in self.tasks[i:i+split_]: + task.add_parameter(key,val) + i += split_ + + def __repr__(self): + return "\n".join([ t.__repr__() for t in self.tasks]) + + def run(self,log_filename=None): + """ + Run all the tasks defined + + Parameters + ---------- + log_filename : str, optional + log filename, by default None + """ + i=0 + for task in self.tasks: + task.run(log_filename=log_filename+"_"+str(i)) + i+=1 + + +if __name__ == "__main__": + g = GridSearchModel("ls",test=["-l", "-h","-lh"],rel=["-i"]) + print(g) + + #g.run() diff --git a/lib/torch_generator.py b/lib/torch_generator.py new file mode 100644 index 0000000..3613086 --- /dev/null +++ b/lib/torch_generator.py @@ -0,0 +1,50 @@ +import torch +from keras.preprocessing.sequence import pad_sequences +import numpy as np + +def chunks(lst, n): + """Yield successive n-sized chunks from lst.""" + for i in range(0, len(lst), n): + yield lst[i:i + n] + +class SentenceDataset(torch.utils.data.Dataset): + 'Characterizes a dataset for PyTorch' + def __init__(self, dataframe,tokenizer,max_len=96,batch_size=32,shuffle=True): + 'Initialization' + self.sentences = dataframe["sentence"].values + self.labels = dataframe["label"].values + self.tokenizer = tokenizer + self.max_len = max_len + + self.batch_size = batch_size + a = np.arange(len(dataframe)) + if shuffle: + np.random.shuffle(a) + self.batch_tokenization = list(chunks(a,batch_size)) + assert(len(self.batch_tokenization[0])==batch_size) + self.current_batch_id = 0 + self.boundaries = (0,0+batch_size) + self.current_batch_tokenized = self.tokenize(self.current_batch_id) + + def tokenize(self,batch_index): + X = [ self.tokenizer.encode(self.sentences[x],add_special_tokens = True,max_length=512,truncation=True) for x in self.batch_tokenization[batch_index]]# Tokenizer + X = pad_sequences(X, maxlen=self.max_len, dtype="long", value=0, truncating="post", padding="post").tolist() + return X + + def __len__(self): + 'Denotes the total number of samples' + return len(self.sentences) + def __getitem__(self, index): + 'Generates one sample of data' + if not index < self.boundaries[1] or not index >= self.boundaries[0]: + self.current_batch_id = index//self.batch_size + self.current_batch_tokenized = self.tokenize(self.current_batch_id) + self.boundaries= (self.current_batch_id*self.batch_size,self.current_batch_id*self.batch_size + self.batch_size) + # Load data and get label + + index_in_batch = index-self.boundaries[0] + #print(self.boundaries,index_in_batch) + X = self.current_batch_tokenized[index_in_batch] + M = [int(token_id > 0) for token_id in X] # attention mask + y = self.labels[index] + return torch.tensor(np.array(X)),torch.tensor(np.array(M)),torch.tensor(np.array(y)) \ No newline at end of file diff --git a/lib/utils.py b/lib/utils.py new file mode 100644 index 0000000..82531b3 --- /dev/null +++ b/lib/utils.py @@ -0,0 +1,220 @@ +# Basic import +import math +import argparse +import os +import json +import time +import datetime + +# Data Structure +import numpy as np +import geopandas as gpd +from shapely.geometry import Point,box + +# NLP +from nltk.tokenize import word_tokenize +from ngram import NGram + +# Visualisation and parallelisation +from tqdm import tqdm + +class LabelEncoder(): + def __init__(self): + self.dict_ = {} + self.cpt = 0 + + def fit_transform(self,list_element): + self.fit(list_element) + return self.transform(list_element) + + def fit(self,list_element): + for l in list_element: + if not l in self.dict_: + self.dict_[l] = self.cpt + self.cpt+=1 + def transform(self,list_element): + return [self.dict_[l] for l in list_element] + + def get_num_classes(self): + return self.cpt + +class TokenizerCustom(): + def __init__(self,vocab): + self.word_index = {vocab[i]:i for i in range(len(vocab))} + self.index_word = {i:vocab[i] for i in range(len(vocab))} + self.N = len(self.index_word) + def texts_to_sequences(self,listText): + seqs = [] + for text in listText: + seqs.append([self.word_index[word] for word in word_tokenize(text) if word in self.word_index]) + return seqs + + +class ConfigurationReader(object): + def __init__(self,configuration_file): + if not os.path.exists(configuration_file): + raise FileNotFoundError("'{0} file could not be found ! '".format(configuration_file)) + + self.configuration = json.load(open(configuration_file)) + + self.__argparser_desc = ("" if not "description" in self.configuration else self.configuration["description"]) + self.parser = argparse.ArgumentParser(description=self.__argparser_desc) + + self.parse_conf() + + def parse_conf(self): + if not "args" in self.configuration: + raise argparse.ArgumentError("","No args given in the configuration file") + + for dict_args in self.configuration["args"]: + if not isinstance(dict_args,dict): + raise ValueError("Args must be dictionnary") + + short_command = dict_args.get("short",None) + long_command = dict_args.get("long",None) + + if not short_command and not long_command: + raise ValueError("No command name was given !") + + add_func_dict_= {} + if "help" in dict_args: + add_func_dict_["help"]= dict_args["help"] + if "default" in dict_args: + add_func_dict_["default"]= dict_args["default"] + if "action" in dict_args: + add_func_dict_["action"]= dict_args["action"] + if "type" in dict_args: + add_func_dict_["type"]= eval(dict_args["type"]) + if "choices" in dict_args: + add_func_dict_["choices"]= dict_args["choices"] + + if not (short_command and long_command): + command = (short_command if not long_command else long_command) + self.parser.add_argument(command,**add_func_dict_) + + elif long_command and short_command: + self.parser.add_argument(short_command,long_command,**add_func_dict_) + + def parse_args(self,input_=None): + if not input_: + return self.parser.parse_args() + return self.parser.parse_args(input_) + + +class MetaDataSerializer(object): + def __init__(self, + model_name, + dataset_name, + rel_code, + cooc_sample_size, + adj_iteration, + ngram_size, + tolerance_value, + epochs, + embedding_dim, + word2vec_iter_nb, + index_fn, + keras_model_fn, + train_test_history_fn): + self.model_name = model_name + self.dataset_name = dataset_name + self.rel_code = rel_code + self.cooc_sample_size = cooc_sample_size + self.adj_iteration = adj_iteration + self.ngram_size = ngram_size + self.tolerance_value = tolerance_value + self.epochs = epochs + self.embedding_dim = embedding_dim + self.word2vec_iter_nb = word2vec_iter_nb + self.index_fn = index_fn + self.keras_model_fn = keras_model_fn + self.train_test_history_fn = train_test_history_fn + + def save(self,fn): + json.dump({ + "model_name":self.model_name, + "dataset_name" : self.dataset_name, + "rel_code" : self.rel_code, + "cooc_sample_size" : self.cooc_sample_size, + "adj_iteration" : self.adj_iteration, + "ngram_size" : self.ngram_size, + "tolerance_value" : self.tolerance_value, + "epochs" : self.epochs, + "embedding_dim" : self.embedding_dim, + "word2vec_iter_nb" : self.word2vec_iter_nb, + "index_fn" : self.index_fn, + "keras_model_fn" : self.keras_model_fn, + "train_test_history_fn" : self.train_test_history_fn + },open(fn,'w')) + +import time + +class Chronometer: + def __init__(self): + self.__task_begin_timestamp = {} + + def start(self, task_name): + """ + Start a new task chronometer + + Parameters + ---------- + task_name : str + task id + + Raises + ------ + ValueError + if a running task already exists with that name + """ + if task_name in self.__task_begin_timestamp: + raise ValueError( + "A running task exists with the name {0}!".format(task_name) + ) + self.__task_begin_timestamp[task_name] = time.time() + + def stop(self, task_name): + """ + Stop and return the duration of the task + + Parameters + ---------- + task_name : str + task id + + Returns + ------- + float + duration of the task in seconds + + Raises + ------ + ValueError + if no task exist with the id `task_name` + """ + if not task_name in self.__task_begin_timestamp: + raise ValueError("The {0} task does not exist!".format(task_name)) + + duration = time.time() - self.__task_begin_timestamp[task_name] + del self.__task_begin_timestamp[task_name] + + return duration + + + # Function to calculate the accuracy of our predictions vs labels +def flat_accuracy(preds, labels): + pred_flat = np.argmax(preds, axis=1).flatten() + labels_flat = labels.flatten() + return np.sum(pred_flat == labels_flat) / len(labels_flat) + + + +def format_time(elapsed): + ''' + Takes a time in seconds and returns a string hh:mm:ss + ''' + # Round to the nearest second. + elapsed_rounded = int(round((elapsed))) + + # Format as hh:mm:ss + return str(datetime.timedelta(seconds=elapsed_rounded)) \ No newline at end of file diff --git a/lib/utils_geo.py b/lib/utils_geo.py new file mode 100644 index 0000000..00b8a1a --- /dev/null +++ b/lib/utils_geo.py @@ -0,0 +1,444 @@ + +import geopandas as gpd +import numpy as np +import pandas as pd + +from shapely.geometry import Point,box +import healpy + +from tqdm import tqdm + + +import pandas as pd, numpy as np +from numba import njit +from helpers import read_geonames +from tqdm import tqdm +from joblib import Parallel,delayed + +import tensorflow as tf +import keras.backend as K + +def tf_deg2rad(deg): + pi_on_180 = 0.017453292519943295 + return deg * pi_on_180 + +# convert lat and lon to a healpix code encoding a region, with a given resolution +def latlon2healpix( lat , lon , res ): + lat = np.radians(lat) + lon = np.radians(lon) + xs = ( np.cos(lat) * np.cos(lon) ) # + ys = ( np.cos(lat) * np.sin(lon) ) # -> Sphere coordinates: https://vvvv.org/blog/polar-spherical-and-geographic-coordinates + zs = ( np.sin(lat) ) # + return healpy.vec2pix( int(res) , xs , ys , zs ) + +def healpix2latlon( code , nside ): + xs, ys, zs = healpy.pix2vec( nside , code ) + lat = np.arctan2(zs, np.sqrt(xs * xs + ys * ys)) * 180.0 / np.pi + lon = np.arctan2(ys, xs) * 180.0 / np.pi + return lat, lon + +def haversine_tf(y_true,y_pred): + """ + Return the geodesic distance between (lon1,lat1) and (lon2,lat2) coordinates + + Parameters + ---------- + lon1 : numeric or array-like (pandas Dataframe works also) + longitude of first coordinates + lat1 : numeric or array-like (pandas Dataframe works also) + latitude of first coordinates + lon2 : numeric or array-like (pandas Dataframe works also) + longitude of second coordinates + lat2 : numeric or array-like (pandas Dataframe works also) + longitude of second coordinates + + Returns + ------- + float or array-like + distance(s) value(s) + """ + lon1, lat1, lon2, lat2 = map(tf_deg2rad, [y_true[:,0], y_true[:,1], y_pred[:,0], y_pred[:,1]]) + dlon = lon2 - lon1 + dlat = lat2 - lat1 + a = K.sin(dlat/2.0)**2 + K.cos(lat1) * K.cos(lat2) * K.sin(dlon/2.0)**2 + + return 6367 * 2 * tf.math.asin(K.sqrt(a)) + +def haversine_tf_1circle(y_true,y_pred): + """ + Return the geodesic distance between (lon1,lat1) and (lon2,lat2) coordinates + + Parameters + ---------- + lon1 : numeric or array-like (pandas Dataframe works also) + longitude of first coordinates + lat1 : numeric or array-like (pandas Dataframe works also) + latitude of first coordinates + lon2 : numeric or array-like (pandas Dataframe works also) + longitude of second coordinates + lat2 : numeric or array-like (pandas Dataframe works also) + longitude of second coordinates + + Returns + ------- + float or array-like + distance(s) value(s) + """ + lon1, lat1, lon2, lat2 = map(tf_deg2rad, [y_true[:,0], y_true[:,1], y_pred[:,0], y_pred[:,1]]) + dlon = lon2 - lon1 + dlat = lat2 - lat1 + a = K.sin(dlat/2.0)**2 + K.cos(lat1) * K.cos(lat2) * K.sin(dlon/2.0)**2 + + return 1 * 2 * tf.math.asin(K.sqrt(a)) + +def to_wgs84_lat(lat): + return ((lat*180)-90) +def to_wgs84_lon(lon): + return ((lon*360)-180) + +def to_wgs84(x): + lon=to_wgs84_lon(x[:,0]) + lat=to_wgs84_lat(x[:,1]) + return tf.stack([lon,lat],axis=1) + +def accuracy_k(k=100):#km + def compute_metric(y_true,y_pred): + return K.less_equal(haversine_tf(to_wgs84(y_true),to_wgs84(y_pred)),k) + return compute_metric + +def haversine_pd(lon1, lat1, lon2, lat2): + """ + Return the geodesic distance between (lon1,lat1) and (lon2,lat2) coordinates + + Parameters + ---------- + lon1 : numeric or array-like (pandas Dataframe works also) + longitude of first coordinates + lat1 : numeric or array-like (pandas Dataframe works also) + latitude of first coordinates + lon2 : numeric or array-like (pandas Dataframe works also) + longitude of second coordinates + lat2 : numeric or array-like (pandas Dataframe works also) + longitude of second coordinates + + Returns + ------- + float or array-like + distance(s) value(s) + """ + lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2]) + dlon = lon2 - lon1 + dlat = lat2 - lat1 + a = np.sin(dlat/2.0)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2.0)**2 + + return 6367 * 2 * np.arcsin(np.sqrt(a)) + + +def get_adjacent(ids,lon1, lat1, lon2, lat2,threshold): + dist_ = haversine_pd(lon1, lat1, lon2, lat2) + return ids[dist_<threshold] + +def get_geonames_adjacency(geoname_data,threshold): + return Parallel(n_jobs=-1,backend="multiprocessing")(delayed(get_adjacent)(geoname_data.geonameid.values, + geoname_data.longitude, + geoname_data.latitude, + row.longitude, + row.latitude, + threshold) for ix,row in tqdm(geoname_data.iterrows(),total=len(geoname_data))) + + +def generate_couple(object_list): + """ + Return a randomly selected couple from an object list. + + Parameters + ---------- + object_list : list + object list + + Returns + ------- + list + list of coupled object + """ + couples = [] + lst = np.arange(len(object_list)) + for _ in range(len(object_list)): + if len(lst) == 1: + break + idx = np.random.choice(np.arange(len(lst))) + idx2 = np.random.choice(np.arange(len(lst))) + while idx2 == idx: + idx2 = np.random.choice(np.arange(len(lst))) + couples.append([object_list[lst[idx]],object_list[lst[idx2]]]) + lst = np.delete(lst,idx) + return couples + +def _hash_couple(o1,o2): + """ + Return an hash for two object ids. + + Parameters + ---------- + o1 : str or int + id of the first objeeect + o2 : str of int + id of the second object + + Returns + ------- + str + hash + """ + return "|".join(map(str,sorted([int(o1),int(o2)]))) + + + +def zero_one_encoding(long,lat): + """ + Encode coordinates (WGS84) between 0 and 1 + + Parameters + ---------- + long : float + longitude value + lat : float + latitude value + + Returns + ------- + float,float + longitude, latitude + """ + return ((long + 180.0 ) / 360.0), ((lat + 90.0 ) / 180.0) + +class Cell(object): + """ + A cell is box placed in geeographical space. + """ + def __init__(self,upperleft_x,upperleft_y,bottomright_x,bottomright_y,x,y): + """ + Constructor + + Parameters + ---------- + upperleft_x : float + upperleft longitude + upperleft_y : float + upperleft latitude + bottomright_x : float + bottom right longitude + bottomright_y : float + bottom right latitude + x : int + cell x coordinates in the grid + y : int + cell y coordinates in the grid + """ + self.upperleft_x,self.upperleft_y,self.bottomright_x,self.bottomright_y = upperleft_x,upperleft_y,bottomright_x,bottomright_y + self.box_ = box(self.upperleft_x,self.upperleft_y,self.bottomright_x,self.bottomright_y) + self.list_object={} # {id:Point(coord)} + + self.x,self.y = x, y + + def contains(self,lat,lon): + """ + Return true if the cell contains a point at given coordinates + + Parameters + ---------- + lat : float + latitude + lon : float + longitude + + Returns + ------- + bool + true if contains + """ + x,y = lon,lat + if x < self.upperleft_x or x > self.bottomright_x: + return False + if y < self.upperleft_y or y > self.bottomright_y: + return False + return True + + def add_object(self,id_,lat,lon): + """ + Connect an object to the cell + + Parameters + ---------- + id_ : int + id + lat : float + latitude + lon : float + longitude + """ + self.list_object[id_] = Point(lon,lat) + + def __repr__(self): + return "upperleft:{0}_{1}_;bottom_right:{2}_{3}".format(self.upperleft_x,self.upperleft_y,self.bottomright_x,self.bottomright_y) + + +class Grid(object): + """ + Define a grid + + """ + def __init__(self,upperleft_x,upperleft_y,bottomright_x,bottomright_y,cell_sub_div_index=[100,50]): + """ + Constructor + + Parameters + ---------- + upperleft_x : float + upperleft longitude + upperleft_y : float + upperleft latitude + bottomright_x : float + bottom right longitude + bottomright_y : float + bottom right latitude + cell_sub_div_index : list, optional + number of division in both latitude and longitude axis (longitude first), by default [100,50] + """ + self.upperleft_x,self.upperleft_y,self.bottomright_x,self.bottomright_y = upperleft_x,upperleft_y,bottomright_x,bottomright_y + + self.x_r = abs(self.bottomright_x - self.upperleft_x)/cell_sub_div_index[0] + self.y_r = abs(self.upperleft_y - self.bottomright_y )/cell_sub_div_index[1] + + self.c_x_r = self.x_r/cell_sub_div_index[0] # Redivide + self.c_y_r = self.y_r/cell_sub_div_index[1] + + self.cells = [] + self.inter_cells = [] + for i in range(cell_sub_div_index[1]): + self.cells.append([]) + for j in range(cell_sub_div_index[0]): + self.cells[-1].append(Cell( + self.upperleft_x+j*self.x_r, + self.upperleft_y+i*self.y_r, + self.upperleft_x+((j+1)*self.x_r), + self.upperleft_y+((i+1)*self.y_r), + j,i) + ) + dec_y = 0 + for i in range(cell_sub_div_index[1]): + self.inter_cells.append([]) + dec_x = 0 + for j in range(cell_sub_div_index[0]): + self.inter_cells[-1].append(Cell( + self.upperleft_x+(j*self.x_r)-self.c_x_r, # TOP + self.upperleft_y+(i*self.y_r)-dec_y, + self.upperleft_x+((j+1)*self.x_r)-self.c_x_r,#(self.u_pos*self.c_x_r), + self.upperleft_y+((i+1)*self.y_r)+self.c_y_r, + j,i) + ) + self.inter_cells[-1].append(Cell( + self.upperleft_x+(j*self.x_r)-self.c_x_r, # CENTER + self.upperleft_y+(i*self.y_r)-self.c_y_r, + self.upperleft_x+((j+1)*self.x_r)+self.c_x_r, + self.upperleft_y+((i+1)*self.y_r)+self.c_y_r, + j,i) + ) + self.inter_cells[-1].append(Cell( + self.upperleft_x+(j*self.x_r)+dec_x, # CENTER + self.upperleft_y+(i*self.y_r)-self.c_y_r, + self.upperleft_x+((j+1)*self.x_r)-self.c_x_r, #LEFT + self.upperleft_y+((i+1)*self.y_r)+self.c_y_r, + j,i) + ) + dec_x = self.c_x_r + dec_y = self.c_y_r + + def fit_data(self,data = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))): + """ + + To avoid unnecessary check when connecting an entity to one or multiple cells, we + filter cells that does not appears in our geographic context (here countries surface). + + Parameters + ---------- + data : GeoDataFrame + geographic context + """ + world = data + world["nn"] = 1 + dissolved = world.dissolve(by="nn").iloc[0].geometry + new_cells= [] + new_inter_cells=[] + for i in tqdm(range(len(self.cells))): + for j in range(len(self.cells[i])): + if dissolved.intersects(self.cells[i][j].box_): + new_cells.append(self.cells[i][j]) + new_inter_cells.extend(self.inter_cells[i][j*3:(j+1)*3]) + + self.cells=new_cells + self.inter_cells = new_inter_cells + + + def __add__(self,a): + """ + Add an object to the grid + + Parameters + ---------- + a : tuple + (id, latitude, longitude) + """ + for c1 in range(len(self.cells)): + if self.cells[c1].contains(a[1],a[2]): + self.cells[c1].add_object(*a) + + for c1 in range(len(self.inter_cells)): + if self.inter_cells[c1].contains(a[1],a[2]): + self.inter_cells[c1].add_object(*a) + + def get_adjacent_relationships(self,random_iteration=10): + """ + Return a list of adjacent relationships founds in each cell. + + Parameters + ---------- + random_iteration : int, optional + number of iteration for random selection of adjacency relationships, by default 10 + + Returns + ------- + list + adjacency relationships + """ + relationships = set([]) + for c1 in tqdm(range(len(self.cells))): + for _ in range(random_iteration): + for t in generate_couple(list(self.cells[c1].list_object.keys())): + relationships.add(_hash_couple(t[0],t[1])) + + for c1 in tqdm(range(len(self.inter_cells))): + for _ in range(random_iteration): + for t in generate_couple(list(self.inter_cells[c1].list_object.keys())): + relationships.add(_hash_couple(t[0],t[1])) + return relationships + + + +def get_adjacency_rels(geodataframe,bounds,subdiv_tuple,random_iter_adjacency): + g = Grid(*bounds,subdiv_tuple) + g.fit_data() + [g+(int(row.geonameid),row.latitude,row.longitude) for ix,row in tqdm(geodataframe["geonameid longitude latitude".split()].iterrows(),total=len(geodataframe))] + return [[int(i) for i in r.split("|")] for r in g.get_adjacent_relationships(random_iter_adjacency)] + +def get_geonames_inclusion_rel(geonames_data,geonames_hierarchy_data_fn): + geonames_hierarchy_data = pd.read_csv(geonames_hierarchy_data_fn,sep="\t",header=None,names="parentId,childId,type".split(",")).fillna("") + geonamesIDS = set(geonames_data.geonameid.values) + filter_mask = (geonames_hierarchy_data.childId.isin(geonamesIDS) & geonames_hierarchy_data.parentId.isin(geonamesIDS)) + return (geonames_hierarchy_data[filter_mask]["childId parentId".split()].values.tolist()) + +def get_bounds(geodataframe): + geodataframe["geometry"] = geodataframe["longitude latitude".split()].apply(lambda x: Point(x.longitude,x.latitude),axis=1) + geodataframe = gpd.GeoDataFrame(geodataframe) + geodataframe["i"]=1 + return geodataframe.dissolve("i").bounds.values[0] # Required to get adjacency relationships diff --git a/lib/word_index.py b/lib/word_index.py new file mode 100644 index 0000000..98e66f8 --- /dev/null +++ b/lib/word_index.py @@ -0,0 +1,180 @@ +import json + +import numpy as np + +from ngram import NGram + +# Machine learning +from gensim.models import Word2Vec + +class WordIndex(): + """ + Class used for encoding words in ngram representation + """ + def __init__(self,loaded = False): + """ + Constructor + + Parameters + ---------- + loaded : bool + if loaded from external file + """ + self.ngram_index = {"":0} + self.index_ngram = {0:""} + self.cpt = 0 + self.max_len = 0 + + self.loaded = loaded + + def split_and_add(self,word): + """ + Split word in multiple ngram and add each one of them to the index + + Parameters + ---------- + word : str + a word + """ + grams = word.lower().split(" ") + [self.add(subword) for subword in grams ] + self.max_len = max(self.max_len,len(grams)) + + def add(self,subword): + """ + Add a ngram to the index + + Parameters + ---------- + ngram : str + ngram + """ + if not subword in self.ngram_index: + self.cpt+=1 + self.ngram_index[subword]=self.cpt + self.index_ngram[self.cpt]=subword + + + def encode(self,word): + """ + Return a ngram representation of a word + + Parameters + ---------- + word : str + a word + + Returns + ------- + list of int + listfrom shapely.geometry import Point,box + of ngram index + """ + subwords = [w.lower() for w in word.split(" ")] + if not self.loaded: + [self.add(ng) for ng in subwords if not ng in self.ngram_index] + if self.max_len < len(subwords): + self.max_len = max(self.max_len,len(subwords)) + return self.complete([self.ngram_index[ng] for ng in subwords if ng in self.ngram_index],self.max_len) + + def complete(self,ngram_encoding,MAX_LEN,filling_item=0): + """ + Complete a ngram encoded version of word with void ngram. It's necessary for neural network. + + Parameters + ---------- + ngram_encoding : list of int + first encoding of a word + MAX_LEN : int + desired length of the encoding + filling_item : int, optional + ngram index you wish to use, by default 0 + + Returns + ------- + list of int + list of ngram index + """ + if self.loaded and len(ngram_encoding) >=MAX_LEN: + return ngram_encoding[:MAX_LEN] + assert len(ngram_encoding) <= MAX_LEN + diff = MAX_LEN - len(ngram_encoding) + ngram_encoding.extend([filling_item]*diff) + return ngram_encoding + + def get_embedding_layer(self,texts,dim=100,**kwargs): + """ + Return an embedding matrix for each ngram using encoded texts. Using gensim.Word2vec model. + + Parameters + ---------- + texts : list of [list of int] + list of encoded word + dim : int, optional + embedding dimension, by default 100 + + Returns + ------- + np.array + embedding matrix + """ + model = Word2Vec([[str(w) for w in t] for t in texts], size=dim,window=5, min_count=1, workers=4,**kwargs) + N = len(self.ngram_index) + embedding_matrix = np.zeros((N,dim)) + for i in range(N): + if str(i) in model.wv: + embedding_matrix[i] = model.wv[str(i)] + return embedding_matrix + + def save(self,fn): + """ + + Save the NgramIndex + + Parameters + ---------- + fn : str + output filename + """ + data = { + "word_index": self.ngram_index, + "cpt_state": self.cpt, + "max_len_state": self.max_len + } + json.dump(data,open(fn,'w')) + + @staticmethod + def load(fn): + """ + + Load a NgramIndex state from a file. + + Parameters + ---------- + fn : str + input filename + + Returns + ------- + NgramIndex + ngram index + + Raises + ------ + KeyError + raised if a required field does not appear in the input file + """ + try: + data = json.load(open(fn)) + except json.JSONDecodeError: + print("Data file must be a JSON") + for key in ["word_index","cpt_state","max_len_state"]: + if not key in data: + raise KeyError("{0} field cannot be found in given file".format(key)) + new_obj = WordIndex(loaded=True) + new_obj.ngram_index = data["ngram_index"] + new_obj.index_ngram = {v:k for k,v in new_obj.ngram_index.items()} + new_obj.cpt = data["cpt_state"] + new_obj.max_len = data["max_len_state"] + return new_obj + diff --git a/parser_config/toponym_combination_embedding.json b/parser_config/toponym_combination_embedding.json new file mode 100644 index 0000000..260d6ec --- /dev/null +++ b/parser_config/toponym_combination_embedding.json @@ -0,0 +1,20 @@ +{ + "description": "Toponym Combination", + "args": [ + { "short": "geoname_input", "help": "Filepath of the Geonames file you want to use." }, + { "short": "geoname_hierachy_input", "help": "Filepath of the Geonames file you want to use." }, + { "short": "-v", "long": "--verbose", "action": "store_true" }, + { "short": "-i", "long": "--inclusion", "action": "store_true" }, + { "short": "-a", "long": "--adjacency", "action": "store_true" }, + { "short": "-w", "long": "--wikipedia-cooc", "action": "store_true" }, + { "long": "--wikipedia-cooc-fn","help":"Cooccurrence data filename"}, + { "long": "--cooc-sample-size", "type": "int", "default": 1 }, + {"long": "--adjacency-iteration", "type":"int","default":1}, + { "short": "-n", "long": "--ngram-size", "type": "int", "default": 4 }, + { "long": "--ngram-word2vec-iter", "type": "int", "default": 50 }, + { "short": "-t", "long": "--tolerance-value", "type": "float", "default": 0.002 }, + { "short": "-e", "long": "--epochs", "type": "int", "default": 100 }, + { "short": "-d", "long": "--dimension", "type": "int", "default": 256 }, + { "long": "--admin_code_1", "default": "None" } + ] +} \ No newline at end of file diff --git a/parser_config/toponym_combination_embedding_v2.json b/parser_config/toponym_combination_embedding_v2.json new file mode 100644 index 0000000..345c1d7 --- /dev/null +++ b/parser_config/toponym_combination_embedding_v2.json @@ -0,0 +1,20 @@ +{ + "description": "Toponym Combination", + "args": [ + { "short": "geoname_input", "help": "Filepath of the Geonames file you want to use." }, + { "short": "geoname_hierachy_input", "help": "Filepath of the Geonames file you want to use." }, + { "short": "-v", "long": "--verbose", "action": "store_true" }, + { "short": "-i", "long": "--inclusion", "action": "store_true" }, + { "short": "-a", "long": "--adjacency", "action": "store_true" }, + { "short": "-w", "long": "--wikipedia-cooc", "action": "store_true" }, + { "long": "--wikipedia-cooc-fn","help":"Cooccurrence data filename"}, + { "long": "--cooc-sample-size", "type": "int", "default": 1 }, + {"long": "--adjacency-iteration", "type":"int","default":1}, + { "short": "-n", "long": "--ngram-size", "type": "int", "default": 4 }, + { "long": "--ngram-word2vec-iter", "type": "int", "default": 50 }, + { "short": "-t", "long": "--tolerance-value", "type": "float", "default": 100 }, + { "short": "-e", "long": "--epochs", "type": "int", "default": 100 }, + { "short": "-d", "long": "--dimension", "type": "int", "default": 256 }, + { "long": "--admin_code_1", "default": "None" } + ] +} \ No newline at end of file diff --git a/parser_config/toponym_combination_embedding_v3.json b/parser_config/toponym_combination_embedding_v3.json new file mode 100644 index 0000000..507e9c5 --- /dev/null +++ b/parser_config/toponym_combination_embedding_v3.json @@ -0,0 +1,20 @@ +{ + "description": "Toponym Combination", + "args": [ + { "short": "dataset_name", "help": "Filepath of the Geonames file you want to use." }, + { "short": "geoname_inclusion", "help": "Filepath of the Geonames file you want to use." }, + { "short": "geonames_adjacent", "help": "Filepath of the Geonames file you want to use." }, + { "long": "wikipedia_cooc", "help": "Cooccurrence data filename" }, + { "short": "-v", "long": "--verbose", "action": "store_true" }, + { "short": "-i", "long": "--inclusion", "action": "store_true" }, + { "short": "-a", "long": "--adjacency", "action": "store_true" }, + { "short": "-w", "long": "--wikipedia", "action": "store_true" }, + { "short": "-n", "long": "--ngram-size", "type": "int", "default": 4 }, + { "long": "--ngram-word2vec-iter", "type": "int", "default": 50 }, + { "short": "-t", "long": "--tolerance-value", "type": "float", "default": 100 }, + { "short": "-e", "long": "--epochs", "type": "int", "default": 100 }, + { "short": "-d", "long": "--dimension", "type": "int", "default": 256 }, + { "short": "-l", "long": "--lstm-layer", "type": "int", "default": 2, "choices": [1, 2] }, + { "long": "--tokenization-method", "type": "str", "default": "char-level", "choices": ["char-level", "word-level", "bert"] } + ] +} \ No newline at end of file diff --git a/predict_toponym_coordinates.py b/predict_toponym_coordinates.py new file mode 100644 index 0000000..ec5d967 --- /dev/null +++ b/predict_toponym_coordinates.py @@ -0,0 +1,133 @@ +from keras.models import load_model +import os +import tensorflow as tf +import keras.backend as K +from lib.ngram_index import NgramIndex +from lib.word_index import WordIndex +import numpy as np + +from tensorflow.python.keras.backend import set_session +from tensorflow.python.keras.models import load_model + + +from lib.utils_geo import haversine_tf_1circle +sess = None +graph = None + +def lat_accuracy(LAT_TOL =1/180.): + def accuracy_at_k_lat(y_true, y_pred): + """ + Metrics use to measure the accuracy of the coordinate prediction. But in comparison to the normal accuracy metrics, we add a tolerance threshold due to the (quasi) impossible + task for neural network to obtain the exact coordinate. + + Parameters + ---------- + y_true : tf.Tensor + truth data + y_pred : tf.Tensor + predicted output + """ + diff = tf.abs(y_true - y_pred) + fit = tf.dtypes.cast(tf.less(diff,LAT_TOL),tf.int64) + return tf.reduce_sum(fit)/tf.size(y_pred,out_type=tf.dtypes.int64) + return accuracy_at_k_lat + +def lon_accuracy(LON_TOL=1/360.): + def accuracy_at_k_lon(y_true, y_pred): + """ + Metrics use to measure the accuracy of the coordinate prediction. But in comparison to the normal accuracy metrics, we add a tolerance threshold due to the (quasi) impossible + task for neural network to obtain the exact coordinate. + + Parameters + ---------- + y_true : tf.Tensor + truth data + y_pred : tf.Tensor + predicted output + """ + diff = tf.abs(y_true - y_pred) + fit = tf.dtypes.cast(tf.less(diff,LON_TOL),tf.int64) + return tf.reduce_sum(fit)/tf.size(y_pred,out_type=tf.dtypes.int64) + return accuracy_at_k_lon + +class Geocoder(object): + """ + >>>geocoder = Geocoder("LSTM_FR.txt_20_4_0.002_None_A_I_C.h5","index_4gram_FR_backup.txt") + >>>lon,lat = geocoder.get_coord("Paris","New-York") + >>>lon,lat = geocoder.wgs_coord(lon,lat) + >>>geocoder.plot_coord("Paris,New-York",lat,lon) + + if you want an interactive map using leafletJS, set to True the `interactive_map` parameter of `Geocoder.plot_coord()` + """ + def __init__(self,keras_model_fn,ngram_index_file): + # global sess + # global graph + # sess = tf.compat.v1.Session() + # graph = tf.compat.v1.get_default_graph() + # set_session(sess) + self.keras_model = load_model(keras_model_fn,custom_objects={"loss":haversine_tf_1circle},compile=False)#custom_objects={"accuracy_at_k_lat":lat_accuracy(),"accuracy_at_k_lon":lon_accuracy()}) + self.ngram_encoder = NgramIndex.load(ngram_index_file) + + def get_coord(self,toponym,context_toponym): + global sess + global graph + p = self.ngram_encoder.complete(self.ngram_encoder.encode(toponym),self.ngram_encoder.max_len) + c = self.ngram_encoder.complete(self.ngram_encoder.encode(context_toponym),self.ngram_encoder.max_len) + p = np.array(p) + c = np.array(c) + # with sess.as_default(): + # with graph.as_default(): + coord = self.keras_model.predict([[p],[c]]) + return coord[0][0],coord[0][1] + + def get_coords(self,list_toponym,list_toponym_context): + p = [self.ngram_encoder.complete(self.ngram_encoder.encode(toponym),self.ngram_encoder.max_len) for toponym in list_toponym] + c = [self.ngram_encoder.complete(self.ngram_encoder.encode(toponym),self.ngram_encoder.max_len) for toponym in list_toponym_context] + + p = np.array(p) + c = np.array(c) + + coords = self.keras_model.predict([p,c]) + return coords[0],coords[1] + + def wgs_coord(self,lon,lat): + return ((lon*360)-180),((lat*180)-90) + + def plot_coord(self,toponym,lat,lon,interactive_map=False,**kwargs): + if interactive_map: + import folium + import tempfile + import webbrowser + fp = tempfile.NamedTemporaryFile(delete=False) + m = folium.Map() + folium.Marker([lat, lon], popup=toponym).add_to(m) + m.save(fp.name) + webbrowser.open('file://' + fp.name) + else: + import matplotlib.pyplot as plt + import geopandas + fig, ax = plt.subplots(1,**kwargs) + world = geopandas.read_file(geopandas.datasets.get_path('naturalearth_lowres')) + world.plot(color='white', edgecolor='black',ax=ax) + ax.plot(lon,lat,marker='o', color='red', markersize=5) + plt.show() + + + +if __name__ == "__main__": + from flask import Flask, escape, request, render_template + + app = Flask(__name__) + + + geocoder = Geocoder("outputs/LSTM_FR.txt_100_4_0.002_None_A_I_C.h5","./outputs/FR.txt_100_4_0.002_None_A_I_C_index") + + @app.route('/',methods=["GET"]) + def display(): + toponym = request.args.get("top", "Paris") + c_toponym = request.args.get("c_top", "Cherbourg") + lon,lat = geocoder.get_coord(toponym,c_toponym) + lon,lat = geocoder.wgs_coord(lon,lat) + return render_template("skeleton.html",lat=lat,lon=lon) + + app.run(host='0.0.0.0') \ No newline at end of file diff --git a/region_model.py b/region_model.py new file mode 100644 index 0000000..b7a6673 --- /dev/null +++ b/region_model.py @@ -0,0 +1,199 @@ +# Base module +import os + +# Structure +import pandas as pd + +# DEEPL module +from keras.layers import Dense, Input, Embedding,concatenate,Bidirectional,LSTM,Dropout +from keras.models import Model +from keras.callbacks import ModelCheckpoint +from tensorflow.keras.layers import Lambda +import keras.backend as K +import tensorflow as tf +from lib.custom_layer import * + +# Custom module +from lib.ngram_index import NgramIndex +from lib.utils import ConfigurationReader, MetaDataSerializer,LabelEncoder +from lib.metrics import lat_accuracy,lon_accuracy +from lib.data_generator import DataGenerator,CoOccurrences,load_embedding,Inclusion,Adjacency +from lib.utils_geo import haversine_tf,accuracy_k,haversine_tf_1circle + +# Logging +import logging + +logging.getLogger('gensim').setLevel(logging.WARNING) + +from helpers import EpochTimer + +# LOGGING CONF +logging.basicConfig( + format='[%(asctime)s][%(levelname)s] %(message)s ', + datefmt='%m/%d/%Y %I:%M:%S %p', + level=logging.INFO + ) + +args = ConfigurationReader("./parser_config/toponym_combination_embedding_v2.json")\ + .parse_args()#("-i --inclusion-fn ../data/geonamesData/hierarchy.txt ../data/geonamesData/allCountries.txt ../data/embeddings/word2vec4gram/4gramWiki+geonames_index.json ../data/embeddings/word2vec4gram/embedding4gramWiki+Geonames.bin".split()) + +#.parse_args("-w --wikipedia-cooc-fn subsetCoocALLv2.csv ../data/geonamesData/allCountries.txt ../data/embeddings/word2vec4gram/4gramWiki+geonames_index.json ../data/embeddings/word2vec4gram/embedding4gramWiki+Geonames.bin".split()) + +# +################################################# +############# MODEL TRAINING PARAMETER ########## +################################################# +NGRAM_SIZE = args.ngram_size +ACCURACY_TOLERANCE = args.k_value +EPOCHS = args.epochs +ADJACENCY_SAMPLING = args.adjacency_sample +COOC_SAMPLING = args.cooc_sample +WORDVEC_ITER = 50 +EMBEDDING_DIM = args.dimension +BATCH_SIZE = args.batch_size +################################################# +########## FILENAME VARIABLE #################### +################################################# +# check for output dir +if not os.path.exists("outputs/"): + os.makedirs("outputs/") + +GEONAME_FN = args.geoname_input +DATASET_NAME = args.geoname_input.split("/")[-1] +GEONAMES_HIERARCHY_FN = args.inclusion_fn +ADJACENCY_REL_FILENAME = args.adjacency_fn +COOC_FN = args.wikipedia_cooc_fn + +PREFIX_OUTPUT_FN = "REGION_{0}_{1}_{2}_{3}".format( + GEONAME_FN.split("/")[-1], + EPOCHS, + NGRAM_SIZE, + ACCURACY_TOLERANCE) + +REL_CODE="" +if args.adjacency: + PREFIX_OUTPUT_FN += "_A" + REL_CODE+= "A" +if args.inclusion: + PREFIX_OUTPUT_FN += "_I" + REL_CODE+= "I" +if args.wikipedia_cooc: + PREFIX_OUTPUT_FN += "_C" + REL_CODE+= "C" + +MODEL_OUTPUT_FN = "outputs/{0}.h5".format(PREFIX_OUTPUT_FN) +INDEX_FN = "outputs/{0}_index".format(PREFIX_OUTPUT_FN) +HISTORY_FN = "outputs/{0}.csv".format(PREFIX_OUTPUT_FN) + + +meta_data = MetaDataSerializer( + DATASET_NAME, + REL_CODE, + COOC_SAMPLING, + ADJACENCY_SAMPLING, + NGRAM_SIZE, + ACCURACY_TOLERANCE, + EPOCHS, + EMBEDDING_DIM, + WORDVEC_ITER, + INDEX_FN, + MODEL_OUTPUT_FN, + HISTORY_FN +) +meta_data.save("outputs/{0}.json".format(PREFIX_OUTPUT_FN)) + + +### PUT DATASRC + GENERATOR + +index = NgramIndex.load(args.ngram_index_fn) + +train_src = [] +test_src = [] + +class_encoder = LabelEncoder() +if args.wikipedia_cooc: + train_src.append(CoOccurrences(COOC_FN + "_train.csv",class_encoder,sampling=4)) + test_src.append(CoOccurrences(COOC_FN + "_test.csv",class_encoder,sampling=4)) + +if args.adjacency: + a_train = Adjacency(ADJACENCY_REL_FILENAME + "_train.csv",GEONAME_FN,sampling=ADJACENCY_SAMPLING,gzip=False) + a_test = Adjacency(ADJACENCY_REL_FILENAME + "_test.csv",GEONAME_FN,sampling=ADJACENCY_SAMPLING,gzip=False) + train_src.append(a_train) + test_src.append(a_test) + +if args.inclusion: + i_train = Inclusion(GEONAME_FN,GEONAMES_HIERARCHY_FN+"_train.csv") + i_test = Inclusion(GEONAME_FN,GEONAMES_HIERARCHY_FN+"_test.csv") + train_src.append(i_train) + test_src.append(i_test) +#Adjacency + + + +d_train = DataGenerator(train_src,index,class_encoder,batch_size=BATCH_SIZE,only_healpix=True) +d_test = DataGenerator(test_src,index,class_encoder,batch_size=BATCH_SIZE,only_healpix=True) + +num_words = len(index.index_ngram) + +############################################################################################# +################################# NGRAM EMBEDDINGS ########################################## +############################################################################################# + +embedding_weights = load_embedding(args.embedding_fn) + + +############################################################################################# +################################# MODEL DEFINITION ########################################## +############################################################################################# + +from keras import regularizers + +input_1 = Input(shape=(index.max_len,)) +input_2 = Input(shape=(index.max_len,)) + +embedding_layer = Embedding(num_words, EMBEDDING_DIM,input_length=index.max_len,trainable=False)#, trainable=True) + +x1 = embedding_layer(input_1) +x2 = embedding_layer(input_2) + +# Each LSTM learn on a permutation of the input toponyms +biLSTM = Bidirectional(LSTM(32,activation="pentanh", recurrent_activation="pentanh")) +x1 = biLSTM(x1) +x2 = biLSTM(x2) +x = concatenate([x1,x2])#,x3]) + +#x = Dense(class_encoder.get_num_classes()*2,activation="relu")(x) + + +aux_layer = Dense(class_encoder.get_num_classes(),activation="softmax",name="aux_layer")(x) + +model = Model(inputs = [input_1,input_2], outputs = aux_layer)#input_3 + +model.compile(loss={"aux_layer":"categorical_crossentropy"}, optimizer='adam',metrics={"aux_layer":"accuracy"}) + + +############################################################################################# +################################# TRAINING LAUNCH ########################################### +############################################################################################# + +checkpoint = ModelCheckpoint(MODEL_OUTPUT_FN + ".part", monitor='loss', verbose=1, + save_best_only=True, mode='auto', period=1) + +epoch_timer = EpochTimer("outputs/"+PREFIX_OUTPUT_FN+"_epoch_timer_output.csv") + + +history = model.fit_generator(generator=d_train, + validation_data=d_test, + verbose=True, + epochs=EPOCHS, + callbacks=[checkpoint,epoch_timer]) + + +hist_df = pd.DataFrame(history.history) +hist_df.to_csv(HISTORY_FN) + +model.save(MODEL_OUTPUT_FN) + +# Erase Model Checkpoint file +if os.path.exists(MODEL_OUTPUT_FN + ".part"): + os.remove(MODEL_OUTPUT_FN + ".part") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a2016e7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,27 @@ +#pyroutelib3 +node2vec +#osrm +geopandas +pandas +numpy +tqdm +networkx +matplotlib +joblib +gensim +sklearn +tensorflow +keras +ngram +shapely +sqlitedict +nltk +folium +flask +numba +healpy +stanza +spacy +torch +torchvision +transformers \ No newline at end of file diff --git a/run_multiple_configurations.py b/run_multiple_configurations.py new file mode 100644 index 0000000..49753c5 --- /dev/null +++ b/run_multiple_configurations.py @@ -0,0 +1,22 @@ +from lib.run import GridSearchModel +from collections import OrderedDict +c_f = "--wikipedia-cooc-fn ../data/wikipedia/cooccurrence_FR.txt" + +# Init GridsearchModel +grid = GridSearchModel(\ + "python3 combination_embeddingsv3inverse.py", + **OrderedDict({ # necessary because some args have to be given in a certain order + "rel":["-w "+c_f,("-i -w "+c_f),"-a -w "+c_f,"-a -i -w "+c_f], # ,"-a -i -w "+c_f ,"-i -a" + "-n":[4], + "--ngram-word2vec-iter" :[100], + "-e":[100], + "geoname_fn":"../data/geonamesData/FR.txt".split(), + "hierarchy_fn":"../data/geonamesData/hierarchy.txt".split() + }.items())) + +print("########### THE FOLLOWING COMMAND(S) WILL BE EXECUTED ###########" ) +[print(task.get_command()) for task in grid.tasks] +print("#################################################################") +grid.run("outputs/log_{0}".format("FR_model_v2")) + +#["-w --wikipedia-cooc-fn ../data/wikipedia/cooccurrence_FR.txt","-w --wikipedia-cooc-fn ../data/wikipedia/cooccurrence_FR.txt -a","-w --wikipedia-cooc-fn ../data/wikipedia/cooccurrence_FR.txt -i"] \ No newline at end of file diff --git a/scripts/embeddingngram.py b/scripts/embeddingngram.py new file mode 100644 index 0000000..a9773eb --- /dev/null +++ b/scripts/embeddingngram.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# coding: utf-8 + + +from lib.ngram_index import NgramIndex +from lib.utils_geo import read_geonames + + + +import pandas as pd +import numpy as np +from tqdm import tqdm + + +from tqdm import tqdm + + + +from gensim.models import Word2Vec +import logging +logging.basicConfig(level="INFO") + + + +df_cooc = pd.read_csv("../data/wikipedia/cooccurrence_ALL.txt",sep="\t") +df_geo = read_geonames("../data/geonamesData/allCountries.txt") + + +geonames_label = df_geo.name.values.tolist() +wiki_labels = df_cooc.title.values.tolist() +p= [wiki_labels.extend(x.split("|")) for x in df_cooc["interlinks"].values] + + +del df_geo +del df_cooc + +N = 5 + + +ng = NgramIndex(N) +p = [ng.split_and_add(x) for x in tqdm(geonames_label)] +p = [ng.split_and_add(x) for x in tqdm(wiki_labels)] +ng.save("{0}gramWiki+Geonames_index.json".format(N)) + +geonames_label.extend(wiki_labels) + +class MySentences(object): + def __init__(self, texts): + self.texts = texts + + def __iter__(self): + for w in self.texts: + yield [str(x)for x in ng.encode(w)] + +model = Word2Vec(MySentences(geonames_label), size=100, window=5, min_count=1, workers=4,sg=1) +model.save("embedding{0}gramWiki+Geonames.bin".format(5)) + + + diff --git a/scripts/generate_cooc_geocoding_dataset.py b/scripts/generate_cooc_geocoding_dataset.py new file mode 100644 index 0000000..232f40b --- /dev/null +++ b/scripts/generate_cooc_geocoding_dataset.py @@ -0,0 +1,41 @@ +import pandas as pd +import re + +#### TODO NEED TO add ARGPARSE !!! +def parse_title_wiki(title_wiki): + """ + Parse Wikipedia title + + Parameters + ---------- + title_wiki : str + wikipedia title + + Returns + ------- + str + parsed wikipedia title + """ + return re.sub("\(.*\)", "", title_wiki).strip().lower() + + +df = pd.read_csv("./cooccurrence_US_FR.txt",sep="\t") + +df["interlinks"] = df.interlinks.apply(lambda x : x.split("|")) +df["interlinks"] = df.interlinks.apply(lambda x : [parse_title_wiki(i) for i in x]) + +df["title"] = df.title.apply(parse_title_wiki) + +def generated_inputs(x): + output = [] + for interlink in x.interlinks: + output.append([x.title,interlink,x.longitude,x.latitude]) + return output + +output_ = [] +for ix,row in df.iterrows(): + output_.extend(generated_inputs(row)) + +new_df = pd.DataFrame(output_,columns="name1 name2 longitude latitude".split()) +new_df = new_df.sample(frac=1) +new_df.to_csv("us_fr_cooc_test.csv",index=False) \ No newline at end of file diff --git a/scripts/get_all_adjacency_rel.py b/scripts/get_all_adjacency_rel.py new file mode 100644 index 0000000..23382a6 --- /dev/null +++ b/scripts/get_all_adjacency_rel.py @@ -0,0 +1,88 @@ +import pandas as pd, numpy as np +from numba import njit +from helpers import read_geonames +from tqdm import tqdm +from joblib import Parallel,delayed +import geopandas as gpd +from lib.utils_geo import Grid,haversine_pd +import matplotlib.pyplot as plt + +import argparse + +parser = argparse.ArgumentParser() + +parser.add_argument("geoname_fn") +parser.add_argument("kilometer_threshold",type=int,default=20) +parser.add_argument("output_fn_prefix") + +args = parser.parse_args("../data/geonamesData/allCountries.txt 20 /home/jacques/ALL_ADJ_224+_".split()) + +GEONAME_FN = args.geoname_fn +PREFIX_OUTPUT_FN = args.output_fn_prefix +KM_THRESHOLD = args.kilometer_threshold + +df = read_geonames(GEONAME_FN) + +def to_str(list_): + """ + Return str representation for each value in list_ + + Parameters + ---------- + list_ : array + array + + Returns + ------- + array + str list + """ + return list(map(str,list_)) + +def get_adjacent(geonameid,ids,lon1, lat1, lon2, lat2,threshold): + """ + Write adjacent entry in geonames for a selected entry + """ + dist_ = haversine_pd(lon1, lat1, lon2, lat2) + adj_ids = ids[dist_<threshold] + out_.write("\n{0},{1},{2},{3}".format(geonameid,"|".join(to_str(adj_ids)),lat2,lon2)) + out_.flush() + + +# WE BUILD a grid over the world map +# It allows to limit unnecessary calculus thus accelerate the whole process +world = gpd.read_file("/media/jacques/DATA/GEODATA/WORLD/world.geo.50m.dissolved") +g = Grid(*world.bounds.values[0],[40,20]) #We build a grid of cell of 40° by 20° +g.fit_data(world) + +# Prepare first output +first_output_fn = "{1}{0}_cells.csv".format(KM_THRESHOLD,PREFIX_OUTPUT_FN) +out_ = open(first_output_fn,'w') +out_.write("geonameid,adjacent_geonameid,latitude,longitude") # HEADER +out_.flush() # Avoid writing bugs + +def get_rels(cells_list): + for c in tqdm(cells_list): + + mask1 = (df.latitude <= c.bottomright_y) & (df.latitude >= c.upperleft_y) + new_df = df[mask1].copy() + mask2 = (new_df.longitude >= c.upperleft_x) & (new_df.longitude <= c.bottomright_x) + new_df = new_df[mask2] + for ix,row in new_df.iterrows(): + get_adjacent(row.geonameid,new_df.geonameid.values,new_df.longitude,new_df.latitude,row.longitude,row.latitude,KM_THRESHOLD) + #Parallel(n_jobs=-1,backend="multiprocessing",temp_folder="/home/jacques/temp/")(delayed(get_adjacent)(row.geonameid,new_df.geonameid.values,new_df.longitude,new_df.latitude,row.longitude,row.latitude,KM_THRESHOLD) for ix,row in new_df.iterrows()) + +world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres')) +ax = world.plot(color="white",edgecolor="black") +for c in g.cells[224:]: + ax.plot(*c.box_.exterior.xy) +plt.show() +get_rels(g.cells[224:]) #~3h + +# Prepare second output +# second_output_fn = "{1}{0}_inter_cells.csv".format(KM_THRESHOLD,PREFIX_OUTPUT_FN) +# out_ = open(second_output_fn,'w') +# out_.write("geonameid,adjacent_geonameid,latitude,longitude") # HEADER +# out_.flush()# Avoid writing bugs + +# get_rels(g.inter_cells) 594 diff --git a/scripts/get_cooccurrence.py b/scripts/get_cooccurrence.py new file mode 100644 index 0000000..4f2bced --- /dev/null +++ b/scripts/get_cooccurrence.py @@ -0,0 +1,40 @@ +import gzip +import json +import re + +import argparse + +import pandas as pd + +from joblib import Parallel,delayed +from tqdm import tqdm + +parser = argparse.ArgumentParser() + +parser.add_argument("page_of_interest_fn") +parser.add_argument("output_fn") +parser.add_argument("-c","--corpus",action="append") + +args = parser.parse_args()#("../wikidata/sample/place_en_fr_page_clean_onlyfrplace.csv test.txt -c frwiki-latest.json.gz -c enwiki-latest.json.gz".split()) + +PAGES_OF_INTEREST_FILE = args.page_of_interest_fn +WIKIPEDIA_CORPORA = args.corpus +OUTPUT_FN = args.output_fn + +if len(WIKIPEDIA_CORPORA)<1: + raise Exception('No corpora was given!') + +df = pd.read_csv(PAGES_OF_INTEREST_FILE) +page_of_interest = set(df.title.values) + +page_coord = {row.title : (row.longitude,row.latitude) for ix,row in df.iterrows()} + +output = open(OUTPUT_FN,'w') +output.write("title\tinterlinks\tlongitude\tlatitude\n") +for wikipedia_corpus in WIKIPEDIA_CORPORA: + for line in tqdm(gzip.GzipFile(wikipedia_corpus,'rb')): + data = json.loads(line) + if data["title"] in page_of_interest: + occ = page_of_interest.intersection(data["interlinks"].keys()) + coord = page_coord[data["title"]] + if len(occ) >0:output.write(data["title"]+"\t"+"|".join(occ)+"\t{0}\t{1}".format(*coord)+"\n") diff --git a/scripts/gethealpix.py b/scripts/gethealpix.py new file mode 100644 index 0000000..6e572fd --- /dev/null +++ b/scripts/gethealpix.py @@ -0,0 +1,32 @@ + + +import pandas as pd + +from tqdm import tqdm +tqdm.pandas() +import argparse + +import numpy as np +import healpy +# convert lat and lon to a healpix code encoding a region, with a given resolution +def latlon2healpix( lat , lon , res ): + lat = np.radians(lat) + lon = np.radians(lon) + xs = ( np.cos(lat) * np.cos(lon) )# + ys = ( np.cos(lat) * np.sin(lon) )# -> Sphere coordinates: https://vvvv.org/blog/polar-spherical-and-geographic-coordinates + zs = ( np.sin(lat) )# + return healpy.vec2pix( int(res) , xs , ys , zs ) + +parser = argparse.ArgumentParser() +parser.add_argument("input_file") +parser.add_argument("output_file") + +args = parser.parse_args() + +df = pd.read_csv(args.input_file,sep="\t") +df["healpix_256"] = df.progress_apply(lambda row:latlon2healpix(lat=row.latitude,lon=row.longitude,res=256),axis=1) +df["healpix_64"] = df.progress_apply(lambda row:latlon2healpix(lat=row.latitude,lon=row.longitude,res=64),axis=1) +df["healpix_32"] = df.progress_apply(lambda row:latlon2healpix(lat=row.latitude,lon=row.longitude,res=32),axis=1) +df["healpix_1"] = df.progress_apply(lambda row:latlon2healpix(lat=row.latitude,lon=row.longitude,res=1),axis=1) + +df.to_csv(args.output_file,sep="\t",index=False) \ No newline at end of file diff --git a/scripts/randoludo.py b/scripts/randoludo.py new file mode 100644 index 0000000..3862d52 --- /dev/null +++ b/scripts/randoludo.py @@ -0,0 +1,50 @@ +import pandas as pd +import numpy as np + +from lib.geocoder import Geocoder + +geocoder = Geocoder("./outputs/IGN_4_100_A_C.h5","./outputs/IGN_4_100_A_C_index") + +df = pd.read_csv("data/rando_toponymes.tsv",sep="\t") +df["name"]=df.name.apply(lambda x:x.split("¦")[0]) + +def heuristic_mean(toponyms): + input_ = np.asarray([[t1,t2] for t2 in toponyms for t1 in toponyms if t2 != t1]) + if len(input_)<1: + input_=np.asarray([[toponyms[0],toponyms[0]]]) + res_geocode = pd.DataFrame(input_,columns="t tc".split()) + lons,lats = geocoder.wgs_coord(*geocoder.get_coords(input_[:,0],input_[:,1])) + res_geocode["lon"] = lons + res_geocode["lat"] = lats + results = {} + for tp in toponyms: + lat = res_geocode[res_geocode.t == tp].lat.mean() + lon = res_geocode[res_geocode.t == tp].lon.mean() + results[tp]={"lat":lat,"lon":lon} + return results + +def heuristic_one_couple(toponyms): + input_ = np.asarray([[t1,t2] for t2 in toponyms for t1 in toponyms if t2 == t1]) + if len(input_)<1: + input_=np.asarray([[toponyms[0],toponyms[0]]]) + res_geocode = pd.DataFrame(input_,columns="t tc".split()) + lons,lats = geocoder.wgs_coord(*geocoder.get_coords(input_[:,0],input_[:,1])) + res_geocode["lon"] = lons + res_geocode["lat"] = lats + results = {} + for tp in toponyms: + lat = res_geocode[res_geocode.t == tp].lat.mean() + lon = res_geocode[res_geocode.t == tp].lon.mean() + results[tp]={"lat":lat,"lon":lon} + return results + +results_fin = [] +for ix,group in df.groupby("filename"): + res_geocode = heuristic_one_couple(group.name_gazetteer.values) + results_fin.extend(group.name_gazetteer.apply(lambda x : res_geocode[x]).values.tolist()) +dd = pd.DataFrame(results_fin).rename(columns={"lat":"lat_pred","lon":"lon_pred"}) +df2 = pd.concat((df,dd),axis=1) + +from lib.utils_geo import haversine_pd +df2["dist_error"] = haversine_pd(df2.longitude,df2.latitude,df2.lon_pred,df2.lat_pred) +print(df2.dist_error.mean()) diff --git a/templates/pair_topo.html b/templates/pair_topo.html new file mode 100644 index 0000000..3366643 --- /dev/null +++ b/templates/pair_topo.html @@ -0,0 +1,55 @@ +{% extends 'skeleton.html' %} + +{% block content %} +<!-- MAP RENDER --> +<div id="mapid"></div> + +<!-- TOPONYM FORM --> +<div class="container" style="background-color: white;padding: 5px;"> + <p> + + </p> + <form action="/" method="get"> + <div class="form-group"> + <label for="formGroupExampleInput">Toponym</label> + <input type="text" class="form-control" name="top" + placeholder="Paris"> + </div> + <div class="form-group"> + <label for="formGroupExampleInput2">Context Toponym</label> + <input type="text" class="form-control" name="c_top" + placeholder="Cherbourg"> + </div> + <button type="submit" class="btn btn-primary">Get Coords !</button> + </form> +</div> +{% endblock %} + +{% block script %} +<script> + + // Initialize the map + // [50, -0.1] are the latitude and longitude + // 4 is the zoom + // mapid is the id of the div where the map will appear + var mymap = L + .map('mapid') + .setView([50, -0.1], 4); + + // Add a tile to the map = a background. Comes from OpenStreetmap + L.tileLayer( + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a>', + }).addTo(mymap); + {% if lat and lon %} + var marker = L.marker([{{lat}}, {{lon}}]).addTo(mymap); + var circle = L.circle([{{lat}}, {{lon}}], { + color: "red", + fillColor: "#f03", + fillOpacity: 0.5, + radius: 100000.0 + }).addTo(mymap); + {% endif %} + +</script> +{% endblock %} \ No newline at end of file diff --git a/templates/skeleton.html b/templates/skeleton.html new file mode 100644 index 0000000..8687b4e --- /dev/null +++ b/templates/skeleton.html @@ -0,0 +1,116 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=auto, initial-scale=1.0"> + <meta http-equiv="X-UA-Compatible" content="ie=edge"> + <title>Geocoder Interface</title> + <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> + + <link href="https://fonts.googleapis.com/css2?family=Kumbh+Sans:wght@300;400;700&display=swap" rel="stylesheet"> + + <!-- Load Leaflet --> + <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.4/dist/leaflet.css" integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA==" crossorigin="" /> + <script src="https://unpkg.com/leaflet@1.3.4/dist/leaflet.js" integrity="sha512-nMMmRyTVoLYqjP9hrbed9S+FzjZHW5gY1TWCHA5ckwXZBadntCNs8kEqAWdrb9O7rxbCaA4lKTIWjDXZxflOcA==" crossorigin=""></script> +</head> + +<body> + <style> + body { + font-family: 'Kumbh Sans', sans-serif; + } + + #mapid { + height: 400px; + width: 100%; + } + + .container-fluid { + padding: 0 !important; + } + + .text_annotated { + line-height: 2em; + } + + .annotation { + border-radius: 5px; + color: white; + padding: 4px; + } + + .person { + background-color: #cf000f; + } + + .place { + background-color: #2c82c9; + } + + .org { + background-color: #00b16a; + } + </style> + + <main class="container-fluid"> + <!-- NAVBAR --> + <nav class="navbar navbar-expand-lg navbar-light bg-light"> + <a class="navbar-brand" href="#">Geocoding using pair of toponyms</a> + <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation"> + <span class="navbar-toggler-icon"></span> + </button> + <div class="collapse navbar-collapse" id="navbarNavAltMarkup"> + <div class="navbar-nav"> + <a class="nav-link" href="/">Toponyms Pair Geocoder</a> + <a class="nav-link" href="/text">Text Geocoder</a> + + + </div> + <div class="navbar-nav ml-auto"> + <li class="nav-item dropdown"> + <a class="nav-link dropdown-toggle" href="#" id="navbarModelDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + Choose Model + </a> + + <div class="dropdown-menu" aria-labelledby="navbarModelDropdown"> + {% for id_ in dict_model %} + <a class="dropdown-item" href="/loadmodel/{{id_}}">{{id_}}</a> + <br>{% endfor %} + </div> + </li> + <li class="nav-item dropdown"> + <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + Choose Lang for Spacy + </a> + <div class="dropdown-menu" aria-labelledby="navbarDropdown"> + <a class="dropdown-item" href="/loadlang/fr">fr</a> + <a class="dropdown-item" href="/loadlang/en">en</a> + </div> + </li> + </div> + </div> + </nav> + {% if request.args.get("msg","") != "" %} + <div class="alert alert-{{msg_code}} alert-dismissible fade show" role="alert"> + {{request.args.get("msg") }} + <button type="button" class="close" data-dismiss="alert" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + + {% endif %} + <h2 class="text-center" style="margin-top: 0.5em;">{{title}}</h2> + + <br>{% block content %}{% endblock %} + </main> + + <!-- JS SCRIPTS --> + <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> + <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> + <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> + + {% block script%} {% endblock %} +</body> + +</html> \ No newline at end of file diff --git a/templates/text.html b/templates/text.html new file mode 100644 index 0000000..677b26e --- /dev/null +++ b/templates/text.html @@ -0,0 +1,60 @@ +{% extends 'skeleton.html' %} {% block content %} +<!-- MAP RENDER --> +<div id="mapid"></div> + +<!-- TOPONYM FORM --> +<div class="container"> + <p> + + </p> + <form action="/geocode" method="post"> + <div class="form-group"> + <label for="text"><h5>Your text</h5></label> + <textarea class="form-control" id="text" name="text" rows="3"></textarea> + <br> + <button class="btn btn-info" id="submit">Geocode</button> + </div> + </form> + <h5>Results :</h5> + <div class="col-lg-12" style="border:1px solid #999;border-radius:5px;"> + <p class="text_annotated" id="result_container"> + {% if data %} {{data["output"]}} {% endif %} + </p> + </div> +</div> + +{% endblock %} {% block script %} +<script> + // Initialize the map + // [50, -0.1] are the latitude and longitude + // 4 is the zoom + // mapid is the id of the div where the map will appear + var mymap = L + .map('mapid') + .setView([50, -0.1], 4); + + // Add a tile to the map = a background. Comes from OpenStreetmap + L.tileLayer( + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a>', + }).addTo(mymap); + +</script> +{% if data %} +<script> + {% for place,coords in data["place_coords"].items() %} + var mark = L.marker([{{coords["lat"]}}, {{coords["lon"]}}],); + mark.bindPopup("{{place}}") + mark.addTo(mymap); + var circle = L.circle([{{coords["lat"]}}, {{coords["lon"]}}], { + color: "red", + fillColor: "#f03", + fillOpacity: 0.5, + radius: 100000.0 + }).addTo(mymap); + + {% endfor %} +</script> +{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/train_bert_geocoder.py b/train_bert_geocoder.py new file mode 100644 index 0000000..d0749b6 --- /dev/null +++ b/train_bert_geocoder.py @@ -0,0 +1,353 @@ +# REQUIREMENTS : pandas keras torch numpy transformers + +""" +Based from the article : https://mccormickml.com/2019/07/22/BERT-fine-tuning/ +by Chris McCormick + +""" +import os +import sys +import time +import random +import argparse +import datetime + +import pandas as pd +import numpy as np + +import tensorflow as tf +import torch + +from tqdm import tqdm +tqdm.pandas() + +from torch.utils.data import TensorDataset, DataLoader, RandomSampler, SequentialSampler +from keras.preprocessing.sequence import pad_sequences +from transformers import BertTokenizer +from transformers import BertForSequenceClassification, AdamW, BertConfig +from transformers import get_linear_schedule_with_warmup + +from sklearn import preprocessing +from joblib import dump + +def flat_accuracy(preds, labels): + pred_flat = np.argmax(preds, axis=1).flatten() + labels_flat = labels.flatten() + return np.sum(pred_flat == labels_flat) / len(labels_flat) + + +def format_time(elapsed): + ''' + Takes a time in seconds and returns a string hh:mm:ss + ''' + # Round to the nearest second. + elapsed_rounded = int(round((elapsed))) + + # Format as hh:mm:ss + return str(datetime.timedelta(seconds=elapsed_rounded)) + +parser = argparse.ArgumentParser() + +parser.add_argument("train" ,help="TSV with two columns : 'sentence' and 'label'") +parser.add_argument("test",help="TSV with two columns : 'sentence' and 'label'") +parser.add_argument("outputdir",help="TSV with two columns : 'sentence' and 'label'") +parser.add_argument("-e","--epochs",type=int,default=5) +parser.add_argument("-b","--batch_size",default=32,type=int) + +args = parser.parse_args()#("-b 32 -e 10 cooc_adj_bert_train.csv cooc_adj_bert_test.csv output_bert_allcooc_adjsampling3radius20km_batch32_epoch10".split()) + +if not os.path.exists(args.train) or not os.path.exists(args.test): + raise FileNotFoundError("Train or Test filepath is incorrect !") + +# Number of training epochs (authors recommend between 2 and 4) +epochs = args.epochs + +# The DataLoader needs to know the batch size for training, so I specify it here. +# For fine-tuning BERT on a specific task, the authors recommend a batch size of +# 16 or 32. + +batch_size = args.batch_size + +# OUTPUT DIR +output_dir = args.outputdir + +if not os.path.exists(args.outputdir): + raise FileNotFoundError("{0} directory does not exists ! ".format(args.output_dir)) +if not os.path.isdir(args.outputdir): + raise NotADirectoryError("{0} is not a directory".format(args.output_dir)) + +df_train = pd.read_csv(args.train, sep="\t") +df_test = pd.read_csv(args.test, sep="\t") + +label_encoder = preprocessing.LabelEncoder() +label_encoder.fit(np.concatenate((df_train.label.values,df_test.label.values))) +dump(label_encoder,filename=output_dir+"/label_encoder.dump") + +df_train["label"] = label_encoder.transform(df_train.label.values) +df_test["label"] = label_encoder.transform(df_test.label.values) + +# Get the GPU device name. +device_name = tf.test.gpu_device_name() + +# The device name should look like the following: +if device_name == '/device:GPU:0': + print('Found GPU at: {}'.format(device_name)) +else: + raise SystemError('GPU device not found') + +# If there's a GPU available... +if torch.cuda.is_available(): + + # Tell PyTorch to use the GPU. + device = torch.device("cuda") + print('There are %d GPU(s) available.' % torch.cuda.device_count()) + print('We will use the GPU:', torch.cuda.get_device_name(0)) +# If not... +else: + print('No GPU available, using the CPU instead.') + device = torch.device("cpu") + + +# Load the BERT tokenizer. +print('Loading {0} tokenizer...'.format("bert-base-multilingual-cased")) +tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased',do_lower_case=False) + +from lib.torch_generator import SentenceDataset +# Create the DataLoader for training set. +train_data = SentenceDataset(df_train,tokenizer,batch_size=batch_size) +train_dataloader = DataLoader(train_data, batch_size=batch_size)#,sampler=train_sampler,) + +# Create the DataLoader for validation set. +validation_data = SentenceDataset(df_test,tokenizer,batch_size=batch_size) +#validation_sampler = SequentialSampler(validation_data) +validation_dataloader = DataLoader(validation_data, batch_size=batch_size)#, sampler=validation_sampler) + +# Load BertForSequenceClassification, the pretrained BERT model with a single +# linear classification layer on top. +model = BertForSequenceClassification.from_pretrained( + "bert-base-multilingual-cased", # Use the 12-layer BERT model, with an uncased vocab. + num_labels = max(df_test.label.max(),df_train.label.max())+1, # The number of output labels--2 for binary classification. + # You can increase this for multi-class tasks. + output_attentions = False, # Whether the model returns attentions weights. + output_hidden_states = False, # Whether the model returns all hidden-states. +) + +# Tell pytorch to run this model on the GPU. +model.cuda() + +optimizer = AdamW(model.parameters(), + lr = 2e-5, # args.learning_rate - default is 5e-5, our notebook had 2e-5 + eps = 1e-8 # args.adam_epsilon - default is 1e-8. + ) + + + +# Total number of training steps is number of batches * number of epochs. +total_steps = len(train_data) * epochs + +# Create the learning rate scheduler. +scheduler = get_linear_schedule_with_warmup(optimizer, + num_warmup_steps = 0, # Default value in run_glue.py + num_training_steps = total_steps) + + + +# Set the seed value all over the place to make this reproducible. +seed_val = 42 + +random.seed(seed_val) +np.random.seed(seed_val) +torch.manual_seed(seed_val) +torch.cuda.manual_seed_all(seed_val) + +# Store the average loss after each epoch so I can plot them. +loss_values = [] + +history = [] +# For each epoch... +for epoch_i in range(0, epochs): + epoch_data={} + + # ======================================== + # Training + # ======================================== + + # Perform one full pass over the training set. + + print("") + print('======== Epoch {:} / {:} ========'.format(epoch_i + 1, epochs)) + print('Training...') + + # Measure how long the training epoch takes. + t0 = time.time() + + # Reset the total loss for this epoch. + total_loss = 0 + + # Put the model into training mode. + model.train() + + # For each batch of training data... + for step, batch in enumerate(train_dataloader): + + # Progress update every 40 batches. + if step % 100 == 0 and not step == 0: + # Calculate elapsed time in minutes. + elapsed = format_time(time.time() - t0) + + # Report progress.#Changed to sys.stdout to avoid uneccessary \n + sys.stdout.write('\r Batch {:>5,} of {:>5,}. Elapsed: {:}.'.format(step, len(train_dataloader), elapsed)) + + # Unpack this training batch from the dataloader. + # + # As I unpack the batch, I'll also copy each tensor to the GPU using the + # `to` method. + # + # `batch` contains three pytorch tensors: + # [0]: input ids + # [1]: attention masks + # [2]: labels + b_input_ids = batch[0].to(device) + b_input_mask = batch[1].to(device) + b_labels = batch[2].to(device) + + # Always clear any previously calculated gradients before performing a + # backward pass. PyTorch doesn't do this automatically because + # accumulating the gradients is "convenient while training RNNs". + # (source: https://stackoverflow.com/questions/48001598/why-do-we-need-to-call-zero-grad-in-pytorch) + model.zero_grad() + + # Perform a forward pass (evaluate the model on this training batch). + # This will return the loss (rather than the model output) because I + # have provided the `labels`. + # The documentation for this `model` function is here: + # https://huggingface.co/transformers/v2.2.0/model_doc/bert.html#transformers.BertForSequenceClassification + outputs = model(b_input_ids, + token_type_ids=None, + attention_mask=b_input_mask, + labels=b_labels) + + # The call to `model` always returns a tuple, so I need to pull the + # loss value out of the tuple. + loss = outputs[0] + + # Accumulate the training loss over all of the batches so that I can + # calculate the average loss at the end. `loss` is a Tensor containing a + # single value; the `.item()` function just returns the Python value + # from the tensor. + + total_loss += loss.item() + + # Perform a backward pass to calculate the gradients. + loss.backward() + + # Clip the norm of the gradients to 1.0. + # This is to help prevent the "exploding gradients" problem. + torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) + + # Update parameters and take a step using the computed gradient. + # The optimizer dictates the "update rule"--how the parameters are + # modified based on their gradients, the learning rate, etc. + optimizer.step() + + # Update the learning rate. + scheduler.step() + + # Calculate the average loss over the training data. + avg_train_loss = total_loss / len(train_dataloader) + + # Store the loss value for plotting the learning curve. + loss_values.append(avg_train_loss) + + print("") + print(" Average training loss: {0:.2f}".format(avg_train_loss)) + print(" Training epoch took: {:}".format(format_time(time.time() - t0))) + epoch_data["loss"]=avg_train_loss + epoch_data["epoch_duration"] = time.time() - t0 + + # ======================================== + # Validation + # ======================================== + # After the completion of each training epoch, measure the performance on + # the validation set. + + print("") + print("Running Validation...") + + t0 = time.time() + + # Put the model in evaluation mode--the dropout layers behave differently + # during evaluation. + model.eval() + + # Tracking variables + eval_loss, eval_accuracy = 0, 0 + nb_eval_steps, nb_eval_examples = 0, 0 + + # Evaluate data for one epoch + for batch in validation_dataloader: + + # Add batch to GPU + batch = tuple(t.to(device) for t in batch) + + # Unpack the inputs from dataloader + b_input_ids, b_input_mask, b_labels = batch + + # Telling the model not to compute or store gradients, saving memory and + # speeding up validation + with torch.no_grad(): + + # Forward pass, calculate logit predictions. + # This will return the logits rather than the loss because we have + # not provided labels. + # token_type_ids is the same as the "segment ids", which + # differentiates sentence 1 and 2 in 2-sentence tasks. + # The documentation for this `model` function is here: + # https://huggingface.co/transformers/v2.2.0/model_doc/bert.html#transformers.BertForSequenceClassification + outputs = model(b_input_ids, + token_type_ids=None, + attention_mask=b_input_mask) + + # Get the "logits" output by the model. The "logits" are the output + # values prior to applying an activation function like the softmax. + logits = outputs[0] + + # Move logits and labels to CPU + logits = logits.detach().cpu().numpy() + label_ids = b_labels.to('cpu').numpy() + + # Calculate the accuracy for this batch of test sentences. + tmp_eval_accuracy = flat_accuracy(logits, label_ids) + + # Accumulate the total accuracy. + eval_accuracy += tmp_eval_accuracy + + # Track the number of batches + nb_eval_steps += 1 + + + # Report the final accuracy for this validation run. + print(" Accuracy: {0:.2f}".format(eval_accuracy/nb_eval_steps)) + print(" Validation took: {:}".format(format_time(time.time() - t0))) + epoch_data["accuracy"] = eval_accuracy/nb_eval_steps + epoch_data["validation_duration"] = time.time() - t0 + history.append(epoch_data) +print("") +print("Training complete!") + +print("Save History") +pd.DataFrame(history).to_csv(output_dir+"/history_bert.csv",sep="\t") + + + +# Create output directory if needed +if not os.path.exists(output_dir): + os.makedirs(output_dir) + +print("Saving model to %s" % output_dir) + +# Save a trained model, configuration and tokenizer using `save_pretrained()`. +# They can then be reloaded using `from_pretrained()` +model_to_save = model.module if hasattr(model, 'module') else model # Take care of distributed/parallel training +model_to_save.save_pretrained(output_dir) +tokenizer.save_pretrained(output_dir) \ No newline at end of file diff --git a/train_geocoder.py b/train_geocoder.py new file mode 100644 index 0000000..71ff546 --- /dev/null +++ b/train_geocoder.py @@ -0,0 +1,401 @@ +# Base module +import re +import os +import json + +# Structure +import pandas as pd +import numpy as np +import geopandas as gpd + +# DEEPL module +from keras.layers import Dense, Input, Embedding,concatenate,Bidirectional,LSTM, Dropout +from keras.models import Model +from keras import backend as K +from keras.callbacks import ModelCheckpoint + +import tensorflow as tf + +# Geometry +from shapely.geometry import Point + +# Custom module +from helpers import read_geonames +from lib.utils_geo import Grid,zero_one_encoding, get_adjacency_rels, get_geonames_inclusion_rel,get_bounds +from lib.ngram_index import NgramIndex +from lib.utils import ConfigurationReader +from lib.metrics import lat_accuracy,lon_accuracy +from lib.utils_geo import haversine_tf,accuracy_k,haversine_tf_1circle + + +# Logging +from tqdm import tqdm +import logging +from helpers import parse_title_wiki,EpochTimer + +logging.getLogger('gensim').setLevel(logging.WARNING) + +def get_new_ids(cooc_data,id_first_value): + """ + Return new ids from cooccurrence data + + Parameters + ---------- + cooc_data : pd.DataFrame + cooccurrence da + id_first_value : int + id beginning value + + Returns + ------- + dict + new ids for each toponyms + """ + topo_id = {} + id_ = id_first_value + for title in cooc_data.title.values: + if not title in topo_id: + id_+=1 + topo_id[id_]=title + for interlinks in cooc_data.interlinks.values: + for interlink in interlinks.split("|"): + if not interlink in topo_id: + id_+=1 + topo_id[id_]=interlink + return topo_id + +# LOGGING CONF +logging.basicConfig( + format='[%(asctime)s][%(levelname)s] %(message)s ', + datefmt='%m/%d/%Y %I:%M:%S %p', + level=logging.INFO + ) + +args = ConfigurationReader("./parser_config/toponym_combination_embedding_v2.json")\ + .parse_args()#("-i -a -w --wikipedia-cooc-fn ../data/wikipedia/cooccurrence_FR.txt -n 4 --ngram-word2vec-iter 1 -e 100 ../data/geonamesData/FR.txt ../data/geonamesData/hierarchy.txt".split()) + +# +################################################# +############# MODEL TRAINING PARAMETER ########## +################################################# +MODEL_NAME = "Bi-LSTM_NGRAM" +NGRAM_SIZE = args.ngram_size +ACCURACY_TOLERANCE = args.tolerance_value +EPOCHS = args.epochs +ITER_ADJACENCY = args.adjacency_iteration +COOC_SAMPLING_NUMBER = args.cooc_sample_size +WORDVEC_ITER = args.ngram_word2vec_iter +EMBEDDING_DIM = 256 +################################################# +########## FILENAME VARIABLE #################### +################################################# +GEONAME_FN = args.geoname_input +DATASET_NAME = args.geoname_input.split("/")[-1] +GEONAMES_HIERARCHY_FN = args.geoname_hierachy_input +REGION_SUFFIX_FN = "" if args.admin_code_1 == "None" else "_" + args.admin_code_1 +ADJACENCY_REL_FILENAME = "{0}_{1}{2}adjacency.json".format( + GEONAME_FN, + ITER_ADJACENCY, + REGION_SUFFIX_FN) + +COOC_FN = args.wikipedia_cooc_fn +PREFIX_OUTPUT_FN = "{0}_{1}_{2}_{3}_{4}".format( + GEONAME_FN.split("/")[-1], + EPOCHS, + NGRAM_SIZE, + ACCURACY_TOLERANCE, + REGION_SUFFIX_FN) + +REL_CODE="" +if args.adjacency: + PREFIX_OUTPUT_FN += "_A" + REL_CODE+= "A" +if args.inclusion: + PREFIX_OUTPUT_FN += "_I" + REL_CODE+= "I" +if args.wikipedia_cooc: + PREFIX_OUTPUT_FN += "_C" + REL_CODE+= "C" + +MODEL_OUTPUT_FN = "outputs/{0}.h5".format(PREFIX_OUTPUT_FN) +INDEX_FN = "outputs/{0}_index".format(PREFIX_OUTPUT_FN) +HISTORY_FN = "outputs/{0}.csv".format(PREFIX_OUTPUT_FN) + +from lib.utils import MetaDataSerializer + +meta_data = MetaDataSerializer( + MODEL_NAME, + DATASET_NAME, + REL_CODE, + COOC_SAMPLING_NUMBER, + ITER_ADJACENCY, + NGRAM_SIZE, + ACCURACY_TOLERANCE, + EPOCHS, + EMBEDDING_DIM, + WORDVEC_ITER, + INDEX_FN, + MODEL_OUTPUT_FN, + HISTORY_FN +) +meta_data.save("outputs/{0}.json".format(PREFIX_OUTPUT_FN)) + +############################################################################################# +################################# LOAD DATA ################################################# +############################################################################################# + +# LOAD Geonames DATA +logging.info("Load Geonames data...") +geoname_data = read_geonames(GEONAME_FN).fillna("") + +train_indices = set(pd.read_csv(GEONAME_FN+"_train.csv").geonameid.values) +test_indices = set(pd.read_csv(GEONAME_FN+"_test.csv").geonameid.values) + +logging.info("Geonames data loaded!") + +# SELECT ENTRY with class == to A and P (Areas and Populated Places) +filtered = geoname_data[geoname_data.feature_class.isin("A P".split())].copy() # Only take area and populated places +#CLEAR RAM +del geoname_data + + +# IF REGION +if args.admin_code_1 != "None": + filtered = filtered[filtered.admin1_code == args.admin_code_1].copy() + +# GET BOUNDS AND REDUCE DATA AVAILABLE FIELDS +filtered = filtered["geonameid name longitude latitude".split()] # KEEP ONLY ID LABEL AND COORD + + + +############################################################################################# +################################# RETRIEVE RELATIONSHIPS #################################### +############################################################################################# + + +# INITIALIZE RELATION STORE +rel_store = [] + +# Retrieve adjacency relationships +if args.adjacency: + logging.info("Retrieve adjacency relationships ! ") + + if not os.path.exists(ADJACENCY_REL_FILENAME): + bounds = get_bounds(filtered) # Required to get adjacency relationships + rel_store.extend(get_adjacency_rels(filtered,bounds,[360,180],ITER_ADJACENCY)) + json.dump(rel_store,open(ADJACENCY_REL_FILENAME,'w')) + else: + logging.info("Open and load data from previous computation!") + rel_store=json.load(open(ADJACENCY_REL_FILENAME)) + + logging.info("{0} adjacency relationships retrieved ! ".format(len(rel_store))) + +# Retrieve inclusion relationships +if args.inclusion: + logging.info("Retrieve inclusion relationships ! ") + + cpt_rel = len(rel_store) + rel_store.extend(get_geonames_inclusion_rel(filtered,GEONAMES_HIERARCHY_FN)) + + logging.info("{0} inclusion relationships retrieved ! ".format(len(rel_store)-cpt_rel)) + + + +if args.wikipedia_cooc: + logging.info("Load Wikipedia Cooccurrence data and merge with geonames") + + cooc_data = pd.read_csv(COOC_FN,sep="\t") + cooc_data["title"] = cooc_data.title.apply(parse_title_wiki) + cooc_data["interlinks"] = cooc_data.interlinks.apply(parse_title_wiki) + id_wikipediatitle = get_new_ids(cooc_data,filtered.geonameid.max()) + wikipediatitle_id = {v:k for k,v in id_wikipediatitle.items()} + title_coord = {row.title: (row.longitude,row.latitude) for _,row in tqdm(cooc_data.iterrows(),total=len(cooc_data))} + cooc_data["geonameid"] = cooc_data.title.apply(lambda x: wikipediatitle_id[x]) + filtered = pd.concat((filtered,cooc_data["geonameid title longitude latitude".split()].rename(columns={"title":"name"}).copy())) + train_cooc_indices,test_cooc_indices = pd.read_csv(COOC_FN+"_train.csv",sep="\t"), pd.read_csv(COOC_FN+"_test.csv",sep="\t") + if not "title" in train_cooc_indices: + train_cooc_indices,test_cooc_indices = pd.read_csv(COOC_FN+"_train.csv"), pd.read_csv(COOC_FN+"_test.csv") + train_indices = train_indices.union(set(train_cooc_indices.title.apply(lambda x: wikipediatitle_id[parse_title_wiki(x)]).values)) + test_indices = test_indices.union(set(test_cooc_indices.title.apply(lambda x: wikipediatitle_id[parse_title_wiki(x)]).values)) + + logging.info("Merged with Geonames data !") + + # EXTRACT rel + logging.info("Extracting cooccurrence relationships") + cpt=0 + for ix, row in tqdm(cooc_data.iterrows(),total=len(cooc_data),desc="Extracting Wikipedia Cooccurrence"): + for inter in np.random.choice(row.interlinks.split("|"),COOC_SAMPLING_NUMBER): + cpt+=1 + rel_store.extend([[row.geonameid,wikipediatitle_id[inter]]]) + logging.info("Extract {0} cooccurrence relationships !".format(cpt)) + + +# STORE ID to name +geoname2name = dict(filtered["geonameid name".split()].values) + +# ENCODING NAME USING N-GRAM SPLITTING +logging.info("Encoding toponyms to ngram...") +index = NgramIndex(NGRAM_SIZE) + + # Identify all ngram available +filtered.name.apply(lambda x : index.split_and_add(x)) +if args.wikipedia_cooc:[index.split_and_add(k) for k in wikipediatitle_id] + +geoname2encodedname = {row.geonameid : index.encode(row.name) for row in filtered.itertuples()} #init a dict with the 'geonameid' --> 'encoded toponym' association + +if args.wikipedia_cooc: + geoname2encodedname.update({v:index.encode(k) for k,v in wikipediatitle_id.items()}) + +# SAVE THE INDEX TO REUSE THE MODEL +index.save(INDEX_FN) + +logging.info("Done !") + + +############################################################################################# +################################# ENCODE COORDINATES ######################################## +############################################################################################# + + + +# Encode each geonames entry coordinates +geoname_vec = {row.geonameid : zero_one_encoding(row.longitude,row.latitude) for row in filtered.itertuples()} +# CLEAR RAM +del filtered + + +EMBEDDING_DIM = 256 +num_words = len(index.index_ngram) # necessary for the embedding matrix + +logging.info("Preparing Input and Output data...") + + +############################################################################################# +################################# BUILD TRAIN/TEST DATASETS ################################# +############################################################################################# + +X_1_train,X_2_train,y_lat_train,y_lon_train=[],[],[],[] +X_1_test,X_2_test,y_lat_test,y_lon_test=[],[],[],[] +y_train,y_test = [],[] + +for couple in rel_store: + geonameId_1,geonameId_2 = couple[0],couple[1] + if not geonameId_1 in geoname2encodedname: + continue + top1,top2 = geoname2encodedname[geonameId_1],geoname2encodedname[geonameId_2] + if geonameId_1 in train_indices: #and geonameId_2 in train_indices: + + X_1_train.append(top1) + X_2_train.append(top2) + + y_train.append([geoname_vec[geonameId_1][0],geoname_vec[geonameId_1][1]]) + #y_lon_train.append(geoname_vec[geonameId_1][0]) + #y_lat_train.append(geoname_vec[geonameId_1][1]) + + else: + X_1_test.append(top1) + X_2_test.append(top2) + + y_test.append([geoname_vec[geonameId_1][0],geoname_vec[geonameId_1][1]]) + #y_lon_test.append(geoname_vec[geonameId_1][0]) + #y_lat_test.append(geoname_vec[geonameId_1][1]) + +# NUMPYZE inputs and output lists +X_1_train = np.array(X_1_train) +X_2_train = np.array(X_2_train) +y_lat_train = np.array(y_lat_train) +y_lon_train = np.array(y_lon_train) +y_train = np.array(y_train) + +X_1_test = np.array(X_1_test) +X_2_test = np.array(X_2_test) +y_lat_test = np.array(y_lat_test) +y_lon_test = np.array(y_lon_test) +y_test = np.array(y_test) + +logging.info("Data prepared !") + + +# check for output dir +if not os.path.exists("outputs/"): + os.makedirs("outputs/") + +############################################################################################# +################################# NGRAM EMBEDDINGS ########################################## +############################################################################################# + + +logging.info("Generating N-GRAM Embedding...") +embedding_weights = index.get_embedding_layer(geoname2encodedname.values(),dim= EMBEDDING_DIM,iter=WORDVEC_ITER) +logging.info("Embedding generated !") + +############################################################################################# +################################# MODEL DEFINITION ########################################## +############################################################################################# + + +input_1 = Input(shape=(index.max_len,)) +input_2 = Input(shape=(index.max_len,)) + +embedding_layer = Embedding(num_words, EMBEDDING_DIM,input_length=index.max_len,weights=[embedding_weights],trainable=False)#, trainable=True) + +x1 = embedding_layer(input_1) +x2 = embedding_layer(input_2) + +# Each LSTM learn on a permutation of the input toponyms +x1 = Bidirectional(LSTM(98))(x1) +x2 = Bidirectional(LSTM(98))(x2) + +x = concatenate([x1,x2])#,x3]) + +x1 = Dense(500,activation="relu")(x) +# x1 = Dropout(0.3)(x1) +x1 = Dense(500,activation="relu")(x1) +# x1 = Dropout(0.3)(x1) + +x2 = Dense(500,activation="relu")(x) +# x2 = Dropout(0.3)(x2) +x2 = Dense(500,activation="relu")(x2) +# x2 = Dropout(0.3)(x2) + +output_lon = Dense(1,activation="sigmoid",name="Output_LON")(x1) +output_lat = Dense(1,activation="sigmoid",name="Output_LAT")(x2) + +output_coord = concatenate([output_lon,output_lat],name="output_coord") + +model = Model(inputs = [input_1,input_2], outputs = output_coord)#input_3 + +model.compile(loss={"output_coord":haversine_tf_1circle}, optimizer='adam',metrics={"output_coord":accuracy_k(ACCURACY_TOLERANCE)}) + +# model = Model(inputs = [input_1,input_2], outputs = [output_lon,output_lat])#input_3 + +# model.compile(loss=['mean_squared_error','mean_squared_error'], optimizer='adam',metrics={"Output_LON":lon_accuracy(),"Output_LAT":lat_accuracy()}) + + +############################################################################################# +################################# TRAINING LAUNCH ########################################### +############################################################################################# + +checkpoint = ModelCheckpoint(MODEL_OUTPUT_FN + ".part", monitor='loss', verbose=1, + save_best_only=True, mode='auto', period=1) + +epoch_timer = EpochTimer("outputs/"+PREFIX_OUTPUT_FN+"_epoch_timer_output.csv") + + +history = model.fit(x=[X_1_train,X_2_train], + y=y_train,#[y_lon_train,y_lat_train], + verbose=True, batch_size=100, + epochs=EPOCHS, + validation_data=([X_1_test,X_2_test],y_test),#[y_lon_test,y_lat_test]), + callbacks=[checkpoint,epoch_timer]) + + +hist_df = pd.DataFrame(history.history) +hist_df.to_csv(HISTORY_FN) + +model.save(MODEL_OUTPUT_FN) + +# Erase Model Checkpoint file +if os.path.exists(MODEL_OUTPUT_FN + ".part"): + import shutil + shutil.rmtree(MODEL_OUTPUT_FN + ".part") \ No newline at end of file diff --git a/train_geocoder_v2.py b/train_geocoder_v2.py new file mode 100644 index 0000000..b44fada --- /dev/null +++ b/train_geocoder_v2.py @@ -0,0 +1,242 @@ +# Base module +import os +import sys + +# Structure +import pandas as pd +import numpy as np + +# DEEPL module +from keras.layers import Dense, Input, Embedding,concatenate,Bidirectional,LSTM, Dropout +from keras.models import Model +from keras.callbacks import ModelCheckpoint + +# Custom module +from lib.utils_geo import zero_one_encoding +from lib.ngram_index import NgramIndex +from lib.word_index import WordIndex +from lib.utils import ConfigurationReader +from lib.utils_geo import accuracy_k,haversine_tf_1circle +from helpers import EpochTimer +from lib.datageneratorv4 import DataGenerator + +# Logging +import logging +logging.getLogger('gensim').setLevel(logging.WARNING) +logging.basicConfig( # LOGGING CONF + format='[%(asctime)s][%(levelname)s] %(message)s ', + datefmt='%m/%d/%Y %I:%M:%S %p', + level=logging.INFO + ) + +import tensorflow as tf +try: + physical_devices = tf.config.list_physical_devices('GPU') + tf.config.experimental.set_memory_growth(physical_devices[0], enable=True) +except: + print("NO GPU FOUND...") + +# COMMAND ARGS +args = ConfigurationReader("./parser_config/toponym_combination_embedding_v3.json")\ + .parse_args()#("IGN ../data/IGN/IGN_inclusion.csv ../data/IGN/IGN_adjacent_corrected.csv ../data/IGN/IGN_cooc.csv -i -w -a -n 4 --ngram-word2vec-iter 1".split()) + +# +################################################# +############# MODEL TRAINING PARAMETER ########## +################################################# +MODEL_NAME = "Bi-LSTM_NGRAM" +NGRAM_SIZE = args.ngram_size +ACCURACY_TOLERANCE = args.tolerance_value +EPOCHS = args.epochs +WORDVEC_ITER = args.ngram_word2vec_iter +EMBEDDING_DIM = args.dimension +################################################# +########## FILENAME VARIABLE #################### +################################################# +INCLUSION_FN = args.geoname_inclusion +ADJACENT_FN = args.geonames_adjacent +COOC_FN = args.wikipedia_cooc + +DATASET_NAME = args.dataset_name + +PREFIX_OUTPUT_FN = DATASET_NAME +PREFIX_OUTPUT_FN+="_{0}".format(NGRAM_SIZE) +PREFIX_OUTPUT_FN+="_{0}".format(EPOCHS) + +if args.adjacency: + PREFIX_OUTPUT_FN += "_A" +if args.inclusion: + PREFIX_OUTPUT_FN += "_I" +if args.wikipedia: + PREFIX_OUTPUT_FN += "_P" + +MODEL_OUTPUT_FN = "outputs/{0}.h5".format(PREFIX_OUTPUT_FN) +INDEX_FN = "outputs/{0}_index".format(PREFIX_OUTPUT_FN) +HISTORY_FN = "outputs/{0}.csv".format(PREFIX_OUTPUT_FN) + +############################################################################################# +################################# LOAD DATA ################################################# +############################################################################################# + +data_used = [] + +if args.wikipedia: + data_used.append(pd.read_csv(COOC_FN,sep="\t")) + +if args.inclusion: + data_used.append(pd.read_csv(INCLUSION_FN,sep="\t")) + +if args.adjacency: + data_used.append(pd.read_csv(ADJACENT_FN, sep="\t")) + +if len(data_used) <1: + print("No Type of toponyms indicated. Stopping the program...") + sys.exit(1) + +pairs_of_toponym = pd.concat(data_used) + +############################################################################################# +################################# RETRIEVE RELATIONSHIPS #################################### +############################################################################################# + +# ENCODING NAME USING N-GRAM SPLITTING +logging.info("Encoding toponyms to ngram...") +index = NgramIndex(NGRAM_SIZE) +if args.tokenization_method == "word-level": + index = WordIndex() +if args.tokenization_method == "bert": + index = NgramIndex(NGRAM_SIZE,bert_tokenization=True) + + # Identify all ngram available +pairs_of_toponym.toponym.apply(lambda x : index.split_and_add(x)) +pairs_of_toponym.toponym_context.apply(lambda x : index.split_and_add(x)) + +num_words = len(index.index_ngram) # necessary for the embedding matrix + +# SAVE THE INDEX TO REUSE THE MODEL +index.save(INDEX_FN) +logging.info("Done !") + +############################################################################################# +################################# NGRAM EMBEDDINGS ########################################## +############################################################################################# + +logging.info("Generating N-GRAM Embedding...") +embedding_weights = index.get_embedding_layer([index.encode(p) for p in np.concatenate((pairs_of_toponym.toponym.unique(),pairs_of_toponym.toponym_context.unique()))],dim= EMBEDDING_DIM,iter=WORDVEC_ITER) +logging.info("Embedding generated !") + +############################################################################################# +################################# BUILD TRAIN/TEST DATASETS ################################# +############################################################################################# +logging.info("Preparing Input and Output data...") + +training_generator = DataGenerator(pairs_of_toponym[pairs_of_toponym.split == "train"],index) +validation_generator = DataGenerator(pairs_of_toponym[pairs_of_toponym.split == "test"],index) +# X_1_train,X_2_train=[],[] +# X_1_test,X_2_test=[],[] +# y_train,y_test = [],[] + +# for couple in pairs_of_toponym["toponym toponym_context split longitude latitude".split()].itertuples(): +# top,top_c,split_ = couple[1], couple[2], couple[3] +# coord = zero_one_encoding(couple[-2],couple[-1]) # 0 and 1 encoding +# enc_top, enc_top_c = index.encode(top),index.encode(top_c) +# if split_ == "train": +# X_1_train.append(enc_top) +# X_2_train.append(enc_top_c) +# y_train.append(list(coord)) +# else: +# X_1_test.append(enc_top) +# X_2_test.append(enc_top_c) +# y_test.append(list(coord)) + +# # "NUMPYZE" inputs and output lists +# X_1_train = np.array(X_1_train) +# X_2_train = np.array(X_2_train) +# y_train = np.array(y_train) + +# X_1_test = np.array(X_1_test) +# X_2_test = np.array(X_2_test) +# y_test = np.array(y_test) + +logging.info("Data prepared !") + + +# check for output dir +if not os.path.exists("outputs/"): + os.makedirs("outputs/") + + +############################################################################################# +################################# MODEL DEFINITION ########################################## +############################################################################################# + +input_1 = Input(shape=(index.max_len,)) +input_2 = Input(shape=(index.max_len,)) + +embedding_layer = Embedding(num_words, EMBEDDING_DIM,input_length=index.max_len,weights=[embedding_weights],trainable=False)#, trainable=True) + +x1 = embedding_layer(input_1) +x2 = embedding_layer(input_2) + +# Each LSTM learn on a permutation of the input toponyms +if args.lstm_layer == 2: + x1 = Bidirectional(LSTM(100))(x1) + x2 = Bidirectional(LSTM(100))(x2) + x = concatenate([x1,x2]) +else: + lstm_unique_layer = Bidirectional(LSTM(100)) + x1 = lstm_unique_layer(x1) + x2 = lstm_unique_layer(x2) + x = concatenate([x1,x2]) + +x1 = Dense(500,activation="relu")(x) +x1 = Dense(500,activation="relu")(x1) + +x2 = Dense(500,activation="relu")(x) +x2 = Dense(500,activation="relu")(x2) + +output_lon = Dense(1,activation="sigmoid",name="Output_LON")(x1) +output_lat = Dense(1,activation="sigmoid",name="Output_LAT")(x2) + +output_coord = concatenate([output_lon,output_lat],name="output_coord") + +model = Model(inputs = [input_1,input_2], outputs = output_coord)#input_3 +model.compile(loss={"output_coord":haversine_tf_1circle}, optimizer='adam',metrics={"output_coord":accuracy_k(ACCURACY_TOLERANCE)}) + +print("Neural Network Architecture : ") +print(model.summary()) +############################################################################################# +################################# TRAINING LAUNCH ########################################### +############################################################################################# + +checkpoint = ModelCheckpoint(MODEL_OUTPUT_FN + ".part", monitor='loss', verbose=1, + save_best_only=True, mode='auto', period=1) + +epoch_timer = EpochTimer("outputs/"+PREFIX_OUTPUT_FN+"_epoch_timer_output.csv") + + + +history = model.fit(training_generator,verbose=True, + validation_data=validation_generator, + callbacks=[checkpoint,epoch_timer],epochs=EPOCHS) + +# history = model.fit(x=[X_1_train,X_2_train], +# y=y_train, +# verbose=True, batch_size=100, +# epochs=EPOCHS, +# validation_data=([X_1_test,X_2_test],y_test),#[y_lon_test,y_lat_test]), +# callbacks=[checkpoint,epoch_timer]) + + +hist_df = pd.DataFrame(history.history) +hist_df.to_csv(HISTORY_FN) + +model.save(MODEL_OUTPUT_FN) + +# Erase Model Checkpoint file +if os.path.exists(MODEL_OUTPUT_FN + ".part"): + try: + import shutil + shutil.rmtree(MODEL_OUTPUT_FN + ".part") + except: # Depends on Keras version + os.remove(MODEL_OUTPUT_FN + ".part") \ No newline at end of file diff --git a/train_test_split_cooccurrence_data.py b/train_test_split_cooccurrence_data.py new file mode 100644 index 0000000..47fb607 --- /dev/null +++ b/train_test_split_cooccurrence_data.py @@ -0,0 +1,68 @@ +import argparse + +import pandas as pd +import numpy as np +import geopandas as gpd + +import logging +logging.basicConfig( + format='[%(asctime)s][%(levelname)s] %(message)s ', + datefmt='%m/%d/%Y %I:%M:%S %p', + level=logging.INFO + ) + +from sklearn.model_selection import train_test_split +from shapely.geometry import Point + +from lib.utils_geo import latlon2healpix + +from tqdm import tqdm + +parser = argparse.ArgumentParser() +parser.add_argument("cooccurrence_file") +parser.add_argument("-s",action="store_true") + +args = parser.parse_args()#("data/wikipedia/cooccurrence_FR.txt".split())#("data/geonamesData/FR.txt".split()) + +# LOAD DATAgeopandas +COOC_FN = args.cooccurrence_file + +logging.info("Load Cooc DATA data...") +cooc_data = pd.read_csv(COOC_FN,sep="\t").fillna("") +logging.info("Cooc data loaded!") + + +cooc_data["cat"] = cooc_data.apply(lambda x:latlon2healpix(x.latitude,x.longitude,64),axis=1) + +# TRAIN AND TEST SPLIT +logging.info("Split Between Train and Test") + +# Cell can be empty +i=0 +while 1: + if len(cooc_data[cooc_data.cat == i])> 1: + X_train,X_test = train_test_split(cooc_data[cooc_data.cat == i]) + break + i+=1 + +for i in np.unique(cooc_data.cat.values): + try: + if not args.s: + x_train,x_test = train_test_split(cooc_data[cooc_data.cat == i]) + else: + x_train,x_test = train_test_split(cooc_data[cooc_data.cat == i].sample(frac=0.1)) + + X_train,X_test = pd.concat((X_train,x_train)),pd.concat((X_test,x_test)) + except Exception as e: + print(e) #print("Error",len(filtered[filtered.cat == i])) + +del X_train["cat"] +del X_test["cat"] + +# SAVING THE DATA +logging.info("Saving Output !") +suffix ="" +if args.s: + suffix = "10per" +X_train.to_csv(COOC_FN+suffix+"_train.csv") +X_test.to_csv(COOC_FN+suffix+"_test.csv") diff --git a/train_test_split_geonames.py b/train_test_split_geonames.py new file mode 100644 index 0000000..9aaf449 --- /dev/null +++ b/train_test_split_geonames.py @@ -0,0 +1,66 @@ +import argparse + +import numpy as np +import pandas as pd +import geopandas as gpd + +import logging +logging.basicConfig( + format='[%(asctime)s][%(levelname)s] %(message)s ', + datefmt='%m/%d/%Y %I:%M:%S %p', + level=logging.INFO + ) + +from sklearn.model_selection import train_test_split + +from lib.utils_geo import latlon2healpix +from helpers import read_geonames + +from tqdm import tqdm + +parser = argparse.ArgumentParser() +parser.add_argument("geoname_file") +parser.add_argument("--feature_classes",help="List of class",default="A P") + +args = parser.parse_args()#("data/geonamesData/FR.txt".split()) + +# LOAD DATAgeopandas +GEONAME_FN = args.geoname_file +FEATURE_CLASSES = args.feature_classes + + +logging.info("Load Geonames data...") +geoname_data = read_geonames(GEONAME_FN).fillna("") +logging.info("Geonames data loaded!") + +# SELECT ENTRY with class == to A and P (Areas and Populated Places) +filtered = geoname_data[geoname_data.feature_class.isin(FEATURE_CLASSES.split())].copy() # Only take area and populated places + +filtered["cat"] = filtered.apply(lambda x:latlon2healpix(x.latitude,x.longitude,64),axis=1) +# TRAIN AND TEST SPLIT +logging.info("Split Between Train and Test") + +# Cell can be empty +cat_unique = filtered.cat.unique() +ci=0 +while 1: + if len(filtered[filtered.cat == cat_unique[ci]])> 1: + X_train,X_test = train_test_split(filtered[filtered.cat == cat_unique[ci]]) + break + ci+=1 + +for i in cat_unique[ci:] : + try: + x_train,x_test = train_test_split(filtered[filtered.cat == i]) + X_train,X_test = pd.concat((X_train,x_train)),pd.concat((X_test,x_test)) + except: + pass #print("Error",len(filtered[filtered.cat == i])) + + +del X_train["cat"] +del X_test["cat"] + +# SAVING THE DATA +logging.info("Saving Output !") +X_train.to_csv(GEONAME_FN+"_train.csv") +X_test.to_csv(GEONAME_FN+"_test.csv") \ No newline at end of file -- GitLab