Microsoft Business Intelligence avec librairies numériques

| Visual Numerics | Fleur-Anne.Blain |

Cette publication technique est destinée aux développeurs Microsoft intéressés par l'intégration d'algorithmes de data mining de tierces parties dans les services d'analyse Microsoft SQL Server 2005 Analysis Services (SSAS). Elle fournira un aperçu de haut niveau de l'architecture SSAS et de son environnement de développement plug-in managé, et elle démontrera le développement d'un plug-in pour un algorithme cluster K-means pour une bibliothèque numérique IMSL® C# avec des exemples de codes.

Commentez ♪

- Titre : Microsoft Business Intelligence avec librairies numériques

- Auteurs : Visual Numerics, Fleur-Anne.Blain

- Parution : 12 janvier 2009

- Mise à jour : 7 avril 2009

- Licence : Copyright ® 2008 Visual Numerics. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Audience

- I-B. Arguments

- I-C. Contexte

- I-D. Architecture plug-in

- II. Développement de plug-in managé

- II-A. Bibliothèque IMSL C# : Intégration de ClusterKMeans

- II-B. Démarrage

- II-C. Changements des métadonnées (Metadata.cs)

- II-D. Changements d'algorithme (Algorithm.cs)

- II-E. Entraînement et persistance de modèles

- II-E-1. Première phase

- II-E-2. Deuxième phase

- II-E-3. Troisième phase

- II-F. Persistance de modèles

- II-G. Prédiction

- II-H. Changements du navigateur d'algorithmes (AlgorithmNavigator.cs)

- II-I. Enregistrer l'algorithme avec Analysis Services

- II-J. Débogage

- III. Autres caractéristiques par défaut pour les développeurs d'algorithmes d'extraction issus de tierces parties

- IV. Expérience d'utilisateur

- IV-A. Excel 2007

- V. Épilogue

- V-A. Conclusion

- V-B. À propos de l'auteur

- V-C. Références

- VI. Annexes

- VniClusterMetadata.cs

- VniClusterKmeansAlgorithm.cs

- AlgorithmNavigator.cs

- Cluster.cs

- VniStore.cs

- VniPatternAttribute.cs

I - Introduction

I-A - Audience

Cette publication technique est destinée aux développeurs Microsoft intéressés par l'intégration d'algorithmes de data mining de tierces parties dans les services d'analyse Microsoft SQL Server 2005 Analysis Services (SSAS). Elle fournira un aperçu de haut niveau de l'architecture SSAS et de son environnement de développement plug-in managé, et elle démontrera le développement d'un plug-in pour un algorithme cluster K-means pour une bibliothèque numérique IMSL® C# avec des exemples de codes.

I-B - Arguments

Ces dernières années, les quantités de données disponibles aux organisations et les capacités de stockage de données ont crû exponentiellement. En conséquence, beaucoup d'organisations travaillent à tirer parti de ces données capturées pour prendre de meilleures décisions commerciales et gagner un avantage compétitif. Par les techniques d'analyse de données d'intelligence commerciale (BI), qui vont du data mining classique aux analyses avancées et prédictives, certaines organisations reposent sur l'analyse de données pour leur direction stratégique. Pour soutenir ces efforts, il est demandé aux développeurs de logiciels et aux professionnels des technologies de l'information d'incorporer des méthodes d'analyse de données avancée dans des applications d'analyse de données.

Sur la base de son expérience avec de nombreux clients mettant en ouvre l'analyse avancée, Visual Numerics a identifié un besoin croissant des organisations d'intégrer l'analyse de données à des systèmes existants et des banques de données (ex. entrepôts ou minientrepôts de données). L'intégration améliore significativement la durée d'analyse et réduit la complexité des systèmes en rapprochant l'analyse des données par rapport aux méthodes traditionnelles extraction-analyse-chargement. Microsoft SQL Server est une cible de premier choix pour l'analyse intégrée avec les capacités plug-in de SASS qui permettent de rapprocher les outils analytiques des données, et en définitive de les rapprocher des utilisateurs finaux des données.

Il existe typiquement deux types d'utilisateurs pour les algorithmes intégrés :

- des développeurs qui utilisent un algorithme pour créer un modèle de data mining, vérifier la précision du modèle et faire des prédictions en utilisant le modèle à l'issue de son entraînement ;

- des clients qui utilisent le modèle créé par le développeur. Par exemple, l'utilisateur d'un Microsoft Excel 2007 pourrait jouer le rôle d'un client.

Cette publication se focalisera sur l'intégration d'un algorithme Bibliothèque IMSL C# dans un environnement Microsoft BI. Les mêmes techniques peuvent s'appliquer à d'autres algorithmes C# de tierces parties.

Pour plus d'informations sur la Bibliothèque IMSL C#, visitez la page produit Bibliothèque IMSL C#.

I-C - Contexte

Microsoft SQL Server fournit des solutions pour le traitement de transactions en ligne à grande échelle, pour le stockage de données et les applications de e-commerce. Avec des ajouts récents, il peut aussi servir de plateforme BI pour l'intégration et l'analyse de données et pour rapporter des solutions. La figure suivante montre les relations entre les composants du SQL Server 2005. Pour plus d'informations, on se reportera à l'Aperçu du serveur SQL.

Figure 1. Microsoft SQL Server TechCenter et les relations entre ses composants

De plus, SQL Server 2005 fournit un studio de gestion SQL pour gérer des objets bases de données et un studio de développement BI pour développer des solutions BI. Ces outils sont basés sur Microsoft Visual Studio.

Le composant du SQL Server sur lequel on doit se focaliser pour intégrer les routines de la Bibliothèque IMSL C# est « Analysis Services » (services d'analyse). On se reportera à la Figure 2 ci-dessous.

Figure 2. Le composant Services d'Analyse du SQL Server

« Analysis Services » est un service Windows qui fournit des fonctionnalités de traitement analytique en ligne (OLAP) et de data mining par une combinaison de serveurs et de technologies client. Par défaut, Microsoft Analysis Services fournit plusieurs algorithmes de data mining, mais permet aussi à des tierces parties d'intégrer de nouveaux algorithmes dans le cadre d'application Analysis Services. Cette extensibilité permet aux classes de la Bibliothèque IMSL C# d'être intégrées à la plateforme BI SQL Server 2005. Pour plus d'informations, on se reportera à la Figure 3 ci-dessous ou reportez-vous à l'article Ajouter des algorithmes de data mining personnalisés au SQL Server 2005.

Figure 3. Architecture plug-in de data mining de SSAS 2005

Dans Microsoft Analysis Services, les algorithmes intégrés de data mining utilisent le Modèle Dimensionnel Unifié (UDM) pour accéder aux données. Le principe de l'UDM est de combiner des données de plusieurs sources de données et de les exposer comme des données virtuelles. Il crée une unique version de la vérité pour les données client. La capacité de créer un UDM rapidement dans le cadre d'application Analysis Services permet aux développeurs de se focaliser sur la logique de leur algorithme de data mining. Pour plus d'informations, on se reportera à la Figure 4 ci-dessous sur le Modèle Dimensionnel Unifié.

Outils clientsSources de données

Figure 4. Modèle Dimensionnel Unifié

I-D - Architecture plug-in

Le moteur de data mining communique avec les algorithmes plug-in via un jeu d'interfaces COM publiques (Component Object Model). Toutefois, la mise en ouvre de plug-ins managés nécessite d'utiliser l'assemblage DMPluginWrapper. Cet assemblage disponible gratuitement implémente les interfaces COM nécessaires à un plug-in et traduit les appels à l'interface en appels compatibles CLI. La Figure 5 montre comment des appels dans un plug-in managé sont traités dans Analysis Services.

Serveur ASAlgorithme plug-in managéAppel de fonction COMRésultats de fonction COMEncrypte les paramètres dans des types managés à l'appel d'une méthode managéeEncrypte les résultats dans des types non managés puis retourne les résultats au serveur

Figure 5. Plug-in managé de communication dans SSAS

II - Développement de plug-in managé

Il faut implémenter trois classes pour intégrer un algorithme de tierce partie dans SQL Server Analysis Services.

- La classe Metadata (métadonnées) - Cette classe est responsable de l'exposition des caractéristiques de l'algorithme et créer des objets algorithmes.

- La classe Algorithm (algorithme) - Cette classe détecte, conserve et utilise les configurations contenues dans les données.

- La classe Navigator (navigateur) - Cette classe est responsable de l'affichage des configurations trouvées dans la classe Algorithm.

Pour plus de détails, on se reportera au Tutoriel de l'algorithme API plug-in managé de data mining dans http://www.sqlserverdatamining.com/

II-A - Bibliothèque IMSL C# : Intégration de ClusterKMeans

Un tutoriel pour la construction d'un algorithme plug-in managé fourni par Microsoft comprend un exemple d'intégration d'un algorithme simple dans SQL Server Analysis Services. Le reste de ce chapitre explique le processus d'intégration pour la classe ClusterKMeans de la bibliothèque IMSL C#.

Il est recommandé de suivre les étapes du tutoriel de l'algorithme plug-in managé de data mining pour créer le shell plug-in. Ce code souche servira comme modèle pour développer l'algorithme ClusterKMeans.

II-B - Démarrage

1. Créez un nouveau répertoire appelé VNIClusterKMeans et copiez-y les fichiers et réglages du shell plug-in. Le shell plug-in est une solution créée en Microsoft Visual Studio 2005.

2. Changez toutes les références du shell name vers VNIClusterKMeans : cela signifie renommer la solution, le projet, le fichier de signature et toutes les références dans les propriétés du projet.

3. Assurez-vous que le projet est signé et que les étapes postconstruction qui enregistrent l'assemblage dans le GAC sont énumérées dans les propriétés du projet.

4. La solution doit avoir deux projets : DMPluginWrapper et VNIClusterKMeans. De plus, VNIClusterKMeans doit référencer le projet DMPluginWrapper. DMPluginWrapper est un assemblage COM interop qui traduit les appels COM de l'Analysis Services Server vers l'algorithme de plug-in managé. Il est disponible gratuitement dans le cadre du téléchargement de Algorithme API plug-in managé de data mining pour SQL Server 2005.

Note : Les classes Metadata, Algorithm et AlgorithmNavigator supportent de nombreuses fonctions, mais ce document ne décrira que les fonctions qui ont besoin d'être modifiées pour ClusterKMeans.

II-C - Changements des métadonnées (Metadata.cs)

Pour rendre le code managé visible au sous-système COM, décorez la classe Metadata avec [ComVisible (true)] et [Guid (<unique_id>)]. Dans ce cas, unique_id s'obtient en sélectionnant Tools Create GUID et en copiant l'unique_ID dans la classe Metadata. Votre déclaration devrait ressembler à ceci :

[ComVisible(true)]
[Guid("891DF04A-6B01-4125-B78E-C6DD8DB93471")]
[MiningAlgorithmClass(typeof(Algorithm))]
public class Metadata : AlgorithmMetadataBase

Ajoutez un constructeur à la classe Metadata. Ce constructeur peut appeler une fonction qui déclare tout paramètre que l'utilisateur pourrait avoir le droit de fixer avant d'appeler l'algorithme. Cela survient habituellement à partir du studio de développement BI ou à partir d'une application client telle que Microsoft Excel. Le code suivant permet aux utilisateurs de fixer la variable cluster_count à partir des applications client.

Public Metadata()
{
Parameters = DeclareParameters();
}

