<jupyter_start><jupyter_text>Boolean Retrieval Model  Library Imports<jupyter_code># !pip install pymorphy2
!pip install tqdm -U
import os
import gzip
import nltk
import pickle
import logging
import itertools
import re
# import pymorphy2

from nltk.corpus import stopwords
nltk.download("stopwords")

from string import punctuation
from collections import defaultdict
from tqdm.notebook import tqdm

logger = logging.getLogger('ir')
logger.setLevel(logging.DEBUG)<jupyter_output>Requirement already up-to-date: tqdm in /usr/local/lib/python3.6/dist-packages (4.41.0)
[nltk_data] Downloading package stopwords to /root/nltk_data...
[nltk_data]   Package stopwords is already up-to-date!<jupyter_text>Reading Files<jupyter_code>from google.colab import drive
drive.mount('/content/drive')
indexing_data_location = '/content/drive/My Drive/Homeworks/InformationRetrieval/hw1_docs.tsv.gz'
query_validate = '/content/drive/My Drive/Homeworks/InformationRetrieval/hw1_queries_validate.tsv'
truedoc_validate = '/content/drive/My Drive/Homeworks/InformationRetrieval/hw1_truedocs_validate.tsv'<jupyter_output><empty_output><jupyter_text>Testing Block Skip If you are evaluating<jupyter_code>test_sen = 'OFFICE HOURS SATURDAY THURSDAY 8 AM 5 PM CONSULAR SECTION TUESDAY THURSDAY AND SUNDAY 9 AM 1 PM EMERGENCY ASSISTANCE ADDRESS STREET 3 COMMUNITY 314 UMM HURAIR FIRST BUR DUBAI AREA DUBAI UAE MAKANI NUMBER 29820 94373 TEL 9714 370 60 60 FAX 9714 370 60 63 E MAIL UZBEKCONSULATE AE MAIL RU P O BOX 50478 LOCATION MAP CONSULATE GENERAL OF THE REPUBLIC OF UZBEKISTAN IN DUBAI UAE PROFILE OF UZBEKISTAN STATE SYMBOLS CONSTITUTION PRESIDENT HISTORY PEOPLE CULTURE ECOLOGY NATIONAL HOLIDAYS ECONOMY DEVELOPMENT OF INTERNATIONAL RELATIONS TRAVELING TO UZBEKISTAN INVITATION TO UZBEKISTAN TOURISM IN UZBEKISTAN HISTORICAL PLACES TOUR COMPANIES HOTELS IN UZBEKISTAN BUSINESS IN UZBEKISTAN WHY UZBEKISTAN INVESTMENT GUIDE INVESTMENT OPPORTUNITIES INVESTMENT PROJECTS MULTIMEDIA S VIDEO AUDIO PHOTO HOME CONSULAR SERVICE O ZBEKCHA НА РУССКОМ IN ENGLISH BILETARIAL RELATION PRESS RELEASES EVENTS CONTACT US USEFUL LINKS 25 TH ANNIVERSARY OF INDEPENDENCE OF UZBEKISTAN ЛЕГКАЯ ПРОМЫШЛЕННОСТЬ УЗБЕКИСТАНА УКРЕПЛЯТЬ И РАСШИРЯТЬ ПОЗИЦИИ НА МИРОВОМ РЫНКЕ 7 МИЛЛИОНОВ ДОЛЛАРОВ И БОЛЕЕ 1 МИЛЛИАРДА ДОЛЛАРОВ СРАВНИТЕ ЭТИ ЦИФРЫ ПОЧУВСТВОВАЛИ РАЗНИЦУ А ТАК КАК ПЕРВАЯ ЦИФРА ПОКАЗЫВАЕТ ОБЪЕМ ЭКСПОРТА ТЕКСТИЛЬНОЙ И ШВЕЙНО ТРИКОТАЖНОЙ ПРОДУКЦИИ ОТЕЧЕСТВЕННЫХ ПРЕДПРИЯТИЙ В 1994 ГОДУ А ВТОРАЯ ХАРАКТЕРИЗУЕТ ТОТ ЖЕ ПОКАЗАТЕЛЬ НО УЖЕ ЗА 2015 ГОД ТО МОЖНО С ПОЛНОЙ УВЕРЕННОСТЬЮ СКАЗАТЬ ЧТО ЗА ГОДЫ НЕЗАВИСИМОСТИ ЛЕГКАЯ ПРОМЫШЛЕННОСТЬ УЗБЕКИСТАНА НЕ ТОЛЬКО УВЕРЕННО ВЫШЛА НА ВНЕШНИЙ РЫНОК НО И ЗАНЯЛА НА НЕМ ПРОЧНЫЕ ПОЗИЦИИ ПРОДОЛЖАЯ АКТИВНО НАРАЩИВАТЬ ЭКСПОРТНЫЙ ПОТЕНЦИАЛ ЧТОБЫ ЛУЧШЕ ПОНЯТЬ И ПО ДОСТОИНСТВУ ОЦЕНИТЬ ТУ ГРАНДИОЗНУЮ РАБОТУ КОТОРАЯ БЫЛА ПРОВЕДЕНА РУКОВОДСТВОМ РЕСПУБЛИКИ И ТРУЖЕНИКАМИ ОТРАСЛИ В ДАННОМ НАПРАВЛЕНИИ НАДО ВСПОМНИТЬ ПОЛОЖЕНИЕ СУЩЕСТВОВАВШЕЕ В ЛЕГКОЙ ПРОМЫШЛЕННОСТИ УЗБЕКИСТАНА В НАЧАЛЕ 90 Х ГОДОВ ПРОШЛОГО ВЕКА КАК ТО В ТЕ ВРЕМЕНА ДОВЕЛОСЬ ПРИСУТСТВОВАТЬ НА ВСТРЕЧЕ НАШИХ СПЕЦИАЛИСТОВ С ЗАРУБЕЖНЫМИ КОЛЛЕГАМИ ПРИЕХАВШИМИ В НАШУ СТРАНУ И ИНТЕРЕСОВАВШИМИСЯ КАК РАЗ СИТУАЦИЕЙ В РЯДЕ ОТРАСЛЕЙ В ТОМ ЧИСЛЕ В ЛЕГКОЙ ПРОМЫШЛЕННОСТИ А ТОЧНЕЕ ПРОИЗВОДСТВОМ ТЕКСТИЛЬНОЙ ПРОДУКЦИИ ОНИ НИКАК НЕ МОГЛИ ПОНЯТЬ ПОЧЕМУ В УЗБЕКИСТАНЕ ГДЕ ВЫРАЩИВАЕТСЯ СТОЛЬКО ХЛОПКА ПРАКТИЧЕСКИ НЕТ СОБСТВЕННЫХ ТКАНЕЙ ГОТОВОЙ ОДЕЖДЫ А НА РЫНКАХ ЛЮДЯМ ПРЕДЛАГАЮТ ИМПОРТНЫЙ ТОВАР ПРИЧЕМ СОМНИТЕЛЬНОГО КАЧЕСТВА ЭТОТ ВОПРОС ПОЧЕМУ ЧАЩЕ ВСЕГО ЗВУЧАЛ В ЗАЛЕ НАПРИМЕР ПОЧЕМУ В РЕСПУБЛИКЕ ЗАНИМАЮТСЯ В ОСНОВНОМ ТОЛЬКО ПОЛУЧЕНИЕМ ВОЛОКНА ИЗ ХЛОПКА СЫРЦА ИЛИ ПОЧЕМУ ИЗ ОБЩЕГО КОЛИЧЕСТВА ВЫРАБОТАННОГО ВОЛОКНА В ПРЯЖУ И ТКАНИ ПЕРЕРАБАТЫВАЕТСЯ ВСЕГО ЛИШЬ 10 ПРОЦЕНТОВ ПОЧЕМУ НА ЭКСПОРТ ОТПРАВЛЯЕТСЯ ТОЛЬКО ХЛОПКОВОЕ ВОЛОКНО А НЕ ГОТОВЫЕ ИЗДЕЛИЯ С БОЛЕЕ ВЫСОКОЙ ДОБАВЛЕННОЙ СТОИМОСТЬЮ ЧТО ГОРАЗДО ПРИБЫЛЬНЕЕ ПОЧЕМУ ОТПРАВЛЯЯ ВЫРАЩЕННЫЙ НЕЛЕГКИМ ТРУДОМ ХЛОПОК ЗА ПРЕДЕЛЫ РЕСПУБЛИКИ ТОТ ЖЕ ХЛОПКОРОБ ЗАТЕМ ВЫНУЖДЕН ПОКУПАТЬ РУБАШКУ ИЗ УЗБЕКСКОГО ХЛОПКА ЗА ГОРАЗДО БОЛЬШУЮ ЦЕНУ И ПОДОБНЫХ ВОПРОСОВ БЫЛО НЕМАЛО НАДО ОТДАТЬ ДОЛЖНОЕ НАШИМ СПЕЦИАЛИСТАМ ОНИ ПЫТАЛИСЬ ОТВЕТИТЬ НА ВСЕ ВОПРОСЫ ГОВОРИЛИ О ТОМ ЧТО В БЫВШЕМ СОЮЗЕ УЗБЕКИСТАНУ ОТВОДИЛАСЬ РОЛЬ ОСНОВНОГО ПРОИЗВОДИТЕЛЯ ХЛОПКА СЫРЦА И ПОСТАВЩИКА ХЛОПКОВОГО ВОЛОКНА ДЛЯ ДАЛЬНЕЙШЕЙ ПЕРЕРАБОТКИ В ДРУГИЕ РЕСПУБЛИКИ О ТОМ ЧТО ОБОРУДОВАНИЕ ИСПОЛЬЗУЕМОЕ НА НЕМНОГОЧИСЛЕННЫХ ТЕКСТИЛЬНЫХ ПРЕДПРИЯТИЯХ БЫЛО МОРАЛЬНО УСТАРЕВШИМ И ФИЗИЧЕСКИ ИЗНОШЕННЫМ ЧТО НЕ ПОЗВОЛЯЛО ВНЕДРЯТЬ НОВЫЕ ТЕХНОЛОГИИ И ВЫПУСКАТЬ КОНКУРЕНТОСПОСОБНУЮ ПРОДУКЦИЮ О ТОМ ЧТО ВОПРОСАМИ ЭКСПОРТА ЗАНИМАЛСЯ ЦЕНТР КОТОРЫЙ И ОПРЕДЕЛЯЛ ЧТО СКОЛЬКО И В КАКИЕ СТРАНЫ ОТПРАВЛЯТЬ А САМА РЕСПУБЛИКА НАПРЯМУЮ ОТ ЭКСПОРТА ХЛОПКОВОГО ВОЛОКНА ДОХОДОВ НЕ ПОЛУЧАЛА МОЖНО БЫЛО ПОСОЧУВСТВОВАТЬ СПЕЦИАЛИСТАМ В ЧАСТНОСТИ ИМ ХОРОШО БЫЛО ИЗВЕСТНО ЧТО РАСПОЛАГАЯ ХОРОШЕЙ СЫРЬЕВОЙ БАЗОЙ ВЫГОДНО РАЗВИВАТЬ ТЕКСТИЛЬНОЕ ПРОИЗВОДСТВО ЧТО НА ПРОИЗВОДСТВО ПРЯЖИ И ТКАНЕЙ ПРИХОДИТСЯ ОКОЛО 80 ПРОЦЕНТОВ ДОБАВЛЕННОЙ СТОИМОСТИ КОНЕЧНОЙ ТОВАРНОЙ ПРОДУКЦИИ ТОГДА КАК НА ПРОИЗВОДСТВО ХЛОПКА ВОЛОКНА ОКОЛО 10 ПРОЦЕНТОВ ТО ЕСТЬ СТРАНЫ ЭКСПОРТЕРЫ ПРЯЖИ И ТКАНЕЙ ПОЛУЧАЮТ ДОХОДЫ В ВОСЕМЬ РАЗ ПРЕВЫШАЮЩИЕ ДОХОД ОТ ЭКСПОРТА ХЛОПКОВОГО ВОЛОКНА А ЕСЛИ ЭКСПОРТИРОВАТЬ ГОТОВУЮ ОДЕЖДУ ТО ДОХОДЫ ВЫРАСТУТ ЕЩЕ БОЛЬШЕ НО В ТЕ ВРЕМЕНА И В ТЕХ УСЛОВИЯХ ОПРЕДЕЛЕННЫХ БЫВШЕЙ КОМАНДНО АДМИНИСТРАТИВНОЙ СИСТЕМОЙ РЕШЕНИЯ ПРИНИМАЛИСЬ НЕ НА МЕСТАХ В СОЮЗНЫХ РЕСПУБЛИКАХ А В ТАК НАЗЫВАЕМОМ ЦЕНТРЕ СИТУАЦИЯ КАРДИНАЛЬНО ИЗМЕНИЛАСЬ ПОСЛЕ ОБРЕТЕНИЯ УЗБЕКИСТАНОМ НЕЗАВИСИМОСТИ РАЗРАБОТКИ И НАЧАЛА РЕАЛИЗАЦИИ ЦЕЛЕНАПРАВЛЕННОЙ СИСТЕМНОЙ ГОСУДАРСТВЕННОЙ ПРОМЫШЛЕННОЙ ПОЛИТИКИ НАЦЕЛЕННОЙ НА ДИВЕРСИФИКАЦИЮ И МОДЕРНИЗАЦИЮ ПОВЫШЕНИЕ КОНКУРЕНТОСПОСОБНОСТИ ОТЕЧЕСТВЕННОЙ ЭКОНОМИКИ ПОДДЕРЖКУ РАЗВИТИЯ ВЕДУЩИХ ОТРАСЛЕЙ В ТОМ ЧИСЛЕ ПО НАРАЩИВАНИЮ ИХ ЭКСПОРТНОГО ПОТЕНЦИАЛА ЗА СЧЕТ УГЛУБЛЕНИЯ ПЕРЕРАБОТКИ МЕСТНОГО СЫРЬЯ ПОСЛЕДОВАТЕЛЬНАЯ И ПЛАНОМЕРНАЯ ПЕРЕОРИЕНТАЦИЯ ЭКСПОРТА С СЫРЬЯ НА ГОТОВУЮ ПРОДУКЦИЮ С ВЫСОКОЙ ДОБАВЛЕННОЙ СТОИМОСТЬЮ ПОЗВОЛИЛА СФОРМИРОВАТЬ НОВУЮ СТРАТЕГИЮ РАЗВИТИЯ ЛЕГКОЙ ПРОМЫШЛЕННОСТИ НАЦЕЛЕННУЮ НА ПЕРСПЕКТИВУ МОЩНЫЙ ИМПУЛЬС НОВОМУ ЭТАПУ РАЗВИТИЯ ОТРАСЛИ ПРИДАЛИ ПРИНЯТЫЕ В РАЗНЫЕ ГОДЫ РУКОВОДСТВОМ РЕСПУБЛИКИ ПРОГРАММНЫЕ'
stemmer_en = nltk.SnowballStemmer("english")
stemmer_ru = nltk.SnowballStemmer("russian")

