Mlj.jl: Column-wise operations in Learning Networks

Created on 11 Oct 2019  ยท  5Comments  ยท  Source: alan-turing-institute/MLJ.jl

Is there a way to perform column-wise operations to nodes of a learning network?

One can use FeatureSelector to manipulate the input source, but I couldn't do the same with the target source.

For example, let's say I want to train 2 models on the same input data but with different target variables. So if I define the target source as a 2-column Array, how do I separate the columns inside the network? Or is there a way to merge specific columns of different nodes into a new node? In general, is there a way to manipulate columns of nodes in such a network?

Thank you!

Most helpful comment

Good question. Answer is yes.

You can think as a node for a proxy for data. Anything you can do to data you can do to a node that is a proxy for that kind of data, using the node method; do ?node for details. Here's a DataFrame example:

julia> X = DataFrame(rand(10,3))
10ร—3 DataFrame
โ”‚ Row โ”‚ x1       โ”‚ x2        โ”‚ x3         โ”‚
โ”‚     โ”‚ Float64  โ”‚ Float64   โ”‚ Float64    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1   โ”‚ 0.518405 โ”‚ 0.869467  โ”‚ 0.676809   โ”‚
โ”‚ 2   โ”‚ 0.16694  โ”‚ 0.381958  โ”‚ 0.840221   โ”‚
โ”‚ 3   โ”‚ 0.832859 โ”‚ 0.285973  โ”‚ 0.166679   โ”‚
โ”‚ 4   โ”‚ 0.32379  โ”‚ 0.183428  โ”‚ 0.00638633 โ”‚
โ”‚ 5   โ”‚ 0.24599  โ”‚ 0.282592  โ”‚ 0.539567   โ”‚
โ”‚ 6   โ”‚ 0.980281 โ”‚ 0.945239  โ”‚ 0.267164   โ”‚
โ”‚ 7   โ”‚ 0.544812 โ”‚ 0.0105223 โ”‚ 0.480417   โ”‚
โ”‚ 8   โ”‚ 0.74989  โ”‚ 0.444539  โ”‚ 0.64379    โ”‚
โ”‚ 9   โ”‚ 0.041312 โ”‚ 0.606745  โ”‚ 0.100776   โ”‚
โ”‚ 10  โ”‚ 0.119828 โ”‚ 0.930362  โ”‚ 0.419426   โ”‚

julia> Xs = source(X)
Source{:input} @ 3โ€ฆ09

julia> Y = node(df -> df[:, [:x1, :x2]], Xs)  # pick off multiple columns
Node @ 1โ€ฆ84 = #15(3โ€ฆ09)

julia> Y()
10ร—2 DataFrame
โ”‚ Row โ”‚ x1       โ”‚ x2        โ”‚
โ”‚     โ”‚ Float64  โ”‚ Float64   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1   โ”‚ 0.518405 โ”‚ 0.869467  โ”‚
โ”‚ 2   โ”‚ 0.16694  โ”‚ 0.381958  โ”‚
โ”‚ 3   โ”‚ 0.832859 โ”‚ 0.285973  โ”‚
โ”‚ 4   โ”‚ 0.32379  โ”‚ 0.183428  โ”‚
โ”‚ 5   โ”‚ 0.24599  โ”‚ 0.282592  โ”‚
โ”‚ 6   โ”‚ 0.980281 โ”‚ 0.945239  โ”‚
โ”‚ 7   โ”‚ 0.544812 โ”‚ 0.0105223 โ”‚
โ”‚ 8   โ”‚ 0.74989  โ”‚ 0.444539  โ”‚
โ”‚ 9   โ”‚ 0.041312 โ”‚ 0.606745  โ”‚
โ”‚ 10  โ”‚ 0.119828 โ”‚ 0.930362  โ”‚

julia> y = node(df -> df.x3, Xs) # pick off single column
Node @ 1โ€ฆ50 = #17(3โ€ฆ09)

