UCDP-AEC

Models and data for the UCDP Abstractive Event analysis Corpus paper
git clone https://esimon.eu/repos/UCDP-AEC.git
Log | Files | Refs | README | LICENSE

retriever.py (5322B)


      1 import gaoya
      2 import pandas as pd
      3 import sys
      4 from fire import Fire
      5 import zstandard
      6 from time import time
      7 
      8 
      9 class Retriever:
     10     def _preprocess(self, stext):
     11         # nonletters = r'[^a-zA-Z]'
     12         nonletters = r'[_0-9\W]'
     13         return stext.str.replace(nonletters, '', regex=True)
     14 
     15     
     16     def preprocess_content(self, fcontent='easy.jsonl'):
     17         """
     18         Preprocess content for faster adding to the index.
     19         To avoid repeated ids, we don't use them, instead use line numbers.
     20         """
     21         print('Loading', fcontent)
     22         df = pd.read_json(fcontent, lines=True)    
     23         #df = df.set_index('id')  # There are duplicates in full.jsonl!
     24         df.index += 1  # line numbers starting with 1 to avoid +0=-0 problem
     25         print('Preprocessing', fcontent)
     26         s1 = self._preprocess(df.article)
     27         s2 = self._preprocess(df.headline + ' ' + df.article)
     28         s2.index *= -1  # negated line numbers correspond to concat(headline,article)
     29         text_ser = pd.concat([s1, s2])
     30         print('Dumping to disk')
     31         text_ser.to_csv(fcontent+'.preproc.tsv', sep='\t')
     32         text_ser.to_pickle(fcontent+'.preproc.pkl')
     33 
     34         print('Survived chars:')
     35         survived_chars = set.union(*text_ser.apply(set))
     36         print(repr(''.join(sorted(survived_chars))))
     37         print('Removed chars:')
     38         all_chars = set.union(*df.article.apply(set))
     39         print(repr(''.join(sorted(all_chars - survived_chars))))
     40 
     41         
     42     def __init__(self, fcontent=None, ng_min=10, ng_max=10):
     43         if fcontent is None:
     44            return
     45 #        import pdb
     46 #        pdb.set_trace()
     47         text_ser = pd.read_pickle(fcontent)
     48         n = len(text_ser)
     49         text_ser.drop(text_ser[text_ser.str.len() < ng_min].index, inplace=True)
     50         print(f'{n} documents loaded, {(n-len(text_ser))/n*100}% were dropped because they are shorter than ng_min={ng_min} chars', file=sys.stderr)
     51 
     52         index = gaoya.minhash.MinHashStringIndex(hash_size=32, 
     53                                                      jaccard_threshold=0.5, 
     54                                                      num_bands=42, 
     55                                                      band_size=3,
     56                                                      analyzer='char', 
     57                                                      lowercase=True, 
     58                                                      ngram_range=(ng_min,ng_max))
     59         
     60         index.par_bulk_insert_docs(text_ser.index, text_ser)
     61         print(f'{len(text_ser)} documents added to index', file=sys.stderr)
     62         del text_ser
     63         self.index = index
     64         self.ng_min = ng_min
     65 
     66         
     67     def _retrieve(self, qdf, out=sys.stdout):
     68         """
     69         qdf: a DataFrame with 2 columns: 'qid' and 'text'
     70         """
     71         if len(qdf) == 0: return
     72         qser = self._preprocess(qdf.text)
     73         short_mask = qdf[qser.str.len() < self.ng_min].index
     74         qser.drop(short_mask, inplace=True)
     75         qdf.drop(short_mask, inplace=True)
     76         res = self.index.par_bulk_query(qser, return_similarity=True)
     77         if not any(r for r in res):
     78             return  # optimization fot the case of no matches
     79         qdf['ans'] = res
     80         qdf = qdf.explode(column='ans', ignore_index=True)  # lists of answers to 1 answer per row
     81         qdf.dropna(subset='ans', inplace=True)  # empty lists of answers
     82         qdf['tid'], qdf['sim'] = qdf.ans.str[0], qdf.ans.str[1]
     83         if len(qdf) > 0:
     84             qdf[['qid','tid','sim','text']].to_json(out, orient='records',lines=True)
     85         
     86         
     87     def retrieve(self, finp='-', batch_size=10000, text_field='text'):
     88         """
     89         Retrieve near-duplicated of texts from stdin (by default) or file. 
     90         For each query text finds similar texts in the index, prints (qid, tid, sim, text) to stdout, where
     91         qid and tid are query text and retrieved text identifiers, sim is their Jaccard similarity, text is the 
     92         retrieved text. 
     93         NB: It is recommended to use batches: 1K queries => 102ms, 10K queries => 955 ms
     94         NB: texts containing less than ng_min letters are not indexed or queried because they become an empty set of ngrams
     95         """
     96         start_line_num = 1
     97         with sys.stdin if finp=='-' else zstandard.open(finp,'r') as inp:
     98             while True:
     99                 st = time()
    100                 df = pd.read_json(inp, nrows=batch_size, lines=True, orient='records', dtype=False)  # dtype=False loads text correctly, otherwise tries infers float/int types sometimes
    101                 if len(df)==0: break  # EOF
    102                 if text_field != 'text':
    103                     df.rename(columns={text_field:'text'}, inplace=True)
    104                 if finp != '-':
    105                     df['qid'] = f'{finp}:' + (df.index + start_line_num).astype(str)
    106                     start_line_num += len(df)
    107                 else:
    108                     df = df[['index','text']].rename(columns={'index':'qid'})
    109                 df.dropna(subset='text', inplace=True)
    110                 dur1 = time() - st
    111                 st = time()
    112                 self._retrieve(df)
    113                 dur2 = time() - st
    114                 print(f"{dur1+dur2}s per batch of {batch_size} docs, {len(df)} queries after filtering, {dur1/dur2}x longer reading than searching", file=sys.stderr)
    115 
    116 
    117 
    118 Fire(Retriever)