目录
让我们预测房地产价格!
分析训练数据
分析列数据类型
数字列
分类列
把他们聚在一起
构建神经网络
提供数据
是时候放下梯度了
提交
总结
链接
这是一系列关于我作为 .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次:
- 把剩下的trainInputs分成几批
- 将这些批次一一喂入神经网络
- 使用我们上面定义的损失函数计算误差
- 通过单个神经元连接的梯度反向传播误差,调整权重
在训练时,它会将网络在其留出用于验证的数据上的错误输出为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