.NET, TensorFlow et les moulins à vent de Kaggle - le voyage commence

Il s'agit d'une série d'articles sur mon voyage en cours dans la forêt sombre des compétitions Kaggle en tant que développeur .NET.

Je me concentrerai sur les réseaux de neurones (presque) purs dans cet article et les suivants. Cela signifie que la plupart des parties ennuyeuses de la préparation de l'ensemble de données, comme le remplissage des valeurs manquantes, la sélection des fonctionnalités, l'analyse des valeurs aberrantes, etc. sera intentionnellement ignoré.

La pile technologique sera l'API C # + TensorFlow tf.keras . À ce jour, il faudra également Windows. Les modèles plus grands dans les futurs articles peuvent avoir besoin d'un GPU approprié pour que leur temps de formation reste sain.

Prédisons les prix de l'immobilier!


Les prix des maisons sont une grande compétition pour les novices. Son jeu de données est petit, il n'y a pas de règles spéciales, le classement public a de nombreux participants et vous pouvez soumettre jusqu'à 4 entrées par jour.

Inscrivez-vous sur Kaggle, si vous ne l'avez pas encore fait, rejoignez ce concours et téléchargez les données. L'objectif est de prédire le prix de vente (colonne SalePrice) pour les entrées dans test.csv . L'archive contient train.csv , qui compte environ 1500 entrées avec un prix de vente connu pour s'entraîner. Nous allons commencer par charger cet ensemble de données et l'explorer un peu avant d'entrer dans les réseaux de neurones.

Analyser les données d'entraînement


Ai-je dit que nous allons sauter la préparation de l'ensemble de données? J'ai menti! Vous devez jeter un oeil au moins une fois.

À ma grande surprise, je n'ai pas trouvé de moyen facile de charger un fichier .csv dans la bibliothèque de classes standard .NET, j'ai donc installé un package NuGet, appelé CsvHelper . Pour simplifier la manipulation des données, j'ai également obtenu mon nouveau package d'extension LINQ préféré MoreLinq .

Chargement de données .csv dans DataTable
static DataTable LoadData(string csvFilePath) { var result = new DataTable(); using (var reader = new CsvDataReader(new CsvReader(new StreamReader(csvFilePath)))) { result.Load(reader); } return result; } 


ML.NET
L'utilisation de DataTable pour la formation à la manipulation des données est, en fait, une mauvaise idée.

ML.NET est censé avoir le chargement .csv et de nombreuses opérations de peparation et d'exploration de données. Cependant, il n'était pas encore prêt à cet usage particulier, lorsque je viens de participer au concours des prix des logements.


Les données ressemblent à ceci (seulement quelques lignes et colonnes):

IdMSSubClassMsoningLotFrontageLotarea
160RL658450
220RL809600
360RL6811250
470RL609550


Après le chargement des données, nous devons supprimer la colonne Id , car elle n'est en fait pas liée au prix des maisons:

 var trainData = LoadData("train.csv"); trainData.Columns.Remove("Id"); 

Analyse des types de données de colonne


DataTable n'infère pas automatiquement les types de données des colonnes et suppose qu'il s'agit de toutes les chaînes. La prochaine étape consiste donc à déterminer ce que nous avons réellement. Pour chaque colonne, j'ai calculé les statistiques suivantes: nombre de valeurs distinctes, combien d'entre elles sont des nombres entiers et combien d'entre elles sont des nombres à virgule flottante (un code source avec toutes les méthodes d'assistance sera lié à la fin de l'article):

 var values = rows.Select(row => (string)row[column]); double floats = values.Percentage(v => double.TryParse(v, out _)); double ints = values.Percentage(v => int.TryParse(v, out _)); int distincts = values.Distinct().Count(); 

Colonnes numériques


Il s'avère que la plupart des colonnes sont en fait des entiers, mais comme les réseaux de neurones fonctionnent principalement sur des nombres flottants, nous les convertirons de toute façon en doubles.

Colonnes catégorielles


D'autres colonnes décrivent les catégories auxquelles appartenait la propriété en vente. Aucun d'entre eux n'a trop de valeurs différentes, ce qui est bien. Pour les utiliser comme entrée pour notre futur réseau de neurones, ils doivent également être convertis pour doubler .

Initialement, je leur ai simplement attribué des numéros de 0 à distinctValueCount - 1, mais cela n'a pas beaucoup de sens, car il n'y a en fait aucune progression de "Facade: Blue" à "Facade: Green" en "Facade: White". Donc, au début, j'ai changé cela en ce qu'on appelle un codage à chaud , où chaque valeur unique obtient une colonne d'entrée distincte. Par exemple, «Facade: Blue» devient [1,0,0] et «Facade: White» devient [0,0,1].