Static public MiningParameterCollection DeclareParameters()
{
MiningParameterCollection parameters = new MiningParameterCollection();
MiningParameter param;

// Exemple de population complète d'un paramètre dans un constructeur
param = new MiningParameter(
"CLUSTER_COUNT",
"Number of Clusters",
"3",
"(0.0, ...)",
true,
true,
typeof(System.Int32));

parameters.Add(param);
return parameters;
}

Modifiez la fonction GetServiceName pour retourner le nom du nouvel algorithme : VNI_ClusterKMeans. Changez aussi GetDisplayName et GetServiceDescription selon votre algorithme.

Modifiez GetParametersCollection pour retourner les paramètres.

Modifiez ParseParameterValue pour analyser les valeurs de paramètre entrées par les utilisateurs.

II-D - Changements d'algorithme (Algorithm.cs)

Cette classe met en ouvre des tâches spécifiques de l'algorithme. Elle est responsable de l'entraînement de l'algorithme, de la détection de tout modèle dans les données et de la prédiction de valeurs par utilisation de l'algorithme entraîné.

II-E - Entraînement et persistance de modèles

L'entraînement pour la classe ClusterKMeans comporte trois phases.

II-E-1 - Première phase

Dans la première phase, vous collecterez les données présentes dans tous les cas d'entraînement. Un Cas est un type de donnée au sein du cadre d'application Analysis Services. Vous pouvez vous représenter un Cas dans une rangée dans une base de données relationnelle. Pour plus d'informations, reportez-vous à l'aide Microsoft Data Mining Help.

Pendant l'entraînement, il vous sera présenté un Cas à la fois. Il vous faudra parcourir tous les Cas et créer une sorte de stockage pour toutes les données présentes dans chaque Cas. Les données collectées seront formatées et utilisées comme argument d'entrée dans la routine ClusterKMeans. Il faut noter qu'il y a perte de performances avec l'approche qui consiste à collecter des données à partir des Cas. Habituellement, les algorithmes traitent directement avec les Cas et ne comportent pas d'étape intermédiaire d'initialisation des données pour les transférer à un algorithme. Toutefois, cette transformation nous permet de profiter des interfaces de programmation existantes de la Bibliothèque IMSL C# sans aucune modification.

Les fonctions que vous aurez besoin d'annuler pour effectuer les tâches ci-dessus sont les suivantes :

- InsertCases - Cette fonction est le point d'entrée de l'entraînement de l'algorithme. Dans cette fonction, vous créerez un nouveau CaseProcessor pour traiter chaque Cas.

- ProcessCase - Cette fonction s'occupe de véritablement traiter un Cas. Dans cette fonction, vous devrez extraire les données du Cas et les conserver dans une sorte de réservoir que l'on pourra retrouver par la suite. Dans l'exemple ClusterKMeans, on a utilisé un objet VniStore pour stocker les valeurs des données. Pour plus de détails, on se reportera à « ClusterKmeans code » dans l'Annexe.

II-E-2 - Deuxième phase

Dans la deuxième phase, vous formaterez les données collectées dans la première phase, vous exécuterez l'algorithme, définirez les modèles de données et associerez les données à chaque modèle.

Les données collectées ont besoin d'être formatées pour être utilisées comme argument d'entrée dans l'algorithme. Dans le cas de ClusterKmeans, les données ont besoin d'être transformées en un ensemble à deux dimensions. Reportez-vous à la documentation de ClusterKmeans pour plus d'explications sur les arguments disponibles. Une fois les données formatées, on peut exécuter l'algorithme. Après l'exécution, vous travaillerez avec les résultats issus de l'algorithme pour définir les modèles de données. Il vaut mieux définir un objet pour représenter un modèle. Pour ClusterKMeans, on a utilisé un objet Cluster (classe) pour représenter un modèle. Cette classe contient toutes les informations liées à ce modèle, telles que les données et les statistiques. Par exemple, si ClusterKMeans détecte trois modèles, vous aurez trois objets Cluster pour représenter chacun des modèles détectés. Une fois l'objet défini pour représenter un modèle, il vous faudra peuple l'objet avec les données associées à ce modèle/cluster spécifique.

La fonction que vous aurez besoin d'annuler ou modifier

- InsertCases - Modifiez le code source pour ajouter la deuxième phase qui exécute l'algorithme et définit les modèles.

Pour ClusterKMeans, un objet VNIStore stocke les données de la première phase, et dans la deuxième phase exécute la routine et associe les données à chaque modèle détecté. Pour plus de détails, on se reportera à « ClusterKmeans code » dans l'Annexe.

II-E-3 - Troisième phase

Dans la troisième phase, vous fixerez les statistiques pour chaque modèle ou cluster. Cela inclut de fixer le nombre d'articles dans un modèle, min, max, variance et probabilité pour chaque attribut. On peut se représenter un attribut comme une colonne dans une rangée de données. L'idée est de fixer la distribution de cluster qui sera utilisée par la méthode de prédiction d'Analysis Services. Pour effectuer cette tâche, il vous faudra ajouter une fonction à votre objet modèle (cluster) pour mettre à jour toute statistique en rapport. Reportez-vous à la fonction updateStats dans la classe Cluster (voyez en Annexe pour les détails).

II-F - Persistance de modèles

L'objectif de la persistance est de sauvegarder toutes les informations nécessaires pour qu'elles puissent être chargées plus tard. L'API SQL Server Analysis Services fournit PersistenceWriter et PersistenceReader pour effectuer ces tâches. La classe Algorithm doit être utilisée pour sauvegarder toute information globale, mais les informations spécifiques au modèle doivent être déléguées à la classe du modèle. Pour ClusterKMeans, l'objet Cluster est responsable de l'écriture et du chargement des informations spécifiques au modèle.

Les fonctions que vous aurez besoin d'annuler sont SaveContent et LoadContent.

II-G - Prédiction

Dans le paradigme Analysis Services, prédire signifie retourner un histogramme (distribution) pour l'attribut cible. Pour ClusterKMeans, il vous faudra déterminer l'appartenance au cluster des nouvelles données puis déléguer la tâche de prédiction à ce cluster qui, en retour, retournera les statistiques issues de la troisième phase du processus d'entraînement du modèle.

Les fonctions que vous aurez besoin d'annuler sont les suivantes :

- Predict - Cette fonction est responsable de la détermination de l'appartenance au cluster et de la délégation de la prédiction à ce cluster ;

- Cluster.predict - Cette fonction est responsable du retour des statistiques déterminées dans la troisième phase du modèle d'entraînement.

II-H - Changements du navigateur d'algorithmes (AlgorithmNavigator.cs)

Cette classe est responsable de l'exposition des modèles détectés par l'algorithme plug-in. SQL Server Analysis Services utilise un objet Navigator (cette classe) pour exposer les modèles. Cet objet a l'aspect d'une structure arborescente. Par conséquent, il peut utiliser la notion de nœud pour afficher des propriétés de nœud, et il permet aussi d'alterner entre nœud parent ou nœud enfant du nœud actuel.

L'implémentation de la classe Navigator dépend de la Visionneuse que vous utiliserez pour les modèles que vous aurez détectés. Par défaut, Microsoft fournit plusieurs Visionneuses pour afficher les clusters : modèles Naïve Bayes, etc. Pour ClusterKMeans, la visionneuse Microsoft par défaut a été utilisée pour afficher les modèles détectés. Le code pour implémenter l'objet Navigator pour la visionneuse est disponible comme exemple en ligne et son listing figure aussi dans « Un tutoriel pour construire un algorithme plug-in managé » (cf. référence). Puisque ce code est disponible, les détails ne figurent pas dans ce chapitre, car il n'y a aucun changement apporté à ce code. Néanmoins, il se peut que vous ayez à modifier des parties de ce code si une visionneuse personnalisée est développée pour les modèles de données détectés.

En plus d'annuler la plupart des fonctions de classe Navigator en fonction de votre type de visionneuse, les fonctions que vous devrez annuler sont les suivantes :

- MetaData.GetViewerType - Fixe le type de visionneuse utilisé pour afficher les modèles de données ;

- MetaData.GetServiceType - Décrit la classe d'algorithmes qui inclut votre algorithme. Pour ClusterKmeans, c'est ServiceTypeClustering ;

- MetaData.GetSupportedStandardFunctions - Inclut le support des fonctions spécifiques du clustering ;

- Algorithm.GetNavigator - Retourne l'objet navigateur. Pour ClusterKMeans, retourne la classe AlgorithmNavigator.

II-I - Enregistrer l'algorithme avec Analysis Services

Cette étape permet à votre algorithme d'être utilisé par Analysis Services. Pour charger l'assemblage que vous avez construit dans Analysis services, il doit être visible dans le Global Assembly Cache (GAC). Les commandes postconstruction dans les propriétés du projet devraient effectuer cette étape ; si vous avez des problèmes, assurez-vous que les étapes postconstruction sont exactes et pointent sur un emplacement valide. Une fois que les assemblages sont visibles dans le GAC, il vous faudra utiliser le modèle XMLA fourni dans le document en ligne « Un tutoriel pour la construction d'un algorithme plug-in managé » (reportez-vous au chapitre Référence dans cette publication). Veillez à modifier le modèle en conséquence pour contenir une description de votre algorithme.

La demande d'enregistrement utilisant le fichier XMLA peut être envoyée depuis le studio de management SQL Server.

1. Lancez le studio de management SQL Server.

2. Connectez-vous au serveur cible Analysis Services.

3. Sélectionnez File New Analysis Services XMLA Query.

4. Collez la déclaration XMLA.

5. Exécutez la déclaration.

Ensuite, il vous faudra redémarrer Analysis Service. Sélectionnez Control Panel Administrative Tools Services SQL Server Analysis Services (MSSQLSERVER) et relancez le service. à ce moment, votre algorithme nouvellement créé devrait être accessible à tous les clients qui se connectent à Analysis Services.

Figure 6. Permettre à un algorithme d'être utilisé par Analysis Services

II-J - Débogage

Pour déboguer votre algorithme, vous devez d'abord l'enregistrer avec Analysis Services (cf. supra). Après enregistrement, sélectionnez Debug Attach pour le traiter à partir de l'environnement Visual Studio. Il apparaît la boîte de dialogue Attach To Process. Dans le champ de texte Attach To, assurez-vous que le code managé a été sélectionné. Parmi les processus disponibles, sélectionnez le processus msmdsrv.exe. Après cette sélection, vous devriez vous retrouver dans la session Debug où vous devriez pouvoir effectuer vos tâches normales de débogage. Pendant une session debug, une application client doit utiliser votre algorithme pour exécution pour s'arrêter à tout point d'interruption valide. Notez que toute modification de votre algorithme nécessitera qu'il soit réenregistré avec Analysis Services.

III - Autres caractéristiques par défaut pour les développeurs d'algorithmes d'extraction issus de tierces parties

En plus de l'UDM, il existe plusieurs caractéristiques par défaut disponibles pour les développeurs d'algorithmes de data mining issus de tierces parties.

Voici une liste de quelques caractéristiques qui peuvent être avantageuses pour vos routines de Bibliothèque IMSL C#.

1. On peut accéder aux algorithmes intégrés d'extraction comme un service web, puisque Analysis Services est un serveur natif XMLA (XML pour Analysis) auquel on peut accéder par les protocoles TCP ou HTTP.

2. Les résultats de data mining peuvent se distribuer facilement via les Reporting Services de SQL Server 2005.

3. Déploiement d'entreprise : multiples utilisateurs, stockage sécurisé, contrôle d'accès et déploiement facile sur un serveur partagé.

4. Interopérabilité avec d'autres produits de data mining via PMML.

