KeyError reverse HashMap — простая совместная модель KNN на Python

У меня проблема с моим рекомендателем. Для некоторых сортов пива мне нужны рекомендации, потому что они работают отлично, но иногда возвращают KeyError. Я понятия не имею, почему это происходит?

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

Изображение KeyError во время выполнения скрипта

**Картинка **

Код

import os
import time
import gc
import argparse
import pandas as pd
from scipy.sparse import csr_matrix
from sklearn.neighbors import NearestNeighbors
from fuzzywuzzy import fuzz

class KnnRecommender:
    """
    This is an item based collaborative filtering recommender with KNN implemented by sklearn
    """
    def __init__(self, path_beers, path_tastingprofiles):
        """
        Recommender requires path to data: movies data and ratings data
        Parameters
        ----------
        path_movies: str, movies data file path
        path_ratings: str, ratings data file path
        """
        self.path_beers = path_beers
        self.path_tastingprofiles = path_tastingprofiles
        self.model = NearestNeighbors()

    def set_model_params(self, n_neighbors, algorithm, metric, n_jobs=None):
        """
        set model params for sklearn.neighbors.NearestNeighbors
        Parameters
        ----------
        n_neighbors: int, optional (default = 5)
        algorithm: {'auto', 'ball_tree', 'kd_tree', 'brute'}, optional
        metric: string or callable, default 'minkowski', or one of
            ['cityblock', 'cosine', 'euclidean', 'l1', 'l2', 'manhattan']
        n_jobs: int or None, optional (default=None)
        """
        if n_jobs and (n_jobs > 1 or n_jobs == -1):
            os.environ['JOBLIB_TEMP_FOLDER'] = '/tmp'
        self.model.set_params(**{
            'n_neighbors': n_neighbors,
            'algorithm': algorithm,
            'metric': metric,
            'n_jobs': n_jobs})

    def _prep_data(self):
        """
        prepare data for recommender
        1. beer-tastingprofile scipy sparse matrix
        2. hashmap of beer to row index in beer-tastingprofile scipy sparse matrix
        """
        # read data
        df_beers = pd.read_csv(
            os.path.join(self.path_beers),
            usecols=['beerID', 'name', 'beertypeID'],
            dtype = {'beerID': 'int32', 'name': 'str', 'beerID': 'int32'})
        df_tastingprofiles = pd.read_csv(
            os.path.join(self.path_tastingprofiles),
            usecols=['beerID', 'malty', 'sweet', 'sour', 'hoppy', 'bitter', 'fruity'],
            dtype = {'beerID': 'int32', 'malty': 'float32', 'sweet': 'float32', 'sour': 'float32', 'hoppy': 'float32', 'bitter': 'float32', 'fruity': 'float32'})


        #filtering beers/removing unprofiled beers
        df_beers_merged = pd.merge(df_tastingprofiles, df_beers, on='beerID')
        df_beers = df_beers_merged.drop(['malty', 'sweet', 'sour', 'hoppy', 'bitter', 'fruity'], axis=1)

        # pivot and create tastingprofile matrix
        df_tastingprofile_features = df_tastingprofiles.set_index('beerID')

        # create mapper from beer name to index
        hashmap = {
            beer: i for i, beer in
            enumerate(list(df_beers.set_index('beerID').loc[df_tastingprofile_features.index].name)) # noqa
        }

        #converting tastingprofile features to scipy sparse matrix
        mat_tastingprofile_features = csr_matrix(df_tastingprofile_features.values)


        # clean up
        del df_beers, df_beers_merged
        del df_tastingprofiles, df_tastingprofile_features
        return mat_tastingprofile_features, hashmap

    def _fuzzy_matching(self, hashmap, fav_beer):
        """
        return the closest match via fuzzy ratio.
        If no match found, return None
        Parameters
        ----------
        hashmap: dict, map beer name to index of the beer in data
        fav_beer: str, name of user input beer
        Return
        ------
        index of the closest match
        """
        match_tuple = []
        # get match
        for name, idx in hashmap.items():
            ratio = fuzz.ratio(name.lower(), fav_beer.lower())
            if ratio >= 60:
                match_tuple.append((name, idx, ratio))
        # sort
        match_tuple = sorted(match_tuple, key=lambda x: x[2])[::-1]
        if not match_tuple:
            print('Oops! No match is found')
        else:
            print('Found possible matches in our database: '
                  '{0}\n'.format([x[0] for x in match_tuple]))
            return match_tuple[0][1]

    def _inference(self, model, data, hashmap,
                   fav_beer, n_recommendations):
        """
        return top n similar beer recommendations based on user's input movie
        Parameters
        ----------
        model: sklearn model, knn model
        data: beer-tastingprofile matrix
        hashmap: dict, map beer name to index of the mobeervie in data
        fav_beer: str, name of user input beer
        n_recommendations: int, top n recommendations
        Return
        ------
        list of top n similar beer recommendations
        """
        # fit
        model.fit(data)
        # get input movie index
        print('You have input movie:', fav_beer)
        idx = self._fuzzy_matching(hashmap, fav_beer)
        # inference
        print('Recommendation system start to make inference')
        print('......\n')
        t0 = time.time()
        distances, indices = model.kneighbors(
            data[idx],
            n_neighbors=n_recommendations+1)
        # get list of raw idx of recommendations
        raw_recommends = \
            sorted(
                list(
                    zip(
                        indices.squeeze().tolist(),
                        distances.squeeze().tolist()
                    )
                ),
                key=lambda x: x[1]
            )[:0:-1]
        print('It took my system {:.2f}s to make inference \n\
              '.format(time.time() - t0))
        # return recommendation (movieId, distance)
        return raw_recommends

    def make_recommendations(self, fav_beer, n_recommendations):
        """
        make top n beer recommendations
        Parameters
        ----------
        fav_beer: str, name of user input beer
        n_recommendations: int, top n recommendations
        """
        # get data
        mat_tastingprofile_features, hashmap = self._prep_data()
        # get recommendations
        raw_recommends = self._inference(
            self.model, mat_tastingprofile_features, hashmap,
            fav_beer, n_recommendations)
        # print results
        reverse_hashmap = {v: k for k, v in hashmap.items()}
        print('Recommendations for {}:'.format(fav_beer))
        for i, (idx, dist) in enumerate(raw_recommends):
            #reverse_hashmap[idx]
            print('{0}: {1}, with distance of {2}'.format(i+1,reverse_hashmap[idx], dist))

