.NET,TensorFlow和Kaggle的风车-旅程开始

这是一系列有关我作为.NET开发人员正在进行的进入Kaggle竞赛的黑暗森林的旅程的系列文章。

在本文和以下文章中,我将专注于(几乎)纯神经网络。 这意味着,数据集准备工作的大部分无聊部分,例如填写缺失值,特征选择,离群值分析等。 将被有意跳过。

技术堆栈将是C#+ TensorFlow tf.keras API。 从今天起,它还将需要Windows。 在以后的文章中,较大的型号可能需要合适的GPU来保持训练时间,以保持理智。

让我们预测房地产价格!


房价对于新手来说是一个巨大的竞争。 它的数据集很小,没有特殊规则,公共排行榜有很多参与者,您每天最多可以提交4个条目。

在Kaggle上注册,如果您还没有注册,请参加比赛,然后下载数据。 目标是预测test.csv中条目的销售价格(SalePrice列)。 存档中包含train.csv ,其中约有1500个条目以已知的销售价格进行训练。 在进入神经网络之前,我们将从加载该数据集开始并进行一些探索。

分析训练数据


我是否说过我们将跳过数据集准备工作? 我撒谎了! 您必须至少看一次。

令我惊讶的是,我没有找到在.NET标准类库中加载.csv文件的简便方法,因此我安装了一个名为CsvHelper的NuGet软件包。 为了简化数据操作,我还获得了我最喜欢的LINQ扩展包MoreLinq

将.csv数据加载到DataTable中
static DataTable LoadData(string csvFilePath) { var result = new DataTable(); using (var reader = new CsvDataReader(new CsvReader(new StreamReader(csvFilePath)))) { result.Load(reader); } return result; } 


ML.NET
使用DataTable训练数据操作实际上是一个坏主意。

ML.NET应该具有.csv加载功能,并且可以进行许多数据分类和浏览操作。 但是,当我刚进入房屋价格竞赛时,还没有为该特定目的做好准备。


数据如下所示(只有几行和几列):

编号MSSubClass鸣声LotFrontage洛塔雷亚
1个60RL65岁8450
220RL809600
360RL6811250
470RL609550


加载数据后,我们需要删除ID列,因为它实际上与房价无关:

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

分析列数据类型


DataTable不会自动推断列的数据类型,并假定其全部为字符串。 因此,下一步就是确定我们实际拥有的东西。 对于每一列,我计算了以下统计信息:不同值的数量,其中有多少个整数,以及有多少个浮点数(本文末尾将链接带有所有辅助方法的源代码):

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

数值栏


事实证明,大多数列实际上是整数,但是由于神经网络大多数都在浮点数上起作用,因此无论如何我们都会将它们转换为双精度数。

分类栏


其他列描述了待售物业所属的类别。 它们都没有太多不同的值,这很好。 要将它们用作我们未来的神经网络的输入,它们也必须转换为两倍

最初,我只是简单地将数字从0分配给distinctValueCount-1,但这并没有多大意义,因为实际上没有从“外观:蓝色”到“外观:绿色”到“外观:白色”的过渡。 因此,在早期,我将其更改为所谓的“ 单热编码” ,其中每个唯一值都获得一个单独的输入列。 例如,“外观:蓝色”变为[1,0,0],“外观:白色”变为[0,0,1]。

让他们在一起


