
Ini adalah serangkaian artikel tentang perjalanan berkelanjutan saya ke hutan gelap kompetisi
Kaggle sebagai pengembang .NET.
Saya akan fokus pada (hampir) jaringan saraf murni dalam artikel ini dan berikut. Ini berarti, bahwa sebagian besar bagian membosankan dari persiapan dataset, seperti mengisi nilai yang hilang, pemilihan fitur, analisis pencilan, dll. akan sengaja dilewati.
Tumpukan teknologi akan menjadi C # + TensorFlow
tf.keras API. Mulai hari ini juga akan memerlukan Windows. Model yang lebih besar di artikel mendatang mungkin membutuhkan GPU yang sesuai agar waktu pelatihan mereka tetap waras.
Mari kita prediksi harga real estat!
House Prices adalah kompetisi yang bagus untuk pemula. Datasetnya kecil, tidak ada aturan khusus, papan publik memiliki banyak peserta, dan Anda dapat mengirimkan hingga 4 entri sehari.
Daftar di Kaggle, jika Anda belum melakukannya, bergabunglah dengan kompetisi ini, dan unduh datanya. Sasarannya adalah untuk memprediksi harga jual (kolom SalePrice) untuk entri dalam
test.csv . Arsip berisi
train.csv , yang memiliki sekitar 1500 entri dengan harga jual yang dikenal untuk
pelatihan . Kami akan mulai dengan memuat dataset itu, dan menjelajahinya sedikit, sebelum masuk ke jaringan saraf.
Analisis data pelatihan
Apakah saya mengatakan kami akan melewatkan persiapan dataset? Saya berbohong! Anda harus melihatnya setidaknya sekali.Yang mengejutkan saya, saya tidak menemukan cara mudah untuk memuat file .csv di perpustakaan kelas standar .NET, jadi saya menginstal paket NuGet, yang disebut
CsvHelper . Untuk mempermudah manipulasi data, saya juga mendapatkan paket ekstensi LINQ favorit saya,
MoreLinq .
Memuat data .csv ke dalam DataTablestatic DataTable LoadData(string csvFilePath) { var result = new DataTable(); using (var reader = new CsvDataReader(new CsvReader(new StreamReader(csvFilePath)))) { result.Load(reader); } return result; }
ML.NETMenggunakan
DataTable untuk melatih manipulasi data, sebenarnya, adalah ide yang buruk.
ML.NET seharusnya memiliki pemuatan .csv dan banyak operasi pemeliharaan dan eksplorasi data. Namun, itu belum siap untuk tujuan tertentu, ketika saya baru saja memasuki kompetisi House Prices.
Data terlihat seperti ini (hanya beberapa baris dan kolom):
Id | MSSubClass | Menandai | LotFrontage | Lotarea |
1 | 60 | RL | 65 | 8450 |
2 | 20 | RL | 80 | 9600 |
3 | 60 | RL | 68 | 11250 |
4 | 70 | RL | 60 | 9550 |
Setelah memuat data, kami perlu menghapus kolom
Id , karena sebenarnya tidak terkait dengan harga rumah:
var trainData = LoadData("train.csv"); trainData.Columns.Remove("Id");
Menganalisis tipe data kolom
DataTable tidak secara otomatis menyimpulkan tipe data kolom, dan menganggap itu semua string. Jadi langkah selanjutnya adalah menentukan apa yang sebenarnya kita miliki. Untuk setiap kolom saya menghitung statistik berikut: jumlah nilai yang berbeda, berapa banyak dari mereka adalah bilangan bulat, dan berapa banyak dari mereka adalah angka floating point (kode sumber dengan semua metode pembantu akan dihubungkan pada akhir artikel):
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();
Kolom angka
Ternyata sebagian besar kolom sebenarnya int, tetapi karena sebagian besar jaringan saraf bekerja pada angka mengambang, kita akan mengubahnya menjadi ganda.
Kolom kategorikal
Kolom lain menggambarkan kategori milik properti yang dijual. Tidak ada satu pun dari mereka yang memiliki terlalu banyak nilai berbeda, yang bagus. Untuk menggunakannya sebagai input untuk jaringan saraf kita di masa depan, mereka harus dikonversi menjadi
dua kali lipat juga.
Awalnya, saya hanya menetapkan angka dari 0 ke differValueCount - 1 untuk mereka, tetapi itu tidak masuk akal, karena sebenarnya tidak ada perkembangan dari "Facade: Blue" melalui "Facade: Green" menjadi "Facade: White". Jadi sejak awal saya mengubahnya menjadi apa yang disebut
pengkodean satu-panas , di mana setiap nilai unik mendapat kolom input yang terpisah. Misalnya "Fasad: Biru" menjadi [1,0,0], dan "Fasad: Putih" menjadi [0,0,1].
Menyatukan mereka semua
Output besar dari eksplorasi dataCentralAir: 2 nilai, int: 0,00%, mengapung: 0,00%
Jalan: 2 nilai, int: 0,00%, mengapung: 0,00%
Utilitas: 2 nilai, int: 0,00%, mengapung: 0,00%
Gang: 3 nilai, ints: 0,00%, mengapung: 0,00%
BsmtHalfBath: 3 nilai, int: 100,00%, mengapung: 100,00%
HalfBath: 3 nilai, int: 100,00%, mengapung: 100,00%
LandSlope: 3 nilai, ints: 0,00%, mengapung: 0,00%
PavedDrive: 3 nilai, ints: 0,00%, mengapung: 0,00%
BsmtFullBath: 4 nilai, int: 100,00%, mengapung: 100,00%
ExterQual: 4 nilai, ints: 0,00%, mengapung: 0,00%
Perapian: 4 nilai, int: 100,00%, mengapung: 100,00%
FullBath: 4 nilai, int: 100,00%, mengapung: 100,00%
GarageFinish: 4 nilai, int: 0,00%, mengapung: 0,00%
KitchenAbvGr: 4 nilai, int: 100,00%, mengapung: 100,00%
DapurKual: 4 nilai, int: 0,00%, mengapung: 0,00%
LandContour: 4 nilai, ints: 0,00%, mengapung: 0,00%
LotShape: 4 nilai, ints: 0,00%, mengapung: 0,00%
PoolQC: 4 nilai, ints: 0,00%, mengapung: 0,00%
BldgType: 5 nilai, ints: 0,00%, mengapung: 0,00%
BsmtCond: 5 nilai, int: 0,00%, mengapung: 0,00%
BsmtExposure: 5 nilai, ints: 0,00%, mengapung: 0,00%
BsmtQual: 5 nilai, int: 0,00%, mengapung: 0,00%
ExterCond: 5 nilai, ints: 0,00%, mengapung: 0,00%
Pagar: 5 nilai, int: 0,00%, mengapung: 0,00%
GarageCars: 5 nilai, int: 100,00%, mengapung: 100,00%
HeatingQC: 5 nilai, ints: 0,00%, mengapung: 0,00%
LotConfig: 5 nilai, ints: 0,00%, mengapung: 0,00%
MasVnrType: 5 nilai, int: 0,00%, mengapung: 0,00%
MiscFeature: 5 nilai, ints: 0,00%, mengapung: 0,00%
MSZoning: 5 nilai, ints: 0,00%, mengapung: 0,00%
YrSold: 5 nilai, int: 100,00%, mengapung: 100,00%
Listrik: 6 nilai, int: 0,00%, mengapung: 0,00%
FireplaceQu: 6 nilai, ints: 0,00%, mengapung: 0,00%
Foundation: 6 nilai, ints: 0,00%, mengapung: 0,00%
GarageCond: 6 nilai, ints: 0,00%, mengapung: 0,00%
GarageQual: 6 nilai, ints: 0,00%, mengapung: 0,00%
Pemanasan: 6 nilai, int: 0,00%, mengapung: 0,00%
RoofStyle: 6 nilai, ints: 0,00%, mengapung: 0,00%
SaleCondition: 6 nilai, ints: 0,00%, mengapung: 0,00%
Nilai BsmtFinType1: 7, int: 0,00%, mengapung: 0,00%
Nilai BsmtFinType2: 7, int: 0,00%, mengapung: 0,00%
Fungsional: 7 nilai, ints: 0,00%, mengapung: 0,00%
GarageType: 7 nilai, ints: 0,00%, mengapung: 0,00%
BedroomAbvGr: 8 nilai, int: 100,00%, mengapung: 100,00%
Kondisi2: 8 nilai, ints: 0,00%, mengapung: 0,00%
HouseStyle: 8 nilai, ints: 0,00%, mengapung: 0,00%
PoolArea: 8 nilai, int: 100,00%, mengapung: 100,00%
RoofMatl: 8 nilai, ints: 0,00%, mengapung: 0,00%
Kondisi1: 9 nilai, ints: 0,00%, mengapung: 0,00%
OverallCond: 9 nilai, ints: 100.00%, mengapung: 100.00%
SaleType: 9 nilai, ints: 0,00%, mengapung: 0,00%
KeseluruhanKual: 10 nilai, int: 100,00%, mengapung: 100,00%
MoSold: 12 nilai, int: 100,00%, mengapung: 100,00%
TotRmsAbvGrd: 12 nilai, int: 100,00%, mengapung: 100,00%
Exterior1st: 15 nilai, ints: 0,00%, mengapung: 0,00%
MSSubClass: 15 nilai, int: 100,00%, mengapung: 100,00%
Exterior2nd: 16 nilai, ints: 0,00%, mengapung: 0,00%
3SsnPorch: 20 nilai, int: 100,00%, mengapung: 100,00%
MiscVal: 21 nilai, int: 100,00%, mengapung: 100,00%
LowQualFinSF: 24 nilai, int: 100,00%, mengapung: 100,00%
Sekitar: 25 nilai, int: 0,00%, mengapung: 0,00%
YearRemodTambahkan: 61 nilai, int: 100,00%, mengapung: 100,00%
ScreenPorch: 76 nilai, int: 100,00%, mengapung: 100,00%
GarageYrBlt: nilai 98, int: 94,45%, mengapung: 94,45%
LotFrontage: nilai 111, ints: 82,26%, mengapung: 82,26%
YearBuilt: 112 nilai, int: 100,00%, mengapung: 100,00%
EnclosedPorch: 120 nilai, int: 100,00%, mengapung: 100,00%
BsmtFinSF2: 144 nilai, int: 100,00%, mengapung: 100,00%
OpenPorchSF: 202 nilai, int: 100,00%, mengapung: 100,00%
WoodDeckSF: 274 nilai, int: 100,00%, mengapung: 100,00%
MasVnrArea: 328 nilai, ints: 99,45%, mengapung: 99,45%
2ndFlrSF: nilai 417, int: 100,00%, mengapung: 100,00%
GarageArea: nilai 441, int: 100,00%, mengapung: 100,00%
BsmtFinSF1: nilai 637, int: 100,00%, mengapung: 100,00%
SaleHarga: 663 nilai, int: 100,00%, mengapung: 100,00%
TotalBsmtSF: nilai 721, int: 100,00%, mengapung: 100,00%
1stFlrSF: 753 nilai, int: 100,00%, mengapung: 100,00%
BsmtUnfSF: 780 nilai, int: 100,00%, mengapung: 100,00%
GrLivArea: nilai 861, int: 100,00%, mengapung: 100,00%
LotArea: nilai 1073, int: 100,00%, mengapung: 100,00%
Banyak kolom nilai:
Eksterior1st: AsbShng, AsphShn, BrkComm, BrkFace, CBlock, CemntBd, HdBoard, ImStucc, MetalSd, Kayu Lapis, Batu, Semen, VinylSd, Wd Sdng, WdShing
Eksterior2nd: AsbShng, AsphShn, Brk Cmn, BrkFace, CBlock, CmentBd, HdBoard, ImStucc, MetalSd, Lainnya, Kayu Lapis, Batu, Semen, VinylSd, Wd Sdng, Wd Shng
Sekitar: Blmngtn, Blueste, BrDale, BrkSide, ClearCr, CollgCr, Crawfor, Edwards, Gilbert, IDOTRR, MeadowV, Mitchel, NAmes, NoRidge, NPkVill, NridgHt, NWAmes, OldTown, Sawyer, SawyerW, Somerst, Stone, Stoner Veenker
pelampung yang tidak dapat diurai
GarageYrBlt: NA
LotFrontage: NA
MasVnrArea: NA
rentang mengambang:
BsmtHalfBath: 0 ... 2
HalfBath: 0 ... 2
BsmtFullBath: 0 ... 3
Perapian: 0 ... 3
FullBath: 0 ... 3
DapurAbvGr: 0 ... 3
GarageCars: 0 ... 4
YrSold: 2006 ... 2010
Kamar TidurAbvGr: 0 ... 8
PoolArea: 0 ... 738
OverallCond: 1 ... 9
KeseluruhanQual: 1 ... 10
MoSold: 1 ... 12
TotRmsAbvGrd: 2 ... 14
MSSubClass: 20 ... 190
3SsnPorch: 0 ... 508
MiscVal: 0 ... 15500
RendahQualFinSF: 0 ... 572
YearRemodAdd: 1950 ... 2010
ScreenPorch: 0 ... 480
GarageYrBlt: 1900 ... 2010
LotFrontage: 21 ... 313
YearBuilt: 1872 ... 2010
TertutupPorch: 0 ... 552
BsmtFinSF2: 0 ... 1474
OpenPorchSF: 0 ... 547
WoodDeckSF: 0 ... 857
MasVnrArea: 0 ... 1600
2ndFlrSF: 0 ... 2065
GarageArea: 0 ... 1418
BsmtFinSF1: 0 ... 5644
SaleHarga: 34.900 ... 755.000
TotalBsmtSF: 0 ... 6110
1stFlrSF: 334 ... 4692
BSmtUnfSF: 0 ... 2336
GrLivArea: 334 ... 5642
LotArea: 1300 ... 215245
Dengan mengingat hal itu, saya membuat
ValueNormalizer berikut, yang mengambil beberapa info tentang nilai-nilai di dalam kolom, dan mengembalikan fungsi, yang mengubah nilai (
string ) menjadi vektor fitur numerik untuk jaringan saraf (
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); } }
Sekarang kita punya data yang dikonversi menjadi format, cocok untuk jaringan saraf. Sudah waktunya untuk membangun satu.
Membangun jaringan saraf
Mulai hari ini, Anda harus menggunakan mesin Windows untuk itu.Jika Anda sudah menginstal Python dan TensorFlow 1.1x, yang Anda butuhkan adalah
<PackageReference Include="Gradient" Version="0.1.10-tech-preview3" />
dalam file .csproj modern Anda. Jika tidak, lihat
manual Gradient untuk melakukan pengaturan awal.
Setelah paket aktif dan berjalan, kita dapat membuat jaringan mendalam pertama kami.
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");
Ini akan membuat jaringan saraf yang tidak terlatih dengan 3 lapisan neuron, dan lapisan putus, yang membantu mencegah overfitting.
tf.nn.relu_fntf.nn.relu_fn adalah fungsi aktivasi untuk neuron kita.
ReLU dikenal bekerja dengan baik di jaringan yang dalam, karena memecahkan
masalah gradien yang hilang : turunan dari fungsi aktivasi non-linear asli cenderung menjadi sangat kecil ketika kesalahan diperbanyak kembali dari lapisan output di jaringan yang dalam. Itu berarti, bahwa lapisan yang lebih dekat ke input hanya akan menyesuaikan sedikit, yang memperlambat pelatihan jaringan dalam secara signifikan.
Putus sekolahDropout adalah lapisan fungsi-khusus dalam jaringan saraf, yang sebenarnya tidak mengandung neuron. Sebagai gantinya, ia beroperasi dengan mengambil setiap input individu, dan secara acak menggantinya dengan 0 pada output sendiri (jika tidak hanya melewati nilai asli bersama). Dengan melakukan hal itu membantu mencegah
overfitting ke fitur yang kurang relevan dalam dataset kecil. Misalnya, jika kami tidak menghapus kolom
Id , jaringan mungkin berpotensi menghafal pemetaan <Id> -> <SalePrice>, yang akan memberi kami akurasi 100% pada set pelatihan, tetapi angka yang sama sekali tidak terkait pada data lain.
Mengapa kita perlu putus sekolah? Data pelatihan kami hanya memiliki ~ 1500 contoh, dan jaringan saraf kecil yang kami bangun ini memiliki> 1.800 bobot yang bisa disetel. Jika itu adalah polinomial sederhana, itu bisa cocok dengan fungsi harga yang kami coba perkiraan dengan tepat. Tetapi kemudian akan memiliki nilai yang sangat besar pada input di luar set pelatihan asli.
Beri makan data
TensorFlow mengharapkan datanya baik dalam array NumPy, atau tensor yang ada. Saya mengubah DataRows menjadi array 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);
Dalam kode di atas kami mengkonversi setiap
DataRow menjadi
ndarray dengan mengambil setiap sel di dalamnya, dan menerapkan
ValueNormalizer yang sesuai dengan kolomnya. Lalu kita meletakkan semua baris ke
ndarray lain, mendapatkan array array.
Tidak diperlukan transformasi seperti itu untuk keluaran, tempat kami hanya mengonversi nilai kereta ke
ndarray lain.
Saatnya turun gradien
Dengan pengaturan ini, yang perlu kita lakukan untuk melatih jaringan kita adalah memanggil fungsi
fit model:
model.fit(trainInputs, trainOutputs, epochs: 2000, validation_split: 0.075, verbose: 2);
Panggilan ini sebenarnya akan menyisihkan 7,5% terakhir dari pelatihan yang ditetapkan untuk validasi, kemudian ulangi sebanyak 2000 kali berikut ini:
- membagi sisa trainInput menjadi batch
- memberi makan batch ini satu per satu ke dalam jaringan saraf
- hitung kesalahan menggunakan fungsi kerugian yang kami definisikan di atas
- backpropagate kesalahan melalui gradien koneksi neuron individu, menyesuaikan bobot
Saat pelatihan, itu akan menampilkan kesalahan jaringan pada data yang disisihkan untuk validasi sebagai
val_loss dan kesalahan pada data pelatihan itu sendiri sebagai
kerugian saja. Secara umum, jika
val_loss menjadi lebih besar, daripada
kerugian , itu berarti jaringan mulai overfitting. Saya akan membahasnya secara lebih rinci dalam artikel-artikel berikut.
Jika Anda melakukan semuanya dengan benar,
akar kuadrat dari salah satu kerugian Anda harus berada di urutan 20000.

