Prédiction des prix via une régression par les k plus proches voisins

Nous reprenons le jeu de données portant sur les transactions immobilières dans le conté de King, et nous allons établir un modèle d’apprentissage supervisé avec le package Graphlab create.

Dans un premier temps, le modèle se contentera du plus proche voisin, puis nous augmenterons le nombre de voisins pour trouver un optimum. En n’oubliant pas qu’ici la variance va diminuer avec le nombre de voisins alors que le bias augmentera, nous chercherons le bon compromis.

L’exercice présenté dans ce post est issu du cours sur la régression dans le parcours “Machine Learning” de l’université de Washington sur Coursera. Nous attaquons la dernière semaine de cours portant sur la régression par la méthode des k plus proches voisins.

import graphlab
import numpy as np

sales = graphlab.SFrame('kc_house_data_small.gl/kc_house_data_small.gl/')

Le bloc de code ci-dessous nous permet de convertir un SFrame en Array numpy 2D, pour le calcul des distances.

def get_numpy_data(data_sframe, features, output):
    data_sframe['constant'] = 1 # Ajout d'une colonne constante à un SFrame
    # On ajoute la colonne "constant" devant la liste des variables pour pouvoir l'extraire avec les autres
    features = ['constant'] + features
    # Sélection des features de data_SFrame donnée par la liste des features dans le SFrame features_sframe (avec la constante):
    features_sframe = data_sframe[features]
    # On convertit features_SFrame en matrice numpy:
    feature_matrix = features_sframe.to_numpy()
    output_sarray = data_sframe[output]
    output_array = output_sarray.to_numpy()
    return(feature_matrix, output_array)

Pour le calcul de distances il faut impérativement normaliser les variables. Autrement, par exemple la surface totale (de l’ordre de plusieurs centaines de m²) aura une influence disproportionnée par rapport au nombre de chambres (limité à 11). La fonction ci-dessous permet de normaliser nos données et retourne également les coefficients de normalisation.

def normalize_features(feature_matrix):
    norms = np.linalg.norm(feature_matrix,axis=0)
    normalized_features = feature_matrix / norms
    return (normalized_features,norms)

Je sépare ensuite les variables en jeu de train/test/validation, puis je crée une liste des variables que je normalise.

(train_and_validation, test) = sales.random_split(.8, seed=1) # initial train/test split
(train, validation) = train_and_validation.random_split(.8, seed=1) # Séparation du training set en training et validation

feature_list = ['bedrooms',  
                'bathrooms',  
                'sqft_living',  
                'sqft_lot',  
                'floors',
                'waterfront',  
                'view',  
                'condition',  
                'grade',  
                'sqft_above',  
                'sqft_basement',
                'yr_built',  
                'yr_renovated',  
                'lat',  
                'long',  
                'sqft_living15',  
                'sqft_lot15']
features_train, output_train = get_numpy_data(train, feature_list, 'price')
features_test, output_test = get_numpy_data(test, feature_list, 'price')
features_valid, output_valid = get_numpy_data(validation, feature_list, 'price')

features_train, norms = normalize_features(features_train) # Normalisation des variables du training set (colonnes)
features_test = features_test / norms # Normalisation du test set par les normes du training test
features_valid = features_valid / norms # Normalisation du validation set par les normes du training set

Le calcul de la distance entre une maison donnée (query) et chaque des maisons du training set se fait simplement via la fonction ci-dessous (axis=1 permet de sommer sur chaque ligne).

def compute_distance(train,query):
    distance = np.sqrt(np.sum((train - query)**2,axis=1))
    return distance

La fonction ci-dessous permet de trouver le k plus proches voisins.

def get_k_nearest(k, feature_matrix, feature_vector):
    distances = compute_distance(feature_matrix, feature_vector)
    index_array = np.argsort(distances)[0:k]
    return index_array

Fonction pour faire une prédiction

#k est le nombre de voisins, feature_matrix celle sur laquelle on va comparer, feature_set est la query
def predict(k, feature_matrix, output_values, feature_set):
    #On récupère les indices des voisins sur lesquels on veut faire la moyenne
    index = get_k_nearest(k,feature_matrix,feature_set)
    #Une fois qu'on a identifié les k voisins, on fait simplement la moyenne de leurs prix
    predicted_value = np.mean(output_values[index])
    return predicted_value

Maintenant on peut écrire une fonction pour calculer le prix de chaque maison d’un set donné. En entrée on donne le nombre de voisins, la matrice à utiliser, les paramètres à considérer.

def predict_full_set(k,feature_matrix, output_values, feature_set):
    n_houses = feature_set.shape[0]
    predicted_values = [predict(k,feature_matrix,output_values,feature_set[i]) for i in range(0,n_houses)]
    return predicted_values

Le RSS est calculé comme suit :

def get_residual_sum_of_squares(predictions, output):   
    # Then compute the residuals/errors
    residual = output - predictions
    # Then square and add them up
    residual_squared = residual * residual
    RSS = residual_squared.sum()
    return(RSS)

Pour trouver le nombre de plus proches voisins optimum, je réalise des prédictions pour des valeurs de k jusqu’à 16, et je calcule à chaque fois le RSS.

RSS_full = []
for i in range(1,16):
    prediction_i = predict_full_set(i,features_train,output_train,features_valid)
    RSS = get_residual_sum_of_squares(prediction_i,output_valid)
    RSS_full.append(RSS)   

import matplotlib.pyplot as plt
%matplotlib inline
kvals = range(1, 16)
plt.plot(kvals, RSS_full,'bo-')

Le résultat est tracé ci-dessous :

Ainsi le RSS le plus faible est obtenu pour k = 8 voisins.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.