5. L'intégration automatique de votre algorithme de data mining dans Excel 2007 permet à la grande base d'utilisateurs Excel d'accéder directement à l'algorithme de data mining grâce aux add-ins de data mining d'Excel.

6. Un appareil dimensionnable d'entraînement et de questionnement.

IV - Expérience d'utilisateur

Ce chapitre donne une brève description de l'expérience des utilisateurs pour le studio de développement BI et Excel.

Les développeurs de data mining utilisent le studio de développement BI pour développer un modèle. Commencez par créer le projet Analysis Services. La figure suivante montre l'état initial d'un projet Analysis Services.

Figure 7. État initial d'un projet Analysis Services

Avant de pouvoir lancer votre algorithme de data mining, il vous faudra définir des sources de données et des vues de sources de données. Cliquez avec le bouton droit de la souris sur les Sources de données et suivez les instructions que vous donne l'assistant. Faites de même pour les Vues de source de données. Vous pouvez vous représenter une source de données comme une base de données et une vue de source de données comme un tableau à l'intérieur de la base de données. Ensuite, cliquez avec le bouton droit de la souris sur la Structure de data mining, et votre algorithme sera automatiquement disponible dans la liste des algorithmes disponibles si l'enregistrement d'algorithme a réussi (voir plus haut).

Figure 8. Boîte de dialogue Sélection de technique de data mining, montrant VNI Cluster K-Means

Suivez les instructions de l'assistant de data mining. Ensuite, il vous faudra déployer la solution. Lorsqu'elle aura été déployée avec succès, vous pourrez rechercher votre modèle, visionner les modèles détectés et les caractéristiques de chaque modèle, et vérifier l'exactitude de votre modèle. Une fois que le développeur de data mining est satisfait du moteur entraîné, les clients (Excel) peuvent l'utiliser pour trouver leurs propres modèles et prédire des valeurs en utilisant le modèle entraîné. La figure suivante montre les modèles détectés.

Figure 9. Les modèles observés à titre d'exemple

IV-A - Excel 2007

Les add-ins de data mining pour Excel 2007 permettent aux utilisateurs soit de créer un nouveau modèle juste comme dans le studio de développement BI, soit d'utiliser un modèle existant créé avec le studio BI. L'onglet data mining d'Excel permet aux utilisateurs d'effectuer la préparation des données, leur modélisation, de vérifier leur exactitude, de les valider, d'utiliser un modèle existant et les gérer. La figure suivante montre les capacités de data mining sous Excel 2007.

Figure 10. Données d'échantillon chargées dans Excel

Les utilisateurs peuvent partitionner leurs données Excel en entraînement et test, créer de nouveaux modèles en utilisant une interface similaire à celle du studio BI et utiliser les données de test pour questionner un modèle existant. Par exemple, en utilisant le modèle entraîné IMSL C# Library ClusterKMeans avec les données de test sur les espèces florales, vous pouvez prédire le nom des espèces. La figure suivante montre l'étape correspondance de colonnes dans l'assistant Questionnement de data mining utilisé pour développer le questionnement pour prédire le nom des espèces de fleurs.

Figure 11. Assistant Questionnement de data mining configurant l'analyse de correspondance de colonnes

V - Épilogue

V-A - Conclusion

L'architecture de l'algorithme plug-in dans SQL Server 2005 Data Mining permet aux classes de bibliothèque IMSL C# de profiter pleinement de la plateforme Microsoft BI (UDM, solutions d'entreprise, etc.) Toutes les routines de la bibliothèque IMSL C# candidates à l'intégration dans SQL Server Analysis Service apportera ses propres défis, mais le développement initial devrait se prêter à des éléments réutilisables qui peuvent être utiles pour intégrer d'autres algorithmes de bibliothèque IMSL.

V-B - À propos de l'auteur

Jasmit Singh est Ingénieur Consultant Senior chez Visual Numerics. Jasmit travaille chez Visual Numerics depuis 2000. Son expérience va de la programmation C et Java à la programmation de bases de données et à la programmation graphique. Avant de travailler avec le Consulting Services group, Jasmit était développeur pour l'équipe de produit PV-WAVE.

Originaire d'Inde, parlant couramment anglais et hindi, Jasmit possède également des bachelor's degrees en mathématiques appliquées et en informatique de l'université de Boulder, Colorado.

V-C - Références

Bibliothèque numérique IMSL C # - Aperçu, documentation technique et CD d'évaluation disponibles sur demande.

Tutoriel de l'algorithme API plug-in managé de data mining est un tutoriel pour la construction d'un algorithme plug-in managé.

Introduction à SQL Server 2005 Data Mining est une brève introduction au data mining.

VI - Annexes

VniClusterMetadata.cs

Expose les caractéristiques de l'algorithme ClusterKMeans.

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.SqlServer.DataMining.PluginAlgorithms;
namespace VNI
{
/* doit créer un numéro GUID en exécutant
* Tools->Create GUID puis utilise Copier-Coller ici
* Ne copiez que le numéro unique et ignorez les autres
* nombres
*/
[ComVisible(true)]
[Guid("9BC1DB7D-52B9-46aa-9469-FF7B5A2B3F88")]
[MiningAlgorithmClass(typeof(VniClusterKMeansAlgorithm))]
public class VniClusterMetadata : AlgorithmMetadataBase
{
// Paramètres
protected MiningParameterCollection parameters;
// modélisation de registre
internal static MiningModelingFlag
MainAttributeFlag = MiningModelingFlag.CustomBase + 1;
/* initialisation de collecte de paramètres */
public VniClusterMetadata()
{
parameters = DeclareParameters();
}
static public MiningParameterCollection DeclareParameters()
{
MiningParameterCollection parameters
= new MiningParameterCollection();
MiningParameter param;
// Exemple de population complète d'un paramètre dans le
// constructeur
param = new MiningParameter(
"CLUSTER_COUNT",
"Number of Clusters",
"3",
"(0.0, ...)",
true,
true,
typeof(System.Int32));
parameters.Add(param);
// Exemple de population d'un paramètre après construction
// Quand on utilise ce constructeur, les réglages suivants
// sont générés :
// - isRequired = false
// - isExposed = true
// - description = ""
// - defaultValue = ""
// - valueEnum = ""
// parameters.Add(param);
return parameters;
}
public override string GetDisplayName()
{
return "VNI Cluster K Means";
}
public override string GetServiceName()
{
return "VNI_ClusterKMeans";
}
public override string GetServiceDescription()
{
// Description d'Arma
return "computes K-means (centroid) Euclidean metric clusters for an input" +
"data starting with initial estimates of the K cluster means.";
}
/* la valeur d'énumération de type service retournée par cette fonction
* décrit la classe d'algorithmes qui inclut votre algorithme, s'il existe.
* Par exemple, des classes populaires d'algorithmes incluent Association * Rules, Classification, and Clustering.
* L'exemple retourne ServiceTypeOther, parce qu'il n'appartient
* réellement à aucune de ces classes.
*
*/
public override PlugInServiceType GetServiceType()
{
return PlugInServiceType.ServiceTypeClustering;
}
/* La chaîne type de visionneuse retournée par cette fonction indique les
* outils dont l'objet visionneuse devrait afficher le contenu des
* modèles entraînés avec votre algorithme. Si le contenu de votre
* algorithme est similaire au contenu d'algorithmes incorporés, vous
* pouvez utiliser l'une des chaînes prédéfinies (commentées). Vous
* pouvez aussi construire votre propre visionneuse personnalisée et
* retourner son identifiant. Pour tout détail sur comment faire cela,
* cf. "Un tutoriel pour construire une visionneuse plug-in" sur
* http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsql90/html/TutConPIV.asp
*/
public override string GetViewerType()
{
//return MiningViewerType.MicrosoftAssociationRules;
//return MiningViewerType.MicrosoftCluster;
//return MiningViewerType.MicrosoftNaiveBayesian;
//return MiningViewerType.MicrosoftNeuralNetwork;
//return MiningViewerType.MicrosoftSequenceCluster;
//return MiningViewerType.MicrosoftTimeSeries;
//return MiningViewerType.MicrosoftTree;
//return string.Empty;
return MiningViewerType.MicrosoftCluster;
}
/* Ceci n'est pas utilisé par le AS mais exposé dans le rowset MINING_ALGORITHMS */
public override MiningScaling GetScaling()
{
return MiningScaling.Medium;
}
/* utilisé par le rowset mining_algorithm */
public override MiningTrainingComplexity GetTrainingComplexity()
{
return MiningTrainingComplexity.Low;
}
public override MiningPredictionComplexity GetPredictionComplexity()
{
return MiningPredictionComplexity.Low;
}
public override MiningExpectedQuality GetExpectedQuality()
{
return MiningExpectedQuality.Low;
}
/* Un algorithme supporte les dimensions de data mining si le contenu des
* modèles entraînés avec cet algorithme peut être organisé comme une
* dimension de data mining.
* Cet exemple retourne false.
*/
public override bool GetSupportsDMDimensions()
{
return false;
}
/* Le support pour les opérations drill-through est décrit au chapitre 10
* de ce document.*/
public override bool GetSupportsDrillThrough()
{
return false;
}
public override bool GetDrillThroughMustIncludeChildren()
{
return false;
}
/* Retourne true si votre modèle traite le case ID comme une variable
* séparée.*/
/* Cet exemple retourne false.*/
public override bool GetCaseIdModeled()
{
return false;
}
/*
* Ceci informe le serveur des statistiques qu'il faut construire avant de
* lancer l'entraînement d'algorithme. les champs d'énumération
* MarginalRequirements peuvent décrire toutes les statistiques
* (cas les plus courants), les statistiques pour attributs d'entrée
* seulement, pour attributs de sortie seulement, ou aucune statistique du
* tout.
*/
public override MarginalRequirements GetMarginalRequirements()
{
return MarginalRequirements.AllStats;
}
/*
* Cette méthode retourne les types de contenu qui sont supportés par cet
* algorithme pour attributs d'entrée.
* Tous les types courants sont supportés par le plug-in managé.
*/
public override MiningColumnContent[] GetSupInputContentTypes()
{
MiningColumnContent[] arInputContentTypes = new MiningColumnContent[]
{
MiningColumnContent.Discrete,
MiningColumnContent.Continuous,
MiningColumnContent.Discretized,
MiningColumnContent.NestedTable,
MiningColumnContent.Key
};
return arInputContentTypes;
}
/* Cette méthode retourne les types de contenu qui sont supportés par cet
* algorithme pour les attributs prévisibles. Tous les types courants sont
* supportés par le plug-in managé.
*/
public override MiningColumnContent[] GetSupPredictContentTypes()
{
MiningColumnContent[] arPredictContentTypes = new MiningColumnContent[]
{
MiningColumnContent.Discrete,
MiningColumnContent.Continuous,
MiningColumnContent.Discretized,
MiningColumnContent.NestedTable,
MiningColumnContent.Key
};
return arPredictContentTypes;
}
/* Cette méthode retourne la liste des fonctions standards Data Mining
* Extensions (DMX) supportées par cet algorithme. La plupart des
* fonctions standards peuvent être supportées sans aucun effort du
* développeur, une fois que la fonction AlgorithmBase.Predict est
* implémentée correctement.
*/
public override SupportedFunction[] GetSupportedStandardFunctions()
{
SupportedFunction[] arFuncs = new SupportedFunction[] {
// Fonctions générales de prédiction
SupportedFunction.PredictSupport,
SupportedFunction.PredictHistogram,
SupportedFunction.PredictProbability,
SupportedFunction.PredictAdjustedProbability,

SupportedFunction.PredictAssociation,
SupportedFunction.PredictStdDev,
SupportedFunction.PredictVariance,
SupportedFunction.RangeMax,
SupportedFunction.RangeMid,
SupportedFunction.RangeMin,
SupportedFunction.DAdjustedProbability,
SupportedFunction.DProbability,
SupportedFunction.DStdDev,
SupportedFunction.DSupport,
SupportedFunction.DVariance,
// fonctions liées au contenu
SupportedFunction.IsDescendent,
SupportedFunction.PredictNodeId,
SupportedFunction.IsInNode,
SupportedFunction.DNodeId,
// fonctions spécifiques du cluster
SupportedFunction.Cluster,
SupportedFunction.ClusterDistance,
SupportedFunction.ClusterPredictHistogram,
SupportedFunction.ClusterProbability,
SupportedFunction.PredictCaseLikelihood,
SupportedFunction.DCluster,
};
return arFuncs;
}
/* Cette méthode effectue une validation du jeu d'attributs avant de lancer
* l'entraînement.
* Par exemple, cette méthode peut garantir qu'au moins un attribut est
* prévisible, dans un algorithme de classification.
*/
public override void ValidateAttributeSet(AttributeSet attributeSet)
{
uint nCount = attributeSet.GetAttributeCount();
int mainAttrs = 0;
int inputAttrs = 0;
for (uint nIndex = 0; nIndex < nCount; nIndex++)
{
bool thisAttIsInput = false;
if ((attributeSet.GetAttributeFlags(nIndex) & AttributeFlags.Input) != 0)
{
inputAttrs++;
thisAttIsInput = true;
}
MiningModelingFlag[] modelingFlags = attributeSet.GetModelingFlags(nIndex);
for (int flagIndex = 0; flagIndex < modelingFlags.Length; flagIndex++)
{
if (modelingFlags[flagIndex] == MainAttributeFlag)
{
if (!thisAttIsInput)
{
string strMessage = string.Format(
"{0} ne peut être appliqué qu'à un attribut d'entrée",
GetModelingFlagName(MainAttributeFlag));
throw new System.Exception(strMessage);
}
mainAttrs++;
}
}
}
}
public override AlgorithmBase CreateAlgorithm(ModelServices model)
{
return new VniClusterKMeansAlgorithm();
}
public override MiningParameterCollection GetParametersCollection()
{
if (parameters == null)
{
DeclareParameters();
}
return parameters;
}
public override object ParseParameterValue(
int parameterIndex,
string parameterValue)
{
// Cette fonction devrait retourner un objet contenant la valeur du
// paramètre
// NOTE!! Le type de l'objet doit correspondre exactement au type déclaré
// du paramètre paramIndex
object retVal = null;
if (parameterIndex == 0)
{
// Ceci est une valeur pour PARAM1, qui est Int32,
// voyez l'implémentation de DeclareParameters
int dVal = System.Convert.ToInt32(parameterValue);
retVal = dVal;
}
/* else if (parameterIndex == 1)
{
// Ceci est une valeur pour PARAM2, qui est String,
// voyez l'implémentation de DeclareParameters
string strVal = parameterValue;
retVal = strVal;
}*/
else
{
throw new System.ArgumentOutOfRangeException("paramIndex");
}
return retVal;
}
/* Principal registre d'attribut ou tout registre personnalisé*/
public override MiningModelingFlag[] GetSupModelingFlags()
{
MiningModelingFlag[] arModelingFlags = new MiningModelingFlag[1];
arModelingFlags[0] = MainAttributeFlag;
// new MiningModelingFlag[] {
// MainAttributeFlag
// };
return arModelingFlags;
}
/* nom du principal registre d'attribut ou tout registre personnalisé*/
public override string GetModelingFlagName(MiningModelingFlag flag)
{
if (flag == MainAttributeFlag)
{
return "VNI_MAIN";
}
else
{
throw new System.Exception("Unknown VNI modeling flag : " +
flag.ToString());
}
}
}
}