大量数据探索
CentralAir:2个值,整数:0.00%,浮动:0.00%
街道:2个值,整数:0.00%,浮动数:0.00%
实用程序:2个值,整数:0.00%,浮点数:0.00%
胡同:3个值,整数:0.00%,浮点数:0.00%
BsmtHalfBath:3个值,整数:100.00%,浮点数:100.00%
HalfBath:3个值,ints:100.00%,floats:100.00%
LandSlope:3个值,整数:0.00%,浮动数:0.00%
PavedDrive:3个值,整数:0.00%,浮点数:0.00%
BsmtFullBath:4个值,整数:100.00%,浮点数:100.00%
ExterQual:4个值,整数:0.00%,浮点数:0.00%
壁炉:4个值,整数:100.00%,浮点数:100.00%
FullBath:4个值,ints:100.00%,floats:100.00%
GarageFinish:4个值,ints:0.00%,float:0.00%
KitchenAbvGr:4个值,整数:100.00%,浮点数:100.00%
KitchenQuality:4值,整数:0.00%,浮点数:0.00%
LandContour:4个值,整数:0.00%,浮动数:0.00%
LotShape:4个值,整数:0.00%,浮点数:0.00%
PoolQC:4个值,整数:0.00%,浮点数:0.00%
BldgType:5个值,整数:0.00%,浮点数:0.00%
BsmtCond:5个值,整数:0.00%,浮点数:0.00%
BsmtExposure:5个值,整数:0.00%,浮点数:0.00%
BsmtQual:5个值,整数:0.00%,浮点数:0.00%
ExterCond:5个值,整数:0.00%,浮点数:0.00%
围栏:5个值,整数:0.00%,浮动:0.00%
车库车:5个值,整数:100.00%,浮动车:100.00%
加热QC:5个值,整数:0.00%,浮点数:0.00%
LotConfig:5个值,整数:0.00%,浮点数:0.00%
MasVnrType:5个值,整数:0.00%,浮点数:0.00%
其他功能:5个值,整数:0.00%,浮点数:0.00%
MSZoning:5个值,整数:0.00%,浮点数:0.00%
已售:5个值,整数:100.00%,浮动数:100.00%
电气:6个值,整数:0.00%,浮点数:0.00%
FireplaceQu:6个值,整数:0.00%,浮点数:0.00%
基础:6个值,整数:0.00%,浮点数:0.00%
GarageCond:6个值,整数:0.00%,浮点数:0.00%
GarageQual:6个值,整数:0.00%,浮点数:0.00%
加热:6个值,整数:0.00%,浮动:0.00%
RoofStyle:6个值,整数:0.00%,浮点数:0.00%
Sale条件:6个值,整数:0.00%,浮动数:0.00%
BsmtFinType1:7个值,整数:0.00%,浮点数:0.00%
BsmtFinType2:7个值,整数:0.00%,浮点数:0.00%
功能:7个值,整数:0.00%,浮点数:0.00%
GarageType:7个值,整数:0.00%,浮点数:0.00%
BedroomAbvGr:8个值,整数:100.00%,浮点数:100.00%
Condition2:8个值,整数:0.00%,浮点数:0.00%
HouseStyle:8个值,整数:0.00%,浮点数:0.00%
PoolArea:8个值,ints:100.00%,floats:100.00%
RoofMatl:8个值,整数:0.00%,浮点数:0.00%
Condition1:9个值,整数:0.00%,浮点数:0.00%
TotalCond:9个值,ints:100.00%,floats:100.00%
SaleType:9个值,整数:0.00%,浮动数:0.00%
综合质量:10个值,整数:100.00%,浮点数:100.00%
MoSold:12个值,ints:100.00%,floats:100.00%
TotRmsAbvGrd:12个值,整数:100.00%,浮点数:100.00%
外部第一个:15个值,整数:0.00%,浮点数:0.00%
MSSubClass:15个值,整数:100.00%,浮点数:100.00%
Exterior2nd:16个值,整数:0.00%,浮点数:0.00%
3SsnPorch:20个值,整数:100.00%,浮动数:100.00%
MiscVal:21个值,整数:100.00%,浮点数:100.00%
LowQualFinSF:24个值,整数:100.00%,浮点数:100.00%
邻里:25个值,整数:0.00%,浮动:0.00%
YearRemodAdd:61个值,整数:100.00%,浮点数:100.00%
ScreenPorch:76个值,ints:100.00%,floats:100.00%
GarageYrBlt:98个值,整数:94.45%,浮点数:94.45%
LotFrontage:111个值,ints:82.26%,floats:82.26%
YearBuilt:112个值,ints:100.00%,floats:100.00%
封闭通道:120个值,整数:100.00%,浮点数:100.00%
BsmtFinSF2:144个值,整数:100.00%,浮点数:100.00%
OpenPorchSF:202个值,整数:100.00%,浮点数:100.00%
WoodDeckSF:274个值,整数:100.00%,浮动:100.00%
MasVnrArea:328个值,整数:99.45%,浮动数:99.45%
2ndFlrSF:417个值,整数:100.00%,浮点数:100.00%
GarageArea:441个值,整数:100.00%,浮动:100.00%
BsmtFinSF1:637个值,整数:100.00%,浮点数:100.00%
售价:663个值,整数:100.00%,流通量:100.00%
TotalBsmtSF:721个值,整数:100.00%,浮点数:100.00%
1stFlrSF:753个值,整数:100.00%,浮点数:100.00%
BsmtUnfSF:780个值,整数:100.00%,浮点数:100.00%
GrLivArea:861值,整数:100.00%,浮点数:100.00%
LotArea:1073值,ints:100.00%,floats:100.00%