Les réunir tous


Grande sortie d'exploration de données
CentralAir: 2 valeurs, pouces: 0,00%, flottants: 0,00%
Rue: 2 valeurs, pouces: 0,00%, flottants: 0,00%
Utilitaires: 2 valeurs, entiers: 0,00%, flottants: 0,00%
Allée: 3 valeurs, pouces: 0,00%, flottants: 0,00%
BsmtHalfBath: 3 valeurs, entiers: 100,00%, flottants: 100,00%
HalfBath: 3 valeurs, entiers: 100,00%, flottants: 100,00%
LandSlope: 3 valeurs, entiers: 0,00%, flottants: 0,00%
PavedDrive: 3 valeurs, pouces: 0,00%, flottants: 0,00%
BsmtFullBath: 4 valeurs, entiers: 100,00%, flottants: 100,00%
ExterQual: 4 valeurs, pouces: 0,00%, flottants: 0,00%
Cheminées: 4 valeurs, pouces: 100,00%, flotteurs: 100,00%
FullBath: 4 valeurs, entiers: 100,00%, flottants: 100,00%
GarageFinish: 4 valeurs, pouces: 0,00%, flottants: 0,00%
KitchenAbvGr: 4 valeurs, pouces: 100,00%, flottants: 100,00%
KitchenQual: 4 valeurs, pouces: 0,00%, flottants: 0,00%
LandContour: 4 valeurs, entiers: 0,00%, flottants: 0,00%
LotShape: 4 valeurs, entiers: 0,00%, flottants: 0,00%
PoolQC: 4 valeurs, entiers: 0,00%, flottants: 0,00%
BldgType: 5 valeurs, entiers: 0,00%, flottants: 0,00%
BsmtCond: 5 valeurs, entiers: 0,00%, flottants: 0,00%
BsmtExposure: 5 valeurs, entiers: 0,00%, flottants: 0,00%
BsmtQual: 5 valeurs, entiers: 0,00%, flottants: 0,00%
ExterCond: 5 valeurs, pouces: 0,00%, flottants: 0,00%
Clôture: 5 valeurs, pouces: 0,00%, flottants: 0,00%
GarageCars: 5 valeurs, entiers: 100,00%, flottants: 100,00%
Chauffage QC: 5 valeurs, pouces: 0,00%, flottants: 0,00%
LotConfig: 5 valeurs, pouces: 0,00%, flottants: 0,00%
MasVnrType: 5 valeurs, entiers: 0,00%, flottants: 0,00%
MiscFeature: 5 valeurs, entiers: 0,00%, flottants: 0,00%
MSZoning: 5 valeurs, entiers: 0,00%, flottants: 0,00%
YrSold: 5 valeurs, entiers: 100,00%, flottants: 100,00%
Électrique: 6 valeurs, pouces: 0,00%, flottants: 0,00%
FireplaceQu: 6 valeurs, pouces: 0,00%, flottants: 0,00%
Fondation: 6 valeurs, entiers: 0,00%, flottants: 0,00%
GarageCond: 6 valeurs, pouces: 0,00%, flottants: 0,00%
GarageQual: 6 valeurs, pouces: 0,00%, flottants: 0,00%
Chauffage: 6 valeurs, pouces: 0,00%, flottants: 0,00%
RoofStyle: 6 valeurs, pouces: 0,00%, flottants: 0,00%
Condition: 6 valeurs, pouces: 0,00%, flottants: 0,00%
BsmtFinType1: 7 valeurs, entiers: 0,00%, flottants: 0,00%
BsmtFinType2: 7 valeurs, entiers: 0,00%, flottants: 0,00%
Fonctionnel: 7 valeurs, pouces: 0,00%, flottants: 0,00%
GarageType: 7 valeurs, pouces: 0,00%, flottants: 0,00%
BedroomAbvGr: 8 valeurs, pouces: 100,00%, flottants: 100,00%
Condition2: 8 valeurs, pouces: 0,00%, flottants: 0,00%
HouseStyle: 8 valeurs, pouces: 0,00%, flottants: 0,00%
PoolArea: 8 valeurs, entiers: 100,00%, flottants: 100,00%
RoofMatl: 8 valeurs, pouces: 0,00%, flottants: 0,00%
Condition 1: 9 valeurs, pouces: 0,00%, flottants: 0,00%
GlobalCond: 9 valeurs, pouces: 100,00%, flottants: 100,00%
SaleType: 9 valeurs, pouces: 0,00%, flottants: 0,00%
Qualité globale: 10 valeurs, pouces: 100,00%, flottants: 100,00%
MoSold: 12 valeurs, pouces: 100,00%, flottants: 100,00%
TotRmsAbvGrd: 12 valeurs, entiers: 100,00%, flottants: 100,00%
Extérieur 1er: 15 valeurs, pouces: 0,00%, flottants: 0,00%
MSSubClass: 15 valeurs, entiers: 100,00%, flottants: 100,00%
Extérieur2e: 16 valeurs, pouces: 0,00%, flottants: 0,00%
3SsnPorch: 20 valeurs, entiers: 100,00%, flottants: 100,00%
MiscVal: 21 valeurs, entiers: 100,00%, flottants: 100,00%
LowQualFinSF: 24 valeurs, entiers: 100,00%, flottants: 100,00%
Quartier: 25 valeurs, entrées: 0,00%, flottants: 0,00%
YearRemodAdd: 61 valeurs, entiers: 100,00%, flottants: 100,00%
ScreenPorch: 76 valeurs, pouces: 100,00%, flottants: 100,00%
GarageYrBlt: 98 valeurs, pouces: 94,45%, flottants: 94,45%
LotFrontage: 111 valeurs, pouces: 82,26%, flottants: 82,26%
Année de construction: 112 valeurs, pouces: 100,00%, flottants: 100,00%
EnclosedPorch: 120 valeurs, entiers: 100,00%, flottants: 100,00%
BsmtFinSF2: 144 valeurs, entiers: 100,00%, flottants: 100,00%
OpenPorchSF: 202 valeurs, entiers: 100,00%, flottants: 100,00%
WoodDeckSF: 274 valeurs, entiers: 100,00%, flottants: 100,00%
MasVnrArea: 328 valeurs, entrées: 99,45%, flottants: 99,45%
2ndFlrSF: 417 valeurs, entiers: 100,00%, flottants: 100,00%
GarageArea: 441 valeurs, entiers: 100,00%, flottants: 100,00%
BsmtFinSF1: 637 valeurs, entiers: 100,00%, flottants: 100,00%
SalePrix: 663 valeurs, pouces: 100,00%, flottants: 100,00%
TotalBsmtSF: 721 valeurs, entiers: 100,00%, flottants: 100,00%
1stFlrSF: 753 valeurs, entiers: 100,00%, flottants: 100,00%
BsmtUnfSF: 780 valeurs, entiers: 100,00%, flottants: 100,00%
GrLivArea: 861 valeurs, entiers: 100,00%, flottants: 100,00%
LotArea: 1073 valeurs, entiers: 100,00%, flottants: 100,00%