VniClusterKmeansAlgorithm.cs

Cette classe implémente des tâches spécifiques de l'algorithme.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SqlServer.DataMining.PluginAlgorithms;
using VNI;
using System.Diagnostics;
using Imsl.Stat;
using Imsl.Math;
using System.Collections;
/* L'algorithme shell plug-in fonctionne de la façon suivante :
* - Pendant l'entraînement, il traverse tous les cas une fois et envoie des
* notifications de traitement.
* - Le contenu ne se compose que du nombre de cas et de la durée du
* traitement.
* Ces informations ne constituent pas des modèles utiles, mais c'est un
* exemple assez simple de comment utiliser les objets de persistance.
* - Le contenu a un noeud unique, étiqueté "All", qui a les statistiques
d'entraînement fixées comme distribution nodale.
* - La prédiction ignore les entrées et se base seulement sur les statistiques
* d'entraînement fixées.
*/
namespace VNI
{
/// <summary>
/// Persistence
/// </summary>
enum VNIClusterPersistenceMarker
{

MainAttribute,
Parameters,
ClusterCount,
ClusterDescription,
ClusterDistribution
}
/// <summary>
/// énumération contenant des délimitateurs dans le contenu
/// persisté
/// </summary>
enum MyPersistenceTag
{
ShellAlgorithmContent,
NumberOfCases
};
public class MyCaseProcessor : ICaseProcessor
{
protected VniClusterKMeansAlgorithm algo;
public MyCaseProcessor(VniClusterKMeansAlgorithm algo)
{
this.algo = algo;
}
public void ProcessCase(long caseID, MiningCase inputCase)
{
// Cherche une annulation toutes les 100 rangées
// Lance aussi une notification de traitement toutes les 100 rangées pour éviter de surcharger le tracerowset
if (caseID % 100 == 0)
{
algo.Context.CheckCancelled();
algo.trainingProgress.Progress();
}
algo.trainingProgress.Current++;
// C'est la condition de clustering triviale, voyez le sommet du // fichier pour les détails
//int destinationCluster = algo.InternalClusterMembership(inputCase);

// Obtention du l'appartenance au cluster
switch (algo.ProcessingPhase)
{
case VniClusterKMeansAlgorithm.MainProcessingPhase:
algo.vniStore.addCase(inputCase);
//algo.Clusters[destinationCluster].PushCase(inputCase);
break;
//case VniClusterKMeansAlgorithm.UpdateSupportPhase:
//algo.Clusters[destinationCluster].UpdateStats(inputCase);
//algo.vniStore.fillClusters(algo.Clusters);
// break;
}
}
}
public class VniClusterKMeansAlgorithm : AlgorithmBase
{
// Paramètres de mining. Saisit les paramètres d'entraînement avec
// leurs valeurs.
protected MiningParameterCollection algorithmParams;
// notifications de trace pendant le traitement
public TaskProgressNotification trainingProgress;
// "principal" attribut (utilisé en partition)
protected System.UInt32 MainAttribute;
protected double MainMean; // moyenne de l'attribut principal, s'il est continu
protected bool MainContinuous; // vrai si l'attribut principal est continu
// Représentation interne de cluster
public InternalCluster[] Clusters;
public int ProcessingPhase = 0;
public const int MainProcessingPhase = 1;
public const int UpdateSupportPhase = 2;
public const int FinalPhase = 3;
public VNIStore vniStore;
// maintenant, fixez-le à 3 mais passez un paramètre pour déterminer le nombre de clusters
public int num_clusters = 0;
public VniClusterKMeansAlgorithm()
{
algorithmParams = VNI.VniClusterMetadata.DeclareParameters();
MainAttribute = 0;
MainContinuous = false;
MainMean = 0.0;
vniStore = new VNIStore(this);
}
// Annulation facultative : on n'est PAS OBLIGE d'annuler ceci.
// L'implémentation base.Initialize ne fait rien, aussi n'est-il pas
// nécessaire de l'invoquer
protected override void Initialize()
{
// Initialise les paramètres avec les valeurs par défaut
this.algorithmParams["CLUSTER_COUNT"].Value = 3;
}
/*
a. La valeur spécifiée par l'utilisateur en déploiement.
b. la valeur par défaut (si aucune n'a été spécifiée par l'utilisateur pendant l'entraînement).
c. La meilleure valeur détectée automatiquement (heuristiquement) par l'algorithme pour le jeu d'entraînement actuel.
*/
protected override object GetTrainingParameterActualValue(int paramOrdinal)
{
return algorithmParams[paramOrdinal].Value;
}
public void ProcessCase(long caseId, MiningCase currentCase)
{
// Assurze-vous que le traitement n'a pas été annulé
this.Context.CheckCancelled();
// Augmente la valeur actuelle de la notification de trace
trainingProgress.Current++;
if (caseId % 100 == 0)
{
// lance la trace tous les 100 cas, pour éviter tout
// impact sur les performances

trainingProgress.Progress();
}
// utilise MiningCase ici pour l'entraînement
}
/* Load/Save content est utilisé pour la persistance des modèles détectés */
protected override void LoadContent(PersistenceReader reader)
{
// Charge l'attribut principal
reader.OpenScope((PersistItemTag)VNIClusterPersistenceMarker.MainAttribute);
reader.GetValue(out this.MainAttribute);
reader.GetValue(out this.MainContinuous);
reader.GetValue(out this.MainMean);
reader.CloseScope();
// Charge les paramètres
reader.OpenScope((PersistItemTag)VNIClusterPersistenceMarker.Parameters);
foreach (MiningParameter param in this.algorithmParams)
{
string name;
reader.GetValue(out name);
if (name != param.Name)
{
throw new System.Exception("Corrupted file -- unrecognized parameter name : " + name);
}
if (param.Name == "CLUSTER_COUNT")
{
int dVal = 0;
reader.GetValue(out dVal);
param.Value = dVal;
}
/*if (param.Name == "PARAM2")
{
string sVal;
reader.GetValue(out sVal);
param.Value = sVal;
}*/
}
reader.CloseScope();

// Charge les clusters
reader.OpenScope((PersistItemTag)VNIClusterPersistenceMarker.ClusterCount);
int clusterCount = 0;
reader.GetValue(out clusterCount);
reader.CloseScope();
Clusters = new InternalCluster[clusterCount];
for (int nIndex = 0; nIndex < clusterCount; nIndex++)
{
Clusters[nIndex] = new InternalCluster(this);
Clusters[nIndex].ClusterID = (ulong)nIndex;
Clusters[nIndex].Description = BuildClusterDescription(nIndex);
Clusters[nIndex].Load(ref reader);
}
}
protected override void SaveContent(PersistenceWriter writer)
{
// Sauvegarde l'attribut principal
writer.OpenScope((PersistItemTag)VNIClusterPersistenceMarker.MainAttribute);
writer.SetValue(this.MainAttribute);
writer.SetValue(this.MainContinuous);
writer.SetValue(this.MainMean);
writer.CloseScope();
// Sauvegarde les valeurs des paramètres connus
writer.OpenScope((PersistItemTag)VNIClusterPersistenceMarker.Parameters);
foreach (MiningParameter param in this.algorithmParams)
{
writer.SetValue(param.Name);
if (param.Name == "CLUSTER_COUNT")
{
int nVal = System.Convert.ToInt32(param.Value);
writer.SetValue(nVal);
}
}
writer.CloseScope();

// Sauvegarde les clusters
writer.OpenScope((PersistItemTag)VNIClusterPersistenceMarker.ClusterCount);
writer.SetValue(Clusters.Length);
writer.CloseScope();
for (int iIndex = 0; iIndex < Clusters.Length; iIndex++)
{
Clusters[iIndex].Save(ref writer);
}
}
protected override AlgorithmNavigationBase GetNavigator(
bool forDMDimensionContent)
{
return new AlgorithmNavigator(this, forDMDimensionContent);
}
private void PrepareForProcessing(int numClusters)
{
/*////////////////////////////////////////////////////////
* Détecte l'attribut principal
* Cherche l'attribut d'entrée qui a le registre MainAttributeFlag
*/
UInt32 nAtt = 0;
MainAttribute = AttributeSet.Unspecified;
for (nAtt = 0; nAtt < this.AttributeSet.GetAttributeCount(); nAtt++)
{
MiningModelingFlag[] flags = this.AttributeSet.GetModelingFlags(nAtt);
for (int flagIndex = 0; flagIndex < flags.Length; flagIndex++)
{
if (flags[flagIndex] == VniClusterMetadata.MainAttributeFlag)
{
MainAttribute = nAtt;
Debug.Assert((AttributeSet.GetAttributeFlags(nAtt) & AttributeFlags.Input) != 0);
break;
}
}
}
if (MainAttribute == AttributeSet.Unspecified)
{

for (nAtt = 0; nAtt < this.AttributeSet.GetAttributeCount(); nAtt++)
{
if ((AttributeSet.GetAttributeFlags(nAtt) & AttributeFlags.Input) != 0)
{
MainAttribute = nAtt;
}
}
}
Debug.Assert(MainAttribute != AttributeSet.Unspecified);
MainContinuous = (AttributeSet.GetAttributeFlags(MainAttribute) & AttributeFlags.Continuous) !=
0;
if (MainContinuous)
{
// Extraît la moyenne
AttributeStatistics stats = this.MarginalStats.GetAttributeStats(MainAttribute);
// gardez à l'esprit que, pour les attributs continus, le premier état manque et le deuxième état contient la moyenne de l'attribut
Debug.Assert(stats.StateStatistics.Count == 2);
Debug.Assert(stats.StateStatistics[1].Value.IsDouble);
MainMean = stats.StateStatistics[1].Value.Double;
}
// Utilisez trainingParams et les statistiques marginales ici pour en déduire le meilleur nombre de clusters
// Cet exemple le fixe This sample à 2
Clusters = new InternalCluster[numClusters];
for (int nIndex = 0; nIndex < numClusters; nIndex++)
{
// crée les clusters
Clusters[nIndex] = new InternalCluster(this);
// fixe les propriétés id du noeud interne
Clusters[nIndex].ClusterID = (ulong)nIndex;
// Généralement, le cluster devrait construire sa propre description
// Dans ce cas, l'algorithme connaît l'attribut principal, par
// conséquent il construira la description
Clusters[nIndex].Description = BuildClusterDescription(nIndex);
}

}
// Généralement, le cluster devrait construire sa propre description
// Dans ce cas, l'algorithme connaît l'attribut principal, par
// conséquent il construira la description
private string BuildClusterDescription(int nIndex)
{
string strRet = string.Empty;
//retourne "VNI Cluster " + nIndex.ToString();
string attName = AttributeSet.GetAttributeDisplayName(MainAttribute, false);
if (MainContinuous)
{
StateValue sVal = new StateValue();
sVal.SetDouble(MainMean);
object val = AttributeSet.UntokenizeAttributeValue(MainAttribute, sVal);
if (nIndex == 0)
{
strRet = string.Format("{0} < {1}", attName, "99999");
}
else
{
strRet = string.Format("{0} >= {1} OR {0} = Manquant", attName, val.ToString());
}
}
else
{
StateValue sVal = new StateValue();
sVal.SetIndex(1);
object val = AttributeSet.UntokenizeAttributeValue(MainAttribute, sVal);
if (nIndex == 0)
{
strRet = string.Format("{0} = {1}", attName, val.ToString());
}
else
{
strRet = string.Format("{0} NOT = {1}", attName, val.ToString());
}
}
return strRet;
}

public int InternalClusterMembership(MiningCase mcase)
{
/* Vérifie les erreurs pour faire que mCase ait les mêmes attributs que les attributs de cluster entraîné
*/
int member = -1;
double[] varr = new double[this.AttributeSet.GetAttributeCount()];
bool mcontinue = mcase.MoveFirst();
while (mcontinue)
{
UInt32 attribute = mcase.Attribute;
StateValue value = mcase.Value;
if (value.IsDouble) /*continu */
{
varr[attribute] = value.Double;
//attrList.Add(value.Double);
}
/* pour toute colonne discrète, il y aura un index
* représentant un état. Par exemple, une colonne avec
* des valeurs A,B,C aura 3 indices
* A=1, B=2, c=3
*/
if (value.IsIndex) /*discrète */
{
varr[attribute] = value.Double;
//attrList.Add((double)value.Index);
}
if (value.IsMissing) /* missing values */
{
//attrList.Add(null);
}
mcontinue = mcase.MoveNext();
}
// double[] vals = (double[])attrList.ToArray(typeof(double));
// utilise la distance euclidienne pour calculer le cluster
// On présume que le cas d'entrée a autant d'attributs que le
// modèle entraîné.
double[,] centers = this.vniStore.getCenters();
double[] distance = new double[Clusters.Length];
double esum = 0.0;

for (int i = 0; i < distance.Length; i++)
{
esum = 0.0;
for (int j = 0; j < varr.Length; j++)
{
esum += (varr[j] - centers[i, j]) * (varr[j] - centers[i, j]);
}
distance[i] = Math.Sqrt(esum);
}
double[] distcopy = new double[distance.Length];
Array.Copy(distance, distcopy, distance.Length);
Array.Sort(distcopy);
for (int m = 0; m < distance.Length; m++)
{
if (distcopy[0] == distance[m])
{
member = m;
break;
}
}
return member;
}
/// <summary>
/// Méthode de pseudo-clustering
/// Retourne 0 pour le premier cluster, 1 pour le deuxième
/// </summary>
/*public int InternalClusterMembership(MiningCase inputCase)
{
int nRet = 1;
bool bContinue = inputCase.MoveFirst();
while (bContinue)
{
if (inputCase.Attribute == MainAttribute)
{
if (MainContinuous)
{
// Safety check
Debug.Assert(inputCase.Value.IsDouble || inputCase.Value.IsMissing);
if (inputCase.Value.IsDouble && (inputCase.Value.Double < MainMean))
{

// Appartient au premier cluster
nRet = 0;
}
}
else
{
// Contrôle de sécurité
Debug.Assert(inputCase.Value.IsIndex || inputCase.Value.IsMissing);
if (inputCase.Value.IsIndex && (inputCase.Value.Index == 1))
{
// Appartient au premier cluster
nRet = 0;
}
}
break;
}
else
{
bContinue = inputCase.MoveNext();
}
}
return nRet;
}*/
/* Début du traitement de Cas. L'objet PushCaseSet nous permet
* d'interagir avec CaseProcessor
*/
protected override void InsertCases(PushCaseSet caseSet, MiningParameterCollection trainingParams)
{
// Initialise le jeu de cluster interne et les paramètres
LoadTrainingParameters(trainingParams);
/* obtient le nombre de clusters spécifié par l'utilisateur */
num_clusters = (int)GetTrainingParameterActualValue(0);
if (num_clusters == 0)
{
throw new System.ArgumentOutOfRangeException("num_clusters");
}
// préparation du traitement (2 clusters)
PrepareForProcessing(num_clusters);
// passe à la phase 1

ProcessingPhase = MainProcessingPhase;
// Boucle d'entraînement principale
while (ProcessingPhase != FinalPhase)
{
// Crée un objet de notification de progression de tâche pour envoyer des évènements de trace
trainingProgress = this.Model.CreateTaskNotification();
trainingProgress.Total = (int)this.MarginalStats.GetTotalCasesCount();
trainingProgress.Current = 0;
switch (ProcessingPhase)
{
case MainProcessingPhase:
trainingProgress.Format = "MainProcessingPhase: processing {0} out of {1}";
bool bSuccess = true;
try
{
trainingProgress.Start();
MyCaseProcessor processor = new MyCaseProcessor(this);
caseSet.StartCases(processor);
}
catch
{
bSuccess = false;
throw;
}
finally
{
trainingProgress.End(bSuccess);
}
break;
case UpdateSupportPhase:
trainingProgress.Format = "Updating support: processing {0} out of {1}";
this.vniStore.fillClusters(this.Clusters);
break;
}
// passe à la phase de traitement suivante
ProcessingPhase++;
}
// Traitement terminé, appelle PostProcess sur chaque cluster

for (int nIndex = 0; nIndex < Clusters.Length; nIndex++)
{
Clusters[nIndex].UpdateStats();
}
}
private void LoadTrainingParameters(MiningParameterCollection trainingParams)
{
// Copie les valeurs des paramètres dans cette collection de paramètres
foreach (MiningParameter param in trainingParams)
{
if (this.algorithmParams[param.Name] != null)
{
this.algorithmParams[param.Name].Value = param.Value;
}
}
}
protected override void Predict(MiningCase inputCase, PredictionResult predictionResult)
{
// Prédiction signifie
// - déterminer le bon cluster
// - effectuer la prédiction de cluster
int nCaseCluster = InternalClusterMembership(inputCase);
Clusters[nCaseCluster].Predict(ref predictionResult);
}
protected override ClusterMembershipInfo[] ClusterMembership(
long caseID,
MiningCase inputCase,
string targetCluster)
{
// Lance une notification de progression
Model.EmitSingleTraceNotification("ClusterMembership ... ");
int clIndex = InternalClusterMembership(inputCase);
string caption = Clusters[clIndex].Caption;
ClusterMembershipInfo[] ret = null;
if (targetCluster.Length > 0)
{
int cltargetCluster = -1;

for (int nIndex = 0; nIndex < Clusters.Length; nIndex++)
{
if (Clusters[nIndex].Caption.CompareTo(targetCluster) == 0)
{
cltargetCluster = nIndex;
break;
}
}
if (cltargetCluster == -1)
return null;
ret = new ClusterMembershipInfo[1];
ret[0] = new ClusterMembershipInfo();
ret[0].Caption = Clusters[cltargetCluster].Caption;
ret[0].ClusterId = Clusters[cltargetCluster].ClusterID;
ret[0].Distance = (cltargetCluster == clIndex) ? 0.0 : 1.0;
ret[0].Membership = 1.0 - ret[0].Distance;
ret[0].NodeUniqueName = Clusters[cltargetCluster].NodeUniqueName;
return ret;
}
ret = new ClusterMembershipInfo[Clusters.Length];
for (int nIndex = 0; nIndex < Clusters.Length; nIndex++)
{
ret[nIndex] = new ClusterMembershipInfo();
ret[nIndex].Caption = Clusters[nIndex].Caption;
ret[nIndex].ClusterId = Clusters[nIndex].ClusterID;
ret[nIndex].Distance = (nIndex == clIndex) ? 0.0 : 1.0;
ret[nIndex].Membership = 1.0 - ret[nIndex].Distance;
ret[nIndex].NodeUniqueName = Clusters[nIndex].NodeUniqueName;
}
return ret;
}
protected override double CaseLikelihood(
long caseID, MiningCase inputCase, bool normalized)
{
// cet exemple ne calcule la distance de cluster,
// aussi tous les cases sont-ils équiprobables
return 1.0;

}
}
}