def parse_args():
    parser = argparse.ArgumentParser(
        prog = "Beer Recommender",
        description = "Run KNN Beer Recommender")
    parser.add_argument('--path', nargs='?', default='',
                         help='input data path')
    parser.add_argument('--beer_filename', nargs='?', default='beer.csv',
                        help='provide beer filename')
    parser.add_argument('--tastingprofile_filename', nargs='?', default='tastingprofile.csv',
                        help='provide tastingprofile filename')
    parser.add_argument('--beer_name', nargs='?', default='',
                        help='provide your favorite beer name')
    parser.add_argument('--top_n', type=int, default=10,
                        help='top n beer recommendations')
    return parser.parse_args()    

if __name__ == '__main__':
    # get args
    args = parse_args()
    data_path = args.path
    beer_filename = args.beer_filename
    tastingprofile_filename = args.tastingprofile_filename
    beer_name = args.beer_name
    top_n = args.top_n
    # initial recommender system
    recommender = KnnRecommender(
    os.path.join(data_path, beer_filename),
    os.path.join(data_path, tastingprofile_filename))
    recommender.set_model_params(20, 'brute', 'cosine', -1)
    # make recommendations
    recommender.make_recommendations(beer_name, top_n)

Кажется, у вас KeyError в reverse_hashmap[idx]. Трудно сказать, что не так в вашем коде без контекста, но я предлагаю вам изменить его на reverse_hashmap.get(idx, 'NO DATA') для целей отладки.

Sergey Pugach 13.03.2019 14:52

@SergeyPugach Если хотите, я могу добавить немного контекста, что бы вы хотели знать? Система должна рекомендовать пиво на основе данных, которые я ввожу. Она использует хэш-карту, чтобы вернуть названия. Но с некоторыми сортами пива выдает KeyError. Отладка мудрая, я уже нашел, в чем проблема. Ключа нет в моей хэш-карте. Но я не знаю, где он берет этот ключ? Другое дело, это код, который я нашел и отредактировал. Большую часть я понимаю, но не могу найти, откуда он взял этот ключ, так как его нет в моей хэш-карте.

Tijmen Elseviers 13.03.2019 16:30

@SergeyPugach Я немного покопался в коде, кажется, что-то не так, когда я заполняю свою хэш-карту данными. У меня 2888 дегустационных профилей. Но хэш-карта имеет только 2369 значений. Так что может я что-то не так делаю. Это все очень ново для меня, поэтому я могу упустить что-то глупое.

Tijmen Elseviers 13.03.2019 17:18
ExtraPictureExtraPicture2
Tijmen Elseviers 13.03.2019 17:27
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
4
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я починил это. Выяснил, что когда я использую имена в качестве значений в Hashmap, дубликаты автоматически удаляются. Таким образом, хэш-карта была меньше, чем полный список баз данных. Я решил это, удалив дубликаты в моем наборе данных, прежде чем использовать его для чего-либо в алгоритме рекомендаций.

Я покажу вам свое простое исправление слиянием фреймов данных pandas и drop_duplicates.

#Remove duplicates from Beers dataset
        df_beers_noduplicates = df_beers.drop_duplicates(subset='name', keep='first', inplace=False)
        df_beers_merged = pd.merge(df_tastingprofiles, df_beers_noduplicates, on='beerID')
        df_beers = df_beers_merged.drop(['malty', 'sweet', 'sour', 'hoppy', 'bitter', 'fruity'], axis=1)
        df_tastingprofiles = df_beers_merged.drop(['name'], axis=1)

Другие вопросы по теме