russian_stopwords = stopwords.words("russian")


# print([stemmer_ru.stem(word) for word in test_sen.split() if stemmer_ru.stem(word) not in stopwords.words("russian") ])
stemmed_sentence = [stemmer_ru.stem(word) for word in test_sen.split()]
tokens = [token for token in stemmed_sentence if token not in russian_stopwords\
            and token.strip() not in punctuation]
print(' '.join(tokens))

# Incase teacher allows lemmatizer 
# But Very slow will stick with stemmer
# morph = pymorphy2.MorphAnalyzer()

# for word in test_sen.split():
#     p = morph.parse(word)[0]
#     print(p.normal_form, end=" ")<jupyter_output><empty_output><jupyter_text>Implementation of Inverted Index<jupyter_code>class InvertedIndexer:
    """
    This class makes inverted index
    """

    def __init__(self, filename=False):
        self.filename = filename
        self.stemmer_ru = nltk.SnowballStemmer("russian")
        self.stopwords = set(stopwords.words("russian")) | set(stopwords.words("english"))
        self.punctuation = punctuation # from string import punctuation
        if filename:
            self.inverted_index = self._build_index(self.filename)
        else:
            self.inverted_index = defaultdict(set)

    def preprocess(self, sentence):
        """
        Method to remove stop words and punctuations return tokens
        """
        NONTEXT = re.compile('[^0-9 a-z#+_а-яё]')

        sentence = sentence.lower()
        sentence = sentence.translate(str.maketrans('', '', punctuation))
        sentence = re.sub(NONTEXT,'',sentence)

        # Heavy Operation Taking lot of time will move it outside
        # tokens = [self.stemmer_ru.stem(word) for word in sentence.split()]

        tokens = [token for token in sentence.split() if token not in self.stopwords]

        return tokens

    def stem_keys(self, inverted_index):
        """
        Called after index is built to stem all the keys and normalize them
        """
        logger.debug('Indexing Complete will not Stem keys and remap indexes')
        temp_dict = defaultdict(set)
        i = 0
        for word in tqdm(inverted_index):

            stemmed_key = self.stemmer_ru.stem(word)
            temp_dict[stemmed_key].update(inverted_index[word])
            inverted_index[word] = None

        inverted_index = temp_dict
        logger.debug('Done Stemmping Indexes')
        return inverted_index

    def _build_index(self, indexing_data_location):
        """
        This method builds the inverted index and returns the invrted index dictionary
        """
        inverted_index = defaultdict(set)
        with gzip.open(indexing_data_location, "rb") as f:

            for line in tqdm(f):
                line = line.decode().split('\t')
                file_number = line[0]
                subject = line[1]
                text = line[2]
                line = subject + ' ' + text

                for word in self.preprocess(line):
                        inverted_index[word].add(int(file_number))

        # Intermediate Save
        # with open('/content/intermediate_inverted_index.pickle', mode='wb') as f:
        #     pickle.dump(inverted_index, f)
        # Collab crashed don't do that 

        inverted_index = self.stem_keys(inverted_index)

        return inverted_index

    def save(self, filename_to_save):
        """
        Save method to save the inverted indexes
        """
        with open(filename_to_save, mode='wb') as f:
            pickle.dump(self.inverted_index, f)

    def load(self, filelocation_to_load):
        """
        Load method to load the inverted indexes
        """
        with open(filelocation_to_load, mode='rb') as f:
            self.inverted_index = pickle.load(f)