AlgorithmNavigator.cs

Expose les modèles détectés par l'algorithme ClusterKMeans.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SqlServer.DataMining.PluginAlgorithms;
using VNI;
namespace VNI
{
class AlgorithmNavigator : AlgorithmNavigationBase
{
VniClusterKMeansAlgorithm algorithm;
bool forDMDimension;
int currentNode;
public AlgorithmNavigator(VniClusterKMeansAlgorithm currentAlgorithm, bool dmDimension)
{
algorithm = currentAlgorithm;
forDMDimension = dmDimension;
currentNode = 0;
}
protected override bool MoveToNextTree()
{
// Arbre unique pour cet algorithme
return false;
}
protected override int GetCurrentNodeId()
{
return currentNode;
}
protected override bool ValidateNodeId(int nodeId)
{

return (nodeId >= 0 && nodeId <= algorithm.Clusters.Length);
}
protected override bool LocateNode(int nodeId)
{
// Le seul noeud valide est 0
if (!ValidateNodeId(nodeId))
return false;
currentNode = nodeId;
return true;
}
protected override int GetNodeIdFromUniqueName(string nodeUniqueName)
{
int nNode = System.Convert.ToInt32(nodeUniqueName);
return nNode;
}
protected override string GetUniqueNameFromNodeId(int nodeId)
{
return nodeId.ToString("D3");
}
protected override uint GetParentCount()
{
switch (currentNode)
{
case 0:
return 0;
default:
return 1;
}
}
protected override void MoveToParent(uint parentIndex)
{
currentNode = 0;
}
protected override int GetParentNodeId(uint parentIndex)
{

return 0;
}
protected override uint GetChildrenCount()
{
switch (currentNode)
{
case 0:
return (uint)algorithm.Clusters.Length;
default:
return 0;
}
}
protected override void MoveToChild(uint childIndex)
{
if (currentNode == 0)
{
currentNode = (int)(childIndex + 1);
}
}
protected override int GetChildNodeId(uint childIndex)
{
if (currentNode == 0)
{
return (int)(childIndex + 1);
}
return -1;
}
protected override NodeType GetNodeType()
{
// Root est un modèle, tout le reste est un cluster
if (currentNode == 0)
return NodeType.Model;
else
return NodeType.Cluster;
}
protected override string GetNodeUniqueName()
{
return GetUniqueNameFromNodeId(currentNode);
}
protected override uint[] GetNodeAttributes()
{
// Il n'y a pas d'association entre un noeud et un attribut
return null;// new uint[] { 1, 2 };
}
protected override double GetDoubleNodeProperty(NodeProperty property)
{
double dRet = 0;
double dTotalSupport = algorithm.MarginalStats.GetTotalCasesCount();
double dNodeSupport = 0.0;
switch (currentNode)
{
case 0:
dNodeSupport = dTotalSupport;
break;
default:
dNodeSupport = algorithm.Clusters[currentNode - 1].Support;
break;
}
switch (property)
{
case NodeProperty.Support:
dRet = dNodeSupport;
break;
case NodeProperty.Score:
dRet = 0;
break;
case NodeProperty.Probability:
dRet = dNodeSupport / dTotalSupport;
break;
case NodeProperty.MarginalProbability:
dRet = dNodeSupport / dTotalSupport;
break;
}

return dRet;
}
protected override string GetStringNodeProperty(NodeProperty property)
{
string strRet = "";
switch (property)
{
case NodeProperty.Caption:
{
// IMPORTANT : L'en-tête d'un noeud peut être modifié par
// admin avec une déclaration telle que
// UPDATE Model.CONTENT SET NODE_CAPTION = 'Some cluster label'
// WHERE NODE_UNIQUE_NAME = '000001'
// Le listing des changements est immédiatement sauvegardé
// dans le modèle. Voici comment y accéder via model
// services
strRet = algorithm.Model.FindNodeCaption(GetNodeUniqueName());
if (strRet.Length == 0)
{
// si c'est vide, ça ne figure pas dans le listing
// génère la description
switch (currentNode)
{
case 0:
strRet = "All";
break;
default:
strRet = algorithm.Clusters[currentNode - 1].Caption;
break;
}
}
}
break;
case NodeProperty.ConditionXml:
// La condition pour qu'un cas corresponde à un noeud doit être
// représentée ici
strRet = "";
break;
case NodeProperty.Description:
switch (currentNode)
{
case 0:
strRet = "All";
break;
default:
strRet = algorithm.Clusters[currentNode - 1].Description; break;
}
break;
case NodeProperty.ModelColumnName:
strRet = "";
break;
case NodeProperty.RuleXml:
switch (currentNode)
{
case 0: strRet = "<Rule>All</Rule>"; break;
default:
strRet = "<Cluster>" + algorithm.Clusters[currentNode - 1].Caption +
"</Cluster>";
break;
}
break;
case NodeProperty.ShortCaption:
switch (currentNode)
{
case 0:
strRet = "All";
break;
default:
strRet = algorithm.Clusters[currentNode - 1].Caption;
break;
}
break;
}
return strRet;
}

protected override AttributeStatistics[] GetNodeDistribution()
{
switch (currentNode)
{
case 0:
{
// Pour le noeud racine, retourne les statistiques
// marginales de tout le modèle d'extraction
int attStats = (int)algorithm.AttributeSet.GetAttributeCount();
AttributeStatistics[] marginalStats = new AttributeStatistics[attStats + 2];
for (uint nIndex = 0; nIndex < attStats; nIndex++)
{
marginalStats[nIndex] = algorithm.MarginalStats.GetAttributeStats(nIndex);
}
// Ajoute des informations supplémentaires dans
// NODE_DISTRIBUTION, pas de chaîne
AttributeStatistics extraInfo = new AttributeStatistics();
extraInfo.Attribute = AttributeSet.Unspecified;
StateStatistics state = new StateStatistics();
state.ValueType = MiningValueType.Intercept;
state.Value.SetDouble(2.0);
extraInfo.StateStatistics.Add(state);
marginalStats[attStats] = extraInfo;
// Ajoute des informations supplémentaires dans
// NODE_DISTRIBUTION : valeur et nom d'attribut
extraInfo = new AttributeStatistics();
extraInfo.Attribute = AttributeSet.Unspecified;
extraInfo.NodeId = "Any string here";
state = new StateStatistics();
state.ValueType = MiningValueType.Other;
state.Value.SetIndex(124);
extraInfo.StateStatistics.Add(state);
marginalStats[attStats + 1] = extraInfo;
return marginalStats;
}
default:
// pour les noeuds clusters, retourne la distribution du cluster

return algorithm.Clusters[currentNode - 1].Distribution;
}
}
}
}

