DateTimeTransformer featurizer - #4521

Merged
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer
Dec 17, 2019
Merged

DateTimeTransformer featurizer#4521
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer

Conversation

@michaelgsharp

Copy link
Copy Markdown
Contributor

This change adds in the DateTimeTransformer into the new Featurizers project. It is the first of a series of PR's that will go in. The DateTimeTransformer is implemented in native code, so this is mostly just a wrapper around that with the appropriate entrypoints for NimbusML as well. Since all the new estimator/transformers follow the same patterns, once this one is reviewed and checked in I will create the other PR's.

@michaelgsharp
michaelgsharp requested a review from a teamDecember 4, 2019 18:11
@michaelgsharpmichaelgsharp self-assigned this Dec 4, 2019
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Year = 1, Month, Day, Hour, Minute, Second, AmPm, Hour12, DayOfWeek, DayOfQuarter, DayOfYear,
WeekOfMonth, QuarterOfYear, HalfOfYear, WeekIso, YearIso, MonthLabel, AmPmLabel, DayOfWeekLabel,
HolidayName, IsPaidTimeOff
};

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend explicitly numbering each enum name. This lets us insert new values later without affecting the enum numeric value.

This is more important if the enum is serialized within the model, which I expect you're doing since the model needs to know which features to produce. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. They are now explicitly numbered. The Countries enum (now HolidayList) is also numbered now.


In reply to: 353910235 [](ancestors = 353910235)

/// <param name="catalog">Transform catalog</param>
/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <param name="columnsToDrop">List of columns to drop, if any</param>

@eerhardteerhardtDec 4, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One drawback to using columnsToDrop is that if a user is only interested in the DayOfYear and DayOfWeekLabel, they need to explicitly exclude all the rest. And then if we ever add a new output column in a future release, they would start getting it, and would need to add the new column to the "to drop" list. #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might recommend a bitwise use of an enum (flags) to say which columns to include.

Default would be DateTimeTransformerEstimator.ColumnsProduced.All = 0xFFFFFFFFFFFFFFFF (uint64).

Then users can bitwise or/and-not to select the columns they want to keep/drop.

Example usage

All output except DayOfYear: (subtractive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.All&~DateTimeTransformerEstimator.ColumnsProduced.DayOfYear)

Only DayOfYear and DayOfWeekLabel: (additive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.DayOfYear|DateTimeTransformerEstimator.ColumnsProduced.DayOfWeekLabel)

#Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, there's a [Flags] attribute in C# -- https://docs.microsoft.com/en-us/dotnet/api/system.flagsattribute?view=netcore-3.0

[Flags]publicenumColumnsProduced:uint64{Year=1<<0,Month=1<<1,Day=1<<2,Hour=1<<3,WeekOfMonth=1<<4,
...}

#Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a way you think would be better? We had also thought about not even including that ability here and just letting the user use the DropColumns transformer to remove any columns they didn't want. That would keep this code cleaner and not duplicate that type of functionality. Thoughts on that?


In reply to: 353911746 [](ancestors = 353911746)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think that doing flags is a great idea personally. I think enums are more clear (if more verbose) than flags usually. As I mentioned to Eric in this same thread though, we can remove this capability from here and just let the user use the DropColumn transformer to remove anything they dont want. What is your opinion on that?


In reply to: 353968394 [](ancestors = 353968394)

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect this featurizer isn't aware of which columns are being requested of it

Maybe it should be made aware? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the usability of the flag method.

There is a user need of not including all outputs as overfitting occurs, mainly on the larger time scales, like year number. Including the year in a classification or regression model often leads to the model memorizing the label value specifically for a date range.

For instance the model will memorize that 2017-03-01 to 2018-01-05, the label value was high. This information is not valuable to memorize when predicting future data. Instead we want to ensure it memorizes that traffic volumes (the label) are higher on weekends (extracting useful patterns).

Hence it is nice to encourage users (as part of the timedate featurizer API itself) to think about which columns to include/exclude. This is as opposed to a user needing a separate DropColumns step in their pipeline. #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So so far we have had 3 suggestions.
1 - Dont support dropping columns as a native part of this transformer.
2 - Use Enums.
3 - Use Flags.

Can we finalize on the decision? I personally agree with Harish that since we already have a drop columns transformer that we can just use that and not have that functionality be present here. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend a way to proactively select the output columns to create during the setup of the transform. Instead of dropping later, just don't create them.

Generally, we should assume users won't think (or know) to drop later. For usability, placing it in the constructor exposes users to the idea, and forces them to consider which columns will be beneficial. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, since this functionality already exists inside of ML.NET as a separate transformer we will be leaving this as is. We can revisit this later as the need arises. Even if this transformer produces all the columns, if no downstream transformers request them then they will never be realized. It shouldn't cause any performance impact this way. #Resolved

HolidayName, IsPaidTimeOff
};

public enum Countries : byte

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want a UINT given that there are ~200 current countries (and they keep changing), and in the future we may a more specific break down, like UnitedStatesBankingHolidays vs. UnitedStatesFederalHolidays

Suggested change
publicenumCountries:byte
publicenumCountries:uint
``` #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point. I have changed the enum to be uint as suggested.


In reply to: 353916304 [](ancestors = 353916304)

public ColumnsProduced[] ColumnsToDrop;

[Argument(ArgumentType.AtMostOnce, HelpText = "Country to get holidays for. Defaults to none if not passed", Name = "Country", ShortName = "ctry", SortOrder = 4)]
public Countries Country = Countries.None;

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Countries may be a bit restrictive of a term for the future. Perhaps name as HolidayList.

Perhaps in the future we'll want UnitedStatesCaliforniaEducationHolidays (see: list). We may also want more specific lists, like UnitedStatesMajorHolidays. Or localized lists like GermanyBavaria (see: list).

Or perhaps in the future we'll want to version the holiday list Germany2019 (as holidays are declared and dropped). Versioning helps old models run in current versions of ML.NET. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I have renamed it to HolidayList, but we can revisit it if needed in the future as well.


In reply to: 353920532 [](ancestors = 353920532)

public int DTCYearIso { get; set; }
public string DTCMonthLabel { get; set; }
public string DTCAmPmLabel { get; set; }
public string DTCDayOfWeekLabel { get; set; }

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be needed to be added to the code being wrapped here, but when featurizing DateTime fields, I prefer polar (Sin/Cos) transforms (along with the others listed).

Clocks and times/dates are cyclic. The current feature set doesn't include this fact.

The main gain of polar date transforms is that it places times like 11:50PM and 12:10AM right next to each other (both mapping to ~0.999 in the cosine transform of percent of day). This lets a model make a decision boundary at CosTimeOfDay > 0.9 to treat these two times as very similar.

Contrast this to the DTCHour feature which will map these to 23 and 0 (issue: non-continuous range, and not possible to group both the 0 and 23 in a single split point, which to a human is similar).

Info:

Code example I use for DateTime feature engineering:

DateTimedt;varinvalidDate=!DateTime.TryParse(input.Date,outdt);varpercentOfDay=(invalidDate?Single.NaN:(float)(newTimeSpan(dt.Hour,dt.Minute,0).TotalMinutes)/1440 f);varpercentOfWeek=(invalidDate?Single.NaN:((float)dt.DayOfWeek+percentOfDay)/7 f);varpercentOfMonth=(invalidDate?Single.NaN:(float)(dt.Day+percentOfDay)/(float)DateTime.DaysInMonth(dt.Year,dt.Month));varpercentOfYear=(invalidDate?Single.NaN:(float)((dt.DayOfYear+percentOfDay)/(newDateTime(dt.Year+1,1,1)-newDateTime(dt.Year,1,1)).TotalDays));// Time features like the current onesoutput.DateWeekday=(invalidDate?Single.NaN:(float)dt.DayOfWeek);output.DateMonth=(invalidDate?Single.NaN:(float)dt.Month);output.DateHour=(invalidDate?Single.NaN:dt.Hour);output.DatePartOfDay=(invalidDate?Single.NaN:(float)Math.Round(dt.AddHours(-3).Hour/6 f));output.InvalidDate=(invalidDate?1.0f:0.0f);// Polar date/time transforms for percent of day & week & year output.CosTimeOfDay=(float)Math.Cos(2*Math.PI*percentOfDay);output.SinTimeOfDay=(float)Math.Sin(2*Math.PI*percentOfDay);output.CosTimeOfWeek=(float)Math.Cos(2*Math.PI*percentOfWeek);output.SinTimeOfWeek=(float)Math.Sin(2*Math.PI*percentOfWeek);output.CosTimeOfMonth=(float)Math.Cos(2*Math.PI*percentOfMonth);output.SinTimeOfMonth=(float)Math.Sin(2*Math.PI*percentOfMonth);output.CosTimeOfYear=(float)Math.Cos(2*Math.PI*percentOfYear);output.SinTimeOfYear=(float)Math.Sin(2*Math.PI*percentOfYear);
``` #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @davidbrownellWork #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add this as a potential feature for Mn. #Resolved

@gvashishthagvashishthaDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean add to the ML.NET plan? Any estimate on how much additional dev time this would take? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the new features is pretty simple. I expect more of the work is in testing. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the input dateTime column a long and not a DateTime? #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long to match a unix timestamp. We will be adding in the ability to use C# DateTime and string in an ISO format in the future, but the initial implementation is just the unix timestamp. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern here is that .NET developers expect to have their date time data using System.DateTime. That is the natural type in .NET. Forcing the user to convert from System.DateTime to a long unix timestamp doesn't seem like a good experience. It would make more sense if the input column was a System.DateTime. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do the conversion yourself here? The user's input column can be DateTime, and then you convert it to a long unix timestamp before passing it to the C++ code? #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to ensure that the process of converting from a string to "Date time" is consistent across frameworks - especially considering that a DateTime in and of itself isn't sufficient in some scenarios, as we lose (potentially important) time zone information in the process. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not asking for string. I'm asking for the ML.NET data type that a user would feed into this transform to be the canonical System.DateTime type and not a long type.

Specifically, this transform should require the input column to be of this type:

/// <summary>
/// The standard date time type. This has representation type of <see cref="DateTime"/>.
/// Note this can have only one possible value, accessible by the singleton static property <see cref="Instance"/>.
/// </summary>
publicsealedclassDateTimeDataViewType:PrimitiveDataViewType

It doesn't make sense for a user to have to convert a DateTime to a long before calling this transform. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically, look at the sample code being checked in under samples:

// Create a small dataset as an IEnumerable.// Future Date - 2025 June 30varsamples=new[]{newDateTimeInput(){Date=1751241600}};

Instead, that sample code should be:

// Create a small dataset as an IEnumerable.varsamples=new[]{newDateTimeInput(){Date=newDateTime(2025,6,30)}};
``` #Resolved

@davidbrownellWorkdavidbrownellWorkDec 6, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - eventually a C# developer will be able to pass System:DateTime, string, or long. #Resolved

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is on our todo list and will be added in in the future. We will eventually be supporting DateTime and string as well, but initial support will just be for long. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
ctx.Writer.Write(data);
}

public void Dispose()

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who calls this Dispose method? We have a problem with tranformers that need to be disposed. See #906 and my comment on #4223

cc @codemzs #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain. I am not manually calling it anywhere. Is it happening during pipeline teardown ok? Or is there more desirable behavior? #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain.

There is no call of it when the pipeline teardown happens. That is why that bug was opened. I'm still not certain on why it was closed. Maybe @codemzs can comment. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

/azp run #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 9, 2019

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

#Resolved

@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

Going to close and reopen the PR to reset the stuck builds. #Resolved

@codecov

codecovBot commented Dec 10, 2019

Copy link
Copy Markdown

Codecov Report

Merging #4521 into master will increase coverage by 0.03%.
The diff coverage is 88.11%.

@@ Coverage Diff @@## master #4521 +/- ##
==========================================
+ Coverage 75.11% 75.15% +0.03% 
==========================================
Files 908 913 +5 Lines 160204 160891 +687 Branches 17254 17292 +38 ==========================================
+ Hits 120340 120911 +571 - Misses 35050 35153 +103 - Partials 4814 4827 +13
FlagCoverage Δ
#Debug75.15% <88.11%> (+0.03%)⬆️
#production70.54% <86.24%> (+0.02%)⬆️
#test90.29% <91.41%> (+0.03%)⬆️
Impacted FilesCoverage Δ
....ML.Tests/Transformers/DateTimeTransformerTests.cs100% <100%> (ø)
...crosoft.ML.Core.Tests/UnitTests/TestEntryPoints.cs98.36% <100%> (ø)⬆️
src/Microsoft.ML.Featurizers/Common.cs19.58% <15%> (ø)
...estFramework/Attributes/NotCentOS7FactAttribute.cs26.08% <26.08%> (ø)
...rc/Microsoft.ML.Featurizers/DateTimeTransformer.cs90.57% <90.57%> (ø)
....ML.AutoML/PipelineSuggesters/PipelineSuggester.cs83.19% <0%> (-3.37%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmDataset.cs74.21% <0%> (-2.12%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmBooster.cs83.8% <0%> (-0.63%)⬇️
...soft.ML.TestFramework/DataPipe/TestDataPipeBase.cs76.08% <0%> (-0.4%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.1% <0%> (-0.16%)⬇️
... and 21 more
#Resolved

@codemzs
codemzs requested a review from natkeDecember 12, 2019 18:27
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
WeekIso = 15,
YearIso = 16,
MonthLabel = 17,
AmPmLabel = 18,

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do AmPm label, would it make sense to also do weekend/weekday? (Often used in feature engineering) #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think thats not a bad idea, but the initial ask we got was just for these columns. We will start with just these and then can add in more if the requirement arises. #Resolved

{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <returns><see cref="DateTimeEstimator"/></returns>
public static DateTimeEstimator DateTimeTransformer(this TransformsCatalog catalog, string inputColumnName, string columnPrefix)

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming of the extension methods are not consistent with the other transformers (I'll leave it to others to judge whether the original scheme makes sense :-), but consistency is probably a more important consideration now)

If you look at the other transforms they mostly have the form Verb-Object e.g. CopyColumns, MapValueToKey etc. This makes sense when you are chaining them together in a pipeline.

Suggestions for this one: FeaturizeDateTime, SplitDateTime, TokenizeDateTime, ExtractDateTime ...

#Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of the ones you listed I would vote FeaturizeDateTime. Anyone have any objections if I rename the extension method to that? #Resolved

case DateTimeEstimator.ColumnsProduced.MonthLabel:
case DateTimeEstimator.ColumnsProduced.AmPmLabel:
case DateTimeEstimator.ColumnsProduced.DayOfWeekLabel:
case DateTimeEstimator.ColumnsProduced.HolidayName:

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usability question, why are we producing multiple output columns of instead of a single vector type column?

The next step after this DateTime transform is:

  • Convert all the INTs/USHORTs/BYTEs columns to Float32
  • One-hot encode the STRING columns ("Friday" to [ 0, 0, 0, 0, 0, 1, 0 ])
  • Concatenate all of these feature to single vector of Float32

Why aren't we just outputting the final vector type column? Then the user can directly use its output, like our other transforms. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a usecase for the individual columns, perhaps just a boolean parameter produceRawColumns, which then produces the individual outputs instead of a singular vector column? #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did it this way just because thats how the original python code works. It splits everything up into different columns and doesn't combine them into a vector. #Resolved

@natkenatkeDec 13, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a single vector is consistent with FeaturizeText. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original code we were bringing to ML.NET returned all individual columns. Because of that, for now, we will be leaving them as individual columns. If we learn that we never need individual columns and only use this as a vector then we can revisit this then. #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 17, 2019

Copy link
Copy Markdown
No pipelines are associated with this pull request.

#Resolved

@michaelgsharp
michaelgsharp merged commit 290e069 into dotnet:masterDec 17, 2019
@michaelgsharp
michaelgsharp deleted the datetime-transformer branch December 17, 2019 21:35
throw new InvalidOperationException($"Unsupported type {type}");
}

// The Native Featurizers do not currently support CentOS7, this method checks the OS and returns true if it is CentOS7.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about RedHat 7?

{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
using (Process process = new Process())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more performant way of doing this check would be to read from the file directly. Starting a new process just to get this info is too heavy-weight IMO.

See https://github.com/dotnet/runtime/blob/6f445d6dc237f59d730e0df47f8630b18887d776/src/installer/managed/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs#L140-L142

@ghostghost locked as resolved and limited conversation to collaborators Mar 19, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@michaelgsharp@natke@justinormont@harishsk@eerhardt@gvashishtha@davidbrownellWork
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

DateTimeTransformer featurizer - #4521

Merged
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer
Dec 17, 2019
Merged

DateTimeTransformer featurizer#4521
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer

Conversation

@michaelgsharp

Copy link
Copy Markdown
Contributor

This change adds in the DateTimeTransformer into the new Featurizers project. It is the first of a series of PR's that will go in. The DateTimeTransformer is implemented in native code, so this is mostly just a wrapper around that with the appropriate entrypoints for NimbusML as well. Since all the new estimator/transformers follow the same patterns, once this one is reviewed and checked in I will create the other PR's.

@michaelgsharp
michaelgsharp requested a review from a teamDecember 4, 2019 18:11
@michaelgsharpmichaelgsharp self-assigned this Dec 4, 2019
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Year = 1, Month, Day, Hour, Minute, Second, AmPm, Hour12, DayOfWeek, DayOfQuarter, DayOfYear,
WeekOfMonth, QuarterOfYear, HalfOfYear, WeekIso, YearIso, MonthLabel, AmPmLabel, DayOfWeekLabel,
HolidayName, IsPaidTimeOff
};

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend explicitly numbering each enum name. This lets us insert new values later without affecting the enum numeric value.

This is more important if the enum is serialized within the model, which I expect you're doing since the model needs to know which features to produce. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. They are now explicitly numbered. The Countries enum (now HolidayList) is also numbered now.


In reply to: 353910235 [](ancestors = 353910235)

/// <param name="catalog">Transform catalog</param>
/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <param name="columnsToDrop">List of columns to drop, if any</param>

@eerhardteerhardtDec 4, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One drawback to using columnsToDrop is that if a user is only interested in the DayOfYear and DayOfWeekLabel, they need to explicitly exclude all the rest. And then if we ever add a new output column in a future release, they would start getting it, and would need to add the new column to the "to drop" list. #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might recommend a bitwise use of an enum (flags) to say which columns to include.

Default would be DateTimeTransformerEstimator.ColumnsProduced.All = 0xFFFFFFFFFFFFFFFF (uint64).

Then users can bitwise or/and-not to select the columns they want to keep/drop.

Example usage

All output except DayOfYear: (subtractive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.All&~DateTimeTransformerEstimator.ColumnsProduced.DayOfYear)

Only DayOfYear and DayOfWeekLabel: (additive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.DayOfYear|DateTimeTransformerEstimator.ColumnsProduced.DayOfWeekLabel)

#Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, there's a [Flags] attribute in C# -- https://docs.microsoft.com/en-us/dotnet/api/system.flagsattribute?view=netcore-3.0

[Flags]publicenumColumnsProduced:uint64{Year=1<<0,Month=1<<1,Day=1<<2,Hour=1<<3,WeekOfMonth=1<<4,
...}

#Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a way you think would be better? We had also thought about not even including that ability here and just letting the user use the DropColumns transformer to remove any columns they didn't want. That would keep this code cleaner and not duplicate that type of functionality. Thoughts on that?


In reply to: 353911746 [](ancestors = 353911746)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think that doing flags is a great idea personally. I think enums are more clear (if more verbose) than flags usually. As I mentioned to Eric in this same thread though, we can remove this capability from here and just let the user use the DropColumn transformer to remove anything they dont want. What is your opinion on that?


In reply to: 353968394 [](ancestors = 353968394)

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect this featurizer isn't aware of which columns are being requested of it

Maybe it should be made aware? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the usability of the flag method.

There is a user need of not including all outputs as overfitting occurs, mainly on the larger time scales, like year number. Including the year in a classification or regression model often leads to the model memorizing the label value specifically for a date range.

For instance the model will memorize that 2017-03-01 to 2018-01-05, the label value was high. This information is not valuable to memorize when predicting future data. Instead we want to ensure it memorizes that traffic volumes (the label) are higher on weekends (extracting useful patterns).

Hence it is nice to encourage users (as part of the timedate featurizer API itself) to think about which columns to include/exclude. This is as opposed to a user needing a separate DropColumns step in their pipeline. #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So so far we have had 3 suggestions.
1 - Dont support dropping columns as a native part of this transformer.
2 - Use Enums.
3 - Use Flags.

Can we finalize on the decision? I personally agree with Harish that since we already have a drop columns transformer that we can just use that and not have that functionality be present here. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend a way to proactively select the output columns to create during the setup of the transform. Instead of dropping later, just don't create them.

Generally, we should assume users won't think (or know) to drop later. For usability, placing it in the constructor exposes users to the idea, and forces them to consider which columns will be beneficial. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, since this functionality already exists inside of ML.NET as a separate transformer we will be leaving this as is. We can revisit this later as the need arises. Even if this transformer produces all the columns, if no downstream transformers request them then they will never be realized. It shouldn't cause any performance impact this way. #Resolved

HolidayName, IsPaidTimeOff
};

public enum Countries : byte

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want a UINT given that there are ~200 current countries (and they keep changing), and in the future we may a more specific break down, like UnitedStatesBankingHolidays vs. UnitedStatesFederalHolidays

Suggested change
publicenumCountries:byte
publicenumCountries:uint
``` #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point. I have changed the enum to be uint as suggested.