De nombreuses colonnes de valeurs:
Extérieur1er: AsbShng, AsphShn, BrkComm, BrkFace, CBlock, CemntBd, HdBoard, ImStucc, MetalSd, Plywood, Stone, Stucco, VinylSd, Wd Sdng, WdShing
Exterior2nd: AsbShng, AsphShn, Brk Cmn, BrkFace, CBlock, CmentBd, HdBoard, ImStucc, MetalSd, Other, Plywood, Stone, Stucco, VinylSd, Wd Sdng, Wd Shng
Quartier: Blmngtn, Blueste, BrDale, BrkSide, ClearCr, CollgCr, Crawfor, Edwards, Gilbert, IDOTRR, MeadowV, Mitchel, NAmes, NoRidge, NPkVill, NridgHt, NWAmes, OldTown, Sawyer, SawyerW, Somerst, Stone, Stoner Veenker

flotteurs non analysables
GarageYrBlt: NA
LotFrontage: NA
MasVnrArea: NA

gammes de flotteurs:
BsmtHalfBath: 0 ... 2
HalfBath: 0 ... 2
BsmtFullBath: 0 ... 3
Cheminées: 0 ... 3
FullBath: 0 ... 3
KitchenAbvGr: 0 ... 3
GarageCars: 0 ... 4
Année: 2006 ... 2010
BedroomAbvGr: 0 ... 8
PoolArea: 0 ... 738
GlobalCond: 1 ... 9
Qualité globale: 1 ... 10
MoSold: 1 ... 12
TotRmsAbvGrd: 2 ... 14
MSSubClass: 20 ... 190
3SsnPorch: 0 ... 508
MiscVal: 0 ... 15500
LowQualFinSF: 0 ... 572
AnnéeRemodAjouter: 1950 ... 2010
ScreenPorch: 0 ... 480
GarageYrBlt: 1900 ... 2010
LotFrontage: 21 ... 313
Année de construction: 1872 ... 2010
EnclosedPorch: 0 ... 552
BsmtFinSF2: 0 ... 1474
OpenPorchSF: 0 ... 547
WoodDeckSF: 0 ... 857
MasVnrArea: 0 ... 1600
2ndFlrSF: 0 ... 2065
GarageArea: 0 ... 1418
BsmtFinSF1: 0 ... 5644
Prix: 34,900 ... 755,000
TotalBsmtSF: 0 ... 6110
1stFlrSF: 334 ... 4692
BsmtUnfSF: 0 ... 2336
GrLivArea: 334 ... 5642
LotArea: 1300 ... 215245