julia> y()
10-element Array{Float64,1}:
 0.6768089324906859   
 0.8402212806559988   
 0.16667879796758434  
 0.0063863346857631065
 0.5395665542748085   
 0.2671635276250435   
 0.48041697446815457  
 0.6437900249878024   
 0.10077583630960008  
 0.41942618943917687  

To do the same thing for generic tables (which include DataFrames) you could use the built-in selectcols method (do ?selectcols for possible arguments):

julia> Y = node(table->selectcols(table, [:x1, :x2]), Xs)
Node @ 8โ€ฆ98 = #21(3โ€ฆ09)

julia> Y()
10ร—2 DataFrame
โ”‚ Row โ”‚ x1       โ”‚ x2        โ”‚
โ”‚     โ”‚ Float64  โ”‚ Float64   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1   โ”‚ 0.518405 โ”‚ 0.869467  โ”‚
โ”‚ 2   โ”‚ 0.16694  โ”‚ 0.381958  โ”‚
โ”‚ 3   โ”‚ 0.832859 โ”‚ 0.285973  โ”‚
โ”‚ 4   โ”‚ 0.32379  โ”‚ 0.183428  โ”‚
โ”‚ 5   โ”‚ 0.24599  โ”‚ 0.282592  โ”‚
โ”‚ 6   โ”‚ 0.980281 โ”‚ 0.945239  โ”‚
โ”‚ 7   โ”‚ 0.544812 โ”‚ 0.0105223 โ”‚
โ”‚ 8   โ”‚ 0.74989  โ”‚ 0.444539  โ”‚
โ”‚ 9   โ”‚ 0.041312 โ”‚ 0.606745  โ”‚
โ”‚ 10  โ”‚ 0.119828 โ”‚ 0.930362  โ”‚

(Maybe we should provide a version of selectcols overloaded for nodes out-of-the-box?)

To merge columns, you can do things like this:

julia> t(v1, v2) = (a=v1, b=v2)  # define a function to put two vectors into a column table
t (generic function with 1 method)

julia> t(rand(3), [1, 2, 3])
(a = [0.0223417, 0.723267, 0.511597],
 b = [1, 2, 3],)

julia> as = source(rand(3))
Source{:input} @ 1โ€ฆ65

julia> bs = source([1,2,3])
Source{:input} @ 2โ€ฆ88

julia> X = node(t, as, bs)
โ”Œ Warning: A node referencing multiple origins when called has been defined:
โ”‚ MLJ.Source{:input}[Source{:input} @ 1โ€ฆ65, Source{:input} @ 2โ€ฆ88]. 
โ”” @ MLJ ~/Dropbox/Julia7/MLJ/MLJ/src/networks.jl:197
Node @ 1โ€ฆ00 = t(1โ€ฆ65, 2โ€ฆ88)

julia> X()
(a = [0.750533, 0.4004, 0.808384],
 b = [1, 2, 3],)

(You can ignore the warning here.)

Does this answer your question?

All 5 comments

Good question. Answer is yes.

You can think as a node for a proxy for data. Anything you can do to data you can do to a node that is a proxy for that kind of data, using the node method; do ?node for details. Here's a DataFrame example:

julia> X = DataFrame(rand(10,3))
10ร—3 DataFrame
โ”‚ Row โ”‚ x1       โ”‚ x2        โ”‚ x3         โ”‚
โ”‚     โ”‚ Float64  โ”‚ Float64   โ”‚ Float64    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1   โ”‚ 0.518405 โ”‚ 0.869467  โ”‚ 0.676809   โ”‚
โ”‚ 2   โ”‚ 0.16694  โ”‚ 0.381958  โ”‚ 0.840221   โ”‚
โ”‚ 3   โ”‚ 0.832859 โ”‚ 0.285973  โ”‚ 0.166679   โ”‚
โ”‚ 4   โ”‚ 0.32379  โ”‚ 0.183428  โ”‚ 0.00638633 โ”‚
โ”‚ 5   โ”‚ 0.24599  โ”‚ 0.282592  โ”‚ 0.539567   โ”‚
โ”‚ 6   โ”‚ 0.980281 โ”‚ 0.945239  โ”‚ 0.267164   โ”‚
โ”‚ 7   โ”‚ 0.544812 โ”‚ 0.0105223 โ”‚ 0.480417   โ”‚
โ”‚ 8   โ”‚ 0.74989  โ”‚ 0.444539  โ”‚ 0.64379    โ”‚
โ”‚ 9   โ”‚ 0.041312 โ”‚ 0.606745  โ”‚ 0.100776   โ”‚
โ”‚ 10  โ”‚ 0.119828 โ”‚ 0.930362  โ”‚ 0.419426   โ”‚