许多值列:
Exterior1st:AsbShng,AsphShn,BrkComm,BrkFace,CBlock,CemntBd,HdBoard,ImStucc,MetalSd,胶合板,石材,灰泥,VinylSd,Wd Sdng,WdShing
Exterior2nd:AsbShng,AsphShn,Brk Cmn,BrkFace,CBlock,CmentBd,HdBoard,ImStucc,MetalSd,其他,胶合板,石材,灰泥,VinylSd,Wd Sdng,Wd Shng
社区:Blmngtn,Blueste,BrDale,BrkSide,ClearCr,CollgCr,Crawfor,Edwards,Gilbert,IDOTRR,MeadowV,Mitchel,NAmes,NoRidge,NPkVill,NridgHt,NWAmes,OldTown,Sawyer,SawyerW,Somerst,Stone,Stoner文克

不可解析的浮点数
GarageYrBlt:不适用
LotFrontage:不适用
MasVnr地区:不适用

浮动范围:
BsmtHalfBath:0 ... 2
半浴:0 ... 2
BsmtFullBath:0 ... 3
壁炉:0 ... 3
全浴:0 ... 3
KitchenAbvGr:0 ... 3
车库车:0 ... 4
已售:2006年... 2010年
BedroomAbvGr:0 ... 8
泳池面积:0 ... 738
总体状况:1 ... 9
综合素质:1 ... 10
MoSold:1 ... 12
TotRmsAbvGrd:2 ... 14
MS子类:20 ... 190
3SsnPorch:0 ... 508
杂项Val:0 ... 15500
LowQualFinSF:0 ... 572
年改款年份:1950年至2010年
屏幕通道:0 ... 480
GarageYrBlt:1900 ... 2010
前额:21 ... 313
建成年份:1872年至2010年
封闭门廊:0 ... 552
BsmtFinSF2:0 ... 1474年
OpenPorchSF:0 ... 547
木甲板SF:0 ... 857
MasVnrArea:0 ... 1600
2ndFlrSF:0至2065
车库面积:0 ... 1418
BsmtFinSF1:0 ... 5644
销售价格:34,900 ... 755,000
TotalBsmtSF:0 ... 6110
1stFlrSF:334 ... 4692
BsmtUnfSF:0 ... 2336
GrLivArea:334 ... 5642
LotArea:1300 ... 215245


考虑到这一点,我构建了以下ValueNormalizer ,它获取有关列内值的一些信息,并返回一个函数,该函数将值( 字符串 )转换为神经网络的数字特征向量( double [] ):

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


现在,我们已经将数据转换为适用于神经网络的格式。 现在是时候建立一个了。

建立神经网络


从今天开始,您将需要使用Windows计算机。

如果您已经安装了Python和TensorFlow 1.1x,那么您所需要做的就是

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