In reply to: 353916304 [](ancestors = 353916304)

public ColumnsProduced[] ColumnsToDrop;

[Argument(ArgumentType.AtMostOnce, HelpText = "Country to get holidays for. Defaults to none if not passed", Name = "Country", ShortName = "ctry", SortOrder = 4)]
public Countries Country = Countries.None;

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Countries may be a bit restrictive of a term for the future. Perhaps name as HolidayList.

Perhaps in the future we'll want UnitedStatesCaliforniaEducationHolidays (see: list). We may also want more specific lists, like UnitedStatesMajorHolidays. Or localized lists like GermanyBavaria (see: list).

Or perhaps in the future we'll want to version the holiday list Germany2019 (as holidays are declared and dropped). Versioning helps old models run in current versions of ML.NET. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I have renamed it to HolidayList, but we can revisit it if needed in the future as well.


In reply to: 353920532 [](ancestors = 353920532)

public int DTCYearIso { get; set; }
public string DTCMonthLabel { get; set; }
public string DTCAmPmLabel { get; set; }
public string DTCDayOfWeekLabel { get; set; }

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be needed to be added to the code being wrapped here, but when featurizing DateTime fields, I prefer polar (Sin/Cos) transforms (along with the others listed).

Clocks and times/dates are cyclic. The current feature set doesn't include this fact.

The main gain of polar date transforms is that it places times like 11:50PM and 12:10AM right next to each other (both mapping to ~0.999 in the cosine transform of percent of day). This lets a model make a decision boundary at CosTimeOfDay > 0.9 to treat these two times as very similar.

Contrast this to the DTCHour feature which will map these to 23 and 0 (issue: non-continuous range, and not possible to group both the 0 and 23 in a single split point, which to a human is similar).

Info:

Code example I use for DateTime feature engineering:

DateTimedt;varinvalidDate=!DateTime.TryParse(input.Date,outdt);varpercentOfDay=(invalidDate?Single.NaN:(float)(newTimeSpan(dt.Hour,dt.Minute,0).TotalMinutes)/1440 f);varpercentOfWeek=(invalidDate?Single.NaN:((float)dt.DayOfWeek+percentOfDay)/7 f);varpercentOfMonth=(invalidDate?Single.NaN:(float)(dt.Day+percentOfDay)/(float)DateTime.DaysInMonth(dt.Year,dt.Month));varpercentOfYear=(invalidDate?Single.NaN:(float)((dt.DayOfYear+percentOfDay)/(newDateTime(dt.Year+1,1,1)-newDateTime(dt.Year,1,1)).TotalDays));// Time features like the current onesoutput.DateWeekday=(invalidDate?Single.NaN:(float)dt.DayOfWeek);output.DateMonth=(invalidDate?Single.NaN:(float)dt.Month);output.DateHour=(invalidDate?Single.NaN:dt.Hour);output.DatePartOfDay=(invalidDate?Single.NaN:(float)Math.Round(dt.AddHours(-3).Hour/6 f));output.InvalidDate=(invalidDate?1.0f:0.0f);// Polar date/time transforms for percent of day & week & year output.CosTimeOfDay=(float)Math.Cos(2*Math.PI*percentOfDay);output.SinTimeOfDay=(float)Math.Sin(2*Math.PI*percentOfDay);output.CosTimeOfWeek=(float)Math.Cos(2*Math.PI*percentOfWeek);output.SinTimeOfWeek=(float)Math.Sin(2*Math.PI*percentOfWeek);output.CosTimeOfMonth=(float)Math.Cos(2*Math.PI*percentOfMonth);output.SinTimeOfMonth=(float)Math.Sin(2*Math.PI*percentOfMonth);output.CosTimeOfYear=(float)Math.Cos(2*Math.PI*percentOfYear);output.SinTimeOfYear=(float)Math.Sin(2*Math.PI*percentOfYear);
``` #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @davidbrownellWork #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add this as a potential feature for Mn. #Resolved

@gvashishthagvashishthaDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean add to the ML.NET plan? Any estimate on how much additional dev time this would take? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the new features is pretty simple. I expect more of the work is in testing. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the input dateTime column a long and not a DateTime? #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long to match a unix timestamp. We will be adding in the ability to use C# DateTime and string in an ISO format in the future, but the initial implementation is just the unix timestamp. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern here is that .NET developers expect to have their date time data using System.DateTime. That is the natural type in .NET. Forcing the user to convert from System.DateTime to a long unix timestamp doesn't seem like a good experience. It would make more sense if the input column was a System.DateTime. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do the conversion yourself here? The user's input column can be DateTime, and then you convert it to a long unix timestamp before passing it to the C++ code? #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to ensure that the process of converting from a string to "Date time" is consistent across frameworks - especially considering that a DateTime in and of itself isn't sufficient in some scenarios, as we lose (potentially important) time zone information in the process. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not asking for string. I'm asking for the ML.NET data type that a user would feed into this transform to be the canonical System.DateTime type and not a long type.

Specifically, this transform should require the input column to be of this type:

/// <summary>
/// The standard date time type. This has representation type of <see cref="DateTime"/>.
/// Note this can have only one possible value, accessible by the singleton static property <see cref="Instance"/>.
/// </summary>
publicsealedclassDateTimeDataViewType:PrimitiveDataViewType

It doesn't make sense for a user to have to convert a DateTime to a long before calling this transform. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically, look at the sample code being checked in under samples:

// Create a small dataset as an IEnumerable.// Future Date - 2025 June 30varsamples=new[]{newDateTimeInput(){Date=1751241600}};

Instead, that sample code should be:

// Create a small dataset as an IEnumerable.varsamples=new[]{newDateTimeInput(){Date=newDateTime(2025,6,30)}};
``` #Resolved

@davidbrownellWorkdavidbrownellWorkDec 6, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - eventually a C# developer will be able to pass System:DateTime, string, or long. #Resolved

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is on our todo list and will be added in in the future. We will eventually be supporting DateTime and string as well, but initial support will just be for long. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
ctx.Writer.Write(data);
}

public void Dispose()

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who calls this Dispose method? We have a problem with tranformers that need to be disposed. See #906 and my comment on #4223

cc @codemzs #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain. I am not manually calling it anywhere. Is it happening during pipeline teardown ok? Or is there more desirable behavior? #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain.

There is no call of it when the pipeline teardown happens. That is why that bug was opened. I'm still not certain on why it was closed. Maybe @codemzs can comment. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

/azp run #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 9, 2019

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

#Resolved

@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

Going to close and reopen the PR to reset the stuck builds. #Resolved

@codecov

codecovBot commented Dec 10, 2019

Copy link
Copy Markdown

Codecov Report

Merging #4521 into master will increase coverage by 0.03%.
The diff coverage is 88.11%.