Cluster.cs

Un objet utilisé pour représenter le modèle détecté (cluster).

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Microsoft.SqlServer.DataMining.PluginAlgorithms;
using System.Collections;
namespace VNI
{
// Représentation interne d'un cluster
// Une instance de cette classe représentera un cluster détecté par
// l'algorithme plug-in.
public class InternalCluster
{
private string nodeUniqueName;
private string description;
/* Chaque cluster maintiendra la distribution des attributs pour
* tous les cas d'entraînement qui finissent dans ce cluster.
*/
private AttributeStatistics[] clusterDistribution;
public VNIPatternAttribute[] vniatts;
/* référence vers l'objet Algorithm qui a détecté ce cluster */
private VniClusterKMeansAlgorithm algo;
// ID interne du cluster
private int clusterID;
private int casesCount;
ArrayList clusterValues;
public InternalCluster(VniClusterKMeansAlgorithm parent)
{
algo = parent;

// Alloue de l'espace pour toutes les statistiques
// ainsi que pour le cluster Prediction
clusterDistribution = new AttributeStatistics[algo.AttributeSet.GetAttributeCount()];
/* pour chaque modèle, nous trouvons dans les données qu'il y
* aura des attributs qui appartiennent à ces modèles.
* VNIPatternattribute garde la trace de chaque attribut dans le
* modèle et ses valeurs et statistiques
*/
vniatts = new VNIPatternAttribute[algo.AttributeSet.GetAttributeCount()];
for (uint nIndex = 0; nIndex < algo.AttributeSet.GetAttributeCount(); nIndex++)
{
//////////////////////////////////////
// Distribution pour ce cluster
clusterDistribution[nIndex] = new AttributeStatistics();
vniatts[nIndex] = new VNIPatternAttribute();
// détermine le nombre d'états
uint statCount = algo.AttributeSet.GetAttributeStateCount(nIndex);
// détermine si l'attribut est continu
bool bContinuous = (algo.AttributeSet.GetAttributeFlags(nIndex) &
AttributeFlags.Continuous) != 0;
clusterDistribution[nIndex].Attribute = nIndex;
clusterDistribution[nIndex].Support = 0;
clusterDistribution[nIndex].Min = 0.0;
clusterDistribution[nIndex].Max = 0.0;
clusterDistribution[nIndex].NodeId = string.Empty;
clusterDistribution[nIndex].Probability = 0.0;
for (int nStatIndex = 0; nStatIndex < statCount; nStatIndex++)
{
StateStatistics stateStat = new StateStatistics();
if (nStatIndex == 0)
stateStat.Value.SetMissing();
else
{
if (bContinuous)
{

Debug.Assert(nStatIndex == 1);
stateStat.Value.SetDouble(0.0);
}
else
stateStat.Value.SetIndex((uint)nStatIndex);
}
stateStat.Probability = 0.0;
stateStat.AdjustedProbability = 0.0;
stateStat.ProbabilityVariance = 0.0;
stateStat.Support = 0.0;
stateStat.Variance = 0.0;
clusterDistribution[nIndex].StateStatistics.Add(stateStat);
}
}
}
// Fait entrer les cas dans le cluster
// Pour les attributs discrets, il suffit d'incrémenter le
// support d'état
// Pour les attributs continus, incrémentez le support d'état et
// actualisez Min et Max
// Additionnez temporairement les valeurs dans le champ de
// valeurs AttributeStatistics
public void PushCase(MiningCase inputCase)
{
bool bContinue = inputCase.MoveFirst();
casesCount++;
while (bContinue)
{
UInt32 attribute = inputCase.Attribute;
StateValue stateVal = inputCase.Value;
AttributeStatistics attStat = this.clusterDistribution[attribute];
bool bContinuous = (algo.AttributeSet.GetAttributeFlags(attribute) &
AttributeFlags.Continuous) != 0;
if (bContinuous)
{
Debug.Assert(attStat.StateStatistics.Count == 2);
// Attribut continu
bool first = attStat.StateStatistics[1].Support == 0.0;

if (stateVal.IsMissing)
{
attStat.StateStatistics[0].Support += 1.0;
}
else
{
Debug.Assert(stateVal.IsDouble);
double thisValue = stateVal.Double;
double dSumSoFar = attStat.StateStatistics[1].Value.Double;
// Incrémente le support pour l'état non
manquant
attStat.StateStatistics[1].Support += 1.0;
attStat.StateStatistics[1].Value.SetDouble(dSumSoFar + thisValue);
// Le support non manquant pour l'attribut est également incrémenté
attStat.Support += 1.0;
if (first)
{
attStat.Min = thisValue;
attStat.Max = thisValue;
}
else
{
if (attStat.Min > thisValue)
attStat.Min = thisValue;
if (attStat.Max < thisValue)
attStat.Max = thisValue;
}
}
}
else
{
// attribut discret
if (stateVal.IsMissing)
{
attStat.StateStatistics[0].Support += 1.0;
}
else
{
// Incrémente le support pour l'état non manquant
Debug.Assert(stateVal.IsIndex);

attStat.StateStatistics[stateVal.Index].Support += 1.0;
// et aussi pour l'attribut
attStat.Support += 1.0;
}
}
bContinue = inputCase.MoveNext();
}
}
public void UpdateStats()
{
// détermine le nombre d'états
//casesCount = algo.vniStore.getCaseCount();
for (int i = 0; i < this.clusterDistribution.Length; i++)
{
uint statCount = algo.AttributeSet.GetAttributeStateCount((uint)i);
AttributeStatistics attStat = this.clusterDistribution[i];
bool bContinuous = (algo.AttributeSet.GetAttributeFlags((uint)i) &
AttributeFlags.Continuous) != 0;
if (bContinuous)
{
casesCount = this.vniatts[i].getCount();
attStat.StateStatistics[1].Support = 0.0;
Debug.Assert(attStat.StateStatistics.Count == 2);
double ExistingSupport = this.vniatts[i].getCount();
attStat.StateStatistics[1].Support = ExistingSupport;
/* sum of values in the cluster */
attStat.StateStatistics[1].Value.SetDouble(vniatts[i].getSum());
attStat.StateStatistics[1].Variance = vniatts[i].getVariance();
attStat.Support = ExistingSupport;
attStat.Min = vniatts[i].getMin();
attStat.Max = vniatts[i].getMax();
//double ExistingSupport = attStats.StateStatistics[1].Support;
//double sumValues = attStats.StateStatistics[1].Value.Double;
//double dExistingMiu = sumValues / this.casesCount;
// Fixe la valeur pour un état existant. C'est Miu (SUM/ExistingSupport)

attStat.StateStatistics[1].Value.SetDouble(vniatts[i].getSum() / ExistingSupport);
// Fixe Prob/AdjProb pour l'état existant
attStat.StateStatistics[1].Probability = (ExistingSupport + 1.0) / (ExistingSupport +
attStat.StateStatistics.Count);
// Lisse adjustProb
attStat.StateStatistics[1].AdjustedProbability =
attStat.StateStatistics[1].Probability;
// Fixe Prob/AdjProb pour un état manquant ??
double MissingSupport = attStat.StateStatistics[0].Support;
attStat.StateStatistics[0].Probability = (MissingSupport + 1.0) / (ExistingSupport +
attStat.StateStatistics.Count);
// Lisse adjustProb
attStat.StateStatistics[0].AdjustedProbability =
attStat.StateStatistics[0].Probability;
// Fixe Prob/AdjProb pour tout l'attribut
attStat.Probability = attStat.StateStatistics[1].Probability;
attStat.AdjustedProbability = attStat.StateStatistics[1].AdjustedProbability;
}
else /* discrete */
{
/* divise encore le support suivant des vars discrètes
* red =1 , blue = 2, green =3. Décide combien de reds,
* blues ou greens il y a dans un cluster
*/
ArrayList vals = this.vniatts[i].getDataValues();
int max = (int)vniatts[i].getMax();
/* démarrage d'états discrets à 1 */
for (int k = 1; k <= max; k++)
{
/*boucle à travers toutes les valeurs de vniatts pour régler le support en fonction de la valeur*/
foreach (Object attrobj in vals)
{
/* null signifie valeur manquante */
if (attrobj != null)
{
if (k == (int)(double)attrobj)
{
Page 40
attStat.StateStatistics[(uint)k].Support += 1.0;
attStat.Support += 1.0;
}
}
}
}
// attribut discret, détecte l'état le plus populaire et calcule les probabilités
double ExistingSupport = 0.0;
for (uint nStateIndex = 0; nStateIndex < statCount; nStateIndex++)
{
double dStateSupport = attStat.StateStatistics[nStateIndex].Support;
attStat.StateStatistics[nStateIndex].Probability = (dStateSupport + 1.0) /
(this.casesCount + statCount);
attStat.StateStatistics[nStateIndex].AdjustedProbability =
attStat.StateStatistics[nStateIndex].Probability;
if (nStateIndex > 0)
ExistingSupport += dStateSupport;
}
// fixe les statistiques globales d'attribut
attStat.Probability = (ExistingSupport + statCount - 1.0) / (ExistingSupport +
statCount);
attStat.AdjustedProbability = attStat.Probability;
}
}
}
// Actualisation des statistiques
// Rien à faire pour discret ou pour continu manquant
// Pour continu, nécessite de calculer StdDev et Variance
// Variance = SUM( Xi - Miu)^2 / N
// Nous avons SUM( Xi) dans Value, d'où Miu = Value/N
// Ici nous incrémentons la Variance avec (Xi - Miu)^2/N
// donc, nous actualisons Value
public void UpdateStats(MiningCase inputCase)
{
// Actualisation des statistiques

bool bContinue = inputCase.MoveFirst();
while (bContinue)
{
UInt32 attribute = inputCase.Attribute;
StateValue stateVal = inputCase.Value;
AttributeStatistics attStat = this.clusterDistribution[attribute];
bool bContinuous = (algo.AttributeSet.GetAttributeFlags(attribute) &
AttributeFlags.Continuous) != 0;
if (bContinuous)
{
if (!stateVal.IsMissing)
{
double ExistingSupport = attStat.StateStatistics[1].Support;
double Miu = attStat.StateStatistics[1].Value.Double / ExistingSupport;
double thisValue = stateVal.Double;
attStat.StateStatistics[1].Variance += ((thisValue - Miu) * (thisValue - Miu) /
ExistingSupport);
}
}
bContinue = inputCase.MoveNext();
}
}
// Post-traitement des clusters pour les attributs continus :
// - état manquant : rien à faire
// - état non manquant : Value est actuellement SUM(Xi), divise par le support existant pour obtenir Miu
// - décide l'état le plus probable, manquant ou existant, pour prédiction
// - copie la probabilité existante, variance, etc. dans les statistiques de l'attribut
// pour les attributs discrets :
// - détecte l'état le plus probable pour prédiction
// - calcule la probabilité d'attribut (ExistingSupport/NumCases)
public void PostProcess()
{
for (uint nIndex = 0; nIndex < algo.AttributeSet.GetAttributeCount(); nIndex++)
{
// détermine le nombre d'états
uint statCount = algo.AttributeSet.GetAttributeStateCount(nIndex);
// détermine si l'attribut est continu
bool bContinuous = (algo.AttributeSet.GetAttributeFlags(nIndex) &
AttributeFlags.Continuous) != 0;
AttributeStatistics attStats = this.clusterDistribution[nIndex];
if (bContinuous)
{
double ExistingSupport = attStats.StateStatistics[1].Support;
double sumValues = attStats.StateStatistics[1].Value.Double;
double dExistingMiu = sumValues / this.casesCount;
// Fixe la valeur pour un état existant. C'est Miu (SUM/ExistingSupport)
attStats.StateStatistics[1].Value.SetDouble(dExistingMiu);
// Fixe Prob/AdjProb pour un état existant
attStats.StateStatistics[1].Probability = (ExistingSupport + 1.0) / (this.casesCount +
attStats.StateStatistics.Count);
// Lisse adjustProb
attStats.StateStatistics[1].AdjustedProbability =
attStats.StateStatistics[1].Probability;
// Fixe Prob/AdjProb pour un état manquant
double MissingSupport = attStats.StateStatistics[0].Support;
attStats.StateStatistics[0].Probability = (MissingSupport + 1.0) / (this.casesCount +
attStats.StateStatistics.Count);
// Lisse adjustProb
attStats.StateStatistics[0].AdjustedProbability =
attStats.StateStatistics[0].Probability;
// Fixe Prob/AdjProb pour tout l'attribut
attStats.Probability = attStats.StateStatistics[1].Probability;
attStats.AdjustedProbability = attStats.StateStatistics[1].AdjustedProbability;
}
else
{
// attribut discret, détecte l'état le plus populaire et calcule les probabilités
double ExistingSupport = 0.0;
for (uint nStateIndex = 0; nStateIndex < statCount; nStateIndex++)
{
double dStateSupport = attStats.StateStatistics[nStateIndex].Support;
attStats.StateStatistics[nStateIndex].Probability = (dStateSupport + 1.0) /
(this.casesCount + statCount);
attStats.StateStatistics[nStateIndex].AdjustedProbability =
attStats.StateStatistics[nStateIndex].Probability;
if (nStateIndex > 0)
ExistingSupport += dStateSupport;
}
// Fixe les statistiques globales de l'attribut
attStats.Probability = (ExistingSupport + statCount - 1.0) / (this.casesCount +
statCount);
attStats.AdjustedProbability = attStats.Probability;
}
}
}
public string NodeUniqueName
{
get
{
return nodeUniqueName;
}
}
public ulong ClusterID
{
get
{
return (ulong)clusterID;
}
set
{
clusterID = (int)value;
// Le nom de noeud unique est 1-based, 0 est la racine
nodeUniqueName = (clusterID + 1).ToString("D3");
}
}
public string Description
{
get
{
return description;
}
set
{
description = value;
}
}
public string Caption
{
get
{
return "Cluster " + (clusterID + 1).ToString();
}
}
public int Support
{
get
{
return casesCount;
}
}
public void Load(ref PersistenceReader reader)
{
// Charge informations sur le cluster
reader.OpenScope((PersistItemTag)VNIClusterPersistenceMarker.ClusterDescription);
reader.GetValue(out nodeUniqueName);
reader.GetValue(out description);
reader.GetValue(out clusterID);
reader.GetValue(out casesCount);
int distLength = 0;
reader.GetValue(out distLength);
reader.CloseScope();
clusterDistribution = new AttributeStatistics[distLength];
for (int nIndex = 0; nIndex < distLength; nIndex++)
{
// Sauvegarde chaque dist
reader.OpenScope((PersistItemTag)VNIClusterPersistenceMarker.ClusterDistribution);
clusterDistribution[nIndex] = new AttributeStatistics();
AttributeStatistics attStats = clusterDistribution[nIndex];
double dVal;
uint uVal;
reader.GetValue(out dVal); attStats.AdjustedProbability = dVal;
reader.GetValue(out uVal); attStats.Attribute = uVal;
reader.GetValue(out dVal); attStats.Max = dVal;
reader.GetValue(out dVal); attStats.Min = dVal;
reader.GetValue(out dVal); attStats.Probability = dVal;
reader.GetValue(out dVal); attStats.Support = dVal;
int statCount;
reader.GetValue(out statCount);
for (int nState = 0; nState < statCount; nState++)
{
StateStatistics stateStat = new StateStatistics();
reader.GetValue(out dVal); stateStat.AdjustedProbability = dVal;
reader.GetValue(out dVal); stateStat.Probability = dVal;
reader.GetValue(out dVal); stateStat.ProbabilityVariance = dVal;
reader.GetValue(out dVal); stateStat.Support = dVal;
reader.GetValue(out dVal); stateStat.Variance = dVal;
bool bIsMissing = false;
reader.GetValue(out bIsMissing);
if (bIsMissing)
{
stateStat.Value.SetMissing();
}
else
{
bool bIsIndex = false;
reader.GetValue(out bIsIndex);
if (bIsIndex)
{
uint indexVal;
reader.GetValue(out indexVal);
stateStat.Value.SetIndex(indexVal);
}
else
{
double dblVal;
reader.GetValue(out dblVal);
stateStat.Value.SetDouble(dblVal);
}
}
attStats.StateStatistics.Add(stateStat);
}
}
}
public void Save(ref PersistenceWriter writer)
{
// Sauvegarde les informations sur le cluster
writer.OpenScope((PersistItemTag)VNIClusterPersistenceMarker.ClusterDescription);
writer.SetValue(nodeUniqueName);
writer.SetValue(description);
writer.SetValue(clusterID);
writer.SetValue(casesCount);
writer.SetValue(clusterDistribution.Length);
writer.CloseScope();
for (int nIndex = 0; nIndex < clusterDistribution.Length; nIndex++)
{
// Sauvegarde chaque dist
writer.OpenScope((PersistItemTag)VNIClusterPersistenceMarker.ClusterDistribution);
AttributeStatistics attStats = clusterDistribution[nIndex];
writer.SetValue(attStats.AdjustedProbability);
writer.SetValue(attStats.Attribute);
writer.SetValue(attStats.Max);
writer.SetValue(attStats.Min);
writer.SetValue(attStats.Probability);
writer.SetValue(attStats.Support);
writer.SetValue(attStats.StateStatistics.Count);
for (int nState = 0; nState < attStats.StateStatistics.Count; nState++)
{
StateStatistics stateStat = attStats.StateStatistics[(uint)nState];
writer.SetValue(stateStat.AdjustedProbability);
writer.SetValue(stateStat.Probability);
writer.SetValue(stateStat.ProbabilityVariance);

writer.SetValue(stateStat.Support);
writer.SetValue(stateStat.Variance);
writer.SetValue(stateStat.Value.IsMissing);
if (!stateStat.Value.IsMissing)
{
writer.SetValue(stateStat.Value.IsIndex);
if (stateStat.Value.IsIndex)
{
writer.SetValue(stateStat.Value.Index);
}
else
{
writer.SetValue(stateStat.Value.Double);
}
}
}
}
}
// Predict : retourne la prédiction la plus probable dans ce cluster
public void Predict(ref PredictionResult predictionResult)
{
// predictionResult contient les options de prédiction et devrait être
// rempli avec les valeurs/statistiques prédites
AttributeGroup outputAttrs = predictionResult.OutputAttributes;
outputAttrs.Reset();
uint nAtt = AttributeSet.Unspecified;
while (outputAttrs.Next(out nAtt))
{
// Vérfie périodiquement si le traitement a été annulé
algo.Context.CheckCancelled();
// Construit la prédiction
AttributeStatistics attStats = new AttributeStatistics();
if (predictionResult.IncludeNodeId)
{
attStats.NodeId = this.NodeUniqueName;
}

attStats.Attribute = nAtt;
attStats.Min = clusterDistribution[nAtt].Min;
attStats.Max = clusterDistribution[nAtt].Max;
attStats.Support = clusterDistribution[nAtt].Support;
attStats.Probability = clusterDistribution[nAtt].Probability;
attStats.AdjustedProbability = clusterDistribution[nAtt].AdjustedProbability;
uint nStatesCount = (uint)clusterDistribution[nAtt].StateStatistics.Count;
for (uint index = 0; index < nStatesCount; index++)
{
StateStatistics clusterStateStat = clusterDistribution[nAtt].StateStatistics[index];
StateStatistics stateStat = new StateStatistics();
stateStat.AdjustedProbability = clusterStateStat.AdjustedProbability;
stateStat.Probability = clusterStateStat.Probability;
stateStat.Support = clusterStateStat.Support;
stateStat.Variance = clusterStateStat.Variance;
stateStat.ProbabilityVariance = clusterStateStat.ProbabilityVariance;
stateStat.Value = clusterStateStat.Value;
attStats.StateStatistics.Add(stateStat);
}
predictionResult.AddPrediction(attStats);
}
}
public AttributeStatistics[] Distribution
{
get
{
return clusterDistribution;
}
}
public void addValues(ArrayList values)
{
clusterValues = values;
}
public ArrayList getValues()
{
return clusterValues;

}
public VNIPatternAttribute[] getVNIAtts()
{
return vniatts;
}
}
}

