Код не работает после перевода с Python на Julia - PullRequest
3 голосов
/ 13 апреля 2019

Я новичок в языке программирования Julia и все еще изучаю его, написав код, который я уже написал на Python (или, по крайней мере, опробовал на Python).

Есть статья, которая объясняет, как сделать очень простую нейронную сеть: https://medium.com/technology-invention-and-more/how-to-build-a-simple-neural-network-in-9-lines-of-python-code-cc8f23647ca1.

Я попробовал код из этой статьи на Python, он работает нормально. Тем не менее, я не использовал вещи с линейной алгеброй в Python раньше (как точка). Сейчас я пытаюсь перевести этот код на Джулию, но есть некоторые вещи, которые я не могу понять. Вот мой код Джулии:

using LinearAlgebra

synaptic_weights = [-0.16595599, 0.44064899, -0.99977125]::Vector{Float64}

sigmoid(x) = 1 / (1 + exp(-x))
sigmoid_derivative(x) = x * (1 -x)

function train(training_set_inputs, training_set_outputs, number_of_training_iterations)
    global synaptic_weights
    for (iteration) in 1:number_of_training_iterations
        output = think(training_set_inputs)

        error = training_set_outputs .- output

        adjustment = dot(transpose(training_set_inputs), error * sigmoid_derivative(output))

        synaptic_weights = synaptic_weights .+ adjustment
    end
end

think(inputs) = sigmoid(dot(inputs, synaptic_weights))

println("Random starting synaptic weights:")
println(synaptic_weights)

training_set_inputs = [0 0 1 ; 1 1 1 ; 1 0 1 ; 0 1 1]::Matrix{Int64}
training_set_outputs = [0, 1, 1, 0]::Vector{Int64}
train(training_set_inputs, training_set_outputs, 10000)

println("New synaptic weights after training:")
println(synaptic_weights)

println("Considering new situation [1, 0, 0] -> ?:")
println(think([1 0 0]))

Я уже пытался инициализировать векторы (например, synaptic_weights) как:

synaptic_weights = [-0.16595599 ; 0.44064899 ; -0.99977125]

Однако код не работает. Точнее, есть 3 вещи, которые мне не понятны:

  1. Правильно ли я инициализирую векторы и матрицы (равно ли это тому, что делает первоначальный автор в Python)?
  2. В Python оригинальный автор использует операторы + и -, где один операнд является вектором, а другой - скаляром. Я не уверен, означает ли это поэлементное сложение или вычитание в Python. Например, равен ли (вектор + скалярный) в Python (вектор. + Скалярный) в Юлии?
  3. Когда я пытаюсь запустить приведенный выше код Джулии, я получаю следующую ошибку:

    ERROR: LoadError: DimensionMismatch("first array has length 12 which does not match the length of the second, 3.")
    Stacktrace:
     [1] dot(::Array{Int64,2}, ::Array{Float64,1}) at C:\Users\julia\AppData\Local\Julia-1.0.3\share\julia\stdlib\v1.0\LinearAlgebra\src\generic.jl:702
     [2] think(::Array{Int64,2}) at C:\Users\Viktória\Documents\julia.jl:21
     [3] train(::Array{Int64,2}, ::Array{Int64,1}, ::Int64) at C:\Users\Viktória\Documents\julia.jl:11
     [4] top-level scope at none:0
    in expression starting at C:\Users\Viktória\Documents\julia.jl:28
    

    Эта ошибка возникает, когда функция думает (входы) пытается вычислить скалярное произведение входных данных и synaptic_weights. В этом случае входные данные - это матрица 4x3, а синаптические веса - это матрица 3x1 (вектор). Я знаю, что их можно умножить, и в результате получится матрица 4х1 (вектор). Не означает ли это, что их точечный продукт можно вычислить?

    В любом случае, это скалярное произведение может быть вычислено в Python с использованием numpy-пакета, поэтому, я думаю, есть определенный способ, которым его также можно вычислить в Julia.

Для скалярного произведения я также попытался создать функцию, которая принимает аргументы a и b в качестве аргументов и пытается вычислить их скалярное произведение: сначала вычисляется произведение a и b, затем возвращается сумма результата. Я не уверен, что это хорошее решение, но код Джулии не дал ожидаемого результата, когда я использовал эту функцию, поэтому я удалил его.

Можете ли вы помочь мне с этим кодом, пожалуйста?

1 Ответ

9 голосов
/ 13 апреля 2019

Вот код, откорректированный для Юлии:

sigmoid(x) = 1 / (1 + exp(-x))
sigmoid_derivative(x) = x * (1 -x)
think(synaptic_weights, inputs) = sigmoid.(inputs * synaptic_weights)

function train!(synaptic_weights, training_set_inputs, training_set_outputs,
                number_of_training_iterations)
    for iteration in 1:number_of_training_iterations
        output = think(synaptic_weights, training_set_inputs)
        error = training_set_outputs .- output
        adjustment =  transpose(training_set_inputs) * (error .* sigmoid_derivative.(output))
        synaptic_weights .+= adjustment
    end
end

synaptic_weights = [-0.16595599, 0.44064899, -0.99977125]
println("Random starting synaptic weights:")
println(synaptic_weights)

training_set_inputs = Float64[0 0 1 ; 1 1 1 ; 1 0 1 ; 0 1 1]
training_set_outputs = Float64[0, 1, 1, 0]
train!(synaptic_weights, training_set_inputs, training_set_outputs, 10000)

println("New synaptic weights after training:")
println(synaptic_weights)

println("Considering new situation [1, 0, 0] -> ?:")
println(think(synaptic_weights, Float64[1 0 0]))

Есть несколько изменений, поэтому, если некоторые из них вам не понятны, пожалуйста, спросите, и я остановлюсь на них.

Самые важные вещи, которые я изменил:

  • не используйте глобальные переменные, так как они значительно снизят производительность
  • сделать все массивы Float64 типом элемента
  • в нескольких местах вам нужно выполнять вещание с . (например, функции sigmoid и sigmoid_derivative определены таким образом, что они ожидают получить число в качестве аргумента, поэтому, когда мы вызываем их . добавляется после их имени для запуска трансляции)
  • использовать стандартное матричное умножение * вместо dot

Код работает примерно в 30 раз быстрее, чем оригинальная реализация в Python. Я не выжимал максимальную производительность для этого кода (теперь он выполняет много выделений, которых можно избежать), так как для этого потребуется немного переписать его логику, и я думаю, вы хотели прямую переопределение.

...