Чтобы выполнить очень похожий метод поиска, такой как простая версия 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