VniStore.cs

Cette classe aide aux traductions de données entre Analysis Services et la routine IMSL cluster K Means.

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using Microsoft.SqlServer.DataMining.PluginAlgorithms;
using System.Collections;
using Imsl.Stat;
using Imsl.Math;
namespace VNI
{
/* Ceci est une classe d'aide qui vous aidera à traduire les données
* entre Analysis Services et les bibliothèques IMSL C#
*/
public class VNIStore
{
private ArrayList caseList;
/* référence vers l'objet Algorithm qui a détecté ce cluster */
private VniClusterKMeansAlgorithm algo;
private ClusterKMeans kmean;
private double[,] cases;
private double[,] centers;
public VNIStore(VniClusterKMeansAlgorithm parent)
{
caseList = new ArrayList();
algo = parent;
}
/* Fonction à exécuter. Cela dépend du choix de l'utilisateur à
* partir de la liste des algorithmes disponibles
*/

private void execute(String function, int cluster_count)
{
int m = 0;
int seeds_inc = caseList.Count / cluster_count;
ArrayList list = translateData(0);
cases = (double[,])list[0];
double[,] cluster_seeds = new double[3, ((double[])caseList[0]).Length];
for (int i = 0; i < cluster_count; i++)
{
for (int j = 0; j < ((double[])caseList[0]).Length; j++)
{
cluster_seeds[m, j] = cases[i * seeds_inc, j];
}
m++;
}
kmean = new ClusterKMeans(cases, cluster_seeds);
// Traduit les données en ce qui est attendu par la fonction
// Initialement, nous utilisons ClusterKMeans.
}
public void addCase(MiningCase mcase)
{
ArrayList attrList = new ArrayList();
double[] varr = new double[algo.AttributeSet.GetAttributeCount()];
bool mcontinue = mcase.MoveFirst();
while (mcontinue)
{
/* utiliser attribute pour indexer dans les valeurs correctes*/
UInt32 attribute = mcase.Attribute;
StateValue value = mcase.Value;
if (value.IsDouble) /*continous */
{
varr[attribute] = value.Double;
//attrList.Add(value.Double) ;
}
/* pour toutes les colonnes discrètes, il y aura un index
* représentant un état. Par exemple, une colonne avec les
* valeurs A,B,C aura 3 indices
* A=1, B=2, C=3
*/
if (value.IsIndex) /*discrete */
{

varr[attribute] = value.Index;
//attrList.Add((double)value.Index);
}
if (value.IsMissing) /* missing values */
{
//varr[attribute] = ;
//attrList.Add(null);
}
mcontinue = mcase.MoveNext();
}
caseList.Add(varr);
}
/* traduit ArrayList des cas d'entrées en des ensembles pour des
* structures pour la routine IMSL C#.
* Retourne un Arraylist d'un élément qui contient l'ensemble/objet
* qui doit être utilisé par la routine C#.
* 0 : utilise la CaseList pour calculer la dimension de l'ensemble
* 1-8 : utilise la CaseList et la crée en dimensions variant de 1 à * 8
* 9 : utilisation pour données spéciales.
* *
*/
private ArrayList translateData(int dim)
{
switch (dim)
{
case 0:
return getArrayFromCaseList();
//break;
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
break;
}

return null;
}
private ArrayList getArrayFromCaseList()
{
int rows = caseList.Count;
if (caseList.Count == 0) return null;
double[] attrlist;
/* Vérifiez le premier élément. Il devrait y avoir une autre
* arraylist avec une taille égale au nombre d'attributs dans
* MiningCase.
* En d'autres termes, si le tableau a 10 rangées et 5 colonnes,
* alors cet ensemble doit avoir 5 éléments.
*
*/
/* Maintenant, créez un ensemble 2d dans ce code, mais nous
* devrions avoir des objets qui convertissent cette datalist en
* 2D, 3D, structure, etc. qui sont nécessaires à la routine CNL. * Il peut exister une classe parente qui traite de la
* conversation principale puis certaines sous-classes qui
* effectuent des conversions spécifiques de certaines tâches
*/
double[,] data = new double[caseList.Count, ((double[])caseList[0]).Length];
for (int i = 0; i < caseList.Count; i++)
{
attrlist = (double[])caseList[i];
for (int j = 0; j < attrlist.Length; j++)
{
/* null signifie valeur manquante */
data[i, j] = (double)attrlist[j];
}
}
ArrayList rlist = new ArrayList();
rlist.Add(data);
return rlist;
}
public void fillClusters(InternalCluster[] clusters)
{
execute("ClusterKMeans", algo.num_clusters);
centers = kmean.Compute();
int[] cmember = kmean.GetClusterMembership();
int[] nc = kmean.GetClusterCounts();
// élimine les valeurs de cluster pour chaque cluster

// fondamentalement, initialise les modèles avec leurs valeurs
// initiales
// cela servira à initialiser les statistiques d'attributs
// utilisées dans la prédiction.
for (int i = 0; i <= nc.Length; i++)
{
// [] indices = new int[nc[i]];
// int m = 0;
for (int j = 0; j < cmember.Length; j++)
{
if (cmember[j] == i + 1)
{
double[] data = (double[])caseList[j];
/* ajoute des valeurs pour chaque attribut */
for (int m = 0; m < data.Length; m++)
{
clusters[i].vniatts[m].addDataValues(data[m]);
clusters[i].vniatts[m].setCount(nc[i]);
}
}
}
}
/* initialise les statistiques pour chaque cluster selon les attributs */
for (int i = 0; i < nc.Length; i++)
{
}
}
public double[,] getCenters()
{
return centers;
}
public int getCaseCount()
{
return caseList.Count;
}
}
}

