841 lines
23 KiB
Julia
841 lines
23 KiB
Julia
module interface
|
||
|
||
|
||
export noNegative!, randomWithProb, randomChoiceWithProb, findIndex, limitvalue, replaceMoreThan,
|
||
replaceLessThan, cartesianAssign!, sumAlongDim3, matMul_3Dto3D_manyTo1batch,
|
||
matMul_3Dto4D_batchwise
|
||
|
||
using JSON3, DataStructures, Distributions, Flux, CUDA
|
||
|
||
#------------------------------------------------------------------------------------------------100
|
||
|
||
noNegative!(a::AbstractVector) = replace!(x -> x < 0 ? 0 : x, a)
|
||
findNotZero(x::AbstractVector) = findall( (!iszero).(x) )
|
||
replaceMoreThan(i, target) = i > target ? target : i
|
||
replaceLessThan(i, target) = i < target ? target : i
|
||
precision(x::Array{<:Array}) = ( std(mean.(x)) / mean(mean.(x)) ) * 100
|
||
precision(x::Array) = std(x) / mean(x) * 100
|
||
replaceAt!(x::AbstractVector, ind::Number, value::Number) = x[ind] = value
|
||
notZero(x::AbstractVector) = (!iszero).(x)
|
||
Zero(x::AbstractVector) = iszero.(x)
|
||
isNan(x::AbstractVector) = isnan.(x)
|
||
isInf(x::Number) = abs(x) === Inf
|
||
isInf(x::AbstractVector) = isinf.(x)
|
||
isLess(x::AbstractVector, target::Number) = isless.(x, target)
|
||
isMore(x::Number, target::Number) = x > target
|
||
isMore(x::AbstractVector, target::Number) = isMore.(x, target)
|
||
absolute(x::AbstractVector) = abs.(x)
|
||
vecEleMul(x::AbstractVector, y::AbstractVector) = x .* y
|
||
vecEleMul(x::Number, y::AbstractVector) = x .* y
|
||
expDecay(initialValue::Number, decayFactor::Number, timePass::Number) =
|
||
initialValue * (1 - decayFactor)^timePass
|
||
mul!(x::AbstractVector, y::AbstractVector) = x .*= y
|
||
mul(x::AbstractVector, y::AbstractVector) = x .* y
|
||
|
||
ReLu(x::Number) = max(0, x)
|
||
|
||
updateVector!(x::AbstractVector, target::Number) = x .= target
|
||
updateVector!(x::AbstractVector, target::AbstractArray) = x .= target
|
||
|
||
function selectAdd!(x::AbstractVector, ind::AbstractVector, value::AbstractVector)
|
||
@. x = x + (ind * value)
|
||
end
|
||
|
||
|
||
""" findIndex(input::String, target::Char)
|
||
|
||
Find target index inside a collection.
|
||
Return 1) Bool array of matched target
|
||
2) CartesianIndex of every matched target
|
||
"""
|
||
function findIndex(input::String, target::Char)
|
||
match_position = []
|
||
for i in input
|
||
if i == target
|
||
append!(match_position, 1)
|
||
else
|
||
append!(match_position, 0)
|
||
end
|
||
end
|
||
match_index = findall(isequal.(match_position, 1))
|
||
|
||
return match_position, match_index
|
||
end
|
||
|
||
function findIndex(input::Array, target::Number)
|
||
match_position = isequal.(input, target)
|
||
match_index = findall(match_position)
|
||
return match_position, match_index
|
||
end
|
||
|
||
# function findIndex(input::Array, target::Array)
|
||
# match_position = isone.(zeros(length(input)))
|
||
# for i in target
|
||
# match_position = match_position + isequal.(input, i)
|
||
# end
|
||
|
||
# match_position = replaceMoreThan.(match_position, 1)
|
||
# match_index = findall(isone.(match_position)) # Findall donot work with Int64 vector [1, 0, 0, 1].
|
||
# # It only works with BitVector. isone() converts Int64 vector [1, 0, 0, 1] into
|
||
# # BitVector [1, 0, 0, 1]
|
||
|
||
# return match_position, match_index
|
||
# end
|
||
|
||
function findIndex(input::Array, target::Symbol)
|
||
match_position = isequal.(input, target)
|
||
match_index = findall(match_position)
|
||
|
||
return match_position, match_index
|
||
end
|
||
|
||
function findIndex(collection::Array{String}, target::String)
|
||
match_position = isequal.(collection, target)
|
||
match_index = findall(match_position)
|
||
|
||
return match_position, match_index
|
||
end
|
||
|
||
function findIndex(collection::Array{String}, target::Array{String})
|
||
match_position = nothing
|
||
match_index = nothing
|
||
|
||
for i in target
|
||
match_pos = isequal.(collection, i)
|
||
match_ind = findall(match_pos)
|
||
|
||
if match_position === nothing
|
||
match_position = match_pos
|
||
|
||
else
|
||
match_position = hcat(match_position, match_pos)
|
||
end
|
||
|
||
if match_index === nothing
|
||
match_index = match_ind
|
||
else
|
||
match_index = hcat(match_index, match_ind)
|
||
end
|
||
end
|
||
|
||
return match_position, match_index
|
||
end
|
||
|
||
function findIndex(collection::OrderedDict, target::Symbol)
|
||
collection_keys = keys(collection)
|
||
collection_keys_array = [i for i in collection_keys]
|
||
match_position = isequal.(collection_keys_array, target)
|
||
match_index = findall(match_position)
|
||
|
||
return match_position, match_index
|
||
end
|
||
|
||
function findMax(collection::AbstractVector)
|
||
maxValue, maxIndex = findmax(collection)
|
||
matchPosition = isequal.(collection, maxValue)
|
||
return maxValue, maxIndex, matchPosition
|
||
end
|
||
|
||
|
||
|
||
""" read_textfile_by_index(folder_path::String, read_file_number::Integer=1)
|
||
|
||
with multiple text file in a folder,
|
||
this function read x_th text file in a folder (filename is sorted by OS)
|
||
|
||
# Example
|
||
utils.read_textfile_by_index(cleaned_data_path, 2)
|
||
read 2nd txt file in a folder
|
||
"""
|
||
function read_textfile_by_index(folder_path::String, read_file_number::Integer=1)
|
||
if isdir(folder_path)
|
||
filenumber = length(readdir(folder_path))
|
||
|
||
if read_file_number > filenumber
|
||
error("you specified read_file_number = $read_file_number which is out
|
||
of range, the cleaned data folder has only $filenumber files")
|
||
return nothing, nothing, nothing
|
||
else
|
||
content = 0
|
||
# open each file in the directory and read
|
||
filename = readdir(folder_path, join=true, sort=false)[read_file_number]
|
||
f = open(filename)
|
||
content = readlines(f)
|
||
# content = read(f)
|
||
close(f)
|
||
end
|
||
|
||
return read_file_number, filename, content
|
||
else
|
||
error("ERROR no file or folder at $folder_path")
|
||
return nothing, nothing, nothing
|
||
end
|
||
|
||
end
|
||
|
||
#------------------------------------------------------------------------------------------------100
|
||
|
||
""" Array_to_JSON3_str(data::AbstractArray)
|
||
|
||
encode Array to JSON3 String
|
||
|
||
# Example
|
||
|
||
a = [1.23 4.7889; 9987.1 -123.07; -0.0027 -6.75]
|
||
json3_str = Array_to_JSON3_str(a)
|
||
|
||
json3_str = {"Array":[1.23,9987.1,-0.0027,4.7889,-123.07,-6.75],"size":[3,2]}
|
||
"""
|
||
function Array_to_JSON3_str(data::AbstractArray)
|
||
d = Dict("Array"=> data, "size"=>size(data))
|
||
json3_str = JSON3.write(d)
|
||
return json3_str
|
||
end
|
||
|
||
#------------------------------------------------------------------------------------------------100
|
||
|
||
""" JSON3_str_to_Array(json3_str::String)
|
||
|
||
decode JSON3 String to Array
|
||
|
||
# Example
|
||
|
||
json3_str = {"Array":[1.23,9987.1,-0.0027,4.7889,-123.07,-6.75],"size":[3,2]}
|
||
a = JSON3_str_to_Array(json3_str)
|
||
|
||
a = [1.23 4.7889; 9987.1 -123.07; -0.0027 -6.75]
|
||
"""
|
||
function JSON3_str_to_Array(json3_str::String)
|
||
d = JSON3.read(json3_str)
|
||
array = reshape(Array(d.Array), (d.size[1], d.size[2]))
|
||
return array
|
||
end
|
||
|
||
#------------------------------------------------------------------------------------------------100
|
||
|
||
""" Convert JSON3.read object to OrderedDict
|
||
|
||
# Example
|
||
dict = dictionary(["a"=>4, "b"=>6])
|
||
OrDict = OrderedDict(dict)
|
||
jsonString = JSON3.write(OrDict) # use jsonString to exchange. One can save it to file or send it thru pub/sub
|
||
jsonObject = JSON3.read(jsonString)
|
||
OrDict2 = JSON3read_to_OrDict(jsonObject) # example here
|
||
Adict2 = dictionary(OrDict2)
|
||
|
||
Andyferris's github https://github.com/andyferris/Dictionaries.jl
|
||
"""
|
||
function JSON3read_to_OrDict(x)
|
||
dict = OrderedDict()
|
||
for (k, v) in x
|
||
k = string(k)
|
||
dict[k] = v
|
||
end
|
||
return dict
|
||
end
|
||
|
||
#------------------------------------------------------------------------------------------------100
|
||
|
||
"""
|
||
print time of cpu executtion at the line inwhich this macro is used
|
||
"""
|
||
macro timeline(expr)
|
||
quote
|
||
print("line ", $(__source__.line), ": ")
|
||
@time $(esc(expr))
|
||
end
|
||
end
|
||
|
||
|
||
batchindex(batch_counter::Number, batch_size::Number; offset=0) =
|
||
(offset + (batch_counter-1) * batch_size + 1) : offset + (batch_counter * batch_size)
|
||
|
||
function flip_true_false(x::Bool)
|
||
if x == true
|
||
x = false
|
||
elseif x == false
|
||
x = true
|
||
else
|
||
error("undefined condition line $(@__LINE__)")
|
||
end
|
||
|
||
return x
|
||
end
|
||
|
||
function flip_true_false(x::Int)
|
||
if x == 1
|
||
x = 0
|
||
elseif x == 0
|
||
x = 1
|
||
else
|
||
throw("not define input of type $(typeof(x)) yet")
|
||
end
|
||
|
||
return x
|
||
end
|
||
|
||
"""
|
||
Return drawed index
|
||
# Example
|
||
drawed_index = randomWithProb([0.5, 0.2, 0.3])
|
||
|
||
"""
|
||
randomWithProb(probability::AbstractVector) = rand(Distributions.Categorical(probability)) # return drawed index
|
||
|
||
"""
|
||
Draw from choices according to its probability.
|
||
Probability range is 0.0 to 1.0 and all probability must summed up to 1
|
||
(may get probability from NNlib's softmax function)
|
||
|
||
# Example
|
||
|
||
draw = draw_choices([true, false, nothing], [0.5, 0.2, 0.3])
|
||
"""
|
||
function randomChoiceWithProb(choices::Array, probability::Array)
|
||
if length(choices) != length(probability)
|
||
error("random is not possible, choices array length != probability array length")
|
||
elseif sum(probability) != 1.0
|
||
error("probability does not sum to 1.0")
|
||
end
|
||
|
||
return choices[randomWithProb(probability)]
|
||
end
|
||
|
||
function randomChoiceOnTarget(target::Number, targetMatch::Number, choices::AbstractVector,
|
||
probability::AbstractVector)
|
||
if length(choices) != length(probability)
|
||
throw("random is not possible, choices array length != probability array length")
|
||
end
|
||
return target == targetMatch ? randomChoiceWithProb(choices, probability) : target
|
||
# dist = Distributions.Categorical(probability)
|
||
# draw_result = choices[rand(dist)]
|
||
end
|
||
|
||
function randomChoiceOnTarget(target::AbstractVector, choiceList::AbstractVector,
|
||
probability::AbstractVector)
|
||
return randomChoiceOnTarget.(target, 1, (choiceList,), (probability,))
|
||
end
|
||
|
||
function linearly_weighted_avg(a::Array)
|
||
total = 0.0
|
||
for (i, v) in enumerate(a)
|
||
total = total + (i * v)
|
||
end
|
||
|
||
return total / sum(a)
|
||
end
|
||
|
||
|
||
""" Convert String that is holded inside a variable to Symbol
|
||
# Example
|
||
x = "hello" # x is a variable holding String "hello" \n
|
||
y = variable_to_symbol(x) # y holds :hello
|
||
"""
|
||
function variable_str_to_symbol(variable)
|
||
semi = :($variable)
|
||
symbol = Symbol(semi)
|
||
|
||
return symbol
|
||
end
|
||
|
||
|
||
""" get useable type of specified fieldname inside a composite struct
|
||
|
||
# Example
|
||
julia> @Base.kwdef mutable struct some_struct
|
||
a::Union{Bool, Nothing} = nothing
|
||
b::Union{Float64, Nothing} = nothing
|
||
c::Union{Int64, AbstractFloat} = 3.5
|
||
d::Union{String, Nothing} = nothing
|
||
end
|
||
|
||
julia> a = some_struct()
|
||
julia> fieldname_useable_type(some_struct, :c) =result=> [Int64, Float64]
|
||
"""
|
||
function fieldname_useable_type(somestruct, fieldname::Symbol;
|
||
test_types=[2.0, 2, true, :h, "str", 'c', missing, nothing])::Vector{DataType}
|
||
new_instance = somestruct()
|
||
useable_type = []
|
||
|
||
for i in test_types
|
||
try
|
||
new_instance.:($fieldname) = i
|
||
type = typeof(new_instance.:($fieldname))
|
||
if type ∉ useable_type
|
||
push!(useable_type, type)
|
||
end
|
||
catch
|
||
|
||
end
|
||
end
|
||
|
||
return useable_type
|
||
end
|
||
|
||
|
||
function randomNoRepeat(drawOptions::Array, draw_number::Integer;
|
||
exclude_list::Union{AbstractArray,Nothing}=nothing)
|
||
draw_option = copy(drawOptions)
|
||
draw_option = isnothing(exclude_list) ? draw_option :
|
||
filter!(x -> x ∉ exclude_list, draw_option)
|
||
shuffle!(draw_option)
|
||
drawed_items = []
|
||
while length(drawed_items) < draw_number
|
||
push!(drawed_items, pop!(draw_option))
|
||
end
|
||
return drawed_items
|
||
end
|
||
|
||
""" using cron to schedule backup job by
|
||
1. sudo nano /etc/crontab <<< this is a system-wide cron file
|
||
2. to execute julia file @ 2.00am everyday add the following line at the buttom of the file
|
||
0 2 * * * root julia-1.7 /home/syncthing_backup_script.jl
|
||
|
||
Requirements using Dates
|
||
"""
|
||
function folderBackup(sourceFolderAbsolutePath::String, # absolute path to folder to be backuped
|
||
backupFolderAbsolutePath::String; # absolute path to folder used to store backup file
|
||
totalBackupFiles::Integer=7, # total backup file, the oldest will be deleted
|
||
containerName::Union{Array{String}, Nothing}=nothing) # container using source_folder
|
||
|
||
sep = (Sys.iswindows() ? "\\" : '/')
|
||
|
||
if sourceFolderAbsolutePath[end] == sep
|
||
sourceFolderAbsolutePath = sourceFolderAbsolutePath[1:end-1]
|
||
end
|
||
if backupFolderAbsolutePath[end] != sl
|
||
backupFolderAbsolutePath = backupFolderAbsolutePath * sep
|
||
end
|
||
|
||
if isdir(backupFolderAbsolutePath)
|
||
else
|
||
mkpath(backupFolderAbsolutePath)
|
||
end
|
||
|
||
# stop running docker container service
|
||
if containerName !== nothing
|
||
println("stop running services")
|
||
for i in containerName
|
||
try run(`docker stop $i`) catch; end
|
||
sleep(10) # wait for services to stop
|
||
end
|
||
end
|
||
|
||
# do backup
|
||
println("doing backup now")
|
||
timestamp = string(Dates.now())
|
||
name = split(sourceFolderAbsolutePath, sep)[end] * "--"
|
||
filename = name * timestamp * ".zip" # resulting compressed filename
|
||
run(`chmod -R a+rwx $sourceFolderAbsolutePath`)
|
||
# zip -r [destination+filename] [source folder to be zipped]
|
||
run(`zip -r $(backupFolderAbsolutePath * filename) $sourceFolderAbsolutePath`)
|
||
|
||
# check if total backup file is more than user specified, if yes, delete the oldest backup
|
||
backupFiles = readdir(backupFolderAbsolutePath)
|
||
|
||
while length(backupFiles) > totalBackupFiles
|
||
run(`rm $(backupFolderAbsolutePath * backupFiles[1])`)
|
||
backupFiles = readdir(backupFolderAbsolutePath)
|
||
end
|
||
|
||
# start docker services
|
||
if containerName !== nothing
|
||
println("start services")
|
||
for i in containerName
|
||
try run(`docker start $i`) catch; end
|
||
sleep(10) # wait for services to stop
|
||
end
|
||
end
|
||
end
|
||
|
||
function lowerclip!(data::AbstractVector, lowerbound::Number)
|
||
replace!(x -> x < lowerbound ? lowerbound : x, data)
|
||
end
|
||
|
||
function upperclip!(data::AbstractVector, upperbound::Number)
|
||
replace!(x -> x > upperbound ? upperbound : x, data)
|
||
end
|
||
|
||
function normalise(x::AbstractArray, mu, std)
|
||
ϵ = oftype(x[1], 1e-5)
|
||
μ = mu
|
||
# σ = std(x, dims=dims, mean=μ, corrected=false) # use this when Zygote#478 gets merged
|
||
σ = std
|
||
return (x .- μ) ./ (σ .+ ϵ)
|
||
end
|
||
|
||
function minMaxScaler(x::AbstractVector)
|
||
min = findmin(x)[1]
|
||
max = findmax(x)[1]
|
||
|
||
scaler(a::Number, min::Number, max::Number) = (a-min) / (max-min)
|
||
return scaler.(x, min, max)
|
||
end
|
||
|
||
""" a = [-1e200, -1e-200, 1e200, 1e-200] \n
|
||
result = vtclamp.(a, 1e-6, 1e6, -1e6, -1e-6)
|
||
"""
|
||
function customclamp(x::Number, poslo::Number, poshi::Number,
|
||
neglo::Number, neghi::Number)
|
||
signx = sign(x)
|
||
if signx == -1
|
||
if neghi < x < 0
|
||
return neghi
|
||
elseif x < neglo
|
||
return neglo
|
||
else
|
||
return x
|
||
end
|
||
elseif signx == +1
|
||
if poshi < x
|
||
return poshi
|
||
elseif 0 < x < poslo
|
||
return poslo
|
||
else
|
||
return x
|
||
end
|
||
end
|
||
end
|
||
|
||
function unitVec(x::AbstractVector)
|
||
y = √(sum(x.^2))
|
||
return x./y
|
||
end
|
||
|
||
function replaceAt!(x::AbstractVector, ind::AbstractVector, value::Number)
|
||
for i in ind
|
||
x[i] = value
|
||
end
|
||
end
|
||
|
||
function signbitVec(x::AbstractVector)
|
||
sign = signbit.(x) * 1
|
||
signVec = replace(s -> s == 0 ? -1 : s, sign)
|
||
return signVec
|
||
end
|
||
|
||
function deleteall!(x::AbstractVector)
|
||
for i in 1:length(x)
|
||
deleteat!(x, 1)
|
||
end
|
||
end
|
||
|
||
""" Select specific range of vectors in a dict, return a new dict
|
||
# Example
|
||
dict = Dict(:a => [1:5...],
|
||
:b => [6:10...])
|
||
|
||
call -> selectRange(dict, 1:3)
|
||
return -> Dict{Any, Any} with 2 entries:
|
||
:a => [1, 2, 3]
|
||
:b => [6, 7, 8]
|
||
"""
|
||
function selectRange(d::Dict{Symbol, <:AbstractVector}, range)
|
||
newDict = Dict{Symbol, AbstractVector}()
|
||
for (k, v) in d
|
||
newDict[k] = v[range]
|
||
end
|
||
|
||
return newDict
|
||
end
|
||
|
||
""" Assign value to a given Dict by array of keys
|
||
|
||
# Example
|
||
d = Dict(
|
||
:a1=> Dict(:c=> 5),
|
||
:a2=> Dict(
|
||
:k=> 10,
|
||
:b=> Dict(
|
||
:s=> "target",
|
||
)
|
||
)
|
||
)
|
||
index = [:a2, :b, :s] \n
|
||
assignDict!(d, [:a2, :b, :s], "wow")
|
||
|
||
return 1 if no target key in a given dict.
|
||
"""
|
||
function assignDict!(dict::Dict, accessArray::Array{Symbol}, valueToAssign)
|
||
wd = nothing
|
||
for i in accessArray
|
||
println(i)
|
||
if i != accessArray[end]
|
||
if wd === nothing && haskey(dict, i)
|
||
wd = Ref(dict[i])
|
||
elseif wd.x !== nothing && haskey(wd.x, i)
|
||
wd = Ref(wd.x[i])
|
||
else
|
||
return 1 # error, no target key in a given dict.
|
||
end
|
||
else
|
||
wd.x[i] = valueToAssign
|
||
return 0
|
||
end
|
||
end
|
||
end
|
||
|
||
""" convert hour(0-23), minute(0-59) into julia time object
|
||
# Example
|
||
time
|
||
"""
|
||
function iTime(h::Integer, m::Integer)
|
||
if h == 0
|
||
h = 12
|
||
ampm = "am"
|
||
elseif 1 <= h <= 11
|
||
ampm = "am"
|
||
elseif h == 12
|
||
ampm = "pm"
|
||
elseif 13 <= h <= 23
|
||
h = h - 12
|
||
ampm = "pm"
|
||
else
|
||
error("hour out of range")
|
||
end
|
||
|
||
m = m < 10 ? "0$m" : m
|
||
t = "$h:$m$ampm"
|
||
|
||
return Time(t, "HH:MMp")
|
||
end
|
||
|
||
""" replace a number according to the limit
|
||
if value is lower than lowerbound return lowerbound replacement value
|
||
if value is more than upperbound return upperbound replacement value
|
||
|
||
# Example
|
||
limitvalue(4, (-5 => 0), (5 => 5))
|
||
"""
|
||
function limitvalue(v::Number, lowerbound::Pair, upperbound::Pair)
|
||
lwLimit, lwReplace = lowerbound
|
||
upLimit, upReplace = upperbound
|
||
|
||
if v < lwLimit
|
||
v = lwReplace
|
||
elseif v > upLimit
|
||
v = upReplace
|
||
else
|
||
end
|
||
return v
|
||
end
|
||
|
||
""" assign matrix b to matrix a according to matrix b's CartesianIndex
|
||
"""
|
||
cartesianAssign!(a::CuArray, b::CuArray) = @cuda cartesianAssign!(a, b)
|
||
|
||
function cartesianAssign!(a, b)
|
||
for (i, v) in enumerate(b)
|
||
a[CartesianIndices(b)[i].I...] = v
|
||
end
|
||
return nothing
|
||
end
|
||
|
||
function sumAlongDim3(a::Array)
|
||
totalDim = length(size(a))
|
||
|
||
if totalDim == 3
|
||
d1, d2, d3 = size(a)
|
||
r = zeros(1, 1, d3)
|
||
for i in 1:d3
|
||
view(r, 1, 1, i) .= sum(a[:, :, i])
|
||
end
|
||
elseif totalDim == 4
|
||
d1, d2, d3, d4 = size(a)
|
||
r = zeros(1, 1, d3, d4)
|
||
for j in 1:d4
|
||
for i in 1:d3
|
||
view(r, 1, 1, i, j) .= sum(a[:, :, i, j])
|
||
end
|
||
end
|
||
else
|
||
error("this condition is not define yet")
|
||
end
|
||
|
||
return r
|
||
end
|
||
|
||
|
||
""" ELEMENT-wise multiply of each slice of 3D input matrix ,a, to all slice of 3D another matrix ,b, and
|
||
concatenate at the 4th dimension.
|
||
|
||
Example
|
||
julia> input = rand(32, 32, 128) # batch at 3rd dim
|
||
julia> weight = rand(32, 32, 1024)
|
||
julia> r = matMul_3Dto3D_manyTo1batch(input, weight);
|
||
julia> size(r)
|
||
(32, 32, 1024, 128)
|
||
"""
|
||
function matMul_3Dto3D_manyTo1batch(a::Array, b::Array; resultStorage::Union{Array, Nothing}=nothing)
|
||
asize = [size(a)...]
|
||
bsize = [size(b)...]
|
||
if resultStorage === nothing
|
||
resultStorage = similar(a, eltype(b), bsize[1], bsize[2], bsize[3], asize[3])
|
||
end
|
||
|
||
c = [slice .* b for slice in eachslice(a, dims=3)]
|
||
resultStorage .= cat(c..., dims=4)
|
||
|
||
return resultStorage
|
||
end
|
||
|
||
# function matMul_3Dto3D_manyTo1batch(a::CuArray, b::CuArray; # XXX working code
|
||
# resultStorage::Union{CuArray, Nothing}=nothing, threads=256)
|
||
# asize = [size(a)...]
|
||
# bsize = [size(b)...]
|
||
# if resultStorage === nothing
|
||
# resultStorage = similar(a, eltype(b), bsize[1], bsize[2], bsize[3], asize[3]) |> gpu
|
||
# end
|
||
# CUDA.@sync begin
|
||
# @cuda threads=threads matMul_3Dto3D_manyTo1batch_gpu!(a, b, resultStorage)
|
||
# end
|
||
# return resultStorage
|
||
# end
|
||
|
||
# function matMul_3Dto3D_manyTo1batch_gpu!(a, b, resultStorage) # XXX working code
|
||
# _, _, _, p = size(resultStorage)
|
||
|
||
# index = threadIdx().x # this example only requires linear indexing, so just use `x`
|
||
# stride = blockDim().x
|
||
# for i in index:stride:p
|
||
# view(resultStorage, :, :, :, i) .= view(a, :, :, i) .* b
|
||
# end
|
||
# return nothing
|
||
# end
|
||
|
||
""" GPU kernel
|
||
"""
|
||
function matMul_3Dto3D_manyTo1batch_gpu!(a, b, resultStorage)
|
||
_, _, batch = size(a) # This kernel use 1 thread per batch
|
||
i = (blockIdx().x - 1) * blockDim().x + threadIdx().x
|
||
if i <= batch # guard against unused threads to accessing memory out of bound
|
||
# @cuprintln("thread $i")
|
||
view(resultStorage, :, :, :, i) .= view(a, :, :, i) .* b
|
||
end
|
||
return nothing
|
||
end
|
||
|
||
""" GPU version of batchMatEleMul
|
||
|
||
Example
|
||
julia> using Flux, CUDA
|
||
julia> device = Flux.CUDA.functional() ? gpu : cpu
|
||
julia> if device == gpu CUDA.device!(0) end
|
||
julia> input = rand(32, 32, 128) |> gpu; # 128-batches
|
||
julia> weight = rand(32, 32, 1024) |> gpu; # 1-batch
|
||
julia> r = matMul_3Dto3D_manyTo1batch(input, weight);
|
||
julia> size(r)
|
||
(32, 32, 1024, 128)
|
||
"""
|
||
function matMul_3Dto3D_manyTo1batch(a::CuArray, b::CuArray;
|
||
resultStorage::Union{CuArray, Nothing}=nothing)
|
||
asize = [size(a)...]
|
||
bsize = [size(b)...]
|
||
if resultStorage === nothing
|
||
resultStorage = similar(a, eltype(b), bsize[1], bsize[2], bsize[3], asize[3]) |> gpu
|
||
end
|
||
|
||
kernel = @cuda launch=false matMul_3Dto3D_manyTo1batch_gpu!(a, b, resultStorage)
|
||
config = launch_configuration(kernel.fun)
|
||
|
||
# threads to be launched. Since one can't launch exact thread number the kernel needs,
|
||
# one just launch threads more than this kernel needs then use a guard inside the kernel
|
||
# to prevent unused threads to access memory.
|
||
threads = min(1024, config.threads) # most NVIDIA gpu has 1024 threads per block
|
||
blocks = cld(asize[3], threads) # This kernel use 1 thread per batch
|
||
|
||
CUDA.@sync begin
|
||
kernel(a, b, resultStorage; threads, blocks)
|
||
end
|
||
return resultStorage
|
||
end
|
||
|
||
|
||
|
||
|
||
""" ELEMENT-wise multiply of each slice of 3D input matrix ,a, to all batch of another 4D matrix ,b, and
|
||
concatenate at the 4th dimension.
|
||
|
||
Example
|
||
julia>
|
||
julia> a = rand(2,2,3) # 3-batches
|
||
julia> b = rand(2,2,4,3) # 3-batches
|
||
julia> r = GeneralUtils.matMul_3Dto4D_batchwise(a, b);
|
||
julia> size(r)
|
||
(2, 2, 4, 3)
|
||
"""
|
||
function matMul_3Dto4D_batchwise(a::Array, b::Array; resultStorage::Union{Array, Nothing}=nothing)
|
||
asize = [size(a)...]
|
||
bsize = [size(b)...]
|
||
if asize[end] != bsize[end]
|
||
error("batch number of a and b must be equal")
|
||
end
|
||
if resultStorage === nothing
|
||
resultStorage = zeros(bsize[1], bsize[2], bsize[3], asize[3])
|
||
end
|
||
for i in 1:asize[3]
|
||
view(resultStorage, :, :, :, i) .= a[:, :, i] .* b[:, :, :, i]
|
||
end
|
||
return resultStorage
|
||
end
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
end # module |