class SolutionPredictor:
    """
    This classes uses object of Hw1SolutionIndexer
    to make boolean search
    """
    def __init__(self, indexer):
        """
        indexer : object of class Hw1SolutionIndexer
        """
        self.indexer = indexer


    def find_docs(self, query):
        """
        This method provides booleaen search
        query : string with text of query
        Returns Python set with documents which contain query words
        Should return maximum 100 docs
        """
        # Your code is here
        tokens = self.indexer.preprocess(query)
        tokens = [self.indexer.stemmer_ru.stem(word) for word in tokens]
        docs_list = set()
        for word in tokens:
            if len(docs_list) > 0:
                docs_list.intersection_update(self.indexer.inverted_index[word])
            else:
                docs_list.update(self.indexer.inverted_index[word])

        return set(itertools.islice(docs_list, 100))<jupyter_output><empty_output><jupyter_text>Testing and Evaluating<jupyter_code>import time


BASELINE_SCORE = 0.3
BASELINE_TIME = 0.1


def load_true_docs(true_docs_path):
    """
    Function loads relevant docs for queries
    """
    true_docs_for_q = {}
    with open(true_docs_path) as f:
        for line in f:
            q, doc = line.strip().split('\t')
            q = int(q)
            doc = int(doc)
            if q not in true_docs_for_q:
                true_docs_for_q[q] = set()
            true_docs_for_q[q].add(doc)
    return true_docs_for_q 


