-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathneuralnetwork_intro.jl
More file actions
2246 lines (1857 loc) · 85.8 KB
/
neuralnetwork_intro.jl
File metadata and controls
2246 lines (1857 loc) · 85.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
### A Pluto.jl notebook ###
# v0.19.5
using Markdown
using InteractiveUtils
# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
quote
local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
local el = $(esc(element))
global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
el
end
end
# ╔═╡ 423cb435-ab09-426e-a29a-0b894fc767ba
begin
using Downloads
using CSV, DataFrames
using MLBase, MLDataUtils
using StatsBase, GLM
using Flux
using Flux: sigmoid, binarycrossentropy, logitbinarycrossentropy
using Plots#, LaTeXStrings
using ValueHistories
using PlutoUI, ProgressLogging
end
# ╔═╡ 2671099c-667e-4c1b-9e5d-f41bd5752938
md"""
## Overview
In this lab, we'll revise the dataset of SDSS & Spitzer photometry with the goal of developing an improved classifier to identify high-${z}$ quasars.
We'll start by fitting logistic regression models using the framework of generalized linear models.
Then, we'll train an equivalent neural network classifier.
You'll get a chance to explore different strategies for training the model.
Finally, you'll get a chance to explore more complex neural network architectures and see if you can build a significantly better neural network classifier.
As before, we first need to read in the dataset.
"""
# ╔═╡ 783788a0-6f9a-4225-98e6-5030d9f21712
md"""
## Prepare the data
### Read data from file
"""
# ╔═╡ 1c792c1d-f1e8-4e5f-8a76-5c7ca5fb8587
md"""
### Create subsets of data for training & testing
We want to divide the dataset into two distinct subsets: `df_cv` (a DataFrame of observations to be used with a cross-validation procedure) and `df_test` (a DataFrame of observations to be used to testing our final model), so that we can use one for model building and the second for testing our final model.
Just in case there's any pattern in the order of the data, we'll shuffle the order of the observations _prior_ to assigning the observations to the two subsets.
The starter code places 66% of the observations into `df_cv` and the rest into `df_test`.
Since high-${z}$ quasars are relatively rare, we'll make a point of using [stratified sampling](https://en.wikipedia.org/wiki/Stratified_sampling), so that both `df_cv` and `df_test` have (nearly) the same fraction of high-${z}$ quasars.
Similarly, since high-${z}$ quasars are relatively rare in our dataset, one might try undersampling the other objects, so that our dataset is nearly balanced.
(Once you've worked through the first section of the notebook, you might want to come back and uncheck the box and see how the results change if you use an unbalanced training set.)
"""
# ╔═╡ 26635f45-1e34-4025-8151-2185d8d84e06
md"""
Undersample non-high-${z}$ quasars to make for balanced datasets?
$(@bind make_balanced CheckBox(default=true))
"""
# ╔═╡ ffe4cdc7-4863-4f0e-b790-4b86afcc56b8
md"""
### Constructing subset for K-fold cross-validation
Eventually, you'll be experimenting with multiple neural network architectures.
We want to save the data in `df_test` for testing after we've finalized all our model choices.
In the mean time, we'll want to have subsets of the observations in `df_cv` for training and validating as part of a **k-fold cross-validation** process.
Below, you can choose how many "folds" to use and which fold will be used for the validation set. That choice implicitly determine which n-1 folds will be used for training our logistic regression and neural network models.
"""
# ╔═╡ 7e0341a3-e52e-4a19-b9ac-969ebdd2161f
md"""
For convenience sake, we'll define several dataframes containing data based on your chosen fold.
"""
# ╔═╡ 4aff0575-014d-4757-8e69-f49ad1f2e82e
md"""
## Logistic Regression Model
In this section, we'll use the general linear regression framework to efficiently fit a logistic regression model.
If you haven't worked through the [logistic regression application lab](), it's suggested you do that first.
We specify a formula specifying that we want to predict the data in column "label" using input data in all the other columns (and an offset term).
"""
# ╔═╡ 65472ed7-4c0a-4695-8dc3-94575a0241dd
md"And we fit a logistic regresion model to the selected training dataset."
# ╔═╡ b419a260-8ec8-46f9-bc5b-0fdd09717867
md"""
Below are the confusion matrices for the selected training and validation sets and the test dataset. The bottom right is the fraction of objects that are both labeled and predicted to be high-${z}$ quasars. The upper left is the fraction of objects both labeled and predicted to be other objects.
"""
# ╔═╡ 6636da1a-64c0-4c12-b102-82a5d49df114
md"""
**Question:** How do these confusion matrices compare to those you obtained as part of the logistic regression lab?
!!! hint "Hint"
The false discovery rates and false omission rates were quite different in the previous lab. Which of the extra steps we took this time is responsible for making them more similar?
"""
# ╔═╡ 7022d21e-10f3-46ac-a5fa-b4b6f7ab7881
md"""
### Compare results across folds
Since generalized linear regression is computationally efficient, we can go ahead and fit logistic regression models (one for each of the fold of data).
Below we show histograms of the false discovery rates and false omission rates for each fold.
The next cell shows the coefficients fit the training set for each fold.
"""
# ╔═╡ f1e1d4ab-d1bf-4684-b3f6-3233b689ba71
md"We can compute the mean and standard deviation of each coefficient across the training data set for each different folds."
# ╔═╡ 54f52161-2337-40fc-a743-58cc0492726f
md"""
**Question:** How do the mean and standard deviation of coefficients from the cross validation procedure compare to the standard errors returned by the `glm` function above? What factors could contribute to the differences?
"""
# ╔═╡ 84c733a4-5f23-42d6-bea5-c7cc89674d41
md"In the subsequent sections on neural networks, we'll use a loss function that is equal to negative log likelihood divided by the number of observations. Let's compute that loss function for results of the standard logistic regression model on the training dataset for each fold, so we'll be able to make comparisons later."
# ╔═╡ bd6b7f5f-7f64-41ce-9ce8-639d6435800a
md"""
## Simple Neural Network for Logistic Regression
Logistic regression can be represented as a very simple neural network.
Doing so will allow us to try out some of the tools and programming patterns used for training neural networks, while still getting (reasonably) prompt feedback.
To implement logistic regression as a neural network, we'll connect all six of the input parameters (six nodes in the input layer) to one node in the output layer and use the sigmoid transfer function (`sigmoid(x) = 1/(1+exp(-x))`). This pattern is so common that any library for building neural networks will provide a function to build such a neural network easily. Using Julia's Flux package, it's just `model_nn0 = Dense(6, 1, Flux.sigmoid)`.
"""
# ╔═╡ ddb12c2e-2764-432c-bc08-c056689e26fd
md"""
To train a neural network, we need to specify several key elements:
- parameters of the neural network:
- loss function: The function to be minimized as a function of the parameters of the neural network.
- training data: The observational data to be used by the loss function
- optimizer: The algorithm that will be used for updating the parameters of the neural network.
Since neural networks can have *lots* of parameters, neural network libraries provide convenience functions to extract the parameters from a network. We did that above with Flux's `params` function.
"""
# ╔═╡ 5abe0169-d7e1-4fd2-b5b5-aae1a693572a
md"**Question:** How many model parameters will this neural network have?"
# ╔═╡ ec3a3d20-f7f0-491c-8b3a-69028ddf036f
md"""
!!! hint "Hint"
sum(length.(nn_param)) = 7
"""
# ╔═╡ fdc82078-1a20-4a9b-84f0-d2010f258f03
md"""
As discussed in the logistic regression lab, the log likelihood for logistic regresssion is
$\ell(w)= \sum_{i=1}^n\left[y_i\log(h_w(x_i)) + (1-y_i)\log(1-h_w(x_i))\right],$
where $y_i$ are the labels for the $i$th object and $x_i$ are the input data for the $i$th object and $h_w(x_i)$ is the value of the output node, evaluated for the input data $x_i$. The subscript $w$ emphasizes that the output of the neural network depends on a the values of the network's weights (and biases).
We estimate weights $w$ by the values that maximize the log-likelihood function above, or equivalently the values that minimize the negative log-likelihood function divided by the number of observations. This is already coded up as the function `binarycrossentropy(predictions, observations)`.
!!! tip "Terminology"
In the machine learning community, the function to be minimized is commonly called the loss function (or cost function). This is slightly more general, since the loss function does not necessarily need to be proportional to a negative log likelihood. For example, a loss function could also include a log prior term or a more arbitrary regularization penalty term.
"""
# ╔═╡ 014fb3c0-ae99-494a-8b2b-c02f4c6c6dd8
md"""
!!! tip "Pro tip:"
We could get better numerical stability by defining the neural network to use a linear transfer function and replacing the loss function with `logitbinarycrossentropy`.
"""
# ╔═╡ 12028b28-1547-40e6-8263-b32a80613914
md"""
It's natural to start using the gradient descent algorithm for optimizing our neural network parameters and a high learning rate of 0.9. We could do that with `optimizer = Descent(0.9)`. In practice, we've specified that the optimizer and its learning rate will be determined by your choice from a drop down box below.
"""
# ╔═╡ 83d2d3c5-f8ec-4bd6-9660-4dda9db75131
md"Now that all the elements are in place, you can visualize the results of training our neural network implementing logistic regression. For starters, it defaults to using just 100 iterations (so the notebook responds quickly). But you'll want to try continuing to train the network for more iterations to see if it can improve further.
You can click `submit` below to continue training the neural network with specified learning rate and optimization algorithm. Depending on the number of iterations it may take significant time to train the neural network and to update the plot below. If the progress bar below is less than 100%, then please be patient while the neural network is training. There's also a button you can click to reinitialize your neural network's weights (with random values)."
# ╔═╡ 0a8873a5-519a-4e6e-8b19-6fe6fc23fb1c
@bind reinit_nn Button("Reinitialize NN weights")
# ╔═╡ 7dccea44-ebe2-4882-8634-019c049591b2
begin
reinit_nn # trigger rerunning this cell when button is clicked
# define neural network w/ 6 inputs, 1 output (and 0 hidden layers)
model_nn0 = Dense(6, 1, sigmoid)
# extract the parameters of that neural network
nn_param = Flux.params(model_nn0)
end;
# ╔═╡ aca55f65-b364-4c20-8cc1-1dd0d4102ea1
loss(x,y) = Flux.binarycrossentropy(model_nn0(x), y)
#= Or we could write the equivalent loss function ourselves using
function loss(x_obs,y_obs)
ϵ = 1e-12 # To prevent issues with log(0)
pred = model_nn0(x_obs)
-mean( y_obs.*log.(pred.+ϵ) .+ (1 .-y_obs).*log.(1 .- pred .+ ϵ))
end
=#
# ╔═╡ ff3f98bf-d3b6-4937-86d1-47e0080a6568
md"""
**Question:** How many iterations did you need to acheive a neural network classifier that performs comparably to the logistic regression classifier above?
**Question:** How does the computational cost of training the neural network compare to the cost of fitting the logistic regression model? What factors contribute to the difference?
**Question:** How does the loss function change if you continue to train the neural network for many more iterations for the training dataset? What about for the validation dataset? What causes this difference?
"""
# ╔═╡ 69a9e752-bad1-4695-83b3-55dd5054c26f
md"""
!!! tip "How did we train the network?"
If you're curious to see how the training was performed, you can read through the function `my_train!` near the end of the notebook. Any modern neural network library will provide functions to help train your network (e.g., Flux's `train!`). In this lab, we opted to use a handwritten function `my_train!` that is quite similar to `train!`, but hopefully is a little easier to read, so you can see what it's doing for yourself.
"""
# ╔═╡ 86c5f0d7-4fdd-4d99-af94-b0d3937580ac
md"""
## More General Neural Networks
In the section above, we intentionally used a very simple neural network that was equivalent to logistic regression.
One of the features of using a neural network is that one can easily scale up the network to include more nodes and layers, so that it becomes a more flexible classifier.
Below, you can select a number of hidden layers (up to 3) and number of hidden nodes in each layers. Once you've selected and submitted an architecture and strategy for optimizing your network in the boxes below, click the checkbox below to start training it.
!!! tip "Tip:"
The time required to train neural networks can increase rapidly with their size, so start small. Think about how many parameters (weights and bias terms) will be required for your neural network.
"""
# ╔═╡ 1009975c-9a8b-40f6-b9c5-b69820adc6b1
md"Check once you're ready to proceed to neural networks using one or more hidden layers. $(@bind ready_for_hidden CheckBox())"
# ╔═╡ f5f1ff7c-c674-4147-9ec1-b1b0f1a8d18a
md"Number of hidden layers: $(@bind num_hidden_layers NumberField(1:3,default=1))"
# ╔═╡ 51218a3f-c7b9-4f8e-8b85-c2e8013cd13d
md"""
**Question:** How does the number of iterations required to train the model well compare to the simpler neural network from the previous section?
**Question:** How does the resulting classifier compare that of the simple neural network or the logistic regression classifier?
**Question:** Can you find a network architecture and training strategy that performs significantly better than the simple neural network and the logistic regression classifier? What are its properties?
**Question:** Does your best neural network perform as well if you change the folds used for training and validation (way at the top of the notebook)?
**Question:** Why was it so important to pick out a distinct test dataset at the very beginning of the notebook?
"""
# ╔═╡ afccede8-4563-4a7e-bca4-4754349e73b3
md"## Setup & Helper functions"
# ╔═╡ 86744470-2b37-45c1-ab76-af838c122378
function find_or_download_data(data_filename::String, url::String)
if contains(gethostname(),"ec2.internal")
data_path = joinpath(homedir(),"data")
isdir(data_path) || mkdir(data_path)
elseif contains(gethostname(),"aci.ics.psu.edu")
data_path = joinpath("/gpfs/scratch",ENV["USER"],"Astroinformatics")
isdir(data_path) || mkdir(data_path)
data_path = joinpath(data_path,"data")
isdir(data_path) || mkdir(data_path)
else
data_path = joinpath(homedir(),"Astroinformatics")
isdir(data_path) || mkdir(data_path)
data_path = joinpath(data_path,"data")
isdir(data_path) || mkdir(data_path)
end
data_path = joinpath(data_path,data_filename)
if !(filesize(data_path) > 0)
Downloads.download(url, data_path)
end
return data_path
end
# ╔═╡ 49f371db-1ad1-4f1c-b5e2-c00a52035c6a
begin
filename = "quasar2.csv"
url = "https://scholarsphere.psu.edu/resources/edc61b33-550d-471d-8e86-1ff5cc8d8f4d/downloads/19732"
data_path = find_or_download_data(filename, url);
end
# ╔═╡ 85066779-be0f-43a3-bde8-c4ab5a3e5ca3
begin
df = CSV.read(data_path, DataFrame, limit=1_000_000, select=[:ug, :gr, :ri, :iz, :zs1, :s1s2, :label], ntasks=1)
df[:,:label] .= 1 .- df[:,:label] # Make label=1 for high-z quasars
col_names = names(df)
df
end
# ╔═╡ f6e68394-34b6-453c-929f-7cc89434e179
base_rate_input = sum(df.label.==1)/length(df.label)
# ╔═╡ 0985bcc3-e686-4aa9-b832-0141cb27c4a4
begin
frac_data_used_for_cv = 0.66
df_cv, df_test = stratifiedobs(x->x.label==1, shuffleobs(df), p=frac_data_used_for_cv);
if make_balanced
df_cv = undersample(x->Bool(x.label),df_cv)
df_test = undersample(x->Bool(x.label),df_test)
end
end;
# ╔═╡ 798d218e-47d7-4c1a-bef4-8a93c0e52236
(;num_in_cv_set = size(df_cv,1), num_in_test_set = size(df_test,1) )
# ╔═╡ fecc9af4-7486-4d21-87bb-5ed1e35cdf6c
fm_logistic_regression = term("label") ~ term(1) + sum(term.(setdiff(col_names,["label"])));
# ╔═╡ ce34a578-ceaf-4c6e-aa73-9ab54cf3cf99
function stratified_kfolds(label::AbstractVector, data, num_folds::Integer)
@assert length(label) == size(data,1)
list_of_folds_idx = StratifiedKfold(label,num_folds)
data_train = map(idx->datasubset(data, idx),list_of_folds_idx)
data_test = map(idx->datasubset(data, setdiff(1:length(label),idx)),
list_of_folds_idx)
(;data_train, data_test, folds_idx = list_of_folds_idx)
end
# ╔═╡ c41187d6-4306-4a92-b10e-c7825e79e79e
begin
# For GLM models
classify(model::RegressionModel, data::AbstractDataFrame; threshold::Real=0.5) = predict(model,data).>=threshold
# For Flux models
classify(model::Union{Chain,Dense}, data::AbstractMatrix; threshold::Real=0.5) = model(data).>=threshold
classify(model::Union{Chain,Dense}, data::AbstractDataFrame; threshold::Real=0.5) = classify(model, Matrix(data)', threshold=threshold)'
end
# ╔═╡ b38f5368-b7a8-4c24-9d3b-e8159fa992da
accuracy(model, data::AbstractDataFrame, y::AbstractVector ) =
sum(classify(model,data) .== Bool.(y) )/length(y)
# ╔═╡ 7d25013d-04f7-474f-8349-e623353c4fa0
function confusion_matrix_int01(a, b)
@assert all(map(x->in(x,(0,1)),a))
@assert all(map(x->in(x,(0,1)),b))
transpose(hcat(.!Bool.(a), Bool.(a))) * hcat(.!Bool.(b), Bool.(b))
end
# ╔═╡ 7931116b-3b3f-455c-80aa-17de872a8965
function calc_classification_diagnostics(model, data, label; threshold = 0.5)
pred = classify(model, data; threshold=threshold)
num_true_positives = sum( label.==1 .&& pred)
num_true_negatives = sum( label.==0 .&& .!pred)
num_false_negatives = sum( label.==1 .&& .!pred)
num_false_positives = sum( label.==0 .&& pred)
num_condition_positives = num_true_positives + num_false_negatives
num_condition_negatives = num_true_negatives + num_false_positives
num_total = num_condition_positives + num_condition_negatives
num_predicted_positives = num_true_positives + num_false_positives
num_predicted_negatives = num_true_negatives + num_false_negatives
true_positive_rate = num_true_positives/num_condition_positives
true_negative_rate = num_true_negatives/num_condition_negatives
false_positive_rate = num_false_positives/num_condition_negatives
false_negative_rate = num_false_negatives/num_condition_positives
accuracy = (num_true_positives+num_true_negatives)/num_total
false_omission_rate = num_false_negatives / num_predicted_negatives
false_discovery_rate = num_false_positives / num_predicted_positives
F1_score = 2*num_true_positives/(2*num_true_positives+num_false_positives+num_false_negatives)
prevalence = (num_true_positives+num_false_negatives)/num_total
return (;threshold, accuracy, false_discovery_rate, false_omission_rate, F1_score,
false_positive_rate, false_negative_rate, true_positive_rate, true_negative_rate,
num_true_positives, num_true_negatives, num_false_positives, num_false_negatives,
num_condition_positives, num_condition_negatives, num_predicted_positives, num_predicted_negatives,
num_total, prevalence )
end
# ╔═╡ 065cad81-4641-4517-a06c-817826e0fd9e
function run_cv_on_logistic_regression(df::AbstractDataFrame, folds_idx)
@assert length(folds_idx)>=1
@assert isa(first(folds_idx), AbstractVector{Int64})
@assert length(first(folds_idx))>=1
local history = MVHistory()
# fold_idx comes from something like StratifiedKfold(df.label, num_folds )
for (i,train_idx) in enumerate(folds_idx)
validation_idx = setdiff(1:length(df.label),train_idx)
model_logistic_regression = glm(fm_logistic_regression, df[train_idx,:], Binomial(), ProbitLink())
result_train = calc_classification_diagnostics(model_logistic_regression, df[train_idx,:], df.label[train_idx])
result_validation = calc_classification_diagnostics(model_logistic_regression, df[validation_idx,:], df.label[validation_idx])
push!(history, :model, i, model_logistic_regression)
push!(history, :loglikelihood, i, loglikelihood(model_logistic_regression))
push!(history, :results_train, i, result_train)
push!(history, :results_validation, i, result_validation)
end
return history
end
# ╔═╡ 7179f3e7-7b8a-468b-847d-5962ce0c1a93
function my_train!(model_nn::Union{Dense,Chain}, loss::Function, param::Flux.Zygote.Params{PT},
train_data::DT, optimizer::Flux.Optimise.AbstractOptimiser;
#= begin optional parameters =#
epochs::Integer=1, test_data = nothing) where { PT<:Any, MT1<:AbstractMatrix, MT2<:AbstractMatrix, DT<:Tuple{MT1,MT2} }
@assert 1<=epochs<Inf
if !isnothing(test_data)
x_test, y_test = test_data
end
history = MVHistory() # For storing intermediate results
@progress for i in 1:epochs
x, y = train_data
results_train = calc_classification_diagnostics(model_nn, x, y)
if !isnothing(test_data) # if test/validation data is provied, evaluate model for it, too.
results_test = calc_classification_diagnostics(model_nn, x_test, y_test)
push!(history, :results_test, i, results_test)
loss_test = loss(x_test, y_test)
push!(history, :loss_test, i, loss_test )
end
gs = gradient(param) do
loss(x,y)
end
push!(history, :loss, i, loss(x,y) )
push!(history, :results_train, i, results_train)
push!(history, :param, i, param)
Flux.Optimise.update!(optimizer, param, gs)
end
return history
end
# ╔═╡ 5cbd25fc-557b-4578-b765-72fcd384d6e0
function plot_classifier_training_history(h::MVHistory, idx_plt)
plt1 = plot(xlabel="Iteration", ylabel="False Discovery Rate", legend=:none)
plt2 = plot(xlabel="Iteration", ylabel="False Omission Rate", legend=:none)
plt3 = plot(xlabel="Iteration", ylabel="Loss", legend=:topright)
scatter!(plt1,get(h,:results_train)[1][idx_plt],
get.(get(h,:results_train)[2][idx_plt],
:false_discovery_rate,nothing), ms=2, markerstrokewidth=0, alpha=0.5, label="Training")
scatter!(plt2,get(h,:results_train)[1][idx_plt],
get.(get(h,:results_train)[2][idx_plt],
:false_omission_rate,nothing), ms=2, markerstrokewidth=0, alpha=0.5, label="Training")
plot!(plt3,get(h,:loss)[1][idx_plt],
get(h,:loss)[2][idx_plt],
label="Training")
if haskey(h,:results_test)
scatter!(plt1,get(h,:results_test)[1][idx_plt],
get.(get(h,:results_test)[2][idx_plt],
:false_discovery_rate,nothing),
ms=2, markerstrokewidth=0, alpha=0.5, label="Validation")
scatter!(plt2,get(h,:results_test)[1][idx_plt],
get.(get(h,:results_test)[2][idx_plt],
:false_omission_rate,nothing),
ms=2, markerstrokewidth=0, alpha=0.5, label="Validation")
plot!(plt3,get(h,:loss_test)[1][idx_plt],
get(h,:loss_test)[2][idx_plt],
alpha=0.7, label="Validation")
end
l = @layout [ a; b c]
plot(plt3, plt1,plt2, layout=l)
end
# ╔═╡ 56529037-956d-4980-875e-85b0eb5644e0
TableOfContents()
# ╔═╡ 2bb65491-d291-4d71-ac5d-247538a1871b
nbsp = html" "
# ╔═╡ 608d9156-f8a8-4886-ae87-b1adab904de5
@bind param_fold confirm(PlutoUI.combine() do Child
md"""
Number of folds: $(Child("num_folds", NumberField(1:10,default=5))) $nbsp $nbsp
Fold to use for validation: $(Child("fold_id", NumberField(1:10)))
""" end )
# ╔═╡ ca25abcc-4f7e-4ace-861b-c8f0416584ed
if !(1<=param_fold.fold_id<=param_fold.num_folds)
md"""
!!! warn "fold_id must be between 1 and the number of folds"
"""
else
df_train_list, df_validation_list, list_of_folds_idx = stratified_kfolds(df_cv.label,df_cv,param_fold.num_folds)
num_in_training_set = size(first(df_train_list),1)
num_in_validation_set = size(first(df_validation_list),1)
nothing
end
# ╔═╡ 73c4adf8-4c5e-4df5-9ba8-3099e79d3dcf
Markdown.parse("""
**Question:** Based on your choices above, approximately how many observations will be in each of the training datasets? What about the validation datasets?
!!! hint "Answer:"
Each training dataset has $num_in_training_set observations.
Each validation dataset has $num_in_validation_set observations.
""")
# ╔═╡ 360bb5b4-ef16-4ba0-a4c0-dd4761a0e13d
lr_cv_results = run_cv_on_logistic_regression(df_cv, list_of_folds_idx);
# ╔═╡ 465c5c55-0d6d-444e-85fe-c40e4b79c462
let
plt1 = plot(legend=:topleft)
train_fdr = get.(get(lr_cv_results,:results_train)[2],:false_discovery_rate,nothing)
valid_fdr = get.(get(lr_cv_results,:results_validation)[2],:false_discovery_rate,nothing)
h_fdr = fit(Histogram, vcat(train_fdr,valid_fdr), nbins=20)
histogram!(plt1,train_fdr, bins=h_fdr.edges, label="Train")
histogram!(plt1,valid_fdr, bins=h_fdr.edges, label="Validation")
xlabel!(plt1,"False Discovery Rate")
xlims!(plt1,0,maximum(first(h_fdr.edges)))
title!(plt1, "Logistic Regression Cross-validation results")
plt2 = plot(legend=:topleft)
train_for = get.(get(lr_cv_results,:results_train)[2],:false_omission_rate,nothing)
valid_for = get.(get(lr_cv_results,:results_validation)[2],:false_omission_rate,nothing)
h_for = fit(Histogram, vcat(train_for, valid_for), nbins=20)
histogram!(plt2, train_for, bins=h_for.edges, label="Train")
histogram!(plt2, valid_for, bins=h_for.edges, label="Validation")
xlabel!(plt2,"False Omission Rate")
xlims!(plt2,0,maximum(first(h_for.edges)))
l = @layout [a;b]
plot(plt1,plt2, layout=l)
end
# ╔═╡ 154a7a1d-170c-45a7-969a-83be985dbb0b
lr_cv_coeffs = hcat(coef.(get(lr_cv_results,:model)[2])...)
# ╔═╡ 324b3e47-15a0-4c5a-9d52-2f827c9fddd3
(;μ=mean(lr_cv_coeffs, dims=2) , σ=std(lr_cv_coeffs, dims=2) )
# ╔═╡ ddf9b625-3396-4866-b4ee-ac1cdd5b31f2
lr_cv_loss = -loglikelihood.(get(lr_cv_results,:model)[2])./length.(list_of_folds_idx)
# ╔═╡ 20965548-d105-4f59-ae1f-b08740c35de2
(;lr_cv_loss_mean = mean(lr_cv_loss), lr_cv_loss_std = std(lr_cv_loss) )
# ╔═╡ 6f2c856c-3bd3-4d35-be93-1b78c68c6b29
begin
train_Xy, validation_Xy = df_train_list[param_fold.fold_id], df_validation_list[param_fold.fold_id]
# Make some convenient variable names for use later
train_X = select(train_Xy, Not(:label), copycols=false)
train_y = select(train_Xy, (:label), copycols=false)
validation_X = select(validation_Xy, Not(:label), copycols=false)
validation_y = select(validation_Xy, (:label), copycols=false)
end;
# ╔═╡ 30090fcd-7c1b-4218-b7fa-d0d917cc13fe
model_logistic_regression = glm(fm_logistic_regression, train_Xy, Binomial(), ProbitLink())
# ╔═╡ d704ffca-e4c9-4b8f-ad4a-07e38fde820f
begin
confusion_training = confusion_matrix_int01(train_y.label, classify(model_logistic_regression, train_X) ) ./length(train_y.label)
confusion_validation = confusion_matrix_int01(validation_y.label, classify(model_logistic_regression, validation_X) ) ./length(validation_y.label)
confusion_testing = confusion_matrix_int01(df_test.label, classify(model_logistic_regression, df_test) )./length(df_test.label)
(;train=confusion_training, valid=confusion_validation, test=confusion_testing)
end
# ╔═╡ 0a736aaa-ba63-41ab-aab1-3f33f6cb7db0
@bind opt_param confirm(
PlutoUI.combine() do Child
md"""
Learning Rate: $( Child("learning_rate",NumberField(0.05:0.05:1, default=0.9)) )
$nbsp $nbsp $nbsp
Optimizer: $( Child("type",Select([Descent => "Gradient Descent", Nesterov => "Nesterov Momentum", ADAM => "ADAM" ])) )
Iterations: $( Child("iterations",NumberField(10:10:2000, default=100)))
$nbsp $nbsp $nbsp
Compute validation data: $( Child("calc_validation_results", CheckBox()))
"""
end
)
# ╔═╡ 776afa73-136a-4346-acaa-2a81a0d03728
optimizer = opt_param.type(opt_param.learning_rate) # Set based on inputs above
# ╔═╡ d0124cc9-73e2-4374-b568-4c01c0396629
begin
local train_data = (Matrix(train_X)', Matrix(train_y)')
if opt_param.calc_validation_results
local validation_data = (Matrix(validation_X)', Matrix(validation_y)')
history_nn0 = my_train!(model_nn0, loss, nn_param, train_data, optimizer,
test_data = validation_data , epochs=opt_param.iterations)
else
history_nn0 = my_train!(model_nn0, loss, nn_param, train_data, optimizer,
epochs=opt_param.iterations)
end
end;
# ╔═╡ 867f8614-794b-4fc7-90df-27037d28d56f
md"""
First iteration to plot: $(@bind first_iter_to_plot Slider(1:opt_param.iterations))
Last iteration to plot: $(@bind last_iter_to_plot Slider(1:opt_param.iterations,default=opt_param.iterations))
"""
# ╔═╡ e7617122-3141-404d-b429-8312d41e08ae
plot_classifier_training_history(history_nn0,first_iter_to_plot:last_iter_to_plot)
# ╔═╡ 9eea2ceb-94ee-4aa8-8f5e-ffdd4debe174
@bind num_nodes confirm(PlutoUI.combine() do Child
md"""
Nodes in each layer:
Hidden Layer 1: $(Child("hidden1",NumberField(1:10,default=2)))
$nbsp $nbsp
Hidden Layer 2: $(Child("hidden2",NumberField(0:(num_hidden_layers>=2 ? 10 : 0),default=num_hidden_layers>=2 ? 1 : 0)))
$nbsp $nbsp
Hidden Layer 3: $(Child("hidden3",NumberField(0:(num_hidden_layers>=3 ? 10 : 0),default=num_hidden_layers>=3 ? 1 : 0)))
"""
end)
# ╔═╡ 2af3988d-6a46-4ae6-ab77-9de5270bf657
md"Reinitialize neural network with hidden nodes: $nbsp $(@bind reinit_my_nn Button())"
# ╔═╡ c33ba176-e1bd-46a8-afca-a3d82eb4bc1a
if ready_for_hidden
reinit_my_nn # trigger rerunning this cell when button is clicked
if num_hidden_layers == 1
model_my_nn = Chain( Dense(6,num_nodes.hidden1, Flux.sigmoid),
Dense(num_nodes.hidden1, 1, Flux.sigmoid) )
elseif num_hidden_layers == 2
model_my_nn = Chain( Dense(6,num_nodes.hidden1, Flux.sigmoid),
Dense(num_nodes.hidden1, num_nodes.hidden2, Flux.sigmoid),
Dense(num_nodes.hidden2, 1, Flux.sigmoid) )
elseif num_hidden_layers == 3
model_my_nn = Chain( Dense(6,num_nodes.hidden1, Flux.sigmoid),
Dense(num_nodes.hidden1, num_nodes.hidden2, Flux.sigmoid),
Dense(num_nodes.hidden2, num_nodes.hidden3, Flux.sigmoid),
Dense(num_nodes.hidden3, 1, Flux.sigmoid) )
else
md"""!!! warn "Invalid number of layers"""
end
my_nn_param = Flux.params(model_my_nn)
my_loss(x,y) = Flux.binarycrossentropy(model_my_nn(x), y)
end;
# ╔═╡ 24740804-7333-4e93-aff9-badede5c440c
if ready_for_hidden
num_param_in_my_nn = sum(length.(my_nn_param))
md"The new neural network with $num_hidden_layers has $num_param_in_my_nn parameters."
end
# ╔═╡ c799d55a-2fb9-4b0a-8ebf-12f9cd4b95db
begin
@bind my_opt_param confirm(
PlutoUI.combine() do Child
md"""
Learning Rate: $( Child("learning_rate",NumberField(0.05:0.05:1, default=0.9)) )
$nbsp $nbsp $nbsp
Optimizer: $( Child("type",Select([Descent => "Gradient Descent", Nesterov => "Nesterov Momentum", ADAM => "ADAM" ], default=Nesterov)) )
$nbsp $nbsp $nbsp
Iterations: $( Child("iterations",NumberField(10:10:2000, default=500)))
"""
end
)
end
# ╔═╡ a4e39577-39ff-4295-a345-c580a062ad01
if ready_for_hidden
my_optimizer = my_opt_param.type(my_opt_param.learning_rate) # Set based on inputs above
end;
# ╔═╡ 7369a73e-a04f-49c9-83f8-82633f8c3efb
if ready_for_hidden
local train_data = (Matrix(train_X)', Matrix(train_y)')
local validation_data = (Matrix(validation_X)', Matrix(validation_y)')
history_my_nn = my_train!(model_my_nn, my_loss, my_nn_param, train_data, my_optimizer,
test_data = validation_data, epochs=my_opt_param.iterations)
end;
# ╔═╡ 86499d0e-bad3-4954-a740-68cba383d790
if ready_for_hidden
md"""
First iteration to plot: $(@bind first_iter_to_plot_hidden Slider(1:my_opt_param.iterations))
Last iteration to plot: $(@bind last_iter_to_plot_hidden Slider(1:my_opt_param.iterations,default=my_opt_param.iterations))
"""
end
# ╔═╡ 9b55d85d-e5b0-46d6-bce8-6f1cbdd991ee
if ready_for_hidden
plot_classifier_training_history(history_my_nn,first_iter_to_plot_hidden:last_iter_to_plot_hidden)
end
# ╔═╡ 9c5a7bb8-2017-45e5-b56e-3745fc775e7c
br = html"<br />"
# ╔═╡ 1d8fe699-810b-44ba-a9cd-80816338e08c
md"""
# Lab 14: Neural Networks for $br Identifying high-z quasars
#### [Penn State Astroinformatics Summer School 2022](https://sites.psu.edu/astrostatistics/astroinfo-su22-program/)
#### [Eric Ford](https://www.personal.psu.edu/ebf11)
"""
# ╔═╡ 3a1d0d17-b17b-4a6a-b997-f3924211ea2d
md"### Old code"
# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c"
GLM = "38e38edf-8417-5370-95a0-9cbb8c7f171a"
MLBase = "f0e99cf1-93fa-52ec-9ecc-5026115318e0"
MLDataUtils = "cc2ba9b6-d476-5e6d-8eaf-a92d5412d41d"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
ProgressLogging = "33c8b6b6-d38a-422a-b730-caa89a2f386c"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
ValueHistories = "98cad3c8-aec3-5f06-8e41-884608649ab7"
[compat]
CSV = "~0.10.4"
DataFrames = "~1.3.4"
Flux = "~0.13.0"
GLM = "~1.7.0"
MLBase = "~0.9.0"
MLDataUtils = "~0.5.4"
Plots = "~1.29.0"
PlutoUI = "~0.7.38"
ProgressLogging = "~0.1.4"
StatsBase = "~0.33.16"
ValueHistories = "~0.5.4"
"""
# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised
julia_version = "1.7.0"
manifest_format = "2.0"
[[deps.AbstractFFTs]]
deps = ["ChainRulesCore", "LinearAlgebra"]
git-tree-sha1 = "6f1d9bc1c08f9f4a8fa92e3ea3cb50153a1b40d4"
uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c"
version = "1.1.0"
[[deps.AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "8eaf9f1b4921132a4cff3f36a1d9ba923b14a481"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.1.4"
[[deps.Accessors]]
deps = ["Compat", "CompositionsBase", "ConstructionBase", "Future", "LinearAlgebra", "MacroTools", "Requires", "Test"]
git-tree-sha1 = "0264a938934447408c7f0be8985afec2a2237af4"
uuid = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697"
version = "0.1.11"
[[deps.Adapt]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "af92965fb30777147966f58acb05da51c5616b5f"
uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
version = "3.3.3"
[[deps.ArgCheck]]
git-tree-sha1 = "a3a402a35a2f7e0b87828ccabbd5ebfbebe356b4"
uuid = "dce04be8-c92d-5529-be00-80e4d2c0e197"
version = "2.3.0"
[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
[[deps.ArrayInterface]]
deps = ["Compat", "IfElse", "LinearAlgebra", "Requires", "SparseArrays", "Static"]
git-tree-sha1 = "81f0cb60dc994ca17f68d9fb7c942a5ae70d9ee4"
uuid = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
version = "5.0.8"
[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
[[deps.BFloat16s]]
deps = ["LinearAlgebra", "Printf", "Random", "Test"]
git-tree-sha1 = "a598ecb0d717092b5539dbbe890c98bac842b072"
uuid = "ab4f0b2a-ad5b-11e8-123f-65d77653426b"
version = "0.2.0"
[[deps.BangBang]]
deps = ["Compat", "ConstructionBase", "Future", "InitialValues", "LinearAlgebra", "Requires", "Setfield", "Tables", "ZygoteRules"]
git-tree-sha1 = "b15a6bc52594f5e4a3b825858d1089618871bf9d"
uuid = "198e06fe-97b7-11e9-32a5-e1d131e6ad66"
version = "0.3.36"
[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
[[deps.Baselet]]
git-tree-sha1 = "aebf55e6d7795e02ca500a689d326ac979aaf89e"
uuid = "9718e550-a3fa-408a-8086-8db961cd8217"
version = "0.1.1"
[[deps.Bzip2_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "19a35467a82e236ff51bc17a3a44b69ef35185a2"
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
version = "1.0.8+0"
[[deps.CEnum]]
git-tree-sha1 = "eb4cb44a499229b3b8426dcfb5dd85333951ff90"
uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
version = "0.4.2"
[[deps.CSV]]
deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings"]
git-tree-sha1 = "873fb188a4b9d76549b81465b1f75c82aaf59238"
uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
version = "0.10.4"
[[deps.CUDA]]
deps = ["AbstractFFTs", "Adapt", "BFloat16s", "CEnum", "CompilerSupportLibraries_jll", "ExprTools", "GPUArrays", "GPUCompiler", "LLVM", "LazyArtifacts", "Libdl", "LinearAlgebra", "Logging", "Printf", "Random", "Random123", "RandomNumbers", "Reexport", "Requires", "SparseArrays", "SpecialFunctions", "TimerOutputs"]
git-tree-sha1 = "19fb33957a5f85efb3cc10e70cf4dd4e30174ac9"
uuid = "052768ef-5323-5732-b1bb-66c8b64840ba"
version = "3.10.0"
[[deps.Cairo_jll]]
deps = ["Artifacts", "Bzip2_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Pkg", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"]
git-tree-sha1 = "4b859a208b2397a7a623a03449e4636bdb17bcf2"
uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a"
version = "1.16.1+1"
[[deps.ChainRules]]
deps = ["ChainRulesCore", "Compat", "IrrationalConstants", "LinearAlgebra", "Random", "RealDot", "SparseArrays", "Statistics"]
git-tree-sha1 = "de68815ccf15c7d3e3e3338f0bd3a8a0528f9b9f"
uuid = "082447d4-558c-5d27-93f4-14fc19e9eca2"
version = "1.33.0"
[[deps.ChainRulesCore]]
deps = ["Compat", "LinearAlgebra", "SparseArrays"]
git-tree-sha1 = "9950387274246d08af38f6eef8cb5480862a435f"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.14.0"
[[deps.ChangesOfVariables]]
deps = ["ChainRulesCore", "LinearAlgebra", "Test"]
git-tree-sha1 = "1e315e3f4b0b7ce40feded39c73049692126cf53"
uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
version = "0.1.3"
[[deps.CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.0"
[[deps.ColorSchemes]]
deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "Random"]
git-tree-sha1 = "7297381ccb5df764549818d9a7d57e45f1057d30"
uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
version = "3.18.0"
[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "a985dc37e357a3b22b260a5def99f3530fb415d3"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.2"
[[deps.ColorVectorSpace]]
deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "SpecialFunctions", "Statistics", "TensorCore"]
git-tree-sha1 = "3f1f500312161f1ae067abe07d13b40f78f32e07"
uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4"
version = "0.9.8"
[[deps.Colors]]
deps = ["ColorTypes", "FixedPointNumbers", "Reexport"]
git-tree-sha1 = "417b0ed7b8b838aa6ca0a87aadf1bb9eb111ce40"
uuid = "5ae59095-9a9b-59fe-a467-6f913c188581"
version = "0.12.8"
[[deps.CommonSubexpressions]]
deps = ["MacroTools", "Test"]
git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7"
uuid = "bbf7d656-a473-5ed7-a52c-81e309532950"
version = "0.3.0"
[[deps.Compat]]
deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
git-tree-sha1 = "b153278a25dd42c65abbf4e62344f9d22e59191b"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.43.0"
[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
[[deps.CompositionsBase]]
git-tree-sha1 = "455419f7e328a1a2493cabc6428d79e951349769"
uuid = "a33af91c-f02d-484b-be07-31d278c5ca2b"
version = "0.1.1"
[[deps.ConstructionBase]]
deps = ["LinearAlgebra"]
git-tree-sha1 = "f74e9d5388b8620b4cee35d4c5a618dd4dc547f4"
uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
version = "1.3.0"
[[deps.ContextVariablesX]]
deps = ["Compat", "Logging", "UUIDs"]
git-tree-sha1 = "8ccaa8c655bc1b83d2da4d569c9b28254ababd6e"
uuid = "6add18c4-b38d-439d-96f6-d6bc489c04c5"
version = "0.1.2"
[[deps.Contour]]
deps = ["StaticArrays"]
git-tree-sha1 = "9f02045d934dc030edad45944ea80dbd1f0ebea7"
uuid = "d38c429a-6771-53c6-b99e-75d170b6e991"
version = "0.5.7"
[[deps.Crayons]]
git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15"
uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f"
version = "4.1.1"
[[deps.DataAPI]]
git-tree-sha1 = "fb5f5316dd3fd4c5e7c30a24d50643b73e37cd40"
uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a"
version = "1.10.0"
[[deps.DataFrames]]
deps = ["Compat", "DataAPI", "Future", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrettyTables", "Printf", "REPL", "Reexport", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"]
git-tree-sha1 = "daa21eb85147f72e41f6352a57fccea377e310a9"
uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
version = "1.3.4"
[[deps.DataStructures]]
deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
git-tree-sha1 = "cc1a8e22627f33c789ab60b36a9132ac050bbf75"
uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
version = "0.18.12"
[[deps.DataValueInterfaces]]
git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6"
uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464"
version = "1.0.0"
[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
[[deps.DefineSingletons]]
git-tree-sha1 = "0fba8b706d0178b4dc7fd44a96a92382c9065c2c"
uuid = "244e2a9f-e319-4986-a169-4d1fe445cd52"
version = "0.1.2"
[[deps.DelimitedFiles]]
deps = ["Mmap"]
uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
[[deps.DensityInterface]]
deps = ["InverseFunctions", "Test"]
git-tree-sha1 = "80c3e8639e3353e5d2912fb3a1916b8455e2494b"
uuid = "b429d917-457f-4dbc-8f4c-0cc954292b1d"
version = "0.4.0"
[[deps.DiffResults]]
deps = ["StaticArrays"]
git-tree-sha1 = "c18e98cba888c6c25d1c3b048e4b3380ca956805"
uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
version = "1.0.3"
[[deps.DiffRules]]
deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"]
git-tree-sha1 = "28d605d9a0ac17118fe2c5e9ce0fbb76c3ceb120"
uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
version = "1.11.0"
[[deps.Distributed]]
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
[[deps.Distributions]]
deps = ["ChainRulesCore", "DensityInterface", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SparseArrays", "SpecialFunctions", "Statistics", "StatsBase", "StatsFuns", "Test"]
git-tree-sha1 = "8a6b49396a4058771c5c072239b2e0a76e2e898c"
uuid = "31c24e10-a181-5473-b8eb-7969acd0382f"
version = "0.25.58"
[[deps.DocStringExtensions]]
deps = ["LibGit2"]
git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
version = "0.8.6"
[[deps.Downloads]]
deps = ["ArgTools", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
[[deps.EarCut_jll]]
deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
git-tree-sha1 = "3f3a2501fa7236e9b911e0f7a588c657e822bb6d"
uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5"
version = "2.2.3+0"
[[deps.Expat_jll]]