julia> Xs = source(X)
Source{:input} @ 3โ€ฆ09

julia> Y = node(df -> df[:, [:x1, :x2]], Xs)  # pick off multiple columns
Node @ 1โ€ฆ84 = #15(3โ€ฆ09)

julia> Y()
10ร—2 DataFrame
โ”‚ Row โ”‚ x1       โ”‚ x2        โ”‚
โ”‚     โ”‚ Float64  โ”‚ Float64   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1   โ”‚ 0.518405 โ”‚ 0.869467  โ”‚
โ”‚ 2   โ”‚ 0.16694  โ”‚ 0.381958  โ”‚
โ”‚ 3   โ”‚ 0.832859 โ”‚ 0.285973  โ”‚
โ”‚ 4   โ”‚ 0.32379  โ”‚ 0.183428  โ”‚
โ”‚ 5   โ”‚ 0.24599  โ”‚ 0.282592  โ”‚
โ”‚ 6   โ”‚ 0.980281 โ”‚ 0.945239  โ”‚
โ”‚ 7   โ”‚ 0.544812 โ”‚ 0.0105223 โ”‚
โ”‚ 8   โ”‚ 0.74989  โ”‚ 0.444539  โ”‚
โ”‚ 9   โ”‚ 0.041312 โ”‚ 0.606745  โ”‚
โ”‚ 10  โ”‚ 0.119828 โ”‚ 0.930362  โ”‚

julia> y = node(df -> df.x3, Xs) # pick off single column
Node @ 1โ€ฆ50 = #17(3โ€ฆ09)

julia> y()
10-element Array{Float64,1}:
 0.6768089324906859   
 0.8402212806559988   
 0.16667879796758434  
 0.0063863346857631065
 0.5395665542748085   
 0.2671635276250435   
 0.48041697446815457  
 0.6437900249878024   
 0.10077583630960008  
 0.41942618943917687  

To do the same thing for generic tables (which include DataFrames) you could use the built-in selectcols method (do ?selectcols for possible arguments):

julia> Y = node(table->selectcols(table, [:x1, :x2]), Xs)
Node @ 8โ€ฆ98 = #21(3โ€ฆ09)

julia> Y()
10ร—2 DataFrame
โ”‚ Row โ”‚ x1       โ”‚ x2        โ”‚
โ”‚     โ”‚ Float64  โ”‚ Float64   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1   โ”‚ 0.518405 โ”‚ 0.869467  โ”‚
โ”‚ 2   โ”‚ 0.16694  โ”‚ 0.381958  โ”‚
โ”‚ 3   โ”‚ 0.832859 โ”‚ 0.285973  โ”‚
โ”‚ 4   โ”‚ 0.32379  โ”‚ 0.183428  โ”‚
โ”‚ 5   โ”‚ 0.24599  โ”‚ 0.282592  โ”‚
โ”‚ 6   โ”‚ 0.980281 โ”‚ 0.945239  โ”‚
โ”‚ 7   โ”‚ 0.544812 โ”‚ 0.0105223 โ”‚
โ”‚ 8   โ”‚ 0.74989  โ”‚ 0.444539  โ”‚
โ”‚ 9   โ”‚ 0.041312 โ”‚ 0.606745  โ”‚
โ”‚ 10  โ”‚ 0.119828 โ”‚ 0.930362  โ”‚

(Maybe we should provide a version of selectcols overloaded for nodes out-of-the-box?)