VniPatternAttribute.cs

Cette classe sert à représenter un attribut dans le motif détecté. Un motif peut consister en un ou plusieurs attributs.

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace VNI
{
/* Microsoft possède le concept de Cas. Par exemple, un tableau de DB est
* un cas. L'enregistrement dans le Cas ou le tableau sont appelés jeu
* d'attributs. Chaque colonne dans un Case ou un tableau est appelé
* attribut. En data mining, le travail consiste à trouver des modèles
* dans vos données. Un motif est constitué de jeux d'attributs.
* Par exemple, en analyse de cluster, nous pourrions trouver 3 clusters,
* et chaque cluster aura différents jeux d'attributs.
* Pour chaque attribut dans le modèle, il nous faut initialiser quelques
* statistiques de base (min, max, variance, etc.)
* Cette classe gardera trace des statistiques de base
*/
public class VNIPatternAttribute
{
ArrayList dataValues;
int count = 0;
public VNIPatternAttribute()
{
dataValues = new ArrayList();
}
public double getMin()
{
if (dataValues.Count > 0)
{
double[] vals = (double[])dataValues.ToArray(typeof(double));
Array.Sort(vals);
return vals[0];
}
return 0;
}
public double getSum()
{
double sum = 0.0;
foreach (Object attrobj in dataValues)
{
/* null signifie valeur manquante */
if (attrobj != null)
{
sum += (double)attrobj;
}
}
return sum;
}
public double getMax()
{
if (dataValues.Count > 0)
{
double[] vals = (double[])dataValues.ToArray(typeof(double));
Array.Sort(vals);
return vals[vals.Length - 1];
}
return 0;
}
public double getVariance()
{
double variance = 0.0;
if (getCount() == 0)
{
return 0;
}
double ExistingSupport = getCount();
double Miu = this.getSum() / ExistingSupport;
foreach (Object attrobj in dataValues)
{
/* null signifie valeur manquante */
if (attrobj != null)
{
double thisValue = (double)attrobj;
variance += (thisValue - Miu) * (thisValue - Miu);
}
}
return variance / ExistingSupport;
}

public void addDataValues(double value)
{
dataValues.Add(value);
}
public int getCount()
{
return count;
}
public void setCount(int count)
{
this.count = count;
}
public ArrayList getDataValues()
{
return dataValues;
}
}
}