Чтобы выполнить очень похожий метод поиска, такой как простая версия Google, самый простой способ — использовать модель Bag of Words для измерения каждого слова в наборе данных. Поэтому при вводе запроса приложение будет возвращать документы с наибольшим суммарным весом в терминах запроса. В этом уроке я буду использовать Python-Flask. Вы можете течь со мной вместе.

ЧАСТЬ I: Подготовка документов/веб-страниц

Сначала начинаем работать в бэкенде. Для этого нам нужно загрузить важные библиотеки. Для этого проекта нам нужны библиотеки numpy, pandas и nltk. Затем мы будем использовать команду открытия файла csv. Для этого мы должны использовать библиотеку pandas. Для этого проекта я беру список отзывов об отелях, которые нахожу в Kaggle.

# Load libraries
import pandas as pd
import numpy as np
import nltk
df = pd.read_csv('./static/fewLondon.csv', encoding='latin-1')
df = df.loc[df['ReviewText'].str.contains('foo') == False]

Теперь нам нужно удалить знаки препинания из всех документов и разметить слова во всех документах. Когда это будет сделано, нам нужно сделать все слова строчными для всех документов. Есть много терминов, которые не так важны для рассмотрения, потому что они используются много раз в каждом документе, например, местоимения, глаголы to-be или предлоги. Такие слова называются стоп-словами. Следующим шагом будет удаление стоп-слов из всех документов. Более того, одно слово в английском языке имеет множество различных форм, таких как времена или множественное число. Таким образом, нам также необходимо выполнить определение корней, чтобы сократить эти слова и рассматривать их как одно и то же. После этих 2-х шагов мы урезаем большой объем памяти для хранения всех терминов.

# remove punctuation from all DOCs
exclude = set(string.punctuation)
alldocslist = []

for index, i in enumerate(searching):
    text = searching
    text = ''.join(ch for ch in text if ch not in exclude)
    alldocslist.append(text)
# tokenize words in all DOCS
plot_data = [[]] * len(alldocslist)

for doc in alldocslist:
    text = doc
    tokentext = word_tokenize(text)
    plot_data[index].append(tokentext)
# make all words lower case for all docs
for x in range(len(searching)):
    lowers = [word.lower() for word in plot_data[0][x]]
    plot_data[0][x] = lowers

print(plot_data[0][1][0:4])

# remove stop words from all docs
stop_words = set(stopwords.words('english'))

for x in range(len(searching)):
    filtered_sentence = [w for w in plot_data[0][x] if not w in stop_words]
    plot_data[0][x] = filtered_sentence

print(plot_data[0][1][0:4])

# stem words EXAMPLE (could try others/lemmers )

snowball_stemmer = SnowballStemmer("english")
stemmed_sentence = [snowball_stemmer.stem(w) for w in filtered_sentence]
stem1 = stemmed_sentence

porter_stemmer = PorterStemmer()
snowball_stemmer = SnowballStemmer("english")
stemmed_sentence = [porter_stemmer.stem(w) for w in filtered_sentence]
stem2 = stemmed_sentence

ЧАСТЬ II: СОЗДАНИЕ ОБРАТНОГО ИНДЕКСА

Теперь нам нужно создать обратный индекс, который дает номер документа для каждого документа и место, где появляется слово. Сначала нам нужно создать список всех слов. Затем мы должны выполнить однократное индексирование, которое является наиболее ресурсоемким шагом и требует времени для выполнения. Но нам нужно запустить это только один раз, потому что мы собираемся сохранить это, чтобы избежать пересчета.

TF (частота терминов) измеряет частоту слова в документе.

TF = (Number of time the word occurs in the text) / (Total number of words in text)

IDF (обратная частота документа) измеряет ранг конкретного слова по его релевантности в тексте.

IDF = (Total number of documents / Number of documents with word t in it)

Таким образом, TF-IDF является произведением TF и ​​IDF:

