diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/ImageClassification/ResnetV2101TransferLearningEarlyStopping.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/ImageClassification/ResnetV2101TransferLearningEarlyStopping.cs index a7192a3157..43638a91e2 100644 --- a/docs/samples/Microsoft.ML.Samples/Dynamic/ImageClassification/ResnetV2101TransferLearningEarlyStopping.cs +++ b/docs/samples/Microsoft.ML.Samples/Dynamic/ImageClassification/ResnetV2101TransferLearningEarlyStopping.cs @@ -65,7 +65,7 @@ public static void Example() var pipeline = mlContext.Transforms.LoadImages("Image", fullImagesetFolderPath, false, "ImagePath") // false indicates we want the image as a VBuffer .Append(mlContext.Model.ImageClassification( "Image", "Label", - // Just by changing/selecting InceptionV3 here instead of + // Just by changing/selecting InceptionV3/MobilenetV2 here instead of // ResnetV2101 you can try a different architecture/pre-trained // model. arch: ImageClassificationEstimator.Architecture.ResnetV2101, diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/ImageClassification/ResnetV2101TransferLearningTrainTestSplit.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/ImageClassification/ResnetV2101TransferLearningTrainTestSplit.cs index 2d15316c0a..2b20ec0695 100644 --- a/docs/samples/Microsoft.ML.Samples/Dynamic/ImageClassification/ResnetV2101TransferLearningTrainTestSplit.cs +++ b/docs/samples/Microsoft.ML.Samples/Dynamic/ImageClassification/ResnetV2101TransferLearningTrainTestSplit.cs @@ -62,7 +62,7 @@ public static void Example() var pipeline = mlContext.Model.ImageClassification( "Image", "Label", - // Just by changing/selecting InceptionV3 here instead of + // Just by changing/selecting InceptionV3/MobilenetV2 here instead of // ResnetV2101 you can try a different architecture/ // pre-trained model. arch: ImageClassificationEstimator.Architecture.ResnetV2101, diff --git a/src/Microsoft.ML.Dnn/DnnCatalog.cs b/src/Microsoft.ML.Dnn/DnnCatalog.cs index b7c5fa5886..55b66c79dd 100644 --- a/src/Microsoft.ML.Dnn/DnnCatalog.cs +++ b/src/Microsoft.ML.Dnn/DnnCatalog.cs @@ -144,7 +144,7 @@ public static ImageClassificationEstimator ImageClassification( { var options = new ImageClassificationEstimator.Options() { - ModelLocation = arch == Architecture.ResnetV2101 ? @"resnet_v2_101_299.meta" : @"InceptionV3.meta", + ModelLocation = ModelLocation[arch], InputColumns = new[] { featuresColumnName }, OutputColumns = new[] { scoreColumnName, predictedLabelColumnName }, LabelColumn = labelColumnName, @@ -194,6 +194,14 @@ public static ImageClassificationEstimator ImageClassification( client.DownloadFile(new Uri($"{baseGitPath}"), @"resnet_v2_101_299.meta"); } } + else if(options.Arch == Architecture.MobilenetV2) + { + var baseGitPath = @"https://tlcresources.blob.core.windows.net/image/MobileNetV2TensorFlow/mobilenet_v2.meta"; + using (WebClient client = new WebClient()) + { + client.DownloadFile(new Uri($"{baseGitPath}"), @"mobilenet_v2.meta"); + } + } } var env = CatalogUtils.GetEnvironment(catalog); diff --git a/src/Microsoft.ML.Dnn/ImageClassificationTransform.cs b/src/Microsoft.ML.Dnn/ImageClassificationTransform.cs index 9332195b72..5688e99f60 100644 --- a/src/Microsoft.ML.Dnn/ImageClassificationTransform.cs +++ b/src/Microsoft.ML.Dnn/ImageClassificationTransform.cs @@ -588,7 +588,8 @@ private void TrainAndEvaluateClassificationLayer(string trainBottleneckFilePath, tf.train.Saver().restore(evalSess, _checkpointPath); (evaluationStep, prediction) = AddEvaluationStep(finalTensor, groundTruthInput); - (_jpegData, _resizedImage) = AddJpegDecoding(299, 299, 3); + var imageSize = ImageClassificationEstimator.ImagePreprocessingSize[options.Arch]; + (_jpegData, _resizedImage) = AddJpegDecoding(imageSize.Item1, imageSize.Item2, 3); return (evalSess, _labelTensor, evaluationStep, prediction); } @@ -827,12 +828,18 @@ internal ImageClassificationTransformer(IHostEnvironment env, Session session, s _bottleneckOperationName = "module_apply_default/hub_output/feature_vector/SpatialSqueeze"; _inputTensorName = "Placeholder"; } + else if(arch == ImageClassificationEstimator.Architecture.MobilenetV2) + { + _bottleneckOperationName = "import/MobilenetV2/Logits/Squeeze"; + _inputTensorName = "import/input"; + } _outputs = new[] { scoreColumnName, predictedLabelColumnName }; if (loadModel == false) { - (_jpegData, _resizedImage) = AddJpegDecoding(299, 299, 3); + var imageSize = ImageClassificationEstimator.ImagePreprocessingSize[arch]; + (_jpegData, _resizedImage) = AddJpegDecoding(imageSize.Item1, imageSize.Item2, 3); _jpegDataTensorName = _jpegData.name; _resizedImageTensorName = _resizedImage.name; @@ -1080,7 +1087,28 @@ public sealed class ImageClassificationEstimator : IEstimator + /// Dictionary mapping model architecture to model location. + /// + internal static IReadOnlyDictionary ModelLocation = new Dictionary + { + { Architecture.ResnetV2101, @"resnet_v2_101_299.meta" }, + { Architecture.InceptionV3, @"InceptionV3.meta" }, + { Architecture.MobilenetV2, @"mobilenet_v2.meta" } + }; + + /// + /// Dictionary mapping model architecture to image input size supported. + /// + internal static IReadOnlyDictionary> ImagePreprocessingSize = new Dictionary> + { + { Architecture.ResnetV2101, new Tuple(299,299) }, + { Architecture.InceptionV3, new Tuple(299,299) }, + { Architecture.MobilenetV2, new Tuple(224,224) } }; /// diff --git a/test/Microsoft.ML.TestFramework/Attributes/TensorflowTheoryAttribute.cs b/test/Microsoft.ML.TestFramework/Attributes/TensorflowTheoryAttribute.cs new file mode 100644 index 0000000000..24b73a56f5 --- /dev/null +++ b/test/Microsoft.ML.TestFramework/Attributes/TensorflowTheoryAttribute.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +namespace Microsoft.ML.TestFramework.Attributes +{ + /// + /// A theory for tests requiring TensorFlow. + /// + public sealed class TensorFlowTheoryAttribute : EnvironmentSpecificTheoryAttribute + { + public TensorFlowTheoryAttribute() : base("TensorFlow is 64-bit only") + { + } + + /// + protected override bool IsEnvironmentSupported() + { + return Environment.Is64BitProcess; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.ML.Tests/ScenariosWithDirectInstantiation/TensorflowTests.cs b/test/Microsoft.ML.Tests/ScenariosWithDirectInstantiation/TensorflowTests.cs index bb70334956..3a0efe6eaa 100644 --- a/test/Microsoft.ML.Tests/ScenariosWithDirectInstantiation/TensorflowTests.cs +++ b/test/Microsoft.ML.Tests/ScenariosWithDirectInstantiation/TensorflowTests.cs @@ -1207,8 +1207,10 @@ public void TensorFlowStringTest() Assert.Equal(string.Join(" ", input.B).Replace("/", " "), textOutput.BOut[0]); } - [TensorFlowFact] - public void TensorFlowImageClassification() + [TensorFlowTheory] + [InlineData(ImageClassificationEstimator.Architecture.ResnetV2101)] + [InlineData(ImageClassificationEstimator.Architecture.MobilenetV2)] + public void TensorFlowImageClassification(ImageClassificationEstimator.Architecture arch) { string assetsRelativePath = @"assets"; string assetsPath = GetAbsolutePath(assetsRelativePath); @@ -1249,10 +1251,10 @@ public void TensorFlowImageClassification() var pipeline = mlContext.Transforms.LoadImages("Image", fullImagesetFolderPath, false, "ImagePath") // false indicates we want the image as a VBuffer .Append(mlContext.Model.ImageClassification( "Image", "Label", - // Just by changing/selecting InceptionV3 here instead of + // Just by changing/selecting InceptionV3/MobilenetV2 here instead of // ResnetV2101 you can try a different architecture/pre-trained // model. - arch: ImageClassificationEstimator.Architecture.ResnetV2101, + arch: arch, epoch: 50, batchSize: 10, learningRate: 0.01f, @@ -1384,7 +1386,7 @@ public void TensorFlowImageClassificationEarlyStoppingIncreasing() var pipeline = mlContext.Transforms.LoadImages("Image", fullImagesetFolderPath, false, "ImagePath") // false indicates we want the image as a VBuffer .Append(mlContext.Model.ImageClassification( "Image", "Label", - // Just by changing/selecting InceptionV3 here instead of + // Just by changing/selecting InceptionV3/MobilenetV2 here instead of // ResnetV2101 you can try a different architecture/pre-trained // model. arch: ImageClassificationEstimator.Architecture.ResnetV2101, @@ -1473,7 +1475,7 @@ public void TensorFlowImageClassificationEarlyStoppingDecreasing() var pipeline = mlContext.Transforms.LoadImages("Image", fullImagesetFolderPath, false, "ImagePath") // false indicates we want the image as a VBuffer .Append(mlContext.Model.ImageClassification( "Image", "Label", - // Just by changing/selecting InceptionV3 here instead of + // Just by changing/selecting InceptionV3/MobilenetV2 here instead of // ResnetV2101 you can try a different architecture/pre-trained // model. arch: ImageClassificationEstimator.Architecture.ResnetV2101,