Kiriman
Saya tidak akan bicara banyak tentang menghasilkan file untuk dikirim di sini. Kode untuk menghitung output sederhana:
const string SubmissionInputFile = "test.csv"; DataTable submissionData = LoadData(SubmissionInputFile); var submissionRows = submissionData.Rows.Cast<DataRow>(); ndarray submissionInputs = GetInputs(submissionRows); ndarray sumissionOutputs = model.predict(submissionInputs);
yang sebagian besar menggunakan fungsi, yang didefinisikan sebelumnya.
Maka Anda perlu menuliskannya ke file .csv, yang hanya merupakan daftar pasangan Id, predict_value.
Ketika Anda mengirimkan hasil Anda, Anda harus mendapatkan skor pada urutan 0,17, yang akan berada di suatu tempat di kuartal terakhir tabel papan publik. Tapi hei, jika sesederhana jaringan 3 layer dengan 27 neuron, para ilmuwan data sial itu tidak akan mendapatkan $ 300k + / y total kompensasi dari perusahaan-perusahaan besar AS
Membungkus
Kode sumber lengkap untuk entri ini (dengan semua pembantu, dan beberapa bagian komentar dari eksplorasi dan eksperimen saya sebelumnya) adalah sekitar 200 baris di
PasteBin .
Pada artikel berikutnya Anda akan melihat orang-orang jahat saya mencoba masuk ke dalam 50% teratas dari papan peringkat publik. Ini akan menjadi petualangan seorang penjelajah amatir, pertarungan dengan The Windmill of Overfitting dengan satu-satunya alat yang dimiliki pengembara - model yang lebih besar (mis. NN dalam, ingat, tidak ada rekayasa fitur manual!). Ini akan menjadi kurang dari tutorial pengkodean, dan lebih dari pencarian pikiran dengan matematika yang benar-benar jahat dan kesimpulan yang aneh. Tetap disini!
Tautan
KagglePersaingan Harga Rumah di KaggleTutorial regresi TensorFlowHalaman muka TensorFlowReferensi TensorFlow APIGradient (TensorFlow binding)