您当前的位置: 首页 >  .net

寒冰屋

暂无认证

  • 2浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

.NET、TensorFlow和Kaggle的风车

寒冰屋 发布时间:2021-08-24 15:41:15 ,浏览量:2

目录

让我们预测房地产价格!

分析训练数据

分析列数据类型

数字列

分类列

把他们聚在一起

构建神经网络

提供数据

是时候放下梯度了

提交

总结

链接

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

在这篇文章和接下来的文章中,我将关注(几乎)纯神经网络。这意味着,将有意跳过数据集准备中大部分无聊的部分,例如填充缺失值、特征选择、异常值分析等。

技术栈将是C#+TensorFlow tf.keras API。截至今天,它还将需要Windows。未来文章中的大型模型可能需要合适的GPU来保证训练时间保持理智。

让我们预测房地产价格!

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

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

分析训练数据

我是说我们会跳过数据集准备吗?我撒了谎!你必须至少看一次。

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

static DataTable LoadData(string csvFilePath) {
  var result = new DataTable();
  using (var reader = new CsvDataReader(new CsvReader(new StreamReader(csvFilePath)))) {
    result.Load(reader);
  }
  return result;
}

利用DataTable训练数据操作是,实际上,一个坏主意。

ML.NET应该具有.csv加载和许多数据准备和探索操作。然而,当我刚刚参加房价竞赛时,它还没有为那个特殊目的做好准备。

数据看起来像这样(只有几行和几列):

ID

子类

分区

地段前台

地段面积

1

60

RL

65

8450

2

20

RL

80

9600

3

60

RL

68

11250

4

70

RL

60

9550

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

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

分析列数据类型

DataTable不会自动推断列的数据类型,并假设它都是string。所以下一步是确定我们实际拥有什么。对于每一列,我计算了以下统计信息:不同值的数量,其中有多少是整数,有多少是浮点数(包含所有辅助方法的源代码将在文章末尾链接):

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();

数字列

事实证明,大多数列实际上是int,但由于神经网络主要处理浮点数,我们无论如何都会将它们转换为double。

分类列

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

最初,我只是将数字从0到分配distinctValueCount-1给它们,但这没有多大意义,因为实际上没有从“Facade: Blue”到“Facade: Green” 再到 “ Facade: White”的进展。很早时,我将其更改为所谓的one-hot encoding,其中每个唯一值都有一个单独的输入列。例如,“Facade: Blue”变成[1,0,0],“Facade: White”变成[0,0,1]。

把他们聚在一起
CentralAir: 2 values, ints: 0.00%, floats: 0.00%
Street: 2 values, ints: 0.00%, floats: 0.00%
Utilities: 2 values, ints: 0.00%, floats: 0.00%
....
LotArea: 1073 values, ints: 100.00%, floats: 100.00%

Many value columns:
Exterior1st: AsbShng, AsphShn, BrkComm, BrkFace, CBlock, CemntBd, HdBoard, 
             ImStucc, MetalSd, Plywood, Stone, Stucco, VinylSd, Wd Sdng, WdShing
Exterior2nd: AsbShng, AsphShn, Brk Cmn, BrkFace, CBlock, CmentBd, HdBoard, 
             ImStucc, MetalSd, Other, Plywood, Stone, Stucco, VinylSd, Wd Sdng, Wd Shng
Neighborhood: Blmngtn, Blueste, BrDale, BrkSide, ClearCr, CollgCr, Crawfor, 
              Edwards, Gilbert, IDOTRR, MeadowV, Mitchel, NAmes, NoRidge, NPkVill, 
              NridgHt, NWAmes, OldTown, Sawyer, SawyerW, Somerst, 
              StoneBr, SWISU, Timber, Veenker

non-parsable floats
GarageYrBlt: NA
LotFrontage: NA
MasVnrArea: NA

float ranges:
BsmtHalfBath: 0...2
HalfBath: 0...2
...
GrLivArea: 334...5642
LotArea: 1300...215245

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

static Func ValueNormalizer(double floats, IEnumerable 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);
  }
}

现在我们已经将数据转换成适合神经网络的格式。是时候进行构建了。

构建神经网络

如果您已经安装了Python 3.6和TensorFlow 1.10.x,您只需要:

在您的现代.csproj文件中。否则,请参阅Gradient 手册进行初始设置。

一旦包启动并运行,我们就可以创建我们的第一个浅层深度网络。

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个神经元层和一个dropout层的未经训练的神经网络,有助于防止过度拟合。

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

Dropout是神经网络中的一个特殊功能层,它实际上不包含神经元本身。相反,它通过获取每个单独的输入进行操作,并随机将其替换为0自输出(否则,它只会传递原始值)。通过这样做,它有助于防止在小dataset中对不太相关的特征进行过度拟合。例如,如果我们没有删除Id列,网络可能已经记住了->精确映射,这将使我们在训练集上具有100%的准确度,但在任何其他数据上的数字完全不相关。为什么我们需要辍学?我们的训练数据只有约1500个示例,而我们构建的这个微型神经网络有>1800个可调权重。如果它是一个简单的多项式,它可以匹配价格函数,我们试图精确地近似。但是,它会对原始训练集之外的任何输入产生巨大的价值。

提供数据

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

using numpy;

...

const string predict = "SalePrice";

ndarray GetInputs(IEnumerable 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);

在上面的代码中,我们将每个DataRow 转换为一个ndarray,方法是取其中的每个单元格,并应用对应于其列的ValueNormalizer。然后,我们将所有行放入另一个 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();
ndarray submissionInputs = GetInputs(submissionRows);
ndarray sumissionOutputs = model.predict(submissionInputs);

它主要使用之前定义的函数。

然后你需要将它们写入一个.csv文件,它只是一个间的的Id,predicted_value对列表。

当你提交你的结果时,你应该得到一个0.17的分数,它会在公共排行榜的最后四分之一的某个地方。但是,嘿,如果它像具有27个神经元的3层网络一样简单,那些讨厌的数据科学家就不会从美国主要公司那里获得每年30万美元以上的总薪酬。

总结

此文章的完整源代码(包含所有帮助程序,以及我早期探索和实验的一些注释掉的部分)在PasteBin上大约有200行。

在下一篇文章中,您将看到我试图进入公共排行榜前50%的恶作剧。这将是业余爱好者的冒险,使用流浪者唯一的工具与过度拟合的风车作斗争——一个更大的模型(例如,深度神经网络,记住,没有手动特征工程!)。这将不再是一个编码教程,而更多地是一个带有非常诡异的数学和一个奇怪的结论的思想探索。

敬请关注!

链接
  • Kaggle
  • Kaggle上的房价竞赛
  • TensorFlow回归教程
  • TensorFlow主页
  • TensorFlow API 参考
  • 渐变(TensorFlow 绑定)

https://www.codeproject.com/Articles/1278115/NET-TensorFlow-and-the-Windmills-of-Kaggle

关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.0459s