To merge columns, you can do things like this:

julia> t(v1, v2) = (a=v1, b=v2)  # define a function to put two vectors into a column table
t (generic function with 1 method)

julia> t(rand(3), [1, 2, 3])
(a = [0.0223417, 0.723267, 0.511597],
 b = [1, 2, 3],)

julia> as = source(rand(3))
Source{:input} @ 1โ€ฆ65

julia> bs = source([1,2,3])
Source{:input} @ 2โ€ฆ88

julia> X = node(t, as, bs)
โ”Œ Warning: A node referencing multiple origins when called has been defined:
โ”‚ MLJ.Source{:input}[Source{:input} @ 1โ€ฆ65, Source{:input} @ 2โ€ฆ88]. 
โ”” @ MLJ ~/Dropbox/Julia7/MLJ/MLJ/src/networks.jl:197
Node @ 1โ€ฆ00 = t(1โ€ฆ65, 2โ€ฆ88)

julia> X()
(a = [0.750533, 0.4004, 0.808384],
 b = [1, 2, 3],)

(You can ignore the warning here.)

Does this answer your question?

Thank you very much for your help, greatly appreciated!

@ablaom sorry for the repeated questions, but I have another one.

Let's say I want to first pre-train and then fine-tune a neural net. Thus, in the the training phase of the learning network, the training data is split into 'pre' and 'fine'. Model 'NNpre' is trained and the weights are saved. Then model 'NNfine' is trained with initial weights equal to the previously saved weights. All of the above are shown in the diagram below.

The problem I have is the following: If I do not include any 'predict' nodes after 'NNpre' or 'NNfine', they are just not trained, because their respective nodal machines do not lead to the final node. However, if I include 'predict' nodes, the test data will also be split into 'pre' and 'fine' (?). The general question is if there is a way to separate the flow of information between the training and predicting phases. Could for example some nodes be activated only during training and not predicting and vice versa?

Thanks again!

network

@petrostat13 Thanks for the interesting use-case.

I don't immediately see a way for the existing API to wrap everything you want in a single composite model. As I understand it, what you want to do is update the learned parameters of some model (a self-tuning neural-network) based on new data. That is, you want online learning, which isn't currently supported, but hopefully will be at some point (https://github.com/alan-turing-institute/MLJ.jl/issues/60).

Re the original comment: selectcols is now overloaded for nodes (MLJ 0.5.1):

julia> using DataFrames, MLJ

julia> X = source(DataFrame(rand(2,3)))
Source{:input} @ 6โ€ฆ49

julia> X()
2ร—3 DataFrame
โ”‚ Row โ”‚ x1       โ”‚ x2       โ”‚ x3       โ”‚
โ”‚     โ”‚ Float64  โ”‚ Float64  โ”‚ Float64  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1   โ”‚ 0.23562  โ”‚ 0.914597 โ”‚ 0.414423 โ”‚
โ”‚ 2   โ”‚ 0.529633 โ”‚ 0.280383 โ”‚ 0.641822 โ”‚

julia> v = selectcols(X, :x1)
Node @ 6โ€ฆ70 = #40(6โ€ฆ49)

julia> v()
2-element Array{Float64,1}:
 0.2356195529795364
 0.5296331600177344

julia> Xsmall = selectcols(X, 1:2)
Node @ 1โ€ฆ11 = #40(6โ€ฆ49)

julia> Xsmall()
2ร—2 DataFrame
โ”‚ Row โ”‚ x1       โ”‚ x2       โ”‚
โ”‚     โ”‚ Float64  โ”‚ Float64  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 1   โ”‚ 0.23562  โ”‚ 0.914597 โ”‚
โ”‚ 2   โ”‚ 0.529633 โ”‚ 0.280383 โ”‚
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ExpandingMan picture ExpandingMan  ยท  7Comments

juliohm picture juliohm  ยท  8Comments

ChrisRackauckas picture ChrisRackauckas  ยท  7Comments

tlienart picture tlienart  ยท  7Comments

azev77 picture azev77  ยท  6Comments