def calc_score(predictor, true_docs_for_q, queries_path):
    """
    Function calcs percent of founded docs and 
    average responding time for query
    """
    t0_predict = time.time()
    num_founded_docs = 0
    num_true_docs = 0
    with open(queries_path) as f:
        for query_num, line in enumerate(f):
            q, q_text = line.strip().split('\t')
            q = int(q)
            pred_docs = predictor.find_docs(q_text)
            try:
                assert (len(pred_docs) <= 100)
            except AssertionError:
                print('Error! You should predict maximum 100 documents')
                return None, None
            true_docs = true_docs_for_q[q]
            founded_docs = true_docs & pred_docs
            num_founded_docs += len(founded_docs)
            num_true_docs += len(true_docs)
    t1_predict = time.time() 
    return num_founded_docs / num_true_docs, (t1_predict - t0_predict) / query_num<jupyter_output><empty_output><jupyter_text>Creating Index<jupyter_code># Comment if index is created
print ('Index is creating...')
start = time.time()
new_index = InvertedIndexer(indexing_data_location) 

end = time.time()
print ('Index has been created and saved as inverted_index.pickle in {:.4f}s'.format(end-start))


# Other utilities
# new_index.save('inverted_index.pickle')
# new_index = InvertedIndexer()
# new_index.load('inverted_index.pickle')
# print ('Index has been created and loaded from inverted_index.pickle in {:.4f}s'.format(end-start))<jupyter_output><empty_output><jupyter_text>Creating Predictor<jupyter_code>new_predictor = SolutionPredictor(new_index)

