I’ve the below function

``````sigmoid(z::Real) = one(z) / (one(z) + exp(-z))
``````

And want to broadcast it for all the element of x, where:

``````X = [
[1, 2, 3],
[3, 4, 5],
[5, 6, 7]
]
``````

I tried the normal way but failed

``````sigmoid.(X)
``````

Broadcast works on the immediate elements of its arguments — in this case, the elements of `X` are themselves arrays, thus `sigmoid.(X)` is passing those inner arrays to `sigmoid`. You need to go down a second layer; often this is clearest with a comprehension:

``````[sigmoid.(inner_array) for inner_array in X]
``````
3 Likes

Another way is to just do

``````(x -> sigmoid.(x)).(X)
``````

This creates an anonymous function (`x -> sigmoid.(x)`) whose whole point is to broadcast `sigmoid` and then you can broadcast that function.

Not to beat a dead horse, but I do wish we had a cleaner way to do recursive broadcast. Maybe something like `@. sigmoid(X) recursive` where the flag `recursive` tells it to keep apply broadcasting until it reaches a scalar.

3 Likes

If wishes were horses, there’d be twenty different resurrected horse zombies running around, each doing broadcast just slightly differently. If you’ll pardon the mixed metaphor.

5 Likes

I think it’s kinda ironic that 3 hours ago the OP posted this thread: Sigmoid function in Julia where he was told “don’t define elementwise functions on arrays, just use broadcast.” But now when he runs into a nested array it becomes clear that the broadcast machinery isn’t general enough to do what he wants.

I guess I just don’t understand why recursive broadcast is always dismissed out of hand.

Because this is a #user:first-steps question. Let’s describe the status quo here, and not bog new users down with potential new and/or competing designs.

6 Likes

Maybe it should also be metioned that, instead of an array of arrays, you are often better off with one higher-dimensional array, in this case a matrix. Some languages don’t distinguish these concepts, but Julia does.

``````julia> M = [1 2 3; 3 4 5; 5 6 7]
3×3 Array{Int64,2}:
1  2  3
3  4  5
5  6  7

julia> transpose(M) == reduce(hcat, X)
true

julia> sigmoid.(M)
3×3 Array{Float64,2}:
0.731059  0.880797  0.952574
0.952574  0.982014  0.993307
0.993307  0.997527  0.999089
``````
5 Likes

Thanks, actually the input come like an output of a previous operation if you can help me to fix it to match your approach, it will be very helpful:

``````a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
x, y = split_seq(a, 3)

function split_seq(sequence::Array, n_steps::Int)
x, y = [], []
for (index, value) in enumerate(sequence)
if index <= length(sequence) - n_steps
seq_x, seq_y = sequence[index:index+n_steps-1], sequence[index+n_steps]
push!(x, seq_x)
push!(y, seq_y)
end
end
return x, y
end
``````

Then I get `x` as:

``````7-element Array{Any,1}:
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
``````

Here are some ways…

``````julia> reduce(hcat, @view a[i:i+2] for i=1:length(a)-2)
3×8 Array{Int64,2}:
1  2  3  4  5  6  7   8
2  3  4  5  6  7  8   9
3  4  5  6  7  8  9  10

julia> reduce(vcat, a[i:i+2]' for i=1:length(a)-2)
8×3 Array{Int64,2}:
1  2   3
2  3   4
...
``````

You could also just make a new array & fill it in with loops, which may well be fastest (if you care) but won’t fit on one line.

1 Like

I think I’d use a two-dimensional comprehension here:

``````split_seq(sequence, n) = [sequence[offset+column] for offset in 0:n-1, column in 1:length(sequence)-n+1]
``````

Or if you really want to be clever —perhaps too clever — you can use broadcast to select your indices (but this won’t be quite as efficient as the above):

``````split_seq(sequence, n) = sequence[(0:n-1) .+ transpose(1:end-n+1)]
``````
1 Like

Perfectly done, but did not get how this code generated a 2D array?

nice, so my function became:

``````function split_seq(sequence::Array, n_steps::Int)
x = reduce(vcat, sequence[i:i+n_steps-1]' for i=1:length(sequence)-n_steps)
y = reduce(vcat, sequence[i+n_steps]' for i=1:length(sequence)-n_steps)
return x, y
end
``````

https://docs.julialang.org/en/v1/manual/arrays/#Comprehensions-1

We could probably use an example for the multi-dimensional capability, but the trick is that there are two comma-separated `for` loops. It creates the Cartesian product of the two, using the first to generate the rows and the second to generate the columns.

``````julia> [row+10*col for row in 1:4, col in 1:5]
4×5 Array{Int64,2}:
11  21  31  41  51
12  22  32  42  52
13  23  33  43  53
14  24  34  44  54
``````
2 Likes

Thanks, and hope you be patent with my questions:

I tried the below and got 2 different answers:

``````split_seq(sequence, n) = [sequence[offset+column] for offset in 0:n-1, column in 1:length(sequence)-n+1]

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
x = split_seq(a, 3)
@show x
``````

``````x = ([1 2 3; 2 3 4; 3 4 5; 4 5 6; 5 6 7; 6 7 8; 7 8 9], [4, 5, 6, 7, 8, 9, 10])
``````

Other way, without creating a function:

``````sequence = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
z = [sequence[offset+column] for offset in 0:3-1, column in 1:length(sequence)-3+1]
@show z
``````

``````z = [1 2 3 4 5 6 7 8; 2 3 4 5 6 7 8 9; 3 4 5 6 7 8 9 10]
``````

I believe that’s because you already have multiple `split_seq` methods defined in your current Julia session — and your original definition is taking precedence because it’s more specific. If you look at `methods(split_seq)`, I believe you’ll see two (or more) things listed:

``````julia> methods(split_seq)
# 2 methods for generic function "split_seq":
[1] split_seq(sequence::Array, n_steps::Int64) in Main at REPL[100]:1
[2] split_seq(sequence, n) in Main at REPL[96]:1
``````

For simple definitions like these, I wouldn’t put types on the arguments — you can rely on duck-typing to accept lots of arguments, and there’s no difference in speed/efficiency.

1 Like

Here is an issue with an idea for nested broadcasts, for those that want to discuss potential enhancements around this in Julia itself. The debate there is quite outdated, though.

Thanks a lot