Dans cet esprit, j'ai construit le ValueNormalizer suivant, qui prend quelques informations sur les valeurs à l'intérieur de la colonne, et renvoie une fonction, qui transforme une valeur (une chaîne ) en un vecteur d' entité numérique pour le réseau de neurones ( double [] ):

ValueNormalizer
 static Func<string, double[]> ValueNormalizer( double floats, IEnumerable<string> values) { if (floats > 0.01) { double max = values.AsDouble().Max().Value; return s => new[] { double.TryParse(s, out double v) ? v / max : -1 }; } else { string[] domain = values.Distinct().OrderBy(v => v).ToArray(); return s => new double[domain.Length+1] .Set(Array.IndexOf(domain, s)+1, 1); } } 


Nous avons maintenant les données converties dans un format adapté à un réseau de neurones. Il est temps d'en construire un.

Construisez un réseau de neurones


À partir d'aujourd'hui, vous devrez utiliser une machine Windows pour cela.

Si vous avez déjà installé Python et TensorFlow 1.1x, tout ce dont vous avez besoin est

 <PackageReference Include="Gradient" Version="0.1.10-tech-preview3" /> 

dans votre fichier .csproj moderne. Sinon, reportez-vous au manuel Gradient pour effectuer la configuration initiale.

Une fois le package opérationnel, nous pouvons créer notre premier réseau profond peu profond.

 using tensorflow; using tensorflow.keras; using tensorflow.keras.layers; using tensorflow.train; ... var model = new Sequential(new Layer[] { new Dense(units: 16, activation: tf.nn.relu_fn), new Dropout(rate: 0.1), new Dense(units: 10, activation: tf.nn.relu_fn), new Dense(units: 1, activation: tf.nn.relu_fn), }); model.compile(optimizer: new AdamOptimizer(), loss: "mean_squared_error"); 

Cela créera un réseau neuronal non formé avec 3 couches de neurones et une couche de décrochage, ce qui aidera à éviter le surapprentissage.

tf.nn.relu_fn
tf.nn.relu_fn est la fonction d'activation de nos neurones. ReLU est connu pour bien fonctionner dans les réseaux profonds, car il résout le problème de gradient qui disparaît : les dérivées des fonctions d'activation non linéaires d'origine avaient tendance à devenir très petites lorsque l'erreur se propageait depuis la couche de sortie dans les réseaux profonds. Cela signifiait que les couches plus proches de l'entrée ne s'ajusteraient que très légèrement, ce qui ralentissait considérablement la formation des réseaux profonds.

