| 
          import Pkg; Pkg.status() | 
        
        
           | 
          
 | 
        
        
           | 
          @info "Loading packages..." | 
        
        
           | 
          import Random, DelimitedFiles | 
        
        
           | 
          import ForwardDiff, ReverseDiff, Zygote, Symbolics | 
        
        
           | 
          using BenchmarkTools | 
        
        
           | 
          
 | 
        
        
           | 
          const AV = AbstractVector{T} where T | 
        
        
           | 
          
 | 
        
        
           | 
          # ========== Objective function ========== | 
        
        
           | 
          
 | 
        
        
           | 
          normal_pdf(x::Real, mean::Real, var::Real) = | 
        
        
           | 
              exp(-(x - mean)^2 / (2var)) / sqrt(2π * var) | 
        
        
           | 
          
 | 
        
        
           | 
          mixture_pdf(x::Real, weights::AV{<:Real}, means::AV{<:Real}, vars::AV{<:Real}) = | 
        
        
           | 
              sum( | 
        
        
           | 
                  w * normal_pdf(x, mean, var) | 
        
        
           | 
                  for (w, mean, var) in zip(weights, means, vars) | 
        
        
           | 
              ) | 
        
        
           | 
          
 | 
        
        
           | 
          normal_pdf(x, mean, var) = | 
        
        
           | 
              exp(-(x - mean)^2 / (2var)) / sqrt(2π * var) | 
        
        
           | 
          
 | 
        
        
           | 
          
 | 
        
        
           | 
          function mixture_loglikelihood(params::AV{<:Real}, data::AV{<:Real})::Real | 
        
        
           | 
              K = length(params) ÷ 3 | 
        
        
           | 
              weights, means, stds = @views params[1:K], params[K+1:2K], params[2K+1:end] | 
        
        
           | 
          
 | 
        
        
           | 
              mat = normal_pdf.(data, means', stds' .^2) # (N, K) | 
        
        
           | 
              #@show size(mat) | 
        
        
           | 
              sum(mat .* weights', dims=2) .|> log |> sum | 
        
        
           | 
              # sum( | 
        
        
           | 
              #     sum( | 
        
        
           | 
              #         weight * normal_pdf(x, mean, std^2) | 
        
        
           | 
              #         for (weight, mean, std) in zip(weights, means, stds) | 
        
        
           | 
              #     ) |> log | 
        
        
           | 
              #     for x in data | 
        
        
           | 
              # ) | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          function generate_gradient(out_fname::AbstractString, K::Integer) | 
        
        
           | 
              @assert K > 0 | 
        
        
           | 
              Symbolics.@variables x ws[1:K] mus[1:K] stds[1:K] | 
        
        
           | 
          
 | 
        
        
           | 
              args=[x, ws, mus, stds] | 
        
        
           | 
              expr = Symbolics.gradient( | 
        
        
           | 
                  mixture_pdf(x, ws, mus, collect(stds) .^2) |> log, | 
        
        
           | 
                  [ws; mus; stds] | 
        
        
           | 
              ) | 
        
        
           | 
          
 | 
        
        
           | 
              fn, fn_mut = Symbolics.build_function(expr, args...) | 
        
        
           | 
               | 
        
        
           | 
              write(out_fname, string(fn_mut)) | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          # ========== Gradient with Symbolics.jl ========== | 
        
        
           | 
          @info "Generating gradient functions..." | 
        
        
           | 
          GRAD_FNS = Union{Nothing, Function}[nothing] | 
        
        
           | 
          for K in 2:5 | 
        
        
           | 
              fname = "grad_$K.jl" | 
        
        
           | 
              @show generate_gradient(fname, K) | 
        
        
           | 
              push!(GRAD_FNS, include(fname)) | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          function my_gradient!(out::AV{<:Real}, tmp::AV{<:Real}, xs::AV{<:Real}, params::AV{<:Real}) | 
        
        
           | 
              K = length(params) ÷ 3 | 
        
        
           | 
              grad! = GRAD_FNS[K] | 
        
        
           | 
              weights, means, stds = @views params[1:K], params[K+1:2K], params[2K+1:end] | 
        
        
           | 
          
 | 
        
        
           | 
              out .= 0 | 
        
        
           | 
              for x in xs | 
        
        
           | 
                  grad!(tmp, x, weights, means, stds) | 
        
        
           | 
                  out .+= tmp | 
        
        
           | 
              end | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          
 | 
        
        
           | 
          # ========== Benchmark setup ========== | 
        
        
           | 
          SEED = 42 | 
        
        
           | 
          N_SAMPLES = 500 | 
        
        
           | 
          N_COMPONENTS = 4 | 
        
        
           | 
          
 | 
        
        
           | 
          rnd = Random.MersenneTwister(SEED) | 
        
        
           | 
          data = randn(rnd, N_SAMPLES) | 
        
        
           | 
          params0 = [rand(rnd, N_COMPONENTS); randn(rnd, N_COMPONENTS); 2rand(rnd, N_COMPONENTS)] | 
        
        
           | 
          DelimitedFiles.writedlm("gen_data.csv", data, ',') | 
        
        
           | 
          DelimitedFiles.writedlm("gen_params0.csv", params0, ',') | 
        
        
           | 
          
 | 
        
        
           | 
          objective = params -> mixture_loglikelihood(params, data) | 
        
        
           | 
          
 | 
        
        
           | 
          @show params0 | 
        
        
           | 
          @show objective(params0) | 
        
        
           | 
          
 | 
        
        
           | 
          @info "Settings" SEED N_SAMPLES N_COMPONENTS length(params0) | 
        
        
           | 
          
 | 
        
        
           | 
          
 | 
        
        
           | 
          # ========== Actual benchmarks ========== | 
        
        
           | 
          @info "Computing gradient w/ Symbolics" | 
        
        
           | 
          let | 
        
        
           | 
              grad_storage = similar(params0) | 
        
        
           | 
              tmp = similar(params0) | 
        
        
           | 
          
 | 
        
        
           | 
              # 1. Compile | 
        
        
           | 
              my_gradient!(grad_storage, tmp, data, params0) | 
        
        
           | 
              # 2. Benchmark | 
        
        
           | 
              trial = run(@benchmarkable $my_gradient!($grad_storage, $tmp, $data, $params0) samples=10_000 evals=1 seconds=60) | 
        
        
           | 
              show(stdout, MIME("text/plain"), trial) | 
        
        
           | 
              println() | 
        
        
           | 
              @show grad_storage | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          @info "Computing gradient w/ ForwardDiff" | 
        
        
           | 
          let | 
        
        
           | 
              grad_storage = similar(params0) | 
        
        
           | 
              cfg_grad = ForwardDiff.GradientConfig(objective, params0, ForwardDiff.Chunk{length(params0)}()) | 
        
        
           | 
          
 | 
        
        
           | 
              # 1. Compile | 
        
        
           | 
              ForwardDiff.gradient!(grad_storage, objective, params0, cfg_grad) | 
        
        
           | 
              # 2. Benchmark | 
        
        
           | 
              trial = run(@benchmarkable ForwardDiff.gradient!($grad_storage, $objective, $params0, $cfg_grad) samples=10_000 evals=1 seconds=60) | 
        
        
           | 
              show(stdout, MIME("text/plain"), trial) | 
        
        
           | 
              println() | 
        
        
           | 
              @show grad_storage | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          @info "Computing gradient w/ ReverseDiff" | 
        
        
           | 
          let | 
        
        
           | 
              grad_storage = similar(params0) | 
        
        
           | 
              objective_tape = ReverseDiff.GradientTape(objective, params0) |> ReverseDiff.compile | 
        
        
           | 
          
 | 
        
        
           | 
              # 1. Compile | 
        
        
           | 
              ReverseDiff.gradient!(grad_storage, objective_tape, params0) | 
        
        
           | 
              # 2. Benchmark | 
        
        
           | 
              trial = run(@benchmarkable ReverseDiff.gradient!($grad_storage, $objective_tape, $params0) samples=10_000 evals=1 seconds=60) | 
        
        
           | 
              show(stdout, MIME("text/plain"), trial) | 
        
        
           | 
              println() | 
        
        
           | 
              @show grad_storage | 
        
        
           | 
          end | 
        
        
           | 
          
 | 
        
        
           | 
          @info "Computing gradient w/ Zygote reverse" | 
        
        
           | 
          let | 
        
        
           | 
              # 1. Compile | 
        
        
           | 
              grad_storage = Zygote.gradient(objective, params0) | 
        
        
           | 
              # 2. Benchmark | 
        
        
           | 
              trial = run(@benchmarkable Zygote.gradient($objective, $params0) samples=10_000 evals=1 seconds=60) | 
        
        
           | 
              show(stdout, MIME("text/plain"), trial) | 
        
        
           | 
              println() | 
        
        
           | 
              @show grad_storage | 
        
        
           | 
          end |