@@ Coverage Diff @@## master #4521 +/- ##
==========================================
+ Coverage 75.11% 75.15% +0.03% 
==========================================
Files 908 913 +5 Lines 160204 160891 +687 Branches 17254 17292 +38 ==========================================
+ Hits 120340 120911 +571 - Misses 35050 35153 +103 - Partials 4814 4827 +13
FlagCoverage Δ
#Debug75.15% <88.11%> (+0.03%)⬆️
#production70.54% <86.24%> (+0.02%)⬆️
#test90.29% <91.41%> (+0.03%)⬆️
Impacted FilesCoverage Δ
....ML.Tests/Transformers/DateTimeTransformerTests.cs100% <100%> (ø)
...crosoft.ML.Core.Tests/UnitTests/TestEntryPoints.cs98.36% <100%> (ø)⬆️
src/Microsoft.ML.Featurizers/Common.cs19.58% <15%> (ø)
...estFramework/Attributes/NotCentOS7FactAttribute.cs26.08% <26.08%> (ø)
...rc/Microsoft.ML.Featurizers/DateTimeTransformer.cs90.57% <90.57%> (ø)
....ML.AutoML/PipelineSuggesters/PipelineSuggester.cs83.19% <0%> (-3.37%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmDataset.cs74.21% <0%> (-2.12%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmBooster.cs83.8% <0%> (-0.63%)⬇️
...soft.ML.TestFramework/DataPipe/TestDataPipeBase.cs76.08% <0%> (-0.4%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.1% <0%> (-0.16%)⬇️
... and 21 more
#Resolved

@codemzs
codemzs requested a review from natkeDecember 12, 2019 18:27
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
WeekIso = 15,
YearIso = 16,
MonthLabel = 17,
AmPmLabel = 18,

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do AmPm label, would it make sense to also do weekend/weekday? (Often used in feature engineering) #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think thats not a bad idea, but the initial ask we got was just for these columns. We will start with just these and then can add in more if the requirement arises. #Resolved

{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <returns><see cref="DateTimeEstimator"/></returns>
public static DateTimeEstimator DateTimeTransformer(this TransformsCatalog catalog, string inputColumnName, string columnPrefix)

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming of the extension methods are not consistent with the other transformers (I'll leave it to others to judge whether the original scheme makes sense :-), but consistency is probably a more important consideration now)

If you look at the other transforms they mostly have the form Verb-Object e.g. CopyColumns, MapValueToKey etc. This makes sense when you are chaining them together in a pipeline.

Suggestions for this one: FeaturizeDateTime, SplitDateTime, TokenizeDateTime, ExtractDateTime ...

#Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of the ones you listed I would vote FeaturizeDateTime. Anyone have any objections if I rename the extension method to that? #Resolved

case DateTimeEstimator.ColumnsProduced.MonthLabel:
case DateTimeEstimator.ColumnsProduced.AmPmLabel:
case DateTimeEstimator.ColumnsProduced.DayOfWeekLabel:
case DateTimeEstimator.ColumnsProduced.HolidayName:

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usability question, why are we producing multiple output columns of instead of a single vector type column?

The next step after this DateTime transform is:

  • Convert all the INTs/USHORTs/BYTEs columns to Float32
  • One-hot encode the STRING columns ("Friday" to [ 0, 0, 0, 0, 0, 1, 0 ])
  • Concatenate all of these feature to single vector of Float32

Why aren't we just outputting the final vector type column? Then the user can directly use its output, like our other transforms. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a usecase for the individual columns, perhaps just a boolean parameter produceRawColumns, which then produces the individual outputs instead of a singular vector column? #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did it this way just because thats how the original python code works. It splits everything up into different columns and doesn't combine them into a vector. #Resolved

@natkenatkeDec 13, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a single vector is consistent with FeaturizeText. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original code we were bringing to ML.NET returned all individual columns. Because of that, for now, we will be leaving them as individual columns. If we learn that we never need individual columns and only use this as a vector then we can revisit this then. #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 17, 2019

Copy link
Copy Markdown
No pipelines are associated with this pull request.

#Resolved

@michaelgsharp
michaelgsharp merged commit 290e069 into dotnet:masterDec 17, 2019
@michaelgsharp
michaelgsharp deleted the datetime-transformer branch December 17, 2019 21:35
throw new InvalidOperationException($"Unsupported type {type}");
}

// The Native Featurizers do not currently support CentOS7, this method checks the OS and returns true if it is CentOS7.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about RedHat 7?

{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
using (Process process = new Process())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more performant way of doing this check would be to read from the file directly. Starting a new process just to get this info is too heavy-weight IMO.

See https://github.com/dotnet/runtime/blob/6f445d6dc237f59d730e0df47f8630b18887d776/src/installer/managed/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs#L140-L142

@ghostghost locked as resolved and limited conversation to collaborators Mar 19, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@michaelgsharp@natke@justinormont@harishsk@eerhardt@gvashishtha@davidbrownellWork
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

DateTimeTransformer featurizer - #4521

Merged
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer
Dec 17, 2019
Merged

DateTimeTransformer featurizer#4521
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer

Conversation

@michaelgsharp

Copy link
Copy Markdown
Contributor

This change adds in the DateTimeTransformer into the new Featurizers project. It is the first of a series of PR's that will go in. The DateTimeTransformer is implemented in native code, so this is mostly just a wrapper around that with the appropriate entrypoints for NimbusML as well. Since all the new estimator/transformers follow the same patterns, once this one is reviewed and checked in I will create the other PR's.

@michaelgsharp
michaelgsharp requested a review from a teamDecember 4, 2019 18:11
@michaelgsharpmichaelgsharp self-assigned this Dec 4, 2019
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Year = 1, Month, Day, Hour, Minute, Second, AmPm, Hour12, DayOfWeek, DayOfQuarter, DayOfYear,
WeekOfMonth, QuarterOfYear, HalfOfYear, WeekIso, YearIso, MonthLabel, AmPmLabel, DayOfWeekLabel,
HolidayName, IsPaidTimeOff
};

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend explicitly numbering each enum name. This lets us insert new values later without affecting the enum numeric value.

This is more important if the enum is serialized within the model, which I expect you're doing since the model needs to know which features to produce. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. They are now explicitly numbered. The Countries enum (now HolidayList) is also numbered now.


In reply to: 353910235 [](ancestors = 353910235)

/// <param name="catalog">Transform catalog</param>
/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <param name="columnsToDrop">List of columns to drop, if any</param>

@eerhardteerhardtDec 4, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One drawback to using columnsToDrop is that if a user is only interested in the DayOfYear and DayOfWeekLabel, they need to explicitly exclude all the rest. And then if we ever add a new output column in a future release, they would start getting it, and would need to add the new column to the "to drop" list. #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might recommend a bitwise use of an enum (flags) to say which columns to include.

Default would be DateTimeTransformerEstimator.ColumnsProduced.All = 0xFFFFFFFFFFFFFFFF (uint64).

Then users can bitwise or/and-not to select the columns they want to keep/drop.

Example usage

All output except DayOfYear: (subtractive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.All&~DateTimeTransformerEstimator.ColumnsProduced.DayOfYear)

Only DayOfYear and DayOfWeekLabel: (additive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.DayOfYear|DateTimeTransformerEstimator.ColumnsProduced.DayOfWeekLabel)

#Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, there's a [Flags] attribute in C# -- https://docs.microsoft.com/en-us/dotnet/api/system.flagsattribute?view=netcore-3.0

[Flags]publicenumColumnsProduced:uint64{Year=1<<0,Month=1<<1,Day=1<<2,Hour=1<<3,WeekOfMonth=1<<4,
...}

#Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a way you think would be better? We had also thought about not even including that ability here and just letting the user use the DropColumns transformer to remove any columns they didn't want. That would keep this code cleaner and not duplicate that type of functionality. Thoughts on that?


In reply to: 353911746 [](ancestors = 353911746)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think that doing flags is a great idea personally. I think enums are more clear (if more verbose) than flags usually. As I mentioned to Eric in this same thread though, we can remove this capability from here and just let the user use the DropColumn transformer to remove anything they dont want. What is your opinion on that?


In reply to: 353968394 [](ancestors = 353968394)

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect this featurizer isn't aware of which columns are being requested of it

Maybe it should be made aware? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the usability of the flag method.

There is a user need of not including all outputs as overfitting occurs, mainly on the larger time scales, like year number. Including the year in a classification or regression model often leads to the model memorizing the label value specifically for a date range.

For instance the model will memorize that 2017-03-01 to 2018-01-05, the label value was high. This information is not valuable to memorize when predicting future data. Instead we want to ensure it memorizes that traffic volumes (the label) are higher on weekends (extracting useful patterns).

Hence it is nice to encourage users (as part of the timedate featurizer API itself) to think about which columns to include/exclude. This is as opposed to a user needing a separate DropColumns step in their pipeline. #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So so far we have had 3 suggestions.
1 - Dont support dropping columns as a native part of this transformer.
2 - Use Enums.
3 - Use Flags.

Can we finalize on the decision? I personally agree with Harish that since we already have a drop columns transformer that we can just use that and not have that functionality be present here. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend a way to proactively select the output columns to create during the setup of the transform. Instead of dropping later, just don't create them.

Generally, we should assume users won't think (or know) to drop later. For usability, placing it in the constructor exposes users to the idea, and forces them to consider which columns will be beneficial. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, since this functionality already exists inside of ML.NET as a separate transformer we will be leaving this as is. We can revisit this later as the need arises. Even if this transformer produces all the columns, if no downstream transformers request them then they will never be realized. It shouldn't cause any performance impact this way. #Resolved

HolidayName, IsPaidTimeOff
};

public enum Countries : byte

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want a UINT given that there are ~200 current countries (and they keep changing), and in the future we may a more specific break down, like UnitedStatesBankingHolidays vs. UnitedStatesFederalHolidays

Suggested change
publicenumCountries:byte
publicenumCountries:uint
``` #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point. I have changed the enum to be uint as suggested.


In reply to: 353916304 [](ancestors = 353916304)

public ColumnsProduced[] ColumnsToDrop;

[Argument(ArgumentType.AtMostOnce, HelpText = "Country to get holidays for. Defaults to none if not passed", Name = "Country", ShortName = "ctry", SortOrder = 4)]
public Countries Country = Countries.None;

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Countries may be a bit restrictive of a term for the future. Perhaps name as HolidayList.

Perhaps in the future we'll want UnitedStatesCaliforniaEducationHolidays (see: list). We may also want more specific lists, like UnitedStatesMajorHolidays. Or localized lists like GermanyBavaria (see: list).

Or perhaps in the future we'll want to version the holiday list Germany2019 (as holidays are declared and dropped). Versioning helps old models run in current versions of ML.NET. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I have renamed it to HolidayList, but we can revisit it if needed in the future as well.


In reply to: 353920532 [](ancestors = 353920532)

public int DTCYearIso { get; set; }
public string DTCMonthLabel { get; set; }
public string DTCAmPmLabel { get; set; }
public string DTCDayOfWeekLabel { get; set; }

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be needed to be added to the code being wrapped here, but when featurizing DateTime fields, I prefer polar (Sin/Cos) transforms (along with the others listed).

Clocks and times/dates are cyclic. The current feature set doesn't include this fact.

The main gain of polar date transforms is that it places times like 11:50PM and 12:10AM right next to each other (both mapping to ~0.999 in the cosine transform of percent of day). This lets a model make a decision boundary at CosTimeOfDay > 0.9 to treat these two times as very similar.

Contrast this to the DTCHour feature which will map these to 23 and 0 (issue: non-continuous range, and not possible to group both the 0 and 23 in a single split point, which to a human is similar).

Info:

Code example I use for DateTime feature engineering:

DateTimedt;varinvalidDate=!DateTime.TryParse(input.Date,outdt);varpercentOfDay=(invalidDate?Single.NaN:(float)(newTimeSpan(dt.Hour,dt.Minute,0).TotalMinutes)/1440 f);varpercentOfWeek=(invalidDate?Single.NaN:((float)dt.DayOfWeek+percentOfDay)/7 f);varpercentOfMonth=(invalidDate?Single.NaN:(float)(dt.Day+percentOfDay)/(float)DateTime.DaysInMonth(dt.Year,dt.Month));varpercentOfYear=(invalidDate?Single.NaN:(float)((dt.DayOfYear+percentOfDay)/(newDateTime(dt.Year+1,1,1)-newDateTime(dt.Year,1,1)).TotalDays));// Time features like the current onesoutput.DateWeekday=(invalidDate?Single.NaN:(float)dt.DayOfWeek);output.DateMonth=(invalidDate?Single.NaN:(float)dt.Month);output.DateHour=(invalidDate?Single.NaN:dt.Hour);output.DatePartOfDay=(invalidDate?Single.NaN:(float)Math.Round(dt.AddHours(-3).Hour/6 f));output.InvalidDate=(invalidDate?1.0f:0.0f);// Polar date/time transforms for percent of day & week & year output.CosTimeOfDay=(float)Math.Cos(2*Math.PI*percentOfDay);output.SinTimeOfDay=(float)Math.Sin(2*Math.PI*percentOfDay);output.CosTimeOfWeek=(float)Math.Cos(2*Math.PI*percentOfWeek);output.SinTimeOfWeek=(float)Math.Sin(2*Math.PI*percentOfWeek);output.CosTimeOfMonth=(float)Math.Cos(2*Math.PI*percentOfMonth);output.SinTimeOfMonth=(float)Math.Sin(2*Math.PI*percentOfMonth);output.CosTimeOfYear=(float)Math.Cos(2*Math.PI*percentOfYear);output.SinTimeOfYear=(float)Math.Sin(2*Math.PI*percentOfYear);
``` #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @davidbrownellWork #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add this as a potential feature for Mn. #Resolved

@gvashishthagvashishthaDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean add to the ML.NET plan? Any estimate on how much additional dev time this would take? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the new features is pretty simple. I expect more of the work is in testing. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the input dateTime column a long and not a DateTime? #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long to match a unix timestamp. We will be adding in the ability to use C# DateTime and string in an ISO format in the future, but the initial implementation is just the unix timestamp. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern here is that .NET developers expect to have their date time data using System.DateTime. That is the natural type in .NET. Forcing the user to convert from System.DateTime to a long unix timestamp doesn't seem like a good experience. It would make more sense if the input column was a System.DateTime. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do the conversion yourself here? The user's input column can be DateTime, and then you convert it to a long unix timestamp before passing it to the C++ code? #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to ensure that the process of converting from a string to "Date time" is consistent across frameworks - especially considering that a DateTime in and of itself isn't sufficient in some scenarios, as we lose (potentially important) time zone information in the process. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not asking for string. I'm asking for the ML.NET data type that a user would feed into this transform to be the canonical System.DateTime type and not a long type.

Specifically, this transform should require the input column to be of this type:

/// <summary>
/// The standard date time type. This has representation type of <see cref="DateTime"/>.
/// Note this can have only one possible value, accessible by the singleton static property <see cref="Instance"/>.
/// </summary>
publicsealedclassDateTimeDataViewType:PrimitiveDataViewType

It doesn't make sense for a user to have to convert a DateTime to a long before calling this transform. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically, look at the sample code being checked in under samples:

// Create a small dataset as an IEnumerable.// Future Date - 2025 June 30varsamples=new[]{newDateTimeInput(){Date=1751241600}};

Instead, that sample code should be:

// Create a small dataset as an IEnumerable.varsamples=new[]{newDateTimeInput(){Date=newDateTime(2025,6,30)}};
``` #Resolved

@davidbrownellWorkdavidbrownellWorkDec 6, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - eventually a C# developer will be able to pass System:DateTime, string, or long. #Resolved

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is on our todo list and will be added in in the future. We will eventually be supporting DateTime and string as well, but initial support will just be for long. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
ctx.Writer.Write(data);
}

public void Dispose()

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who calls this Dispose method? We have a problem with tranformers that need to be disposed. See #906 and my comment on #4223

cc @codemzs #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain. I am not manually calling it anywhere. Is it happening during pipeline teardown ok? Or is there more desirable behavior? #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain.

There is no call of it when the pipeline teardown happens. That is why that bug was opened. I'm still not certain on why it was closed. Maybe @codemzs can comment. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

/azp run #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 9, 2019

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

#Resolved

@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

Going to close and reopen the PR to reset the stuck builds. #Resolved

@codecov

codecovBot commented Dec 10, 2019

Copy link
Copy Markdown

Codecov Report

Merging #4521 into master will increase coverage by 0.03%.
The diff coverage is 88.11%.

@@ Coverage Diff @@## master #4521 +/- ##
==========================================
+ Coverage 75.11% 75.15% +0.03% 
==========================================
Files 908 913 +5 Lines 160204 160891 +687 Branches 17254 17292 +38 ==========================================
+ Hits 120340 120911 +571 - Misses 35050 35153 +103 - Partials 4814 4827 +13
FlagCoverage Δ
#Debug75.15% <88.11%> (+0.03%)⬆️
#production70.54% <86.24%> (+0.02%)⬆️
#test90.29% <91.41%> (+0.03%)⬆️
Impacted FilesCoverage Δ
....ML.Tests/Transformers/DateTimeTransformerTests.cs100% <100%> (ø)
...crosoft.ML.Core.Tests/UnitTests/TestEntryPoints.cs98.36% <100%> (ø)⬆️
src/Microsoft.ML.Featurizers/Common.cs19.58% <15%> (ø)
...estFramework/Attributes/NotCentOS7FactAttribute.cs26.08% <26.08%> (ø)
...rc/Microsoft.ML.Featurizers/DateTimeTransformer.cs90.57% <90.57%> (ø)
....ML.AutoML/PipelineSuggesters/PipelineSuggester.cs83.19% <0%> (-3.37%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmDataset.cs74.21% <0%> (-2.12%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmBooster.cs83.8% <0%> (-0.63%)⬇️
...soft.ML.TestFramework/DataPipe/TestDataPipeBase.cs76.08% <0%> (-0.4%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.1% <0%> (-0.16%)⬇️
... and 21 more
#Resolved

@codemzs
codemzs requested a review from natkeDecember 12, 2019 18:27
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
WeekIso = 15,
YearIso = 16,
MonthLabel = 17,
AmPmLabel = 18,

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do AmPm label, would it make sense to also do weekend/weekday? (Often used in feature engineering) #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think thats not a bad idea, but the initial ask we got was just for these columns. We will start with just these and then can add in more if the requirement arises. #Resolved

{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <returns><see cref="DateTimeEstimator"/></returns>
public static DateTimeEstimator DateTimeTransformer(this TransformsCatalog catalog, string inputColumnName, string columnPrefix)

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming of the extension methods are not consistent with the other transformers (I'll leave it to others to judge whether the original scheme makes sense :-), but consistency is probably a more important consideration now)

If you look at the other transforms they mostly have the form Verb-Object e.g. CopyColumns, MapValueToKey etc. This makes sense when you are chaining them together in a pipeline.

Suggestions for this one: FeaturizeDateTime, SplitDateTime, TokenizeDateTime, ExtractDateTime ...

#Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of the ones you listed I would vote FeaturizeDateTime. Anyone have any objections if I rename the extension method to that? #Resolved

case DateTimeEstimator.ColumnsProduced.MonthLabel:
case DateTimeEstimator.ColumnsProduced.AmPmLabel:
case DateTimeEstimator.ColumnsProduced.DayOfWeekLabel:
case DateTimeEstimator.ColumnsProduced.HolidayName:

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usability question, why are we producing multiple output columns of instead of a single vector type column?

The next step after this DateTime transform is:

  • Convert all the INTs/USHORTs/BYTEs columns to Float32
  • One-hot encode the STRING columns ("Friday" to [ 0, 0, 0, 0, 0, 1, 0 ])
  • Concatenate all of these feature to single vector of Float32

Why aren't we just outputting the final vector type column? Then the user can directly use its output, like our other transforms. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a usecase for the individual columns, perhaps just a boolean parameter produceRawColumns, which then produces the individual outputs instead of a singular vector column? #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did it this way just because thats how the original python code works. It splits everything up into different columns and doesn't combine them into a vector. #Resolved

@natkenatkeDec 13, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a single vector is consistent with FeaturizeText. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original code we were bringing to ML.NET returned all individual columns. Because of that, for now, we will be leaving them as individual columns. If we learn that we never need individual columns and only use this as a vector then we can revisit this then. #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 17, 2019

Copy link
Copy Markdown
No pipelines are associated with this pull request.

#Resolved

@michaelgsharp
michaelgsharp merged commit 290e069 into dotnet:masterDec 17, 2019
@michaelgsharp
michaelgsharp deleted the datetime-transformer branch December 17, 2019 21:35
throw new InvalidOperationException($"Unsupported type {type}");
}

// The Native Featurizers do not currently support CentOS7, this method checks the OS and returns true if it is CentOS7.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about RedHat 7?

{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
using (Process process = new Process())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more performant way of doing this check would be to read from the file directly. Starting a new process just to get this info is too heavy-weight IMO.

See https://github.com/dotnet/runtime/blob/6f445d6dc237f59d730e0df47f8630b18887d776/src/installer/managed/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs#L140-L142

@ghostghost locked as resolved and limited conversation to collaborators Mar 19, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@michaelgsharp@natke@justinormont@harishsk@eerhardt@gvashishtha@davidbrownellWork
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

DateTimeTransformer featurizer - #4521

Merged
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer
Dec 17, 2019
Merged

DateTimeTransformer featurizer#4521
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer

Conversation

@michaelgsharp

Copy link
Copy Markdown
Contributor

This change adds in the DateTimeTransformer into the new Featurizers project. It is the first of a series of PR's that will go in. The DateTimeTransformer is implemented in native code, so this is mostly just a wrapper around that with the appropriate entrypoints for NimbusML as well. Since all the new estimator/transformers follow the same patterns, once this one is reviewed and checked in I will create the other PR's.

@michaelgsharp
michaelgsharp requested a review from a teamDecember 4, 2019 18:11
@michaelgsharpmichaelgsharp self-assigned this Dec 4, 2019
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Year = 1, Month, Day, Hour, Minute, Second, AmPm, Hour12, DayOfWeek, DayOfQuarter, DayOfYear,
WeekOfMonth, QuarterOfYear, HalfOfYear, WeekIso, YearIso, MonthLabel, AmPmLabel, DayOfWeekLabel,
HolidayName, IsPaidTimeOff
};

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend explicitly numbering each enum name. This lets us insert new values later without affecting the enum numeric value.

This is more important if the enum is serialized within the model, which I expect you're doing since the model needs to know which features to produce. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. They are now explicitly numbered. The Countries enum (now HolidayList) is also numbered now.


In reply to: 353910235 [](ancestors = 353910235)

/// <param name="catalog">Transform catalog</param>
/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <param name="columnsToDrop">List of columns to drop, if any</param>

@eerhardteerhardtDec 4, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One drawback to using columnsToDrop is that if a user is only interested in the DayOfYear and DayOfWeekLabel, they need to explicitly exclude all the rest. And then if we ever add a new output column in a future release, they would start getting it, and would need to add the new column to the "to drop" list. #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might recommend a bitwise use of an enum (flags) to say which columns to include.

Default would be DateTimeTransformerEstimator.ColumnsProduced.All = 0xFFFFFFFFFFFFFFFF (uint64).

Then users can bitwise or/and-not to select the columns they want to keep/drop.

Example usage

All output except DayOfYear: (subtractive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.All&~DateTimeTransformerEstimator.ColumnsProduced.DayOfYear)

Only DayOfYear and DayOfWeekLabel: (additive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.DayOfYear|DateTimeTransformerEstimator.ColumnsProduced.DayOfWeekLabel)

#Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, there's a [Flags] attribute in C# -- https://docs.microsoft.com/en-us/dotnet/api/system.flagsattribute?view=netcore-3.0

[Flags]publicenumColumnsProduced:uint64{Year=1<<0,Month=1<<1,Day=1<<2,Hour=1<<3,WeekOfMonth=1<<4,
...}

#Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a way you think would be better? We had also thought about not even including that ability here and just letting the user use the DropColumns transformer to remove any columns they didn't want. That would keep this code cleaner and not duplicate that type of functionality. Thoughts on that?


In reply to: 353911746 [](ancestors = 353911746)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think that doing flags is a great idea personally. I think enums are more clear (if more verbose) than flags usually. As I mentioned to Eric in this same thread though, we can remove this capability from here and just let the user use the DropColumn transformer to remove anything they dont want. What is your opinion on that?


In reply to: 353968394 [](ancestors = 353968394)

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect this featurizer isn't aware of which columns are being requested of it

Maybe it should be made aware? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the usability of the flag method.

There is a user need of not including all outputs as overfitting occurs, mainly on the larger time scales, like year number. Including the year in a classification or regression model often leads to the model memorizing the label value specifically for a date range.

For instance the model will memorize that 2017-03-01 to 2018-01-05, the label value was high. This information is not valuable to memorize when predicting future data. Instead we want to ensure it memorizes that traffic volumes (the label) are higher on weekends (extracting useful patterns).

Hence it is nice to encourage users (as part of the timedate featurizer API itself) to think about which columns to include/exclude. This is as opposed to a user needing a separate DropColumns step in their pipeline. #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So so far we have had 3 suggestions.
1 - Dont support dropping columns as a native part of this transformer.
2 - Use Enums.
3 - Use Flags.

Can we finalize on the decision? I personally agree with Harish that since we already have a drop columns transformer that we can just use that and not have that functionality be present here. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend a way to proactively select the output columns to create during the setup of the transform. Instead of dropping later, just don't create them.

Generally, we should assume users won't think (or know) to drop later. For usability, placing it in the constructor exposes users to the idea, and forces them to consider which columns will be beneficial. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, since this functionality already exists inside of ML.NET as a separate transformer we will be leaving this as is. We can revisit this later as the need arises. Even if this transformer produces all the columns, if no downstream transformers request them then they will never be realized. It shouldn't cause any performance impact this way. #Resolved

HolidayName, IsPaidTimeOff
};

public enum Countries : byte

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want a UINT given that there are ~200 current countries (and they keep changing), and in the future we may a more specific break down, like UnitedStatesBankingHolidays vs. UnitedStatesFederalHolidays

Suggested change
publicenumCountries:byte
publicenumCountries:uint
``` #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point. I have changed the enum to be uint as suggested.


In reply to: 353916304 [](ancestors = 353916304)

public ColumnsProduced[] ColumnsToDrop;

[Argument(ArgumentType.AtMostOnce, HelpText = "Country to get holidays for. Defaults to none if not passed", Name = "Country", ShortName = "ctry", SortOrder = 4)]
public Countries Country = Countries.None;

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Countries may be a bit restrictive of a term for the future. Perhaps name as HolidayList.

Perhaps in the future we'll want UnitedStatesCaliforniaEducationHolidays (see: list). We may also want more specific lists, like UnitedStatesMajorHolidays. Or localized lists like GermanyBavaria (see: list).

Or perhaps in the future we'll want to version the holiday list Germany2019 (as holidays are declared and dropped). Versioning helps old models run in current versions of ML.NET. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I have renamed it to HolidayList, but we can revisit it if needed in the future as well.


In reply to: 353920532 [](ancestors = 353920532)

public int DTCYearIso { get; set; }
public string DTCMonthLabel { get; set; }
public string DTCAmPmLabel { get; set; }
public string DTCDayOfWeekLabel { get; set; }

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be needed to be added to the code being wrapped here, but when featurizing DateTime fields, I prefer polar (Sin/Cos) transforms (along with the others listed).

Clocks and times/dates are cyclic. The current feature set doesn't include this fact.

The main gain of polar date transforms is that it places times like 11:50PM and 12:10AM right next to each other (both mapping to ~0.999 in the cosine transform of percent of day). This lets a model make a decision boundary at CosTimeOfDay > 0.9 to treat these two times as very similar.

Contrast this to the DTCHour feature which will map these to 23 and 0 (issue: non-continuous range, and not possible to group both the 0 and 23 in a single split point, which to a human is similar).

Info:

Code example I use for DateTime feature engineering:

DateTimedt;varinvalidDate=!DateTime.TryParse(input.Date,outdt);varpercentOfDay=(invalidDate?Single.NaN:(float)(newTimeSpan(dt.Hour,dt.Minute,0).TotalMinutes)/1440 f);varpercentOfWeek=(invalidDate?Single.NaN:((float)dt.DayOfWeek+percentOfDay)/7 f);varpercentOfMonth=(invalidDate?Single.NaN:(float)(dt.Day+percentOfDay)/(float)DateTime.DaysInMonth(dt.Year,dt.Month));varpercentOfYear=(invalidDate?Single.NaN:(float)((dt.DayOfYear+percentOfDay)/(newDateTime(dt.Year+1,1,1)-newDateTime(dt.Year,1,1)).TotalDays));// Time features like the current onesoutput.DateWeekday=(invalidDate?Single.NaN:(float)dt.DayOfWeek);output.DateMonth=(invalidDate?Single.NaN:(float)dt.Month);output.DateHour=(invalidDate?Single.NaN:dt.Hour);output.DatePartOfDay=(invalidDate?Single.NaN:(float)Math.Round(dt.AddHours(-3).Hour/6 f));output.InvalidDate=(invalidDate?1.0f:0.0f);// Polar date/time transforms for percent of day & week & year output.CosTimeOfDay=(float)Math.Cos(2*Math.PI*percentOfDay);output.SinTimeOfDay=(float)Math.Sin(2*Math.PI*percentOfDay);output.CosTimeOfWeek=(float)Math.Cos(2*Math.PI*percentOfWeek);output.SinTimeOfWeek=(float)Math.Sin(2*Math.PI*percentOfWeek);output.CosTimeOfMonth=(float)Math.Cos(2*Math.PI*percentOfMonth);output.SinTimeOfMonth=(float)Math.Sin(2*Math.PI*percentOfMonth);output.CosTimeOfYear=(float)Math.Cos(2*Math.PI*percentOfYear);output.SinTimeOfYear=(float)Math.Sin(2*Math.PI*percentOfYear);
``` #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @davidbrownellWork #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add this as a potential feature for Mn. #Resolved

@gvashishthagvashishthaDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean add to the ML.NET plan? Any estimate on how much additional dev time this would take? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the new features is pretty simple. I expect more of the work is in testing. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the input dateTime column a long and not a DateTime? #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long to match a unix timestamp. We will be adding in the ability to use C# DateTime and string in an ISO format in the future, but the initial implementation is just the unix timestamp. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern here is that .NET developers expect to have their date time data using System.DateTime. That is the natural type in .NET. Forcing the user to convert from System.DateTime to a long unix timestamp doesn't seem like a good experience. It would make more sense if the input column was a System.DateTime. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do the conversion yourself here? The user's input column can be DateTime, and then you convert it to a long unix timestamp before passing it to the C++ code? #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to ensure that the process of converting from a string to "Date time" is consistent across frameworks - especially considering that a DateTime in and of itself isn't sufficient in some scenarios, as we lose (potentially important) time zone information in the process. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not asking for string. I'm asking for the ML.NET data type that a user would feed into this transform to be the canonical System.DateTime type and not a long type.

Specifically, this transform should require the input column to be of this type:

/// <summary>
/// The standard date time type. This has representation type of <see cref="DateTime"/>.
/// Note this can have only one possible value, accessible by the singleton static property <see cref="Instance"/>.
/// </summary>
publicsealedclassDateTimeDataViewType:PrimitiveDataViewType

It doesn't make sense for a user to have to convert a DateTime to a long before calling this transform. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically, look at the sample code being checked in under samples:

// Create a small dataset as an IEnumerable.// Future Date - 2025 June 30varsamples=new[]{newDateTimeInput(){Date=1751241600}};

Instead, that sample code should be:

// Create a small dataset as an IEnumerable.varsamples=new[]{newDateTimeInput(){Date=newDateTime(2025,6,30)}};
``` #Resolved

@davidbrownellWorkdavidbrownellWorkDec 6, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - eventually a C# developer will be able to pass System:DateTime, string, or long. #Resolved

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is on our todo list and will be added in in the future. We will eventually be supporting DateTime and string as well, but initial support will just be for long. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
ctx.Writer.Write(data);
}

public void Dispose()

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who calls this Dispose method? We have a problem with tranformers that need to be disposed. See #906 and my comment on #4223

cc @codemzs #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain. I am not manually calling it anywhere. Is it happening during pipeline teardown ok? Or is there more desirable behavior? #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain.

There is no call of it when the pipeline teardown happens. That is why that bug was opened. I'm still not certain on why it was closed. Maybe @codemzs can comment. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

/azp run #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 9, 2019

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

#Resolved

@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

Going to close and reopen the PR to reset the stuck builds. #Resolved

@codecov

codecovBot commented Dec 10, 2019

Copy link
Copy Markdown

Codecov Report

Merging #4521 into master will increase coverage by 0.03%.
The diff coverage is 88.11%.

@@ Coverage Diff @@## master #4521 +/- ##
==========================================
+ Coverage 75.11% 75.15% +0.03% 
==========================================
Files 908 913 +5 Lines 160204 160891 +687 Branches 17254 17292 +38 ==========================================
+ Hits 120340 120911 +571 - Misses 35050 35153 +103 - Partials 4814 4827 +13
FlagCoverage Δ
#Debug75.15% <88.11%> (+0.03%)⬆️
#production70.54% <86.24%> (+0.02%)⬆️
#test90.29% <91.41%> (+0.03%)⬆️
Impacted FilesCoverage Δ
....ML.Tests/Transformers/DateTimeTransformerTests.cs100% <100%> (ø)
...crosoft.ML.Core.Tests/UnitTests/TestEntryPoints.cs98.36% <100%> (ø)⬆️
src/Microsoft.ML.Featurizers/Common.cs19.58% <15%> (ø)
...estFramework/Attributes/NotCentOS7FactAttribute.cs26.08% <26.08%> (ø)
...rc/Microsoft.ML.Featurizers/DateTimeTransformer.cs90.57% <90.57%> (ø)
....ML.AutoML/PipelineSuggesters/PipelineSuggester.cs83.19% <0%> (-3.37%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmDataset.cs74.21% <0%> (-2.12%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmBooster.cs83.8% <0%> (-0.63%)⬇️
...soft.ML.TestFramework/DataPipe/TestDataPipeBase.cs76.08% <0%> (-0.4%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.1% <0%> (-0.16%)⬇️
... and 21 more
#Resolved

@codemzs
codemzs requested a review from natkeDecember 12, 2019 18:27
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
WeekIso = 15,
YearIso = 16,
MonthLabel = 17,
AmPmLabel = 18,

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do AmPm label, would it make sense to also do weekend/weekday? (Often used in feature engineering) #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think thats not a bad idea, but the initial ask we got was just for these columns. We will start with just these and then can add in more if the requirement arises. #Resolved

{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <returns><see cref="DateTimeEstimator"/></returns>
public static DateTimeEstimator DateTimeTransformer(this TransformsCatalog catalog, string inputColumnName, string columnPrefix)

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming of the extension methods are not consistent with the other transformers (I'll leave it to others to judge whether the original scheme makes sense :-), but consistency is probably a more important consideration now)

If you look at the other transforms they mostly have the form Verb-Object e.g. CopyColumns, MapValueToKey etc. This makes sense when you are chaining them together in a pipeline.

Suggestions for this one: FeaturizeDateTime, SplitDateTime, TokenizeDateTime, ExtractDateTime ...

#Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of the ones you listed I would vote FeaturizeDateTime. Anyone have any objections if I rename the extension method to that? #Resolved

case DateTimeEstimator.ColumnsProduced.MonthLabel:
case DateTimeEstimator.ColumnsProduced.AmPmLabel:
case DateTimeEstimator.ColumnsProduced.DayOfWeekLabel:
case DateTimeEstimator.ColumnsProduced.HolidayName:

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usability question, why are we producing multiple output columns of instead of a single vector type column?

The next step after this DateTime transform is:

  • Convert all the INTs/USHORTs/BYTEs columns to Float32
  • One-hot encode the STRING columns ("Friday" to [ 0, 0, 0, 0, 0, 1, 0 ])
  • Concatenate all of these feature to single vector of Float32

Why aren't we just outputting the final vector type column? Then the user can directly use its output, like our other transforms. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a usecase for the individual columns, perhaps just a boolean parameter produceRawColumns, which then produces the individual outputs instead of a singular vector column? #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did it this way just because thats how the original python code works. It splits everything up into different columns and doesn't combine them into a vector. #Resolved

@natkenatkeDec 13, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a single vector is consistent with FeaturizeText. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original code we were bringing to ML.NET returned all individual columns. Because of that, for now, we will be leaving them as individual columns. If we learn that we never need individual columns and only use this as a vector then we can revisit this then. #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 17, 2019

Copy link
Copy Markdown
No pipelines are associated with this pull request.

#Resolved

@michaelgsharp
michaelgsharp merged commit 290e069 into dotnet:masterDec 17, 2019
@michaelgsharp
michaelgsharp deleted the datetime-transformer branch December 17, 2019 21:35
throw new InvalidOperationException($"Unsupported type {type}");
}

// The Native Featurizers do not currently support CentOS7, this method checks the OS and returns true if it is CentOS7.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about RedHat 7?

{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
using (Process process = new Process())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more performant way of doing this check would be to read from the file directly. Starting a new process just to get this info is too heavy-weight IMO.

See https://github.com/dotnet/runtime/blob/6f445d6dc237f59d730e0df47f8630b18887d776/src/installer/managed/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs#L140-L142

@ghostghost locked as resolved and limited conversation to collaborators Mar 19, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@michaelgsharp@natke@justinormont@harishsk@eerhardt@gvashishtha@davidbrownellWork
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

DateTimeTransformer featurizer - #4521

Merged
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer
Dec 17, 2019
Merged

DateTimeTransformer featurizer#4521
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer

Conversation

@michaelgsharp

Copy link
Copy Markdown
Contributor

This change adds in the DateTimeTransformer into the new Featurizers project. It is the first of a series of PR's that will go in. The DateTimeTransformer is implemented in native code, so this is mostly just a wrapper around that with the appropriate entrypoints for NimbusML as well. Since all the new estimator/transformers follow the same patterns, once this one is reviewed and checked in I will create the other PR's.

@michaelgsharp
michaelgsharp requested a review from a teamDecember 4, 2019 18:11
@michaelgsharpmichaelgsharp self-assigned this Dec 4, 2019
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Year = 1, Month, Day, Hour, Minute, Second, AmPm, Hour12, DayOfWeek, DayOfQuarter, DayOfYear,
WeekOfMonth, QuarterOfYear, HalfOfYear, WeekIso, YearIso, MonthLabel, AmPmLabel, DayOfWeekLabel,
HolidayName, IsPaidTimeOff
};

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend explicitly numbering each enum name. This lets us insert new values later without affecting the enum numeric value.

This is more important if the enum is serialized within the model, which I expect you're doing since the model needs to know which features to produce. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. They are now explicitly numbered. The Countries enum (now HolidayList) is also numbered now.


In reply to: 353910235 [](ancestors = 353910235)

/// <param name="catalog">Transform catalog</param>
/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <param name="columnsToDrop">List of columns to drop, if any</param>

@eerhardteerhardtDec 4, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One drawback to using columnsToDrop is that if a user is only interested in the DayOfYear and DayOfWeekLabel, they need to explicitly exclude all the rest. And then if we ever add a new output column in a future release, they would start getting it, and would need to add the new column to the "to drop" list. #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might recommend a bitwise use of an enum (flags) to say which columns to include.

Default would be DateTimeTransformerEstimator.ColumnsProduced.All = 0xFFFFFFFFFFFFFFFF (uint64).

Then users can bitwise or/and-not to select the columns they want to keep/drop.

Example usage

All output except DayOfYear: (subtractive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.All&~DateTimeTransformerEstimator.ColumnsProduced.DayOfYear)

Only DayOfYear and DayOfWeekLabel: (additive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.DayOfYear|DateTimeTransformerEstimator.ColumnsProduced.DayOfWeekLabel)

#Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, there's a [Flags] attribute in C# -- https://docs.microsoft.com/en-us/dotnet/api/system.flagsattribute?view=netcore-3.0

[Flags]publicenumColumnsProduced:uint64{Year=1<<0,Month=1<<1,Day=1<<2,Hour=1<<3,WeekOfMonth=1<<4,
...}

#Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a way you think would be better? We had also thought about not even including that ability here and just letting the user use the DropColumns transformer to remove any columns they didn't want. That would keep this code cleaner and not duplicate that type of functionality. Thoughts on that?


In reply to: 353911746 [](ancestors = 353911746)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think that doing flags is a great idea personally. I think enums are more clear (if more verbose) than flags usually. As I mentioned to Eric in this same thread though, we can remove this capability from here and just let the user use the DropColumn transformer to remove anything they dont want. What is your opinion on that?


In reply to: 353968394 [](ancestors = 353968394)

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect this featurizer isn't aware of which columns are being requested of it

Maybe it should be made aware? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the usability of the flag method.

There is a user need of not including all outputs as overfitting occurs, mainly on the larger time scales, like year number. Including the year in a classification or regression model often leads to the model memorizing the label value specifically for a date range.

For instance the model will memorize that 2017-03-01 to 2018-01-05, the label value was high. This information is not valuable to memorize when predicting future data. Instead we want to ensure it memorizes that traffic volumes (the label) are higher on weekends (extracting useful patterns).

Hence it is nice to encourage users (as part of the timedate featurizer API itself) to think about which columns to include/exclude. This is as opposed to a user needing a separate DropColumns step in their pipeline. #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So so far we have had 3 suggestions.
1 - Dont support dropping columns as a native part of this transformer.
2 - Use Enums.
3 - Use Flags.

Can we finalize on the decision? I personally agree with Harish that since we already have a drop columns transformer that we can just use that and not have that functionality be present here. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend a way to proactively select the output columns to create during the setup of the transform. Instead of dropping later, just don't create them.

Generally, we should assume users won't think (or know) to drop later. For usability, placing it in the constructor exposes users to the idea, and forces them to consider which columns will be beneficial. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, since this functionality already exists inside of ML.NET as a separate transformer we will be leaving this as is. We can revisit this later as the need arises. Even if this transformer produces all the columns, if no downstream transformers request them then they will never be realized. It shouldn't cause any performance impact this way. #Resolved

HolidayName, IsPaidTimeOff
};

public enum Countries : byte

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want a UINT given that there are ~200 current countries (and they keep changing), and in the future we may a more specific break down, like UnitedStatesBankingHolidays vs. UnitedStatesFederalHolidays

Suggested change
publicenumCountries:byte
publicenumCountries:uint
``` #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point. I have changed the enum to be uint as suggested.


In reply to: 353916304 [](ancestors = 353916304)

public ColumnsProduced[] ColumnsToDrop;

[Argument(ArgumentType.AtMostOnce, HelpText = "Country to get holidays for. Defaults to none if not passed", Name = "Country", ShortName = "ctry", SortOrder = 4)]
public Countries Country = Countries.None;

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Countries may be a bit restrictive of a term for the future. Perhaps name as HolidayList.

Perhaps in the future we'll want UnitedStatesCaliforniaEducationHolidays (see: list). We may also want more specific lists, like UnitedStatesMajorHolidays. Or localized lists like GermanyBavaria (see: list).

Or perhaps in the future we'll want to version the holiday list Germany2019 (as holidays are declared and dropped). Versioning helps old models run in current versions of ML.NET. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I have renamed it to HolidayList, but we can revisit it if needed in the future as well.


In reply to: 353920532 [](ancestors = 353920532)

public int DTCYearIso { get; set; }
public string DTCMonthLabel { get; set; }
public string DTCAmPmLabel { get; set; }
public string DTCDayOfWeekLabel { get; set; }

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be needed to be added to the code being wrapped here, but when featurizing DateTime fields, I prefer polar (Sin/Cos) transforms (along with the others listed).

Clocks and times/dates are cyclic. The current feature set doesn't include this fact.

The main gain of polar date transforms is that it places times like 11:50PM and 12:10AM right next to each other (both mapping to ~0.999 in the cosine transform of percent of day). This lets a model make a decision boundary at CosTimeOfDay > 0.9 to treat these two times as very similar.

Contrast this to the DTCHour feature which will map these to 23 and 0 (issue: non-continuous range, and not possible to group both the 0 and 23 in a single split point, which to a human is similar).

Info:

Code example I use for DateTime feature engineering:

DateTimedt;varinvalidDate=!DateTime.TryParse(input.Date,outdt);varpercentOfDay=(invalidDate?Single.NaN:(float)(newTimeSpan(dt.Hour,dt.Minute,0).TotalMinutes)/1440 f);varpercentOfWeek=(invalidDate?Single.NaN:((float)dt.DayOfWeek+percentOfDay)/7 f);varpercentOfMonth=(invalidDate?Single.NaN:(float)(dt.Day+percentOfDay)/(float)DateTime.DaysInMonth(dt.Year,dt.Month));varpercentOfYear=(invalidDate?Single.NaN:(float)((dt.DayOfYear+percentOfDay)/(newDateTime(dt.Year+1,1,1)-newDateTime(dt.Year,1,1)).TotalDays));// Time features like the current onesoutput.DateWeekday=(invalidDate?Single.NaN:(float)dt.DayOfWeek);output.DateMonth=(invalidDate?Single.NaN:(float)dt.Month);output.DateHour=(invalidDate?Single.NaN:dt.Hour);output.DatePartOfDay=(invalidDate?Single.NaN:(float)Math.Round(dt.AddHours(-3).Hour/6 f));output.InvalidDate=(invalidDate?1.0f:0.0f);// Polar date/time transforms for percent of day & week & year output.CosTimeOfDay=(float)Math.Cos(2*Math.PI*percentOfDay);output.SinTimeOfDay=(float)Math.Sin(2*Math.PI*percentOfDay);output.CosTimeOfWeek=(float)Math.Cos(2*Math.PI*percentOfWeek);output.SinTimeOfWeek=(float)Math.Sin(2*Math.PI*percentOfWeek);output.CosTimeOfMonth=(float)Math.Cos(2*Math.PI*percentOfMonth);output.SinTimeOfMonth=(float)Math.Sin(2*Math.PI*percentOfMonth);output.CosTimeOfYear=(float)Math.Cos(2*Math.PI*percentOfYear);output.SinTimeOfYear=(float)Math.Sin(2*Math.PI*percentOfYear);
``` #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @davidbrownellWork #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add this as a potential feature for Mn. #Resolved

@gvashishthagvashishthaDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean add to the ML.NET plan? Any estimate on how much additional dev time this would take? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the new features is pretty simple. I expect more of the work is in testing. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the input dateTime column a long and not a DateTime? #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long to match a unix timestamp. We will be adding in the ability to use C# DateTime and string in an ISO format in the future, but the initial implementation is just the unix timestamp. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern here is that .NET developers expect to have their date time data using System.DateTime. That is the natural type in .NET. Forcing the user to convert from System.DateTime to a long unix timestamp doesn't seem like a good experience. It would make more sense if the input column was a System.DateTime. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do the conversion yourself here? The user's input column can be DateTime, and then you convert it to a long unix timestamp before passing it to the C++ code? #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to ensure that the process of converting from a string to "Date time" is consistent across frameworks - especially considering that a DateTime in and of itself isn't sufficient in some scenarios, as we lose (potentially important) time zone information in the process. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not asking for string. I'm asking for the ML.NET data type that a user would feed into this transform to be the canonical System.DateTime type and not a long type.

Specifically, this transform should require the input column to be of this type:

/// <summary>
/// The standard date time type. This has representation type of <see cref="DateTime"/>.
/// Note this can have only one possible value, accessible by the singleton static property <see cref="Instance"/>.
/// </summary>
publicsealedclassDateTimeDataViewType:PrimitiveDataViewType

It doesn't make sense for a user to have to convert a DateTime to a long before calling this transform. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically, look at the sample code being checked in under samples:

// Create a small dataset as an IEnumerable.// Future Date - 2025 June 30varsamples=new[]{newDateTimeInput(){Date=1751241600}};

Instead, that sample code should be:

// Create a small dataset as an IEnumerable.varsamples=new[]{newDateTimeInput(){Date=newDateTime(2025,6,30)}};
``` #Resolved

@davidbrownellWorkdavidbrownellWorkDec 6, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - eventually a C# developer will be able to pass System:DateTime, string, or long. #Resolved

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is on our todo list and will be added in in the future. We will eventually be supporting DateTime and string as well, but initial support will just be for long. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
ctx.Writer.Write(data);
}

public void Dispose()

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who calls this Dispose method? We have a problem with tranformers that need to be disposed. See #906 and my comment on #4223

cc @codemzs #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain. I am not manually calling it anywhere. Is it happening during pipeline teardown ok? Or is there more desirable behavior? #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain.

There is no call of it when the pipeline teardown happens. That is why that bug was opened. I'm still not certain on why it was closed. Maybe @codemzs can comment. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

/azp run #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 9, 2019

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

#Resolved

@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

Going to close and reopen the PR to reset the stuck builds. #Resolved

@codecov

codecovBot commented Dec 10, 2019

Copy link
Copy Markdown

Codecov Report

Merging #4521 into master will increase coverage by 0.03%.
The diff coverage is 88.11%.

@@ Coverage Diff @@## master #4521 +/- ##
==========================================
+ Coverage 75.11% 75.15% +0.03% 
==========================================
Files 908 913 +5 Lines 160204 160891 +687 Branches 17254 17292 +38 ==========================================
+ Hits 120340 120911 +571 - Misses 35050 35153 +103 - Partials 4814 4827 +13
FlagCoverage Δ
#Debug75.15% <88.11%> (+0.03%)⬆️
#production70.54% <86.24%> (+0.02%)⬆️
#test90.29% <91.41%> (+0.03%)⬆️
Impacted FilesCoverage Δ
....ML.Tests/Transformers/DateTimeTransformerTests.cs100% <100%> (ø)
...crosoft.ML.Core.Tests/UnitTests/TestEntryPoints.cs98.36% <100%> (ø)⬆️
src/Microsoft.ML.Featurizers/Common.cs19.58% <15%> (ø)
...estFramework/Attributes/NotCentOS7FactAttribute.cs26.08% <26.08%> (ø)
...rc/Microsoft.ML.Featurizers/DateTimeTransformer.cs90.57% <90.57%> (ø)
....ML.AutoML/PipelineSuggesters/PipelineSuggester.cs83.19% <0%> (-3.37%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmDataset.cs74.21% <0%> (-2.12%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmBooster.cs83.8% <0%> (-0.63%)⬇️
...soft.ML.TestFramework/DataPipe/TestDataPipeBase.cs76.08% <0%> (-0.4%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.1% <0%> (-0.16%)⬇️
... and 21 more
#Resolved

@codemzs
codemzs requested a review from natkeDecember 12, 2019 18:27
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
WeekIso = 15,
YearIso = 16,
MonthLabel = 17,
AmPmLabel = 18,

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do AmPm label, would it make sense to also do weekend/weekday? (Often used in feature engineering) #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think thats not a bad idea, but the initial ask we got was just for these columns. We will start with just these and then can add in more if the requirement arises. #Resolved

{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <returns><see cref="DateTimeEstimator"/></returns>
public static DateTimeEstimator DateTimeTransformer(this TransformsCatalog catalog, string inputColumnName, string columnPrefix)

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming of the extension methods are not consistent with the other transformers (I'll leave it to others to judge whether the original scheme makes sense :-), but consistency is probably a more important consideration now)

If you look at the other transforms they mostly have the form Verb-Object e.g. CopyColumns, MapValueToKey etc. This makes sense when you are chaining them together in a pipeline.

Suggestions for this one: FeaturizeDateTime, SplitDateTime, TokenizeDateTime, ExtractDateTime ...

#Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of the ones you listed I would vote FeaturizeDateTime. Anyone have any objections if I rename the extension method to that? #Resolved

case DateTimeEstimator.ColumnsProduced.MonthLabel:
case DateTimeEstimator.ColumnsProduced.AmPmLabel:
case DateTimeEstimator.ColumnsProduced.DayOfWeekLabel:
case DateTimeEstimator.ColumnsProduced.HolidayName:

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usability question, why are we producing multiple output columns of instead of a single vector type column?

The next step after this DateTime transform is:

  • Convert all the INTs/USHORTs/BYTEs columns to Float32
  • One-hot encode the STRING columns ("Friday" to [ 0, 0, 0, 0, 0, 1, 0 ])
  • Concatenate all of these feature to single vector of Float32

Why aren't we just outputting the final vector type column? Then the user can directly use its output, like our other transforms. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a usecase for the individual columns, perhaps just a boolean parameter produceRawColumns, which then produces the individual outputs instead of a singular vector column? #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did it this way just because thats how the original python code works. It splits everything up into different columns and doesn't combine them into a vector. #Resolved

@natkenatkeDec 13, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a single vector is consistent with FeaturizeText. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original code we were bringing to ML.NET returned all individual columns. Because of that, for now, we will be leaving them as individual columns. If we learn that we never need individual columns and only use this as a vector then we can revisit this then. #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 17, 2019

Copy link
Copy Markdown
No pipelines are associated with this pull request.

#Resolved

@michaelgsharp
michaelgsharp merged commit 290e069 into dotnet:masterDec 17, 2019
@michaelgsharp
michaelgsharp deleted the datetime-transformer branch December 17, 2019 21:35
throw new InvalidOperationException($"Unsupported type {type}");
}

// The Native Featurizers do not currently support CentOS7, this method checks the OS and returns true if it is CentOS7.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about RedHat 7?

{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
using (Process process = new Process())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more performant way of doing this check would be to read from the file directly. Starting a new process just to get this info is too heavy-weight IMO.

See https://github.com/dotnet/runtime/blob/6f445d6dc237f59d730e0df47f8630b18887d776/src/installer/managed/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs#L140-L142

@ghostghost locked as resolved and limited conversation to collaborators Mar 19, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@michaelgsharp@natke@justinormont@harishsk@eerhardt@gvashishtha@davidbrownellWork
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

DateTimeTransformer featurizer - #4521

Merged
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer
Dec 17, 2019
Merged

DateTimeTransformer featurizer#4521
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer

Conversation

@michaelgsharp

Copy link
Copy Markdown
Contributor

This change adds in the DateTimeTransformer into the new Featurizers project. It is the first of a series of PR's that will go in. The DateTimeTransformer is implemented in native code, so this is mostly just a wrapper around that with the appropriate entrypoints for NimbusML as well. Since all the new estimator/transformers follow the same patterns, once this one is reviewed and checked in I will create the other PR's.

@michaelgsharp
michaelgsharp requested a review from a teamDecember 4, 2019 18:11
@michaelgsharpmichaelgsharp self-assigned this Dec 4, 2019
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Year = 1, Month, Day, Hour, Minute, Second, AmPm, Hour12, DayOfWeek, DayOfQuarter, DayOfYear,
WeekOfMonth, QuarterOfYear, HalfOfYear, WeekIso, YearIso, MonthLabel, AmPmLabel, DayOfWeekLabel,
HolidayName, IsPaidTimeOff
};

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend explicitly numbering each enum name. This lets us insert new values later without affecting the enum numeric value.

This is more important if the enum is serialized within the model, which I expect you're doing since the model needs to know which features to produce. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. They are now explicitly numbered. The Countries enum (now HolidayList) is also numbered now.


In reply to: 353910235 [](ancestors = 353910235)

/// <param name="catalog">Transform catalog</param>
/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <param name="columnsToDrop">List of columns to drop, if any</param>

@eerhardteerhardtDec 4, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One drawback to using columnsToDrop is that if a user is only interested in the DayOfYear and DayOfWeekLabel, they need to explicitly exclude all the rest. And then if we ever add a new output column in a future release, they would start getting it, and would need to add the new column to the "to drop" list. #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might recommend a bitwise use of an enum (flags) to say which columns to include.

Default would be DateTimeTransformerEstimator.ColumnsProduced.All = 0xFFFFFFFFFFFFFFFF (uint64).

Then users can bitwise or/and-not to select the columns they want to keep/drop.

Example usage

All output except DayOfYear: (subtractive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.All&~DateTimeTransformerEstimator.ColumnsProduced.DayOfYear)

Only DayOfYear and DayOfWeekLabel: (additive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.DayOfYear|DateTimeTransformerEstimator.ColumnsProduced.DayOfWeekLabel)

#Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, there's a [Flags] attribute in C# -- https://docs.microsoft.com/en-us/dotnet/api/system.flagsattribute?view=netcore-3.0

[Flags]publicenumColumnsProduced:uint64{Year=1<<0,Month=1<<1,Day=1<<2,Hour=1<<3,WeekOfMonth=1<<4,
...}

#Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a way you think would be better? We had also thought about not even including that ability here and just letting the user use the DropColumns transformer to remove any columns they didn't want. That would keep this code cleaner and not duplicate that type of functionality. Thoughts on that?


In reply to: 353911746 [](ancestors = 353911746)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think that doing flags is a great idea personally. I think enums are more clear (if more verbose) than flags usually. As I mentioned to Eric in this same thread though, we can remove this capability from here and just let the user use the DropColumn transformer to remove anything they dont want. What is your opinion on that?


In reply to: 353968394 [](ancestors = 353968394)

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect this featurizer isn't aware of which columns are being requested of it

Maybe it should be made aware? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the usability of the flag method.

There is a user need of not including all outputs as overfitting occurs, mainly on the larger time scales, like year number. Including the year in a classification or regression model often leads to the model memorizing the label value specifically for a date range.

For instance the model will memorize that 2017-03-01 to 2018-01-05, the label value was high. This information is not valuable to memorize when predicting future data. Instead we want to ensure it memorizes that traffic volumes (the label) are higher on weekends (extracting useful patterns).

Hence it is nice to encourage users (as part of the timedate featurizer API itself) to think about which columns to include/exclude. This is as opposed to a user needing a separate DropColumns step in their pipeline. #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So so far we have had 3 suggestions.
1 - Dont support dropping columns as a native part of this transformer.
2 - Use Enums.
3 - Use Flags.

Can we finalize on the decision? I personally agree with Harish that since we already have a drop columns transformer that we can just use that and not have that functionality be present here. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend a way to proactively select the output columns to create during the setup of the transform. Instead of dropping later, just don't create them.

Generally, we should assume users won't think (or know) to drop later. For usability, placing it in the constructor exposes users to the idea, and forces them to consider which columns will be beneficial. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, since this functionality already exists inside of ML.NET as a separate transformer we will be leaving this as is. We can revisit this later as the need arises. Even if this transformer produces all the columns, if no downstream transformers request them then they will never be realized. It shouldn't cause any performance impact this way. #Resolved

HolidayName, IsPaidTimeOff
};

public enum Countries : byte

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want a UINT given that there are ~200 current countries (and they keep changing), and in the future we may a more specific break down, like UnitedStatesBankingHolidays vs. UnitedStatesFederalHolidays

Suggested change
publicenumCountries:byte
publicenumCountries:uint
``` #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point. I have changed the enum to be uint as suggested.


In reply to: 353916304 [](ancestors = 353916304)

public ColumnsProduced[] ColumnsToDrop;

[Argument(ArgumentType.AtMostOnce, HelpText = "Country to get holidays for. Defaults to none if not passed", Name = "Country", ShortName = "ctry", SortOrder = 4)]
public Countries Country = Countries.None;

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Countries may be a bit restrictive of a term for the future. Perhaps name as HolidayList.

Perhaps in the future we'll want UnitedStatesCaliforniaEducationHolidays (see: list). We may also want more specific lists, like UnitedStatesMajorHolidays. Or localized lists like GermanyBavaria (see: list).

Or perhaps in the future we'll want to version the holiday list Germany2019 (as holidays are declared and dropped). Versioning helps old models run in current versions of ML.NET. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I have renamed it to HolidayList, but we can revisit it if needed in the future as well.


In reply to: 353920532 [](ancestors = 353920532)

public int DTCYearIso { get; set; }
public string DTCMonthLabel { get; set; }
public string DTCAmPmLabel { get; set; }
public string DTCDayOfWeekLabel { get; set; }

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be needed to be added to the code being wrapped here, but when featurizing DateTime fields, I prefer polar (Sin/Cos) transforms (along with the others listed).

Clocks and times/dates are cyclic. The current feature set doesn't include this fact.

The main gain of polar date transforms is that it places times like 11:50PM and 12:10AM right next to each other (both mapping to ~0.999 in the cosine transform of percent of day). This lets a model make a decision boundary at CosTimeOfDay > 0.9 to treat these two times as very similar.

Contrast this to the DTCHour feature which will map these to 23 and 0 (issue: non-continuous range, and not possible to group both the 0 and 23 in a single split point, which to a human is similar).

Info:

Code example I use for DateTime feature engineering:

DateTimedt;varinvalidDate=!DateTime.TryParse(input.Date,outdt);varpercentOfDay=(invalidDate?Single.NaN:(float)(newTimeSpan(dt.Hour,dt.Minute,0).TotalMinutes)/1440 f);varpercentOfWeek=(invalidDate?Single.NaN:((float)dt.DayOfWeek+percentOfDay)/7 f);varpercentOfMonth=(invalidDate?Single.NaN:(float)(dt.Day+percentOfDay)/(float)DateTime.DaysInMonth(dt.Year,dt.Month));varpercentOfYear=(invalidDate?Single.NaN:(float)((dt.DayOfYear+percentOfDay)/(newDateTime(dt.Year+1,1,1)-newDateTime(dt.Year,1,1)).TotalDays));// Time features like the current onesoutput.DateWeekday=(invalidDate?Single.NaN:(float)dt.DayOfWeek);output.DateMonth=(invalidDate?Single.NaN:(float)dt.Month);output.DateHour=(invalidDate?Single.NaN:dt.Hour);output.DatePartOfDay=(invalidDate?Single.NaN:(float)Math.Round(dt.AddHours(-3).Hour/6 f));output.InvalidDate=(invalidDate?1.0f:0.0f);// Polar date/time transforms for percent of day & week & year output.CosTimeOfDay=(float)Math.Cos(2*Math.PI*percentOfDay);output.SinTimeOfDay=(float)Math.Sin(2*Math.PI*percentOfDay);output.CosTimeOfWeek=(float)Math.Cos(2*Math.PI*percentOfWeek);output.SinTimeOfWeek=(float)Math.Sin(2*Math.PI*percentOfWeek);output.CosTimeOfMonth=(float)Math.Cos(2*Math.PI*percentOfMonth);output.SinTimeOfMonth=(float)Math.Sin(2*Math.PI*percentOfMonth);output.CosTimeOfYear=(float)Math.Cos(2*Math.PI*percentOfYear);output.SinTimeOfYear=(float)Math.Sin(2*Math.PI*percentOfYear);
``` #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @davidbrownellWork #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add this as a potential feature for Mn. #Resolved

@gvashishthagvashishthaDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean add to the ML.NET plan? Any estimate on how much additional dev time this would take? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the new features is pretty simple. I expect more of the work is in testing. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the input dateTime column a long and not a DateTime? #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long to match a unix timestamp. We will be adding in the ability to use C# DateTime and string in an ISO format in the future, but the initial implementation is just the unix timestamp. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern here is that .NET developers expect to have their date time data using System.DateTime. That is the natural type in .NET. Forcing the user to convert from System.DateTime to a long unix timestamp doesn't seem like a good experience. It would make more sense if the input column was a System.DateTime. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do the conversion yourself here? The user's input column can be DateTime, and then you convert it to a long unix timestamp before passing it to the C++ code? #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to ensure that the process of converting from a string to "Date time" is consistent across frameworks - especially considering that a DateTime in and of itself isn't sufficient in some scenarios, as we lose (potentially important) time zone information in the process. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not asking for string. I'm asking for the ML.NET data type that a user would feed into this transform to be the canonical System.DateTime type and not a long type.

Specifically, this transform should require the input column to be of this type:

/// <summary>
/// The standard date time type. This has representation type of <see cref="DateTime"/>.
/// Note this can have only one possible value, accessible by the singleton static property <see cref="Instance"/>.
/// </summary>
publicsealedclassDateTimeDataViewType:PrimitiveDataViewType

It doesn't make sense for a user to have to convert a DateTime to a long before calling this transform. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically, look at the sample code being checked in under samples:

// Create a small dataset as an IEnumerable.// Future Date - 2025 June 30varsamples=new[]{newDateTimeInput(){Date=1751241600}};

Instead, that sample code should be:

// Create a small dataset as an IEnumerable.varsamples=new[]{newDateTimeInput(){Date=newDateTime(2025,6,30)}};
``` #Resolved

@davidbrownellWorkdavidbrownellWorkDec 6, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - eventually a C# developer will be able to pass System:DateTime, string, or long. #Resolved

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is on our todo list and will be added in in the future. We will eventually be supporting DateTime and string as well, but initial support will just be for long. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
ctx.Writer.Write(data);
}

public void Dispose()

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who calls this Dispose method? We have a problem with tranformers that need to be disposed. See #906 and my comment on #4223

cc @codemzs #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain. I am not manually calling it anywhere. Is it happening during pipeline teardown ok? Or is there more desirable behavior? #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain.

There is no call of it when the pipeline teardown happens. That is why that bug was opened. I'm still not certain on why it was closed. Maybe @codemzs can comment. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

/azp run #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 9, 2019

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

#Resolved

@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

Going to close and reopen the PR to reset the stuck builds. #Resolved

@codecov

codecovBot commented Dec 10, 2019

Copy link
Copy Markdown

Codecov Report

Merging #4521 into master will increase coverage by 0.03%.
The diff coverage is 88.11%.

@@ Coverage Diff @@## master #4521 +/- ##
==========================================
+ Coverage 75.11% 75.15% +0.03% 
==========================================
Files 908 913 +5 Lines 160204 160891 +687 Branches 17254 17292 +38 ==========================================
+ Hits 120340 120911 +571 - Misses 35050 35153 +103 - Partials 4814 4827 +13
FlagCoverage Δ
#Debug75.15% <88.11%> (+0.03%)⬆️
#production70.54% <86.24%> (+0.02%)⬆️
#test90.29% <91.41%> (+0.03%)⬆️
Impacted FilesCoverage Δ
....ML.Tests/Transformers/DateTimeTransformerTests.cs100% <100%> (ø)
...crosoft.ML.Core.Tests/UnitTests/TestEntryPoints.cs98.36% <100%> (ø)⬆️
src/Microsoft.ML.Featurizers/Common.cs19.58% <15%> (ø)
...estFramework/Attributes/NotCentOS7FactAttribute.cs26.08% <26.08%> (ø)
...rc/Microsoft.ML.Featurizers/DateTimeTransformer.cs90.57% <90.57%> (ø)
....ML.AutoML/PipelineSuggesters/PipelineSuggester.cs83.19% <0%> (-3.37%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmDataset.cs74.21% <0%> (-2.12%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmBooster.cs83.8% <0%> (-0.63%)⬇️
...soft.ML.TestFramework/DataPipe/TestDataPipeBase.cs76.08% <0%> (-0.4%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.1% <0%> (-0.16%)⬇️
... and 21 more
#Resolved

@codemzs
codemzs requested a review from natkeDecember 12, 2019 18:27
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
WeekIso = 15,
YearIso = 16,
MonthLabel = 17,
AmPmLabel = 18,

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do AmPm label, would it make sense to also do weekend/weekday? (Often used in feature engineering) #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think thats not a bad idea, but the initial ask we got was just for these columns. We will start with just these and then can add in more if the requirement arises. #Resolved

{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <returns><see cref="DateTimeEstimator"/></returns>
public static DateTimeEstimator DateTimeTransformer(this TransformsCatalog catalog, string inputColumnName, string columnPrefix)

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming of the extension methods are not consistent with the other transformers (I'll leave it to others to judge whether the original scheme makes sense :-), but consistency is probably a more important consideration now)

If you look at the other transforms they mostly have the form Verb-Object e.g. CopyColumns, MapValueToKey etc. This makes sense when you are chaining them together in a pipeline.

Suggestions for this one: FeaturizeDateTime, SplitDateTime, TokenizeDateTime, ExtractDateTime ...

#Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of the ones you listed I would vote FeaturizeDateTime. Anyone have any objections if I rename the extension method to that? #Resolved

case DateTimeEstimator.ColumnsProduced.MonthLabel:
case DateTimeEstimator.ColumnsProduced.AmPmLabel:
case DateTimeEstimator.ColumnsProduced.DayOfWeekLabel:
case DateTimeEstimator.ColumnsProduced.HolidayName:

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usability question, why are we producing multiple output columns of instead of a single vector type column?

The next step after this DateTime transform is:

  • Convert all the INTs/USHORTs/BYTEs columns to Float32
  • One-hot encode the STRING columns ("Friday" to [ 0, 0, 0, 0, 0, 1, 0 ])
  • Concatenate all of these feature to single vector of Float32

Why aren't we just outputting the final vector type column? Then the user can directly use its output, like our other transforms. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a usecase for the individual columns, perhaps just a boolean parameter produceRawColumns, which then produces the individual outputs instead of a singular vector column? #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did it this way just because thats how the original python code works. It splits everything up into different columns and doesn't combine them into a vector. #Resolved

@natkenatkeDec 13, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a single vector is consistent with FeaturizeText. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original code we were bringing to ML.NET returned all individual columns. Because of that, for now, we will be leaving them as individual columns. If we learn that we never need individual columns and only use this as a vector then we can revisit this then. #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 17, 2019

Copy link
Copy Markdown
No pipelines are associated with this pull request.

#Resolved

@michaelgsharp
michaelgsharp merged commit 290e069 into dotnet:masterDec 17, 2019
@michaelgsharp
michaelgsharp deleted the datetime-transformer branch December 17, 2019 21:35
throw new InvalidOperationException($"Unsupported type {type}");
}

// The Native Featurizers do not currently support CentOS7, this method checks the OS and returns true if it is CentOS7.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about RedHat 7?

{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
using (Process process = new Process())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more performant way of doing this check would be to read from the file directly. Starting a new process just to get this info is too heavy-weight IMO.

See https://github.com/dotnet/runtime/blob/6f445d6dc237f59d730e0df47f8630b18887d776/src/installer/managed/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs#L140-L142

@ghostghost locked as resolved and limited conversation to collaborators Mar 19, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@michaelgsharp@natke@justinormont@harishsk@eerhardt@gvashishtha@davidbrownellWork
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

DateTimeTransformer featurizer - #4521

Merged
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer
Dec 17, 2019
Merged

DateTimeTransformer featurizer#4521
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer

Conversation

@michaelgsharp

Copy link
Copy Markdown
Contributor

This change adds in the DateTimeTransformer into the new Featurizers project. It is the first of a series of PR's that will go in. The DateTimeTransformer is implemented in native code, so this is mostly just a wrapper around that with the appropriate entrypoints for NimbusML as well. Since all the new estimator/transformers follow the same patterns, once this one is reviewed and checked in I will create the other PR's.

@michaelgsharp
michaelgsharp requested a review from a teamDecember 4, 2019 18:11
@michaelgsharpmichaelgsharp self-assigned this Dec 4, 2019
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Year = 1, Month, Day, Hour, Minute, Second, AmPm, Hour12, DayOfWeek, DayOfQuarter, DayOfYear,
WeekOfMonth, QuarterOfYear, HalfOfYear, WeekIso, YearIso, MonthLabel, AmPmLabel, DayOfWeekLabel,
HolidayName, IsPaidTimeOff
};

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend explicitly numbering each enum name. This lets us insert new values later without affecting the enum numeric value.

This is more important if the enum is serialized within the model, which I expect you're doing since the model needs to know which features to produce. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. They are now explicitly numbered. The Countries enum (now HolidayList) is also numbered now.


In reply to: 353910235 [](ancestors = 353910235)

/// <param name="catalog">Transform catalog</param>
/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <param name="columnsToDrop">List of columns to drop, if any</param>

@eerhardteerhardtDec 4, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One drawback to using columnsToDrop is that if a user is only interested in the DayOfYear and DayOfWeekLabel, they need to explicitly exclude all the rest. And then if we ever add a new output column in a future release, they would start getting it, and would need to add the new column to the "to drop" list. #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might recommend a bitwise use of an enum (flags) to say which columns to include.

Default would be DateTimeTransformerEstimator.ColumnsProduced.All = 0xFFFFFFFFFFFFFFFF (uint64).

Then users can bitwise or/and-not to select the columns they want to keep/drop.

Example usage

All output except DayOfYear: (subtractive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.All&~DateTimeTransformerEstimator.ColumnsProduced.DayOfYear)

Only DayOfYear and DayOfWeekLabel: (additive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.DayOfYear|DateTimeTransformerEstimator.ColumnsProduced.DayOfWeekLabel)

#Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, there's a [Flags] attribute in C# -- https://docs.microsoft.com/en-us/dotnet/api/system.flagsattribute?view=netcore-3.0

[Flags]publicenumColumnsProduced:uint64{Year=1<<0,Month=1<<1,Day=1<<2,Hour=1<<3,WeekOfMonth=1<<4,
...}

#Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a way you think would be better? We had also thought about not even including that ability here and just letting the user use the DropColumns transformer to remove any columns they didn't want. That would keep this code cleaner and not duplicate that type of functionality. Thoughts on that?


In reply to: 353911746 [](ancestors = 353911746)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think that doing flags is a great idea personally. I think enums are more clear (if more verbose) than flags usually. As I mentioned to Eric in this same thread though, we can remove this capability from here and just let the user use the DropColumn transformer to remove anything they dont want. What is your opinion on that?


In reply to: 353968394 [](ancestors = 353968394)

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect this featurizer isn't aware of which columns are being requested of it

Maybe it should be made aware? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the usability of the flag method.

There is a user need of not including all outputs as overfitting occurs, mainly on the larger time scales, like year number. Including the year in a classification or regression model often leads to the model memorizing the label value specifically for a date range.

For instance the model will memorize that 2017-03-01 to 2018-01-05, the label value was high. This information is not valuable to memorize when predicting future data. Instead we want to ensure it memorizes that traffic volumes (the label) are higher on weekends (extracting useful patterns).

Hence it is nice to encourage users (as part of the timedate featurizer API itself) to think about which columns to include/exclude. This is as opposed to a user needing a separate DropColumns step in their pipeline. #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So so far we have had 3 suggestions.
1 - Dont support dropping columns as a native part of this transformer.
2 - Use Enums.
3 - Use Flags.

Can we finalize on the decision? I personally agree with Harish that since we already have a drop columns transformer that we can just use that and not have that functionality be present here. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend a way to proactively select the output columns to create during the setup of the transform. Instead of dropping later, just don't create them.

Generally, we should assume users won't think (or know) to drop later. For usability, placing it in the constructor exposes users to the idea, and forces them to consider which columns will be beneficial. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, since this functionality already exists inside of ML.NET as a separate transformer we will be leaving this as is. We can revisit this later as the need arises. Even if this transformer produces all the columns, if no downstream transformers request them then they will never be realized. It shouldn't cause any performance impact this way. #Resolved

HolidayName, IsPaidTimeOff
};

public enum Countries : byte

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want a UINT given that there are ~200 current countries (and they keep changing), and in the future we may a more specific break down, like UnitedStatesBankingHolidays vs. UnitedStatesFederalHolidays

Suggested change
publicenumCountries:byte
publicenumCountries:uint
``` #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point. I have changed the enum to be uint as suggested.


In reply to: 353916304 [](ancestors = 353916304)

public ColumnsProduced[] ColumnsToDrop;

[Argument(ArgumentType.AtMostOnce, HelpText = "Country to get holidays for. Defaults to none if not passed", Name = "Country", ShortName = "ctry", SortOrder = 4)]
public Countries Country = Countries.None;

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Countries may be a bit restrictive of a term for the future. Perhaps name as HolidayList.

Perhaps in the future we'll want UnitedStatesCaliforniaEducationHolidays (see: list). We may also want more specific lists, like UnitedStatesMajorHolidays. Or localized lists like GermanyBavaria (see: list).

Or perhaps in the future we'll want to version the holiday list Germany2019 (as holidays are declared and dropped). Versioning helps old models run in current versions of ML.NET. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I have renamed it to HolidayList, but we can revisit it if needed in the future as well.


In reply to: 353920532 [](ancestors = 353920532)

public int DTCYearIso { get; set; }
public string DTCMonthLabel { get; set; }
public string DTCAmPmLabel { get; set; }
public string DTCDayOfWeekLabel { get; set; }

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be needed to be added to the code being wrapped here, but when featurizing DateTime fields, I prefer polar (Sin/Cos) transforms (along with the others listed).

Clocks and times/dates are cyclic. The current feature set doesn't include this fact.

The main gain of polar date transforms is that it places times like 11:50PM and 12:10AM right next to each other (both mapping to ~0.999 in the cosine transform of percent of day). This lets a model make a decision boundary at CosTimeOfDay > 0.9 to treat these two times as very similar.

Contrast this to the DTCHour feature which will map these to 23 and 0 (issue: non-continuous range, and not possible to group both the 0 and 23 in a single split point, which to a human is similar).

Info:

Code example I use for DateTime feature engineering:

DateTimedt;varinvalidDate=!DateTime.TryParse(input.Date,outdt);varpercentOfDay=(invalidDate?Single.NaN:(float)(newTimeSpan(dt.Hour,dt.Minute,0).TotalMinutes)/1440 f);varpercentOfWeek=(invalidDate?Single.NaN:((float)dt.DayOfWeek+percentOfDay)/7 f);varpercentOfMonth=(invalidDate?Single.NaN:(float)(dt.Day+percentOfDay)/(float)DateTime.DaysInMonth(dt.Year,dt.Month));varpercentOfYear=(invalidDate?Single.NaN:(float)((dt.DayOfYear+percentOfDay)/(newDateTime(dt.Year+1,1,1)-newDateTime(dt.Year,1,1)).TotalDays));// Time features like the current onesoutput.DateWeekday=(invalidDate?Single.NaN:(float)dt.DayOfWeek);output.DateMonth=(invalidDate?Single.NaN:(float)dt.Month);output.DateHour=(invalidDate?Single.NaN:dt.Hour);output.DatePartOfDay=(invalidDate?Single.NaN:(float)Math.Round(dt.AddHours(-3).Hour/6 f));output.InvalidDate=(invalidDate?1.0f:0.0f);// Polar date/time transforms for percent of day & week & year output.CosTimeOfDay=(float)Math.Cos(2*Math.PI*percentOfDay);output.SinTimeOfDay=(float)Math.Sin(2*Math.PI*percentOfDay);output.CosTimeOfWeek=(float)Math.Cos(2*Math.PI*percentOfWeek);output.SinTimeOfWeek=(float)Math.Sin(2*Math.PI*percentOfWeek);output.CosTimeOfMonth=(float)Math.Cos(2*Math.PI*percentOfMonth);output.SinTimeOfMonth=(float)Math.Sin(2*Math.PI*percentOfMonth);output.CosTimeOfYear=(float)Math.Cos(2*Math.PI*percentOfYear);output.SinTimeOfYear=(float)Math.Sin(2*Math.PI*percentOfYear);
``` #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @davidbrownellWork #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add this as a potential feature for Mn. #Resolved

@gvashishthagvashishthaDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean add to the ML.NET plan? Any estimate on how much additional dev time this would take? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the new features is pretty simple. I expect more of the work is in testing. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the input dateTime column a long and not a DateTime? #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long to match a unix timestamp. We will be adding in the ability to use C# DateTime and string in an ISO format in the future, but the initial implementation is just the unix timestamp. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern here is that .NET developers expect to have their date time data using System.DateTime. That is the natural type in .NET. Forcing the user to convert from System.DateTime to a long unix timestamp doesn't seem like a good experience. It would make more sense if the input column was a System.DateTime. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do the conversion yourself here? The user's input column can be DateTime, and then you convert it to a long unix timestamp before passing it to the C++ code? #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to ensure that the process of converting from a string to "Date time" is consistent across frameworks - especially considering that a DateTime in and of itself isn't sufficient in some scenarios, as we lose (potentially important) time zone information in the process. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not asking for string. I'm asking for the ML.NET data type that a user would feed into this transform to be the canonical System.DateTime type and not a long type.

Specifically, this transform should require the input column to be of this type:

/// <summary>
/// The standard date time type. This has representation type of <see cref="DateTime"/>.
/// Note this can have only one possible value, accessible by the singleton static property <see cref="Instance"/>.
/// </summary>
publicsealedclassDateTimeDataViewType:PrimitiveDataViewType

It doesn't make sense for a user to have to convert a DateTime to a long before calling this transform. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically, look at the sample code being checked in under samples:

// Create a small dataset as an IEnumerable.// Future Date - 2025 June 30varsamples=new[]{newDateTimeInput(){Date=1751241600}};

Instead, that sample code should be:

// Create a small dataset as an IEnumerable.varsamples=new[]{newDateTimeInput(){Date=newDateTime(2025,6,30)}};
``` #Resolved

@davidbrownellWorkdavidbrownellWorkDec 6, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - eventually a C# developer will be able to pass System:DateTime, string, or long. #Resolved

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is on our todo list and will be added in in the future. We will eventually be supporting DateTime and string as well, but initial support will just be for long. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
ctx.Writer.Write(data);
}

public void Dispose()

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who calls this Dispose method? We have a problem with tranformers that need to be disposed. See #906 and my comment on #4223

cc @codemzs #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain. I am not manually calling it anywhere. Is it happening during pipeline teardown ok? Or is there more desirable behavior? #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain.

There is no call of it when the pipeline teardown happens. That is why that bug was opened. I'm still not certain on why it was closed. Maybe @codemzs can comment. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

/azp run #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 9, 2019

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

#Resolved

@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

Going to close and reopen the PR to reset the stuck builds. #Resolved

@codecov

codecovBot commented Dec 10, 2019

Copy link
Copy Markdown

Codecov Report

Merging #4521 into master will increase coverage by 0.03%.
The diff coverage is 88.11%.

@@ Coverage Diff @@## master #4521 +/- ##
==========================================
+ Coverage 75.11% 75.15% +0.03% 
==========================================
Files 908 913 +5 Lines 160204 160891 +687 Branches 17254 17292 +38 ==========================================
+ Hits 120340 120911 +571 - Misses 35050 35153 +103 - Partials 4814 4827 +13
FlagCoverage Δ
#Debug75.15% <88.11%> (+0.03%)⬆️
#production70.54% <86.24%> (+0.02%)⬆️
#test90.29% <91.41%> (+0.03%)⬆️
Impacted FilesCoverage Δ
....ML.Tests/Transformers/DateTimeTransformerTests.cs100% <100%> (ø)
...crosoft.ML.Core.Tests/UnitTests/TestEntryPoints.cs98.36% <100%> (ø)⬆️
src/Microsoft.ML.Featurizers/Common.cs19.58% <15%> (ø)
...estFramework/Attributes/NotCentOS7FactAttribute.cs26.08% <26.08%> (ø)
...rc/Microsoft.ML.Featurizers/DateTimeTransformer.cs90.57% <90.57%> (ø)
....ML.AutoML/PipelineSuggesters/PipelineSuggester.cs83.19% <0%> (-3.37%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmDataset.cs74.21% <0%> (-2.12%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmBooster.cs83.8% <0%> (-0.63%)⬇️
...soft.ML.TestFramework/DataPipe/TestDataPipeBase.cs76.08% <0%> (-0.4%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.1% <0%> (-0.16%)⬇️
... and 21 more
#Resolved

@codemzs
codemzs requested a review from natkeDecember 12, 2019 18:27
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
WeekIso = 15,
YearIso = 16,
MonthLabel = 17,
AmPmLabel = 18,

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do AmPm label, would it make sense to also do weekend/weekday? (Often used in feature engineering) #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think thats not a bad idea, but the initial ask we got was just for these columns. We will start with just these and then can add in more if the requirement arises. #Resolved

{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <returns><see cref="DateTimeEstimator"/></returns>
public static DateTimeEstimator DateTimeTransformer(this TransformsCatalog catalog, string inputColumnName, string columnPrefix)

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming of the extension methods are not consistent with the other transformers (I'll leave it to others to judge whether the original scheme makes sense :-), but consistency is probably a more important consideration now)

If you look at the other transforms they mostly have the form Verb-Object e.g. CopyColumns, MapValueToKey etc. This makes sense when you are chaining them together in a pipeline.

Suggestions for this one: FeaturizeDateTime, SplitDateTime, TokenizeDateTime, ExtractDateTime ...

#Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of the ones you listed I would vote FeaturizeDateTime. Anyone have any objections if I rename the extension method to that? #Resolved

case DateTimeEstimator.ColumnsProduced.MonthLabel:
case DateTimeEstimator.ColumnsProduced.AmPmLabel:
case DateTimeEstimator.ColumnsProduced.DayOfWeekLabel:
case DateTimeEstimator.ColumnsProduced.HolidayName:

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usability question, why are we producing multiple output columns of instead of a single vector type column?

The next step after this DateTime transform is:

  • Convert all the INTs/USHORTs/BYTEs columns to Float32
  • One-hot encode the STRING columns ("Friday" to [ 0, 0, 0, 0, 0, 1, 0 ])
  • Concatenate all of these feature to single vector of Float32

Why aren't we just outputting the final vector type column? Then the user can directly use its output, like our other transforms. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a usecase for the individual columns, perhaps just a boolean parameter produceRawColumns, which then produces the individual outputs instead of a singular vector column? #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did it this way just because thats how the original python code works. It splits everything up into different columns and doesn't combine them into a vector. #Resolved

@natkenatkeDec 13, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a single vector is consistent with FeaturizeText. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original code we were bringing to ML.NET returned all individual columns. Because of that, for now, we will be leaving them as individual columns. If we learn that we never need individual columns and only use this as a vector then we can revisit this then. #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 17, 2019

Copy link
Copy Markdown
No pipelines are associated with this pull request.

#Resolved

@michaelgsharp
michaelgsharp merged commit 290e069 into dotnet:masterDec 17, 2019
@michaelgsharp
michaelgsharp deleted the datetime-transformer branch December 17, 2019 21:35
throw new InvalidOperationException($"Unsupported type {type}");
}

// The Native Featurizers do not currently support CentOS7, this method checks the OS and returns true if it is CentOS7.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about RedHat 7?

{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
using (Process process = new Process())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more performant way of doing this check would be to read from the file directly. Starting a new process just to get this info is too heavy-weight IMO.

See https://github.com/dotnet/runtime/blob/6f445d6dc237f59d730e0df47f8630b18887d776/src/installer/managed/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs#L140-L142

@ghostghost locked as resolved and limited conversation to collaborators Mar 19, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@michaelgsharp@natke@justinormont@harishsk@eerhardt@gvashishtha@davidbrownellWork
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

DateTimeTransformer featurizer - #4521

Merged
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer
Dec 17, 2019
Merged

DateTimeTransformer featurizer#4521
michaelgsharp merged 8 commits into
dotnet:masterfrom
michaelgsharp:datetime-transformer

Conversation

@michaelgsharp

Copy link
Copy Markdown
Contributor

This change adds in the DateTimeTransformer into the new Featurizers project. It is the first of a series of PR's that will go in. The DateTimeTransformer is implemented in native code, so this is mostly just a wrapper around that with the appropriate entrypoints for NimbusML as well. Since all the new estimator/transformers follow the same patterns, once this one is reviewed and checked in I will create the other PR's.

@michaelgsharp
michaelgsharp requested a review from a teamDecember 4, 2019 18:11
@michaelgsharpmichaelgsharp self-assigned this Dec 4, 2019
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Year = 1, Month, Day, Hour, Minute, Second, AmPm, Hour12, DayOfWeek, DayOfQuarter, DayOfYear,
WeekOfMonth, QuarterOfYear, HalfOfYear, WeekIso, YearIso, MonthLabel, AmPmLabel, DayOfWeekLabel,
HolidayName, IsPaidTimeOff
};

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend explicitly numbering each enum name. This lets us insert new values later without affecting the enum numeric value.

This is more important if the enum is serialized within the model, which I expect you're doing since the model needs to know which features to produce. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. They are now explicitly numbered. The Countries enum (now HolidayList) is also numbered now.


In reply to: 353910235 [](ancestors = 353910235)

/// <param name="catalog">Transform catalog</param>
/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <param name="columnsToDrop">List of columns to drop, if any</param>

@eerhardteerhardtDec 4, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One drawback to using columnsToDrop is that if a user is only interested in the DayOfYear and DayOfWeekLabel, they need to explicitly exclude all the rest. And then if we ever add a new output column in a future release, they would start getting it, and would need to add the new column to the "to drop" list. #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might recommend a bitwise use of an enum (flags) to say which columns to include.

Default would be DateTimeTransformerEstimator.ColumnsProduced.All = 0xFFFFFFFFFFFFFFFF (uint64).

Then users can bitwise or/and-not to select the columns they want to keep/drop.

Example usage

All output except DayOfYear: (subtractive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.All&~DateTimeTransformerEstimator.ColumnsProduced.DayOfYear)

Only DayOfYear and DayOfWeekLabel: (additive column selection)

varpipeline=mlContext.Transforms.DateTimeTransformer("Date","DTC",DateTimeTransformerEstimator.ColumnsProduced.DayOfYear|DateTimeTransformerEstimator.ColumnsProduced.DayOfWeekLabel)

#Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, there's a [Flags] attribute in C# -- https://docs.microsoft.com/en-us/dotnet/api/system.flagsattribute?view=netcore-3.0

[Flags]publicenumColumnsProduced:uint64{Year=1<<0,Month=1<<1,Day=1<<2,Hour=1<<3,WeekOfMonth=1<<4,
...}

#Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a way you think would be better? We had also thought about not even including that ability here and just letting the user use the DropColumns transformer to remove any columns they didn't want. That would keep this code cleaner and not duplicate that type of functionality. Thoughts on that?


In reply to: 353911746 [](ancestors = 353911746)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think that doing flags is a great idea personally. I think enums are more clear (if more verbose) than flags usually. As I mentioned to Eric in this same thread though, we can remove this capability from here and just let the user use the DropColumn transformer to remove anything they dont want. What is your opinion on that?


In reply to: 353968394 [](ancestors = 353968394)

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect this featurizer isn't aware of which columns are being requested of it

Maybe it should be made aware? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the usability of the flag method.

There is a user need of not including all outputs as overfitting occurs, mainly on the larger time scales, like year number. Including the year in a classification or regression model often leads to the model memorizing the label value specifically for a date range.

For instance the model will memorize that 2017-03-01 to 2018-01-05, the label value was high. This information is not valuable to memorize when predicting future data. Instead we want to ensure it memorizes that traffic volumes (the label) are higher on weekends (extracting useful patterns).

Hence it is nice to encourage users (as part of the timedate featurizer API itself) to think about which columns to include/exclude. This is as opposed to a user needing a separate DropColumns step in their pipeline. #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So so far we have had 3 suggestions.
1 - Dont support dropping columns as a native part of this transformer.
2 - Use Enums.
3 - Use Flags.

Can we finalize on the decision? I personally agree with Harish that since we already have a drop columns transformer that we can just use that and not have that functionality be present here. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend a way to proactively select the output columns to create during the setup of the transform. Instead of dropping later, just don't create them.

Generally, we should assume users won't think (or know) to drop later. For usability, placing it in the constructor exposes users to the idea, and forces them to consider which columns will be beneficial. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, since this functionality already exists inside of ML.NET as a separate transformer we will be leaving this as is. We can revisit this later as the need arises. Even if this transformer produces all the columns, if no downstream transformers request them then they will never be realized. It shouldn't cause any performance impact this way. #Resolved

HolidayName, IsPaidTimeOff
};

public enum Countries : byte

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want a UINT given that there are ~200 current countries (and they keep changing), and in the future we may a more specific break down, like UnitedStatesBankingHolidays vs. UnitedStatesFederalHolidays

Suggested change
publicenumCountries:byte
publicenumCountries:uint
``` #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point. I have changed the enum to be uint as suggested.


In reply to: 353916304 [](ancestors = 353916304)

public ColumnsProduced[] ColumnsToDrop;

[Argument(ArgumentType.AtMostOnce, HelpText = "Country to get holidays for. Defaults to none if not passed", Name = "Country", ShortName = "ctry", SortOrder = 4)]
public Countries Country = Countries.None;

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Countries may be a bit restrictive of a term for the future. Perhaps name as HolidayList.

Perhaps in the future we'll want UnitedStatesCaliforniaEducationHolidays (see: list). We may also want more specific lists, like UnitedStatesMajorHolidays. Or localized lists like GermanyBavaria (see: list).

Or perhaps in the future we'll want to version the holiday list Germany2019 (as holidays are declared and dropped). Versioning helps old models run in current versions of ML.NET. #Resolved

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I have renamed it to HolidayList, but we can revisit it if needed in the future as well.


In reply to: 353920532 [](ancestors = 353920532)

public int DTCYearIso { get; set; }
public string DTCMonthLabel { get; set; }
public string DTCAmPmLabel { get; set; }
public string DTCDayOfWeekLabel { get; set; }

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be needed to be added to the code being wrapped here, but when featurizing DateTime fields, I prefer polar (Sin/Cos) transforms (along with the others listed).

Clocks and times/dates are cyclic. The current feature set doesn't include this fact.

The main gain of polar date transforms is that it places times like 11:50PM and 12:10AM right next to each other (both mapping to ~0.999 in the cosine transform of percent of day). This lets a model make a decision boundary at CosTimeOfDay > 0.9 to treat these two times as very similar.

Contrast this to the DTCHour feature which will map these to 23 and 0 (issue: non-continuous range, and not possible to group both the 0 and 23 in a single split point, which to a human is similar).

Info:

Code example I use for DateTime feature engineering:

DateTimedt;varinvalidDate=!DateTime.TryParse(input.Date,outdt);varpercentOfDay=(invalidDate?Single.NaN:(float)(newTimeSpan(dt.Hour,dt.Minute,0).TotalMinutes)/1440 f);varpercentOfWeek=(invalidDate?Single.NaN:((float)dt.DayOfWeek+percentOfDay)/7 f);varpercentOfMonth=(invalidDate?Single.NaN:(float)(dt.Day+percentOfDay)/(float)DateTime.DaysInMonth(dt.Year,dt.Month));varpercentOfYear=(invalidDate?Single.NaN:(float)((dt.DayOfYear+percentOfDay)/(newDateTime(dt.Year+1,1,1)-newDateTime(dt.Year,1,1)).TotalDays));// Time features like the current onesoutput.DateWeekday=(invalidDate?Single.NaN:(float)dt.DayOfWeek);output.DateMonth=(invalidDate?Single.NaN:(float)dt.Month);output.DateHour=(invalidDate?Single.NaN:dt.Hour);output.DatePartOfDay=(invalidDate?Single.NaN:(float)Math.Round(dt.AddHours(-3).Hour/6 f));output.InvalidDate=(invalidDate?1.0f:0.0f);// Polar date/time transforms for percent of day & week & year output.CosTimeOfDay=(float)Math.Cos(2*Math.PI*percentOfDay);output.SinTimeOfDay=(float)Math.Sin(2*Math.PI*percentOfDay);output.CosTimeOfWeek=(float)Math.Cos(2*Math.PI*percentOfWeek);output.SinTimeOfWeek=(float)Math.Sin(2*Math.PI*percentOfWeek);output.CosTimeOfMonth=(float)Math.Cos(2*Math.PI*percentOfMonth);output.SinTimeOfMonth=(float)Math.Sin(2*Math.PI*percentOfMonth);output.CosTimeOfYear=(float)Math.Cos(2*Math.PI*percentOfYear);output.SinTimeOfYear=(float)Math.Sin(2*Math.PI*percentOfYear);
``` #Resolved

@justinormontjustinormontDec 4, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @davidbrownellWork #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add this as a potential feature for Mn. #Resolved

@gvashishthagvashishthaDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean add to the ML.NET plan? Any estimate on how much additional dev time this would take? #Resolved

@justinormontjustinormontDec 5, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the new features is pretty simple. I expect more of the work is in testing. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the input dateTime column a long and not a DateTime? #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

long to match a unix timestamp. We will be adding in the ability to use C# DateTime and string in an ISO format in the future, but the initial implementation is just the unix timestamp. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern here is that .NET developers expect to have their date time data using System.DateTime. That is the natural type in .NET. Forcing the user to convert from System.DateTime to a long unix timestamp doesn't seem like a good experience. It would make more sense if the input column was a System.DateTime. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do the conversion yourself here? The user's input column can be DateTime, and then you convert it to a long unix timestamp before passing it to the C++ code? #Resolved

@davidbrownellWorkdavidbrownellWorkDec 5, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to ensure that the process of converting from a string to "Date time" is consistent across frameworks - especially considering that a DateTime in and of itself isn't sufficient in some scenarios, as we lose (potentially important) time zone information in the process. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not asking for string. I'm asking for the ML.NET data type that a user would feed into this transform to be the canonical System.DateTime type and not a long type.

Specifically, this transform should require the input column to be of this type:

/// <summary>
/// The standard date time type. This has representation type of <see cref="DateTime"/>.
/// Note this can have only one possible value, accessible by the singleton static property <see cref="Instance"/>.
/// </summary>
publicsealedclassDateTimeDataViewType:PrimitiveDataViewType

It doesn't make sense for a user to have to convert a DateTime to a long before calling this transform. #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifically, look at the sample code being checked in under samples:

// Create a small dataset as an IEnumerable.// Future Date - 2025 June 30varsamples=new[]{newDateTimeInput(){Date=1751241600}};

Instead, that sample code should be:

// Create a small dataset as an IEnumerable.varsamples=new[]{newDateTimeInput(){Date=newDateTime(2025,6,30)}};
``` #Resolved

@davidbrownellWorkdavidbrownellWorkDec 6, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - eventually a C# developer will be able to pass System:DateTime, string, or long. #Resolved

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is on our todo list and will be added in in the future. We will eventually be supporting DateTime and string as well, but initial support will just be for long. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
ctx.Writer.Write(data);
}

public void Dispose()

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who calls this Dispose method? We have a problem with tranformers that need to be disposed. See #906 and my comment on #4223

cc @codemzs #Resolved

@michaelgsharpmichaelgsharpDec 5, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain. I am not manually calling it anywhere. Is it happening during pipeline teardown ok? Or is there more desirable behavior? #Resolved

@eerhardteerhardtDec 5, 2019

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should just be called automatically when the pipeline teardown happens as mentioned in that comment chain.

There is no call of it when the pipeline teardown happens. That is why that bug was opened. I'm still not certain on why it was closed. Maybe @codemzs can comment. #Resolved

Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

/azp run #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 9, 2019

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

#Resolved

@michaelgsharp

michaelgsharp commented Dec 9, 2019

Copy link
Copy Markdown
ContributorAuthor

Going to close and reopen the PR to reset the stuck builds. #Resolved

@codecov

codecovBot commented Dec 10, 2019

Copy link
Copy Markdown

Codecov Report

Merging #4521 into master will increase coverage by 0.03%.
The diff coverage is 88.11%.

@@ Coverage Diff @@## master #4521 +/- ##
==========================================
+ Coverage 75.11% 75.15% +0.03% 
==========================================
Files 908 913 +5 Lines 160204 160891 +687 Branches 17254 17292 +38 ==========================================
+ Hits 120340 120911 +571 - Misses 35050 35153 +103 - Partials 4814 4827 +13
FlagCoverage Δ
#Debug75.15% <88.11%> (+0.03%)⬆️
#production70.54% <86.24%> (+0.02%)⬆️
#test90.29% <91.41%> (+0.03%)⬆️
Impacted FilesCoverage Δ
....ML.Tests/Transformers/DateTimeTransformerTests.cs100% <100%> (ø)
...crosoft.ML.Core.Tests/UnitTests/TestEntryPoints.cs98.36% <100%> (ø)⬆️
src/Microsoft.ML.Featurizers/Common.cs19.58% <15%> (ø)
...estFramework/Attributes/NotCentOS7FactAttribute.cs26.08% <26.08%> (ø)
...rc/Microsoft.ML.Featurizers/DateTimeTransformer.cs90.57% <90.57%> (ø)
....ML.AutoML/PipelineSuggesters/PipelineSuggester.cs83.19% <0%> (-3.37%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmDataset.cs74.21% <0%> (-2.12%)⬇️
...rc/Microsoft.ML.LightGbm/WrappedLightGbmBooster.cs83.8% <0%> (-0.63%)⬇️
...soft.ML.TestFramework/DataPipe/TestDataPipeBase.cs76.08% <0%> (-0.4%)⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs86.1% <0%> (-0.16%)⬇️
... and 21 more
#Resolved

@codemzs
codemzs requested a review from natkeDecember 12, 2019 18:27
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs
Comment threadsrc/Microsoft.ML.Featurizers/DateTimeTransformer.cs Outdated
WeekIso = 15,
YearIso = 16,
MonthLabel = 17,
AmPmLabel = 18,

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do AmPm label, would it make sense to also do weekend/weekday? (Often used in feature engineering) #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think thats not a bad idea, but the initial ask we got was just for these columns. We will start with just these and then can add in more if the requirement arises. #Resolved

{
ValueGetter<T> result = (ref T dst) =>
{
long dateTime = default;

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree on using System.DateTime from the beginning. This will make the transform consistent with other parts of the API, as well as more usable to .NET developers #Resolved

/// <param name="inputColumnName">Input column name</param>
/// <param name="columnPrefix">Prefix to add to the generated columns</param>
/// <returns><see cref="DateTimeEstimator"/></returns>
public static DateTimeEstimator DateTimeTransformer(this TransformsCatalog catalog, string inputColumnName, string columnPrefix)

@natkenatkeDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming of the extension methods are not consistent with the other transformers (I'll leave it to others to judge whether the original scheme makes sense :-), but consistency is probably a more important consideration now)

If you look at the other transforms they mostly have the form Verb-Object e.g. CopyColumns, MapValueToKey etc. This makes sense when you are chaining them together in a pipeline.

Suggestions for this one: FeaturizeDateTime, SplitDateTime, TokenizeDateTime, ExtractDateTime ...

#Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of the ones you listed I would vote FeaturizeDateTime. Anyone have any objections if I rename the extension method to that? #Resolved

case DateTimeEstimator.ColumnsProduced.MonthLabel:
case DateTimeEstimator.ColumnsProduced.AmPmLabel:
case DateTimeEstimator.ColumnsProduced.DayOfWeekLabel:
case DateTimeEstimator.ColumnsProduced.HolidayName:

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usability question, why are we producing multiple output columns of instead of a single vector type column?

The next step after this DateTime transform is:

  • Convert all the INTs/USHORTs/BYTEs columns to Float32
  • One-hot encode the STRING columns ("Friday" to [ 0, 0, 0, 0, 0, 1, 0 ])
  • Concatenate all of these feature to single vector of Float32

Why aren't we just outputting the final vector type column? Then the user can directly use its output, like our other transforms. #Resolved

@justinormontjustinormontDec 12, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a usecase for the individual columns, perhaps just a boolean parameter produceRawColumns, which then produces the individual outputs instead of a singular vector column? #Resolved

@michaelgsharpmichaelgsharpDec 12, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did it this way just because thats how the original python code works. It splits everything up into different columns and doesn't combine them into a vector. #Resolved

@natkenatkeDec 13, 2019

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a single vector is consistent with FeaturizeText. #Resolved

@michaelgsharpmichaelgsharpDec 16, 2019

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original code we were bringing to ML.NET returned all individual columns. Because of that, for now, we will be leaving them as individual columns. If we learn that we never need individual columns and only use this as a vector then we can revisit this then. #Resolved

@azure-pipelines

azure-pipelinesBot commented Dec 17, 2019

Copy link
Copy Markdown
No pipelines are associated with this pull request.

#Resolved

@michaelgsharp
michaelgsharp merged commit 290e069 into dotnet:masterDec 17, 2019
@michaelgsharp
michaelgsharp deleted the datetime-transformer branch December 17, 2019 21:35
throw new InvalidOperationException($"Unsupported type {type}");
}

// The Native Featurizers do not currently support CentOS7, this method checks the OS and returns true if it is CentOS7.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about RedHat 7?

{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
using (Process process = new Process())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more performant way of doing this check would be to read from the file directly. Starting a new process just to get this info is too heavy-weight IMO.

See https://github.com/dotnet/runtime/blob/6f445d6dc237f59d730e0df47f8630b18887d776/src/installer/managed/Microsoft.DotNet.PlatformAbstractions/Native/PlatformApis.cs#L140-L142

@ghostghost locked as resolved and limited conversation to collaborators Mar 19, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@michaelgsharp@natke@justinormont@harishsk@eerhardt@gvashishtha@davidbrownellWork