在您的现代.csproj文件中。 否则,请参考“ 渐变”手册进行初始设置。

程序包启动并运行后,我们可以创建第一个浅层深度网络。

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

这将创建一个具有3个神经元层和一个辍学层的未经训练的神经网络,有助于防止过度拟合。

tf.nn.relu_fn
tf.nn.relu_fn是我们神经元的激活功能。 众所周知, ReLU在深度网络中能很好地工作,因为它解决了消失的梯度问题 :当误差从深度网络中的输出层传播回去时,原始非线性激活函数的导数往往变得非常小。 这意味着,靠近输入的层仅会进行很小的调整,这会大大减慢对深度网络的训练。

辍学
辍学是神经网络中的特殊功能层,实际上不包含神经元。 取而代之的是,它通过获取每个单独的输入进行操作,并在自身输出上将其随机替换为0(否则,它只会传递原始值)。 这样可以防止在较小的数据集中过度拟合不太相关的特征。 例如,如果我们不删除Id列,则网络可能准确地存储了<Id>-> <SalePrice>映射,这将为我们提供100%的训练集准确性,但在其他任何数据上却完全不相关。

为什么我们需要辍学? 我们的训练数据只有1500个示例,而我们构建的这个小型神经网络具有1800多个可调权重。 如果它是一个简单的多项式,则可以与我们试图精确逼近的价格函数匹配。 但是在原始训练集之外的任何输入上,它都将具有巨大的价值。

送入数据


TensorFlow期望其数据以NumPy数组或现有张量存在。 我将DataRows转换为NumPy数组:

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

在上面的代码中,我们通过获取其中的每个单元格并应用与其列对应的ValueNormalizer ,将每个DataRow转换为ndarray 。 然后,将所有行放入另一个ndarray中 ,得到一个数组数组。

输出不需要这种转换,我们只需将训练值转换为另一个ndarray即可

时间到了梯度


使用此设置,我们需要训练网络的所有工作就是调用模型的fit函数:

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

该呼叫实际上将预留训练集的最后7.5%进行验证,然后重复以下2000次:

  1. 将其余的trainInputs分为几批
  2. 将这些批次一一喂入神经网络
  3. 使用上面定义的损失函数计算误差
  4. 通过单个神经元连接的梯度反向传播错误,调整权重

训练时,它将在网络上保留用于验证的数据的错误输出为val_loss ,而训练数据本身的错误将输出为loss 。 通常,如果val_loss变得比loss大得多,则意味着网络开始过度拟合。 我将在以下文章中更详细地讨论这一点。

如果您正确地进行了所有操作,则损失之一的平方根应为20000左右。



投稿


关于生成要提交到此处的文件,我不会过多谈论。 计算输出的代码很简单:

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

它主要使用先前定义的功能。

然后,您需要将它们写入.csv文件,该文件只是ID,预测值对的列表。

提交结果时,您应获得0.17的分数,该分数将位于公共排行榜表的最后四分之一位置。 但是,如果它像具有27个神经元的3层网络那样简单,那么那些讨厌的数据科学家将不会从美国主要公司那里得到30万美元/年的总补偿。

总结


此条目的完整源代码(包括所有助手,以及我先前的探索和实验中一些注释掉的部分)在PasteBin上大约有200行。

在下一篇文章中,您将看到我的恶作剧者试图进入该公共排行榜的前50%。 这将是业余爱好者的冒险,是与流浪者唯一的工具与过拟合的风车之战-更大的模型(例如,深度神经网络,请记住,没有手动特征工程!)。 这将不再是编码教程,而是更多的思想探索,其中包含真正的歪曲数学和怪异的结论。 敬请期待!

友情链接


卡格勒
Kaggle的房价竞争
TensorFlow回归教程
TensorFlow主页
TensorFlow API参考
渐变(TensorFlow绑定)

Source: https://habr.com/ru/post/zh-CN437174/


All Articles