TF-IDF = TF * IDF
tfidf_paragraph = tfidf(df['ReviewText'])
def tfidf( paragraph ):
    # Tokenize using the white spaces
    dictOfWords = {}
    for index, sentence in enumerate(paragraph):
        try:
            tokenizedWords = nltk.tokenize.WhitespaceTokenizer().tokenize(paragraph[index])
            dictOfWords[index] = [(word, tokenizedWords.count(word)) for word in tokenizedWords]
        except KeyError:
            continue

    # second: remove duplicates
    termFrequency = {}

    for i in range(0, len(paragraph)):
        listOfNoDuplicates = []
        try:
            for wordFreq in dictOfWords[i]:
                if wordFreq not in listOfNoDuplicates:
                    listOfNoDuplicates.append(wordFreq)
                termFrequency[i] = listOfNoDuplicates
        except KeyError:
            continue

    # Third: normalized term frequency
    normalizedTermFrequency = {}
    for i in range(0, len(paragraph)):
        try:
            sentence = dictOfWords[i]
            lenOfSentence = len(sentence)
            listOfNormalized = []
            for wordFreq in termFrequency[i]:
                normalizedFreq = wordFreq[1] / lenOfSentence
                listOfNormalized.append((wordFreq[0], normalizedFreq))
            normalizedTermFrequency[i] = listOfNormalized
        except KeyError:
            continue

    # print(normalizedTermFrequency)
    # ---Calculate IDF

    # First: put al sentences together and tokenze words

    allDocuments = ''
    for sentence in paragraph:
        allDocuments += sentence + ' '
    print (allDocuments)
    allDocumentsTokenized = allDocuments.split(' ')

    # print(allDocumentsTokenized)
    allDocumentsNoDuplicates = []

    for word in allDocumentsTokenized:
        if word not in allDocumentsNoDuplicates:
            allDocumentsNoDuplicates.append(word)

    # print(allDocumentsNoDuplicates)
    # Second calculate the number of documents where the term t appears

    dictOfNumberOfDocumentsWithTermInside = {}

    for index, voc in enumerate(allDocumentsNoDuplicates):
        count = 0
        for sentence in paragraph:
            if voc in sentence:
                count += 1
        dictOfNumberOfDocumentsWithTermInside[index] = (voc, count)

    # print(dictOfNumberOfDocumentsWithTermInside)

    # calculate IDF

    dictOFIDFNoDuplicates = {}

    for i in range(0, len(normalizedTermFrequency)):
        listOfIDFCalcs = []
        try:
            for word in normalizedTermFrequency[i]:
                for x in range(0, len(dictOfNumberOfDocumentsWithTermInside)):
                    if word[0] == dictOfNumberOfDocumentsWithTermInside[x][0]:
                        listOfIDFCalcs.append(
                            (word[0],
                             math.log(len(paragraph) / dictOfNumberOfDocumentsWithTermInside[x][1])))
            dictOFIDFNoDuplicates[i] = listOfIDFCalcs
        except KeyError:
            continue
    # print(dictOFIDFNoDuplicates)

    # Multiply tf by idf for tf-idf

    dictOFTF_IDF = {}
    for i in range(0, len(normalizedTermFrequency)):
        listOFTF_IDF = []
        try:
            TFsentence = normalizedTermFrequency[i]
            IDFsentence = dictOFIDFNoDuplicates[i]
            for x in range(0, len(TFsentence)):
                try:
                    listOFTF_IDF.append((TFsentence[x][0], TFsentence[x][1] * IDFsentence[x][1]))
                except IndexError:
                    continue


            dictOFTF_IDF[i] = listOFTF_IDF
        except KeyError:
            continue
    #print(dictOFTF_IDF)
    return dictOFTF_IDF

ЧАСТЬ III: Настройка внешнего интерфейса и доработка веб-приложения

Затем мы создаем поиск слов, который принимает несколько слов и находит документы, содержащие оба слова, а также показатели для ранжирования. Результаты сортируются в соответствии с оценкой TF-IDF. Мы используем HTML5, CSS3, bootstrap 4 и javascript для внешнего интерфейса, а также используем javascript для выделения условий поиска. Когда приложение будет готово, оно должно работать так: http://jeewangw.pythonanywhere.com/

Вклад и вызов

Набор данных, который я нашел на kaggle, был неправильно отформатирован и закодирован. Таким образом, это создало много проблем при работе над бэкэндом. Я потратил много часов, пытаясь решить проблему. Сначала я думал изменить набор данных, позже решил исправить проблемы и провел больше исследований по проблеме. Наконец, через неделю я сам пришел к решению. Мне пришлось использовать исключение try-catch во многих разделах кода, чтобы сделать его работоспособным. Это была самая сложная часть работы над текстовым поиском. Одним из моих вкладов было решение этой проблемы. Другие вклады заключались в сохранении рассчитанной оценки TF-IDF в формате CSV для каждого документа, а также в проектировании и разработке внешнего интерфейса. Большая часть серверной части уже была сделана, но не для моего набора данных. Итак, мне пришлось изменить 60% его.

for i in range(0, len(normalizedTermFrequency)):
    listOFTF_IDF = []
    try:
        TFsentence = normalizedTermFrequency[i]
        IDFsentence = dictOFIDFNoDuplicates[i]
        for x in range(0, len(TFsentence)):
            try:
                listOFTF_IDF.append((TFsentence[x][0], TFsentence[x][1] * IDFsentence[x][1]))
            except IndexError:
                continue


        dictOFTF_IDF[i] = listOFTF_IDF
    except KeyError:
        continue
#print(dictOFTF_IDF)
return dictOFTF_IDF

В дополнение к этому я также создал свое веб-приложение для перевода и поиска иностранного текста.

Эксперименты

«Английский» стеммер лучше, чем оригинальный стеммер «портер».

print(SnowballStemmer(“английский”).stem(“щедро”))

щедрый

print(SnowballStemmer("портер").stem("щедро"))

генерировать

# stem words (could try others/lemmers )

snowball_stemmer = SnowballStemmer("english")
stemmed_sentence = [snowball_stemmer.stem(w) for w in filtered_sentence]
stem1 = stemmed_sentence

porter_stemmer = PorterStemmer()
stemmed_sentence = [porter_stemmer.stem(w) for w in filtered_sentence]
stem2 = stemmed_sentence

использованная литература

Датасет взят с: https://www.kaggle.com/PromptCloudHQ/reviews-of-londonbased-hotels

Формулы для TF-IDF взяты из статьи Амита Кумара Джайсвала: https://www.kaggle.com/amitkumarjaiswal/nlp-search-engine