123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- """
- Author: Sebastian Vendt, University of Ulm
- This script implements the convolutional neural network for infering tap locations from motion data frames.
- For detailed documentation please read through ReportMotionLogger.pdf in the github repo.
- """
- using ArgParse
- s = ArgParseSettings()
- @add_arg_table s begin
- "--gpu"
- help = "set, if you want to train on the GPU"
- action = :store_true
- "--eval"
- help = "set, if you want to validate instead of test after training"
- action = :store_true
- "--epochs"
- help = "Number of epochs"
- arg_type = Int64
- default = 100
- "--logmsg"
- help = "additional message describing the training log"
- arg_type = String
- default = ""
- "--csv"
- help = "set, if you additionally want a csv output of the learning process"
- action = :store_true
- "--runD"
- help = "set, if you want to run the default config"
- action = :store_true
- end
- parsed_args = parse_args(ARGS, s)
- using Flux, Statistics
- using Flux: onecold
- using BSON
- using Dates
- using Printf
- using NNlib
- using FeedbackNets
- include("./dataManager.jl")
- include("./verbose.jl")
- using .dataManager: make_batch
- using .verbose
- using Logging
- using Random
- import LinearAlgebra: norm
- norm(x::TrackedArray{T}) where T = sqrt(sum(abs2.(x)) + eps(T))
- ######################
- # PARAMETERS
- ######################
- const batch_size = 100
- momentum = 0.99f0
- const lambda = 0.0005f0
- const delta = 0.00006
- learning_rate = 0.03f0
- validate = parsed_args["eval"]
- const epochs = parsed_args["epochs"]
- const decay_rate = 0.1f0
- const decay_step = 60
- const usegpu = parsed_args["gpu"]
- const printout_interval = 2
- const time_format = "HH:MM:SS"
- const date_format = "dd_mm_yyyy"
- data_size = (60, 6) # resulting in a 300ms frame
- # DEFAULT ARCHITECTURE
- channels = 1
- features = [32, 64, 128] # needs to find the relation between the axis which represents the screen position
- kernel = [(5,1), (5,1), (2,6)] # convolute only horizontally, last should convolute all 6 rows together to map relations between the channels
- pooldims = [(3,1), (3,1)]# (30,6) -> (15,6)
- # formula for calculating output dimensions of convolution:
- # dim1 = ((dim1 - Filtersize + 2 * padding) / stride) + 1
- inputDense = [0, 600, 300] # the first dense layer is automatically calculated
- dropout_rate = 0.3f0
- # random search values
- rs_momentum = [0.9, 0.92, 0.94, 0.96, 0.98, 0.99]
- rs_features = [[32, 64, 128], [64, 64, 64], [32, 32, 32], [96, 192, 192]]
- rs_dropout_rate = [0.1, 0.3, 0.4, 0.6, 0.8]
- rs_kernel = [[(3,1), (3,1), (3,6)], [(5,1), (5,1), (3,6)], [(7,1), (7,1), (3,6)], [(3,1), (3,1), (2,6)], [(5,1), (5,1), (2,6)], [(7,1), (7,1), (2,6)],
- [(7,1), (5,1), (2,6)], [(7,1), (5,1), (3,6)], [(5,1), (3,1), (2,6)], [(5,1), (3,1), (3,6)]]
- rs_pooldims = [[(2,1), (2,1)], [(3,1), (3,1)]]
- rs_learning_rate = [1, 0.3, 0.1, 0.03, 0.01, 0.003, 0.001]
- dataset_folderpath = "../MATLAB/TrainingData/"
- dataset_name = "2019_09_09_1658"
- const model_save_location = "../trainedModels/"
- const log_save_location = "./logs/"
- if usegpu
- using CuArrays
- end
- debug_str = ""
- log_msg = parsed_args["logmsg"]
- csv_out = parsed_args["csv"]
- runD = parsed_args["runD"]
- io = nothing
- io_csv = nothing
- @debug begin
- global debug_str
- debug_str = "DEBUG_"
- "------DEBUGGING ACTIVATED------"
- end
- function adapt_learnrate(epoch_idx)
- return learning_rate * decay_rate^(epoch_idx / decay_step)
- end
- # TODO different idea for the accuracy: draw circle around ground truth and if prediction lays within the circle count this as a hit
- function accuracy(model, x, y)
- y_hat = Tracker.data(model(x))
- return mean(mapslices(button_number, y_hat, dims=1) .== mapslices(button_number, y, dims=1))
- end
- function accuracy(model, dataset)
- acc = 0.0f0
- for (data, labels) in dataset
- acc += accuracy(model, data, labels)
- end
- return acc / length(dataset)
- end
- function button_number(X)
- return (X[1] * 1080) ÷ 360 + 3 * ((X[2] * 980) ÷ 245)
- end
- function loss(model, x, y)
- # quadratic euclidean distance + norm
- return Flux.mse(model(x), y) + lambda * sum(norm, params(model))
- end
- function loss(model, dataset)
- loss_val = 0.0f0
- for (data, labels) in dataset
- loss_val += Tracker.data(loss(model, data, labels))
- end
- return loss_val / length(dataset)
- end
- function load_dataset()
- train = make_batch(dataset_folderpath, "$(dataset_name)_TRAIN.mat", normalize_data=false, truncate_data=false)
- val = make_batch(dataset_folderpath, "$(dataset_name)_VAL.mat", normalize_data=false, truncate_data=false)
- test = make_batch(dataset_folderpath, "$(dataset_name)_TEST.mat", normalize_data=false, truncate_data=false)
- return (train, val, test)
- end
- function create_model()
- return Chain(
- Conv(kernel[1], channels=>features[1], relu, pad=map(x -> x ÷ 2, kernel[1])),
- MaxPool(pooldims[1], stride=pooldims[1]),
- Conv(kernel[2], features[1]=>features[2], relu, pad=map(x -> x ÷ 2, kernel[2])),
- MaxPool(pooldims[2], stride=pooldims[2]),
- Conv(kernel[3], features[2]=>features[3], relu),
- # MaxPool(),
- flatten,
- Dense(prod((data_size .÷ pooldims[1] .÷ pooldims[2]) .- kernel[3] .+ 1) * features[3], inputDense[2], relu),
- Dropout(dropout_rate),
- Dense(inputDense[2], inputDense[3], relu),
- Dropout(dropout_rate),
- Dense(inputDense[3], 2, σ), # coordinates between 0 and 1
- )
- end
- function log(model, epoch, use_testset)
- Flux.testmode!(model, true)
-
- if(epoch == 0) # evalutation phase
- if(use_testset) @printf(io, "[%s] INIT Loss(test): f% Accuarcy: %f\n", Dates.format(now(), time_format), loss(model, test_set), accuracy(model, test_set))
- else @printf(io, "[%s] INIT Loss(val): %f Accuarcy: %f\n", Dates.format(now(), time_format), loss(model, validation_set), accuracy(model, validation_set)) end
- elseif(epoch == epochs)
- @printf(io, "[%s] Epoch %3d: Loss(train): %f Loss(val): %f\n", Dates.format(now(), time_format), epoch, loss(model, train_set), loss(model, validation_set))
- if(use_testset)
- @printf(io, "[%s] FINAL(%d) Loss(test): %f Accuarcy: %f\n", Dates.format(now(), time_format), epoch, loss(model, test_set), accuracy(model, test_set))
- else
- @printf(io, "[%s] FINAL(%d) Loss(val): %f Accuarcy: %f\n", Dates.format(now(), time_format), epoch, loss(model, validation_set), accuracy(model, validation_set))
- end
- else # learning phase
- if (rem(epoch, printout_interval) == 0)
- @printf(io, "[%s] Epoch %3d: Loss(train): %f Loss(val): %f\n", Dates.format(now(), time_format), epoch, loss(model, train_set), loss(model, validation_set))
- end
- end
- Flux.testmode!(model, false)
- end
- function log_csv(model, epoch)
- Flux.testmode!(model, true)
- if(csv_out) @printf(io_csv, "%d, %f, %f\n", epoch, loss(model, train_set), loss(model, validation_set)) end
- Flux.testmode!(model, false)
- end
- function eval_model(model)
- Flux.testmode!(model, true)
- if (validate) return (loss(model, validation_set), accuracy(model, validation_set))
- else return (loss(model, test_set), accuracy(model, test_set)) end
- end
- function train_model()
- model = create_model()
- if (usegpu) model = gpu(model) end
- opt = Momentum(learning_rate, momentum)
- log(model, 0, !validate)
- Flux.testmode!(model, false) # bring model in training mode
- last_loss = loss(model, train_set)
- for i in 1:epochs
- flush(io)
- Flux.train!((x, y) -> loss(model, x, y), params(model), train_set, opt)
- opt.eta = adapt_learnrate(i)
- log_csv(model, i)
- log(model, i, !validate)
- end
- return eval_model(model)
- end
- function random_search()
- rng = MersenneTwister()
- results = []
- global epochs = 40
- for search in 1:800
- # create random set
- global momentum = rand(rng, rs_momentum)
- global features = rand(rng, rs_features)
- global dropout_rate = rand(rng, rs_dropout_rate)
- global kernel = rand(rng, rs_kernel)
- global pooldims = rand(rng, rs_pooldims)
- global learning_rate = rand(rng, rs_learning_rate)
-
- # printf configuration
- config1 = "momentum$(momentum), features=$(features), dropout_rate=$(dropout_rate)"
- config2 = "kernel=$(kernel), pooldims=$(pooldims), learning_rate=$(learning_rate)"
- @printf(io, "\nSearch %d of %d\n", search, 500)
- @printf(io, "%s\n", config1)
- @printf(io, "%s\n\n", config2)
-
- (loss, accuracy) = train_model()
- push!(results, (search, loss, accuracy))
- end
- return results
- end
- # logging framework
- fp = "$(log_save_location)$(debug_str)log_$(Dates.format(now(), date_format)).log"
- io = open(fp, "a+")
- global_logger(SimpleLogger(io)) # for debug outputs
- @printf(Base.stdout, "Logging to File: %s\n", fp)
- @printf(io, "\n--------[%s %s]--------\n", Dates.format(now(), date_format), Dates.format(now(), time_format))
- @printf(io, "%s\n", log_msg)
- # csv handling
- if (csv_out)
- fp_csv = "$(log_save_location)$(debug_str)csv_$(Dates.format(now(), date_format)).csv"
- io_csv = open(fp_csv, "w+") # read, write, create, truncate
- @printf(io_csv, "epoch, loss(train), loss(val)\n")
- end
- # dump configuration
- @debug begin
- for symbol in names(Main)
- var = "$(symbol) = $(eval(symbol))"
- @printf(io, "%s\n", var)
- end
- "--------End of VAR DUMP--------"
- end
- flush(io)
- flush(Base.stdout)
- train, validation, test = load_dataset()
- if (usegpu)
- const train_set = gpu.(train)
- const validation_set = gpu.(validation)
- const test_set = gpu.(test)
- end
- if(!runD)
- results = random_search()
- BSON.@save "results.bson" results
- #TODO sort and print best 5-10 results
- sort!(results, by = x -> x[2])
- # print results
- @printf("Best results by Loss:\n")
- for idx in 1:5
- @printf("#%d: Loss %f, accuracy %f in Search: %d\n", idx, results[idx][2], results[idx][3], results[idx][1])
- end
-
- sort!(results, by = x -> x[3], rev=true)
- @printf("Best results by Accuarcy:\n")
- for idx in 1:5
- @printf("#%d: Accuarcy: %f, Loss %f in Search: %d\n", idx, results[idx][3], results[idx][2], results[idx][1])
- end
- else
- train_model()
- end
|