refactoring
This commit is contained in:
parent
a5322ba227
commit
79eae9003b
|
|
@ -1,42 +0,0 @@
|
|||
"""
|
||||
bg = Bulgarian
|
||||
cs = Czech
|
||||
da = Danish
|
||||
de = German
|
||||
el = Greek
|
||||
en = English
|
||||
es = Spanish
|
||||
et = Estonian
|
||||
fi = Finnish
|
||||
fr = French
|
||||
hu = Hungarian
|
||||
it = Italian
|
||||
lt = Lithuanian
|
||||
lv = Latvian
|
||||
nl = Dutch
|
||||
mt = Maltese
|
||||
pl = Polish
|
||||
pt = Portuguese
|
||||
ro = Romanian
|
||||
sk = Slovak
|
||||
sl = Slovene
|
||||
sv = Swedish
|
||||
"""
|
||||
|
||||
NLTK_LANGMAP = {'da': 'danish', 'nl': 'dutch', 'en': 'english', 'fi': 'finnish', 'fr': 'french', 'de': 'german',
|
||||
'hu': 'hungarian', 'it': 'italian', 'pt': 'portuguese', 'ro': 'romanian', 'es': 'spanish', 'sv': 'swedish'}
|
||||
|
||||
|
||||
#top 10 languages in wikipedia order by the number of articles
|
||||
#LANGS_10_MOST_WIKI = ['en','fr','sv','de','es','it','pt','nl','pl','ro']
|
||||
|
||||
#all languages in JRC-acquis v3
|
||||
JRC_LANGS = ['bg','cs','da','de','el','en','es','et','fi','fr','hu','it','lt','lv','mt','nl','pl','pt','ro','sk','sl','sv']
|
||||
JRC_LANGS_WITH_NLTK_STEMMING = ['da', 'nl', 'en', 'fi', 'fr', 'de', 'hu', 'it', 'pt', 'es', 'sv'] # 'romanian deleted for incompatibility issues'
|
||||
|
||||
RCV2_LANGS = ['ru', 'de', 'fr', 'sv', 'no', 'da', 'pt', 'it', 'es', 'jp', 'htw', 'nl']
|
||||
RCV2_LANGS_WITH_NLTK_STEMMING = ['de', 'fr', 'sv', 'da', 'pt', 'it', 'es', 'nl']
|
||||
|
||||
lang_set = {'JRC_NLTK':JRC_LANGS_WITH_NLTK_STEMMING, 'JRC':JRC_LANGS,
|
||||
'RCV2_NLTK':RCV2_LANGS_WITH_NLTK_STEMMING, 'RCV2':RCV2_LANGS}
|
||||
|
||||
|
|
@ -1,321 +0,0 @@
|
|||
from __future__ import print_function
|
||||
import os, sys
|
||||
from os.path import join
|
||||
import tarfile
|
||||
import xml.etree.ElementTree as ET
|
||||
from sklearn.datasets import get_data_home
|
||||
import pickle
|
||||
from util.file import download_file, list_dirs, list_files
|
||||
import rdflib
|
||||
from rdflib.namespace import RDF, SKOS
|
||||
from rdflib import URIRef
|
||||
import zipfile
|
||||
from data.languages import JRC_LANGS
|
||||
from collections import Counter
|
||||
from random import shuffle
|
||||
from data.languages import lang_set
|
||||
|
||||
"""
|
||||
JRC Acquis' Nomenclature:
|
||||
bg = Bulgarian
|
||||
cs = Czech
|
||||
da = Danish
|
||||
de = German
|
||||
el = Greek
|
||||
en = English
|
||||
es = Spanish
|
||||
et = Estonian
|
||||
fi = Finnish
|
||||
fr = French
|
||||
hu = Hungarian
|
||||
it = Italian
|
||||
lt = Lithuanian
|
||||
lv = Latvian
|
||||
nl = Dutch
|
||||
mt = Maltese
|
||||
pl = Polish
|
||||
pt = Portuguese
|
||||
ro = Romanian
|
||||
sk = Slovak
|
||||
sl = Slovene
|
||||
sv = Swedish
|
||||
"""
|
||||
|
||||
class JRCAcquis_Document:
|
||||
def __init__(self, id, name, lang, year, head, body, categories):
|
||||
self.id = id
|
||||
self.parallel_id = name
|
||||
self.lang = lang
|
||||
self.year = year
|
||||
self.text = body if not head else head + "\n" + body
|
||||
self.categories = categories
|
||||
|
||||
# this is a workaround... for some reason, acutes are codified in a non-standard manner in titles
|
||||
# however, it seems that the title is often appearing as the first paragraph in the text/body (with
|
||||
# standard codification), so it might be preferable not to read the header after all (as here by default)
|
||||
def _proc_acute(text):
|
||||
for ch in ['a','e','i','o','u']:
|
||||
text = text.replace('%'+ch+'acute%',ch)
|
||||
return text
|
||||
|
||||
def parse_document(file, year, head=False):
|
||||
root = ET.parse(file).getroot()
|
||||
|
||||
doc_name = root.attrib['n'] # e.g., '22006A0211(01)'
|
||||
doc_lang = root.attrib['lang'] # e.g., 'es'
|
||||
doc_id = root.attrib['id'] # e.g., 'jrc22006A0211_01-es'
|
||||
doc_categories = [cat.text for cat in root.findall('.//teiHeader/profileDesc/textClass/classCode[@scheme="eurovoc"]')]
|
||||
doc_head = _proc_acute(root.find('.//text/body/head').text) if head else ''
|
||||
doc_body = '\n'.join([p.text for p in root.findall('.//text/body/div[@type="body"]/p')])
|
||||
|
||||
def raise_if_empty(field, from_file):
|
||||
if isinstance(field, str):
|
||||
if not field.strip():
|
||||
raise ValueError("Empty field in file %s" % from_file)
|
||||
|
||||
raise_if_empty(doc_name, file)
|
||||
raise_if_empty(doc_lang, file)
|
||||
raise_if_empty(doc_id, file)
|
||||
if head: raise_if_empty(doc_head, file)
|
||||
raise_if_empty(doc_body, file)
|
||||
|
||||
return JRCAcquis_Document(id=doc_id, name=doc_name, lang=doc_lang, year=year, head=doc_head, body=doc_body, categories=doc_categories)
|
||||
|
||||
# removes documents without a counterpart in all other languages
|
||||
def _force_parallel(doclist, langs):
|
||||
n_langs = len(langs)
|
||||
par_id_count = Counter([d.parallel_id for d in doclist])
|
||||
parallel_doc_ids = set([id for id,count in par_id_count.items() if count==n_langs])
|
||||
return [doc for doc in doclist if doc.parallel_id in parallel_doc_ids]
|
||||
|
||||
def random_sampling_avoiding_parallel(doclist):
|
||||
random_order = list(range(len(doclist)))
|
||||
shuffle(random_order)
|
||||
sampled_request = []
|
||||
parallel_ids = set()
|
||||
for ind in random_order:
|
||||
pid = doclist[ind].parallel_id
|
||||
if pid not in parallel_ids:
|
||||
sampled_request.append(doclist[ind])
|
||||
parallel_ids.add(pid)
|
||||
print('random_sampling_no_parallel:: from {} documents to {} documents'.format(len(doclist), len(sampled_request)))
|
||||
return sampled_request
|
||||
|
||||
|
||||
#filters out documents which do not contain any category in the cat_filter list, and filter all labels not in cat_filter
|
||||
def _filter_by_category(doclist, cat_filter):
|
||||
if not isinstance(cat_filter, frozenset):
|
||||
cat_filter = frozenset(cat_filter)
|
||||
filtered = []
|
||||
for doc in doclist:
|
||||
doc.categories = list(cat_filter & set(doc.categories))
|
||||
if doc.categories:
|
||||
doc.categories.sort()
|
||||
filtered.append(doc)
|
||||
print("filtered %d documents out without categories in the filter list" % (len(doclist) - len(filtered)))
|
||||
return filtered
|
||||
|
||||
#filters out categories with less than cat_threshold documents (and filters documents containing those categories)
|
||||
def _filter_by_frequency(doclist, cat_threshold):
|
||||
cat_count = Counter()
|
||||
for d in doclist:
|
||||
cat_count.update(d.categories)
|
||||
|
||||
freq_categories = [cat for cat,count in cat_count.items() if count>cat_threshold]
|
||||
freq_categories.sort()
|
||||
return _filter_by_category(doclist, freq_categories), freq_categories
|
||||
|
||||
#select top most_frequent categories (and filters documents containing those categories)
|
||||
def _most_common(doclist, most_frequent):
|
||||
cat_count = Counter()
|
||||
for d in doclist:
|
||||
cat_count.update(d.categories)
|
||||
|
||||
freq_categories = [cat for cat,count in cat_count.most_common(most_frequent)]
|
||||
freq_categories.sort()
|
||||
return _filter_by_category(doclist, freq_categories), freq_categories
|
||||
|
||||
def _get_categories(request):
|
||||
final_cats = set()
|
||||
for d in request:
|
||||
final_cats.update(d.categories)
|
||||
return list(final_cats)
|
||||
|
||||
def fetch_jrcacquis(langs=None, data_path=None, years=None, ignore_unclassified=True, cat_filter=None, cat_threshold=0,
|
||||
parallel=None, most_frequent=-1, DOWNLOAD_URL_BASE ='http://optima.jrc.it/Acquis/JRC-Acquis.3.0/corpus/'):
|
||||
|
||||
assert parallel in [None, 'force', 'avoid'], 'parallel mode not supported'
|
||||
if not langs:
|
||||
langs = JRC_LANGS
|
||||
else:
|
||||
if isinstance(langs, str): langs = [langs]
|
||||
for l in langs:
|
||||
if l not in JRC_LANGS:
|
||||
raise ValueError('Language %s is not among the valid languages in JRC-Acquis v3' % l)
|
||||
|
||||
if not data_path:
|
||||
data_path = get_data_home()
|
||||
|
||||
if not os.path.exists(data_path):
|
||||
os.mkdir(data_path)
|
||||
|
||||
request = []
|
||||
total_read = 0
|
||||
for l in langs:
|
||||
file_name = 'jrc-'+l+'.tgz'
|
||||
archive_path = join(data_path, file_name)
|
||||
|
||||
if not os.path.exists(archive_path):
|
||||
print("downloading language-specific dataset (once and for all) into %s" % data_path)
|
||||
DOWNLOAD_URL = join(DOWNLOAD_URL_BASE, file_name)
|
||||
download_file(DOWNLOAD_URL, archive_path)
|
||||
print("untarring dataset...")
|
||||
tarfile.open(archive_path, 'r:gz').extractall(data_path)
|
||||
|
||||
documents_dir = join(data_path, l)
|
||||
|
||||
print("Reading documents...")
|
||||
read = 0
|
||||
for dir in list_dirs(documents_dir):
|
||||
year = int(dir)
|
||||
if years==None or year in years:
|
||||
year_dir = join(documents_dir,dir)
|
||||
pickle_name = join(data_path, 'jrc_' + l + '_' + dir + '.pickle')
|
||||
if os.path.exists(pickle_name):
|
||||
print("loading from file %s" % pickle_name)
|
||||
l_y_documents = pickle.load(open(pickle_name, "rb"))
|
||||
read += len(l_y_documents)
|
||||
else:
|
||||
l_y_documents = []
|
||||
all_documents = list_files(year_dir)
|
||||
empty = 0
|
||||
for i,doc_file in enumerate(all_documents):
|
||||
try:
|
||||
jrc_doc = parse_document(join(year_dir, doc_file), year)
|
||||
except ValueError:
|
||||
jrc_doc = None
|
||||
|
||||
if jrc_doc and (not ignore_unclassified or jrc_doc.categories):
|
||||
l_y_documents.append(jrc_doc)
|
||||
else: empty += 1
|
||||
if len(all_documents)>50 and ((i+1) % (len(all_documents)/50) == 0):
|
||||
print('\r\tfrom %s: completed %d%%' % (year_dir, int((i+1)*100.0/len(all_documents))), end='')
|
||||
read+=1
|
||||
print('\r\tfrom %s: completed 100%% read %d documents (discarded %d without categories or empty fields)\n' % (year_dir, i+1, empty), end='')
|
||||
print("\t\t(Pickling object for future runs in %s)" % pickle_name)
|
||||
pickle.dump(l_y_documents, open(pickle_name, 'wb'), pickle.HIGHEST_PROTOCOL)
|
||||
request += l_y_documents
|
||||
print("Read %d documents for language %s\n" % (read, l))
|
||||
total_read += read
|
||||
print("Read %d documents in total" % (total_read))
|
||||
|
||||
if parallel=='force':
|
||||
request = _force_parallel(request, langs)
|
||||
elif parallel == 'avoid':
|
||||
request = random_sampling_avoiding_parallel(request)
|
||||
|
||||
final_cats = _get_categories(request)
|
||||
|
||||
if cat_filter:
|
||||
request = _filter_by_category(request, cat_filter)
|
||||
final_cats = _get_categories(request)
|
||||
if cat_threshold > 0:
|
||||
request, final_cats = _filter_by_frequency(request, cat_threshold)
|
||||
if most_frequent != -1 and len(final_cats) > most_frequent:
|
||||
request, final_cats = _most_common(request, most_frequent)
|
||||
|
||||
return request, final_cats
|
||||
|
||||
def print_cat_analysis(request):
|
||||
cat_count = Counter()
|
||||
for d in request:
|
||||
cat_count.update(d.categories)
|
||||
print("Number of active categories: {}".format(len(cat_count)))
|
||||
print(cat_count.most_common())
|
||||
|
||||
# inspects the Eurovoc thesaurus in order to select a subset of categories
|
||||
# currently, only 'broadest' policy (i.e., take all categories with no parent category), and 'all' is implemented
|
||||
def inspect_eurovoc(data_path, eurovoc_skos_core_concepts_filename='eurovoc_in_skos_core_concepts.rdf',
|
||||
eurovoc_url="http://publications.europa.eu/mdr/resource/thesaurus/eurovoc-20160630-0/skos/eurovoc_in_skos_core_concepts.zip",
|
||||
select="broadest"):
|
||||
|
||||
fullpath_pickle = join(data_path, select+'_concepts.pickle')
|
||||
if os.path.exists(fullpath_pickle):
|
||||
print("Pickled object found in %s. Loading it." % fullpath_pickle)
|
||||
return pickle.load(open(fullpath_pickle,'rb'))
|
||||
|
||||
fullpath = join(data_path, eurovoc_skos_core_concepts_filename)
|
||||
if not os.path.exists(fullpath):
|
||||
print("Path %s does not exist. Trying to download the skos EuroVoc file from %s" % (data_path, eurovoc_url))
|
||||
download_file(eurovoc_url, fullpath)
|
||||
print("Unzipping file...")
|
||||
zipped = zipfile.ZipFile(data_path + '.zip', 'r')
|
||||
zipped.extract("eurovoc_in_skos_core_concepts.rdf", data_path)
|
||||
zipped.close()
|
||||
|
||||
print("Parsing %s" %fullpath)
|
||||
g = rdflib.Graph()
|
||||
g.parse(location=fullpath, format="application/rdf+xml")
|
||||
|
||||
if select == "all":
|
||||
print("Selecting all concepts")
|
||||
all_concepts = list(g.subjects(RDF.type, SKOS.Concept))
|
||||
all_concepts = [c.toPython().split('/')[-1] for c in all_concepts]
|
||||
all_concepts.sort()
|
||||
selected_concepts = all_concepts
|
||||
elif select=="broadest":
|
||||
print("Selecting broadest concepts (those without any other broader concept linked to it)")
|
||||
all_concepts = set(g.subjects(RDF.type, SKOS.Concept))
|
||||
narrower_concepts = set(g.subjects(SKOS.broader, None))
|
||||
broadest_concepts = [c.toPython().split('/')[-1] for c in (all_concepts - narrower_concepts)]
|
||||
broadest_concepts.sort()
|
||||
selected_concepts = broadest_concepts
|
||||
elif select=="leaves":
|
||||
print("Selecting leaves concepts (those not linked as broader of any other concept)")
|
||||
all_concepts = set(g.subjects(RDF.type, SKOS.Concept))
|
||||
broad_concepts = set(g.objects(None, SKOS.broader))
|
||||
leave_concepts = [c.toPython().split('/')[-1] for c in (all_concepts - broad_concepts)]
|
||||
leave_concepts.sort()
|
||||
selected_concepts = leave_concepts
|
||||
else:
|
||||
raise ValueError("Selection policy %s is not currently supported" % select)
|
||||
|
||||
print("%d %s concepts found" % (len(selected_concepts), leave_concepts))
|
||||
print("Pickling concept list for faster further requests in %s" % fullpath_pickle)
|
||||
pickle.dump(selected_concepts, open(fullpath_pickle, 'wb'), pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
return selected_concepts
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
def single_label_fragment(doclist):
|
||||
single = [d for d in doclist if len(d.categories) < 2]
|
||||
final_categories = set([d.categories[0] if d.categories else [] for d in single])
|
||||
print('{} single-label documents ({} categories) from the original {} documents'.format(len(single),
|
||||
len(final_categories),
|
||||
len(doclist)))
|
||||
return single, list(final_categories)
|
||||
|
||||
train_years = list(range(1986, 2006))
|
||||
test_years = [2006]
|
||||
cat_policy = 'leaves'
|
||||
most_common_cat = 300
|
||||
# JRC_DATAPATH = "/media/moreo/1TB Volume/Datasets/JRC_Acquis_v3"
|
||||
JRC_DATAPATH = "/storage/andrea/FUNNELING/data/JRC_Acquis_v3"
|
||||
langs = lang_set['JRC_NLTK']
|
||||
cat_list = inspect_eurovoc(JRC_DATAPATH, select=cat_policy)
|
||||
sys.exit()
|
||||
|
||||
training_docs, label_names = fetch_jrcacquis(langs=langs, data_path=JRC_DATAPATH, years=train_years,cat_filter=cat_list, cat_threshold=1, parallel=None,most_frequent=most_common_cat)
|
||||
test_docs, label_namestest = fetch_jrcacquis(langs=langs, data_path=JRC_DATAPATH, years=test_years, cat_filter=label_names,parallel='force')
|
||||
|
||||
print('JRC-train: {} documents, {} labels'.format(len(training_docs), len(label_names)))
|
||||
print('JRC-test: {} documents, {} labels'.format(len(test_docs), len(label_namestest)))
|
||||
|
||||
training_docs, label_names = single_label_fragment(training_docs)
|
||||
test_docs, label_namestest = single_label_fragment(test_docs)
|
||||
|
||||
print('JRC-train: {} documents, {} labels'.format(len(training_docs), len(label_names)))
|
||||
print('JRC-test: {} documents, {} labels'.format(len(test_docs), len(label_namestest)))
|
||||
|
||||
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
from zipfile import ZipFile
|
||||
import xml.etree.ElementTree as ET
|
||||
from data.languages import RCV2_LANGS_WITH_NLTK_STEMMING, RCV2_LANGS
|
||||
from util.file import list_files
|
||||
from sklearn.datasets import get_data_home
|
||||
import gzip
|
||||
from os.path import join, exists
|
||||
from util.file import download_file_if_not_exists
|
||||
import re
|
||||
from collections import Counter
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
"""
|
||||
RCV2's Nomenclature:
|
||||
ru = Russian
|
||||
da = Danish
|
||||
de = German
|
||||
es = Spanish
|
||||
lat = Spanish Latin-American (actually is also 'es' in the collection)
|
||||
fr = French
|
||||
it = Italian
|
||||
nl = Dutch
|
||||
pt = Portuguese
|
||||
sv = Swedish
|
||||
ja = Japanese
|
||||
htw = Chinese
|
||||
no = Norwegian
|
||||
"""
|
||||
|
||||
RCV1_TOPICHIER_URL = "http://www.ai.mit.edu/projects/jmlr/papers/volume5/lewis04a/a02-orig-topics-hierarchy/rcv1.topics.hier.orig"
|
||||
RCV1PROC_BASE_URL= 'http://www.ai.mit.edu/projects/jmlr/papers/volume5/lewis04a/a12-token-files'
|
||||
RCV1_BASE_URL = "http://www.daviddlewis.com/resources/testcollections/rcv1/"
|
||||
RCV2_BASE_URL = "http://trec.nist.gov/data/reuters/reuters.html"
|
||||
|
||||
rcv1_test_data_gz = ['lyrl2004_tokens_test_pt0.dat.gz',
|
||||
'lyrl2004_tokens_test_pt1.dat.gz',
|
||||
'lyrl2004_tokens_test_pt2.dat.gz',
|
||||
'lyrl2004_tokens_test_pt3.dat.gz']
|
||||
|
||||
rcv1_train_data_gz = ['lyrl2004_tokens_train.dat.gz']
|
||||
|
||||
rcv1_doc_cats_data_gz = 'rcv1-v2.topics.qrels.gz'
|
||||
|
||||
RCV2_LANG_DIR = {'ru':'REUTE000',
|
||||
'de':'REUTE00A',
|
||||
'fr':'REUTE00B',
|
||||
'sv':'REUTE001',
|
||||
'no':'REUTE002',
|
||||
'da':'REUTE003',
|
||||
'pt':'REUTE004',
|
||||
'it':'REUTE005',
|
||||
'es':'REUTE006',
|
||||
'lat':'REUTE007',
|
||||
'jp':'REUTE008',
|
||||
'htw':'REUTE009',
|
||||
'nl':'REUTERS_'}
|
||||
|
||||
|
||||
class RCV_Document:
|
||||
|
||||
def __init__(self, id, text, categories, date='', lang=None):
|
||||
self.id = id
|
||||
self.date = date
|
||||
self.lang = lang
|
||||
self.text = text
|
||||
self.categories = categories
|
||||
|
||||
|
||||
class ExpectedLanguageException(Exception): pass
|
||||
class IDRangeException(Exception): pass
|
||||
|
||||
|
||||
nwords = []
|
||||
|
||||
def parse_document(xml_content, assert_lang=None, valid_id_range=None):
|
||||
root = ET.fromstring(xml_content)
|
||||
if assert_lang:
|
||||
if assert_lang not in root.attrib.values():
|
||||
if assert_lang != 'jp' or 'ja' not in root.attrib.values(): # some documents are attributed to 'ja', others to 'jp'
|
||||
raise ExpectedLanguageException('error: document of a different language')
|
||||
|
||||
doc_id = root.attrib['itemid']
|
||||
if valid_id_range is not None:
|
||||
if not valid_id_range[0] <= int(doc_id) <= valid_id_range[1]:
|
||||
raise IDRangeException
|
||||
|
||||
doc_categories = [cat.attrib['code'] for cat in
|
||||
root.findall('.//metadata/codes[@class="bip:topics:1.0"]/code')]
|
||||
|
||||
doc_date = root.attrib['date']
|
||||
doc_title = root.find('.//title').text
|
||||
doc_headline = root.find('.//headline').text
|
||||
doc_body = '\n'.join([p.text for p in root.findall('.//text/p')])
|
||||
|
||||
if not doc_body:
|
||||
raise ValueError('Empty document')
|
||||
|
||||
if doc_title is None: doc_title = ''
|
||||
if doc_headline is None or doc_headline in doc_title: doc_headline = ''
|
||||
text = '\n'.join([doc_title, doc_headline, doc_body]).strip()
|
||||
|
||||
text_length = len(text.split())
|
||||
global nwords
|
||||
nwords.append(text_length)
|
||||
|
||||
return RCV_Document(id=doc_id, text=text, categories=doc_categories, date=doc_date, lang=assert_lang)
|
||||
|
||||
|
||||
def fetch_RCV1(data_path, split='all'):
|
||||
|
||||
assert split in ['train', 'test', 'all'], 'split should be "train", "test", or "all"'
|
||||
|
||||
request = []
|
||||
labels = set()
|
||||
read_documents = 0
|
||||
lang = 'en'
|
||||
|
||||
training_documents = 23149
|
||||
test_documents = 781265
|
||||
|
||||
if split == 'all':
|
||||
split_range = (2286, 810596)
|
||||
expected = training_documents+test_documents
|
||||
elif split == 'train':
|
||||
split_range = (2286, 26150)
|
||||
expected = training_documents
|
||||
else:
|
||||
split_range = (26151, 810596)
|
||||
expected = test_documents
|
||||
|
||||
global nwords
|
||||
nwords=[]
|
||||
for part in list_files(data_path):
|
||||
if not re.match('\d+\.zip', part): continue
|
||||
target_file = join(data_path, part)
|
||||
assert exists(target_file), \
|
||||
"You don't seem to have the file "+part+" in " + data_path + ", and the RCV1 corpus can not be downloaded"+\
|
||||
" w/o a formal permission. Please, refer to " + RCV1_BASE_URL + " for more information."
|
||||
zipfile = ZipFile(target_file)
|
||||
for xmlfile in zipfile.namelist():
|
||||
xmlcontent = zipfile.open(xmlfile).read()
|
||||
try:
|
||||
doc = parse_document(xmlcontent, assert_lang=lang, valid_id_range=split_range)
|
||||
labels.update(doc.categories)
|
||||
request.append(doc)
|
||||
read_documents += 1
|
||||
except ValueError:
|
||||
print('\n\tskipping document {} with inconsistent language label: expected language {}'.format(part+'/'+xmlfile, lang))
|
||||
except (IDRangeException, ExpectedLanguageException) as e:
|
||||
pass
|
||||
print('\r[{}] read {} documents'.format(part, len(request)), end='')
|
||||
if read_documents == expected: break
|
||||
if read_documents == expected: break
|
||||
print()
|
||||
print('ave:{} std {} min {} max {}'.format(np.mean(nwords), np.std(nwords), np.min(nwords), np.max(nwords)))
|
||||
return request, list(labels)
|
||||
|
||||
|
||||
def fetch_RCV2(data_path, languages=None):
|
||||
|
||||
if not languages:
|
||||
languages = list(RCV2_LANG_DIR.keys())
|
||||
else:
|
||||
assert set(languages).issubset(set(RCV2_LANG_DIR.keys())), 'languages not in scope'
|
||||
|
||||
request = []
|
||||
labels = set()
|
||||
global nwords
|
||||
nwords=[]
|
||||
for lang in languages:
|
||||
path = join(data_path, RCV2_LANG_DIR[lang])
|
||||
lang_docs_read = 0
|
||||
for part in list_files(path):
|
||||
target_file = join(path, part)
|
||||
assert exists(target_file), \
|
||||
"You don't seem to have the file "+part+" in " + path + ", and the RCV2 corpus can not be downloaded"+\
|
||||
" w/o a formal permission. Please, refer to " + RCV2_BASE_URL + " for more information."
|
||||
zipfile = ZipFile(target_file)
|
||||
for xmlfile in zipfile.namelist():
|
||||
xmlcontent = zipfile.open(xmlfile).read()
|
||||
try:
|
||||
doc = parse_document(xmlcontent, assert_lang=lang)
|
||||
labels.update(doc.categories)
|
||||
request.append(doc)
|
||||
lang_docs_read += 1
|
||||
except ValueError:
|
||||
print('\n\tskipping document {} with inconsistent language label: expected language {}'.format(RCV2_LANG_DIR[lang]+'/'+part+'/'+xmlfile, lang))
|
||||
except (IDRangeException, ExpectedLanguageException) as e:
|
||||
pass
|
||||
print('\r[{}] read {} documents, {} for language {}'.format(RCV2_LANG_DIR[lang]+'/'+part, len(request), lang_docs_read, lang), end='')
|
||||
print()
|
||||
print('ave:{} std {} min {} max {}'.format(np.mean(nwords), np.std(nwords), np.min(nwords), np.max(nwords)))
|
||||
return request, list(labels)
|
||||
|
||||
|
||||
def fetch_topic_hierarchy(path, topics='all'):
|
||||
assert topics in ['all', 'leaves']
|
||||
|
||||
download_file_if_not_exists(RCV1_TOPICHIER_URL, path)
|
||||
hierarchy = {}
|
||||
for line in open(path, 'rt'):
|
||||
parts = line.strip().split()
|
||||
parent,child = parts[1],parts[3]
|
||||
if parent not in hierarchy:
|
||||
hierarchy[parent]=[]
|
||||
hierarchy[parent].append(child)
|
||||
|
||||
del hierarchy['None']
|
||||
del hierarchy['Root']
|
||||
print(hierarchy)
|
||||
|
||||
if topics=='all':
|
||||
topics = set(hierarchy.keys())
|
||||
for parent in hierarchy.keys():
|
||||
topics.update(hierarchy[parent])
|
||||
return list(topics)
|
||||
elif topics=='leaves':
|
||||
parents = set(hierarchy.keys())
|
||||
childs = set()
|
||||
for parent in hierarchy.keys():
|
||||
childs.update(hierarchy[parent])
|
||||
return list(childs.difference(parents))
|
||||
|
||||
|
||||
|
|
@ -1,304 +0,0 @@
|
|||
from __future__ import print_function
|
||||
# import ijson
|
||||
# from ijson.common import ObjectBuilder
|
||||
import os, sys
|
||||
from os.path import join
|
||||
from bz2 import BZ2File
|
||||
import pickle
|
||||
from util.file import list_dirs, list_files, makedirs_if_not_exist
|
||||
from itertools import islice
|
||||
import re
|
||||
from xml.sax.saxutils import escape
|
||||
import numpy as np
|
||||
|
||||
policies = ["IN_ALL_LANGS", "IN_ANY_LANG"]
|
||||
|
||||
"""
|
||||
This file contains a set of tools for processing the Wikipedia multilingual documents.
|
||||
In what follows, it is assumed that you have already downloaded a Wikipedia dump (https://dumps.wikimedia.org/)
|
||||
and have processed each document to clean their texts with one of the tools:
|
||||
- https://github.com/aesuli/wikipediatools (Python 2)
|
||||
- https://github.com/aesuli/wikipedia-extractor (Python 3)
|
||||
It is also assumed you have dowloaded the all-entities json file (e.g., https://dumps.wikimedia.org/wikidatawiki/entities/latest-all.json.bz2)
|
||||
|
||||
This tools help you in:
|
||||
- Processes the huge json file as a stream, and create a multilingual map of corresponding titles for each language.
|
||||
Set the policy = "IN_ALL_LANGS" will extract only titles which appear in all (AND) languages, whereas "IN_ANY_LANG"
|
||||
extracts all titles appearing in at least one (OR) language (warning: this will creates a huge dictionary).
|
||||
Note: This version is quite slow. Although it is run once for all, you might be prefer to take a look at "Wikidata in BigQuery".
|
||||
- Processes the huge json file as a stream a creates a simplified file which occupies much less and is far faster to be processed.
|
||||
- Use the multilingual map to extract, from the clean text versions, individual xml documents containing all
|
||||
language-specific versions from the document.
|
||||
- Fetch the multilingual documents to create, for each of the specified languages, a list containing all documents,
|
||||
in a way that the i-th element from any list refers to the same element in the respective language.
|
||||
"""
|
||||
|
||||
def _doc_generator(text_path, langs):
|
||||
dotspace = re.compile(r'\.(?!\s)')
|
||||
for l,lang in enumerate(langs):
|
||||
print("Processing language <%s> (%d/%d)" % (lang, l, len(langs)))
|
||||
lang_dir = join(text_path, lang)
|
||||
split_dirs = list_dirs(lang_dir)
|
||||
for sd,split_dir in enumerate(split_dirs):
|
||||
print("\tprocessing split_dir <%s> (%d/%d)" % (split_dir, sd, len(split_dirs)))
|
||||
split_files = list_files(join(lang_dir, split_dir))
|
||||
for sf,split_file in enumerate(split_files):
|
||||
print("\t\tprocessing split_file <%s> (%d/%d)" % (split_file, sf, len(split_files)))
|
||||
with BZ2File(join(lang_dir, split_dir, split_file), 'r', buffering=1024*1024) as fi:
|
||||
while True:
|
||||
doc_lines = list(islice(fi, 3))
|
||||
if doc_lines:
|
||||
# some sentences are not followed by a space after the dot
|
||||
doc_lines[1] = dotspace.sub('. ', doc_lines[1])
|
||||
# [workaround] I found html symbol was not treated, and unescaping it now might not help...
|
||||
doc_lines[1] = escape(doc_lines[1].replace(" ", " "))
|
||||
yield doc_lines, lang
|
||||
else: break
|
||||
|
||||
def _extract_title(doc_lines):
|
||||
m = re.search('title="(.+?)"', doc_lines[0])
|
||||
if m: return m.group(1).decode('utf-8')
|
||||
else: raise ValueError("Error in xml format: document head is %s" % doc_lines[0])
|
||||
|
||||
def _create_doc(target_file, id, doc, lang):
|
||||
doc[0] = doc[0][:-2] + (' lang="%s">\n'%lang)
|
||||
with open(target_file, 'w') as fo:
|
||||
fo.write('<multidoc id="%s">\n'%id)
|
||||
[fo.write(line) for line in doc]
|
||||
fo.write('</multidoc>')
|
||||
|
||||
def _append_doc(target_file, doc, lang):
|
||||
doc[0] = doc[0][:-2] + (' lang="%s">\n' % lang)
|
||||
with open(target_file, 'r', buffering=1024*1024) as fi:
|
||||
lines = fi.readlines()
|
||||
if doc[0] in lines[1::3]:
|
||||
return
|
||||
lines[-1:-1]=doc
|
||||
with open(target_file, 'w', buffering=1024*1024) as fo:
|
||||
[fo.write(line) for line in lines]
|
||||
|
||||
def extract_multilingual_documents(inv_dict, langs, text_path, out_path):
|
||||
if not os.path.exists(out_path):
|
||||
os.makedirs(out_path)
|
||||
for lang in langs:
|
||||
if lang not in inv_dict:
|
||||
raise ValueError("Lang %s is not in the dictionary" % lang)
|
||||
|
||||
docs_created = len(list_files(out_path))
|
||||
print("%d multilingual documents found." % docs_created)
|
||||
for doc,lang in _doc_generator(text_path, langs):
|
||||
title = _extract_title(doc)
|
||||
|
||||
if title in inv_dict[lang]:
|
||||
#pass
|
||||
ids = inv_dict[lang][title]
|
||||
for id in ids:
|
||||
target_file = join(out_path, id) + ".xml"
|
||||
if os.path.exists(target_file):
|
||||
_append_doc(target_file, doc, lang)
|
||||
else:
|
||||
_create_doc(target_file, id, doc, lang)
|
||||
docs_created+=1
|
||||
else:
|
||||
if not re.match('[A-Za-z]+', title):
|
||||
print("Title <%s> for lang <%s> not in dictionary" % (title, lang))
|
||||
|
||||
|
||||
|
||||
def extract_multilingual_titles_from_simplefile(data_dir, filename, langs, policy="IN_ALL_LANGS", return_both=True):
|
||||
simplified_file = join(data_dir,filename)
|
||||
|
||||
if policy not in policies:
|
||||
raise ValueError("Policy %s not supported." % policy)
|
||||
print("extracting multilingual titles with policy %s (%s)" % (policy,' '.join(langs)))
|
||||
|
||||
lang_prefix = list(langs)
|
||||
lang_prefix.sort()
|
||||
pickle_prefix = "extraction_" + "_".join(lang_prefix) + "." + policy
|
||||
pickle_dict = join(data_dir, pickle_prefix+".multi_dict.pickle")
|
||||
pickle_invdict = join(data_dir, pickle_prefix+".multi_invdict.pickle")
|
||||
if os.path.exists(pickle_invdict):
|
||||
if return_both and os.path.exists(pickle_dict):
|
||||
print("Pickled files found in %s. Loading both (direct and inverse dictionaries)." % data_dir)
|
||||
return pickle.load(open(pickle_dict, 'rb')), pickle.load(open(pickle_invdict, 'rb'))
|
||||
elif return_both==False:
|
||||
print("Pickled file found in %s. Loading inverse dictionary only." % pickle_invdict)
|
||||
return pickle.load(open(pickle_invdict, 'rb'))
|
||||
|
||||
multiling_titles = {}
|
||||
inv_dict = {lang:{} for lang in langs}
|
||||
|
||||
def process_entry(line):
|
||||
parts = line.strip().split('\t')
|
||||
id = parts[0]
|
||||
if id in multiling_titles:
|
||||
raise ValueError("id <%s> already indexed" % id)
|
||||
|
||||
titles = dict(((lang_title[:lang_title.find(':')],lang_title[lang_title.find(':')+1:].decode('utf-8')) for lang_title in parts[1:]))
|
||||
for lang in titles.keys():
|
||||
if lang not in langs:
|
||||
del titles[lang]
|
||||
|
||||
if (policy == "IN_ALL_LANGS" and len(titles) == len(langs))\
|
||||
or (policy == "IN_ANY_LANG" and len(titles) > 0):
|
||||
multiling_titles[id] = titles
|
||||
for lang, title in titles.items():
|
||||
if title in inv_dict[lang]:
|
||||
inv_dict[lang][title].append(id)
|
||||
inv_dict[lang][title] = [id]
|
||||
|
||||
with BZ2File(simplified_file, 'r', buffering=1024*1024*16) as fi:
|
||||
completed = 0
|
||||
try:
|
||||
for line in fi:
|
||||
process_entry(line)
|
||||
completed += 1
|
||||
if completed % 10 == 0:
|
||||
print("\rCompleted %d\ttitles %d" % (completed,len(multiling_titles)), end="")
|
||||
print("\rCompleted %d\t\ttitles %d" % (completed, len(multiling_titles)), end="\n")
|
||||
except EOFError:
|
||||
print("\nUnexpected file ending... saving anyway")
|
||||
|
||||
print("Pickling dictionaries in %s" % data_dir)
|
||||
pickle.dump(multiling_titles, open(pickle_dict,'wb'), pickle.HIGHEST_PROTOCOL)
|
||||
pickle.dump(inv_dict, open(pickle_invdict, 'wb'), pickle.HIGHEST_PROTOCOL)
|
||||
print("Done")
|
||||
|
||||
return (multiling_titles, inv_dict) if return_both else inv_dict
|
||||
|
||||
|
||||
# in https://dumps.wikimedia.org/wikidatawiki/entities/latest-all.json.bz2
|
||||
def simplify_json_file(data_dir, langs, policy="IN_ALL_LANGS", json_file = "latest-all.json.bz2"):
|
||||
latest_all_json_file = join(data_dir,json_file)
|
||||
|
||||
if policy not in policies:
|
||||
raise ValueError("Policy %s not supported." % policy)
|
||||
|
||||
print("extracting multilingual titles with policy %s (%s)" % (policy,' '.join(langs)))
|
||||
|
||||
lang_prefix = list(langs)
|
||||
lang_prefix.sort()
|
||||
simple_titles_path = join(data_dir, "extraction_" + "_".join(lang_prefix) + "." + policy)
|
||||
|
||||
def process_entry(last, fo):
|
||||
global written
|
||||
id = last["id"]
|
||||
titles = None
|
||||
if policy == "IN_ALL_LANGS" and langs.issubset(last["labels"].keys()):
|
||||
titles = {lang: last["labels"][lang]["value"] for lang in langs}
|
||||
elif policy == "IN_ANY_LANG":
|
||||
titles = {lang: last["labels"][lang]["value"] for lang in langs if lang in last["labels"]}
|
||||
|
||||
if titles:
|
||||
fo.write((id+'\t'+'\t'.join([lang+':'+titles[lang] for lang in titles.keys()])+'\n').encode('utf-8'))
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
written = 0
|
||||
with BZ2File(latest_all_json_file, 'r', buffering=1024*1024*16) as fi, \
|
||||
BZ2File(join(data_dir,simple_titles_path+".simple.bz2"),'w') as fo:
|
||||
builder = ObjectBuilder()
|
||||
completed = 0
|
||||
for event, value in ijson.basic_parse(fi, buf_size=1024*1024*16):
|
||||
builder.event(event, value)
|
||||
if len(builder.value)>1:
|
||||
if process_entry(builder.value.pop(0), fo): written += 1
|
||||
completed += 1
|
||||
print("\rCompleted %d\ttitles %d" % (completed,written), end="")
|
||||
print("")
|
||||
|
||||
#process the last entry
|
||||
process_entry(builder.value.pop(0))
|
||||
|
||||
return simple_titles_path
|
||||
|
||||
"""
|
||||
Reads all multi-lingual documents in a folder (see wikipedia_tools.py to generate them) and generates, for each of the
|
||||
specified languages, a list contanining all its documents, so that the i-th element of any list refers to the language-
|
||||
specific version of the same document. Documents are forced to contain version in all specified languages and to contain
|
||||
a minimum number of words; otherwise it is discarded.
|
||||
"""
|
||||
class MinWordsNotReached(Exception): pass
|
||||
class WrongDocumentFormat(Exception): pass
|
||||
|
||||
def _load_multilang_doc(path, langs, min_words=100):
|
||||
import xml.etree.ElementTree as ET
|
||||
from xml.etree.ElementTree import Element, ParseError
|
||||
try:
|
||||
root = ET.parse(path).getroot()
|
||||
doc = {}
|
||||
for lang in langs:
|
||||
doc_body = root.find('.//doc[@lang="' + lang + '"]')
|
||||
if isinstance(doc_body, Element):
|
||||
n_words = len(doc_body.text.split(' '))
|
||||
if n_words >= min_words:
|
||||
doc[lang] = doc_body.text
|
||||
else:
|
||||
raise MinWordsNotReached
|
||||
else:
|
||||
raise WrongDocumentFormat
|
||||
except ParseError:
|
||||
raise WrongDocumentFormat
|
||||
return doc
|
||||
|
||||
#returns the multilingual documents mapped by language, and a counter with the number of documents readed
|
||||
def fetch_wikipedia_multilingual(wiki_multi_path, langs, min_words=100, deletions=False, max_documents=-1, pickle_name=None):
|
||||
if pickle_name and os.path.exists(pickle_name):
|
||||
print("unpickling %s" % pickle_name)
|
||||
return pickle.load(open(pickle_name, 'rb'))
|
||||
|
||||
multi_docs = list_files(wiki_multi_path)
|
||||
mling_documents = {l:[] for l in langs}
|
||||
valid_documents = 0
|
||||
minwords_exception = 0
|
||||
wrongdoc_exception = 0
|
||||
for d,multi_doc in enumerate(multi_docs):
|
||||
print("\rProcessed %d/%d documents, valid %d/%d, few_words=%d, few_langs=%d" %
|
||||
(d, len(multi_docs), valid_documents, len(multi_docs), minwords_exception, wrongdoc_exception),end="")
|
||||
doc_path = join(wiki_multi_path, multi_doc)
|
||||
try:
|
||||
m_doc = _load_multilang_doc(doc_path, langs, min_words)
|
||||
valid_documents += 1
|
||||
for l in langs:
|
||||
mling_documents[l].append(m_doc[l])
|
||||
except MinWordsNotReached:
|
||||
minwords_exception += 1
|
||||
if deletions: os.remove(doc_path)
|
||||
except WrongDocumentFormat:
|
||||
wrongdoc_exception += 1
|
||||
if deletions: os.remove(doc_path)
|
||||
if max_documents>0 and valid_documents>=max_documents:
|
||||
break
|
||||
|
||||
if pickle_name:
|
||||
print("Pickling wikipedia documents object in %s" % pickle_name)
|
||||
pickle.dump(mling_documents, open(pickle_name, 'wb'), pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
return mling_documents
|
||||
|
||||
def random_wiki_sample(l_wiki, max_documents):
|
||||
if max_documents == 0: return None
|
||||
langs = list(l_wiki.keys())
|
||||
assert len(np.unique([len(l_wiki[l]) for l in langs])) == 1, 'documents across languages do not seem to be aligned'
|
||||
ndocs_per_lang = len(l_wiki[langs[0]])
|
||||
if ndocs_per_lang > max_documents:
|
||||
sel = set(np.random.choice(list(range(ndocs_per_lang)), max_documents, replace=False))
|
||||
for lang in langs:
|
||||
l_wiki[lang] = [d for i, d in enumerate(l_wiki[lang]) if i in sel]
|
||||
return l_wiki
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
wikipedia_home = "../Datasets/Wikipedia"
|
||||
|
||||
from data.languages import JRC_LANGS_WITH_NLTK_STEMMING as langs
|
||||
langs = frozenset(langs)
|
||||
|
||||
simple_titles_path = simplify_json_file(wikipedia_home, langs, policy="IN_ALL_LANGS", json_file="latest-all.json.bz2")
|
||||
_, inv_dict = extract_multilingual_titles_from_simplefile(wikipedia_home, simple_titles_path, langs, policy='IN_ALL_LANGS')
|
||||
extract_multilingual_documents(inv_dict, langs, join(wikipedia_home,'text'),
|
||||
out_path=join(wikipedia_home, 'multilingual_docs_JRC_NLTK'))
|
||||
|
||||
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
from nltk.corpus import stopwords
|
||||
from data.languages import NLTK_LANGMAP
|
||||
from nltk import word_tokenize
|
||||
from nltk.stem import SnowballStemmer
|
||||
|
||||
|
||||
def preprocess_documents(documents, lang):
|
||||
tokens = NLTKStemTokenizer(lang, verbose=True)
|
||||
sw = stopwords.words(NLTK_LANGMAP[lang])
|
||||
return [' '.join([w for w in tokens(doc) if w not in sw]) for doc in documents]
|
||||
|
||||
|
||||
class NLTKStemTokenizer(object):
|
||||
|
||||
def __init__(self, lang, verbose=False):
|
||||
if lang not in NLTK_LANGMAP:
|
||||
raise ValueError('Language %s is not supported in NLTK' % lang)
|
||||
self.verbose=verbose
|
||||
self.called = 0
|
||||
self.wnl = SnowballStemmer(NLTK_LANGMAP[lang])
|
||||
self.cache = {}
|
||||
|
||||
def __call__(self, doc):
|
||||
self.called += 1
|
||||
if self.verbose:
|
||||
print("\r\t\t[documents processed %d]" % (self.called), end="")
|
||||
tokens = word_tokenize(doc)
|
||||
stems = []
|
||||
for t in tokens:
|
||||
if t not in self.cache:
|
||||
self.cache[t] = self.wnl.stem(t)
|
||||
stems.append(self.cache[t])
|
||||
return stems
|
||||
|
|
@ -1,270 +0,0 @@
|
|||
import math
|
||||
import numpy as np
|
||||
from scipy.stats import t
|
||||
from joblib import Parallel, delayed
|
||||
from scipy.sparse import csr_matrix, csc_matrix
|
||||
|
||||
|
||||
def get_probs(tpr, fpr, pc):
|
||||
# tpr = p(t|c) = p(tp)/p(c) = p(tp)/(p(tp)+p(fn))
|
||||
# fpr = p(t|_c) = p(fp)/p(_c) = p(fp)/(p(fp)+p(tn))
|
||||
pnc = 1.0 - pc
|
||||
tp = tpr * pc
|
||||
fn = pc - tp
|
||||
fp = fpr * pnc
|
||||
tn = pnc - fp
|
||||
return ContTable(tp=tp, fn=fn, fp=fp, tn=tn)
|
||||
|
||||
|
||||
def apply_tsr(tpr, fpr, pc, tsr):
|
||||
cell = get_probs(tpr, fpr, pc)
|
||||
return tsr(cell)
|
||||
|
||||
|
||||
def positive_information_gain(cell):
|
||||
if cell.tpr() < cell.fpr():
|
||||
return 0.0
|
||||
else:
|
||||
return information_gain(cell)
|
||||
|
||||
|
||||
def posneg_information_gain(cell):
|
||||
ig = information_gain(cell)
|
||||
if cell.tpr() < cell.fpr():
|
||||
return -ig
|
||||
else:
|
||||
return ig
|
||||
|
||||
|
||||
def __ig_factor(p_tc, p_t, p_c):
|
||||
den = p_t * p_c
|
||||
if den != 0.0 and p_tc != 0:
|
||||
return p_tc * math.log(p_tc / den, 2)
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
|
||||
def information_gain(cell):
|
||||
return __ig_factor(cell.p_tp(), cell.p_f(), cell.p_c()) + \
|
||||
__ig_factor(cell.p_fp(), cell.p_f(), cell.p_not_c()) +\
|
||||
__ig_factor(cell.p_fn(), cell.p_not_f(), cell.p_c()) + \
|
||||
__ig_factor(cell.p_tn(), cell.p_not_f(), cell.p_not_c())
|
||||
|
||||
|
||||
def information_gain_mod(cell):
|
||||
return (__ig_factor(cell.p_tp(), cell.p_f(), cell.p_c()) + __ig_factor(cell.p_tn(), cell.p_not_f(), cell.p_not_c())) \
|
||||
- (__ig_factor(cell.p_fp(), cell.p_f(), cell.p_not_c()) + __ig_factor(cell.p_fn(), cell.p_not_f(), cell.p_c()))
|
||||
|
||||
|
||||
def pointwise_mutual_information(cell):
|
||||
return __ig_factor(cell.p_tp(), cell.p_f(), cell.p_c())
|
||||
|
||||
|
||||
def gain_ratio(cell):
|
||||
pc = cell.p_c()
|
||||
pnc = 1.0 - pc
|
||||
norm = pc * math.log(pc, 2) + pnc * math.log(pnc, 2)
|
||||
return information_gain(cell) / (-norm)
|
||||
|
||||
|
||||
def chi_square(cell):
|
||||
den = cell.p_f() * cell.p_not_f() * cell.p_c() * cell.p_not_c()
|
||||
if den==0.0: return 0.0
|
||||
num = gss(cell)**2
|
||||
return num / den
|
||||
|
||||
|
||||
def relevance_frequency(cell):
|
||||
a = cell.tp
|
||||
c = cell.fp
|
||||
if c == 0: c = 1
|
||||
return math.log(2.0 + (a * 1.0 / c), 2)
|
||||
|
||||
|
||||
def idf(cell):
|
||||
if cell.p_f()>0:
|
||||
return math.log(1.0 / cell.p_f())
|
||||
return 0.0
|
||||
|
||||
|
||||
def gss(cell):
|
||||
return cell.p_tp()*cell.p_tn() - cell.p_fp()*cell.p_fn()
|
||||
|
||||
|
||||
def conf_interval(xt, n):
|
||||
if n>30:
|
||||
z2 = 3.84145882069 # norm.ppf(0.5+0.95/2.0)**2
|
||||
else:
|
||||
z2 = t.ppf(0.5 + 0.95 / 2.0, df=max(n-1,1)) ** 2
|
||||
p = (xt + 0.5 * z2) / (n + z2)
|
||||
amplitude = 0.5 * z2 * math.sqrt((p * (1.0 - p)) / (n + z2))
|
||||
return p, amplitude
|
||||
|
||||
def strength(minPosRelFreq, minPos, maxNeg):
|
||||
if minPos > maxNeg:
|
||||
return math.log(2.0 * minPosRelFreq, 2.0)
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
|
||||
#set cancel_features=True to allow some features to be weighted as 0 (as in the original article)
|
||||
#however, for some extremely imbalanced dataset caused all documents to be 0
|
||||
def conf_weight(cell, cancel_features=False):
|
||||
c = cell.get_c()
|
||||
not_c = cell.get_not_c()
|
||||
tp = cell.tp
|
||||
fp = cell.fp
|
||||
|
||||
pos_p, pos_amp = conf_interval(tp, c)
|
||||
neg_p, neg_amp = conf_interval(fp, not_c)
|
||||
|
||||
min_pos = pos_p-pos_amp
|
||||
max_neg = neg_p+neg_amp
|
||||
den = (min_pos + max_neg)
|
||||
minpos_relfreq = min_pos / (den if den != 0 else 1)
|
||||
|
||||
str_tplus = strength(minpos_relfreq, min_pos, max_neg);
|
||||
|
||||
if str_tplus == 0 and not cancel_features:
|
||||
return 1e-20
|
||||
|
||||
return str_tplus;
|
||||
|
||||
|
||||
class ContTable:
|
||||
|
||||
def __init__(self, tp=0, tn=0, fp=0, fn=0):
|
||||
self.tp=tp
|
||||
self.tn=tn
|
||||
self.fp=fp
|
||||
self.fn=fn
|
||||
|
||||
def get_d(self): return self.tp + self.tn + self.fp + self.fn
|
||||
|
||||
def get_c(self): return self.tp + self.fn
|
||||
|
||||
def get_not_c(self): return self.tn + self.fp
|
||||
|
||||
def get_f(self): return self.tp + self.fp
|
||||
|
||||
def get_not_f(self): return self.tn + self.fn
|
||||
|
||||
def p_c(self): return (1.0*self.get_c())/self.get_d()
|
||||
|
||||
def p_not_c(self): return 1.0-self.p_c()
|
||||
|
||||
def p_f(self): return (1.0*self.get_f())/self.get_d()
|
||||
|
||||
def p_not_f(self): return 1.0-self.p_f()
|
||||
|
||||
def p_tp(self): return (1.0*self.tp) / self.get_d()
|
||||
|
||||
def p_tn(self): return (1.0*self.tn) / self.get_d()
|
||||
|
||||
def p_fp(self): return (1.0*self.fp) / self.get_d()
|
||||
|
||||
def p_fn(self): return (1.0*self.fn) / self.get_d()
|
||||
|
||||
def tpr(self):
|
||||
c = 1.0*self.get_c()
|
||||
return self.tp / c if c > 0.0 else 0.0
|
||||
|
||||
def fpr(self):
|
||||
_c = 1.0*self.get_not_c()
|
||||
return self.fp / _c if _c > 0.0 else 0.0
|
||||
|
||||
|
||||
def round_robin_selection(X, Y, k, tsr_function=positive_information_gain):
|
||||
print(f'[selectiong {k} terms]')
|
||||
nC = Y.shape[1]
|
||||
FC = get_tsr_matrix(get_supervised_matrix(X, Y), tsr_function).T
|
||||
best_features_idx = np.argsort(-FC, axis=0).flatten()
|
||||
tsr_values = FC.flatten()
|
||||
selected_indexes_set = set()
|
||||
selected_indexes = list()
|
||||
selected_value = list()
|
||||
from_category = list()
|
||||
round_robin = iter(best_features_idx)
|
||||
values_iter = iter(tsr_values)
|
||||
round=0
|
||||
while len(selected_indexes) < k:
|
||||
term_idx = next(round_robin)
|
||||
term_val = next(values_iter)
|
||||
if term_idx not in selected_indexes_set:
|
||||
selected_indexes_set.add(term_idx)
|
||||
selected_indexes.append(term_idx)
|
||||
selected_value.append(term_val)
|
||||
from_category.append(round)
|
||||
round = (round + 1) % nC
|
||||
return np.asarray(selected_indexes, dtype=int), np.asarray(selected_value, dtype=float), np.asarray(from_category)
|
||||
|
||||
|
||||
def feature_label_contingency_table(positive_document_indexes, feature_document_indexes, nD):
|
||||
tp_ = len(positive_document_indexes & feature_document_indexes)
|
||||
fp_ = len(feature_document_indexes - positive_document_indexes)
|
||||
fn_ = len(positive_document_indexes - feature_document_indexes)
|
||||
tn_ = nD - (tp_ + fp_ + fn_)
|
||||
return ContTable(tp=tp_, tn=tn_, fp=fp_, fn=fn_)
|
||||
|
||||
|
||||
def category_tables(feature_sets, category_sets, c, nD, nF):
|
||||
return [feature_label_contingency_table(category_sets[c], feature_sets[f], nD) for f in range(nF)]
|
||||
|
||||
|
||||
"""
|
||||
Computes the nC x nF supervised matrix M where Mcf is the 4-cell contingency table for feature f and class c.
|
||||
Efficiency O(nF x nC x log(S)) where S is the sparse factor
|
||||
"""
|
||||
def get_supervised_matrix(coocurrence_matrix, label_matrix, n_jobs=-1):
|
||||
nD, nF = coocurrence_matrix.shape
|
||||
nD2, nC = label_matrix.shape
|
||||
|
||||
if nD != nD2:
|
||||
raise ValueError('Number of rows in coocurrence matrix shape %s and label matrix shape %s is not consistent' %
|
||||
(coocurrence_matrix.shape,label_matrix.shape))
|
||||
|
||||
def nonzero_set(matrix, col):
|
||||
return set(matrix[:, col].nonzero()[0])
|
||||
|
||||
if isinstance(coocurrence_matrix, csr_matrix):
|
||||
coocurrence_matrix = csc_matrix(coocurrence_matrix)
|
||||
feature_sets = [nonzero_set(coocurrence_matrix, f) for f in range(nF)]
|
||||
category_sets = [nonzero_set(label_matrix, c) for c in range(nC)]
|
||||
cell_matrix = Parallel(n_jobs=n_jobs, backend="threading")(delayed(category_tables)(feature_sets, category_sets, c, nD, nF) for c in range(nC))
|
||||
return np.array(cell_matrix)
|
||||
|
||||
# obtains the matrix T where Tcf=tsr(f,c) is the tsr score for category c and feature f
|
||||
def get_tsr_matrix(cell_matrix, tsr_score_funtion):
|
||||
nC,nF = cell_matrix.shape
|
||||
tsr_matrix = [[tsr_score_funtion(cell_matrix[c,f]) for f in range(nF)] for c in range(nC)]
|
||||
return np.array(tsr_matrix)
|
||||
|
||||
|
||||
""" The Fisher-score [1] is not computed on the 4-cell contingency table, but can
|
||||
take as input any real-valued feature column (e.g., tf-idf weights).
|
||||
feat is the feature vector, and c is a binary classification vector.
|
||||
This implementation covers only the binary case, while the formula is defined for multiclass
|
||||
single-label scenarios, for which the version [2] might be preferred.
|
||||
[1] R.O. Duda, P.E. Hart, and D.G. Stork. Pattern classification. Wiley-interscience, 2012.
|
||||
[2] Gu, Q., Li, Z., & Han, J. (2012). Generalized fisher score for feature selection. arXiv preprint arXiv:1202.3725.
|
||||
"""
|
||||
def fisher_score_binary(feat, c):
|
||||
neg = np.ones_like(c) - c
|
||||
|
||||
npos = np.sum(c)
|
||||
nneg = np.sum(neg)
|
||||
|
||||
mupos = np.mean(feat[c == 1])
|
||||
muneg = np.mean(feat[neg == 1])
|
||||
mu = np.mean(feat)
|
||||
|
||||
stdpos = np.std(feat[c == 1])
|
||||
stdneg = np.std(feat[neg == 1])
|
||||
|
||||
num = npos * ((mupos - mu) ** 2) + nneg * ((muneg - mu) ** 2)
|
||||
den = npos * (stdpos ** 2) + nneg * (stdneg ** 2)
|
||||
|
||||
if den>0:
|
||||
return num / den
|
||||
else:
|
||||
return num
|
||||
|
|
@ -1,710 +0,0 @@
|
|||
from os.path import join, exists
|
||||
from nltk.corpus import stopwords
|
||||
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
|
||||
from sklearn.preprocessing import MultiLabelBinarizer
|
||||
from data.reader.jrcacquis_reader import *
|
||||
from data.languages import lang_set, NLTK_LANGMAP, RCV2_LANGS_WITH_NLTK_STEMMING
|
||||
from data.reader.rcv_reader import fetch_RCV1, fetch_RCV2, fetch_topic_hierarchy
|
||||
from data.text_preprocessor import NLTKStemTokenizer, preprocess_documents
|
||||
import pickle
|
||||
import numpy as np
|
||||
from sklearn.model_selection import train_test_split
|
||||
from scipy.sparse import issparse
|
||||
import itertools
|
||||
from tqdm import tqdm
|
||||
import re
|
||||
from scipy.sparse import csr_matrix
|
||||
|
||||
|
||||
class MultilingualDataset:
|
||||
"""
|
||||
A multilingual dataset is a dictionary of training and test documents indexed by language code.
|
||||
Train and test sets are represented as tuples of the type (X,Y,ids), where X is a matrix representation of the
|
||||
documents (e.g., a document-by-term sparse csr_matrix), Y is a document-by-label binary np.array indicating the
|
||||
labels of each document, and ids is a list of document-identifiers from the original collection.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.dataset_name = ""
|
||||
self.multiling_dataset = {}
|
||||
|
||||
def add(self, lang, Xtr, Ytr, Xte, Yte, tr_ids=None, te_ids=None):
|
||||
self.multiling_dataset[lang] = ((Xtr, Ytr, tr_ids), (Xte, Yte, te_ids))
|
||||
|
||||
def save(self, file):
|
||||
self.sort_indexes()
|
||||
pickle.dump(self, open(file, 'wb'), pickle.HIGHEST_PROTOCOL)
|
||||
return self
|
||||
|
||||
def __getitem__(self, item):
|
||||
if item in self.langs():
|
||||
return self.multiling_dataset[item]
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def load(cls, file):
|
||||
data = pickle.load(open(file, 'rb'))
|
||||
data.sort_indexes()
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def load_ids(cls, file):
|
||||
data = pickle.load(open(file, 'rb'))
|
||||
tr_ids = {lang:tr_ids for (lang,((_,_,tr_ids), (_,_,_))) in data.multiling_dataset.items()}
|
||||
te_ids = {lang: te_ids for (lang, ((_, _, _), (_, _, te_ids))) in data.multiling_dataset.items()}
|
||||
return tr_ids, te_ids
|
||||
|
||||
def sort_indexes(self):
|
||||
for (lang, ((Xtr,_,_),(Xte,_,_))) in self.multiling_dataset.items():
|
||||
if issparse(Xtr): Xtr.sort_indices()
|
||||
if issparse(Xte): Xte.sort_indices()
|
||||
|
||||
def set_view(self, categories=None, languages=None):
|
||||
if categories is not None:
|
||||
if isinstance(categories, int):
|
||||
categories = np.array([categories])
|
||||
elif isinstance(categories, list):
|
||||
categories = np.array(categories)
|
||||
self.categories_view = categories
|
||||
if languages is not None:
|
||||
self.languages_view = languages
|
||||
|
||||
def training(self, mask_numbers=False, target_as_csr=False):
|
||||
return self.lXtr(mask_numbers), self.lYtr(as_csr=target_as_csr)
|
||||
|
||||
def test(self, mask_numbers=False, target_as_csr=False):
|
||||
return self.lXte(mask_numbers), self.lYte(as_csr=target_as_csr)
|
||||
|
||||
def lXtr(self, mask_numbers=False):
|
||||
proc = lambda x:_mask_numbers(x) if mask_numbers else x
|
||||
# return {lang: Xtr for (lang, ((Xtr, _, _), _)) in self.multiling_dataset.items() if lang in self.langs()}
|
||||
return {lang:proc(Xtr) for (lang, ((Xtr,_,_),_)) in self.multiling_dataset.items() if lang in self.langs()}
|
||||
|
||||
def lXte(self, mask_numbers=False):
|
||||
proc = lambda x: _mask_numbers(x) if mask_numbers else x
|
||||
# return {lang: Xte for (lang, (_, (Xte, _, _))) in self.multiling_dataset.items() if lang in self.langs()}
|
||||
return {lang:proc(Xte) for (lang, (_,(Xte,_,_))) in self.multiling_dataset.items() if lang in self.langs()}
|
||||
|
||||
def lYtr(self, as_csr=False):
|
||||
lY = {lang:self.cat_view(Ytr) for (lang, ((_,Ytr,_),_)) in self.multiling_dataset.items() if lang in self.langs()}
|
||||
if as_csr:
|
||||
lY = {l:csr_matrix(Y) for l,Y in lY.items()}
|
||||
return lY
|
||||
|
||||
def lYte(self, as_csr=False):
|
||||
lY = {lang:self.cat_view(Yte) for (lang, (_,(_,Yte,_))) in self.multiling_dataset.items() if lang in self.langs()}
|
||||
if as_csr:
|
||||
lY = {l:csr_matrix(Y) for l,Y in lY.items()}
|
||||
return lY
|
||||
|
||||
def cat_view(self, Y):
|
||||
if hasattr(self, 'categories_view'):
|
||||
return Y[:,self.categories_view]
|
||||
else:
|
||||
return Y
|
||||
|
||||
def langs(self):
|
||||
if hasattr(self, 'languages_view'):
|
||||
langs = self.languages_view
|
||||
else:
|
||||
langs = sorted(self.multiling_dataset.keys())
|
||||
return langs
|
||||
|
||||
def num_categories(self):
|
||||
return self.lYtr()[self.langs()[0]].shape[1]
|
||||
|
||||
def show_dimensions(self):
|
||||
def shape(X):
|
||||
return X.shape if hasattr(X, 'shape') else len(X)
|
||||
for (lang, ((Xtr, Ytr, IDtr), (Xte, Yte, IDte))) in self.multiling_dataset.items():
|
||||
if lang not in self.langs(): continue
|
||||
print("Lang {}, Xtr={}, ytr={}, Xte={}, yte={}".format(lang, shape(Xtr), self.cat_view(Ytr).shape, shape(Xte), self.cat_view(Yte).shape))
|
||||
|
||||
def show_category_prevalences(self):
|
||||
nC = self.num_categories()
|
||||
accum_tr = np.zeros(nC, dtype=np.int)
|
||||
accum_te = np.zeros(nC, dtype=np.int)
|
||||
in_langs = np.zeros(nC, dtype=np.int) # count languages with at least one positive example (per category)
|
||||
for (lang, ((Xtr, Ytr, IDtr), (Xte, Yte, IDte))) in self.multiling_dataset.items():
|
||||
if lang not in self.langs(): continue
|
||||
prev_train = np.sum(self.cat_view(Ytr), axis=0)
|
||||
prev_test = np.sum(self.cat_view(Yte), axis=0)
|
||||
accum_tr += prev_train
|
||||
accum_te += prev_test
|
||||
in_langs += (prev_train>0)*1
|
||||
print(lang+'-train', prev_train)
|
||||
print(lang+'-test', prev_test)
|
||||
print('all-train', accum_tr)
|
||||
print('all-test', accum_te)
|
||||
|
||||
return accum_tr, accum_te, in_langs
|
||||
|
||||
def set_labels(self, labels):
|
||||
self.labels = labels
|
||||
|
||||
def _mask_numbers(data):
|
||||
mask_moredigit = re.compile(r'\s[\+-]?\d{5,}([\.,]\d*)*\b')
|
||||
mask_4digit = re.compile(r'\s[\+-]?\d{4}([\.,]\d*)*\b')
|
||||
mask_3digit = re.compile(r'\s[\+-]?\d{3}([\.,]\d*)*\b')
|
||||
mask_2digit = re.compile(r'\s[\+-]?\d{2}([\.,]\d*)*\b')
|
||||
mask_1digit = re.compile(r'\s[\+-]?\d{1}([\.,]\d*)*\b')
|
||||
masked = []
|
||||
for text in tqdm(data, desc='masking numbers'):
|
||||
text = ' ' + text
|
||||
text = mask_moredigit.sub(' MoreDigitMask', text)
|
||||
text = mask_4digit.sub(' FourDigitMask', text)
|
||||
text = mask_3digit.sub(' ThreeDigitMask', text)
|
||||
text = mask_2digit.sub(' TwoDigitMask', text)
|
||||
text = mask_1digit.sub(' OneDigitMask', text)
|
||||
masked.append(text.replace('.','').replace(',','').strip())
|
||||
return masked
|
||||
|
||||
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
def get_active_labels(doclist):
|
||||
cat_list = set()
|
||||
for d in doclist:
|
||||
cat_list.update(d.categories)
|
||||
return list(cat_list)
|
||||
|
||||
def filter_by_categories(doclist, keep_categories):
|
||||
catset = frozenset(keep_categories)
|
||||
for d in doclist:
|
||||
d.categories = list(set(d.categories).intersection(catset))
|
||||
|
||||
def __years_to_str(years):
|
||||
if isinstance(years, list):
|
||||
if len(years) > 1:
|
||||
return str(years[0])+'-'+str(years[-1])
|
||||
return str(years[0])
|
||||
return str(years)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# Matrix builders
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
def build_independent_matrices(dataset_name, langs, training_docs, test_docs, label_names, wiki_docs=[], preprocess=True):
|
||||
"""
|
||||
Builds the document-by-term weighted matrices for each language. Representations are independent of each other,
|
||||
i.e., each language-specific matrix lies in a dedicate feature space.
|
||||
:param dataset_name: the name of the dataset (str)
|
||||
:param langs: list of languages (str)
|
||||
:param training_docs: map {lang:doc-list} where each doc is a tuple (text, categories, id)
|
||||
:param test_docs: map {lang:doc-list} where each doc is a tuple (text, categories, id)
|
||||
:param label_names: list of names of labels (str)
|
||||
:param wiki_docs: doc-list (optional), if specified, project all wiki docs in the feature spaces built for the languages
|
||||
:param preprocess: whether or not to apply language-specific text preprocessing (stopword removal and stemming)
|
||||
:return: a MultilingualDataset. If wiki_docs has been specified, a dictionary lW is also returned, which indexes
|
||||
by language the processed wikipedia documents in their respective language-specific feature spaces
|
||||
"""
|
||||
|
||||
mlb = MultiLabelBinarizer()
|
||||
mlb.fit([label_names])
|
||||
|
||||
lW = {}
|
||||
|
||||
multilingual_dataset = MultilingualDataset()
|
||||
multilingual_dataset.dataset_name = dataset_name
|
||||
multilingual_dataset.set_labels(mlb.classes_)
|
||||
for lang in langs:
|
||||
print("\nprocessing %d training, %d test, %d wiki for language <%s>" %
|
||||
(len(training_docs[lang]), len(test_docs[lang]), len(wiki_docs[lang]) if wiki_docs else 0, lang))
|
||||
|
||||
tr_data, tr_labels, IDtr = zip(*training_docs[lang])
|
||||
te_data, te_labels, IDte = zip(*test_docs[lang])
|
||||
|
||||
if preprocess:
|
||||
tfidf = TfidfVectorizer(strip_accents='unicode', min_df=3, sublinear_tf=True,
|
||||
tokenizer=NLTKStemTokenizer(lang, verbose=True),
|
||||
stop_words=stopwords.words(NLTK_LANGMAP[lang]))
|
||||
else:
|
||||
tfidf = TfidfVectorizer(strip_accents='unicode', min_df=3, sublinear_tf=True)
|
||||
|
||||
Xtr = tfidf.fit_transform(tr_data)
|
||||
Xte = tfidf.transform(te_data)
|
||||
if wiki_docs:
|
||||
lW[lang] = tfidf.transform(wiki_docs[lang])
|
||||
|
||||
Ytr = mlb.transform(tr_labels)
|
||||
Yte = mlb.transform(te_labels)
|
||||
|
||||
multilingual_dataset.add(lang, Xtr, Ytr, Xte, Yte, IDtr, IDte)
|
||||
|
||||
multilingual_dataset.show_dimensions()
|
||||
multilingual_dataset.show_category_prevalences()
|
||||
|
||||
if wiki_docs:
|
||||
return multilingual_dataset, lW
|
||||
else:
|
||||
return multilingual_dataset
|
||||
|
||||
|
||||
# creates a MultilingualDataset where matrices shares a single yuxtaposed feature space
|
||||
def build_juxtaposed_matrices(dataset_name, langs, training_docs, test_docs, label_names, preprocess=True):
|
||||
"""
|
||||
Builds the document-by-term weighted matrices for each language. Representations are not independent of each other,
|
||||
since all of them lie on the same yuxtaposed feature space.
|
||||
:param dataset_name: the name of the dataset (str)
|
||||
:param langs: list of languages (str)
|
||||
:param training_docs: map {lang:doc-list} where each doc is a tuple (text, categories, id)
|
||||
:param test_docs: map {lang:doc-list} where each doc is a tuple (text, categories, id)
|
||||
:param label_names: list of names of labels (str)
|
||||
:param preprocess: whether or not to apply language-specific text preprocessing (stopword removal and stemming)
|
||||
:return: a MultilingualDataset. If wiki_docs has been specified, a dictionary lW is also returned, which indexes
|
||||
by language the processed wikipedia documents in their respective language-specific feature spaces
|
||||
"""
|
||||
|
||||
multiling_dataset = MultilingualDataset()
|
||||
multiling_dataset.dataset_name = dataset_name
|
||||
|
||||
mlb = MultiLabelBinarizer()
|
||||
mlb.fit([label_names])
|
||||
|
||||
multiling_dataset.set_labels(mlb.classes_)
|
||||
|
||||
tr_data_stack = []
|
||||
for lang in langs:
|
||||
print("\nprocessing %d training and %d test for language <%s>" % (len(training_docs[lang]), len(test_docs[lang]), lang))
|
||||
tr_data, tr_labels, tr_ID = zip(*training_docs[lang])
|
||||
te_data, te_labels, te_ID = zip(*test_docs[lang])
|
||||
if preprocess:
|
||||
tr_data = preprocess_documents(tr_data, lang)
|
||||
te_data = preprocess_documents(te_data, lang)
|
||||
tr_data_stack.extend(tr_data)
|
||||
multiling_dataset.add(lang, tr_data, tr_labels, te_data, te_labels, tr_ID, te_ID)
|
||||
|
||||
tfidf = TfidfVectorizer(strip_accents='unicode', min_df=3, sublinear_tf=True)
|
||||
tfidf.fit(tr_data_stack)
|
||||
|
||||
for lang in langs:
|
||||
print("\nweighting documents for language <%s>" % (lang))
|
||||
(tr_data, tr_labels, tr_ID), (te_data, te_labels, te_ID) = multiling_dataset[lang]
|
||||
Xtr = tfidf.transform(tr_data)
|
||||
Xte = tfidf.transform(te_data)
|
||||
Ytr = mlb.transform(tr_labels)
|
||||
Yte = mlb.transform(te_labels)
|
||||
multiling_dataset.add(lang,Xtr,Ytr,Xte,Yte,tr_ID,te_ID)
|
||||
|
||||
multiling_dataset.show_dimensions()
|
||||
return multiling_dataset
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# Methods to recover the original documents from the MultilingualDataset's ids
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
"""
|
||||
This method has been added a posteriori, to create document embeddings using the polylingual embeddings of the recent
|
||||
article 'Word Translation without Parallel Data'; basically, it takes one of the splits and retrieves the RCV documents
|
||||
from the doc ids and then pickles an object (tr_docs, te_docs, label_names) in the outpath
|
||||
"""
|
||||
def retrieve_rcv_documents_from_dataset(datasetpath, rcv1_data_home, rcv2_data_home, outpath):
|
||||
|
||||
tr_ids, te_ids = MultilingualDataset.load_ids(datasetpath)
|
||||
assert tr_ids.keys() == te_ids.keys(), 'inconsistent keys tr vs te'
|
||||
langs = list(tr_ids.keys())
|
||||
|
||||
print('fetching the datasets')
|
||||
rcv1_documents, labels_rcv1 = fetch_RCV1(rcv1_data_home, split='train')
|
||||
rcv2_documents, labels_rcv2 = fetch_RCV2(rcv2_data_home, [l for l in langs if l != 'en'])
|
||||
|
||||
filter_by_categories(rcv1_documents, labels_rcv2)
|
||||
filter_by_categories(rcv2_documents, labels_rcv1)
|
||||
|
||||
label_names = get_active_labels(rcv1_documents + rcv2_documents)
|
||||
print('Active labels in RCV1/2 {}'.format(len(label_names)))
|
||||
|
||||
print('rcv1: {} train, {} test, {} categories'.format(len(rcv1_documents), 0, len(label_names)))
|
||||
print('rcv2: {} documents'.format(len(rcv2_documents)), Counter([doc.lang for doc in rcv2_documents]))
|
||||
|
||||
all_docs = rcv1_documents + rcv2_documents
|
||||
mlb = MultiLabelBinarizer()
|
||||
mlb.fit([label_names])
|
||||
|
||||
dataset = MultilingualDataset()
|
||||
for lang in langs:
|
||||
analyzer = CountVectorizer(strip_accents='unicode', min_df=3,
|
||||
stop_words=stopwords.words(NLTK_LANGMAP[lang])).build_analyzer()
|
||||
|
||||
Xtr,Ytr,IDtr = zip(*[(d.text,d.categories,d.id) for d in all_docs if d.lang == lang and d.id in tr_ids[lang]])
|
||||
Xte,Yte,IDte = zip(*[(d.text,d.categories,d.id) for d in all_docs if d.lang == lang and d.id in te_ids[lang]])
|
||||
Xtr = [' '.join(analyzer(d)) for d in Xtr]
|
||||
Xte = [' '.join(analyzer(d)) for d in Xte]
|
||||
Ytr = mlb.transform(Ytr)
|
||||
Yte = mlb.transform(Yte)
|
||||
dataset.add(lang, Xtr, Ytr, Xte, Yte, IDtr, IDte)
|
||||
|
||||
dataset.save(outpath)
|
||||
|
||||
"""
|
||||
Same thing but for JRC-Acquis
|
||||
"""
|
||||
def retrieve_jrc_documents_from_dataset(datasetpath, jrc_data_home, train_years, test_years, cat_policy, most_common_cat, outpath):
|
||||
|
||||
tr_ids, te_ids = MultilingualDataset.load_ids(datasetpath)
|
||||
assert tr_ids.keys() == te_ids.keys(), 'inconsistent keys tr vs te'
|
||||
langs = list(tr_ids.keys())
|
||||
|
||||
print('fetching the datasets')
|
||||
|
||||
cat_list = inspect_eurovoc(jrc_data_home, select=cat_policy)
|
||||
training_docs, label_names = fetch_jrcacquis(langs=langs, data_path=jrc_data_home, years=train_years,
|
||||
cat_filter=cat_list, cat_threshold=1, parallel=None,
|
||||
most_frequent=most_common_cat)
|
||||
test_docs, _ = fetch_jrcacquis(langs=langs, data_path=jrc_data_home, years=test_years, cat_filter=label_names,
|
||||
parallel='force')
|
||||
|
||||
def filter_by_id(doclist, ids):
|
||||
ids_set = frozenset(itertools.chain.from_iterable(ids.values()))
|
||||
return [x for x in doclist if (x.parallel_id+'__'+x.id) in ids_set]
|
||||
|
||||
training_docs = filter_by_id(training_docs, tr_ids)
|
||||
test_docs = filter_by_id(test_docs, te_ids)
|
||||
|
||||
print('jrc: {} train, {} test, {} categories'.format(len(training_docs), len(test_docs), len(label_names)))
|
||||
|
||||
mlb = MultiLabelBinarizer()
|
||||
mlb.fit([label_names])
|
||||
|
||||
dataset = MultilingualDataset()
|
||||
for lang in langs:
|
||||
analyzer = CountVectorizer(strip_accents='unicode', min_df=3,
|
||||
stop_words=stopwords.words(NLTK_LANGMAP[lang])).build_analyzer()
|
||||
|
||||
Xtr,Ytr,IDtr = zip(*[(d.text,d.categories,d.parallel_id+'__'+d.id) for d in training_docs if d.lang == lang])
|
||||
Xte,Yte,IDte = zip(*[(d.text,d.categories,d.parallel_id+'__'+d.id) for d in test_docs if d.lang == lang])
|
||||
Xtr = [' '.join(analyzer(d)) for d in Xtr]
|
||||
Xte = [' '.join(analyzer(d)) for d in Xte]
|
||||
Ytr = mlb.transform(Ytr)
|
||||
Yte = mlb.transform(Yte)
|
||||
dataset.add(lang, Xtr, Ytr, Xte, Yte, IDtr, IDte)
|
||||
|
||||
dataset.save(outpath)
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# Dataset Generators
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
def prepare_jrc_datasets(jrc_data_home, wiki_data_home, langs, train_years, test_years, cat_policy, most_common_cat=-1, max_wiki=5000, run=0):
|
||||
from data.reader.wikipedia_tools import fetch_wikipedia_multilingual, random_wiki_sample
|
||||
|
||||
|
||||
"""
|
||||
Prepare all datasets for JRC-Acquis. The datasets include the "feature-independent" version, the
|
||||
"feature-yuxtaposed" version, the monolingual version for the UpperBound, and the processed wikipedia matrices.
|
||||
In all cases, training documents are strictly non-parallel, and test documents are strictly parallel
|
||||
:param jrc_data_home: path to the raw JRC-Acquis documents (it will be downloaded if not found), and the path where
|
||||
all splits will be generated
|
||||
:param wiki_data_home: path to the wikipedia dump (see data/readers/wikipedia_tools.py)
|
||||
:param langs: the list of languages to consider (as defined in data/languages.py)
|
||||
:param train_years: a list of ints containing the years to be considered as training documents
|
||||
:param test_years: a list of ints containing the years to be considered as test documents
|
||||
:param cat_policy: a string indicating which category selection policy to apply. Valid policies are, e.g., "all"
|
||||
(select all categories), "broadest" (select only the broadest concepts in the taxonomy), or "leaves" (select the
|
||||
leaves concepts in the taxonomy). See inspect_eurovoc from data/reader/jrcacquis_reader.py for more details
|
||||
:param most_common_cat: the maximum number of most common categories to consider, or -1 to keep them all
|
||||
:param max_wiki: the maximum number of wikipedia documents to consider (default 5000)
|
||||
:param run: a numeric label naming the random split (useful to keep track of different runs)
|
||||
:return: None
|
||||
"""
|
||||
|
||||
name = 'JRCacquis'
|
||||
run = '_run' + str(run)
|
||||
config_name = 'jrc_nltk_' + __years_to_str(train_years) + \
|
||||
'vs' + __years_to_str(test_years) + \
|
||||
'_' + cat_policy + \
|
||||
('_top' + str(most_common_cat) if most_common_cat!=-1 else '') + \
|
||||
'_noparallel_processed'
|
||||
|
||||
indep_path = join(jrc_data_home, config_name + run + '.pickle')
|
||||
upper_path = join(jrc_data_home, config_name + run + '_upper.pickle')
|
||||
yuxta_path = join(jrc_data_home, config_name + run + '_yuxtaposed.pickle')
|
||||
wiki_path = join(jrc_data_home, config_name + run + '.wiki.pickle')
|
||||
wiki_docs_path = join(jrc_data_home, config_name + '.wiki.raw.pickle')
|
||||
|
||||
cat_list = inspect_eurovoc(jrc_data_home, select=cat_policy)
|
||||
training_docs, label_names = fetch_jrcacquis(langs=langs, data_path=jrc_data_home, years=train_years,
|
||||
cat_filter=cat_list, cat_threshold=1, parallel=None,
|
||||
most_frequent=most_common_cat)
|
||||
test_docs, _ = fetch_jrcacquis(langs=langs, data_path=jrc_data_home, years=test_years, cat_filter=label_names,
|
||||
parallel='force')
|
||||
|
||||
print('Generating feature-independent dataset...')
|
||||
training_docs_no_parallel = random_sampling_avoiding_parallel(training_docs)
|
||||
|
||||
def _group_by_lang(doc_list, langs):
|
||||
return {lang: [(d.text, d.categories, d.parallel_id + '__' + d.id) for d in doc_list if d.lang == lang]
|
||||
for lang in langs}
|
||||
|
||||
training_docs = _group_by_lang(training_docs, langs)
|
||||
training_docs_no_parallel = _group_by_lang(training_docs_no_parallel, langs)
|
||||
test_docs = _group_by_lang(test_docs, langs)
|
||||
if not exists(indep_path):
|
||||
wiki_docs=None
|
||||
if max_wiki>0:
|
||||
if not exists(wiki_docs_path):
|
||||
wiki_docs = fetch_wikipedia_multilingual(wiki_data_home, langs, min_words=50, deletions=False)
|
||||
wiki_docs = random_wiki_sample(wiki_docs, max_wiki)
|
||||
pickle.dump(wiki_docs, open(wiki_docs_path, 'wb'), pickle.HIGHEST_PROTOCOL)
|
||||
else:
|
||||
wiki_docs = pickle.load(open(wiki_docs_path, 'rb'))
|
||||
wiki_docs = random_wiki_sample(wiki_docs, max_wiki)
|
||||
|
||||
if wiki_docs:
|
||||
lang_data, wiki_docs = build_independent_matrices(name, langs, training_docs_no_parallel, test_docs, label_names, wiki_docs)
|
||||
pickle.dump(wiki_docs, open(wiki_path, 'wb'), pickle.HIGHEST_PROTOCOL)
|
||||
else:
|
||||
lang_data = build_independent_matrices(name, langs, training_docs_no_parallel, test_docs, label_names)
|
||||
|
||||
lang_data.save(indep_path)
|
||||
|
||||
print('Generating upper-bound (English-only) dataset...')
|
||||
if not exists(upper_path):
|
||||
training_docs_eng_only = {'en':training_docs['en']}
|
||||
test_docs_eng_only = {'en':test_docs['en']}
|
||||
build_independent_matrices(name, ['en'], training_docs_eng_only, test_docs_eng_only, label_names).save(upper_path)
|
||||
|
||||
print('Generating yuxtaposed dataset...')
|
||||
if not exists(yuxta_path):
|
||||
build_juxtaposed_matrices(name, langs, training_docs_no_parallel, test_docs, label_names).save(yuxta_path)
|
||||
|
||||
|
||||
def prepare_rcv_datasets(outpath, rcv1_data_home, rcv2_data_home, wiki_data_home, langs,
|
||||
train_for_lang=1000, test_for_lang=1000, max_wiki=5000, preprocess=True, run=0):
|
||||
from data.reader.wikipedia_tools import fetch_wikipedia_multilingual, random_wiki_sample
|
||||
"""
|
||||
Prepare all datasets for RCV1/RCV2. The datasets include the "feature-independent" version, the
|
||||
"feature-yuxtaposed" version, the monolingual version for the UpperBound, and the processed wikipedia matrices.
|
||||
|
||||
:param outpath: path where all splits will be dumped
|
||||
:param rcv1_data_home: path to the RCV1-v2 dataset (English only)
|
||||
:param rcv2_data_home: path to the RCV2 dataset (all languages other than English)
|
||||
:param wiki_data_home: path to the wikipedia dump (see data/readers/wikipedia_tools.py)
|
||||
:param langs: the list of languages to consider (as defined in data/languages.py)
|
||||
:param train_for_lang: maximum number of training documents per language
|
||||
:param test_for_lang: maximum number of test documents per language
|
||||
:param max_wiki: the maximum number of wikipedia documents to consider (default 5000)
|
||||
:param preprocess: whether or not to apply language-specific preprocessing (stopwords removal and stemming)
|
||||
:param run: a numeric label naming the random split (useful to keep track of different runs)
|
||||
:return: None
|
||||
"""
|
||||
|
||||
assert 'en' in langs, 'English is not in requested languages, but is needed for some datasets'
|
||||
assert len(langs)>1, 'the multilingual dataset cannot be built with only one dataset'
|
||||
assert not preprocess or set(langs).issubset(set(RCV2_LANGS_WITH_NLTK_STEMMING+['en'])), \
|
||||
"languages not in RCV1-v2/RCV2 scope or not in valid for NLTK's processing"
|
||||
|
||||
name = 'RCV1/2'
|
||||
run = '_run' + str(run)
|
||||
config_name = 'rcv1-2_nltk_trByLang'+str(train_for_lang)+'_teByLang'+str(test_for_lang)+\
|
||||
('_processed' if preprocess else '_raw')
|
||||
|
||||
indep_path = join(outpath, config_name + run + '.pickle')
|
||||
upper_path = join(outpath, config_name + run +'_upper.pickle')
|
||||
yuxta_path = join(outpath, config_name + run +'_yuxtaposed.pickle')
|
||||
wiki_path = join(outpath, config_name + run + '.wiki.pickle')
|
||||
wiki_docs_path = join(outpath, config_name + '.wiki.raw.pickle')
|
||||
|
||||
print('fetching the datasets')
|
||||
rcv1_documents, labels_rcv1 = fetch_RCV1(rcv1_data_home, split='train')
|
||||
rcv2_documents, labels_rcv2 = fetch_RCV2(rcv2_data_home, [l for l in langs if l!='en'])
|
||||
filter_by_categories(rcv1_documents, labels_rcv2)
|
||||
filter_by_categories(rcv2_documents, labels_rcv1)
|
||||
|
||||
label_names = get_active_labels(rcv1_documents+rcv2_documents)
|
||||
print('Active labels in RCV1/2 {}'.format(len(label_names)))
|
||||
|
||||
print('rcv1: {} train, {} test, {} categories'.format(len(rcv1_documents), 0, len(label_names)))
|
||||
print('rcv2: {} documents'.format(len(rcv2_documents)), Counter([doc.lang for doc in rcv2_documents]))
|
||||
|
||||
lang_docs = {lang: [d for d in rcv1_documents + rcv2_documents if d.lang == lang] for lang in langs}
|
||||
|
||||
# for the upper bound there are no parallel versions, so for the English case, we take as many documents as there
|
||||
# would be in the multilingual case -- then we will extract from them only train_for_lang for the other cases
|
||||
print('Generating upper-bound (English-only) dataset...')
|
||||
train, test = train_test_split(lang_docs['en'], train_size=train_for_lang*len(langs), test_size=test_for_lang, shuffle=True)
|
||||
train_lang_doc_map = {'en':[(d.text, d.categories, d.id) for d in train]}
|
||||
test_lang_doc_map = {'en':[(d.text, d.categories, d.id) for d in test]}
|
||||
build_independent_matrices(name, ['en'], train_lang_doc_map, test_lang_doc_map, label_names).save(upper_path)
|
||||
|
||||
train_lang_doc_map['en'] = train_lang_doc_map['en'][:train_for_lang]
|
||||
for lang in langs:
|
||||
if lang=='en': continue # already split
|
||||
test_take = min(test_for_lang, len(lang_docs[lang])-train_for_lang)
|
||||
train, test = train_test_split(lang_docs[lang], train_size=train_for_lang, test_size=test_take, shuffle=True)
|
||||
train_lang_doc_map[lang] = [(d.text, d.categories, d.id) for d in train]
|
||||
test_lang_doc_map[lang] = [(d.text, d.categories, d.id) for d in test]
|
||||
|
||||
print('Generating feature-independent dataset...')
|
||||
wiki_docs=None
|
||||
if max_wiki>0:
|
||||
if not exists(wiki_docs_path):
|
||||
wiki_docs = fetch_wikipedia_multilingual(wiki_data_home, langs, min_words=50, deletions=False)
|
||||
wiki_docs = random_wiki_sample(wiki_docs, max_wiki)
|
||||
pickle.dump(wiki_docs, open(wiki_docs_path, 'wb'), pickle.HIGHEST_PROTOCOL)
|
||||
else:
|
||||
wiki_docs = pickle.load(open(wiki_docs_path, 'rb'))
|
||||
wiki_docs = random_wiki_sample(wiki_docs, max_wiki)
|
||||
|
||||
if wiki_docs:
|
||||
lang_data, wiki_docs_matrix = build_independent_matrices(name, langs, train_lang_doc_map, test_lang_doc_map, label_names, wiki_docs, preprocess)
|
||||
pickle.dump(wiki_docs_matrix, open(wiki_path, 'wb'), pickle.HIGHEST_PROTOCOL)
|
||||
else:
|
||||
lang_data = build_independent_matrices(name, langs, train_lang_doc_map, test_lang_doc_map, label_names, wiki_docs, preprocess)
|
||||
|
||||
lang_data.save(indep_path)
|
||||
|
||||
print('Generating yuxtaposed dataset...')
|
||||
build_juxtaposed_matrices(name, langs, train_lang_doc_map, test_lang_doc_map, label_names, preprocess).save(yuxta_path)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
# Methods to generate full RCV and JRC datasets
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
def full_rcv_(rcv1_data_home, rcv2_data_home, outpath, langs):
|
||||
|
||||
|
||||
print('fetching the datasets')
|
||||
rcv1_train_documents, labels_rcv1 = fetch_RCV1(rcv1_data_home, split='train')
|
||||
rcv1_test_documents, labels_rcv1_test = fetch_RCV1(rcv1_data_home, split='test')
|
||||
rcv2_documents, labels_rcv2 = fetch_RCV2(rcv2_data_home, [l for l in langs if l != 'en'])
|
||||
|
||||
filter_by_categories(rcv1_train_documents, labels_rcv2)
|
||||
filter_by_categories(rcv1_test_documents, labels_rcv2)
|
||||
filter_by_categories(rcv2_documents, labels_rcv1)
|
||||
|
||||
label_names = get_active_labels(rcv1_train_documents + rcv2_documents)
|
||||
print('Active labels in RCV1/2 {}'.format(len(label_names)))
|
||||
|
||||
print('rcv1: {} train, {} test, {} categories'.format(len(rcv1_train_documents), len(rcv1_test_documents), len(label_names)))
|
||||
print('rcv2: {} documents'.format(len(rcv2_documents)), Counter([doc.lang for doc in rcv2_documents]))
|
||||
|
||||
mlb = MultiLabelBinarizer()
|
||||
mlb.fit([label_names])
|
||||
|
||||
all_docs = rcv1_train_documents + rcv1_test_documents + rcv2_documents
|
||||
lang_docs = {lang: [d for d in all_docs if d.lang == lang] for lang in langs}
|
||||
|
||||
def get_ids(doclist):
|
||||
return frozenset([d.id for d in doclist])
|
||||
|
||||
tr_ids = {'en': get_ids(rcv1_train_documents)}
|
||||
te_ids = {'en': get_ids(rcv1_test_documents)}
|
||||
for lang in langs:
|
||||
if lang == 'en': continue
|
||||
tr_ids[lang], te_ids[lang] = train_test_split([d.id for d in lang_docs[lang]], test_size=.3)
|
||||
|
||||
dataset = MultilingualDataset()
|
||||
dataset.dataset_name = 'RCV1/2-full'
|
||||
for lang in langs:
|
||||
print(f'processing {lang} with {len(tr_ids[lang])} training documents and {len(te_ids[lang])} documents')
|
||||
analyzer = CountVectorizer(
|
||||
strip_accents='unicode', min_df=3, stop_words=stopwords.words(NLTK_LANGMAP[lang])
|
||||
).build_analyzer()
|
||||
|
||||
Xtr,Ytr,IDtr = zip(*[(d.text,d.categories,d.id) for d in lang_docs[lang] if d.id in tr_ids[lang]])
|
||||
Xte,Yte,IDte = zip(*[(d.text,d.categories,d.id) for d in lang_docs[lang] if d.id in te_ids[lang]])
|
||||
Xtr = [' '.join(analyzer(d)) for d in Xtr]
|
||||
Xte = [' '.join(analyzer(d)) for d in Xte]
|
||||
Ytr = mlb.transform(Ytr)
|
||||
Yte = mlb.transform(Yte)
|
||||
dataset.add(lang, _mask_numbers(Xtr), Ytr, _mask_numbers(Xte), Yte, IDtr, IDte)
|
||||
|
||||
dataset.save(outpath)
|
||||
|
||||
|
||||
def full_jrc_(jrc_data_home, langs, train_years, test_years, outpath, cat_policy='all', most_common_cat=300):
|
||||
|
||||
print('fetching the datasets')
|
||||
cat_list = inspect_eurovoc(jrc_data_home, select=cat_policy)
|
||||
training_docs, label_names = fetch_jrcacquis(
|
||||
langs=langs, data_path=jrc_data_home, years=train_years, cat_filter=cat_list, cat_threshold=1, parallel=None, most_frequent=most_common_cat
|
||||
)
|
||||
test_docs, _ = fetch_jrcacquis(
|
||||
langs=langs, data_path=jrc_data_home, years=test_years, cat_filter=label_names, parallel='force'
|
||||
)
|
||||
|
||||
def _group_by_lang(doc_list, langs):
|
||||
return {lang: [d for d in doc_list if d.lang == lang] for lang in langs}
|
||||
|
||||
training_docs = _group_by_lang(training_docs, langs)
|
||||
test_docs = _group_by_lang(test_docs, langs)
|
||||
|
||||
mlb = MultiLabelBinarizer()
|
||||
mlb.fit([label_names])
|
||||
|
||||
dataset = MultilingualDataset()
|
||||
data.dataset_name = 'JRC-Acquis-full'
|
||||
for lang in langs:
|
||||
analyzer = CountVectorizer(
|
||||
strip_accents='unicode', min_df=3, stop_words=stopwords.words(NLTK_LANGMAP[lang])
|
||||
).build_analyzer()
|
||||
|
||||
Xtr, Ytr, IDtr = zip(*[(d.text, d.categories, d.parallel_id + '__' + d.id) for d in training_docs[lang] if d.lang == lang])
|
||||
Xte, Yte, IDte = zip(*[(d.text, d.categories, d.parallel_id + '__' + d.id) for d in test_docs[lang] if d.lang == lang])
|
||||
Xtr = [' '.join(analyzer(d)) for d in Xtr]
|
||||
Xte = [' '.join(analyzer(d)) for d in Xte]
|
||||
Ytr = mlb.transform(Ytr)
|
||||
Yte = mlb.transform(Yte)
|
||||
dataset.add(lang, _mask_numbers(Xtr), Ytr, _mask_numbers(Xte), Yte, IDtr, IDte)
|
||||
|
||||
dataset.save(outpath)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
# MAIN BUILDER
|
||||
#-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
if __name__=='__main__':
|
||||
import sys
|
||||
RCV1_PATH = '../Datasets/RCV1-v2/unprocessed_corpus'
|
||||
RCV2_PATH = '../Datasets/RCV2'
|
||||
JRC_DATAPATH = "../Datasets/JRC_Acquis_v3"
|
||||
full_rcv_(RCV1_PATH, RCV2_PATH, outpath='../rcv2/rcv1-2_doclist_full_processed.pickle', langs=RCV2_LANGS_WITH_NLTK_STEMMING + ['en'])
|
||||
# full_jrc_(JRC_DATAPATH, lang_set['JRC_NLTK'], train_years=list(range(1958, 2006)), test_years=[2006], outpath='../jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_full_processed.pickle', cat_policy='all', most_common_cat=300)
|
||||
sys.exit(0)
|
||||
|
||||
# datasetpath = '../jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_full_processed.pickle' # '../rcv2/rcv1-2_doclist_full_processed.pickle'
|
||||
# data = MultilingualDataset.load(datasetpath)
|
||||
# data.dataset_name='JRC-Acquis-full'#'RCV1/2-full'
|
||||
# for lang in RCV2_LANGS_WITH_NLTK_STEMMING + ['en']:
|
||||
# (Xtr, ytr, idtr), (Xte, yte, idte) = data.multiling_dataset[lang]
|
||||
# data.multiling_dataset[lang] = ((_mask_numbers(Xtr), ytr, idtr), (_mask_numbers(Xte), yte, idte))
|
||||
# data.save('../jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_full_processed.pickle')#'../rcv2/rcv1-2_doclist_full_processed_2.pickle')
|
||||
# sys.exit(0)
|
||||
|
||||
assert len(sys.argv) == 5, "wrong number of arguments; required: " \
|
||||
"<JRC_PATH> <RCV1_PATH> <RCV2_PATH> <WIKI_PATH> "
|
||||
|
||||
JRC_DATAPATH = sys.argv[1] # "../Datasets/JRC_Acquis_v3"
|
||||
RCV1_PATH = sys.argv[2] #'../Datasets/RCV1-v2/unprocessed_corpus'
|
||||
RCV2_PATH = sys.argv[3] #'../Datasets/RCV2'
|
||||
WIKI_DATAPATH = sys.argv[4] #"../Datasets/Wikipedia/multilingual_docs_JRC_NLTK"
|
||||
|
||||
langs = lang_set['JRC_NLTK']
|
||||
max_wiki = 5000
|
||||
|
||||
for run in range(0,10):
|
||||
print('Building JRC-Acquis datasets run', run)
|
||||
prepare_jrc_datasets(JRC_DATAPATH, WIKI_DATAPATH, langs,
|
||||
train_years=list(range(1958, 2006)), test_years=[2006], max_wiki=max_wiki,
|
||||
cat_policy='all', most_common_cat=300, run=run)
|
||||
|
||||
print('Building RCV1-v2/2 datasets run', run)
|
||||
prepare_rcv_datasets(RCV2_PATH, RCV1_PATH, RCV2_PATH, WIKI_DATAPATH, RCV2_LANGS_WITH_NLTK_STEMMING + ['en'],
|
||||
train_for_lang=1000, test_for_lang=1000, max_wiki=max_wiki, run=run)
|
||||
|
||||
# uncomment this code if you want to retrieve the original documents to generate the data splits for PLE
|
||||
# (make sure you have not modified the above parameters, or adapt the following paths accordingly...)
|
||||
# datasetpath = join(RCV2_PATH,'rcv1-2_nltk_trByLang1000_teByLang1000_processed_run{}.pickle'.format(run))
|
||||
# outpath = datasetpath.replace('_nltk_','_doclist_')
|
||||
# retrieve_rcv_documents_from_dataset(datasetpath, RCV1_PATH, RCV2_PATH, outpath)
|
||||
|
||||
# datasetpath = join(JRC_DATAPATH, 'jrc_nltk_1958-2005vs2006_all_top300_noparallel_processed_run{}.pickle'.format(run))
|
||||
# outpath = datasetpath.replace('_nltk_', '_doclist_')
|
||||
# retrieve_jrc_documents_from_dataset(datasetpath, JRC_DATAPATH, train_years=list(range(1958, 2006)), test_years=[2006], cat_policy='all', most_common_cat=300, outpath=outpath)
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
import os
|
||||
from torchtext.vocab import Vectors
|
||||
import torch
|
||||
from abc import ABC, abstractmethod
|
||||
from util.SIF_embed import *
|
||||
|
||||
|
||||
class PretrainedEmbeddings(ABC):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@abstractmethod
|
||||
def vocabulary(self): pass
|
||||
|
||||
@abstractmethod
|
||||
def dim(self): pass
|
||||
|
||||
@classmethod
|
||||
def reindex(cls, words, word2index):
|
||||
if isinstance(words, dict):
|
||||
words = list(zip(*sorted(words.items(), key=lambda x: x[1])))[0]
|
||||
|
||||
source_idx, target_idx = [], []
|
||||
for i, word in enumerate(words):
|
||||
if word not in word2index: continue
|
||||
j = word2index[word]
|
||||
source_idx.append(i)
|
||||
target_idx.append(j)
|
||||
source_idx = np.asarray(source_idx)
|
||||
target_idx = np.asarray(target_idx)
|
||||
return source_idx, target_idx
|
||||
|
||||
|
||||
class FastTextWikiNews(Vectors):
|
||||
|
||||
url_base = 'Cant auto-download MUSE embeddings'
|
||||
path = '../embeddings/wiki.multi.{}.vec'
|
||||
_name = '/wiki.multi.{}.vec'
|
||||
|
||||
def __init__(self, cache, language="en", **kwargs):
|
||||
url = self.url_base.format(language)
|
||||
name = cache + self._name.format(language)
|
||||
super(FastTextWikiNews, self).__init__(name, cache=cache, url=url, **kwargs)
|
||||
|
||||
|
||||
class FastTextMUSE(PretrainedEmbeddings):
|
||||
def __init__(self, path, lang, limit=None):
|
||||
super().__init__()
|
||||
assert os.path.exists(path), print(f'pre-trained vectors not found in {path}')
|
||||
self.embed = FastTextWikiNews(path, lang, max_vectors=limit)
|
||||
|
||||
def vocabulary(self):
|
||||
return set(self.embed.stoi.keys())
|
||||
|
||||
def dim(self):
|
||||
return self.embed.dim
|
||||
|
||||
def extract(self, words):
|
||||
source_idx, target_idx = PretrainedEmbeddings.reindex(words, self.embed.stoi)
|
||||
extraction = torch.zeros((len(words), self.dim()))
|
||||
extraction[source_idx] = self.embed.vectors[target_idx]
|
||||
return extraction
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
from abc import ABC, abstractmethod
|
||||
import torch, torchtext
|
||||
# import gensim
|
||||
# import os
|
||||
import numpy as np
|
||||
|
||||
|
||||
# class KeyedVectors:
|
||||
#
|
||||
# def __init__(self, word2index, weights):
|
||||
# assert len(word2index)==weights.shape[0], 'wrong number of dimensions'
|
||||
# index2word = {i:w for w,i in word2index.items()}
|
||||
# assert len([i for i in range(len(index2word)) if i not in index2word])==0, 'gaps in indexing not allowed'
|
||||
# self.word2index = word2index
|
||||
# self.index2word = index2word
|
||||
# self.weights = weights
|
||||
#
|
||||
# def extract(self, words):
|
||||
# dim = self.weights.shape[1]
|
||||
# v_size = len(words)
|
||||
#
|
||||
# source_idx, target_idx = [], []
|
||||
# for i,word in enumerate(words):
|
||||
# if word not in self.word2index: continue
|
||||
# j = self.word2index[word]
|
||||
# source_idx.append(i)
|
||||
# target_idx.append(j)
|
||||
#
|
||||
# extraction = np.zeros((v_size, dim))
|
||||
# extraction[np.asarray(source_idx)] = self.weights[np.asarray(target_idx)]
|
||||
#
|
||||
# return extraction
|
||||
|
||||
|
||||
# class PretrainedEmbeddings(ABC):
|
||||
#
|
||||
# def __init__(self):
|
||||
# super().__init__()
|
||||
#
|
||||
# @abstractmethod
|
||||
# def vocabulary(self): pass
|
||||
#
|
||||
# @abstractmethod
|
||||
# def dim(self): pass
|
||||
#
|
||||
# @classmethod
|
||||
# def reindex(cls, words, word2index):
|
||||
# source_idx, target_idx = [], []
|
||||
# for i, word in enumerate(words):
|
||||
# if word not in word2index: continue
|
||||
# j = word2index[word]
|
||||
# source_idx.append(i)
|
||||
# target_idx.append(j)
|
||||
# source_idx = np.asarray(source_idx)
|
||||
# target_idx = np.asarray(target_idx)
|
||||
# return source_idx, target_idx
|
||||
|
||||
|
||||
# class GloVe(PretrainedEmbeddings):
|
||||
#
|
||||
# def __init__(self, setname='840B', path='./vectors_cache', max_vectors=None):
|
||||
# super().__init__()
|
||||
# print(f'Loading GloVe pretrained vectors from torchtext')
|
||||
# self.embed = torchtext.vocab.GloVe(setname, cache=path, max_vectors=max_vectors)
|
||||
# print('Done')
|
||||
#
|
||||
# def vocabulary(self):
|
||||
# return set(self.embed.stoi.keys())
|
||||
#
|
||||
# def dim(self):
|
||||
# return self.embed.dim
|
||||
#
|
||||
# def extract(self, words):
|
||||
# source_idx, target_idx = PretrainedEmbeddings.reindex(words, self.embed.stoi)
|
||||
# extraction = torch.zeros((len(words), self.dim()))
|
||||
# extraction[source_idx] = self.embed.vectors[target_idx]
|
||||
# return extraction
|
||||
|
||||
|
||||
# class Word2Vec(PretrainedEmbeddings):
|
||||
#
|
||||
# def __init__(self, path, limit=None):
|
||||
# super().__init__()
|
||||
# print(f'Loading word2vec pretrained vectors from {path}')
|
||||
# assert os.path.exists(path), print(f'pre-trained keyed vectors not found in {path}')
|
||||
# self.embed = gensim.models.KeyedVectors.load_word2vec_format(path, binary=True, limit=limit)
|
||||
# self.word2index={w:i for i,w in enumerate(self.embed.index2word)}
|
||||
# print('Done')
|
||||
#
|
||||
# def vocabulary(self):
|
||||
# return set(self.word2index.keys())
|
||||
#
|
||||
# def dim(self):
|
||||
# return self.embed.vector_size
|
||||
#
|
||||
# def extract(self, words):
|
||||
# source_idx, target_idx = PretrainedEmbeddings.reindex(words, self.word2index)
|
||||
# extraction = np.zeros((len(words), self.dim()))
|
||||
# extraction[source_idx] = self.embed.vectors[target_idx]
|
||||
# extraction = torch.from_numpy(extraction).float()
|
||||
# return extraction
|
||||
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
from data.tsr_function__ import get_supervised_matrix, get_tsr_matrix, information_gain, chi_square
|
||||
import numpy as np
|
||||
|
||||
|
||||
def zscores(x, axis=0): #scipy.stats.zscores does not avoid division by 0, which can indeed occur
|
||||
std = np.clip(np.std(x, ddof=1, axis=axis), 1e-5, None)
|
||||
mean = np.mean(x, axis=axis)
|
||||
return (x - mean) / std
|
||||
|
||||
|
||||
def supervised_embeddings_tfidf(X,Y):
|
||||
tfidf_norm = X.sum(axis=0)
|
||||
tfidf_norm[tfidf_norm==0] = 1
|
||||
F = (X.T).dot(Y) / tfidf_norm.T
|
||||
return F
|
||||
|
||||
|
||||
def supervised_embeddings_ppmi(X,Y):
|
||||
Xbin = X>0
|
||||
D = X.shape[0]
|
||||
Pxy = (Xbin.T).dot(Y)/D
|
||||
Px = Xbin.sum(axis=0)/D
|
||||
Py = Y.sum(axis=0)/D
|
||||
F = np.asarray(Pxy/(Px.T*Py))
|
||||
F = np.maximum(F, 1.0)
|
||||
F = np.log(F)
|
||||
return F
|
||||
|
||||
|
||||
def supervised_embeddings_tsr(X,Y, tsr_function=information_gain, max_documents=25000):
|
||||
D = X.shape[0]
|
||||
if D>max_documents:
|
||||
print(f'sampling {max_documents}')
|
||||
random_sample = np.random.permutation(D)[:max_documents]
|
||||
X = X[random_sample]
|
||||
Y = Y[random_sample]
|
||||
cell_matrix = get_supervised_matrix(X, Y)
|
||||
F = get_tsr_matrix(cell_matrix, tsr_score_funtion=tsr_function).T
|
||||
return F
|
||||
|
||||
|
||||
def get_supervised_embeddings(X, Y, reduction, max_label_space=300, voc=None, lang='None', binary_structural_problems=-1, method='dotn', dozscore=True):
|
||||
if max_label_space != 0:
|
||||
print('computing supervised embeddings...')
|
||||
nC = Y.shape[1]
|
||||
|
||||
if method=='ppmi':
|
||||
F = supervised_embeddings_ppmi(X, Y)
|
||||
elif method == 'dotn':
|
||||
F = supervised_embeddings_tfidf(X, Y)
|
||||
elif method == 'ig':
|
||||
F = supervised_embeddings_tsr(X, Y, information_gain)
|
||||
elif method == 'chi2':
|
||||
F = supervised_embeddings_tsr(X, Y, chi_square)
|
||||
|
||||
if dozscore:
|
||||
F = zscores(F, axis=0)
|
||||
|
||||
# Dumping F-matrix for further studies
|
||||
dump_it = False
|
||||
if dump_it:
|
||||
with open(f'/home/andreapdr/funneling_pdr/src/dumps/WCE_{lang}.tsv', 'w') as outfile:
|
||||
np.savetxt(outfile, F, delimiter='\t')
|
||||
with open(f'/home/andreapdr/funneling_pdr/src/dumps/dict_WCE_{lang}.tsv', 'w') as outfile:
|
||||
for token in voc.keys():
|
||||
outfile.write(token+'\n')
|
||||
|
||||
return F
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
dataset_path=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_noparallel_processed_run
|
||||
logfile=../log/log10run_dl_jrc.csv
|
||||
|
||||
runs='0 1 2 3 4 5 6 7 8 9'
|
||||
for run in $runs
|
||||
do
|
||||
dataset=$dataset_path$run.pickle
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --posteriors --tunable --plotmode --test-each 20
|
||||
done
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
dataset_path=/home/moreo/CLESA/rcv2/rcv1-2_doclist_trByLang1000_teByLang1000_processed_run
|
||||
logfile=../log/log10run_dl_rcv.csv
|
||||
|
||||
runs='0 1 2 3 4 5 6 7 8 9'
|
||||
for run in $runs
|
||||
do
|
||||
dataset=$dataset_path$run.pickle
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --posteriors --tunable --plotmode --test-each 20
|
||||
done
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
dataset=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_noparallel_processed_run0.pickle
|
||||
logfile=./results/10run_jrc_final_results.csv
|
||||
|
||||
runs='0 1 2 3 4 5 6 7 8 9'
|
||||
for run in $runs
|
||||
do
|
||||
dataset=$dataset_path$run.pickle
|
||||
python main_multimodal_cls.py $dataset -o $logfile -P -z -c --l2
|
||||
python main_multimodal_cls.py $dataset -o $logfile -S -z -c --l2
|
||||
python main_multimodal_cls.py $dataset -o $logfile -U -z -c --l2
|
||||
|
||||
done
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
dataset_path=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_noparallel_processed_run
|
||||
logfile=./results/funnelling_10run_jrc_CIKM.csv
|
||||
|
||||
runs='6 7 8 9' #0 1 2 3 4 5
|
||||
for run in $runs
|
||||
do
|
||||
dataset=$dataset_path$run.pickle
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -S -c -r -z --l2 --allprob # last combination for CIKM 3 Pr(views) concatenated (done up to run5)
|
||||
python main_multimodal_cls.py $dataset -o $logfile -P -U -S -c -r -z --l2 --allprob # last combination for CIKM 3 views concatenated
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -S -c -r -a -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -c -r -a -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -P -S -c -r -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -c -r -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -c -P -U -r -z --l2
|
||||
#python main_gFun.py $dataset -o $logfile -c -P -U -S -r -z --l2
|
||||
done
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
dataset_path=/home/moreo/CLESA/rcv2/rcv1-2_doclist_trByLang1000_teByLang1000_processed_run
|
||||
logfile=./results/10run_rcv_final_results.csv
|
||||
|
||||
runs='0 1 2 3 4 5 6 7 8 9'
|
||||
|
||||
for run in $runs
|
||||
do
|
||||
dataset=$dataset_path$run.pickle
|
||||
python main_multimodal_cls.py $dataset -o $logfile -P -z -c --l2
|
||||
python main_multimodal_cls.py $dataset -o $logfile -S -z -c --l2
|
||||
python main_multimodal_cls.py $dataset -o $logfile -U -z -c --l2
|
||||
|
||||
done
|
||||
|
||||
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
dataset_path=/home/moreo/CLESA/rcv2/rcv1-2_doclist_trByLang1000_teByLang1000_processed_run
|
||||
logfile=./results/funnelling_10run_rcv_CIKM_allprob_concatenated.csv
|
||||
|
||||
runs='0 1 2 3 4 5 6 7 8 9'
|
||||
for run in $runs
|
||||
do
|
||||
dataset=$dataset_path$run.pickle
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -S -c -r -z --l2 --allprob # last combination for CIKM 3 Pr(views) concatenated
|
||||
python main_multimodal_cls.py $dataset -o $logfile -P -U -S -c -r -z --l2 --allprob # last combination for CIKM 3 views concatenated
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -c -r -a -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -S -c -r -a -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -P -S -c -r -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -c -r -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -c -P -U -r -z --l2
|
||||
#python main_gFun.py $dataset -o $logfile -c -P -U -S -r -z --l2
|
||||
done
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
dataset_path=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_noparallel_processed_run#
|
||||
|
||||
runs='1 2 3 4 5 6 7 8 9'
|
||||
for run in $runs
|
||||
do
|
||||
dataset=$dataset_path$run.pickle
|
||||
modelpath=/home/andreapdr/funneling_pdr/hug_checkpoint/mBERT-jrc_run$runs
|
||||
python main_mbert_extractor.py --dataset $dataset --modelpath $modelpath
|
||||
done
|
||||
|
||||
dataset=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_full_processed.pickle
|
||||
python main_mbert_extractor.py --dataset $dataset --modelpath $modelpath
|
||||
|
|
@ -1,329 +0,0 @@
|
|||
import argparse
|
||||
import torch.nn as nn
|
||||
from torch.optim.lr_scheduler import StepLR
|
||||
from dataset_builder import MultilingualDataset
|
||||
from learning.transformers import load_muse_embeddings
|
||||
from models.lstm_class import RNNMultilingualClassifier
|
||||
from util.csv_log import CSVLog
|
||||
from util.early_stop import EarlyStopping
|
||||
from util.common import *
|
||||
from util.file import create_if_not_exist
|
||||
from time import time
|
||||
from tqdm import tqdm
|
||||
from util.evaluation import evaluate
|
||||
from util.file import get_file_name
|
||||
# import pickle
|
||||
|
||||
allowed_nets = {'rnn'}
|
||||
|
||||
# instantiates the net, initializes the model parameters, and sets embeddings trainable if requested
|
||||
def init_Net(nC, multilingual_index, xavier_uniform=True):
|
||||
net=opt.net
|
||||
assert net in allowed_nets, f'{net} not supported, valid ones are={allowed_nets}'
|
||||
|
||||
# instantiate the required net
|
||||
if net=='rnn':
|
||||
only_post = opt.posteriors and (not opt.pretrained) and (not opt.supervised)
|
||||
if only_post:
|
||||
print('working on ONLY POST mode')
|
||||
model = RNNMultilingualClassifier(
|
||||
output_size=nC,
|
||||
hidden_size=opt.hidden,
|
||||
lvocab_size=multilingual_index.l_vocabsize(),
|
||||
learnable_length=opt.learnable,
|
||||
lpretrained=multilingual_index.l_embeddings(),
|
||||
drop_embedding_range=multilingual_index.sup_range,
|
||||
drop_embedding_prop=opt.sup_drop,
|
||||
post_probabilities=opt.posteriors,
|
||||
only_post=only_post,
|
||||
bert_embeddings=opt.mbert
|
||||
)
|
||||
|
||||
# weight initialization
|
||||
if xavier_uniform:
|
||||
for p in model.parameters():
|
||||
if p.dim() > 1 and p.requires_grad:
|
||||
nn.init.xavier_uniform_(p)
|
||||
|
||||
if opt.tunable:
|
||||
# this has to be performed *after* Xavier initialization is done,
|
||||
# otherwise the pretrained embedding parameters will be overrided
|
||||
model.finetune_pretrained()
|
||||
|
||||
return model.cuda()
|
||||
|
||||
|
||||
def set_method_name():
|
||||
method_name = f'{opt.net}(H{opt.hidden})'
|
||||
if opt.pretrained:
|
||||
method_name += f'-Muse'
|
||||
if opt.supervised:
|
||||
method_name += f'-WCE'
|
||||
if opt.posteriors:
|
||||
method_name += f'-Posteriors'
|
||||
if opt.mbert:
|
||||
method_name += f'-mBert'
|
||||
if (opt.pretrained or opt.supervised) and opt.tunable:
|
||||
method_name += '-(trainable)'
|
||||
else:
|
||||
method_name += '-(static)'
|
||||
if opt.learnable > 0:
|
||||
method_name += f'-Learnable{opt.learnable}'
|
||||
return method_name
|
||||
|
||||
|
||||
def init_optimizer(model, lr):
|
||||
return torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=lr, weight_decay=opt.weight_decay)
|
||||
|
||||
|
||||
def init_logfile(method_name, opt):
|
||||
logfile = CSVLog(opt.log_file, ['dataset', 'method', 'epoch', 'measure', 'value', 'run', 'timelapse'])
|
||||
logfile.set_default('dataset', opt.dataset)
|
||||
logfile.set_default('run', opt.seed)
|
||||
logfile.set_default('method', method_name)
|
||||
assert opt.force or not logfile.already_calculated(), f'results for dataset {opt.dataset} method {method_name} ' \
|
||||
f'and run {opt.seed} already calculated'
|
||||
return logfile
|
||||
|
||||
|
||||
# loads the MUSE embeddings if requested, or returns empty dictionaries otherwise
|
||||
def load_pretrained_embeddings(we_path, langs):
|
||||
lpretrained = lpretrained_vocabulary = none_dict(langs)
|
||||
if opt.pretrained:
|
||||
lpretrained = load_muse_embeddings(we_path, langs, n_jobs=-1)
|
||||
lpretrained_vocabulary = {l: lpretrained[l].vocabulary() for l in langs}
|
||||
return lpretrained, lpretrained_vocabulary
|
||||
|
||||
|
||||
def get_lr(optimizer):
|
||||
for param_group in optimizer.param_groups:
|
||||
return param_group['lr']
|
||||
|
||||
|
||||
def train(model, batcher, ltrain_index, ltrain_posteriors, ltrain_bert, lytr, tinit, logfile, criterion, optim, epoch, method_name):
|
||||
_dataset_path = opt.dataset.split('/')[-1].split('_')
|
||||
dataset_id = _dataset_path[0] + _dataset_path[-1]
|
||||
|
||||
loss_history = []
|
||||
model.train()
|
||||
for idx, (batch, post, bert_emb, target, lang) in enumerate(batcher.batchify(ltrain_index, ltrain_posteriors, ltrain_bert, lytr)):
|
||||
optim.zero_grad()
|
||||
# _out = model(batch, post, bert_emb, lang)
|
||||
loss = criterion(model(batch, post, bert_emb, lang), target)
|
||||
loss.backward()
|
||||
clip_gradient(model)
|
||||
optim.step()
|
||||
loss_history.append(loss.item())
|
||||
|
||||
if idx % opt.log_interval == 0:
|
||||
interval_loss = np.mean(loss_history[-opt.log_interval:])
|
||||
print(f'{dataset_id} {method_name} Epoch: {epoch}, Step: {idx}, lr={get_lr(optim):.5f}, Training Loss: {interval_loss:.6f}')
|
||||
|
||||
mean_loss = np.mean(interval_loss)
|
||||
logfile.add_row(epoch=epoch, measure='tr_loss', value=mean_loss, timelapse=time() - tinit)
|
||||
return mean_loss
|
||||
|
||||
|
||||
def test(model, batcher, ltest_index, ltest_posteriors, lte_bert, lyte, tinit, epoch, logfile, criterion, measure_prefix):
|
||||
|
||||
loss_history = []
|
||||
model.eval()
|
||||
langs = sorted(ltest_index.keys())
|
||||
predictions = {l:[] for l in langs}
|
||||
yte_stacked = {l:[] for l in langs}
|
||||
batcher.init_offset()
|
||||
for batch, post, bert_emb, target, lang in tqdm(batcher.batchify(ltest_index, ltest_posteriors, lte_bert, lyte), desc='evaluation: '):
|
||||
logits = model(batch, post, bert_emb, lang)
|
||||
loss = criterion(logits, target).item()
|
||||
prediction = predict(logits)
|
||||
predictions[lang].append(prediction)
|
||||
yte_stacked[lang].append(target.detach().cpu().numpy())
|
||||
loss_history.append(loss)
|
||||
|
||||
ly = {l:np.vstack(yte_stacked[l]) for l in langs}
|
||||
ly_ = {l:np.vstack(predictions[l]) for l in langs}
|
||||
l_eval = evaluate(ly, ly_)
|
||||
metrics = []
|
||||
for lang in langs:
|
||||
macrof1, microf1, macrok, microk = l_eval[lang]
|
||||
metrics.append([macrof1, microf1, macrok, microk])
|
||||
if measure_prefix == 'te':
|
||||
print(f'Lang {lang}: macro-F1={macrof1:.3f} micro-F1={microf1:.3f}')
|
||||
Mf1, mF1, MK, mk = np.mean(np.array(metrics), axis=0)
|
||||
print(f'[{measure_prefix}] Averages: MF1, mF1, MK, mK [{Mf1:.5f}, {mF1:.5f}, {MK:.5f}, {mk:.5f}]')
|
||||
|
||||
mean_loss = np.mean(loss_history)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-macro-F1', value=Mf1, timelapse=time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-micro-F1', value=mF1, timelapse=time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-macro-K', value=MK, timelapse=time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-micro-K', value=mk, timelapse=time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-loss', value=mean_loss, timelapse=time() - tinit)
|
||||
|
||||
return Mf1
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
def main():
|
||||
DEBUGGING = False
|
||||
|
||||
method_name = set_method_name()
|
||||
logfile = init_logfile(method_name, opt)
|
||||
|
||||
# Loading the dataset
|
||||
data = MultilingualDataset.load(opt.dataset)
|
||||
# data.set_view(languages=['it', 'fr']) # Testing with less langs
|
||||
data.show_dimensions()
|
||||
langs = data.langs()
|
||||
l_devel_raw, l_devel_target = data.training(target_as_csr=True)
|
||||
l_test_raw, l_test_target = data.test(target_as_csr=True)
|
||||
|
||||
# Loading the MUSE pretrained embeddings (only if requested)
|
||||
lpretrained, lpretrained_vocabulary = load_pretrained_embeddings(opt.we_path, langs)
|
||||
# lpretrained_vocabulary = none_dict(langs) # do not keep track of words known in pretrained embeddings vocabulary that are also present in test set
|
||||
|
||||
# Data preparation: indexing / splitting / embedding matrices (pretrained + supervised) / posterior probs
|
||||
multilingual_index = MultilingualIndex()
|
||||
multilingual_index.index(l_devel_raw, l_devel_target, l_test_raw, lpretrained_vocabulary)
|
||||
multilingual_index.train_val_split(val_prop=0.2, max_val=2000, seed=opt.seed)
|
||||
multilingual_index.embedding_matrices(lpretrained, opt.supervised)
|
||||
if opt.posteriors:
|
||||
if DEBUGGING:
|
||||
import pickle
|
||||
with open('/home/andreapdr/funneling_pdr/dumps/posteriors_jrc_run0.pickle', 'rb') as infile:
|
||||
data_post = pickle.load(infile)
|
||||
lPtr = data_post[0]
|
||||
lPva = data_post[1]
|
||||
lPte = data_post[2]
|
||||
print('## DEBUGGING MODE: loaded dumped posteriors for jrc run0')
|
||||
else:
|
||||
lPtr, lPva, lPte = multilingual_index.posterior_probabilities(max_training_docs_by_lang=5000)
|
||||
else:
|
||||
lPtr, lPva, lPte = None, None, None
|
||||
|
||||
if opt.mbert:
|
||||
_dataset_path = opt.dataset.split('/')[-1].split('_')
|
||||
_model_folder = _dataset_path[0] + '_' + _dataset_path[-1].replace('.pickle', '')
|
||||
# print(f'Model Folder: {_model_folder}')
|
||||
|
||||
if DEBUGGING:
|
||||
with open('/home/andreapdr/funneling_pdr/dumps/mBert_jrc_run0.pickle', 'rb') as infile:
|
||||
data_embed = pickle.load(infile)
|
||||
tr_bert_embeddings = data_embed[0]
|
||||
va_bert_embeddings = data_embed[1]
|
||||
te_bert_embeddings = data_embed[2]
|
||||
print('## DEBUGGING MODE: loaded dumped mBert embeddings for jrc run0')
|
||||
else:
|
||||
tr_bert_embeddings, va_bert_embeddings, te_bert_embeddings \
|
||||
= multilingual_index.bert_embeddings(f'/home/andreapdr/funneling_pdr/hug_checkpoint/mBERT-{_model_folder}/')
|
||||
else:
|
||||
tr_bert_embeddings, va_bert_embeddings, te_bert_embeddings = None, None, None
|
||||
|
||||
# Model initialization
|
||||
model = init_Net(data.num_categories(), multilingual_index)
|
||||
|
||||
optim = init_optimizer(model, lr=opt.lr)
|
||||
criterion = torch.nn.BCEWithLogitsLoss().cuda()
|
||||
lr_scheduler = StepLR(optim, step_size=25, gamma=0.5)
|
||||
batcher_train = Batch(opt.batch_size, batches_per_epoch=10, languages=langs, lpad=multilingual_index.l_pad())
|
||||
batcher_eval = Batch(opt.batch_size, batches_per_epoch=-1, languages=langs, lpad=multilingual_index.l_pad())
|
||||
|
||||
tinit = time()
|
||||
create_if_not_exist(opt.checkpoint_dir)
|
||||
early_stop = EarlyStopping(model, optimizer=optim, patience=opt.patience,
|
||||
checkpoint=f'{opt.checkpoint_dir}/{method_name}-{get_file_name(opt.dataset)}')
|
||||
|
||||
l_train_index, l_train_target = multilingual_index.l_train()
|
||||
l_val_index, l_val_target = multilingual_index.l_val()
|
||||
l_test_index = multilingual_index.l_test_index()
|
||||
|
||||
print('-'*80)
|
||||
print('Start training')
|
||||
for epoch in range(1, opt.nepochs + 1):
|
||||
train(model, batcher_train, l_train_index, lPtr, tr_bert_embeddings, l_train_target, tinit, logfile, criterion, optim, epoch, method_name)
|
||||
lr_scheduler.step() # reduces the learning rate
|
||||
|
||||
# validation
|
||||
macrof1 = test(model, batcher_eval, l_val_index, lPva, va_bert_embeddings, l_val_target, tinit, epoch, logfile, criterion, 'va')
|
||||
early_stop(macrof1, epoch)
|
||||
if opt.test_each>0:
|
||||
if (opt.plotmode and (epoch==1 or epoch%opt.test_each==0)) or (not opt.plotmode and epoch%opt.test_each==0 and epoch<opt.nepochs):
|
||||
test(model, batcher_eval, l_test_index, lPte, l_test_target, tinit, epoch, logfile, criterion, 'te')
|
||||
|
||||
if early_stop.STOP:
|
||||
print('[early-stop] STOP')
|
||||
if not opt.plotmode: # with plotmode activated, early-stop is ignored
|
||||
break
|
||||
|
||||
# training is over
|
||||
# restores the best model according to the Mf1 of the validation set (only when plotmode==False)
|
||||
# stoptime = early_stop.stop_time - tinit
|
||||
# stopepoch = early_stop.best_epoch
|
||||
# logfile.add_row(epoch=stopepoch, measure=f'early-stop', value=early_stop.best_score, timelapse=stoptime)
|
||||
|
||||
if opt.plotmode==False:
|
||||
print('-' * 80)
|
||||
print('Training over. Performing final evaluation')
|
||||
|
||||
# torch.cuda.empty_cache()
|
||||
model = early_stop.restore_checkpoint()
|
||||
|
||||
if opt.val_epochs>0:
|
||||
print(f'running last {opt.val_epochs} training epochs on the validation set')
|
||||
for val_epoch in range(1, opt.val_epochs + 1):
|
||||
batcher_train.init_offset()
|
||||
train(model, batcher_train, l_val_index, lPva, va_bert_embeddings, l_val_target, tinit, logfile, criterion, optim, epoch+val_epoch, method_name)
|
||||
|
||||
# final test
|
||||
print('Training complete: testing')
|
||||
test(model, batcher_eval, l_test_index, lPte, te_bert_embeddings, l_test_target, tinit, epoch, logfile, criterion, 'te')
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser(description='Neural text classification with Word-Class Embeddings')
|
||||
parser.add_argument('dataset', type=str, metavar='datasetpath', help=f'path to the pickled dataset')
|
||||
parser.add_argument('--batch-size', type=int, default=50, metavar='int', help='input batch size (default: 100)')
|
||||
parser.add_argument('--batch-size-test', type=int, default=250, metavar='int', help='batch size for testing (default: 250)')
|
||||
parser.add_argument('--nepochs', type=int, default=200, metavar='int', help='number of epochs (default: 200)')
|
||||
parser.add_argument('--patience', type=int, default=10, metavar='int', help='patience for early-stop (default: 10)')
|
||||
parser.add_argument('--plotmode', action='store_true', default=False, help='in plot mode executes a long run in order '
|
||||
'to generate enough data to produce trend plots (test-each should be >0. This mode is '
|
||||
'used to produce plots, and does not perform an evaluation on the test set.')
|
||||
parser.add_argument('--hidden', type=int, default=512, metavar='int', help='hidden lstm size (default: 512)')
|
||||
parser.add_argument('--lr', type=float, default=1e-3, metavar='float', help='learning rate (default: 1e-3)')
|
||||
parser.add_argument('--weight_decay', type=float, default=0, metavar='float', help='weight decay (default: 0)')
|
||||
parser.add_argument('--sup-drop', type=float, default=0.5, metavar='[0.0, 1.0]', help='dropout probability for the supervised matrix (default: 0.5)')
|
||||
parser.add_argument('--seed', type=int, default=1, metavar='int', help='random seed (default: 1)')
|
||||
parser.add_argument('--svm-max-docs', type=int, default=1000, metavar='int', help='maximum number of documents by '
|
||||
'language used to train the calibrated SVMs (only used if --posteriors is active)')
|
||||
parser.add_argument('--log-interval', type=int, default=10, metavar='int', help='how many batches to wait before printing training status')
|
||||
parser.add_argument('--log-file', type=str, default='../log/log.csv', metavar='str', help='path to the log csv file')
|
||||
parser.add_argument('--test-each', type=int, default=0, metavar='int', help='how many epochs to wait before invoking test (default: 0, only at the end)')
|
||||
parser.add_argument('--checkpoint-dir', type=str, default='../checkpoint', metavar='str', help='path to the directory containing checkpoints')
|
||||
parser.add_argument('--net', type=str, default='rnn', metavar='str', help=f'net, one in {allowed_nets}')
|
||||
parser.add_argument('--pretrained', action='store_true', default=False, help='use MUSE pretrained embeddings')
|
||||
parser.add_argument('--supervised', action='store_true', default=False, help='use supervised embeddings')
|
||||
parser.add_argument('--posteriors', action='store_true', default=False, help='concatenate posterior probabilities to doc embeddings')
|
||||
parser.add_argument('--learnable', type=int, default=0, metavar='int', help='dimension of the learnable embeddings (default 0)')
|
||||
parser.add_argument('--val-epochs', type=int, default=1, metavar='int', help='number of training epochs to perform on the '
|
||||
'validation set once training is over (default 1)')
|
||||
parser.add_argument('--we-path', type=str, default='../embeddings', metavar='str',
|
||||
help=f'path to MUSE pretrained embeddings')
|
||||
parser.add_argument('--max-label-space', type=int, default=300, metavar='int', help='larger dimension allowed for the '
|
||||
'feature-label embedding (if larger, then PCA with this number of components is applied '
|
||||
'(default 300)')
|
||||
parser.add_argument('--force', action='store_true', default=False, help='do not check if this experiment has already been run')
|
||||
parser.add_argument('--tunable', action='store_true', default=False,
|
||||
help='pretrained embeddings are tunable from the beginning (default False, i.e., static)')
|
||||
parser.add_argument('--mbert', action='store_true', default=False,
|
||||
help='use mBert embeddings')
|
||||
|
||||
opt = parser.parse_args()
|
||||
|
||||
assert torch.cuda.is_available(), 'CUDA not available'
|
||||
assert not opt.plotmode or opt.test_each > 0, 'plot mode implies --test-each>0'
|
||||
# if opt.pickle_dir: opt.pickle_path = join(opt.pickle_dir, f'{opt.dataset}.pickle')
|
||||
torch.manual_seed(opt.seed)
|
||||
|
||||
main()
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
import os
|
||||
from dataset_builder import MultilingualDataset
|
||||
from util.evaluation import *
|
||||
from optparse import OptionParser
|
||||
from util.file import exists
|
||||
from util.results import PolylingualClassificationResults
|
||||
from util.util import get_learner, get_params
|
||||
|
||||
parser = OptionParser()
|
||||
|
||||
parser.add_option("-d", "--dataset", dest="dataset",
|
||||
help="Path to the multilingual dataset processed and stored in .pickle format",
|
||||
default="/home/moreo/CLESA/rcv2/rcv1-2_doclist_trByLang1000_teByLang1000_processed_run0.pickle")
|
||||
|
||||
parser.add_option("-o", "--output", dest="output",
|
||||
help="Result file", type=str, default='./results/results.csv')
|
||||
|
||||
parser.add_option("-e", "--mode-embed", dest="mode_embed",
|
||||
help="Set the embedding to be used [none, unsupervised, supervised, both]", type=str, default='none')
|
||||
|
||||
parser.add_option("-w", "--we-path", dest="we_path",
|
||||
help="Path to the polylingual word embeddings", default='/home/andreapdr/CLESA/')
|
||||
|
||||
parser.add_option('-t', "--we-type", dest="we_type", help="Aligned embeddings to use [FastText, MUSE]", type=str,
|
||||
default='MUSE')
|
||||
|
||||
parser.add_option("-s", "--set_c", dest="set_c",type=float,
|
||||
help="Set the C parameter", default=1)
|
||||
|
||||
parser.add_option("-c", "--optimc", dest="optimc", action='store_true',
|
||||
help="Optimize hyperparameters", default=False)
|
||||
|
||||
parser.add_option("-j", "--n_jobs", dest="n_jobs",type=int,
|
||||
help="Number of parallel jobs (default is -1, all)", default=-1)
|
||||
|
||||
parser.add_option("-p", "--pca", dest="max_labels_S", type=int,
|
||||
help="If smaller than number of target classes, PCA will be applied to supervised matrix. "
|
||||
"If set to 0 it will automatically search for the best number of components. "
|
||||
"If set to -1 it will apply PCA to the vstacked supervised matrix (PCA dim set to 50 atm)",
|
||||
default=300)
|
||||
|
||||
parser.add_option("-u", "--upca", dest="max_labels_U", type=int,
|
||||
help="If smaller than Unsupervised Dimension, PCA will be applied to unsupervised matrix."
|
||||
" If set to 0 it will automatically search for the best number of components", default=300)
|
||||
|
||||
parser.add_option("-l", dest="lang", type=str)
|
||||
|
||||
if __name__ == '__main__':
|
||||
(op, args) = parser.parse_args()
|
||||
|
||||
assert exists(op.dataset), 'Unable to find file '+str(op.dataset)
|
||||
assert not (op.set_c != 1. and op.optimc), 'Parameter C cannot be defined along with optim_c option'
|
||||
|
||||
dataset_file = os.path.basename(op.dataset)
|
||||
|
||||
results = PolylingualClassificationResults('./results/PLE_results.csv')
|
||||
|
||||
data = MultilingualDataset.load(op.dataset)
|
||||
data.show_dimensions()
|
||||
|
||||
# data.set_view(languages=['en','it', 'pt', 'sv'], categories=list(range(10)))
|
||||
# data.set_view(languages=[op.lang])
|
||||
# data.set_view(categories=list(range(10)))
|
||||
lXtr, lytr = data.training()
|
||||
lXte, lyte = data.test()
|
||||
|
||||
if op.set_c != -1:
|
||||
meta_parameters = None
|
||||
else:
|
||||
meta_parameters = [{'C': [1e3, 1e2, 1e1, 1, 1e-1]}]
|
||||
|
||||
# Embeddings and WCE config
|
||||
_available_mode = ['none', 'unsupervised', 'supervised', 'both']
|
||||
_available_type = ['MUSE', 'FastText']
|
||||
assert op.mode_embed in _available_mode, f'{op.mode_embed} not in {_available_mode}'
|
||||
assert op.we_type in _available_type, f'{op.we_type} not in {_available_type}'
|
||||
|
||||
if op.mode_embed == 'none':
|
||||
config = {'unsupervised': False,
|
||||
'supervised': False,
|
||||
'we_type': None}
|
||||
_config_id = 'None'
|
||||
elif op.mode_embed == 'unsupervised':
|
||||
config = {'unsupervised': True,
|
||||
'supervised': False,
|
||||
'we_type': op.we_type}
|
||||
_config_id = 'M'
|
||||
elif op.mode_embed == 'supervised':
|
||||
config = {'unsupervised': False,
|
||||
'supervised': True,
|
||||
'we_type': None}
|
||||
_config_id = 'F'
|
||||
elif op.mode_embed == 'both':
|
||||
config = {'unsupervised': True,
|
||||
'supervised': True,
|
||||
'we_type': op.we_type}
|
||||
_config_id = 'M+F'
|
||||
|
||||
config['reduction'] = 'PCA'
|
||||
config['max_label_space'] = op.max_labels_S
|
||||
config['dim_reduction_unsupervised'] = op.max_labels_U
|
||||
# config['post_pca'] = op.post_pca
|
||||
# config['plot_covariance_matrices'] = True
|
||||
|
||||
result_id = dataset_file + 'MLE_andrea' + _config_id + ('_optimC' if op.optimc else '')
|
||||
|
||||
ple = PolylingualEmbeddingsClassifier(wordembeddings_path='/home/andreapdr/CLESA/',
|
||||
config = config,
|
||||
learner=get_learner(calibrate=False),
|
||||
c_parameters=get_params(dense=False),
|
||||
n_jobs=op.n_jobs)
|
||||
|
||||
print('# Fitting ...')
|
||||
ple.fit(lXtr, lytr)
|
||||
|
||||
print('# Evaluating ...')
|
||||
ple_eval = evaluate_method(ple, lXte, lyte)
|
||||
|
||||
metrics = []
|
||||
for lang in lXte.keys():
|
||||
macrof1, microf1, macrok, microk = ple_eval[lang]
|
||||
metrics.append([macrof1, microf1, macrok, microk])
|
||||
print('Lang %s: macro-F1=%.3f micro-F1=%.3f' % (lang, macrof1, microf1))
|
||||
results.add_row('MLE', 'svm', _config_id, config['we_type'],
|
||||
'no','no', op.optimc, op.dataset.split('/')[-1], ple.time,
|
||||
lang, macrof1, microf1, macrok, microk, '')
|
||||
print('Averages: MF1, mF1, MK, mK', np.mean(np.array(metrics), axis=0))
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
import os
|
||||
from dataset_builder import MultilingualDataset
|
||||
# from learning.learners import *
|
||||
# from learning.learners import FunnellingMultimodal
|
||||
from learning.transformers import PosteriorProbabilitiesEmbedder, TfidfVectorizerMultilingual, WordClassEmbedder, MuseEmbedder, FeatureSet2Posteriors, Voting
|
||||
from util.evaluation import *
|
||||
from optparse import OptionParser
|
||||
from util.file import exists
|
||||
from util.results import PolylingualClassificationResults
|
||||
from sklearn.svm import SVC
|
||||
|
||||
parser = OptionParser()
|
||||
|
||||
# parser.add_option("-d", "--dataset", dest="dataset",
|
||||
# help="Path to the multilingual dataset processed and stored in .pickle format",
|
||||
# default="../rcv2/rcv1-2_doclist_trByLang1000_teByLang1000_processed_run0.pickle")
|
||||
|
||||
parser.add_option("-o", "--output", dest="output",
|
||||
help="Result file", type=str, default='./results/results.csv')
|
||||
|
||||
parser.add_option("-P", "--probs", dest="posteriors", action='store_true',
|
||||
help="Add posterior probabilities to the document embedding representation", default=False)
|
||||
|
||||
parser.add_option("-S", "--supervised", dest="supervised", action='store_true',
|
||||
help="Add supervised (Word-Class Embeddings) to the document embedding representation", default=False)
|
||||
|
||||
parser.add_option("-U", "--pretrained", dest="pretrained", action='store_true',
|
||||
help="Add pretrained MUSE embeddings to the document embedding representation", default=False)
|
||||
|
||||
parser.add_option("-w", "--we-path", dest="we_path",
|
||||
help="Path to the MUSE polylingual word embeddings", default='../embeddings')
|
||||
|
||||
parser.add_option("-s", "--set_c", dest="set_c",type=float,
|
||||
help="Set the C parameter", default=1)
|
||||
|
||||
parser.add_option("-c", "--optimc", dest="optimc", action='store_true',
|
||||
help="Optimize hyperparameters", default=False)
|
||||
|
||||
parser.add_option("-j", "--n_jobs", dest="n_jobs",type=int,
|
||||
help="Number of parallel jobs (default is -1, all)", default=-1)
|
||||
|
||||
parser.add_option("-p", "--pca", dest="max_labels_S", type=int,
|
||||
help="If smaller than number of target classes, PCA will be applied to supervised matrix. ",
|
||||
default=300)
|
||||
|
||||
parser.add_option("-r", "--remove-pc", dest="sif", action='store_true',
|
||||
help="Remove common component when computing dot product of word embedding matrices", default=False)
|
||||
|
||||
# parser.add_option("-u", "--upca", dest="max_labels_U", type=int,
|
||||
# help="If smaller than Unsupervised Dimension, PCA will be applied to unsupervised matrix."
|
||||
# " If set to 0 it will automatically search for the best number of components", default=300)
|
||||
|
||||
# parser.add_option("-a", dest="post_pca",
|
||||
# help="If set to True, will apply PCA to the z-space (posterior probabilities stacked along with "
|
||||
# "embedding space", default=False)
|
||||
|
||||
|
||||
def get_learner(calibrate=False, kernel='linear'):
|
||||
return SVC(kernel=kernel, probability=calibrate, cache_size=1000, C=op.set_c, random_state=1, gamma='auto')
|
||||
|
||||
|
||||
def get_params(dense=False):
|
||||
if not op.optimc:
|
||||
return None
|
||||
c_range = [1e4, 1e3, 1e2, 1e1, 1, 1e-1]
|
||||
kernel = 'rbf' if dense else 'linear'
|
||||
return [{'kernel': [kernel], 'C': c_range, 'gamma':['auto']}]
|
||||
|
||||
#######################################################################################################################
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
(op, args) = parser.parse_args()
|
||||
|
||||
assert len(args)==1, 'required argument "datapath" missing (path to the pickled dataset)'
|
||||
dataset = args[0]
|
||||
|
||||
assert exists(dataset), 'Unable to find file '+str(dataset)
|
||||
assert not (op.set_c != 1. and op.optimc), 'Parameter C cannot be defined along with optim_c option'
|
||||
assert op.posteriors or op.supervised or op.pretrained, 'empty set of document embeddings is not allowed'
|
||||
|
||||
dataset_file = os.path.basename(dataset)
|
||||
|
||||
results = PolylingualClassificationResults(op.output)
|
||||
|
||||
data = MultilingualDataset.load(dataset)
|
||||
data.show_dimensions()
|
||||
|
||||
lXtr, lytr = data.training()
|
||||
lXte, lyte = data.test()
|
||||
|
||||
meta_parameters = None if op.set_c != -1 else [{'C': [1, 1e3, 1e2, 1e1, 1e-1]}]
|
||||
|
||||
# result_id = f'{dataset_file}_Prob{op.posteriors}_WCE{op.supervised}(PCA{op.max_labels_S})_MUSE{op.pretrained}{"_optimC" if op.optimc else ""}'
|
||||
result_id = f'{dataset_file}_ProbPost={op.posteriors}_WCE={op.supervised}(PCA={op.max_labels_S})_' \
|
||||
f'MUSE={op.pretrained}_weight={"todo"}_l2={"todo"}_zscore={"todo"}{"_optimC" if op.optimc else ""}'
|
||||
print(f'{result_id}')
|
||||
|
||||
# text preprocessing
|
||||
tfidfvectorizer = TfidfVectorizerMultilingual(sublinear_tf=True, use_idf=True)
|
||||
|
||||
lXtr = tfidfvectorizer.fit_transform(lXtr, lytr)
|
||||
lXte = tfidfvectorizer.transform(lXte)
|
||||
lV = tfidfvectorizer.vocabulary()
|
||||
|
||||
classifiers = []
|
||||
if op.posteriors:
|
||||
classifiers.append(PosteriorProbabilitiesEmbedder(first_tier_learner=get_learner(calibrate=True), first_tier_parameters=None))
|
||||
if op.supervised:
|
||||
classifiers.append(FeatureSet2Posteriors(WordClassEmbedder(max_label_space=op.max_labels_S)))
|
||||
if op.pretrained:
|
||||
classifiers.append(FeatureSet2Posteriors(MuseEmbedder(op.we_path, lV=lV)))
|
||||
|
||||
classifier = Voting(*classifiers)
|
||||
|
||||
print('# Fitting ...')
|
||||
classifier.fit(lXtr, lytr)
|
||||
|
||||
print('\n# Evaluating ...')
|
||||
l_eval = evaluate_method(classifier, lXte, lyte)
|
||||
|
||||
# renaming arguments to be printed on log
|
||||
_id = ''
|
||||
_id_conf = [op.posteriors, op.supervised, op.pretrained]
|
||||
_id_name = ['+P', '+W', '+M']
|
||||
for i, conf in enumerate(_id_conf):
|
||||
if conf:
|
||||
_id += _id_name[i]
|
||||
_id = _id.lstrip('+')
|
||||
_dataset_path = dataset.split('/')[-1].split('_')
|
||||
dataset_id = _dataset_path[0] + _dataset_path[-1]
|
||||
|
||||
metrics = []
|
||||
for lang in lXte.keys():
|
||||
macrof1, microf1, macrok, microk = l_eval[lang]
|
||||
metrics.append([macrof1, microf1, macrok, microk])
|
||||
print(f'Lang {lang}: macro-F1={macrof1:.3f} micro-F1={microf1:.3f}')
|
||||
results.add_row(method='Voting',
|
||||
learner='svm',
|
||||
optimp=op.optimc,
|
||||
sif=op.sif,
|
||||
zscore='todo',
|
||||
l2='todo',
|
||||
wescaler='todo',
|
||||
pca=op.max_labels_S,
|
||||
id=_id,
|
||||
dataset=dataset_id,
|
||||
time='todo',
|
||||
lang=lang,
|
||||
macrof1=macrof1,
|
||||
microf1=microf1,
|
||||
macrok=macrok,
|
||||
microk=microk,
|
||||
notes='')
|
||||
print('Averages: MF1, mF1, MK, mK', np.mean(np.array(metrics), axis=0))
|
||||
|
|
@ -1,390 +0,0 @@
|
|||
from dataset_builder import MultilingualDataset
|
||||
from transformers import BertTokenizer, BertForSequenceClassification, AdamW
|
||||
from torch.utils.data import Dataset, DataLoader
|
||||
import numpy as np
|
||||
import torch
|
||||
from util.common import predict
|
||||
from time import time
|
||||
from util.csv_log import CSVLog
|
||||
from util.evaluation import evaluate
|
||||
from util.early_stop import EarlyStopping
|
||||
from torch.optim.lr_scheduler import StepLR
|
||||
from sklearn.model_selection import train_test_split
|
||||
from copy import deepcopy
|
||||
import argparse
|
||||
# from torch.utils.tensorboard import SummaryWriter
|
||||
|
||||
|
||||
def check_sentences(sentences):
|
||||
tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')
|
||||
for sentence in sentences:
|
||||
converted = [tokenizer._convert_id_to_token(token) for token in sentence.numpy() if token != 0]
|
||||
print(converted)
|
||||
return
|
||||
|
||||
|
||||
def get_model(n_out):
|
||||
print('# Initializing model ...')
|
||||
model = BertForSequenceClassification.from_pretrained('bert-base-multilingual-cased', num_labels=n_out)
|
||||
return model
|
||||
|
||||
|
||||
def set_method_name():
|
||||
return 'mBERT'
|
||||
|
||||
|
||||
def init_optimizer(model, lr):
|
||||
# return AdamW(model.parameters(), lr=lr, weight_decay=opt.weight_decay)
|
||||
no_decay = ['bias', 'LayerNorm.weight']
|
||||
optimizer_grouped_parameters = [
|
||||
{'params': [p for n, p in model.named_parameters()
|
||||
if not any(nd in n for nd in no_decay)],
|
||||
'weight_decay': opt.weight_decay},
|
||||
{'params': [p for n, p in model.named_parameters()
|
||||
if any(nd in n for nd in no_decay)],
|
||||
'weight_decay': opt.weight_decay}
|
||||
]
|
||||
optimizer = AdamW(optimizer_grouped_parameters, lr=lr)
|
||||
return optimizer
|
||||
|
||||
|
||||
def init_logfile(method_name, opt):
|
||||
logfile = CSVLog(opt.log_file, ['dataset', 'method', 'epoch', 'measure', 'value', 'run', 'timelapse'])
|
||||
logfile.set_default('dataset', opt.dataset)
|
||||
logfile.set_default('run', opt.seed)
|
||||
logfile.set_default('method', method_name)
|
||||
assert opt.force or not logfile.already_calculated(), f'results for dataset {opt.dataset} method {method_name} ' \
|
||||
f'and run {opt.seed} already calculated'
|
||||
return logfile
|
||||
|
||||
|
||||
def get_lr(optimizer):
|
||||
for param_group in optimizer.param_groups:
|
||||
return param_group['lr']
|
||||
|
||||
|
||||
def get_dataset_name(datapath):
|
||||
possible_splits = [str(i) for i in range(10)]
|
||||
splitted = datapath.split('_')
|
||||
id_split = splitted[-1].split('.')[0][-1]
|
||||
if id_split in possible_splits:
|
||||
dataset_name = splitted[0].split('/')[-1]
|
||||
return f'{dataset_name}_run{id_split}'
|
||||
elif splitted[-2].split('.')[0] == 'full':
|
||||
dataset_name = splitted[0].split('/')[-1]
|
||||
return f'{dataset_name}_fullrun'
|
||||
|
||||
|
||||
def load_datasets(datapath):
|
||||
data = MultilingualDataset.load(datapath)
|
||||
# data.set_view(languages=['it']) #, categories=[0, 1, 2, 3, 4]) # Testing with less langs
|
||||
data.show_dimensions()
|
||||
|
||||
l_devel_raw, l_devel_target = data.training(target_as_csr=False)
|
||||
l_test_raw, l_test_target = data.test(target_as_csr=False)
|
||||
|
||||
return l_devel_raw, l_devel_target, l_test_raw, l_test_target
|
||||
|
||||
|
||||
def do_tokenization(l_dataset, max_len=512, verbose=True):
|
||||
if verbose:
|
||||
print('# Starting Tokenization ...')
|
||||
tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')
|
||||
langs = l_dataset.keys()
|
||||
l_tokenized = {}
|
||||
for lang in langs:
|
||||
l_tokenized[lang] = tokenizer(l_dataset[lang],
|
||||
truncation=True,
|
||||
max_length=max_len,
|
||||
padding='max_length')
|
||||
return l_tokenized
|
||||
|
||||
|
||||
class TrainingDataset(Dataset):
|
||||
"""
|
||||
data: dict of lang specific tokenized data
|
||||
labels: dict of lang specific targets
|
||||
"""
|
||||
|
||||
def __init__(self, data, labels):
|
||||
self.langs = data.keys()
|
||||
self.lang_ids = {lang: identifier for identifier, lang in enumerate(self.langs)}
|
||||
|
||||
for i, lang in enumerate(self.langs):
|
||||
_data = data[lang]['input_ids']
|
||||
_data = np.array(_data)
|
||||
_labels = labels[lang]
|
||||
_lang_value = np.full(len(_data), self.lang_ids[lang])
|
||||
|
||||
if i == 0:
|
||||
self.data = _data
|
||||
self.labels = _labels
|
||||
self.lang_index = _lang_value
|
||||
else:
|
||||
self.data = np.vstack((self.data, _data))
|
||||
self.labels = np.vstack((self.labels, _labels))
|
||||
self.lang_index = np.concatenate((self.lang_index, _lang_value))
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
x = self.data[idx]
|
||||
y = self.labels[idx]
|
||||
lang = self.lang_index[idx]
|
||||
|
||||
return x, torch.tensor(y, dtype=torch.float), lang
|
||||
|
||||
def get_lang_ids(self):
|
||||
return self.lang_ids
|
||||
|
||||
def get_nclasses(self):
|
||||
if hasattr(self, 'labels'):
|
||||
return len(self.labels[0])
|
||||
else:
|
||||
print('Method called before init!')
|
||||
|
||||
|
||||
def freeze_encoder(model):
|
||||
for param in model.base_model.parameters():
|
||||
param.requires_grad = False
|
||||
return model
|
||||
|
||||
|
||||
def check_param_grad_status(model):
|
||||
print('#' * 50)
|
||||
print('Model paramater status:')
|
||||
for name, child in model.named_children():
|
||||
trainable = False
|
||||
for param in child.parameters():
|
||||
if param.requires_grad:
|
||||
trainable = True
|
||||
if not trainable:
|
||||
print(f'{name} is frozen')
|
||||
else:
|
||||
print(f'{name} is not frozen')
|
||||
print('#' * 50)
|
||||
|
||||
|
||||
def train(model, train_dataloader, epoch, criterion, optim, method_name, tinit, logfile, writer):
|
||||
_dataset_path = opt.dataset.split('/')[-1].split('_')
|
||||
dataset_id = _dataset_path[0] + _dataset_path[-1]
|
||||
|
||||
loss_history = []
|
||||
model.train()
|
||||
|
||||
for idx, (batch, target, lang_idx) in enumerate(train_dataloader):
|
||||
optim.zero_grad()
|
||||
out = model(batch.cuda())
|
||||
logits = out[0]
|
||||
loss = criterion(logits, target.cuda())
|
||||
loss.backward()
|
||||
# clip_gradient(model)
|
||||
optim.step()
|
||||
loss_history.append(loss.item())
|
||||
|
||||
if writer is not None:
|
||||
_n_step = (epoch - 1) * (len(train_dataloader)) + idx
|
||||
writer.add_scalar('Loss_step/Train', loss, _n_step)
|
||||
|
||||
# Check tokenized sentences consistency
|
||||
# check_sentences(batch.cpu())
|
||||
|
||||
if idx % opt.log_interval == 0:
|
||||
interval_loss = np.mean(loss_history[-opt.log_interval:])
|
||||
print(
|
||||
f'{dataset_id} {method_name} Epoch: {epoch}, Step: {idx}, lr={get_lr(optim):.5f}, Training Loss: {interval_loss:.6f}')
|
||||
|
||||
mean_loss = np.mean(interval_loss)
|
||||
logfile.add_row(epoch=epoch, measure='tr_loss', value=mean_loss, timelapse=time() - tinit)
|
||||
return mean_loss
|
||||
|
||||
|
||||
def test(model, test_dataloader, lang_ids, tinit, epoch, logfile, criterion, measure_prefix, writer):
|
||||
print('# Validating model ...')
|
||||
loss_history = []
|
||||
model.eval()
|
||||
langs = lang_ids.keys()
|
||||
id_2_lang = {v: k for k, v in lang_ids.items()}
|
||||
predictions = {l: [] for l in langs}
|
||||
yte_stacked = {l: [] for l in langs}
|
||||
|
||||
for batch, target, lang_idx in test_dataloader:
|
||||
out = model(batch.cuda())
|
||||
logits = out[0]
|
||||
loss = criterion(logits, target.cuda()).item()
|
||||
prediction = predict(logits)
|
||||
loss_history.append(loss)
|
||||
|
||||
# Assigning prediction to dict in predictions and yte_stacked according to lang_idx
|
||||
for i, pred in enumerate(prediction):
|
||||
lang_pred = id_2_lang[lang_idx.numpy()[i]]
|
||||
predictions[lang_pred].append(pred)
|
||||
yte_stacked[lang_pred].append(target[i].detach().cpu().numpy())
|
||||
|
||||
ly = {l: np.vstack(yte_stacked[l]) for l in langs}
|
||||
ly_ = {l: np.vstack(predictions[l]) for l in langs}
|
||||
l_eval = evaluate(ly, ly_)
|
||||
metrics = []
|
||||
for lang in langs:
|
||||
macrof1, microf1, macrok, microk = l_eval[lang]
|
||||
metrics.append([macrof1, microf1, macrok, microk])
|
||||
if measure_prefix == 'te':
|
||||
print(f'Lang {lang}: macro-F1={macrof1:.3f} micro-F1={microf1:.3f}')
|
||||
Mf1, mF1, MK, mk = np.mean(np.array(metrics), axis=0)
|
||||
print(f'[{measure_prefix}] Averages: MF1, mF1, MK, mK [{Mf1:.5f}, {mF1:.5f}, {MK:.5f}, {mk:.5f}]')
|
||||
if writer is not None:
|
||||
writer.add_scalars('Eval Metrics', {'Mf1': Mf1, 'mF1': mF1, 'MK': MK, 'mk':mk}, epoch)
|
||||
|
||||
mean_loss = np.mean(loss_history)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-macro-F1', value=Mf1, timelapse=time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-micro-F1', value=mF1, timelapse=time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-macro-K', value=MK, timelapse=time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-micro-K', value=mk, timelapse=time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-loss', value=mean_loss, timelapse=time() - tinit)
|
||||
|
||||
return Mf1
|
||||
|
||||
|
||||
def get_tr_val_split(l_tokenized_tr, l_devel_target, val_prop, max_val, seed):
|
||||
l_split_va = deepcopy(l_tokenized_tr)
|
||||
l_split_val_target = {l: [] for l in l_tokenized_tr.keys()}
|
||||
l_split_tr = deepcopy(l_tokenized_tr)
|
||||
l_split_tr_target = {l: [] for l in l_tokenized_tr.keys()}
|
||||
|
||||
for lang in l_tokenized_tr.keys():
|
||||
val_size = int(min(len(l_tokenized_tr[lang]['input_ids']) * val_prop, max_val))
|
||||
l_split_tr[lang]['input_ids'], l_split_va[lang]['input_ids'], l_split_tr_target[lang], l_split_val_target[
|
||||
lang] = \
|
||||
train_test_split(l_tokenized_tr[lang]['input_ids'], l_devel_target[lang], test_size=val_size,
|
||||
random_state=seed, shuffle=True)
|
||||
|
||||
return l_split_tr, l_split_tr_target, l_split_va, l_split_val_target
|
||||
|
||||
|
||||
def main():
|
||||
print('Running main ...')
|
||||
|
||||
DATAPATH = opt.dataset
|
||||
MAX_LEN = 512
|
||||
method_name = set_method_name()
|
||||
logfile = init_logfile(method_name, opt)
|
||||
|
||||
l_devel_raw, l_devel_target, l_test_raw, l_test_target = load_datasets(DATAPATH)
|
||||
l_tokenized_tr = do_tokenization(l_devel_raw, max_len=MAX_LEN)
|
||||
|
||||
l_split_tr, l_split_tr_target, l_split_va, l_split_val_target = get_tr_val_split(l_tokenized_tr, l_devel_target,
|
||||
val_prop=0.2, max_val=2000,
|
||||
seed=opt.seed)
|
||||
|
||||
l_tokenized_te = do_tokenization(l_test_raw, max_len=MAX_LEN)
|
||||
|
||||
tr_dataset = TrainingDataset(l_split_tr, l_split_tr_target)
|
||||
va_dataset = TrainingDataset(l_split_va, l_split_val_target)
|
||||
te_dataset = TrainingDataset(l_tokenized_te, l_test_target)
|
||||
|
||||
tr_dataloader = DataLoader(tr_dataset, batch_size=4, shuffle=True)
|
||||
va_dataloader = DataLoader(va_dataset, batch_size=2, shuffle=True)
|
||||
te_dataloader = DataLoader(te_dataset, batch_size=2, shuffle=False)
|
||||
|
||||
|
||||
# Initializing model
|
||||
nC = tr_dataset.get_nclasses()
|
||||
model = get_model(nC)
|
||||
model = model.cuda()
|
||||
criterion = torch.nn.BCEWithLogitsLoss().cuda()
|
||||
optim = init_optimizer(model, lr=opt.lr)
|
||||
lr_scheduler = StepLR(optim, step_size=25, gamma=0.1)
|
||||
early_stop = EarlyStopping(model, optimizer=optim, patience=opt.patience,
|
||||
checkpoint=f'/home/andreapdr/funneling_pdr/hug_checkpoint/{method_name}-{get_dataset_name(opt.dataset)}',
|
||||
is_bert=True)
|
||||
|
||||
# Freezing encoder
|
||||
# model = freeze_encoder(model)
|
||||
check_param_grad_status(model)
|
||||
|
||||
# Tensorboard logger
|
||||
# writer = SummaryWriter('../log/tensorboard_logs/')
|
||||
|
||||
# Training loop
|
||||
tinit = time()
|
||||
lang_ids = va_dataset.lang_ids
|
||||
for epoch in range(1, opt.nepochs + 1):
|
||||
print('# Start Training ...')
|
||||
train(model, tr_dataloader, epoch, criterion, optim, method_name, tinit, logfile, writer=None)
|
||||
lr_scheduler.step() # reduces the learning rate
|
||||
|
||||
# Validation
|
||||
macrof1 = test(model, va_dataloader, lang_ids, tinit, epoch, logfile, criterion, 'va', writer=None)
|
||||
early_stop(macrof1, epoch)
|
||||
if opt.test_each > 0:
|
||||
if (opt.plotmode and (epoch == 1 or epoch % opt.test_each == 0)) or (
|
||||
not opt.plotmode and epoch % opt.test_each == 0 and epoch < opt.nepochs):
|
||||
test(model, te_dataloader, lang_ids, tinit, epoch, logfile, criterion, 'te', writer=None)
|
||||
|
||||
if early_stop.STOP:
|
||||
print('[early-stop] STOP')
|
||||
if not opt.plotmode:
|
||||
break
|
||||
|
||||
if not opt.plotmode:
|
||||
print('-' * 80)
|
||||
print('Training over. Performing final evaluation')
|
||||
|
||||
model = early_stop.restore_checkpoint()
|
||||
model = model.cuda()
|
||||
|
||||
if opt.val_epochs > 0:
|
||||
print(f'running last {opt.val_epochs} training epochs on the validation set')
|
||||
for val_epoch in range(1, opt.val_epochs + 1):
|
||||
train(model, va_dataloader, epoch + val_epoch, criterion, optim, method_name, tinit, logfile, writer=None)
|
||||
|
||||
# final test
|
||||
print('Training complete: testing')
|
||||
test(model, te_dataloader, lang_ids, tinit, epoch, logfile, criterion, 'te', writer=None)
|
||||
|
||||
# writer.flush()
|
||||
# writer.close()
|
||||
exit('Code Executed!')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='Neural text classification with Word-Class Embeddings - mBert model')
|
||||
|
||||
parser.add_argument('--dataset', type=str,
|
||||
default='/home/moreo/CLESA/rcv2/rcv1-2_doclist_trByLang1000_teByLang1000_processed_run0.pickle',
|
||||
metavar='datasetpath', help=f'path to the pickled dataset')
|
||||
parser.add_argument('--nepochs', type=int, default=200, metavar='int',
|
||||
help='number of epochs (default: 200)')
|
||||
parser.add_argument('--lr', type=float, default=2e-5, metavar='float',
|
||||
help='learning rate (default: 2e-5)')
|
||||
parser.add_argument('--weight_decay', type=float, default=0, metavar='float',
|
||||
help='weight decay (default: 0)')
|
||||
parser.add_argument('--patience', type=int, default=10, metavar='int',
|
||||
help='patience for early-stop (default: 10)')
|
||||
parser.add_argument('--log-interval', type=int, default=20, metavar='int',
|
||||
help='how many batches to wait before printing training status')
|
||||
parser.add_argument('--log-file', type=str, default='../log/log_mBert.csv', metavar='str',
|
||||
help='path to the log csv file')
|
||||
parser.add_argument('--seed', type=int, default=1, metavar='int', help='random seed (default: 1)')
|
||||
parser.add_argument('--force', action='store_true', default=False,
|
||||
help='do not check if this experiment has already been run')
|
||||
parser.add_argument('--checkpoint-dir', type=str, default='../checkpoint', metavar='str',
|
||||
help='path to the directory containing checkpoints')
|
||||
parser.add_argument('--plotmode', action='store_true', default=False,
|
||||
help='in plot mode executes a long run in order '
|
||||
'to generate enough data to produce trend plots (test-each should be >0. This mode is '
|
||||
'used to produce plots, and does not perform an evaluation on the test set.')
|
||||
parser.add_argument('--test-each', type=int, default=0, metavar='int',
|
||||
help='how many epochs to wait before invoking test (default: 0, only at the end)')
|
||||
parser.add_argument('--val-epochs', type=int, default=1, metavar='int',
|
||||
help='number of training epochs to perform on the validation set once training is over (default 1)')
|
||||
opt = parser.parse_args()
|
||||
|
||||
# Testing different parameters ...
|
||||
opt.weight_decay = 0.01
|
||||
opt.lr = 1e-5
|
||||
opt.patience = 5
|
||||
|
||||
main()
|
||||
# TODO: refactor .cuda() -> .to(device) in order to check if the process is faster on CPU given the bigger batch size
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
from experiment_scripts.main_mbert import *
|
||||
import pickle
|
||||
|
||||
|
||||
class ExtractorDataset(Dataset):
|
||||
"""
|
||||
data: dict of lang specific tokenized data
|
||||
labels: dict of lang specific targets
|
||||
"""
|
||||
|
||||
def __init__(self, data):
|
||||
self.langs = data.keys()
|
||||
self.lang_ids = {lang: identifier for identifier, lang in enumerate(self.langs)}
|
||||
|
||||
for i, lang in enumerate(self.langs):
|
||||
_data = data[lang]['input_ids']
|
||||
_data = np.array(_data)
|
||||
_lang_value = np.full(len(_data), self.lang_ids[lang])
|
||||
|
||||
if i == 0:
|
||||
self.data = _data
|
||||
self.lang_index = _lang_value
|
||||
else:
|
||||
self.data = np.vstack((self.data, _data))
|
||||
self.lang_index = np.concatenate((self.lang_index, _lang_value))
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
x = self.data[idx]
|
||||
lang = self.lang_index[idx]
|
||||
|
||||
return x, lang
|
||||
|
||||
def get_lang_ids(self):
|
||||
return self.lang_ids
|
||||
|
||||
|
||||
def feature_extractor(data, lang_ids, model_path='/home/andreapdr/funneling_pdr/hug_checkpoint/mBERT-jrc_run0/'):
|
||||
print('# Feature Extractor Mode...')
|
||||
from transformers import BertConfig
|
||||
config = BertConfig.from_pretrained('bert-base-multilingual-cased', output_hidden_states=True, num_labels=300)
|
||||
model = BertForSequenceClassification.from_pretrained(model_path,
|
||||
config=config).cuda()
|
||||
|
||||
"""
|
||||
Hidden State = Tuple of torch.FloatTensor (one for the output of the embeddings + one for
|
||||
the output of each layer) of shape (batch_size, sequence_length, hidden_size)
|
||||
"""
|
||||
all_batch_embeddings = {}
|
||||
id2lang = {v:k for k,v in lang_ids.items()}
|
||||
with torch.no_grad():
|
||||
for batch, target, lang_idx in data:
|
||||
out = model(batch.cuda())
|
||||
last_hidden_state = out[1][-1]
|
||||
batch_embeddings = last_hidden_state[:, 0, :]
|
||||
for i, l_idx in enumerate(lang_idx.numpy()):
|
||||
if id2lang[l_idx] not in all_batch_embeddings.keys():
|
||||
all_batch_embeddings[id2lang[l_idx]] = batch_embeddings[i].detach().cpu().numpy()
|
||||
else:
|
||||
all_batch_embeddings[id2lang[l_idx]] = np.vstack((all_batch_embeddings[id2lang[l_idx]],
|
||||
batch_embeddings[i].detach().cpu().numpy()))
|
||||
|
||||
return all_batch_embeddings, id2lang
|
||||
|
||||
|
||||
def main():
|
||||
print('Running main ...')
|
||||
print(f'Model path: {opt.modelpath}\nDataset path: {opt.dataset}')
|
||||
DATAPATH = opt.dataset
|
||||
MAX_LEN = 512
|
||||
|
||||
l_devel_raw, l_devel_target, l_test_raw, l_test_target = load_datasets(DATAPATH)
|
||||
l_tokenized_tr = do_tokenization(l_devel_raw, max_len=MAX_LEN)
|
||||
l_tokenized_te = do_tokenization(l_test_raw, max_len=MAX_LEN)
|
||||
|
||||
tr_dataset = TrainingDataset(l_tokenized_tr, l_devel_target)
|
||||
tr_lang_ids = tr_dataset.lang_ids
|
||||
|
||||
te_dataset = TrainingDataset(l_tokenized_te, l_test_target)
|
||||
te_lang_ids = te_dataset.lang_ids
|
||||
|
||||
tr_dataloader = DataLoader(tr_dataset, batch_size=64, shuffle=False) # Shuffle False to extract doc embeddings
|
||||
te_dataloader = DataLoader(te_dataset, batch_size=64, shuffle=False) # Shuffle False to extract doc
|
||||
|
||||
tr_all_batch_embeddings, id2lang_tr = feature_extractor(tr_dataloader, tr_lang_ids, opt.modelpath) # Extracting doc embed for devel
|
||||
with open(f'{opt.modelpath}/TR_embed_{get_dataset_name(opt.dataset)}.pkl', 'wb') as outfile:
|
||||
pickle.dump((tr_all_batch_embeddings, id2lang_tr), outfile)
|
||||
|
||||
te_all_batch_embeddings, id2lang_te = feature_extractor(te_dataloader, te_lang_ids, opt.modelpath) # Extracting doc embed for test
|
||||
with open(f'{opt.modelpath}/TE_embed_{get_dataset_name(opt.dataset)}.pkl', 'wb') as outfile:
|
||||
pickle.dump((te_all_batch_embeddings, id2lang_te), outfile)
|
||||
|
||||
exit('Extraction completed!')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='mBert model document embedding extractor')
|
||||
|
||||
parser.add_argument('--dataset', type=str,
|
||||
default='/home/moreo/CLESA/rcv2/rcv1-2_doclist_trByLang1000_teByLang1000_processed_run0.pickle',
|
||||
metavar='datasetpath', help=f'path to the pickled dataset')
|
||||
parser.add_argument('--seed', type=int, default=1, metavar='int', help='random seed (default: 1)')
|
||||
parser.add_argument('--modelpath', type=str, default='/home/andreapdr/funneling_pdr/hug_checkpoint/mBERT-jrc_run0',
|
||||
metavar='modelpath', help=f'path to pre-trained mBert model')
|
||||
opt = parser.parse_args()
|
||||
|
||||
main()
|
||||
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import os
|
||||
from dataset_builder import MultilingualDataset
|
||||
from optparse import OptionParser
|
||||
from util.file import exists
|
||||
import numpy as np
|
||||
from sklearn.feature_extraction.text import CountVectorizer
|
||||
|
||||
parser = OptionParser(usage="usage: %prog datapath [options]")
|
||||
|
||||
(op, args) = parser.parse_args()
|
||||
assert len(args)==1, 'required argument "datapath" missing (path to the pickled dataset)'
|
||||
dataset = args[0]
|
||||
assert exists(dataset), 'Unable to find file '+str(dataset)
|
||||
|
||||
dataset_file = os.path.basename(dataset)
|
||||
|
||||
data = MultilingualDataset.load(dataset)
|
||||
data.set_view(languages=['it'])
|
||||
data.show_dimensions()
|
||||
lXtr, lytr = data.training()
|
||||
lXte, lyte = data.test()
|
||||
|
||||
vect_lXtr = dict()
|
||||
vectorizer = CountVectorizer()
|
||||
vect_lXtr['it'] = vectorizer.fit_transform(lXtr['it'])
|
||||
# print(type(vect_lXtr['it']))
|
||||
|
||||
corr = vect_lXtr['it'].T.dot(lytr['it'])
|
||||
# print(corr.shape)
|
||||
sum_correlated_class = corr.sum(axis=0)
|
||||
print(len(sum_correlated_class))
|
||||
print(sum_correlated_class.max())
|
||||
|
||||
|
||||
w2idx = vectorizer.vocabulary_
|
||||
idx2w = {v:k for k,v in w2idx.items()}
|
||||
|
||||
word_tot_corr = corr.sum(axis=1)
|
||||
print(word_tot_corr.shape)
|
||||
dict_word_tot_corr = {v:k for k,v in enumerate(word_tot_corr)}
|
||||
|
||||
sorted_word_tot_corr = np.sort(word_tot_corr)
|
||||
sorted_word_tot_corr = sorted_word_tot_corr[len(sorted_word_tot_corr)-200:]
|
||||
|
||||
top_idx = [dict_word_tot_corr[k] for k in sorted_word_tot_corr]
|
||||
print([idx2w[idx] for idx in top_idx])
|
||||
print([elem for elem in top_idx])
|
||||
print(corr[8709])
|
||||
print('Finished...')
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
dataset=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_noparallel_processed_run0.pickle
|
||||
logfile=./results/final_combinations_jrc.csv
|
||||
#A.2: ensembling feature sets (combinations of posteriors, wce, muse):
|
||||
# - exploring different ways of putting different feature sets together: concatenation, FeatureSetToPosteriors, averaging, voting, etc...
|
||||
# (no one seems to improve over standard funnelling [the improved version after A.1] with posteriors probabilities...)
|
||||
|
||||
# aggregation=concatenation
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -r -z --l2
|
||||
#python main_gFun.py $dataset -o $logfile -P -S -r -z --l2
|
||||
#python main_gFun.py $dataset -o $logfile -U -S -r -z --l2
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -S -r -z --l2
|
||||
#
|
||||
|
||||
##FeatureSetToPosteriors (aggregation mean)
|
||||
python main_multimodal_cls.py $dataset -o $logfile -P -U -r -a -z --l2 --allprob
|
||||
python main_multimodal_cls.py $dataset -o $logfile -P -S -r -a -z --l2 --allprob
|
||||
python main_multimodal_cls.py $dataset -o $logfile -U -S -r -a -z --l2 --allprob
|
||||
python main_multimodal_cls.py $dataset -o $logfile -P -U -S -r -a -z --l2 --allprob
|
||||
|
||||
##FeatureSetToPosteriors
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -r -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -P -S -r -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -U -S -r -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -S -r -z --l2 --allprob
|
||||
|
||||
#MajorityVoting
|
||||
#python main_majorityvoting_cls.py $dataset -o $logfile -P -U -r
|
||||
#python main_majorityvoting_cls.py $dataset -o $logfile -P -S -r
|
||||
#python main_majorityvoting_cls.py $dataset -o $logfile -U -S -r
|
||||
#python main_majorityvoting_cls.py $dataset -o $logfile -P -U -S -r
|
||||
|
||||
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
dataset=/home/moreo/CLESA/rcv2/rcv1-2_doclist_trByLang1000_teByLang1000_processed_run0.pickle
|
||||
logfile=./results/final_combinations_rcv.csv
|
||||
#A.2: ensembling feature sets (combinations of posteriors, wce, muse):
|
||||
# - exploring different ways of putting different feature sets together: concatenation, FeatureSetToPosteriors, averaging, voting, etc...
|
||||
# (no one seems to improve over standard funnelling [the improved version after A.1] with posteriors probabilities...)
|
||||
|
||||
# aggregation=concatenation
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -r -z --l2
|
||||
#python main_gFun.py $dataset -o $logfile -P -S -r -z --l2
|
||||
#python main_gFun.py $dataset -o $logfile -U -S -r -z --l2
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -S -r -z --l2
|
||||
#
|
||||
##FeatureSetToPosteriors (aggregation mean)
|
||||
python main_multimodal_cls.py $dataset -o $logfile -P -U -r -a -z --l2 --allprob
|
||||
python main_multimodal_cls.py $dataset -o $logfile -P -S -r -a -z --l2 --allprob
|
||||
python main_multimodal_cls.py $dataset -o $logfile -U -S -r -a -z --l2 --allprob
|
||||
python main_multimodal_cls.py $dataset -o $logfile -P -U -S -r -a -z --l2 --allprob
|
||||
|
||||
##FeatureSetToPosteriors
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -r -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -P -S -r -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -U -S -r -z --l2 --allprob
|
||||
#python main_gFun.py $dataset -o $logfile -P -U -S -r -z --l2 --allprob
|
||||
|
||||
#MajorityVoting
|
||||
#python main_majorityvoting_cls.py $dataset -o $logfile -P -U -r
|
||||
#python main_majorityvoting_cls.py $dataset -o $logfile -P -S -r
|
||||
#python main_majorityvoting_cls.py $dataset -o $logfile -U -S -r
|
||||
#python main_majorityvoting_cls.py $dataset -o $logfile -P -U -S -r
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
logfile=../log/log_pre_jrc.csv
|
||||
dataset=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_noparallel_processed_run0.pickle
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --hidden 128 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --hidden 128 --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --hidden 256 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --hidden 256 --tunable --plotmode --test-each 20
|
||||
|
||||
python main_deep_learning.py $dataset --log-file $logfile --supervised --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --supervised --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --supervised --hidden 128 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --supervised --hidden 128 --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --supervised --hidden 256 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --supervised --hidden 256 --tunable --plotmode --test-each 20
|
||||
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --hidden 128 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --hidden 128 --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --hidden 256 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --hidden 256 --tunable --plotmode --test-each 20
|
||||
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --posteriors --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --posteriors --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --posteriors --hidden 128 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --posteriors --hidden 128 --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --posteriors --hidden 256 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --log-file $logfile --pretrained --supervised --posteriors --hidden 256 --tunable --plotmode --test-each 20
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
dataset=/home/moreo/CLESA/rcv2/rcv1-2_doclist_trByLang1000_teByLang1000_processed_run0.pickle
|
||||
python main_deep_learning.py $dataset --pretrained --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --hidden 128 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --hidden 128 --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --hidden 256 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --hidden 256 --tunable --plotmode --test-each 20
|
||||
|
||||
python main_deep_learning.py $dataset --supervised --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --supervised --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --supervised --hidden 128 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --supervised --hidden 128 --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --supervised --hidden 256 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --supervised --hidden 256 --tunable --plotmode --test-each 20
|
||||
|
||||
python main_deep_learning.py $dataset --pretrained --supervised --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --supervised --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --supervised --hidden 128 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --supervised --hidden 128 --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --supervised --hidden 256 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --supervised --hidden 256 --tunable --plotmode --test-each 20
|
||||
|
||||
python main_deep_learning.py $dataset --pretrained --supervised --posteriors --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --supervised --posteriors --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --supervised --posteriors --hidden 128 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --supervised --posteriors --hidden 128 --tunable --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --supervised --posteriors --hidden 256 --plotmode --test-each 20
|
||||
python main_deep_learning.py $dataset --pretrained --supervised --posteriors --hidden 256 --tunable --plotmode --test-each 20
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
dataset=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_full_processed.pickle
|
||||
seeds='5' #2 3 4 5 6 7 8 9 10'
|
||||
for seed in $seeds
|
||||
do
|
||||
#python main_deep_learning.py $dataset --log-file ../log/jrc_fullrun_wce.csv --supervised --seed $seed
|
||||
#python main_deep_learning.py $dataset --log-file ../log/jrc_fullrun_wce_trainable.csv --supervised --tunable --seed $seed
|
||||
python main_deep_learning.py $dataset --log-file ../log/jrc_fullrun_post_wce_muse_static.csv --posteriors --supervised --pretrained --seed $seed --force
|
||||
|
||||
#python main_deep_learning.py $dataset --log-file ../log/jrc_fullrun_muse.csv --pretrained --seed $seed
|
||||
#python main_deep_learning.py $dataset --log-file ../log/jrc_fullrun_muse_trainable.csv --pretrained --tunable --seed $seed
|
||||
|
||||
#python main_deep_learning.py $dataset --log-file ../log/jrc_fullrun_wce_muse.csv --supervised --pretrained --seed $seed
|
||||
#python main_deep_learning.py $dataset --log-file ../log/jrc_fullrun_wce_muse_trainable40000.csv --supervised --pretrained --tunable --seed $seed
|
||||
#python main_deep_learning.py $dataset --log-file ../log/jrc_fullrun_post_wce_muse_trainable.csv --posteriors --supervised --pretrained --tunable --seed $seed --force
|
||||
|
||||
done
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
dataset=/home/moreo/CLESA/rcv2/rcv1-2_doclist_full_processed.pickle
|
||||
seeds='1 ' #2 3 4 5' # 6 7 8 9 10'
|
||||
for seed in $seeds
|
||||
do
|
||||
#python main_deep_learning.py $dataset --log-file ../log/rcv_fullrun_wce.csv --supervised --seed $seed
|
||||
#python main_deep_learning.py $dataset --log-file ../log/rcv_fullrun_wce_trainable.csv --supervised --tunable --seed $seed
|
||||
python main_deep_learning.py $dataset --log-file ../log/rcv_fullrun_post_wce_muse_static_plotmode.csv --posteriors --supervised --pretrained --seed $seed --plotmode --test-each 200
|
||||
|
||||
|
||||
|
||||
#python main_deep_learning.py $dataset --log-file ../log/rcv_fullrun_muse.csv --pretrained --seed $seed
|
||||
#python main_deep_learning.py $dataset --log-file ../log/rcv_fullrun_muse_trainable.csv --pretrained --tunable --seed $seed
|
||||
|
||||
#python main_deep_learning.py $dataset --log-file ../log/rcv_fullrun_wce_muse.csv --supervised --pretrained --seed $seed
|
||||
#python main_deep_learning.py $dataset --log-file ../log/rcv_fullrun_wce_muse_trainable.csv --supervised --pretrained --tunable --seed $seed
|
||||
|
||||
# python main_deep_learning.py $dataset --log-file ../log/rcv_fullrun_post_wce_muse_static.csv --posteriors --supervised --pretrained --seed $seed
|
||||
# python main_deep_learning.py $dataset --log-file ../log/rcv_fullrun_post_wce_muse_trainable_plotmode.csv --posteriors --supervised --pretrained --tunable --seed $seed --plotmode --test-each 200
|
||||
#python main_deep_learning.py $dataset --log-file ../log/rcv_fullrun_post_wce_muse_trainable.csv --posteriors --supervised --pretrained --tunable --seed $seed
|
||||
done
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
#dataset_path=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_noparallel_processed_run
|
||||
#logfile=../log/log_FunBert_jrc.csv
|
||||
#
|
||||
#runs='0 1 2 3 4'
|
||||
#for run in $runs
|
||||
#do
|
||||
# dataset=$dataset_path$run.pickle
|
||||
# python main_deep_learning.py $dataset --supervised --pretrained --posteriors --mbert --log-file $logfile #--tunable
|
||||
#done
|
||||
|
||||
dataset=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_full_processed.pickle
|
||||
logfile=../log/log_FunBert_fulljrc_static.csv
|
||||
|
||||
python main_deep_learning.py $dataset --supervised --pretrained --posteriors --mbert --log-file $logfile
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
#dataset_path=/home/moreo/CLESA/rcv2/rcv1-2_doclist_trByLang1000_teByLang1000_processed_run
|
||||
#logfile=../log/log_FunBert_rcv_static.csv
|
||||
#
|
||||
#runs='0 1 2 3 4'
|
||||
#for run in $runs
|
||||
#do
|
||||
# dataset=$dataset_path$run.pickle
|
||||
# python main_deep_learning.py $dataset --supervised --pretrained --posteriors --mbert --log-file $logfile
|
||||
#done
|
||||
|
||||
dataset=/home/moreo/CLESA/rcv2/rcv1-2_doclist_full_processed.pickle
|
||||
logfile=../log/log_FunBert_fullrcv_static.csv
|
||||
|
||||
python main_deep_learning.py $dataset --supervised --pretrained --posteriors --mbert --log-file $logfile
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
#dataset_path=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_noparallel_processed_run
|
||||
#logfile=../log/log_mBert_jrc_NEW.csv
|
||||
#
|
||||
#runs='0 1 2 3 4'
|
||||
#for run in $runs
|
||||
#do
|
||||
# dataset=$dataset_path$run.pickle
|
||||
# python main_mbert.py --dataset $dataset --log-file $logfile --nepochs=50
|
||||
#done
|
||||
|
||||
logfile=../log/log_mBert_fulljrc.csv
|
||||
dataset=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_full_processed.pickle
|
||||
python main_mbert.py --dataset $dataset --log-file $logfile --nepochs=50
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
#dataset_path=/home/moreo/CLESA/rcv2/rcv1-2_doclist_trByLang1000_teByLang1000_processed_run
|
||||
#logfile=../log/log_mBert_rcv_NEW.csv
|
||||
#
|
||||
#runs='0 1 2 3 4'
|
||||
#for run in $runs
|
||||
#do
|
||||
# dataset=$dataset_path$run.pickle
|
||||
# python main_mbert.py --dataset $dataset --log-file $logfile --nepochs=50
|
||||
#done
|
||||
|
||||
logfile=../log/log_mBert_fullrcv.csv
|
||||
dataset=/home/moreo/CLESA/rcv2/rcv1-2_doclist_full_processed.pickle
|
||||
python main_mbert.py --dataset $dataset --log-file $logfile --nepochs=30 --patience 3
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
dataset=/home/moreo/CLESA/jrc_acquis/jrc_doclist_1958-2005vs2006_all_top300_noparallel_processed_run0.pickle
|
||||
|
||||
######################################## POSTERIORS
|
||||
# Posteriors
|
||||
python main_multimodal_cls.py $dataset -P # + zscore
|
||||
python main_multimodal_cls.py $dataset -P -z # +l2norm
|
||||
python main_multimodal_cls.py $dataset -P -z --l2 # +feature weight
|
||||
|
||||
|
||||
######################################### WCE
|
||||
#WCE supervised
|
||||
python main_multimodal_cls.py $dataset -S # + zscore
|
||||
python main_multimodal_cls.py $dataset -S -z # +l2norm
|
||||
python main_multimodal_cls.py $dataset -S -z --l2 # +feature weight
|
||||
python main_multimodal_cls.py $dataset -S -z -r --l2 # + SIF - PCA
|
||||
|
||||
python main_multimodal_cls.py $dataset -S -z -p 250 --l2 # +feature weight + pca
|
||||
python main_multimodal_cls.py $dataset -S -z -r -p 250 --l2 # + SIF
|
||||
|
||||
python main_multimodal_cls.py $dataset -S -z --l2 --feat-weight ig # -feature weight
|
||||
python main_multimodal_cls.py $dataset -S -z -r --l2 --feat-weight ig
|
||||
python main_multimodal_cls.py $dataset -S -z -p 250 --l2 --feat-weight ig # + pca
|
||||
python main_multimodal_cls.py $dataset -S -z -r -p 250 --l2 --feat-weight ig
|
||||
|
||||
|
||||
python main_multimodal_cls.py $dataset -S -z --l2 --feat-weight pmi
|
||||
python main_multimodal_cls.py $dataset -S -z -r --l2 --feat-weight pmi
|
||||
python main_multimodal_cls.py $dataset -S -z -p 250 --l2 --feat-weight pmi
|
||||
python main_multimodal_cls.py $dataset -S -z -r -p 250 --l2 --feat-weight pmi
|
||||
|
||||
################################# MUSE
|
||||
|
||||
# MUSE unsupervised
|
||||
python main_multimodal_cls.py $dataset -U # + zscore
|
||||
python main_multimodal_cls.py $dataset -U -z # +l2norm
|
||||
python main_multimodal_cls.py $dataset -U -z --l2 # +feature weight
|
||||
python main_multimodal_cls.py $dataset -U -z -r --l2 # + SIF - PCA
|
||||
|
||||
python main_multimodal_cls.py $dataset -U -z --l2 --feat-weight ig # -feature weight + pca
|
||||
python main_multimodal_cls.py $dataset -U -z -r --l2 --feat-weight ig
|
||||
|
||||
python main_multimodal_cls.py $dataset -U -z --l2 --feat-weight pmi
|
||||
python main_multimodal_cls.py $dataset -U -z -r --l2 --feat-weight pmi
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
dataset=/home/moreo/CLESA/rcv2/rcv1-2_doclist_trByLang1000_teByLang1000_processed_run0.pickle
|
||||
|
||||
######################################## POSTERIORS
|
||||
# Posteriors
|
||||
python main_multimodal_cls.py $dataset -P # + zscore
|
||||
python main_multimodal_cls.py $dataset -P -z # +l2norm
|
||||
python main_multimodal_cls.py $dataset -P -z --l2 # +feature weight
|
||||
|
||||
|
||||
######################################### WCE
|
||||
#WCE supervised
|
||||
python main_multimodal_cls.py $dataset -S # + zscore
|
||||
python main_multimodal_cls.py $dataset -S -z # +l2norm
|
||||
python main_multimodal_cls.py $dataset -S -z --l2 # +feature weight
|
||||
python main_multimodal_cls.py $dataset -S -z -r --l2 # + SIF - PCA
|
||||
|
||||
python main_multimodal_cls.py $dataset -S -z -p 50 --l2 # +feature weight + pca
|
||||
python main_multimodal_cls.py $dataset -S -z -r -p 50 --l2 # + SIF
|
||||
|
||||
python main_multimodal_cls.py $dataset -S -z --l2 --feat-weight ig # -feature weight
|
||||
python main_multimodal_cls.py $dataset -S -z -r --l2 --feat-weight ig
|
||||
python main_multimodal_cls.py $dataset -S -z -p 50 --l2 --feat-weight ig # + pca
|
||||
python main_multimodal_cls.py $dataset -S -z -r -p 50 --l2 --feat-weight ig
|
||||
|
||||
|
||||
python main_multimodal_cls.py $dataset -S -z --l2 --feat-weight pmi
|
||||
python main_multimodal_cls.py $dataset -S -z -r --l2 --feat-weight pmi
|
||||
python main_multimodal_cls.py $dataset -S -z -p 50 --l2 --feat-weight pmi
|
||||
python main_multimodal_cls.py $dataset -S -z -r -p 50 --l2 --feat-weight pmi
|
||||
|
||||
################################# MUSE
|
||||
|
||||
# MUSE unsupervised
|
||||
python main_multimodal_cls.py $dataset -U # + zscore
|
||||
python main_multimodal_cls.py $dataset -U -z # +l2norm
|
||||
python main_multimodal_cls.py $dataset -U -z --l2 # +feature weight
|
||||
python main_multimodal_cls.py $dataset -U -z -r --l2 # + SIF - PCA
|
||||
|
||||
python main_multimodal_cls.py $dataset -U -z --l2 --feat-weight ig # -feature weight + pca
|
||||
python main_multimodal_cls.py $dataset -U -z -r --l2 --feat-weight ig
|
||||
|
||||
python main_multimodal_cls.py $dataset -U -z --l2 --feat-weight pmi
|
||||
python main_multimodal_cls.py $dataset -U -z -r --l2 --feat-weight pmi
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
dataset=/home/moreo/CLESA/rcv2/rcv1-2_doclist_full_processed.pickle
|
||||
seeds='1 2 3 4 5 6 7 8 9 10'
|
||||
for seed in $seeds
|
||||
do
|
||||
python main_deep_learning.py $dataset --log-file ../log/time_GRU.csv --supervised --nepochs 50 --seed $seed
|
||||
done
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
import numpy as np
|
||||
import time
|
||||
from scipy.sparse import issparse
|
||||
from sklearn.multiclass import OneVsRestClassifier
|
||||
from sklearn.model_selection import GridSearchCV
|
||||
from joblib import Parallel, delayed
|
||||
|
||||
|
||||
def _sort_if_sparse(X):
|
||||
if issparse(X) and not X.has_sorted_indices:
|
||||
X.sort_indices()
|
||||
|
||||
|
||||
def _joblib_transform_multiling(transformer, lX, n_jobs=-1):
|
||||
if n_jobs == 1:
|
||||
return {lang:transformer(lX[lang]) for lang in lX.keys()}
|
||||
else:
|
||||
langs = list(lX.keys())
|
||||
transformations = Parallel(n_jobs=n_jobs)(delayed(transformer)(lX[lang]) for lang in langs)
|
||||
return {lang: transformations[i] for i, lang in enumerate(langs)}
|
||||
|
||||
|
||||
class TrivialRejector:
|
||||
def fit(self, X, y):
|
||||
self.cats = y.shape[1]
|
||||
return self
|
||||
|
||||
def decision_function(self, X): return np.zeros((X.shape[0],self.cats))
|
||||
|
||||
def predict(self, X): return np.zeros((X.shape[0],self.cats))
|
||||
|
||||
def predict_proba(self, X): return np.zeros((X.shape[0],self.cats))
|
||||
|
||||
def best_params(self): return {}
|
||||
|
||||
|
||||
class NaivePolylingualClassifier:
|
||||
"""
|
||||
Is a mere set of independet MonolingualClassifiers
|
||||
"""
|
||||
def __init__(self, base_learner, parameters=None, n_jobs=-1):
|
||||
self.base_learner = base_learner
|
||||
self.parameters = parameters
|
||||
self.model = None
|
||||
self.n_jobs = n_jobs
|
||||
|
||||
def fit(self, lX, ly):
|
||||
"""
|
||||
trains the independent monolingual classifiers
|
||||
:param lX: a dictionary {language_label: X csr-matrix}
|
||||
:param ly: a dictionary {language_label: y np.array}
|
||||
:return: self
|
||||
"""
|
||||
tinit = time.time()
|
||||
assert set(lX.keys()) == set(ly.keys()), 'inconsistent language mappings in fit'
|
||||
langs = list(lX.keys())
|
||||
for lang in langs:
|
||||
_sort_if_sparse(lX[lang])
|
||||
|
||||
models = Parallel(n_jobs=self.n_jobs)\
|
||||
(delayed(MonolingualClassifier(self.base_learner, parameters=self.parameters).fit)((lX[lang]),ly[lang]) for lang in langs)
|
||||
|
||||
self.model = {lang: models[i] for i, lang in enumerate(langs)}
|
||||
self.empty_categories = {lang:self.model[lang].empty_categories for lang in langs}
|
||||
self.time = time.time() - tinit
|
||||
return self
|
||||
|
||||
def decision_function(self, lX):
|
||||
"""
|
||||
:param lX: a dictionary {language_label: X csr-matrix}
|
||||
:return: a dictionary of classification scores for each class
|
||||
"""
|
||||
assert self.model is not None, 'predict called before fit'
|
||||
assert set(lX.keys()).issubset(set(self.model.keys())), 'unknown languages requested in decision function'
|
||||
langs=list(lX.keys())
|
||||
scores = Parallel(n_jobs=self.n_jobs)(delayed(self.model[lang].decision_function)(lX[lang]) for lang in langs)
|
||||
return {lang:scores[i] for i,lang in enumerate(langs)}
|
||||
|
||||
def predict_proba(self, lX):
|
||||
"""
|
||||
:param lX: a dictionary {language_label: X csr-matrix}
|
||||
:return: a dictionary of probabilities that each document belongs to each class
|
||||
"""
|
||||
assert self.model is not None, 'predict called before fit'
|
||||
assert set(lX.keys()).issubset(set(self.model.keys())), 'unknown languages requested in decision function'
|
||||
langs=list(lX.keys())
|
||||
scores = Parallel(n_jobs=self.n_jobs, max_nbytes=None)(delayed(self.model[lang].predict_proba)(lX[lang]) for lang in langs)
|
||||
return {lang:scores[i] for i,lang in enumerate(langs)}
|
||||
|
||||
def predict(self, lX):
|
||||
"""
|
||||
:param lX: a dictionary {language_label: X csr-matrix}
|
||||
:return: a dictionary of predictions
|
||||
"""
|
||||
assert self.model is not None, 'predict called before fit'
|
||||
assert set(lX.keys()).issubset(set(self.model.keys())), 'unknown languages requested in predict'
|
||||
if self.n_jobs == 1:
|
||||
return {lang:self.model[lang].transform(lX[lang]) for lang in lX.keys()}
|
||||
else:
|
||||
langs = list(lX.keys())
|
||||
scores = Parallel(n_jobs=self.n_jobs)(delayed(self.model[lang].predict)(lX[lang]) for lang in langs)
|
||||
return {lang: scores[i] for i, lang in enumerate(langs)}
|
||||
|
||||
def best_params(self):
|
||||
return {l:model.best_params() for l,model in self.model.items()}
|
||||
|
||||
|
||||
class MonolingualClassifier:
|
||||
|
||||
def __init__(self, base_learner, parameters=None, n_jobs=-1):
|
||||
self.learner = base_learner
|
||||
self.parameters = parameters
|
||||
self.model = None
|
||||
self.n_jobs = n_jobs
|
||||
self.best_params_ = None
|
||||
|
||||
def fit(self, X, y):
|
||||
if X.shape[0] == 0:
|
||||
print('Warning: X has 0 elements, a trivial rejector will be created')
|
||||
self.model = TrivialRejector().fit(X,y)
|
||||
self.empty_categories = np.arange(y.shape[1])
|
||||
return self
|
||||
|
||||
tinit = time.time()
|
||||
_sort_if_sparse(X)
|
||||
self.empty_categories = np.argwhere(np.sum(y, axis=0)==0).flatten()
|
||||
|
||||
# multi-class format
|
||||
if len(y.shape) == 2:
|
||||
if self.parameters is not None:
|
||||
self.parameters = [{'estimator__' + key: params[key] for key in params.keys()}
|
||||
for params in self.parameters]
|
||||
self.model = OneVsRestClassifier(self.learner, n_jobs=self.n_jobs)
|
||||
else:
|
||||
self.model = self.learner
|
||||
raise NotImplementedError('not working as a base-classifier for funneling if there are gaps in '
|
||||
'the labels across languages')
|
||||
|
||||
# parameter optimization?
|
||||
if self.parameters:
|
||||
print('debug: optimizing parameters:', self.parameters)
|
||||
self.model = GridSearchCV(self.model, param_grid=self.parameters, refit=True, cv=5, n_jobs=self.n_jobs,
|
||||
error_score=0, verbose=10)
|
||||
|
||||
# print(f'fitting: {self.model} on matrices of shape X={X.shape} Y={y.shape}')
|
||||
print(f'fitting: Mono-lingual Classifier on matrices of shape X={X.shape} Y={y.shape}')
|
||||
self.model.fit(X, y)
|
||||
if isinstance(self.model, GridSearchCV):
|
||||
self.best_params_ = self.model.best_params_
|
||||
print('best parameters: ', self.best_params_)
|
||||
self.time = time.time()-tinit
|
||||
return self
|
||||
|
||||
def decision_function(self, X):
|
||||
assert self.model is not None, 'predict called before fit'
|
||||
_sort_if_sparse(X)
|
||||
return self.model.decision_function(X)
|
||||
|
||||
def predict_proba(self, X):
|
||||
assert self.model is not None, 'predict called before fit'
|
||||
assert hasattr(self.model, 'predict_proba'), 'the probability predictions are not enabled in this model'
|
||||
_sort_if_sparse(X)
|
||||
return self.model.predict_proba(X)
|
||||
|
||||
def predict(self, X):
|
||||
assert self.model is not None, 'predict called before fit'
|
||||
_sort_if_sparse(X)
|
||||
return self.model.predict(X)
|
||||
|
||||
def best_params(self):
|
||||
return self.best_params_
|
||||
|
|
@ -1,863 +0,0 @@
|
|||
from torch.optim.lr_scheduler import StepLR
|
||||
from torch.utils.data import DataLoader
|
||||
from data.tsr_function__ import get_tsr_matrix, get_supervised_matrix, pointwise_mutual_information, information_gain
|
||||
from embeddings.embeddings import FastTextMUSE
|
||||
from embeddings.supervised import supervised_embeddings_tfidf, zscores
|
||||
from learning.learners import NaivePolylingualClassifier, MonolingualClassifier, _joblib_transform_multiling
|
||||
from sklearn.decomposition import PCA
|
||||
from scipy.sparse import hstack
|
||||
from util_transformers.StandardizeTransformer import StandardizeTransformer
|
||||
from util.SIF_embed import remove_pc
|
||||
from sklearn.preprocessing import normalize
|
||||
from scipy.sparse import csr_matrix
|
||||
from models.mBert import *
|
||||
from models.lstm_class import *
|
||||
from util.csv_log import CSVLog
|
||||
from util.file import get_file_name, create_if_not_exist, exists
|
||||
from util.early_stop import EarlyStopping
|
||||
from util.common import *
|
||||
import pickle
|
||||
import time
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Data Processing
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
class FeatureWeight:
|
||||
|
||||
def __init__(self, weight='tfidf', agg='mean'):
|
||||
assert weight in ['tfidf', 'pmi', 'ig'] or callable(
|
||||
weight), 'weight should either be "tfidf" or a callable function'
|
||||
assert agg in ['mean', 'max'], 'aggregation function should either be "mean" or "max"'
|
||||
self.weight = weight
|
||||
self.agg = agg
|
||||
self.fitted = False
|
||||
if weight == 'pmi':
|
||||
self.weight = pointwise_mutual_information
|
||||
elif weight == 'ig':
|
||||
self.weight = information_gain
|
||||
|
||||
def fit(self, lX, ly):
|
||||
if not self.fitted:
|
||||
if self.weight == 'tfidf':
|
||||
self.lF = {l: np.ones(X.shape[1]) for l, X in lX.items()}
|
||||
else:
|
||||
self.lF = {}
|
||||
for l in lX.keys():
|
||||
X, y = lX[l], ly[l]
|
||||
|
||||
print(f'getting supervised cell-matrix lang {l}')
|
||||
tsr_matrix = get_tsr_matrix(get_supervised_matrix(X, y), tsr_score_funtion=self.weight)
|
||||
if self.agg == 'max':
|
||||
F = tsr_matrix.max(axis=0)
|
||||
elif self.agg == 'mean':
|
||||
F = tsr_matrix.mean(axis=0)
|
||||
self.lF[l] = F
|
||||
self.fitted = True
|
||||
return self
|
||||
|
||||
def transform(self, lX):
|
||||
return {lang: csr_matrix.multiply(lX[lang], self.lF[lang]) for lang in lX.keys()}
|
||||
|
||||
def fit_transform(self, lX, ly):
|
||||
return self.fit(lX, ly).transform(lX)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# View Generators (aka first-tier learners)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
class PosteriorProbabilitiesEmbedder:
|
||||
|
||||
def __init__(self, first_tier_learner, first_tier_parameters=None, l2=True, n_jobs=-1, is_training=True, storing_path='../dumps/'):
|
||||
self.fist_tier_learner = first_tier_learner
|
||||
self.fist_tier_parameters = first_tier_parameters
|
||||
self.l2 = l2
|
||||
self.n_jobs = n_jobs
|
||||
self.doc_projector = NaivePolylingualClassifier(
|
||||
self.fist_tier_learner, self.fist_tier_parameters, n_jobs=n_jobs
|
||||
)
|
||||
self.requires_tfidf = True
|
||||
self.storing_path = storing_path
|
||||
self.is_training = is_training
|
||||
|
||||
def fit(self, lX, lY, lV=None, called_by_viewgen=False):
|
||||
# if exists(self.storing_path + '/tr') or exists(self.storing_path + '/te'):
|
||||
# print(f'NB: Avoid fitting {self.storing_path.split("/")[2]} since we have already pre-computed results')
|
||||
# return self
|
||||
if not called_by_viewgen:
|
||||
# Avoid printing if method is called by another View Gen (e.g., GRU ViewGen)
|
||||
print('### Posterior Probabilities View Generator (X)')
|
||||
print('fitting the projectors... {}'.format(lX.keys()))
|
||||
self.doc_projector.fit(lX, lY)
|
||||
return self
|
||||
|
||||
def transform(self, lX):
|
||||
# if dir exist, load and return already computed results
|
||||
# _endpoint = 'tr' if self.is_training else 'te'
|
||||
# _actual_path = self.storing_path + '/' + _endpoint
|
||||
# if exists(_actual_path):
|
||||
# print('NB: loading pre-computed results!')
|
||||
# with open(_actual_path + '/X.pickle', 'rb') as infile:
|
||||
# self.is_training = False
|
||||
# return pickle.load(infile)
|
||||
|
||||
lZ = self.predict_proba(lX)
|
||||
lZ = _normalize(lZ, self.l2)
|
||||
# create dir and dump computed results
|
||||
# create_if_not_exist(_actual_path)
|
||||
# with open(_actual_path + '/X.pickle', 'wb') as outfile:
|
||||
# pickle.dump(lZ, outfile)
|
||||
self.is_training = False
|
||||
return lZ
|
||||
|
||||
def fit_transform(self, lX, ly=None, lV=None):
|
||||
return self.fit(lX, ly).transform(lX)
|
||||
|
||||
def best_params(self):
|
||||
return self.doc_projector.best_params()
|
||||
|
||||
def predict(self, lX, ly=None):
|
||||
return self.doc_projector.predict(lX)
|
||||
|
||||
def predict_proba(self, lX, ly=None):
|
||||
print(f'generating posterior probabilities for {sum([X.shape[0] for X in lX.values()])} documents')
|
||||
lZ = self.doc_projector.predict_proba(lX)
|
||||
return lZ
|
||||
|
||||
|
||||
class MuseEmbedder:
|
||||
|
||||
def __init__(self, path, lV=None, l2=True, n_jobs=-1, featureweight=FeatureWeight(), sif=False):
|
||||
self.path = path
|
||||
self.lV = lV
|
||||
self.l2 = l2
|
||||
self.n_jobs = n_jobs
|
||||
self.featureweight = featureweight
|
||||
self.sif = sif
|
||||
self.requires_tfidf = True
|
||||
|
||||
def fit(self, lX, ly, lV=None):
|
||||
assert lV is not None or self.lV is not None, 'lV not specified'
|
||||
print('### MUSE View Generator (M)')
|
||||
print(f'Loading fastText pretrained vectors for languages {list(lX.keys())}...')
|
||||
self.langs = sorted(lX.keys())
|
||||
self.MUSE = load_muse_embeddings(self.path, self.langs, self.n_jobs)
|
||||
lWordList = {l: self._get_wordlist_from_word2index(lV[l]) for l in self.langs}
|
||||
self.MUSE = {l: Muse.extract(lWordList[l]).numpy() for l, Muse in self.MUSE.items()}
|
||||
self.featureweight.fit(lX, ly)
|
||||
return self
|
||||
|
||||
def transform(self, lX):
|
||||
MUSE = self.MUSE
|
||||
lX = self.featureweight.transform(lX)
|
||||
XdotMUSE = Parallel(n_jobs=self.n_jobs)(
|
||||
delayed(XdotM)(lX[lang], MUSE[lang], self.sif) for lang in self.langs)
|
||||
lMuse = {l: XdotMUSE[i] for i, l in enumerate(self.langs)}
|
||||
lMuse = _normalize(lMuse, self.l2)
|
||||
return lMuse
|
||||
|
||||
def fit_transform(self, lX, ly, lV):
|
||||
return self.fit(lX, ly, lV).transform(lX)
|
||||
|
||||
def _get_wordlist_from_word2index(self, word2index):
|
||||
return list(zip(*sorted(word2index.items(), key=lambda x: x[1])))[0]
|
||||
|
||||
def _get_output_dim(self):
|
||||
return self.MUSE['da'].shape[1]
|
||||
|
||||
|
||||
class WordClassEmbedder:
|
||||
|
||||
def __init__(self, l2=True, n_jobs=-1, max_label_space=300, featureweight=FeatureWeight(), sif=False):
|
||||
self.n_jobs = n_jobs
|
||||
self.l2 = l2
|
||||
self.max_label_space = max_label_space
|
||||
self.featureweight = featureweight
|
||||
self.sif = sif
|
||||
self.requires_tfidf = True
|
||||
|
||||
def fit(self, lX, ly, lV=None):
|
||||
print('### WCE View Generator (M)')
|
||||
print('Computing supervised embeddings...')
|
||||
self.langs = sorted(lX.keys())
|
||||
WCE = Parallel(n_jobs=self.n_jobs)(
|
||||
delayed(word_class_embedding_matrix)(lX[lang], ly[lang], self.max_label_space) for lang in self.langs
|
||||
)
|
||||
self.lWCE = {l: WCE[i] for i, l in enumerate(self.langs)}
|
||||
self.featureweight.fit(lX, ly)
|
||||
return self
|
||||
|
||||
def transform(self, lX):
|
||||
lWCE = self.lWCE
|
||||
lX = self.featureweight.transform(lX)
|
||||
XdotWCE = Parallel(n_jobs=self.n_jobs)(
|
||||
delayed(XdotM)(lX[lang], lWCE[lang], self.sif) for lang in self.langs
|
||||
)
|
||||
lwce = {l: XdotWCE[i] for i, l in enumerate(self.langs)}
|
||||
lwce = _normalize(lwce, self.l2)
|
||||
return lwce
|
||||
|
||||
def fit_transform(self, lX, ly, lV=None):
|
||||
return self.fit(lX, ly).transform(lX)
|
||||
|
||||
def _get_output_dim(self):
|
||||
return 73 # TODO !
|
||||
|
||||
|
||||
class MBertEmbedder:
|
||||
|
||||
def __init__(self, doc_embed_path=None, patience=10, checkpoint_dir='../hug_checkpoint/', path_to_model=None,
|
||||
nC=None, avoid_loading=False):
|
||||
self.doc_embed_path = doc_embed_path
|
||||
self.patience = patience
|
||||
self.checkpoint_dir = checkpoint_dir
|
||||
self.fitted = False
|
||||
self.requires_tfidf = False
|
||||
self.avoid_loading = avoid_loading
|
||||
if path_to_model is None:
|
||||
self.model = None
|
||||
else:
|
||||
config = BertConfig.from_pretrained('bert-base-multilingual-cased', output_hidden_states=True,
|
||||
num_labels=nC)
|
||||
if self.avoid_loading:
|
||||
self.model = None
|
||||
else:
|
||||
self.model = BertForSequenceClassification.from_pretrained(path_to_model, config=config).cuda() # TODO: setting model to None in order to avoid loading it onto gpu if we have already pre-computed results!
|
||||
self.fitted = True
|
||||
|
||||
def fit(self, lX, ly, lV=None, seed=0, nepochs=200, lr=1e-5, val_epochs=1):
|
||||
print('### mBERT View Generator (B)')
|
||||
if self.fitted is True:
|
||||
print('Bert model already fitted!')
|
||||
return self
|
||||
|
||||
print('Fine-tune mBert on the given dataset.')
|
||||
l_tokenized_tr = do_tokenization(lX, max_len=512)
|
||||
l_split_tr, l_split_tr_target, l_split_va, l_split_val_target = get_tr_val_split(l_tokenized_tr, ly,
|
||||
val_prop=0.2, max_val=2000,
|
||||
seed=seed)
|
||||
|
||||
tr_dataset = TrainingDataset(l_split_tr, l_split_tr_target)
|
||||
va_dataset = TrainingDataset(l_split_va, l_split_val_target)
|
||||
tr_dataloader = DataLoader(tr_dataset, batch_size=64, shuffle=True)
|
||||
va_dataloader = DataLoader(va_dataset, batch_size=64, shuffle=True)
|
||||
|
||||
nC = tr_dataset.get_nclasses()
|
||||
model = get_model(nC)
|
||||
model = model.cuda()
|
||||
criterion = torch.nn.BCEWithLogitsLoss().cuda()
|
||||
optim = init_optimizer(model, lr=lr, weight_decay=0.01)
|
||||
lr_scheduler = StepLR(optim, step_size=25, gamma=0.1)
|
||||
early_stop = EarlyStopping(model, optimizer=optim, patience=self.patience,
|
||||
checkpoint=self.checkpoint_dir,
|
||||
is_bert=True)
|
||||
|
||||
# Training loop
|
||||
logfile = '../log/log_mBert_extractor.csv'
|
||||
method_name = 'mBert_feature_extractor'
|
||||
|
||||
tinit = time()
|
||||
lang_ids = va_dataset.lang_ids
|
||||
for epoch in range(1, nepochs + 1):
|
||||
print('# Start Training ...')
|
||||
train(model, tr_dataloader, epoch, criterion, optim, method_name, tinit, logfile)
|
||||
lr_scheduler.step() # reduces the learning rate # TODO arg epoch?
|
||||
|
||||
# Validation
|
||||
macrof1 = test(model, va_dataloader, lang_ids, tinit, epoch, logfile, criterion, 'va')
|
||||
early_stop(macrof1, epoch)
|
||||
|
||||
if early_stop.STOP:
|
||||
print('[early-stop] STOP')
|
||||
break
|
||||
|
||||
model = early_stop.restore_checkpoint()
|
||||
self.model = model.cuda()
|
||||
|
||||
if val_epochs > 0:
|
||||
print(f'running last {val_epochs} training epochs on the validation set')
|
||||
for val_epoch in range(1, val_epochs + 1):
|
||||
train(self.model, va_dataloader, epoch + val_epoch, criterion, optim, method_name, tinit, logfile)
|
||||
|
||||
self.fitted = True
|
||||
return self
|
||||
|
||||
def transform(self, lX):
|
||||
assert self.fitted is True, 'Calling transform without any initialized model! - call init first or on init' \
|
||||
'pass the "path_to_model" arg.'
|
||||
print('Obtaining document embeddings from pretrained mBert ')
|
||||
l_tokenized_X = do_tokenization(lX, max_len=512, verbose=True)
|
||||
feat_dataset = ExtractorDataset(l_tokenized_X)
|
||||
feat_lang_ids = feat_dataset.lang_ids
|
||||
dataloader = DataLoader(feat_dataset, batch_size=64)
|
||||
all_batch_embeddings, id2lang = feature_extractor(dataloader, feat_lang_ids, self.model)
|
||||
return all_batch_embeddings
|
||||
|
||||
def fit_transform(self, lX, ly, lV=None):
|
||||
return self.fit(lX, ly).transform(lX)
|
||||
|
||||
|
||||
class RecurrentEmbedder:
|
||||
|
||||
def __init__(self, pretrained, supervised, multilingual_dataset, options, concat=False, lr=1e-3,
|
||||
we_path='../embeddings', hidden_size=512, sup_drop=0.5, posteriors=False, patience=10,
|
||||
test_each=0, checkpoint_dir='../checkpoint', model_path=None, n_jobs=-1):
|
||||
self.pretrained = pretrained
|
||||
self.supervised = supervised
|
||||
self.concat = concat
|
||||
self.requires_tfidf = False
|
||||
self.multilingual_dataset = multilingual_dataset
|
||||
self.model = None
|
||||
self.we_path = we_path
|
||||
self.langs = multilingual_dataset.langs()
|
||||
self.hidden_size = hidden_size
|
||||
self.sup_drop = sup_drop
|
||||
self.posteriors = posteriors
|
||||
self.patience = patience
|
||||
self.checkpoint_dir = checkpoint_dir
|
||||
self.test_each = test_each
|
||||
self.options = options
|
||||
self.seed = options.seed
|
||||
self.model_path = model_path
|
||||
self.n_jobs = n_jobs
|
||||
self.is_trained = False
|
||||
|
||||
## INIT MODEL for training
|
||||
self.lXtr, self.lytr = self.multilingual_dataset.training(target_as_csr=True)
|
||||
self.lXte, self.lyte = self.multilingual_dataset.test(target_as_csr=True)
|
||||
self.nC = self.lyte[self.langs[0]].shape[1]
|
||||
lpretrained, self.lpretrained_vocabulary = self._load_pretrained_embeddings(self.we_path, self.langs)
|
||||
self.multilingual_index = MultilingualIndex()
|
||||
self.multilingual_index.index(self.lXtr, self.lytr, self.lXte, self.lpretrained_vocabulary)
|
||||
self.multilingual_index.train_val_split(val_prop=0.2, max_val=2000, seed=self.seed)
|
||||
self.multilingual_index.embedding_matrices(lpretrained, self.supervised)
|
||||
|
||||
if model_path is not None:
|
||||
self.is_trained = True
|
||||
self.model = torch.load(model_path)
|
||||
else:
|
||||
self.model = self._init_Net()
|
||||
|
||||
self.optim = init_optimizer(self.model, lr=lr)
|
||||
self.criterion = torch.nn.BCEWithLogitsLoss().cuda()
|
||||
self.lr_scheduler = StepLR(self.optim, step_size=25, gamma=0.5)
|
||||
self.early_stop = EarlyStopping(self.model, optimizer=self.optim, patience=self.patience,
|
||||
checkpoint=f'{self.checkpoint_dir}/gru_viewgen_-{get_file_name(self.options.dataset)}')
|
||||
|
||||
def fit(self, lX, ly, lV=None, batch_size=128, nepochs=200, val_epochs=1):
|
||||
print('### Gated Recurrent Unit View Generator (G)')
|
||||
if self.model is None:
|
||||
print('TODO: Init model!')
|
||||
if not self.is_trained:
|
||||
# Batchify input
|
||||
self.multilingual_index.train_val_split(val_prop=0.2, max_val=2000, seed=self.seed)
|
||||
l_train_index, l_train_target = self.multilingual_index.l_train()
|
||||
l_val_index, l_val_target = self.multilingual_index.l_val()
|
||||
l_test_index = self.multilingual_index.l_test_index()
|
||||
batcher_train = BatchGRU(batch_size, batches_per_epoch=batch_size, languages=self.langs,
|
||||
lpad=self.multilingual_index.l_pad())
|
||||
batcher_eval = BatchGRU(batch_size, batches_per_epoch=batch_size, languages=self.langs,
|
||||
lpad=self.multilingual_index.l_pad())
|
||||
|
||||
# Train loop
|
||||
print('Start training')
|
||||
method_name = 'gru_view_generator'
|
||||
logfile = init_logfile_nn(method_name, self.options)
|
||||
tinit = time.time()
|
||||
for epoch in range(1, nepochs + 1):
|
||||
train_gru(model=self.model, batcher=batcher_train, ltrain_index=l_train_index, lytr=l_train_target,
|
||||
tinit=tinit, logfile=logfile, criterion=self.criterion, optim=self.optim,
|
||||
epoch=epoch, method_name=method_name, opt=self.options, ltrain_posteriors=None,
|
||||
ltrain_bert=None)
|
||||
self.lr_scheduler.step()
|
||||
|
||||
# validation step
|
||||
macrof1 = test_gru(self.model, batcher_eval, l_val_index, None, None, l_val_target, tinit, epoch,
|
||||
logfile, self.criterion, 'va')
|
||||
|
||||
self.early_stop(macrof1, epoch)
|
||||
if self.test_each > 0:
|
||||
test_gru(self.model, batcher_eval, l_test_index, None, None, self.lyte, tinit, epoch,
|
||||
logfile, self.criterion, 'te')
|
||||
|
||||
if self.early_stop.STOP:
|
||||
print('[early-stop] STOP')
|
||||
print('Restoring best model...')
|
||||
break
|
||||
|
||||
self.model = self.early_stop.restore_checkpoint()
|
||||
print(f'running last {val_epochs} training epochs on the validation set')
|
||||
for val_epoch in range(1, val_epochs+1):
|
||||
batcher_train.init_offset()
|
||||
train_gru(model=self.model, batcher=batcher_train, ltrain_index=l_train_index, lytr=l_train_target,
|
||||
tinit=tinit, logfile=logfile, criterion=self.criterion, optim=self.optim,
|
||||
epoch=epoch, method_name=method_name, opt=self.options, ltrain_posteriors=None,
|
||||
ltrain_bert=None)
|
||||
self.is_trained = True
|
||||
|
||||
return self
|
||||
|
||||
def transform(self, lX, batch_size=64):
|
||||
lX = self.multilingual_index.get_indexed(lX, self.lpretrained_vocabulary)
|
||||
lX = self._get_doc_embeddings(lX, batch_size)
|
||||
return lX
|
||||
|
||||
def fit_transform(self, lX, ly, lV=None):
|
||||
return self.fit(lX, ly).transform(lX)
|
||||
|
||||
def _get_doc_embeddings(self, lX, batch_size):
|
||||
assert self.is_trained, 'Model is not trained, cannot call transform before fitting the model!'
|
||||
print('Generating document embeddings via GRU')
|
||||
_lX = {}
|
||||
|
||||
l_devel_target = self.multilingual_index.l_devel_target()
|
||||
|
||||
# show_gpu('RNN init at extraction')
|
||||
for idx, (batch, post, target, lang) in enumerate(batchify(lX, None, l_devel_target,
|
||||
batch_size, self.multilingual_index.l_pad())):
|
||||
if lang not in _lX.keys():
|
||||
_lX[lang] = self.model.get_embeddings(batch, lang)
|
||||
else:
|
||||
_lX[lang] = np.concatenate((_lX[lang], self.model.get_embeddings(batch, lang)), axis=0)
|
||||
# show_gpu('RNN after batch pred at extraction')
|
||||
return _lX
|
||||
|
||||
# loads the MUSE embeddings if requested, or returns empty dictionaries otherwise
|
||||
def _load_pretrained_embeddings(self, we_path, langs):
|
||||
lpretrained = lpretrained_vocabulary = self._none_dict(langs)
|
||||
lpretrained = load_muse_embeddings(we_path, langs, n_jobs=self.n_jobs)
|
||||
lpretrained_vocabulary = {l: lpretrained[l].vocabulary() for l in langs}
|
||||
return lpretrained, lpretrained_vocabulary
|
||||
|
||||
def _none_dict(self, langs):
|
||||
return {l:None for l in langs}
|
||||
|
||||
# instantiates the net, initializes the model parameters, and sets embeddings trainable if requested
|
||||
def _init_Net(self, xavier_uniform=True):
|
||||
model = RNNMultilingualClassifier(
|
||||
output_size=self.nC,
|
||||
hidden_size=self.hidden_size,
|
||||
lvocab_size=self.multilingual_index.l_vocabsize(),
|
||||
learnable_length=0,
|
||||
lpretrained=self.multilingual_index.l_embeddings(),
|
||||
drop_embedding_range=self.multilingual_index.sup_range,
|
||||
drop_embedding_prop=self.sup_drop,
|
||||
post_probabilities=self.posteriors
|
||||
)
|
||||
return model.cuda()
|
||||
|
||||
|
||||
class DocEmbedderList:
|
||||
|
||||
def __init__(self, *embedder_list, aggregation='concat'):
|
||||
assert aggregation in {'concat', 'mean'}, 'unknown aggregation mode, valid are "concat" and "mean"'
|
||||
if len(embedder_list) == 0:
|
||||
embedder_list = []
|
||||
self.embedders = embedder_list
|
||||
self.aggregation = aggregation
|
||||
print(f'Aggregation mode: {self.aggregation}')
|
||||
|
||||
def fit(self, lX, ly, lV=None, tfidf=None):
|
||||
for transformer in self.embedders:
|
||||
_lX = lX
|
||||
if transformer.requires_tfidf:
|
||||
_lX = tfidf
|
||||
transformer.fit(_lX, ly, lV)
|
||||
return self
|
||||
|
||||
def transform(self, lX, tfidf=None):
|
||||
if self.aggregation == 'concat':
|
||||
return self.transform_concat(lX, tfidf)
|
||||
elif self.aggregation == 'mean':
|
||||
return self.transform_mean(lX, tfidf)
|
||||
|
||||
def transform_concat(self, lX, tfidf):
|
||||
if len(self.embedders) == 1:
|
||||
if self.embedders[0].requires_tfidf:
|
||||
lX = tfidf
|
||||
return self.embedders[0].transform(lX)
|
||||
|
||||
some_sparse = False
|
||||
langs = sorted(lX.keys())
|
||||
|
||||
lZparts = {l: [] for l in langs}
|
||||
for transformer in self.embedders:
|
||||
_lX = lX
|
||||
if transformer.requires_tfidf:
|
||||
_lX = tfidf
|
||||
lZ = transformer.transform(_lX)
|
||||
for l in langs:
|
||||
Z = lZ[l]
|
||||
some_sparse = some_sparse or issparse(Z)
|
||||
lZparts[l].append(Z)
|
||||
|
||||
hstacker = hstack if some_sparse else np.hstack
|
||||
return {l: hstacker(lZparts[l]) for l in langs}
|
||||
|
||||
def transform_mean(self, lX, tfidf):
|
||||
if len(self.embedders) == 1:
|
||||
if self.embedders[0].requires_tfidf:
|
||||
lX = tfidf
|
||||
return self.embedders[0].transform(lX)
|
||||
|
||||
langs = sorted(lX.keys())
|
||||
lZparts = {l: None for l in langs}
|
||||
|
||||
for transformer in self.embedders:
|
||||
_lX = lX
|
||||
if transformer.requires_tfidf:
|
||||
_lX = tfidf
|
||||
lZ = transformer.transform(_lX)
|
||||
for l in langs:
|
||||
Z = lZ[l]
|
||||
if lZparts[l] is None:
|
||||
lZparts[l] = Z
|
||||
else:
|
||||
lZparts[l] += Z
|
||||
|
||||
n_transformers = len(self.embedders)
|
||||
|
||||
return {l: lZparts[l] / n_transformers for l in langs}
|
||||
|
||||
def fit_transform(self, lX, ly, lV=None, tfidf=None):
|
||||
return self.fit(lX, ly, lV, tfidf).transform(lX, tfidf)
|
||||
|
||||
def best_params(self):
|
||||
return {'todo'}
|
||||
|
||||
def append(self, embedder):
|
||||
self.embedders.append(embedder)
|
||||
|
||||
|
||||
class FeatureSet2Posteriors:
|
||||
def __init__(self, transformer, method_id, requires_tfidf=False, l2=True, n_jobs=-1, storing_path='../dumps/'):
|
||||
self.transformer = transformer
|
||||
self.l2 = l2
|
||||
self.n_jobs = n_jobs
|
||||
self.prob_classifier = MetaClassifier(
|
||||
SVC(kernel='rbf', gamma='auto', probability=True, cache_size=1000, random_state=1), n_jobs=n_jobs)
|
||||
self.requires_tfidf = requires_tfidf
|
||||
|
||||
self.storing_path = storing_path
|
||||
self.is_training = True
|
||||
self.method_id = method_id
|
||||
|
||||
def fit(self, lX, ly, lV=None):
|
||||
if exists(self.storing_path + '/tr') or exists(self.storing_path + '/te'):
|
||||
print(f'NB: Avoid fitting {self.storing_path.split("/")[2]} since we have already pre-computed results')
|
||||
return self
|
||||
|
||||
if lV is None and hasattr(self.transformer, 'lV'):
|
||||
lV = self.transformer.lV
|
||||
lZ = self.transformer.fit_transform(lX, ly, lV)
|
||||
self.prob_classifier.fit(lZ, ly)
|
||||
return self
|
||||
|
||||
def transform(self, lX):
|
||||
# if dir exist, load and return already computed results
|
||||
# _endpoint = 'tr' if self.is_training else 'te'
|
||||
# _actual_path = self.storing_path + '/' + _endpoint
|
||||
# if exists(_actual_path):
|
||||
# print('NB: loading pre-computed results!')
|
||||
# with open(_actual_path + '/' + self.method_id + '.pickle', 'rb') as infile:
|
||||
# self.is_training = False
|
||||
# return pickle.load(infile)
|
||||
|
||||
lP = self.predict_proba(lX)
|
||||
lP = _normalize(lP, self.l2)
|
||||
# create dir and dump computed results
|
||||
# create_if_not_exist(_actual_path)
|
||||
# with open(_actual_path + '/' + self.method_id + '.pickle', 'wb') as outfile:
|
||||
# pickle.dump(lP, outfile)
|
||||
self.is_training = False
|
||||
return lP
|
||||
|
||||
def fit_transform(self, lX, ly, lV):
|
||||
return self.fit(lX, ly, lV).transform(lX)
|
||||
|
||||
def predict(self, lX, ly=None):
|
||||
lZ = self.transformer.transform(lX)
|
||||
return self.prob_classifier.predict(lZ)
|
||||
|
||||
def predict_proba(self, lX, ly=None):
|
||||
lZ = self.transformer.transform(lX)
|
||||
return self.prob_classifier.predict_proba(lZ)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Meta-Classifier (aka second-tier learner)
|
||||
# ------------------------------------------------------------------
|
||||
class MetaClassifier:
|
||||
|
||||
def __init__(self, meta_learner, meta_parameters=None, n_jobs=-1, standardize_range=None):
|
||||
self.n_jobs = n_jobs
|
||||
self.model = MonolingualClassifier(base_learner=meta_learner, parameters=meta_parameters, n_jobs=n_jobs)
|
||||
self.standardize_range = standardize_range
|
||||
|
||||
def fit(self, lZ, ly):
|
||||
tinit = time.time()
|
||||
Z, y = self.stack(lZ, ly)
|
||||
|
||||
self.standardizer = StandardizeTransformer(range=self.standardize_range)
|
||||
Z = self.standardizer.fit_transform(Z)
|
||||
|
||||
print('fitting the Z-space of shape={}'.format(Z.shape))
|
||||
self.model.fit(Z, y)
|
||||
self.time = time.time() - tinit
|
||||
|
||||
def stack(self, lZ, ly=None):
|
||||
langs = list(lZ.keys())
|
||||
Z = np.vstack([lZ[lang] for lang in langs]) # Z is the language independent space
|
||||
if ly is not None:
|
||||
y = np.vstack([ly[lang] for lang in langs])
|
||||
return Z, y
|
||||
else:
|
||||
return Z
|
||||
|
||||
def predict(self, lZ, ly=None):
|
||||
lZ = _joblib_transform_multiling(self.standardizer.transform, lZ, n_jobs=self.n_jobs)
|
||||
return _joblib_transform_multiling(self.model.predict, lZ, n_jobs=self.n_jobs)
|
||||
|
||||
def predict_proba(self, lZ, ly=None):
|
||||
lZ = _joblib_transform_multiling(self.standardizer.transform, lZ, n_jobs=self.n_jobs)
|
||||
return _joblib_transform_multiling(self.model.predict_proba, lZ, n_jobs=self.n_jobs)
|
||||
|
||||
def best_params(self):
|
||||
return self.model.best_params()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Ensembling (aka Funnelling)
|
||||
# ------------------------------------------------------------------
|
||||
class Funnelling:
|
||||
def __init__(self,
|
||||
vectorizer: TfidfVectorizerMultilingual,
|
||||
first_tier: DocEmbedderList,
|
||||
meta: MetaClassifier):
|
||||
self.vectorizer = vectorizer
|
||||
self.first_tier = first_tier
|
||||
self.meta = meta
|
||||
self.n_jobs = meta.n_jobs
|
||||
|
||||
def fit(self, lX, ly, target_lang=None):
|
||||
if target_lang is not None:
|
||||
LX = lX.copy()
|
||||
LX.update(target_lang)
|
||||
self.vectorizer.fit(LX)
|
||||
tfidf_lX = self.vectorizer.transform(lX)
|
||||
else:
|
||||
tfidf_lX = self.vectorizer.fit_transform(lX, ly)
|
||||
lV = self.vectorizer.vocabulary()
|
||||
print('## Fitting first-tier learners!')
|
||||
lZ = self.first_tier.fit_transform(lX, ly, lV, tfidf=tfidf_lX)
|
||||
print('## Fitting meta-learner!')
|
||||
self.meta.fit(lZ, ly)
|
||||
|
||||
def predict(self, lX, ly=None):
|
||||
tfidf_lX = self.vectorizer.transform(lX)
|
||||
lZ = self.first_tier.transform(lX, tfidf=tfidf_lX)
|
||||
ly_ = self.meta.predict(lZ)
|
||||
return ly_
|
||||
|
||||
def best_params(self):
|
||||
return {'1st-tier': self.first_tier.best_params(),
|
||||
'meta': self.meta.best_params()}
|
||||
|
||||
|
||||
class Voting:
|
||||
def __init__(self, *prob_classifiers):
|
||||
assert all([hasattr(p, 'predict_proba') for p in prob_classifiers]), 'not all classifiers are probabilistic'
|
||||
self.prob_classifiers = prob_classifiers
|
||||
|
||||
def fit(self, lX, ly, lV=None):
|
||||
for classifier in self.prob_classifiers:
|
||||
classifier.fit(lX, ly, lV)
|
||||
|
||||
def predict(self, lX, ly=None):
|
||||
lP = {l: [] for l in lX.keys()}
|
||||
for classifier in self.prob_classifiers:
|
||||
lPi = classifier.predict_proba(lX)
|
||||
for l in lX.keys():
|
||||
lP[l].append(lPi[l])
|
||||
|
||||
lP = {l: np.stack(Plist).mean(axis=0) for l, Plist in lP.items()}
|
||||
ly = {l: P > 0.5 for l, P in lP.items()}
|
||||
|
||||
return ly
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# HELPERS
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
def load_muse_embeddings(we_path, langs, n_jobs=-1):
|
||||
MUSE = Parallel(n_jobs=n_jobs)(
|
||||
delayed(FastTextMUSE)(we_path, lang) for lang in langs
|
||||
)
|
||||
return {l: MUSE[i] for i, l in enumerate(langs)}
|
||||
|
||||
|
||||
def word_class_embedding_matrix(X, Y, max_label_space=300):
|
||||
WCE = supervised_embeddings_tfidf(X, Y)
|
||||
WCE = zscores(WCE, axis=0)
|
||||
|
||||
nC = Y.shape[1]
|
||||
if nC > max_label_space:
|
||||
print(f'supervised matrix has more dimensions ({nC}) than the allowed limit {max_label_space}. '
|
||||
f'Applying PCA(n_components={max_label_space})')
|
||||
pca = PCA(n_components=max_label_space)
|
||||
WCE = pca.fit(WCE).transform(WCE)
|
||||
|
||||
return WCE
|
||||
|
||||
|
||||
def XdotM(X, M, sif):
|
||||
E = X.dot(M)
|
||||
if sif:
|
||||
# print("removing pc...")
|
||||
E = remove_pc(E, npc=1)
|
||||
return E
|
||||
|
||||
|
||||
def _normalize(lX, l2=True):
|
||||
return {l: normalize(X) for l, X in lX.items()} if l2 else lX
|
||||
|
||||
|
||||
class BatchGRU:
|
||||
def __init__(self, batchsize, batches_per_epoch, languages, lpad, max_pad_length=500):
|
||||
self.batchsize = batchsize
|
||||
self.batches_per_epoch = batches_per_epoch
|
||||
self.languages = languages
|
||||
self.lpad = lpad
|
||||
self.max_pad_length = max_pad_length
|
||||
self.init_offset()
|
||||
|
||||
def init_offset(self):
|
||||
self.offset = {lang: 0 for lang in self.languages}
|
||||
|
||||
def batchify(self, l_index, l_post, l_bert, llabels, extractor=False):
|
||||
langs = self.languages
|
||||
l_num_samples = {l: len(l_index[l]) for l in langs}
|
||||
|
||||
max_samples = max(l_num_samples.values())
|
||||
n_batches = max_samples // self.batchsize + 1 * (max_samples % self.batchsize > 0)
|
||||
if self.batches_per_epoch != -1 and self.batches_per_epoch < n_batches:
|
||||
n_batches = self.batches_per_epoch
|
||||
|
||||
for b in range(n_batches):
|
||||
for lang in langs:
|
||||
index, labels = l_index[lang], llabels[lang]
|
||||
offset = self.offset[lang]
|
||||
if offset >= l_num_samples[lang]:
|
||||
offset = 0
|
||||
limit = offset+self.batchsize
|
||||
|
||||
batch_slice = slice(offset, limit)
|
||||
batch = index[batch_slice]
|
||||
batch_labels = labels[batch_slice].toarray()
|
||||
|
||||
post = None
|
||||
bert_emb = None
|
||||
|
||||
batch = pad(batch, pad_index=self.lpad[lang], max_pad_length=self.max_pad_length)
|
||||
batch = torch.LongTensor(batch).cuda()
|
||||
target = torch.FloatTensor(batch_labels).cuda()
|
||||
|
||||
self.offset[lang] = limit
|
||||
|
||||
yield batch, post, bert_emb, target, lang
|
||||
|
||||
|
||||
def pad(index_list, pad_index, max_pad_length=None):
|
||||
pad_length = np.max([len(index) for index in index_list])
|
||||
if max_pad_length is not None:
|
||||
pad_length = min(pad_length, max_pad_length)
|
||||
for i,indexes in enumerate(index_list):
|
||||
index_list[i] = [pad_index]*(pad_length-len(indexes)) + indexes[:pad_length]
|
||||
return index_list
|
||||
|
||||
|
||||
def train_gru(model, batcher, ltrain_index, lytr, tinit, logfile, criterion, optim, epoch, method_name, opt,
|
||||
ltrain_posteriors=None, ltrain_bert=None, log_interval=10):
|
||||
_dataset_path = opt.dataset.split('/')[-1].split('_')
|
||||
dataset_id = _dataset_path[0] + _dataset_path[-1]
|
||||
|
||||
# show_gpu('RNN init pre-training')
|
||||
loss_history = []
|
||||
model.train()
|
||||
for idx, (batch, post, bert_emb, target, lang) in enumerate(batcher.batchify(ltrain_index, ltrain_posteriors, ltrain_bert, lytr)):
|
||||
optim.zero_grad()
|
||||
loss = criterion(model(batch, post, bert_emb, lang), target)
|
||||
loss.backward()
|
||||
clip_gradient(model)
|
||||
optim.step()
|
||||
loss_history.append(loss.item())
|
||||
# show_gpu('RNN after batch prediction')
|
||||
|
||||
if idx % log_interval == 0:
|
||||
interval_loss = np.mean(loss_history[-log_interval:])
|
||||
print(f'{dataset_id} {method_name} Epoch: {epoch}, Step: {idx}, lr={get_lr(optim):.5f}, '
|
||||
f'Training Loss: {interval_loss:.6f}')
|
||||
|
||||
mean_loss = np.mean(interval_loss)
|
||||
logfile.add_row(epoch=epoch, measure='tr_loss', value=mean_loss, timelapse=time.time() - tinit)
|
||||
return mean_loss
|
||||
|
||||
|
||||
def test_gru(model, batcher, ltest_index, ltest_posteriors, lte_bert, lyte, tinit, epoch, logfile, criterion, measure_prefix):
|
||||
loss_history = []
|
||||
model.eval()
|
||||
langs = sorted(ltest_index.keys())
|
||||
predictions = {l: [] for l in langs}
|
||||
yte_stacked = {l: [] for l in langs}
|
||||
batcher.init_offset()
|
||||
for batch, post, bert_emb, target, lang in tqdm(batcher.batchify(ltest_index, ltest_posteriors, lte_bert, lyte),
|
||||
desc='evaluation: '):
|
||||
logits = model(batch, post, bert_emb, lang)
|
||||
loss = criterion(logits, target).item()
|
||||
prediction = predict(logits)
|
||||
predictions[lang].append(prediction)
|
||||
yte_stacked[lang].append(target.detach().cpu().numpy())
|
||||
loss_history.append(loss)
|
||||
|
||||
ly = {l:np.vstack(yte_stacked[l]) for l in langs}
|
||||
ly_ = {l:np.vstack(predictions[l]) for l in langs}
|
||||
l_eval = evaluate(ly, ly_)
|
||||
metrics = []
|
||||
for lang in langs:
|
||||
macrof1, microf1, macrok, microk = l_eval[lang]
|
||||
metrics.append([macrof1, microf1, macrok, microk])
|
||||
if measure_prefix == 'te':
|
||||
print(f'Lang {lang}: macro-F1={macrof1:.3f} micro-F1={microf1:.3f}')
|
||||
Mf1, mF1, MK, mk = np.mean(np.array(metrics), axis=0)
|
||||
print(f'[{measure_prefix}] Averages: MF1, mF1, MK, mK [{Mf1:.5f}, {mF1:.5f}, {MK:.5f}, {mk:.5f}]')
|
||||
|
||||
mean_loss = np.mean(loss_history)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-macro-F1', value=Mf1, timelapse=time.time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-micro-F1', value=mF1, timelapse=time.time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-macro-K', value=MK, timelapse=time.time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-micro-K', value=mk, timelapse=time.time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-loss', value=mean_loss, timelapse=time.time() - tinit)
|
||||
|
||||
return Mf1
|
||||
|
||||
|
||||
def clip_gradient(model, clip_value=1e-1):
|
||||
params = list(filter(lambda p: p.grad is not None, model.parameters()))
|
||||
for p in params:
|
||||
p.grad.data.clamp_(-clip_value, clip_value)
|
||||
|
||||
|
||||
def init_logfile_nn(method_name, opt):
|
||||
import os
|
||||
logfile = CSVLog(opt.logfile_gru, ['dataset', 'method', 'epoch', 'measure', 'value', 'run', 'timelapse'])
|
||||
logfile.set_default('dataset', opt.dataset)
|
||||
logfile.set_default('run', opt.seed)
|
||||
logfile.set_default('method', get_method_name(os.path.basename(opt.dataset), opt.posteriors, opt.supervised, opt.pretrained, opt.mbert,
|
||||
opt.gruViewGenerator, opt.gruMUSE, opt.gruWCE, opt.agg, opt.allprob))
|
||||
assert opt.force or not logfile.already_calculated(), f'results for dataset {opt.dataset} method {method_name} ' \
|
||||
f'and run {opt.seed} already calculated'
|
||||
return logfile
|
||||
166
src/main_gFun.py
166
src/main_gFun.py
|
|
@ -1,166 +0,0 @@
|
|||
import os
|
||||
from dataset_builder import MultilingualDataset
|
||||
from learning.transformers import *
|
||||
from util.evaluation import *
|
||||
from util.file import exists
|
||||
from util.results import PolylingualClassificationResults
|
||||
from util.common import *
|
||||
from util.parser_options import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
(op, args) = parser.parse_args()
|
||||
dataset = op.dataset
|
||||
assert exists(dataset), 'Unable to find file '+str(dataset)
|
||||
assert not (op.set_c != 1. and op.optimc), 'Parameter C cannot be defined along with optim_c option'
|
||||
assert op.posteriors or op.supervised or op.pretrained or op.mbert or op.gruViewGenerator, \
|
||||
'empty set of document embeddings is not allowed'
|
||||
if op.gruViewGenerator:
|
||||
assert op.gruWCE or op.gruMUSE, 'Initializing Gated Recurrent embedding layer without ' \
|
||||
'explicit initialization of GRU View Generator'
|
||||
|
||||
l2 = op.l2
|
||||
dataset_file = os.path.basename(dataset)
|
||||
results = PolylingualClassificationResults('../log/' + op.output)
|
||||
allprob = 'Prob' if op.allprob else ''
|
||||
|
||||
method_name, dataset_name = get_method_name(dataset, op.posteriors, op.supervised, op.pretrained, op.mbert,
|
||||
op.gruViewGenerator, op.gruMUSE, op.gruWCE, op.agg, op.allprob)
|
||||
|
||||
print(f'Method: gFun{method_name}\nDataset: {dataset_name}')
|
||||
print('-'*50)
|
||||
|
||||
n_jobs = -1 # TODO SETTING n_JOBS
|
||||
|
||||
standardize_range = slice(0, 0)
|
||||
if op.zscore:
|
||||
standardize_range = None
|
||||
|
||||
# load dataset
|
||||
data = MultilingualDataset.load(dataset)
|
||||
# data.set_view(languages=['it']) # TODO: DEBUG SETTING
|
||||
data.show_dimensions()
|
||||
lXtr, lytr = data.training()
|
||||
lXte, lyte = data.test()
|
||||
|
||||
# text preprocessing
|
||||
tfidfvectorizer = TfidfVectorizerMultilingual(sublinear_tf=True, use_idf=True)
|
||||
|
||||
# feature weighting (for word embeddings average)
|
||||
feat_weighting = FeatureWeight(op.feat_weight, agg='mean')
|
||||
|
||||
# document embedding modules aka View Generators
|
||||
doc_embedder = DocEmbedderList(aggregation='mean' if op.agg else 'concat')
|
||||
|
||||
# init View Generators
|
||||
if op.posteriors:
|
||||
"""
|
||||
View Generator (-X): cast document representations encoded via TFIDF into posterior probabilities by means
|
||||
of a set of SVM.
|
||||
"""
|
||||
# Check if we already have VG outputs from previous runs
|
||||
VG_name = 'X'
|
||||
storing_path = f'../dumps/{VG_name}/{dataset_name.split(".")[0]}'
|
||||
exist = exists(storing_path)
|
||||
doc_embedder.append(PosteriorProbabilitiesEmbedder(first_tier_learner=get_learner(calibrate=True,
|
||||
kernel='linear',
|
||||
C=op.set_c),
|
||||
l2=l2, storing_path=storing_path, n_jobs=n_jobs))
|
||||
|
||||
if op.supervised:
|
||||
"""
|
||||
View Generator (-W): generates document representation via Word-Class-Embeddings.
|
||||
Document embeddings are obtained via weighted sum of document's constituent embeddings.
|
||||
"""
|
||||
VG_name = 'W'
|
||||
storing_path = f'../dumps/{VG_name}/{dataset_name.split(".")[0]}'
|
||||
exist = exists(storing_path)
|
||||
wce = WordClassEmbedder(max_label_space=op.max_labels_S, l2=l2, featureweight=feat_weighting,
|
||||
sif=op.sif, n_jobs=n_jobs)
|
||||
if op.allprob:
|
||||
wce = FeatureSet2Posteriors(wce, method_id=VG_name, requires_tfidf=True, l2=l2, storing_path=storing_path,
|
||||
n_jobs=n_jobs)
|
||||
doc_embedder.append(wce)
|
||||
|
||||
if op.pretrained:
|
||||
"""
|
||||
View Generator (-M): generates document representation via MUSE embeddings (Fasttext multilingual word
|
||||
embeddings). Document embeddings are obtained via weighted sum of document's constituent embeddings.
|
||||
"""
|
||||
VG_name = 'M'
|
||||
storing_path = f'../dumps/{VG_name}/{dataset_name.split(".")[0]}'
|
||||
exist = exists(storing_path)
|
||||
muse = MuseEmbedder(op.we_path, l2=l2, featureweight=feat_weighting, sif=op.sif, n_jobs=n_jobs)
|
||||
if op.allprob:
|
||||
muse = FeatureSet2Posteriors(muse, method_id=VG_name, requires_tfidf=True, l2=l2, storing_path=storing_path,
|
||||
n_jobs=n_jobs)
|
||||
doc_embedder.append(muse)
|
||||
|
||||
if op.gruViewGenerator:
|
||||
"""
|
||||
View Generator (-G): generates document embedding by means of a Gated Recurrent Units. The model can be
|
||||
initialized with different (multilingual/aligned) word representations (e.g., MUSE, WCE, ecc.,).
|
||||
Output dimension is (n_docs, 512). If --allprob output will be casted to posterior prob space via SVM.
|
||||
"""
|
||||
VG_name = 'G'
|
||||
VG_name += '_muse' if op.gruMUSE else ''
|
||||
VG_name += '_wce' if op.gruWCE else ''
|
||||
storing_path = 'Nope' # f'../dumps/{VG_name}/{dataset_name.split(".")[0]}'
|
||||
rnn_embedder = RecurrentEmbedder(pretrained=op.gruMUSE, supervised=op.gruWCE, multilingual_dataset=data,
|
||||
options=op, model_path=None, n_jobs=n_jobs)
|
||||
if op.allprob:
|
||||
rnn_embedder = FeatureSet2Posteriors(rnn_embedder, method_id=VG_name, requires_tfidf=False,
|
||||
storing_path=storing_path, n_jobs=n_jobs)
|
||||
doc_embedder.append(rnn_embedder)
|
||||
|
||||
if op.mbert:
|
||||
"""
|
||||
View generator (-B): generates document embedding via mBERT model.
|
||||
"""
|
||||
VG_name = 'B'
|
||||
storing_path = f'../dumps/{VG_name}/{dataset_name.split(".")[0]}'
|
||||
avoid_loading = False if op.avoid_loading else True # TODO research setting (set to false mBert will be loaded into gpu to get doc emebds (aka, only the first time for each run))
|
||||
|
||||
mbert = MBertEmbedder(path_to_model=op.bert_path, nC=data.num_categories(), avoid_loading=avoid_loading)
|
||||
if op.allprob:
|
||||
mbert = FeatureSet2Posteriors(mbert, method_id=VG_name, l2=l2, storing_path=storing_path)
|
||||
doc_embedder.append(mbert)
|
||||
|
||||
# metaclassifier
|
||||
meta_parameters = None if op.set_c != -1 else [{'C': [1, 1e3, 1e2, 1e1, 1e-1]}]
|
||||
meta = MetaClassifier(meta_learner=get_learner(calibrate=False, kernel='rbf', C=op.set_c),
|
||||
meta_parameters=get_params(op.optimc), standardize_range=standardize_range, n_jobs=n_jobs)
|
||||
|
||||
# ensembling the modules
|
||||
classifier = Funnelling(vectorizer=tfidfvectorizer, first_tier=doc_embedder, meta=meta)
|
||||
|
||||
print('\n# Fitting Funnelling Architecture...')
|
||||
tinit = time.time()
|
||||
classifier.fit(lXtr, lytr)
|
||||
time = time.time()-tinit
|
||||
|
||||
print('\n# Evaluating ...')
|
||||
l_eval = evaluate_method(classifier, lXte, lyte)
|
||||
|
||||
metrics = []
|
||||
for lang in lXte.keys():
|
||||
macrof1, microf1, macrok, microk = l_eval[lang]
|
||||
metrics.append([macrof1, microf1, macrok, microk])
|
||||
print(f'Lang {lang}: macro-F1={macrof1:.3f} micro-F1={microf1:.3f}')
|
||||
results.add_row(method='MultiModal',
|
||||
learner='SVM',
|
||||
optimp=op.optimc,
|
||||
sif=op.sif,
|
||||
zscore=op.zscore,
|
||||
l2=op.l2,
|
||||
wescaler=op.feat_weight,
|
||||
pca=op.max_labels_S,
|
||||
id=method_name,
|
||||
dataset=dataset_name,
|
||||
time=time,
|
||||
lang=lang,
|
||||
macrof1=macrof1,
|
||||
microf1=microf1,
|
||||
macrok=macrok,
|
||||
microk=microk,
|
||||
notes='')
|
||||
print('Averages: MF1, mF1, MK, mK', np.round(np.mean(np.array(metrics), axis=0), 3))
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
import torch.nn as nn
|
||||
from torch.nn import functional as F
|
||||
import torch
|
||||
|
||||
class CNN_pdr(nn.Module):
|
||||
|
||||
def __init__(self, output_size, out_channels, compositional_dim, vocab_size, emb_dim, embeddings=None, drop_embedding_range=None,
|
||||
drop_embedding_prop=0, drop_prob=0.5):
|
||||
super(CNN_pdr, self).__init__()
|
||||
self.vocab_size = vocab_size
|
||||
self.emb_dim = emb_dim
|
||||
self.embeddings = torch.FloatTensor(embeddings)
|
||||
self.embedding_layer = nn.Embedding(vocab_size, emb_dim, _weight=self.embeddings)
|
||||
self.kernel_heights = kernel_heights=[3,5,7]
|
||||
self.stride = 1
|
||||
self.padding = 0
|
||||
self.drop_embedding_range = drop_embedding_range
|
||||
self.drop_embedding_prop = drop_embedding_prop
|
||||
assert 0 <= drop_embedding_prop <= 1, 'drop_embedding_prop: wrong range'
|
||||
self.nC = 73
|
||||
|
||||
self.conv1 = nn.Conv2d(1, compositional_dim, (self.kernel_heights[0], self.emb_dim), self.stride, self.padding)
|
||||
self.dropout = nn.Dropout(drop_prob)
|
||||
self.label = nn.Linear(len(kernel_heights) * out_channels, output_size)
|
||||
self.fC = nn.Linear(compositional_dim + self.nC, self.nC)
|
||||
|
||||
|
||||
def forward(self, x, svm_output):
|
||||
x = torch.LongTensor(x)
|
||||
svm_output = torch.FloatTensor(svm_output)
|
||||
x = self.embedding_layer(x)
|
||||
x = self.conv1(x.unsqueeze(1))
|
||||
x = F.relu(x.squeeze(3))
|
||||
x = F.max_pool1d(x, x.size()[2]).squeeze(2)
|
||||
x = torch.cat((x, svm_output), 1)
|
||||
x = F.sigmoid(self.fC(x))
|
||||
return x #.detach().numpy()
|
||||
|
||||
# logits = self.label(x)
|
||||
# return logits
|
||||
|
||||
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
|
||||
|
||||
def init_embeddings(pretrained, vocab_size, learnable_length, device='cuda'):
|
||||
pretrained_embeddings = None
|
||||
pretrained_length = 0
|
||||
if pretrained is not None:
|
||||
pretrained_length = pretrained.shape[1]
|
||||
assert pretrained.shape[0] == vocab_size, 'pre-trained matrix does not match with the vocabulary size'
|
||||
pretrained_embeddings = nn.Embedding(vocab_size, pretrained_length)
|
||||
pretrained_embeddings.weight = nn.Parameter(pretrained, requires_grad=False)
|
||||
# pretrained_embeddings.to(device)
|
||||
|
||||
learnable_embeddings = None
|
||||
if learnable_length > 0:
|
||||
learnable_embeddings = nn.Embedding(vocab_size, learnable_length)
|
||||
# learnable_embeddings.to(device)
|
||||
|
||||
embedding_length = learnable_length + pretrained_length
|
||||
assert embedding_length > 0, '0-size embeddings'
|
||||
|
||||
return pretrained_embeddings, learnable_embeddings, embedding_length
|
||||
|
||||
|
||||
def embed(model, input, lang):
|
||||
input_list = []
|
||||
if model.lpretrained_embeddings[lang]:
|
||||
input_list.append(model.lpretrained_embeddings[lang](input))
|
||||
if model.llearnable_embeddings[lang]:
|
||||
input_list.append(model.llearnable_embeddings[lang](input))
|
||||
return torch.cat(tensors=input_list, dim=2)
|
||||
|
||||
|
||||
def embedding_dropout(input, drop_range, p_drop=0.5, training=True):
|
||||
if p_drop > 0 and training and drop_range is not None:
|
||||
p = p_drop
|
||||
drop_from, drop_to = drop_range
|
||||
m = drop_to - drop_from #length of the supervised embedding
|
||||
l = input.shape[2] #total embedding length
|
||||
corr = (1 - p)
|
||||
input[:, :, drop_from:drop_to] = corr * F.dropout(input[:, :, drop_from:drop_to], p=p)
|
||||
input /= (1 - (p * m / l))
|
||||
|
||||
return input
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
#taken from https://github.com/prakashpandey9/Text-Classification-Pytorch/blob/master/models/LSTM.py
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from torch.autograd import Variable
|
||||
from models.helpers import *
|
||||
|
||||
|
||||
class RNNMultilingualClassifier(nn.Module):
|
||||
|
||||
def __init__(self, output_size, hidden_size, lvocab_size, learnable_length, lpretrained=None,
|
||||
drop_embedding_range=None, drop_embedding_prop=0, post_probabilities=True, only_post=False,
|
||||
bert_embeddings=False):
|
||||
|
||||
super(RNNMultilingualClassifier, self).__init__()
|
||||
self.output_size = output_size
|
||||
self.hidden_size = hidden_size
|
||||
self.drop_embedding_range = drop_embedding_range
|
||||
self.drop_embedding_prop = drop_embedding_prop
|
||||
self.post_probabilities = post_probabilities
|
||||
self.bert_embeddings = bert_embeddings
|
||||
assert 0 <= drop_embedding_prop <= 1, 'drop_embedding_prop: wrong range'
|
||||
|
||||
self.lpretrained_embeddings = nn.ModuleDict()
|
||||
self.llearnable_embeddings = nn.ModuleDict()
|
||||
self.embedding_length = None
|
||||
self.langs = sorted(lvocab_size.keys())
|
||||
self.only_post = only_post
|
||||
|
||||
self.n_layers = 1
|
||||
self.n_directions = 1
|
||||
|
||||
self.dropout = nn.Dropout(0.6)
|
||||
|
||||
lstm_out = 256
|
||||
ff1 = 512
|
||||
ff2 = 256
|
||||
|
||||
lpretrained_embeddings = {}
|
||||
llearnable_embeddings = {}
|
||||
if only_post==False:
|
||||
for l in self.langs:
|
||||
pretrained = lpretrained[l] if lpretrained else None
|
||||
pretrained_embeddings, learnable_embeddings, embedding_length = init_embeddings(
|
||||
pretrained, lvocab_size[l], learnable_length
|
||||
)
|
||||
lpretrained_embeddings[l] = pretrained_embeddings
|
||||
llearnable_embeddings[l] = learnable_embeddings
|
||||
self.embedding_length = embedding_length
|
||||
|
||||
# self.lstm = nn.LSTM(self.embedding_length, hidden_size, dropout=0.2 if self.n_layers>1 else 0, num_layers=self.n_layers, bidirectional=(self.n_directions==2))
|
||||
self.rnn = nn.GRU(self.embedding_length, hidden_size)
|
||||
self.linear0 = nn.Linear(hidden_size * self.n_directions, lstm_out)
|
||||
self.lpretrained_embeddings.update(lpretrained_embeddings)
|
||||
self.llearnable_embeddings.update(llearnable_embeddings)
|
||||
|
||||
self.linear1 = nn.Linear(lstm_out, ff1)
|
||||
self.linear2 = nn.Linear(ff1, ff2)
|
||||
|
||||
if only_post:
|
||||
self.label = nn.Linear(output_size, output_size)
|
||||
elif post_probabilities and not bert_embeddings:
|
||||
self.label = nn.Linear(ff2 + output_size, output_size)
|
||||
elif bert_embeddings and not post_probabilities:
|
||||
self.label = nn.Linear(ff2 + 768, output_size)
|
||||
elif post_probabilities and bert_embeddings:
|
||||
self.label = nn.Linear(ff2 + output_size + 768, output_size)
|
||||
else:
|
||||
self.label = nn.Linear(ff2, output_size)
|
||||
|
||||
def forward(self, input, post, bert_embed, lang):
|
||||
if self.only_post:
|
||||
doc_embedding = post
|
||||
else:
|
||||
doc_embedding = self.transform(input, lang)
|
||||
if self.post_probabilities:
|
||||
doc_embedding = torch.cat([doc_embedding, post], dim=1)
|
||||
if self.bert_embeddings:
|
||||
doc_embedding = torch.cat([doc_embedding, bert_embed], dim=1)
|
||||
|
||||
logits = self.label(doc_embedding)
|
||||
return logits
|
||||
|
||||
def transform(self, input, lang):
|
||||
batch_size = input.shape[0]
|
||||
input = embed(self, input, lang)
|
||||
input = embedding_dropout(input, drop_range=self.drop_embedding_range, p_drop=self.drop_embedding_prop,
|
||||
training=self.training)
|
||||
input = input.permute(1, 0, 2)
|
||||
h_0 = Variable(torch.zeros(self.n_layers*self.n_directions, batch_size, self.hidden_size).cuda())
|
||||
# c_0 = Variable(torch.zeros(self.n_layers*self.n_directions, batch_size, self.hidden_size).cuda())
|
||||
# output, (_, _) = self.lstm(input, (h_0, c_0))
|
||||
output, _ = self.rnn(input, h_0)
|
||||
output = output[-1, :, :]
|
||||
output = F.relu(self.linear0(output))
|
||||
output = self.dropout(F.relu(self.linear1(output)))
|
||||
output = self.dropout(F.relu(self.linear2(output)))
|
||||
return output
|
||||
|
||||
def finetune_pretrained(self):
|
||||
for l in self.langs:
|
||||
self.lpretrained_embeddings[l].requires_grad = True
|
||||
self.lpretrained_embeddings[l].weight.requires_grad = True
|
||||
|
||||
def get_embeddings(self, input, lang):
|
||||
batch_size = input.shape[0]
|
||||
input = embed(self, input, lang)
|
||||
input = embedding_dropout(input, drop_range=self.drop_embedding_range, p_drop=self.drop_embedding_prop,
|
||||
training=self.training)
|
||||
input = input.permute(1, 0, 2)
|
||||
h_0 = Variable(torch.zeros(self.n_layers * self.n_directions, batch_size, self.hidden_size).cuda())
|
||||
output, _ = self.rnn(input, h_0)
|
||||
output = output[-1, :, :]
|
||||
return output.cpu().detach().numpy()
|
||||
|
||||
|
|
@ -1,247 +0,0 @@
|
|||
from copy import deepcopy
|
||||
import torch
|
||||
from torch.utils.data import Dataset
|
||||
from transformers import BertForSequenceClassification, BertTokenizer, AdamW, BertConfig
|
||||
from sklearn.model_selection import train_test_split
|
||||
from util.evaluation import *
|
||||
from time import time
|
||||
from util.common import show_gpu
|
||||
|
||||
|
||||
def predict(logits, classification_type='multilabel'):
|
||||
if classification_type == 'multilabel':
|
||||
prediction = torch.sigmoid(logits) > 0.5
|
||||
elif classification_type == 'singlelabel':
|
||||
prediction = torch.argmax(logits, dim=1).view(-1, 1)
|
||||
else:
|
||||
print('unknown classification type')
|
||||
|
||||
return prediction.detach().cpu().numpy()
|
||||
|
||||
|
||||
class TrainingDataset(Dataset):
|
||||
|
||||
def __init__(self, data, labels):
|
||||
self.langs = data.keys()
|
||||
self.lang_ids = {lang: identifier for identifier, lang in enumerate(self.langs)}
|
||||
|
||||
for i, lang in enumerate(self.langs):
|
||||
_data = data[lang]['input_ids']
|
||||
_data = np.array(_data)
|
||||
_labels = labels[lang]
|
||||
_lang_value = np.full(len(_data), self.lang_ids[lang])
|
||||
|
||||
if i == 0:
|
||||
self.data = _data
|
||||
self.labels = _labels
|
||||
self.lang_index = _lang_value
|
||||
else:
|
||||
self.data = np.vstack((self.data, _data))
|
||||
self.labels = np.vstack((self.labels, _labels))
|
||||
self.lang_index = np.concatenate((self.lang_index, _lang_value))
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
x = self.data[idx]
|
||||
y = self.labels[idx]
|
||||
lang = self.lang_index[idx]
|
||||
|
||||
return x, torch.tensor(y, dtype=torch.float), lang
|
||||
|
||||
def get_lang_ids(self):
|
||||
return self.lang_ids
|
||||
|
||||
def get_nclasses(self):
|
||||
if hasattr(self, 'labels'):
|
||||
return len(self.labels[0])
|
||||
else:
|
||||
print('Method called before init!')
|
||||
|
||||
|
||||
class ExtractorDataset(Dataset):
|
||||
"""
|
||||
data: dict of lang specific tokenized data
|
||||
labels: dict of lang specific targets
|
||||
"""
|
||||
|
||||
def __init__(self, data):
|
||||
self.langs = data.keys()
|
||||
self.lang_ids = {lang: identifier for identifier, lang in enumerate(self.langs)}
|
||||
|
||||
for i, lang in enumerate(self.langs):
|
||||
_data = data[lang]['input_ids']
|
||||
_data = np.array(_data)
|
||||
_lang_value = np.full(len(_data), self.lang_ids[lang])
|
||||
|
||||
if i == 0:
|
||||
self.data = _data
|
||||
self.lang_index = _lang_value
|
||||
else:
|
||||
self.data = np.vstack((self.data, _data))
|
||||
self.lang_index = np.concatenate((self.lang_index, _lang_value))
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
x = self.data[idx]
|
||||
lang = self.lang_index[idx]
|
||||
|
||||
return x, lang
|
||||
|
||||
def get_lang_ids(self):
|
||||
return self.lang_ids
|
||||
|
||||
|
||||
def get_model(n_out):
|
||||
print('# Initializing model ...')
|
||||
model = BertForSequenceClassification.from_pretrained('bert-base-multilingual-cased', num_labels=n_out)
|
||||
return model
|
||||
|
||||
|
||||
def init_optimizer(model, lr, weight_decay=0):
|
||||
no_decay = ['bias', 'LayerNorm.weight']
|
||||
optimizer_grouped_parameters = [
|
||||
{'params': [p for n, p in model.named_parameters()
|
||||
if not any(nd in n for nd in no_decay)],
|
||||
'weight_decay': weight_decay},
|
||||
{'params': [p for n, p in model.named_parameters()
|
||||
if any(nd in n for nd in no_decay)],
|
||||
'weight_decay': weight_decay}
|
||||
]
|
||||
optimizer = AdamW(optimizer_grouped_parameters, lr=lr)
|
||||
return optimizer
|
||||
|
||||
|
||||
def get_lr(optimizer):
|
||||
for param_group in optimizer.param_groups:
|
||||
return param_group['lr']
|
||||
|
||||
|
||||
def get_tr_val_split(l_tokenized_tr, l_devel_target, val_prop, max_val, seed):
|
||||
l_split_va = deepcopy(l_tokenized_tr)
|
||||
l_split_val_target = {l: [] for l in l_tokenized_tr.keys()}
|
||||
l_split_tr = deepcopy(l_tokenized_tr)
|
||||
l_split_tr_target = {l: [] for l in l_tokenized_tr.keys()}
|
||||
|
||||
for lang in l_tokenized_tr.keys():
|
||||
val_size = int(min(len(l_tokenized_tr[lang]['input_ids']) * val_prop, max_val))
|
||||
l_split_tr[lang]['input_ids'], l_split_va[lang]['input_ids'], l_split_tr_target[lang], l_split_val_target[
|
||||
lang] = \
|
||||
train_test_split(l_tokenized_tr[lang]['input_ids'], l_devel_target[lang], test_size=val_size,
|
||||
random_state=seed, shuffle=True)
|
||||
|
||||
return l_split_tr, l_split_tr_target, l_split_va, l_split_val_target
|
||||
|
||||
|
||||
def do_tokenization(l_dataset, max_len=512, verbose=True):
|
||||
if verbose:
|
||||
print('# Starting Tokenization ...')
|
||||
tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased')
|
||||
langs = l_dataset.keys()
|
||||
l_tokenized = {}
|
||||
for lang in langs:
|
||||
l_tokenized[lang] = tokenizer(l_dataset[lang],
|
||||
truncation=True,
|
||||
max_length=max_len,
|
||||
padding='max_length')
|
||||
return l_tokenized
|
||||
|
||||
|
||||
def train(model, train_dataloader, epoch, criterion, optim, method_name, tinit, logfile, log_interval=10):
|
||||
# _dataset_path = opt.dataset.split('/')[-1].split('_')
|
||||
# dataset_id = _dataset_path[0] + _dataset_path[-1]
|
||||
dataset_id = 'TODO fix this!' # TODO
|
||||
|
||||
loss_history = []
|
||||
model.train()
|
||||
|
||||
for idx, (batch, target, lang_idx) in enumerate(train_dataloader):
|
||||
optim.zero_grad()
|
||||
out = model(batch.cuda())
|
||||
logits = out[0]
|
||||
loss = criterion(logits, target.cuda())
|
||||
loss.backward()
|
||||
# clip_gradient(model)
|
||||
optim.step()
|
||||
loss_history.append(loss.item())
|
||||
|
||||
if idx % log_interval == 0:
|
||||
interval_loss = np.mean(loss_history[log_interval:])
|
||||
print(
|
||||
f'{dataset_id} {method_name} Epoch: {epoch}, Step: {idx}, lr={get_lr(optim):.5f}, Training Loss: {interval_loss:.6f}')
|
||||
|
||||
mean_loss = np.mean(interval_loss)
|
||||
logfile.add_row(epoch=epoch, measure='tr_loss', value=mean_loss, timelapse=time() - tinit)
|
||||
return mean_loss
|
||||
|
||||
|
||||
def test(model, test_dataloader, lang_ids, tinit, epoch, logfile, criterion, measure_prefix):
|
||||
print('# Validating model ...')
|
||||
loss_history = []
|
||||
model.eval()
|
||||
langs = lang_ids.keys()
|
||||
id_2_lang = {v: k for k, v in lang_ids.items()}
|
||||
predictions = {l: [] for l in langs}
|
||||
yte_stacked = {l: [] for l in langs}
|
||||
|
||||
for batch, target, lang_idx in test_dataloader:
|
||||
out = model(batch.cuda())
|
||||
logits = out[0]
|
||||
loss = criterion(logits, target.cuda()).item()
|
||||
prediction = predict(logits)
|
||||
loss_history.append(loss)
|
||||
|
||||
# Assigning prediction to dict in predictions and yte_stacked according to lang_idx
|
||||
for i, pred in enumerate(prediction):
|
||||
lang_pred = id_2_lang[lang_idx.numpy()[i]]
|
||||
predictions[lang_pred].append(pred)
|
||||
yte_stacked[lang_pred].append(target[i].detach().cpu().numpy())
|
||||
|
||||
ly = {l: np.vstack(yte_stacked[l]) for l in langs}
|
||||
ly_ = {l: np.vstack(predictions[l]) for l in langs}
|
||||
l_eval = evaluate(ly, ly_)
|
||||
metrics = []
|
||||
for lang in langs:
|
||||
macrof1, microf1, macrok, microk = l_eval[lang]
|
||||
metrics.append([macrof1, microf1, macrok, microk])
|
||||
if measure_prefix == 'te':
|
||||
print(f'Lang {lang}: macro-F1={macrof1:.3f} micro-F1={microf1:.3f}')
|
||||
Mf1, mF1, MK, mk = np.mean(np.array(metrics), axis=0)
|
||||
print(f'[{measure_prefix}] Averages: MF1, mF1, MK, mK [{Mf1:.5f}, {mF1:.5f}, {MK:.5f}, {mk:.5f}]')
|
||||
|
||||
mean_loss = np.mean(loss_history)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-macro-F1', value=Mf1, timelapse=time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-micro-F1', value=mF1, timelapse=time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-macro-K', value=MK, timelapse=time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-micro-K', value=mk, timelapse=time() - tinit)
|
||||
logfile.add_row(epoch=epoch, measure=f'{measure_prefix}-loss', value=mean_loss, timelapse=time() - tinit)
|
||||
|
||||
return Mf1
|
||||
|
||||
|
||||
def feature_extractor(data, lang_ids, model):
|
||||
print('# Feature Extractor Mode...')
|
||||
"""
|
||||
Hidden State = Tuple of torch.FloatTensor (one for the output of the embeddings + one for
|
||||
the output of each layer) of shape (batch_size, sequence_length, hidden_size)
|
||||
"""
|
||||
# show_gpu('Before Training')
|
||||
all_batch_embeddings = {}
|
||||
id2lang = {v: k for k, v in lang_ids.items()}
|
||||
with torch.no_grad():
|
||||
for batch, lang_idx in data:
|
||||
out = model(batch.cuda())
|
||||
# show_gpu('After Batch Prediction')
|
||||
last_hidden_state = out[1][-1]
|
||||
batch_embeddings = last_hidden_state[:, 0, :]
|
||||
for i, l_idx in enumerate(lang_idx.numpy()):
|
||||
if id2lang[l_idx] not in all_batch_embeddings.keys():
|
||||
all_batch_embeddings[id2lang[l_idx]] = batch_embeddings[i].detach().cpu().numpy()
|
||||
else:
|
||||
all_batch_embeddings[id2lang[l_idx]] = np.vstack((all_batch_embeddings[id2lang[l_idx]],
|
||||
batch_embeddings[i].detach().cpu().numpy()))
|
||||
# show_gpu('After Full Prediction')
|
||||
return all_batch_embeddings, id2lang
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
# df = pd.read_csv("/home/andreapdr/funneling_pdr/src/results/final_results.csv", delimiter='\t')
|
||||
df = pd.read_csv("10run_rcv_final_results.csv", delimiter='\t')
|
||||
pivot = pd.pivot_table(df, values=['macrof1', 'microf1', 'macrok', 'microk'], index=['method', 'id', 'optimp', 'zscore', 'l2', 'wescaler', 'pca', 'sif'], aggfunc=[np.mean, np.std])
|
||||
with pd.option_context('display.max_rows', None):
|
||||
print(pivot.round(3))
|
||||
print('Finished ...')
|
||||
|
||||
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
import numpy as np
|
||||
from sklearn.decomposition import TruncatedSVD
|
||||
|
||||
def get_weighted_average(We, x, w):
|
||||
"""
|
||||
Compute the weighted average vectors
|
||||
:param We: We[i,:] is the vector for word i
|
||||
:param x: x[i, :] are the indices of the words in sentence i
|
||||
:param w: w[i, :] are the weights for the words in sentence i
|
||||
:return: emb[i, :] are the weighted average vector for sentence i
|
||||
"""
|
||||
n_samples = x.shape[0]
|
||||
emb = np.zeros((n_samples, We.shape[1]))
|
||||
for i in range(n_samples):
|
||||
emb[i,:] = w[i,:].dot(We[x[i,:],:]) / np.count_nonzero(w[i,:])
|
||||
return emb
|
||||
|
||||
def compute_pc(X,npc=1):
|
||||
"""
|
||||
Compute the principal components.
|
||||
:param X: X[i,:] is a data point
|
||||
:param npc: number of principal components to remove
|
||||
:return: component_[i,:] is the i-th pc
|
||||
"""
|
||||
svd = TruncatedSVD(n_components=npc, n_iter=7, random_state=0)
|
||||
svd.fit(X)
|
||||
return svd.components_
|
||||
|
||||
def remove_pc(X, npc=1):
|
||||
"""
|
||||
Remove the projection on the principal components
|
||||
:param X: X[i,:] is a data point
|
||||
:param npc: number of principal components to remove
|
||||
:return: XX[i, :] is the data point after removing its projection
|
||||
"""
|
||||
pc = compute_pc(X, npc)
|
||||
if npc==1:
|
||||
XX = X - X.dot(pc.transpose()) * pc
|
||||
else:
|
||||
XX = X - X.dot(pc.transpose()).dot(pc)
|
||||
return XX
|
||||
|
||||
|
||||
def SIF_embedding(We, x, w, params):
|
||||
"""
|
||||
Compute the scores between pairs of sentences using weighted average + removing the projection on the first principal component
|
||||
:param We: We[i,:] is the vector for word i
|
||||
:param x: x[i, :] are the indices of the words in the i-th sentence
|
||||
:param w: w[i, :] are the weights for the words in the i-th sentence
|
||||
:param params.rmpc: if >0, remove the projections of the sentence embeddings to their first principal component
|
||||
:return: emb, emb[i, :] is the embedding for sentence i
|
||||
"""
|
||||
emb = get_weighted_average(We, x, w)
|
||||
if params.rmpc > 0:
|
||||
emb = remove_pc(emb, params.rmpc)
|
||||
return emb
|
||||
|
|
@ -1,542 +0,0 @@
|
|||
import subprocess
|
||||
import warnings
|
||||
from sklearn.feature_extraction.text import TfidfVectorizer
|
||||
from sklearn.svm import SVC
|
||||
from sklearn.model_selection import train_test_split
|
||||
from embeddings.supervised import get_supervised_embeddings
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
import torch
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
|
||||
|
||||
def index(data, vocab, known_words, analyzer, unk_index, out_of_vocabulary):
|
||||
"""
|
||||
Index (i.e., replaces word strings with numerical indexes) a list of string documents
|
||||
:param data: list of string documents
|
||||
:param vocab: a fixed mapping [str]->[int] of words to indexes
|
||||
:param known_words: a set of known words (e.g., words that, despite not being included in the vocab, can be retained
|
||||
because they are anyway contained in a pre-trained embedding set that we know in advance)
|
||||
:param analyzer: the preprocessor in charge of transforming the document string into a chain of string words
|
||||
:param unk_index: the index of the 'unknown token', i.e., a symbol that characterizes all words that we cannot keep
|
||||
:param out_of_vocabulary: an incremental mapping [str]->[int] of words to indexes that will index all those words that
|
||||
are not in the original vocab but that are in the known_words
|
||||
:return:
|
||||
"""
|
||||
indexes=[]
|
||||
vocabsize = len(vocab)
|
||||
unk_count = 0
|
||||
knw_count = 0
|
||||
out_count = 0
|
||||
pbar = tqdm(data, desc=f'indexing documents')
|
||||
for text in pbar:
|
||||
words = analyzer(text)
|
||||
index = []
|
||||
for word in words:
|
||||
if word in vocab:
|
||||
idx = vocab[word]
|
||||
else:
|
||||
if word in known_words:
|
||||
if word not in out_of_vocabulary:
|
||||
out_of_vocabulary[word] = vocabsize+len(out_of_vocabulary)
|
||||
idx = out_of_vocabulary[word]
|
||||
out_count += 1
|
||||
else:
|
||||
idx = unk_index
|
||||
unk_count += 1
|
||||
index.append(idx)
|
||||
indexes.append(index)
|
||||
knw_count += len(index)
|
||||
pbar.set_description(f'[unk = {unk_count}/{knw_count}={(100.*unk_count/knw_count):.2f}%]'
|
||||
f'[out = {out_count}/{knw_count}={(100.*out_count/knw_count):.2f}%]')
|
||||
return indexes
|
||||
|
||||
|
||||
def define_pad_length(index_list):
|
||||
lengths = [len(index) for index in index_list]
|
||||
return int(np.mean(lengths)+np.std(lengths))
|
||||
|
||||
|
||||
def pad(index_list, pad_index, max_pad_length=None):
|
||||
pad_length = np.max([len(index) for index in index_list])
|
||||
if max_pad_length is not None:
|
||||
pad_length = min(pad_length, max_pad_length)
|
||||
for i,indexes in enumerate(index_list):
|
||||
index_list[i] = [pad_index]*(pad_length-len(indexes)) + indexes[:pad_length]
|
||||
return index_list
|
||||
|
||||
|
||||
class Index:
|
||||
def __init__(self, devel_raw, devel_target, test_raw, lang):
|
||||
self.lang = lang
|
||||
self.devel_raw = devel_raw
|
||||
self.devel_target = devel_target
|
||||
self.test_raw = test_raw
|
||||
|
||||
def index(self, pretrained_vocabulary, analyzer, vocabulary):
|
||||
self.word2index = dict(vocabulary) # word2idx
|
||||
known_words = set(self.word2index.keys())
|
||||
if pretrained_vocabulary is not None:
|
||||
known_words.update(pretrained_vocabulary)
|
||||
|
||||
self.word2index['UNKTOKEN'] = len(self.word2index)
|
||||
self.word2index['PADTOKEN'] = len(self.word2index)
|
||||
self.unk_index = self.word2index['UNKTOKEN']
|
||||
self.pad_index = self.word2index['PADTOKEN']
|
||||
|
||||
# index documents and keep track of test terms outside the development vocabulary that are in Muse (if available)
|
||||
self.out_of_vocabulary = dict()
|
||||
self.devel_index = index(self.devel_raw, self.word2index, known_words, analyzer, self.unk_index, self.out_of_vocabulary)
|
||||
self.test_index = index(self.test_raw, self.word2index, known_words, analyzer, self.unk_index, self.out_of_vocabulary)
|
||||
|
||||
self.vocabsize = len(self.word2index) + len(self.out_of_vocabulary)
|
||||
|
||||
print(f'[indexing complete for lang {self.lang}] vocabulary-size={self.vocabsize}')
|
||||
|
||||
def train_val_split(self, val_prop, max_val, seed):
|
||||
devel = self.devel_index
|
||||
target = self.devel_target
|
||||
devel_raw = self.devel_raw
|
||||
|
||||
val_size = int(min(len(devel) * val_prop, max_val))
|
||||
|
||||
self.train_index, self.val_index, self.train_target, self.val_target, self.train_raw, self.val_raw = \
|
||||
train_test_split(
|
||||
devel, target, devel_raw, test_size=val_size, random_state=seed, shuffle=True
|
||||
)
|
||||
|
||||
print(f'split lang {self.lang}: train={len(self.train_index)} val={len(self.val_index)} test={len(self.test_index)}')
|
||||
|
||||
def get_word_list(self):
|
||||
def extract_word_list(word2index):
|
||||
return [w for w,i in sorted(word2index.items(), key=lambda x: x[1])]
|
||||
|
||||
word_list = extract_word_list(self.word2index)
|
||||
word_list += extract_word_list(self.out_of_vocabulary)
|
||||
return word_list
|
||||
|
||||
def compose_embedding_matrix(self, pretrained, supervised, Xtr=None, Ytr=None):
|
||||
print(f'[generating embedding matrix for lang {self.lang}]')
|
||||
|
||||
self.wce_range = None
|
||||
embedding_parts = []
|
||||
|
||||
if pretrained is not None:
|
||||
print('\t[pretrained-matrix]')
|
||||
word_list = self.get_word_list()
|
||||
muse_embeddings = pretrained.extract(word_list)
|
||||
embedding_parts.append(muse_embeddings)
|
||||
del pretrained
|
||||
|
||||
if supervised:
|
||||
print('\t[supervised-matrix]')
|
||||
F = get_supervised_embeddings(Xtr, Ytr, reduction=None, method='dotn')
|
||||
num_missing_rows = self.vocabsize - F.shape[0]
|
||||
F = np.vstack((F, np.zeros(shape=(num_missing_rows, F.shape[1]))))
|
||||
F = torch.from_numpy(F).float()
|
||||
|
||||
offset = 0
|
||||
if embedding_parts:
|
||||
offset = embedding_parts[0].shape[1]
|
||||
self.wce_range = [offset, offset + F.shape[1]]
|
||||
|
||||
embedding_parts.append(F)
|
||||
|
||||
make_dumps = False
|
||||
if make_dumps:
|
||||
print(f'Dumping Embedding Matrices ...')
|
||||
import pickle
|
||||
with open(f'../dumps/dump_{self.lang}_rcv.pkl', 'wb') as outfile:
|
||||
pickle.dump((self.lang, embedding_parts, self.word2index), outfile)
|
||||
with open(f'../dumps/corpus_{self.lang}_rcv.pkl', 'wb') as outfile2:
|
||||
pickle.dump((self.lang, self.devel_raw, self.devel_target), outfile2)
|
||||
|
||||
self.embedding_matrix = torch.cat(embedding_parts, dim=1)
|
||||
|
||||
print(f'[embedding matrix for lang {self.lang} has shape {self.embedding_matrix.shape}]')
|
||||
|
||||
|
||||
def none_dict(langs):
|
||||
return {l:None for l in langs}
|
||||
|
||||
|
||||
class MultilingualIndex:
|
||||
def __init__(self): #, add_language_trace=False):
|
||||
self.l_index = {}
|
||||
self.l_vectorizer = TfidfVectorizerMultilingual(sublinear_tf=True, use_idf=True)
|
||||
# self.l_vectorizer = TfidfVectorizerMultilingual(sublinear_tf=True, use_idf=True, max_features=25000)
|
||||
# self.add_language_trace=add_language_trace}
|
||||
|
||||
def index(self, l_devel_raw, l_devel_target, l_test_raw, l_pretrained_vocabulary):
|
||||
self.langs = sorted(l_devel_raw.keys())
|
||||
|
||||
#build the vocabularies
|
||||
self.l_vectorizer.fit(l_devel_raw)
|
||||
l_vocabulary = self.l_vectorizer.vocabulary()
|
||||
l_analyzer = self.l_vectorizer.get_analyzer()
|
||||
|
||||
for l in self.langs:
|
||||
self.l_index[l] = Index(l_devel_raw[l], l_devel_target[l], l_test_raw[l], l)
|
||||
self.l_index[l].index(l_pretrained_vocabulary[l], l_analyzer[l], l_vocabulary[l])
|
||||
|
||||
def get_indexed(self, l_texts, pretrained_vocabulary=None):
|
||||
assert len(self.l_index) != 0, 'Cannot index data without first index call to multilingual index!'
|
||||
l_indexed = {}
|
||||
for l, texts in l_texts.items():
|
||||
if l in self.langs:
|
||||
word2index = self.l_index[l].word2index
|
||||
known_words = set(word2index.keys())
|
||||
if pretrained_vocabulary[l] is not None:
|
||||
known_words.update(pretrained_vocabulary[l])
|
||||
l_indexed[l] = index(texts,
|
||||
vocab=word2index,
|
||||
known_words=known_words,
|
||||
analyzer=self.l_vectorizer.get_analyzer(l),
|
||||
unk_index=word2index['UNKTOKEN'],
|
||||
out_of_vocabulary=dict())
|
||||
return l_indexed
|
||||
|
||||
def train_val_split(self, val_prop=0.2, max_val=2000, seed=42):
|
||||
for l,index in self.l_index.items():
|
||||
index.train_val_split(val_prop, max_val, seed=seed)
|
||||
|
||||
def embedding_matrices(self, lpretrained, supervised):
|
||||
lXtr = self.get_lXtr() if supervised else none_dict(self.langs)
|
||||
lYtr = self.l_train_target() if supervised else none_dict(self.langs)
|
||||
for l,index in self.l_index.items():
|
||||
index.compose_embedding_matrix(lpretrained[l], supervised, lXtr[l], lYtr[l])
|
||||
self.sup_range = index.wce_range
|
||||
|
||||
|
||||
def bert_embeddings(self, bert_path, max_len=512, batch_size=64, stored_embeddings=False):
|
||||
show_gpu('GPU memory before initializing mBert model:')
|
||||
# TODO: load dumped embeddings?
|
||||
from experiment_scripts.main_mbert_extractor import do_tokenization, ExtractorDataset, DataLoader
|
||||
from transformers import BertConfig, BertForSequenceClassification
|
||||
|
||||
print('[mBERT] generating mBERT doc embeddings')
|
||||
lXtr_raw = self.get_raw_lXtr()
|
||||
lXva_raw = self.get_raw_lXva()
|
||||
lXte_raw = self.get_raw_lXte()
|
||||
|
||||
print('# Tokenizing datasets')
|
||||
l_tokenized_tr = do_tokenization(lXtr_raw, max_len=max_len, verbose=False)
|
||||
tr_dataset = ExtractorDataset(l_tokenized_tr)
|
||||
tr_lang_ids = tr_dataset.lang_ids
|
||||
tr_dataloader = DataLoader(tr_dataset, batch_size=batch_size, shuffle=False)
|
||||
|
||||
l_tokenized_va = do_tokenization(lXva_raw, max_len=max_len, verbose=False)
|
||||
va_dataset = ExtractorDataset(l_tokenized_va)
|
||||
va_lang_ids = va_dataset.lang_ids
|
||||
va_dataloader = DataLoader(va_dataset, batch_size=batch_size, shuffle=False)
|
||||
|
||||
l_tokenized_te = do_tokenization(lXte_raw, max_len=max_len, verbose=False)
|
||||
te_dataset = ExtractorDataset(l_tokenized_te)
|
||||
te_lang_ids = te_dataset.lang_ids
|
||||
te_dataloader = DataLoader(te_dataset, batch_size=batch_size, shuffle=False)
|
||||
|
||||
num_labels = self.l_index[self.langs[0]].val_target.shape[1]
|
||||
config = BertConfig.from_pretrained('bert-base-multilingual-cased', output_hidden_states=True,
|
||||
num_labels=num_labels)
|
||||
model = BertForSequenceClassification.from_pretrained(bert_path,
|
||||
config=config).cuda()
|
||||
print('# Extracting document embeddings')
|
||||
tr_bert_embeddings, id2lang_tr = self.do_bert_embeddings(model, tr_dataloader, tr_lang_ids, verbose=False)
|
||||
va_bert_embeddings, id2lang_va = self.do_bert_embeddings(model, va_dataloader, va_lang_ids, verbose=False)
|
||||
te_bert_embeddings, id2lang_te = self.do_bert_embeddings(model, te_dataloader, te_lang_ids, verbose=False)
|
||||
|
||||
show_gpu('GPU memory before after mBert model:')
|
||||
# Freeing GPU's memory
|
||||
import gc
|
||||
del model, tr_dataloader, va_dataloader, te_dataloader
|
||||
gc.collect()
|
||||
torch.cuda.empty_cache()
|
||||
show_gpu('GPU memory after clearing cache:')
|
||||
return tr_bert_embeddings, va_bert_embeddings, te_bert_embeddings
|
||||
|
||||
|
||||
@staticmethod
|
||||
def do_bert_embeddings(model, data, lang_ids, verbose=True):
|
||||
if verbose:
|
||||
print('# Feature Extractor Mode...')
|
||||
all_batch_embeddings = {}
|
||||
id2lang = {v: k for k, v in lang_ids.items()}
|
||||
with torch.no_grad():
|
||||
for batch, lang_idx in data:
|
||||
out = model(batch.cuda())
|
||||
last_hidden_state = out[1][-1]
|
||||
batch_embeddings = last_hidden_state[:, 0, :]
|
||||
for i, l_idx in enumerate(lang_idx.numpy()):
|
||||
if id2lang[l_idx] not in all_batch_embeddings.keys():
|
||||
all_batch_embeddings[id2lang[l_idx]] = batch_embeddings[i].detach().cpu().numpy()
|
||||
else:
|
||||
all_batch_embeddings[id2lang[l_idx]] = np.vstack((all_batch_embeddings[id2lang[l_idx]],
|
||||
batch_embeddings[i].detach().cpu().numpy()))
|
||||
|
||||
return all_batch_embeddings, id2lang
|
||||
|
||||
def get_raw_lXtr(self):
|
||||
lXtr_raw = {k:[] for k in self.langs}
|
||||
lYtr_raw = {k: [] for k in self.langs}
|
||||
for lang in self.langs:
|
||||
lXtr_raw[lang] = self.l_index[lang].train_raw
|
||||
lYtr_raw[lang] = self.l_index[lang].train_raw
|
||||
return lXtr_raw
|
||||
|
||||
def get_raw_lXva(self):
|
||||
lXva_raw = {k: [] for k in self.langs}
|
||||
for lang in self.langs:
|
||||
lXva_raw[lang] = self.l_index[lang].val_raw
|
||||
|
||||
return lXva_raw
|
||||
|
||||
def get_raw_lXte(self):
|
||||
lXte_raw = {k: [] for k in self.langs}
|
||||
for lang in self.langs:
|
||||
lXte_raw[lang] = self.l_index[lang].test_raw
|
||||
|
||||
return lXte_raw
|
||||
|
||||
def get_lXtr(self):
|
||||
if not hasattr(self, 'lXtr'):
|
||||
self.lXtr = self.l_vectorizer.transform({l: index.train_raw for l, index in self.l_index.items()})
|
||||
return self.lXtr
|
||||
|
||||
def get_lXva(self):
|
||||
if not hasattr(self, 'lXva'):
|
||||
self.lXva = self.l_vectorizer.transform({l: index.val_raw for l, index in self.l_index.items()})
|
||||
return self.lXva
|
||||
|
||||
def get_lXte(self):
|
||||
if not hasattr(self, 'lXte'):
|
||||
self.lXte = self.l_vectorizer.transform({l: index.test_raw for l, index in self.l_index.items()})
|
||||
return self.lXte
|
||||
|
||||
def l_vocabsize(self):
|
||||
return {l:index.vocabsize for l,index in self.l_index.items()}
|
||||
|
||||
def l_embeddings(self):
|
||||
return {l:index.embedding_matrix for l,index in self.l_index.items()}
|
||||
|
||||
def l_pad(self):
|
||||
return {l: index.pad_index for l, index in self.l_index.items()}
|
||||
|
||||
def l_train_index(self):
|
||||
return {l: index.train_index for l, index in self.l_index.items()}
|
||||
|
||||
def l_train_target(self):
|
||||
return {l: index.train_target for l, index in self.l_index.items()}
|
||||
|
||||
def l_val_index(self):
|
||||
return {l: index.val_index for l, index in self.l_index.items()}
|
||||
|
||||
def l_val_target(self):
|
||||
return {l: index.val_target for l, index in self.l_index.items()}
|
||||
|
||||
def l_test_index(self):
|
||||
return {l: index.test_index for l, index in self.l_index.items()}
|
||||
|
||||
def l_devel_index(self):
|
||||
return {l: index.devel_index for l, index in self.l_index.items()}
|
||||
|
||||
def l_devel_target(self):
|
||||
return {l: index.devel_target for l, index in self.l_index.items()}
|
||||
|
||||
def l_train(self):
|
||||
return self.l_train_index(), self.l_train_target()
|
||||
|
||||
def l_val(self):
|
||||
return self.l_val_index(), self.l_val_target()
|
||||
|
||||
|
||||
class Batch:
|
||||
def __init__(self, batchsize, batches_per_epoch, languages, lpad, max_pad_length=500):
|
||||
self.batchsize = batchsize
|
||||
self.batches_per_epoch = batches_per_epoch
|
||||
self.languages = languages
|
||||
self.lpad=lpad
|
||||
self.max_pad_length=max_pad_length
|
||||
self.init_offset()
|
||||
|
||||
def init_offset(self):
|
||||
self.offset = {lang: 0 for lang in self.languages}
|
||||
|
||||
def batchify(self, l_index, l_post, l_bert, llabels):
|
||||
langs = self.languages
|
||||
l_num_samples = {l:len(l_index[l]) for l in langs}
|
||||
|
||||
max_samples = max(l_num_samples.values())
|
||||
n_batches = max_samples // self.batchsize + 1 * (max_samples % self.batchsize > 0)
|
||||
if self.batches_per_epoch != -1 and self.batches_per_epoch < n_batches:
|
||||
n_batches = self.batches_per_epoch
|
||||
|
||||
for b in range(n_batches):
|
||||
for lang in langs:
|
||||
index, labels = l_index[lang], llabels[lang]
|
||||
offset = self.offset[lang]
|
||||
if offset >= l_num_samples[lang]:
|
||||
offset = 0
|
||||
limit = offset+self.batchsize
|
||||
|
||||
batch_slice = slice(offset, limit)
|
||||
batch = index[batch_slice]
|
||||
batch_labels = labels[batch_slice].toarray()
|
||||
|
||||
post = None
|
||||
if l_post is not None:
|
||||
post = torch.FloatTensor(l_post[lang][batch_slice]).cuda()
|
||||
|
||||
bert_emb = None
|
||||
if l_bert is not None:
|
||||
bert_emb = torch.FloatTensor(l_bert[lang][batch_slice]).cuda()
|
||||
|
||||
batch = pad(batch, pad_index=self.lpad[lang], max_pad_length=self.max_pad_length)
|
||||
|
||||
batch = torch.LongTensor(batch).cuda()
|
||||
target = torch.FloatTensor(batch_labels).cuda()
|
||||
|
||||
self.offset[lang] = limit
|
||||
|
||||
yield batch, post, bert_emb, target, lang
|
||||
|
||||
|
||||
def batchify(l_index, l_post, llabels, batchsize, lpad, max_pad_length=500):
|
||||
langs = sorted(l_index.keys())
|
||||
nsamples = max([len(l_index[l]) for l in langs])
|
||||
nbatches = nsamples // batchsize + 1*(nsamples%batchsize>0)
|
||||
for b in range(nbatches):
|
||||
for lang in langs:
|
||||
index, labels = l_index[lang], llabels[lang]
|
||||
|
||||
if b * batchsize >= len(index):
|
||||
continue
|
||||
batch = index[b*batchsize:(b+1)*batchsize]
|
||||
batch_labels = labels[b*batchsize:(b+1)*batchsize].toarray()
|
||||
post = None
|
||||
if l_post is not None:
|
||||
post = torch.FloatTensor(l_post[lang][b*batchsize:(b+1)*batchsize]).cuda()
|
||||
batch = pad(batch, pad_index=lpad[lang], max_pad_length=max_pad_length)
|
||||
batch = torch.LongTensor(batch)
|
||||
target = torch.FloatTensor(batch_labels)
|
||||
yield batch.cuda(), post, target.cuda(), lang
|
||||
|
||||
|
||||
def batchify_unlabelled(index_list, batchsize, pad_index, max_pad_length=500):
|
||||
nsamples = len(index_list)
|
||||
nbatches = nsamples // batchsize + 1*(nsamples%batchsize>0)
|
||||
for b in range(nbatches):
|
||||
batch = index_list[b*batchsize:(b+1)*batchsize]
|
||||
batch = pad(batch, pad_index=pad_index, max_pad_length=max_pad_length)
|
||||
batch = torch.LongTensor(batch)
|
||||
yield batch.cuda()
|
||||
|
||||
|
||||
def clip_gradient(model, clip_value=1e-1):
|
||||
params = list(filter(lambda p: p.grad is not None, model.parameters()))
|
||||
for p in params:
|
||||
p.grad.data.clamp_(-clip_value, clip_value)
|
||||
|
||||
|
||||
def predict(logits, classification_type='multilabel'):
|
||||
if classification_type == 'multilabel':
|
||||
prediction = torch.sigmoid(logits) > 0.5
|
||||
elif classification_type == 'singlelabel':
|
||||
prediction = torch.argmax(logits, dim=1).view(-1, 1)
|
||||
else:
|
||||
print('unknown classification type')
|
||||
|
||||
return prediction.detach().cpu().numpy()
|
||||
|
||||
|
||||
def count_parameters(model):
|
||||
return sum(p.numel() for p in model.parameters() if p.requires_grad)
|
||||
|
||||
|
||||
def show_gpu(msg):
|
||||
"""
|
||||
ref: https://discuss.pytorch.org/t/access-gpu-memory-usage-in-pytorch/3192/4
|
||||
"""
|
||||
|
||||
def query(field):
|
||||
return (subprocess.check_output(
|
||||
['nvidia-smi', f'--query-gpu={field}',
|
||||
'--format=csv,nounits,noheader'],
|
||||
encoding='utf-8'))
|
||||
|
||||
def to_int(result):
|
||||
return int(result.strip().split('\n')[0])
|
||||
|
||||
used = to_int(query('memory.used'))
|
||||
total = to_int(query('memory.total'))
|
||||
pct = used / total
|
||||
print('\n' + msg, f'{100 * pct:2.1f}% ({used} out of {total})')
|
||||
|
||||
|
||||
class TfidfVectorizerMultilingual:
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
|
||||
def fit(self, lX, ly=None):
|
||||
self.langs = sorted(lX.keys())
|
||||
self.vectorizer = {l: TfidfVectorizer(**self.kwargs).fit(lX[l]) for l in self.langs}
|
||||
# self.vectorizer = {l: TfidfVectorizer(**self.kwargs).fit(lX[l]) for l in lX.keys()}
|
||||
return self
|
||||
|
||||
def transform(self, lX):
|
||||
return {l: self.vectorizer[l].transform(lX[l]) for l in self.langs}
|
||||
# return {l: self.vectorizer[l].transform(lX[l]) for l in lX.keys()}
|
||||
|
||||
def fit_transform(self, lX, ly=None):
|
||||
return self.fit(lX, ly).transform(lX)
|
||||
|
||||
def vocabulary(self, l=None):
|
||||
if l is None:
|
||||
return {l: self.vectorizer[l].vocabulary_ for l in self.langs}
|
||||
else:
|
||||
return self.vectorizer[l].vocabulary_
|
||||
|
||||
def get_analyzer(self, l=None):
|
||||
if l is None:
|
||||
return {l: self.vectorizer[l].build_analyzer() for l in self.langs}
|
||||
else:
|
||||
return self.vectorizer[l].build_analyzer()
|
||||
|
||||
|
||||
def get_learner(calibrate=False, kernel='linear', C=1):
|
||||
return SVC(kernel=kernel, probability=calibrate, cache_size=1000, C=C, random_state=1, gamma='auto', verbose=False)
|
||||
|
||||
|
||||
def get_params(optimc=False):
|
||||
if not optimc:
|
||||
return None
|
||||
c_range = [1e4, 1e3, 1e2, 1e1, 1, 1e-1]
|
||||
kernel = 'rbf'
|
||||
return [{'kernel': [kernel], 'C': c_range, 'gamma':['auto']}]
|
||||
|
||||
|
||||
def get_method_name(dataset, posteriors, supervised, pretrained, mbert, gru,
|
||||
gruMUSE, gruWCE, agg, allprob):
|
||||
_id = '-'
|
||||
_id_conf = [posteriors, supervised, pretrained, mbert, gru]
|
||||
_id_name = ['X', 'W', 'M', 'B', 'G']
|
||||
for i, conf in enumerate(_id_conf):
|
||||
if conf:
|
||||
_id += _id_name[i]
|
||||
_id = _id if not gruMUSE else _id + '_muse'
|
||||
_id = _id if not gruWCE else _id + '_wce'
|
||||
_id = _id if not agg else _id + '_mean'
|
||||
_id = _id if not allprob else _id + '_allprob'
|
||||
|
||||
_dataset_path = dataset.split('/')[-1].split('_')
|
||||
dataset_id = _dataset_path[0] + _dataset_path[-1]
|
||||
return _id, dataset_id
|
||||
|
||||
|
||||
def get_zscl_setting(langs):
|
||||
settings = []
|
||||
for elem in langs:
|
||||
for tar in langs:
|
||||
if elem != tar:
|
||||
settings.append((elem, tar))
|
||||
return settings
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
import os
|
||||
import pandas as pd
|
||||
pd.set_option('display.max_rows', 500)
|
||||
pd.set_option('display.max_columns', 500)
|
||||
pd.set_option('display.width', 1000)
|
||||
|
||||
|
||||
class CSVLog:
|
||||
|
||||
def __init__(self, file, columns=None, autoflush=True, verbose=False, overwrite=False):
|
||||
self.file = file
|
||||
self.autoflush = autoflush
|
||||
self.verbose = verbose
|
||||
if os.path.exists(file) and not overwrite:
|
||||
self.tell('Loading existing file from {}'.format(file))
|
||||
self.df = pd.read_csv(file, sep='\t')
|
||||
self.columns = sorted(self.df.columns.values.tolist())
|
||||
else:
|
||||
self.tell('File {} does not exist or overwrite=True. Creating new frame.'.format(file))
|
||||
assert columns is not None, 'columns cannot be None'
|
||||
self.columns = sorted(columns)
|
||||
dir = os.path.dirname(self.file)
|
||||
if dir and not os.path.exists(dir): os.makedirs(dir)
|
||||
self.df = pd.DataFrame(columns=self.columns)
|
||||
self.defaults={}
|
||||
|
||||
def already_calculated(self, **kwargs):
|
||||
df = self.df
|
||||
if df.shape[0]==0:
|
||||
return False
|
||||
if len(kwargs)==0:
|
||||
kwargs = self.defaults
|
||||
for key,val in kwargs.items():
|
||||
df = df.loc[df[key]==val]
|
||||
if df.shape[0]==0: return False
|
||||
return True
|
||||
|
||||
def set_default(self, param, value):
|
||||
self.defaults[param]=value
|
||||
|
||||
def add_row(self, **kwargs):
|
||||
for key in self.defaults.keys():
|
||||
if key not in kwargs:
|
||||
kwargs[key]=self.defaults[key]
|
||||
colums = sorted(list(kwargs.keys()))
|
||||
values = [kwargs[col_i] for col_i in colums]
|
||||
s = pd.Series(values, index=self.columns)
|
||||
self.df = self.df.append(s, ignore_index=True)
|
||||
if self.autoflush: self.flush()
|
||||
# self.tell(s.to_string())
|
||||
self.tell(kwargs)
|
||||
|
||||
def flush(self):
|
||||
self.df.to_csv(self.file, index=False, sep='\t')
|
||||
|
||||
def tell(self, msg):
|
||||
if self.verbose: print(msg)
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
from sklearn.decomposition import PCA
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
def run_pca(dim, X):
|
||||
"""
|
||||
:param dim: number of pca components to keep
|
||||
:param X: dictionary str(lang): matrix
|
||||
:return: dict lang: reduced matrix
|
||||
"""
|
||||
r = dict()
|
||||
pca = PCA(n_components=dim)
|
||||
for lang in X.keys():
|
||||
r[lang] = pca.fit_transform(X[lang])
|
||||
return r
|
||||
|
||||
|
||||
def get_optimal_dim(X, embed_type):
|
||||
"""
|
||||
:param X: dict str(lang) : csr_matrix of embeddings unsupervised or supervised
|
||||
:param embed_type: (str) embedding matrix type: S or U (WCE supervised or U unsupervised MUSE/FASTTEXT)
|
||||
:return:
|
||||
"""
|
||||
_idx = []
|
||||
|
||||
plt.figure(figsize=(15, 10))
|
||||
if embed_type == 'U':
|
||||
plt.title(f'Unsupervised Embeddings {"TODO"} Explained Variance')
|
||||
else:
|
||||
plt.title(f'WCE Explained Variance')
|
||||
plt.xlabel('Number of Components')
|
||||
plt.ylabel('Variance (%)')
|
||||
|
||||
for lang in X.keys():
|
||||
pca = PCA(n_components=X[lang].shape[1])
|
||||
pca.fit(X[lang])
|
||||
_r = pca.explained_variance_ratio_
|
||||
_r = np.cumsum(_r)
|
||||
plt.plot(_r, label=lang)
|
||||
for i in range(len(_r) - 1, 1, -1):
|
||||
delta = _r[i] - _r[i - 1]
|
||||
if delta > 0:
|
||||
_idx.append(i)
|
||||
break
|
||||
best_n = max(_idx)
|
||||
plt.axvline(best_n, color='r', label='optimal N')
|
||||
plt.legend()
|
||||
plt.show()
|
||||
return best_n
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
#adapted from https://github.com/Bjarten/early-stopping-pytorch/blob/master/pytorchtools.py
|
||||
import torch
|
||||
from transformers import BertForSequenceClassification
|
||||
from time import time
|
||||
from util.file import create_if_not_exist
|
||||
import warnings
|
||||
|
||||
class EarlyStopping:
|
||||
|
||||
def __init__(self, model, optimizer, patience=20, verbose=True, checkpoint='./checkpoint.pt', is_bert=False):
|
||||
# set patience to 0 or -1 to avoid stopping, but still keeping track of the best value and model parameters
|
||||
self.patience_limit = patience
|
||||
self.patience = patience
|
||||
self.verbose = verbose
|
||||
self.best_score = None
|
||||
self.best_epoch = None
|
||||
self.stop_time = None
|
||||
self.checkpoint = checkpoint
|
||||
self.model = model
|
||||
self.optimizer = optimizer
|
||||
self.STOP = False
|
||||
self.is_bert = is_bert
|
||||
|
||||
def __call__(self, watch_score, epoch):
|
||||
|
||||
if self.STOP:
|
||||
return
|
||||
|
||||
if self.best_score is None or watch_score >= self.best_score:
|
||||
self.best_score = watch_score
|
||||
self.best_epoch = epoch
|
||||
self.stop_time = time()
|
||||
if self.checkpoint:
|
||||
self.print(f'[early-stop] improved, saving model in {self.checkpoint}')
|
||||
if self.is_bert:
|
||||
print(f'Serializing Huggingface model...')
|
||||
create_if_not_exist(self.checkpoint)
|
||||
self.model.save_pretrained(self.checkpoint)
|
||||
else:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
torch.save(self.model, self.checkpoint)
|
||||
# with open(self.checkpoint)
|
||||
# torch.save({'state_dict': self.model.state_dict(),
|
||||
# 'optimizer_state_dict': self.optimizer.state_dict()}, self.checkpoint)
|
||||
else:
|
||||
self.print(f'[early-stop] improved')
|
||||
self.patience = self.patience_limit
|
||||
else:
|
||||
self.patience -= 1
|
||||
if self.patience == 0:
|
||||
self.STOP = True
|
||||
self.print(f'[early-stop] patience exhausted')
|
||||
else:
|
||||
if self.patience>0: # if negative, then early-stop is ignored
|
||||
self.print(f'[early-stop] patience={self.patience}')
|
||||
|
||||
def reinit_counter(self):
|
||||
self.STOP = False
|
||||
self.patience=self.patience_limit
|
||||
|
||||
def restore_checkpoint(self):
|
||||
print(f'restoring best model from epoch {self.best_epoch}...')
|
||||
if self.is_bert:
|
||||
return BertForSequenceClassification.from_pretrained(self.checkpoint)
|
||||
else:
|
||||
return torch.load(self.checkpoint)
|
||||
|
||||
def print(self, msg):
|
||||
if self.verbose:
|
||||
print(msg)
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
# from sklearn.externals.joblib import Parallel, delayed
|
||||
from joblib import Parallel, delayed
|
||||
from util.metrics import *
|
||||
from sklearn.metrics import f1_score
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
|
||||
def evaluation_metrics(y, y_):
|
||||
if len(y.shape)==len(y_.shape)==1 and len(np.unique(y))>2: #single-label
|
||||
raise NotImplementedError()#return f1_score(y,y_,average='macro'), f1_score(y,y_,average='micro')
|
||||
else: #the metrics I implemented assume multiclass multilabel classification as binary classifiers
|
||||
return macroF1(y, y_), microF1(y, y_), macroK(y, y_), microK(y, y_)
|
||||
|
||||
|
||||
def soft_evaluation_metrics(y, y_):
|
||||
if len(y.shape)==len(y_.shape)==1 and len(np.unique(y))>2: #single-label
|
||||
raise NotImplementedError()#return f1_score(y,y_,average='macro'), f1_score(y,y_,average='micro')
|
||||
else: #the metrics I implemented assume multiclass multilabel classification as binary classifiers
|
||||
return smoothmacroF1(y, y_), smoothmicroF1(y, y_), smoothmacroK(y, y_), smoothmicroK(y, y_)
|
||||
|
||||
|
||||
def evaluate(ly_true, ly_pred, metrics=evaluation_metrics, n_jobs=-1):
|
||||
print('evaluation (n_jobs={})'.format(n_jobs))
|
||||
if n_jobs == 1:
|
||||
return {lang: metrics(ly_true[lang], ly_pred[lang]) for lang in ly_true.keys()}
|
||||
else:
|
||||
langs = list(ly_true.keys())
|
||||
evals = Parallel(n_jobs=n_jobs)(delayed(metrics)(ly_true[lang], ly_pred[lang]) for lang in langs)
|
||||
return {lang: evals[i] for i, lang in enumerate(langs)}
|
||||
|
||||
|
||||
def average_results(l_eval, show=True):
|
||||
metrics = []
|
||||
for lang in l_eval.keys():
|
||||
macrof1, microf1, macrok, microk = l_eval[lang]
|
||||
metrics.append([macrof1, microf1, macrok, microk])
|
||||
if show:
|
||||
print('Lang %s: macro-F1=%.3f micro-F1=%.3f' % (lang, macrof1, microf1))
|
||||
|
||||
ave = np.mean(np.array(metrics), axis=0)
|
||||
if show:
|
||||
print('Averages: MF1, mF1, MK, mK', ave)
|
||||
return ave
|
||||
|
||||
|
||||
def evaluate_method(polylingual_method, lX, ly, predictor=None, soft=False, return_time=False):
|
||||
tinit = time.time()
|
||||
print('prediction for test')
|
||||
assert set(lX.keys()) == set(ly.keys()), 'inconsistent dictionaries in evaluate'
|
||||
n_jobs = polylingual_method.n_jobs if hasattr(polylingual_method, 'n_jobs') else -1
|
||||
|
||||
if predictor is None:
|
||||
predictor = polylingual_method.predict
|
||||
|
||||
metrics = evaluation_metrics
|
||||
if soft is True:
|
||||
metrics = soft_evaluation_metrics
|
||||
ly_ = predictor(lX, ly)
|
||||
|
||||
eval_ = evaluate(ly, ly_, metrics=metrics, n_jobs=n_jobs)
|
||||
if return_time:
|
||||
return eval_, time.time()-tinit
|
||||
else:
|
||||
return eval_
|
||||
|
||||
|
||||
def evaluate_single_lang(polylingual_method, X, y, lang, predictor=None, soft=False):
|
||||
print('prediction for test in a single language')
|
||||
if predictor is None:
|
||||
predictor = polylingual_method.predict
|
||||
|
||||
metrics = evaluation_metrics
|
||||
if soft is True:
|
||||
metrics = soft_evaluation_metrics
|
||||
|
||||
ly_ = predictor({lang:X})
|
||||
return metrics(y, ly_[lang])
|
||||
|
||||
|
||||
def get_binary_counters(polylingual_method, lX, ly, predictor=None):
|
||||
print('prediction for test')
|
||||
assert set(lX.keys()) == set(ly.keys()), 'inconsistent dictionaries in evaluate'
|
||||
n_jobs = polylingual_method.n_jobs
|
||||
if predictor is None:
|
||||
predictor = polylingual_method.predict
|
||||
ly_ = predictor(lX)
|
||||
print('evaluation (n_jobs={})'.format(n_jobs))
|
||||
if n_jobs == 1:
|
||||
return {lang: binary_counters(ly[lang], ly_[lang]) for lang in ly.keys()}
|
||||
else:
|
||||
langs = list(ly.keys())
|
||||
evals = Parallel(n_jobs=n_jobs)(delayed(binary_counters)(ly[lang], ly_[lang]) for lang in langs)
|
||||
return {lang: evals[i] for i, lang in enumerate(langs)}
|
||||
|
||||
|
||||
def binary_counters(y, y_):
|
||||
y = np.reshape(y, (-1))
|
||||
assert y.shape==y_.shape and len(y.shape)==1, 'error, binary vector expected'
|
||||
counters = hard_single_metric_statistics(y, y_)
|
||||
return counters.tp, counters.tn, counters.fp, counters.fn
|
||||
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
from os import listdir, makedirs
|
||||
from os.path import isdir, isfile, join, exists, dirname
|
||||
#from sklearn.externals.six.moves import urllib
|
||||
import urllib
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def download_file(url, archive_filename):
|
||||
def progress(blocknum, bs, size):
|
||||
total_sz_mb = '%.2f MB' % (size / 1e6)
|
||||
current_sz_mb = '%.2f MB' % ((blocknum * bs) / 1e6)
|
||||
print('\rdownloaded %s / %s' % (current_sz_mb, total_sz_mb), end='')
|
||||
print("Downloading %s" % url)
|
||||
urllib.request.urlretrieve(url, filename=archive_filename, reporthook=progress)
|
||||
print("")
|
||||
|
||||
def download_file_if_not_exists(url, archive_path):
|
||||
if exists(archive_path): return
|
||||
makedirs_if_not_exist(dirname(archive_path))
|
||||
download_file(url,archive_path)
|
||||
|
||||
|
||||
def ls(dir, typecheck):
|
||||
el = [f for f in listdir(dir) if typecheck(join(dir, f))]
|
||||
el.sort()
|
||||
return el
|
||||
|
||||
def list_dirs(dir):
|
||||
return ls(dir, typecheck=isdir)
|
||||
|
||||
def list_files(dir):
|
||||
return ls(dir, typecheck=isfile)
|
||||
|
||||
def makedirs_if_not_exist(path):
|
||||
if not exists(path): makedirs(path)
|
||||
|
||||
def create_if_not_exist(path):
|
||||
if not exists(path): makedirs(path)
|
||||
|
||||
def get_parent_name(path):
|
||||
return Path(path).parent
|
||||
|
||||
def get_file_name(path):
|
||||
return Path(path).name
|
||||
|
|
@ -1,255 +0,0 @@
|
|||
import numpy as np
|
||||
from scipy.sparse import lil_matrix, issparse
|
||||
from sklearn.metrics import f1_score, accuracy_score
|
||||
|
||||
|
||||
|
||||
"""
|
||||
Scikit learn provides a full set of evaluation metrics, but they treat special cases differently.
|
||||
I.e., when the number of true positives, false positives, and false negatives ammount to 0, all
|
||||
affected metrices (precision, recall, and thus f1) output 0 in Scikit learn.
|
||||
We adhere to the common practice of outputting 1 in this case since the classifier has correctly
|
||||
classified all examples as negatives.
|
||||
"""
|
||||
|
||||
class ContTable:
|
||||
def __init__(self, tp=0, tn=0, fp=0, fn=0):
|
||||
self.tp=tp
|
||||
self.tn=tn
|
||||
self.fp=fp
|
||||
self.fn=fn
|
||||
|
||||
def get_d(self): return self.tp + self.tn + self.fp + self.fn
|
||||
|
||||
def get_c(self): return self.tp + self.fn
|
||||
|
||||
def get_not_c(self): return self.tn + self.fp
|
||||
|
||||
def get_f(self): return self.tp + self.fp
|
||||
|
||||
def get_not_f(self): return self.tn + self.fn
|
||||
|
||||
def p_c(self): return (1.0*self.get_c())/self.get_d()
|
||||
|
||||
def p_not_c(self): return 1.0-self.p_c()
|
||||
|
||||
def p_f(self): return (1.0*self.get_f())/self.get_d()
|
||||
|
||||
def p_not_f(self): return 1.0-self.p_f()
|
||||
|
||||
def p_tp(self): return (1.0*self.tp) / self.get_d()
|
||||
|
||||
def p_tn(self): return (1.0*self.tn) / self.get_d()
|
||||
|
||||
def p_fp(self): return (1.0*self.fp) / self.get_d()
|
||||
|
||||
def p_fn(self): return (1.0*self.fn) / self.get_d()
|
||||
|
||||
def tpr(self):
|
||||
c = 1.0*self.get_c()
|
||||
return self.tp / c if c > 0.0 else 0.0
|
||||
|
||||
def fpr(self):
|
||||
_c = 1.0*self.get_not_c()
|
||||
return self.fp / _c if _c > 0.0 else 0.0
|
||||
|
||||
def __add__(self, other):
|
||||
return ContTable(tp=self.tp + other.tp, tn=self.tn + other.tn, fp=self.fp + other.fp, fn=self.fn + other.fn)
|
||||
|
||||
def accuracy(cell):
|
||||
return (cell.tp + cell.tn)*1.0 / (cell.tp + cell.fp + cell.fn + cell.tn)
|
||||
|
||||
def f1(cell):
|
||||
num = 2.0 * cell.tp
|
||||
den = 2.0 * cell.tp + cell.fp + cell.fn
|
||||
if den>0: return num / den
|
||||
#we define f1 to be 1 if den==0 since the classifier has correctly classified all instances as negative
|
||||
return 1.0
|
||||
|
||||
def K(cell):
|
||||
specificity, recall = 0., 0.
|
||||
|
||||
AN = cell.tn + cell.fp
|
||||
if AN != 0:
|
||||
specificity = cell.tn*1. / AN
|
||||
|
||||
AP = cell.tp + cell.fn
|
||||
if AP != 0:
|
||||
recall = cell.tp*1. / AP
|
||||
|
||||
if AP == 0:
|
||||
return 2. * specificity - 1.
|
||||
elif AN == 0:
|
||||
return 2. * recall - 1.
|
||||
else:
|
||||
return specificity + recall - 1.
|
||||
|
||||
#computes the (hard) counters tp, fp, fn, and tn fron a true and predicted vectors of hard decisions
|
||||
#true_labels and predicted_labels are two vectors of shape (number_documents,)
|
||||
def hard_single_metric_statistics(true_labels, predicted_labels):
|
||||
assert len(true_labels)==len(predicted_labels), "Format not consistent between true and predicted labels."
|
||||
nd = len(true_labels)
|
||||
tp = np.sum(predicted_labels[true_labels==1])
|
||||
fp = np.sum(predicted_labels[true_labels == 0])
|
||||
fn = np.sum(true_labels[predicted_labels == 0])
|
||||
tn = nd - (tp+fp+fn)
|
||||
return ContTable(tp=tp, tn=tn, fp=fp, fn=fn)
|
||||
|
||||
#computes the (soft) contingency table where tp, fp, fn, and tn are the cumulative masses for the posterioir
|
||||
# probabilitiesfron with respect to the true binary labels
|
||||
#true_labels and posterior_probabilities are two vectors of shape (number_documents,)
|
||||
def soft_single_metric_statistics(true_labels, posterior_probabilities):
|
||||
assert len(true_labels)==len(posterior_probabilities), "Format not consistent between true and predicted labels."
|
||||
tp = np.sum(posterior_probabilities[true_labels == 1])
|
||||
fn = np.sum(1. - posterior_probabilities[true_labels == 1])
|
||||
fp = np.sum(posterior_probabilities[true_labels == 0])
|
||||
tn = np.sum(1. - posterior_probabilities[true_labels == 0])
|
||||
return ContTable(tp=tp, tn=tn, fp=fp, fn=fn)
|
||||
|
||||
#if the classifier is single class, then the prediction is a vector of shape=(nD,) which causes issues when compared
|
||||
#to the true labels (of shape=(nD,1)). This method increases the dimensions of the predictions.
|
||||
def __check_consistency_and_adapt(true_labels, predictions):
|
||||
if predictions.ndim == 1:
|
||||
return __check_consistency_and_adapt(true_labels, np.expand_dims(predictions, axis=1))
|
||||
if true_labels.ndim == 1:
|
||||
return __check_consistency_and_adapt(np.expand_dims(true_labels, axis=1),predictions)
|
||||
if true_labels.shape != predictions.shape:
|
||||
raise ValueError("True and predicted label matrices shapes are inconsistent %s %s."
|
||||
% (true_labels.shape, predictions.shape))
|
||||
_,nC = true_labels.shape
|
||||
return true_labels, predictions, nC
|
||||
|
||||
def macro_average(true_labels, predicted_labels, metric, metric_statistics=hard_single_metric_statistics):
|
||||
true_labels, predicted_labels, nC = __check_consistency_and_adapt(true_labels, predicted_labels)
|
||||
return np.mean([metric(metric_statistics(true_labels[:, c], predicted_labels[:, c])) for c in range(nC)])
|
||||
|
||||
def micro_average(true_labels, predicted_labels, metric, metric_statistics=hard_single_metric_statistics):
|
||||
true_labels, predicted_labels, nC = __check_consistency_and_adapt(true_labels, predicted_labels)
|
||||
|
||||
accum = ContTable()
|
||||
for c in range(nC):
|
||||
other = metric_statistics(true_labels[:, c], predicted_labels[:, c])
|
||||
accum = accum + other
|
||||
|
||||
return metric(accum)
|
||||
|
||||
#true_labels and predicted_labels are two matrices in sklearn.preprocessing.MultiLabelBinarizer format
|
||||
def macroF1(true_labels, predicted_labels):
|
||||
return macro_average(true_labels,predicted_labels, f1)
|
||||
|
||||
#true_labels and predicted_labels are two matrices in sklearn.preprocessing.MultiLabelBinarizer format
|
||||
def microF1(true_labels, predicted_labels):
|
||||
return micro_average(true_labels, predicted_labels, f1)
|
||||
|
||||
#true_labels and predicted_labels are two matrices in sklearn.preprocessing.MultiLabelBinarizer format
|
||||
def macroK(true_labels, predicted_labels):
|
||||
return macro_average(true_labels,predicted_labels, K)
|
||||
|
||||
#true_labels and predicted_labels are two matrices in sklearn.preprocessing.MultiLabelBinarizer format
|
||||
def microK(true_labels, predicted_labels):
|
||||
return micro_average(true_labels, predicted_labels, K)
|
||||
|
||||
#true_labels is a matrix in sklearn.preprocessing.MultiLabelBinarizer format and posterior_probabilities is a matrix
|
||||
#of the same shape containing real values in [0,1]
|
||||
def smoothmacroF1(true_labels, posterior_probabilities):
|
||||
return macro_average(true_labels,posterior_probabilities, f1, metric_statistics=soft_single_metric_statistics)
|
||||
|
||||
#true_labels is a matrix in sklearn.preprocessing.MultiLabelBinarizer format and posterior_probabilities is a matrix
|
||||
#of the same shape containing real values in [0,1]
|
||||
def smoothmicroF1(true_labels, posterior_probabilities):
|
||||
return micro_average(true_labels, posterior_probabilities, f1, metric_statistics=soft_single_metric_statistics)
|
||||
|
||||
#true_labels is a matrix in sklearn.preprocessing.MultiLabelBinarizer format and posterior_probabilities is a matrix
|
||||
#of the same shape containing real values in [0,1]
|
||||
def smoothmacroK(true_labels, posterior_probabilities):
|
||||
return macro_average(true_labels,posterior_probabilities, K, metric_statistics=soft_single_metric_statistics)
|
||||
|
||||
#true_labels is a matrix in sklearn.preprocessing.MultiLabelBinarizer format and posterior_probabilities is a matrix
|
||||
#of the same shape containing real values in [0,1]
|
||||
def smoothmicroK(true_labels, posterior_probabilities):
|
||||
return micro_average(true_labels, posterior_probabilities, K, metric_statistics=soft_single_metric_statistics)
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
Scikit learn provides a full set of evaluation metrics, but they treat special cases differently.
|
||||
I.e., when the number of true positives, false positives, and false negatives ammount to 0, all
|
||||
affected metrices (precision, recall, and thus f1) output 0 in Scikit learn.
|
||||
We adhere to the common practice of outputting 1 in this case since the classifier has correctly
|
||||
classified all examples as negatives.
|
||||
"""
|
||||
|
||||
def evaluation(y_true, y_pred, classification_type):
|
||||
|
||||
if classification_type == 'multilabel':
|
||||
eval_function = multilabel_eval
|
||||
elif classification_type == 'singlelabel':
|
||||
eval_function = singlelabel_eval
|
||||
|
||||
Mf1, mf1, accuracy = eval_function(y_true, y_pred)
|
||||
|
||||
return Mf1, mf1, accuracy
|
||||
|
||||
|
||||
def multilabel_eval(y, y_):
|
||||
|
||||
tp = y.multiply(y_)
|
||||
|
||||
fn = lil_matrix(y.shape)
|
||||
true_ones = y==1
|
||||
fn[true_ones]=1-tp[true_ones]
|
||||
|
||||
fp = lil_matrix(y.shape)
|
||||
pred_ones = y_==1
|
||||
if pred_ones.nnz>0:
|
||||
fp[pred_ones]=1-tp[pred_ones]
|
||||
|
||||
#macro-f1
|
||||
tp_macro = np.asarray(tp.sum(axis=0), dtype=int).flatten()
|
||||
fn_macro = np.asarray(fn.sum(axis=0), dtype=int).flatten()
|
||||
fp_macro = np.asarray(fp.sum(axis=0), dtype=int).flatten()
|
||||
|
||||
pos_pred = tp_macro+fp_macro
|
||||
pos_true = tp_macro+fn_macro
|
||||
prec=np.zeros(shape=tp_macro.shape,dtype=float)
|
||||
rec=np.zeros(shape=tp_macro.shape,dtype=float)
|
||||
np.divide(tp_macro, pos_pred, out=prec, where=pos_pred>0)
|
||||
np.divide(tp_macro, pos_true, out=rec, where=pos_true>0)
|
||||
den=prec+rec
|
||||
|
||||
macrof1=np.zeros(shape=tp_macro.shape,dtype=float)
|
||||
np.divide(np.multiply(prec,rec),den,out=macrof1,where=den>0)
|
||||
macrof1 *=2
|
||||
|
||||
macrof1[(pos_pred==0)*(pos_true==0)]=1
|
||||
macrof1 = np.mean(macrof1)
|
||||
|
||||
#micro-f1
|
||||
tp_micro = tp_macro.sum()
|
||||
fn_micro = fn_macro.sum()
|
||||
fp_micro = fp_macro.sum()
|
||||
pos_pred = tp_micro + fp_micro
|
||||
pos_true = tp_micro + fn_micro
|
||||
prec = (tp_micro / pos_pred) if pos_pred>0 else 0
|
||||
rec = (tp_micro / pos_true) if pos_true>0 else 0
|
||||
den = prec+rec
|
||||
microf1 = 2*prec*rec/den if den>0 else 0
|
||||
if pos_pred==pos_true==0:
|
||||
microf1=1
|
||||
|
||||
#accuracy
|
||||
ndecisions = np.multiply(*y.shape)
|
||||
tn = ndecisions - (tp_micro+fn_micro+fp_micro)
|
||||
acc = (tp_micro+tn)/ndecisions
|
||||
|
||||
return macrof1,microf1,acc
|
||||
|
||||
|
||||
def singlelabel_eval(y, y_):
|
||||
if issparse(y_): y_ = y_.toarray().flatten()
|
||||
macrof1 = f1_score(y, y_, average='macro')
|
||||
microf1 = f1_score(y, y_, average='micro')
|
||||
acc = accuracy_score(y, y_)
|
||||
return macrof1,microf1,acc
|
||||
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
from optparse import OptionParser
|
||||
|
||||
parser = OptionParser(usage="usage: %prog datapath [options]")
|
||||
|
||||
parser.add_option("-d", dest='dataset', type=str, metavar='datasetpath', help=f'path to the pickled dataset')
|
||||
|
||||
parser.add_option("-o", "--output", dest="output",
|
||||
help="Result file", type=str, default='../log/multiModal_log.csv')
|
||||
|
||||
parser.add_option("-X", "--posteriors", dest="posteriors", action='store_true',
|
||||
help="Add posterior probabilities to the document embedding representation", default=False)
|
||||
|
||||
parser.add_option("-W", "--supervised", dest="supervised", action='store_true',
|
||||
help="Add supervised (Word-Class Embeddings) to the document embedding representation", default=False)
|
||||
|
||||
parser.add_option("-M", "--pretrained", dest="pretrained", action='store_true',
|
||||
help="Add pretrained MUSE embeddings to the document embedding representation", default=False)
|
||||
|
||||
parser.add_option("-B", "--mbert", dest="mbert", action='store_true',
|
||||
help="Add multilingual Bert (mBert) document embedding representation", default=False)
|
||||
|
||||
parser.add_option('-G', dest='gruViewGenerator', action='store_true',
|
||||
help="Add document embedding generated via recurrent net (GRU)", default=False)
|
||||
|
||||
parser.add_option("--l2", dest="l2", action='store_true',
|
||||
help="Activates l2 normalization as a post-processing for the document embedding views",
|
||||
default=True)
|
||||
|
||||
parser.add_option("--allprob", dest="allprob", action='store_true',
|
||||
help="All views are generated as posterior probabilities. This affects the supervised and pretrained"
|
||||
"embeddings, for which a calibrated classifier is generated, which generates the posteriors",
|
||||
default=True)
|
||||
|
||||
parser.add_option("--feat-weight", dest="feat_weight",
|
||||
help="Term weighting function to weight the averaged embeddings", type=str, default='tfidf')
|
||||
|
||||
parser.add_option("-w", "--we-path", dest="we_path",
|
||||
help="Path to the MUSE polylingual word embeddings", default='../embeddings')
|
||||
|
||||
parser.add_option("-s", "--set_c", dest="set_c", type=float,
|
||||
help="Set the C parameter", default=1)
|
||||
|
||||
parser.add_option("-c", "--optimc", dest="optimc", action='store_true',
|
||||
help="Optimize hyperparameters", default=False)
|
||||
|
||||
parser.add_option("-j", "--n_jobs", dest="n_jobs", type=int,
|
||||
help="Number of parallel jobs (default is -1, all)", default=-1)
|
||||
|
||||
parser.add_option("-p", "--pca", dest="max_labels_S", type=int,
|
||||
help="If smaller than number of target classes, PCA will be applied to supervised matrix. ",
|
||||
default=300)
|
||||
|
||||
parser.add_option("-r", "--remove-pc", dest="sif", action='store_true',
|
||||
help="Remove common component when computing dot product of word embedding matrices", default=True)
|
||||
|
||||
parser.add_option("-z", "--zscore", dest="zscore", action='store_true',
|
||||
help="Z-score normalize matrices (WCE and MUSE)", default=True)
|
||||
|
||||
parser.add_option("-a", "--agg", dest="agg", action='store_true',
|
||||
help="Set aggregation function of the common Z-space to average (Default: concatenation)",
|
||||
default=True)
|
||||
|
||||
parser.add_option("-l", dest="avoid_loading", action="store_true",
|
||||
help="TODO", default=False)
|
||||
|
||||
# ------------------------------------------------------------------------------------
|
||||
|
||||
parser.add_option('--hidden', type=int, default=512, metavar='int',
|
||||
help='hidden lstm size (default: 512)')
|
||||
|
||||
parser.add_option('--sup-drop', type=float, default=0.5, metavar='[0.0, 1.0]',
|
||||
help='dropout probability for the supervised matrix (default: 0.5)')
|
||||
|
||||
parser.add_option('--tunable', action='store_true', default=False,
|
||||
help='pretrained embeddings are tunable from the beginning (default False, i.e., static)')
|
||||
|
||||
parser.add_option('--logfile_gru', dest='logfile_gru', default='../log/log_gru_viewgenerator.csv')
|
||||
|
||||
parser.add_option('--seed', type=int, default=1, metavar='int', help='random seed (default: 1)')
|
||||
|
||||
parser.add_option('--force', action='store_true', default=False,
|
||||
help='do not check if this experiment has already been run')
|
||||
|
||||
parser.add_option('--gruMuse', dest='gruMUSE', action='store_true', default=False,
|
||||
help='Deploy MUSE embedding as embedding layer of the GRU View Generator')
|
||||
|
||||
parser.add_option('--gruWce', dest='gruWCE', action='store_true', default=False,
|
||||
help='Deploy WCE embedding as embedding layer of the GRU View Generator')
|
||||
|
||||
parser.add_option('--gru-path', dest='gru_path', default=None,
|
||||
help='Set the path to a pretrained GRU model (aka, -G view generator)')
|
||||
|
||||
parser.add_option('--bert-path', dest='bert_path', default=None,
|
||||
help='Set the path to a pretrained mBERT model (aka, -B view generator)')
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
import os
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
class PolylingualClassificationResults:
|
||||
def __init__(self, file, autoflush=True, verbose=False):
|
||||
self.file = file
|
||||
self.columns = ['method',
|
||||
'learner',
|
||||
'optimp',
|
||||
'sif',
|
||||
'zscore',
|
||||
'l2',
|
||||
'wescaler',
|
||||
'pca',
|
||||
'id',
|
||||
'dataset',
|
||||
'time',
|
||||
'lang',
|
||||
'macrof1',
|
||||
'microf1',
|
||||
'macrok',
|
||||
'microk',
|
||||
'notes']
|
||||
self.autoflush = autoflush
|
||||
self.verbose = verbose
|
||||
if os.path.exists(file):
|
||||
self.tell('Loading existing file from {}'.format(file))
|
||||
self.df = pd.read_csv(file, sep='\t')
|
||||
else:
|
||||
self.tell('File {} does not exist. Creating new frame.'.format(file))
|
||||
dir = os.path.dirname(self.file)
|
||||
if dir and not os.path.exists(dir): os.makedirs(dir)
|
||||
self.df = pd.DataFrame(columns=self.columns)
|
||||
|
||||
def already_calculated(self, id):
|
||||
return (self.df['id'] == id).any()
|
||||
|
||||
def add_row(self, method, learner, optimp, sif, zscore, l2, wescaler, pca, id, dataset, time, lang, macrof1, microf1, macrok=np.nan, microk=np.nan, notes=''):
|
||||
s = pd.Series([method, learner, optimp,sif, zscore, l2, wescaler, pca, id, dataset, time, lang, macrof1, microf1, macrok, microk, notes], index=self.columns)
|
||||
self.df = self.df.append(s, ignore_index=True)
|
||||
if self.autoflush: self.flush()
|
||||
self.tell(s.to_string())
|
||||
|
||||
def flush(self):
|
||||
self.df.to_csv(self.file, index=False, sep='\t')
|
||||
|
||||
def tell(self, msg):
|
||||
if self.verbose: print(msg)
|
||||
|
||||
|
||||
class ZSCLResults:
|
||||
def __init__(self, file, autoflush=True, verbose=False):
|
||||
self.file = file
|
||||
self.columns = ['method',
|
||||
'optimp',
|
||||
'source',
|
||||
'target',
|
||||
'id',
|
||||
'dataset',
|
||||
'time',
|
||||
'lang',
|
||||
'macrof1',
|
||||
'microf1',
|
||||
'macrok',
|
||||
'microk',
|
||||
'notes']
|
||||
self.autoflush = autoflush
|
||||
self.verbose = verbose
|
||||
if os.path.exists(file):
|
||||
self.tell('Loading existing file from {}'.format(file))
|
||||
self.df = pd.read_csv(file, sep='\t')
|
||||
else:
|
||||
self.tell('File {} does not exist. Creating new frame.'.format(file))
|
||||
dir = os.path.dirname(self.file)
|
||||
if dir and not os.path.exists(dir): os.makedirs(dir)
|
||||
self.df = pd.DataFrame(columns=self.columns)
|
||||
|
||||
def already_calculated(self, id):
|
||||
return (self.df['id'] == id).any()
|
||||
|
||||
def add_row(self, method, optimp, id, source, target, dataset, time, lang, macrof1, microf1, macrok=np.nan, microk=np.nan, notes=''):
|
||||
s = pd.Series([method, optimp, id, source, target, dataset, time, lang, macrof1, microf1, macrok, microk, notes], index=self.columns)
|
||||
self.df = self.df.append(s, ignore_index=True)
|
||||
if self.autoflush: self.flush()
|
||||
self.tell(s.to_string())
|
||||
|
||||
def flush(self):
|
||||
self.df.to_csv(self.file, index=False, sep='\t')
|
||||
|
||||
def tell(self, msg):
|
||||
if self.verbose: print(msg)
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
from sklearn.svm import SVC
|
||||
from tqdm import tqdm
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def mask_numbers(data, number_mask='numbermask'):
|
||||
mask = re.compile(r'\b[0-9][0-9.,-]*\b')
|
||||
masked = []
|
||||
for text in tqdm(data, desc='masking numbers'):
|
||||
masked.append(mask.sub(number_mask, text))
|
||||
return masked
|
||||
|
||||
|
||||
def fill_missing_classes(lXtr, lytr):
|
||||
pass
|
||||
|
||||
|
||||
def get_learner(calibrate=False, kernel='linear'):
|
||||
return SVC(kernel=kernel, probability=calibrate, cache_size=1000, C=op.set_c, random_state=1, class_weight='balanced', gamma='auto')
|
||||
|
||||
|
||||
def get_params(dense=False):
|
||||
if not op.optimc:
|
||||
return None
|
||||
c_range = [1e4, 1e3, 1e2, 1e1, 1, 1e-1]
|
||||
kernel = 'rbf' if dense else 'linear'
|
||||
return [{'kernel': [kernel], 'C': c_range, 'gamma':['auto']}]
|
||||
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
|
||||
class StandardizeTransformer:
|
||||
|
||||
def __init__(self, axis=0, range=None):
|
||||
assert range is None or isinstance(range, slice), 'wrong format for range, should either be None or a slice'
|
||||
self.axis = axis
|
||||
self.yetfit = False
|
||||
self.range = range
|
||||
|
||||
def fit(self, X):
|
||||
print('fitting Standardizer...')
|
||||
std=np.std(X, axis=self.axis, ddof=1)
|
||||
self.std = np.clip(std, 1e-5, None)
|
||||
self.mean = np.mean(X, axis=self.axis)
|
||||
if self.range is not None:
|
||||
ones = np.ones_like(self.std)
|
||||
zeros = np.zeros_like(self.mean)
|
||||
ones[self.range] = self.std[self.range]
|
||||
zeros[self.range] = self.mean[self.range]
|
||||
self.std = ones
|
||||
self.mean = zeros
|
||||
self.yetfit=True
|
||||
return self
|
||||
|
||||
def transform(self, X):
|
||||
if not self.yetfit: 'transform called before fit'
|
||||
return (X - self.mean) / self.std
|
||||
|
||||
def fit_transform(self, X):
|
||||
return self.fit(X).transform(X)
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
import numpy as np
|
||||
import sklearn
|
||||
# from sklearn.externals.joblib import Parallel, delayed
|
||||
from joblib import Parallel, delayed
|
||||
|
||||
class ESA(object):
|
||||
"""
|
||||
Implementation of Explicit Sematic Analysis (ESA) in its mono-lingual version, as a transformer
|
||||
"""
|
||||
supported_similarity = ['dot', 'cosine']
|
||||
|
||||
def __init__(self, similarity='dot', centered=False, post=None):
|
||||
"""
|
||||
:param similarity: the similarity measure between documents to be used
|
||||
:param centered: set to True to subtract the expected similarity due to randomness (experimental)
|
||||
:param post: any valid sklearn normalization method to be applied to the resulting doc embeddings, or None (default)
|
||||
"""
|
||||
assert similarity in self.supported_similarity, ("Similarity method %s is not supported" % similarity)
|
||||
self.similarity = similarity
|
||||
self.centered = centered
|
||||
self.post_processing = post
|
||||
self.W = None
|
||||
|
||||
def fit(self, W):
|
||||
"""
|
||||
:param W: doc-by-term already processed matrix of wikipedia documents
|
||||
:return: self
|
||||
"""
|
||||
self.W = W
|
||||
return self
|
||||
|
||||
def transform(self, X):
|
||||
"""
|
||||
:param X: doc-by-term matrix that is to be transformed into the ESA space.
|
||||
:return: the matrix X transformed into the ESA space in numpy format
|
||||
"""
|
||||
assert self.W is not None, 'transform method called before fit'
|
||||
|
||||
W = self.W
|
||||
assert X.shape[1] == W.shape[1], ('the feature spaces for X=%s and W=%s do not agree' % (str(X.shape), str(W.shape)))
|
||||
|
||||
if self.similarity in ['dot', 'cosine']:
|
||||
if self.similarity == 'cosine':
|
||||
X = sklearn.preprocessing.normalize(X, norm='l2', axis=1, copy=True)
|
||||
W = sklearn.preprocessing.normalize(W, norm='l2', axis=1, copy=True)
|
||||
|
||||
esa = (X.dot(W.T)).toarray()
|
||||
if self.centered:
|
||||
pX = (X > 0).sum(1) / float(X.shape[1])
|
||||
pW = (W > 0).sum(1) / float(W.shape[1])
|
||||
pXpW = np.sqrt(pX.dot(pW.transpose()))
|
||||
esa = esa - pXpW
|
||||
|
||||
if self.post_processing:
|
||||
esa = sklearn.preprocessing.normalize(esa, norm=self.post_processing, axis=1, copy=True)
|
||||
|
||||
return esa
|
||||
|
||||
def fit_transform(self, W, X, Y=None):
|
||||
self.fit(W)
|
||||
return self.transform(X, Y)
|
||||
|
||||
def dimensionality(self):
|
||||
return self.W.shape[0]
|
||||
|
||||
|
||||
|
||||
class CLESA(ESA):
|
||||
"""
|
||||
Implementation of Cross-Lingual Explicit Sematic Analysis (ESA) as a transformer
|
||||
"""
|
||||
|
||||
def __init__(self, similarity='dot', centered=False, post=False, n_jobs=-1):
|
||||
super(CLESA, self).__init__(similarity, centered, post)
|
||||
self.lESA = None
|
||||
self.langs = None
|
||||
self.n_jobs = n_jobs
|
||||
|
||||
def fit(self, lW):
|
||||
"""
|
||||
:param lW: a dictionary of {language: doc-by-term wiki matrix}
|
||||
:return: self
|
||||
"""
|
||||
assert len(np.unique([W.shape[0] for W in lW.values()])) == 1, "inconsistent dimensions across languages"
|
||||
|
||||
self.dimensions = list(lW.values())[0].shape[0]
|
||||
self.langs = list(lW.keys())
|
||||
self.lESA = {lang:ESA(self.similarity, self.centered, self.post_processing).fit(lW[lang]) for lang in self.langs}
|
||||
return self
|
||||
|
||||
def transform(self, lX):
|
||||
"""
|
||||
:param lX: dictionary of {language : doc-by-term matrix} that is to be transformed into the CL-ESA space
|
||||
:return: a dictionary {language : doc-by-dim matrix} containing the matrix-transformed versions
|
||||
"""
|
||||
assert self.lESA is not None, 'transform method called before fit'
|
||||
assert set(lX.keys()).issubset(set(self.langs)), 'languages in lX are not scope'
|
||||
langs = list(lX.keys())
|
||||
trans = Parallel(n_jobs=self.n_jobs)(delayed(self.lESA[lang].transform)(lX[lang]) for lang in langs)
|
||||
return {lang:trans[i] for i,lang in enumerate(langs)}
|
||||
|
||||
def fit_transform(self, lW, lX):
|
||||
return self.fit(lW).transform(lX)
|
||||
|
||||
def languages(self):
|
||||
return list(self.lESA.keys())
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
import numpy as np
|
||||
from sklearn.preprocessing import normalize
|
||||
from scipy.sparse import csr_matrix, issparse
|
||||
from scipy.spatial.distance import cosine
|
||||
import operator
|
||||
import functools
|
||||
import math, sys
|
||||
# from sklearn.externals.joblib import Parallel, delayed
|
||||
from joblib import Parallel, delayed
|
||||
|
||||
|
||||
class DistributionalCorrespondenceIndexing:
|
||||
|
||||
prob_dcf = ['linear', 'pmi']
|
||||
vect_dcf = ['cosine']
|
||||
valid_dcf = prob_dcf + vect_dcf
|
||||
valid_post = ['normal', 'l2', None]
|
||||
|
||||
def __init__(self, dcf='cosine', post='normal', n_jobs=-1):
|
||||
"""
|
||||
:param dcf: a distributional correspondence function name (e.g., 'cosine') or a callable f(u,v) which measures
|
||||
the distribucional correspondence between vectors u and v
|
||||
:param post: post-processing function to apply to document embeddings. Default is to standardize it into a
|
||||
normal distribution; other functions allowed are 'l2' or None
|
||||
"""
|
||||
if post not in self.valid_post:
|
||||
raise ValueError("unknown post processing function; valid ones are [%s]" % ', '.join(self.valid_post))
|
||||
|
||||
if isinstance(dcf, str):
|
||||
if dcf not in self.valid_dcf:
|
||||
raise ValueError("unknown dcf; use any in [%s]" % ', '.join(self.valid_dcf))
|
||||
self.dcf = getattr(DistributionalCorrespondenceIndexing, dcf)
|
||||
elif hasattr(dcf, '__call__'):
|
||||
self.dcf = dcf
|
||||
else:
|
||||
raise ValueError('param dcf should either be a valid dcf name in [%s] or a callable comparing two vectors')
|
||||
#self.dcf = lambda u,v:dcf(u,v)
|
||||
self.post = post
|
||||
self.domains = None
|
||||
self.dFP = None
|
||||
self.n_jobs = n_jobs
|
||||
|
||||
def fit(self, dU, dP):
|
||||
"""
|
||||
:param dU: a dictionary of {domain:dsm_matrix}, where dsm is a document-by-term matrix representing the
|
||||
distributional semantic model for a specific domain
|
||||
:param dP: a dictionary {domain:pivot_matrix} where domain is a string representing each domain,
|
||||
and pivot_matrix has shape (d,p) with d the dimensionality of the distributional space, and p the
|
||||
number of pivots
|
||||
:return: self
|
||||
"""
|
||||
self.domains = list(dP.keys())
|
||||
assert len(np.unique([P.shape[1] for P in dP.values()]))==1, "inconsistent number of pivots across domains"
|
||||
assert set(dU.keys())==set(self.domains), "inconsistent domains in dU and dP"
|
||||
assert not [1 for d in self.domains if dU[d].shape[0]!=dP[d].shape[0]], \
|
||||
"inconsistent dimensions between distributional and pivot spaces"
|
||||
self.dimensions = list(dP.values())[0].shape[1]
|
||||
# embed the feature space from each domain using the pivots of that domain
|
||||
#self.dFP = {d:self.dcf_dist(dU[d].transpose(), dP[d].transpose()) for d in self.domains}
|
||||
transformations = Parallel(n_jobs=self.n_jobs)(delayed(self.dcf_dist)(dU[d].transpose(),dP[d].transpose()) for d in self.domains)
|
||||
self.dFP = {d: transformations[i] for i, d in enumerate(self.domains)}
|
||||
|
||||
def _dom_transform(self, X, FP):
|
||||
_X = X.dot(FP)
|
||||
if self.post == 'l2':
|
||||
_X = normalize(_X, norm='l2', axis=1)
|
||||
elif self.post == 'normal':
|
||||
std = np.clip(np.std(_X, axis=0), 1e-5, None)
|
||||
_X = (_X - np.mean(_X, axis=0)) / std
|
||||
return _X
|
||||
|
||||
# dX is a dictionary of {domain:dsm}, where dsm (distributional semantic model) is, e.g., a document-by-term csr_matrix
|
||||
def transform(self, dX):
|
||||
assert self.dFP is not None, 'transform method called before fit'
|
||||
assert set(dX.keys()).issubset(self.domains), 'domains in dX are not scope'
|
||||
domains = list(dX.keys())
|
||||
transformations = Parallel(n_jobs=self.n_jobs)(delayed(self._dom_transform)(dX[d], self.dFP[d]) for d in domains)
|
||||
return {d: transformations[i] for i, d in enumerate(domains)}
|
||||
|
||||
def fit_transform(self, dU, dP, dX):
|
||||
return self.fit(dU, dP).transform(dX)
|
||||
|
||||
def _prevalence(self, v):
|
||||
if issparse(v):
|
||||
return float(v.nnz) / functools.reduce(operator.mul, v.shape, 1) #this works for arrays of any rank
|
||||
elif isinstance(v, np.ndarray):
|
||||
return float(v[v>0].size) / v.size
|
||||
|
||||
def linear(self, u, v, D):
|
||||
tp, fp, fn, tn = self._get_4cellcounters(u, v, D)
|
||||
den1=tp+fn
|
||||
den2=tn+fp
|
||||
tpr = (tp*1./den1) if den1!=0 else 0.
|
||||
tnr = (tn*1./den2) if den2!=0 else 0.
|
||||
return tpr + tnr - 1
|
||||
|
||||
def pmi(self, u, v, D):
|
||||
tp, fp, fn, tn = self._get_4cellcounters(u, v, D)
|
||||
|
||||
Pxy = tp * 1. / D
|
||||
Pxny = fp * 1. / D
|
||||
Pnxy = fn * 1. / D
|
||||
Px = Pxy + Pxny
|
||||
Py = Pxy + Pnxy
|
||||
|
||||
if (Px == 0 or Py == 0 or Pxy == 0):
|
||||
return 0.0
|
||||
|
||||
score = math.log2(Pxy / (Px * Py))
|
||||
if np.isnan(score) or np.isinf(score):
|
||||
print('NAN')
|
||||
sys.exit()
|
||||
return score
|
||||
|
||||
def cosine(self, u, v):
|
||||
pu = self._prevalence(u)
|
||||
pv = self._prevalence(v)
|
||||
return cosine(u, v) - np.sqrt(pu * pv)
|
||||
|
||||
def _get_4cellcounters(self, u, v, D):
|
||||
"""
|
||||
:param u: a set of indexes with a non-zero value
|
||||
:param v: a set of indexes with a non-zero value
|
||||
:param D: the number of events (i.e., all posible indexes)
|
||||
:return: the 4-cell contingency values tp, fp, fn, tn)
|
||||
"""
|
||||
common=u.intersection(v)
|
||||
tp = len(common)
|
||||
fp = len(u) - len(common)
|
||||
fn = len(v) - len(common)
|
||||
tn = D - (tp + fp + fn)
|
||||
return tp, fp, fn, tn
|
||||
|
||||
def dcf_dist(self, U, V):
|
||||
nU,D = U.shape
|
||||
nV = V.shape[0]
|
||||
if issparse(U): U = U.toarray()
|
||||
if issparse(V): V = V.toarray()
|
||||
|
||||
dists = np.zeros((nU, nV))
|
||||
if self.dcf.__name__ in self.prob_dcf:
|
||||
def hits_index(v):
|
||||
return set(np.argwhere(v>0).reshape(-1).tolist())
|
||||
Vhits = {i:hits_index(V[i]) for i in range(nV)}
|
||||
for i in range(nU):
|
||||
Ui_hits = hits_index(U[i])
|
||||
for j in range(nV):
|
||||
dists[i, j] = self.dcf(self, Ui_hits, Vhits[j], D)
|
||||
else:
|
||||
for i in range(nU):
|
||||
for j in range(nV):
|
||||
dists[i, j] = self.dcf(self, U[i], V[j])
|
||||
return dists
|
||||
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
import math
|
||||
import numpy as np
|
||||
from scipy.sparse import csr_matrix, issparse
|
||||
|
||||
class RandomIndexingBoC(object):
|
||||
|
||||
def __init__(self, latent_dimensions, non_zeros=2):
|
||||
self.latent_dimensions = latent_dimensions
|
||||
self.k = non_zeros
|
||||
self.ri_dict = None
|
||||
|
||||
def fit_transform(self, X):
|
||||
return self.fit(X).transform(X)
|
||||
|
||||
def fit(self, X):
|
||||
nF = X.shape[1]
|
||||
nL = self.latent_dimensions
|
||||
format = 'csr' if issparse(X) else 'np'
|
||||
self.ri_dict = _create_random_index_dictionary(shape=(nF, nL), k=self.k, normalized=True, format=format)
|
||||
return self
|
||||
|
||||
def transform(self, X):
|
||||
assert X.shape[1] == self.ri_dict.shape[0], 'feature space is inconsistent with the RI dictionary'
|
||||
if self.ri_dict is None:
|
||||
raise ValueError("Error: transform method called before fit.")
|
||||
P = X.dot(self.ri_dict)
|
||||
if issparse(P):
|
||||
P.sort_indices()
|
||||
return P
|
||||
|
||||
|
||||
def _create_random_index_dictionary(shape, k, normalized=False, format='csr', positive=False):
|
||||
assert format in ['csr', 'np'], 'Format should be in "[csr, np]"'
|
||||
nF, latent_dimensions = shape
|
||||
print("Creating the random index dictionary for |V|={} with {} dimensions".format(nF,latent_dimensions))
|
||||
val = 1.0 if not normalized else 1.0/math.sqrt(k)
|
||||
#ri_dict = csr_matrix((nF, latent_dimensions)) if format == 'csr' else np.zeros((nF, latent_dimensions))
|
||||
ri_dict = np.zeros((nF, latent_dimensions))
|
||||
|
||||
#TODO: optimize
|
||||
for t in range(nF):
|
||||
dims = np.zeros(k, dtype=np.int32)
|
||||
dims[0] = t % latent_dimensions #the first dimension is choosen in a round-robin manner (prevents gaps)
|
||||
dims[1:] = np.random.choice(latent_dimensions, size=k-1, replace=False)
|
||||
values = (np.random.randint(0,2, size=k)*2.0-1.0) * val if not positive else np.array([+val]*k)
|
||||
ri_dict[t,dims]=values
|
||||
print("\rprogress [%.2f%% complete]" % (t * 100.0 / nF), end='')
|
||||
print('\nDone')
|
||||
|
||||
if format=='csr':
|
||||
ri_dict = csr_matrix(ri_dict)
|
||||
return ri_dict
|
||||
|
||||
Loading…
Reference in New Issue