# Lets check your predictor on simple russian query 
some_query = 'ПРИВЕТ МИР'
try:
    assert (isinstance(new_predictor.find_docs(some_query), set))
except AssertionError:
    print('Error! You always should return Python set')


# Lets check your predictor on query with words which you did not see 
one_missed_word = 'ПРИВЕТ #SOMESTRANGE_WORD#'
all_missed_words = '#SOMESTRANGE_WORD#, HAHAHA#'
try:
    assert (isinstance(new_predictor.find_docs(one_missed_word), set))
    assert (isinstance(new_predictor.find_docs(all_missed_words), set))
except AssertionError:
    print('Error! You should return Python set even if you do not know any word in query')


true_docs_for_q = load_true_docs(truedoc_validate)
score, avgtime = calc_score(new_predictor, true_docs_for_q, query_validate)
if score is None or avgtime is None:
    print('Problem')

print ('Percent of founded docs: {}. Baseline result: {}'.format(score, BASELINE_SCORE))
print ('Average time for searching: {}. Baseline result: {}'.format(avgtime, BASELINE_TIME))<jupyter_output>Percent of founded docs: 0.34725634725634724. Baseline result: 0.3
Average time for searching: 0.0029340528306506927. Baseline result: 0.1
{'fraction_non_alphanumeric': 0.06296788637794724, 'fraction_numerical': 0.010704540684251033, 'mean_word_length': 3.8503562945368173, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 28, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '91582', 'license': 'MIT', 'n_tokens_mistral': 6942, 'n_tokens_neox': 7322, 'n_words': 1401, 'path': 'Boolean_Retrieval_Model.ipynb', 'repo_name': 'shivammehta007/Information-Retrieval', 'size': '14293'}