Abandon
Le décrochage est une couche de fonctions spéciales dans les réseaux de neurones, qui ne contient en fait pas de neurones en tant que tels. Au lieu de cela, il fonctionne en prenant chaque entrée individuelle et la remplace de manière aléatoire par 0 sur la sortie automatique (sinon elle transmet simplement la valeur d'origine). Ce faisant, cela permet d'éviter le sur- ajustement à des entités moins pertinentes dans un petit ensemble de données. Par exemple, si nous ne supprimions pas la colonne Id , le réseau pourrait potentiellement mémoriser le mappage <Id> -> <SalePrice> exactement, ce qui nous donnerait une précision de 100% sur l'ensemble de formation, mais des chiffres complètement indépendants sur toutes les autres données.

Pourquoi avons-nous besoin d'abandon? Nos données d'entraînement n'ont que ~ 1500 exemples, et ce minuscule réseau de neurones que nous avons construit a> 1800 poids accordables. S'il s'agissait d'un simple polynôme, il pourrait correspondre à la fonction de prix que nous essayons d'approcher exactement. Mais alors, il aurait des valeurs énormes sur toutes les entrées en dehors de l'ensemble de formation d'origine.

Nourrir les données


TensorFlow attend ses données soit dans des tableaux NumPy, soit dans des tenseurs existants. Je convertis DataRows en tableaux NumPy:

 using numpy; ... const string predict = "SalePrice"; ndarray GetInputs(IEnumerable<DataRow> rowSeq) { return np.array(rowSeq.Select(row => np.array( columnTypes .Where(c => c.column.ColumnName != predict) .SelectMany(column => column.normalizer( row.Table.Columns.Contains(column.column.ColumnName) ? (string)row[column.column.ColumnName] : "-1")) .ToArray())) .ToArray() ); } var predictColumn = columnTypes.Single(c => c.column.ColumnName == predict); ndarray trainOutputs = np.array(predictColumn.trainValues .AsDouble() .Select(v => v ?? -1) .ToArray()); ndarray trainInputs = GetInputs(trainRows); 

Dans le code ci-dessus, nous convertissons chaque DataRow en ndarray en prenant chaque cellule qu'il contient et en appliquant le ValueNormalizer correspondant à sa colonne. Ensuite, nous mettons toutes les lignes dans un autre ndarray , obtenant un tableau de tableaux.

Aucune transformation de ce type n'est nécessaire pour les sorties, où nous convertissons simplement les valeurs du train en un autre ndarray .

Il est temps de descendre le gradient


Avec cette configuration, tout ce que nous devons faire pour former notre réseau est d'appeler la fonction d' ajustement du modèle:

 model.fit(trainInputs, trainOutputs, epochs: 2000, validation_split: 0.075, verbose: 2); 

Cet appel annulera en fait les 7,5% restants de l'ensemble de formation pour validation, puis répétera les 2000 fois suivantes:

  1. diviser le reste du train Entrées en lots
  2. alimenter ces lots un par un dans le réseau neuronal
  3. erreur de calcul en utilisant la fonction de perte que nous avons définie ci-dessus
  4. rétropropagation de l'erreur à travers les gradients des connexions neuronales individuelles, en ajustant les poids

Pendant la formation, il affichera l'erreur du réseau sur les données qu'il a mises de côté pour validation en tant que val_loss et l'erreur sur les données de formation elles-mêmes comme une simple perte . Généralement, si val_loss devient beaucoup plus important que la perte , cela signifie que le réseau a commencé à sur-ajuster. J'aborderai cela plus en détail dans les articles suivants.

Si vous avez tout fait correctement, une racine carrée de l'une de vos pertes devrait être de l'ordre de 20000.



Soumission


Je ne parlerai pas beaucoup de la génération du fichier à soumettre ici. Le code pour calculer les sorties est simple:

 const string SubmissionInputFile = "test.csv"; DataTable submissionData = LoadData(SubmissionInputFile); var submissionRows = submissionData.Rows.Cast<DataRow>(); ndarray submissionInputs = GetInputs(submissionRows); ndarray sumissionOutputs = model.predict(submissionInputs); 

qui utilise principalement des fonctions, qui ont été définies précédemment.

Ensuite, vous devez les écrire dans un fichier .csv, qui est simplement une liste de paires Id, preded_value.

Lorsque vous soumettez votre résultat, vous devriez obtenir un score de l'ordre de 0,17, qui se situerait quelque part dans le dernier quart du tableau du classement public. Mais bon, si c'était aussi simple qu'un réseau à 3 couches avec 27 neurones, ces scientifiques de données embêtants n'obtiendraient pas 300k $ + / an de compensations totales des grandes entreprises américaines

Envelopper


Le code source complet de cette entrée (avec tous les assistants et certaines des parties commentées de mes explorations et expériences précédentes) est d'environ 200 lignes sur le PasteBin .

Dans le prochain article, vous verrez mes manigances essayer d'atteindre le top 50% de ce classement public. Ce sera une aventure de compagnon amateur, un combat avec The Windmill of Overfitting avec le seul outil dont dispose le vagabond - un modèle plus grand (par exemple, NN profond, rappelez-vous, pas d'ingénierie manuelle des fonctionnalités!). Ce sera moins un tutoriel de codage, et plus une quête de pensée avec des mathématiques vraiment croches et une conclusion étrange. Restez à l'écoute!

Les liens


Kaggle
Concours des prix des logements sur Kaggle
Tutoriel de régression TensorFlow
Page d'accueil de TensorFlow
Référence de l'API TensorFlow
Dégradé (liaison TensorFlow)

Source: https://habr.com/ru/post/fr437174/


All Articles