訂閱
糾錯(cuò)
加入自媒體

使用Flux.jl進(jìn)行圖像分類

在PyTorch從事一個(gè)項(xiàng)目,這個(gè)項(xiàng)目創(chuàng)建一個(gè)深度學(xué)習(xí)模型,可以檢測(cè)未知物種的疾病。

最近,決定在Julia中重建這個(gè)項(xiàng)目,并將其用作學(xué)習(xí)Flux.jl[1]的練習(xí),這是Julia最流行的深度學(xué)習(xí)包(至少在GitHub上按星級(jí)排名)。

但在這樣做的過(guò)程中,遇到了一些挑戰(zhàn),這些挑戰(zhàn)在網(wǎng)上或文檔中找不到好的例子。因此,決定寫這篇文章,作為其他任何想在Flux做類似事情的人的參考資料。

這是給誰(shuí)的?

因?yàn)镕lux.jl(以下簡(jiǎn)稱為“Flux”)是一個(gè)深度學(xué)習(xí)包,所以我主要為熟悉深度學(xué)習(xí)概念(如遷移學(xué)習(xí))的讀者編寫這篇文章。

雖然在寫這篇文章時(shí)也考慮到了Flux的一個(gè)半新手(比如我自己),但其他人可能會(huì)覺(jué)得這很有價(jià)值。只是要知道,寫這篇文章并不是對(duì)Julia或通量的全面介紹或指導(dǎo)。為此,將分別參考其他資源,如官方的Julia和Flux文檔。

最后,對(duì)PyTorch做了幾個(gè)比較。了解本文觀點(diǎn)并不需要有PyTorch的經(jīng)驗(yàn),但有PyTorch經(jīng)驗(yàn)的人可能會(huì)覺(jué)得它特別有趣。

為什么是Julia?為什么選擇Flux.jl?

如果你已經(jīng)使用了Julia和/或Flux,你可能可以跳過(guò)本節(jié)。此外,許多其他人已經(jīng)寫了很多關(guān)于這個(gè)問(wèn)題的帖子,所以我將簡(jiǎn)短介紹。

歸根結(jié)底,我喜歡Julia。它在數(shù)值計(jì)算方面很出色,編程時(shí)真的很開心,而且速度很快。原生快速:不需要NumPy或其他底層C++代碼的包裝器。

至于為什么選擇Flux,是因?yàn)樗荍ulia中最流行的深度學(xué)習(xí)框架,用純Julia編寫,可與Julia生態(tài)系統(tǒng)組合。

項(xiàng)目本身

好吧,既然我已經(jīng)無(wú)恥地說(shuō)服了Julia,現(xiàn)在是時(shí)候了解項(xiàng)目本身的信息了。

我使用了三個(gè)數(shù)據(jù)集——PlantVillage[2]、PlantLeaves[3]和PlantaeK[4]——涵蓋了許多不同的物種。

我使用PlantVillage作為訓(xùn)練集,其他兩個(gè)組合作為測(cè)試集。這意味著模型必須學(xué)習(xí)一些可以推廣到未知物種的知識(shí),因?yàn)闇y(cè)試集將包含未經(jīng)訓(xùn)練的物種。

了解到這一點(diǎn),我創(chuàng)建了三個(gè)模型:

使用ResNet遷移學(xué)習(xí)的基線

具有自定義CNN架構(gòu)的孿生(又名暹羅)神經(jīng)網(wǎng)絡(luò)

具有遷移學(xué)習(xí)的孿生神經(jīng)網(wǎng)絡(luò)

本文的大部分內(nèi)容將詳細(xì)介紹處理數(shù)據(jù)、創(chuàng)建和訓(xùn)練模型的一些挑戰(zhàn)和痛點(diǎn)。

處理數(shù)據(jù)

第一個(gè)挑戰(zhàn)是數(shù)據(jù)集的格式錯(cuò)誤。我不會(huì)在這里詳細(xì)介紹如何對(duì)它們進(jìn)行預(yù)處理,但最重要的是我創(chuàng)建了兩個(gè)圖像目錄,即訓(xùn)練和測(cè)試。

這兩個(gè)文件都填充了一長(zhǎng)串圖像,分別命名為img0.jpg、img1.jpg、imm2.jpg等。我還創(chuàng)建了兩個(gè)CSV,一個(gè)用于訓(xùn)練集,一個(gè)為測(cè)試集,其中一列包含文件名,一列包含標(biāo)簽。

上述結(jié)構(gòu)很關(guān)鍵,因?yàn)閿?shù)據(jù)集的總?cè)萘砍^(guò)10 GB,我電腦的內(nèi)存肯定無(wú)法容納,更不用說(shuō)GPU的內(nèi)存了。因此,我們需要使用DataLoader。(如果你曾經(jīng)使用過(guò)PyTorch,你會(huì)很熟悉;這里的概念與PyTorch基本相同。)

為了在Flux中實(shí)現(xiàn)這一點(diǎn),我們需要?jiǎng)?chuàng)建一個(gè)自定義結(jié)構(gòu)來(lái)包裝我們的數(shù)據(jù)集,以允許它批量加載數(shù)據(jù)。

為了讓我們的自定義結(jié)構(gòu)能夠構(gòu)造數(shù)據(jù)加載器,我們需要做的就是為類型定義兩個(gè)方法:length和getindex。下面是我們將用于數(shù)據(jù)集的實(shí)現(xiàn):

using Flux

using Images

using FileIO

using DataFrames

using Pipe

"""

    ImageDataContainer(labels_df, img_dir)

Implements the functions `length` and `getindex`, which are required to use ImageDataContainer

as an argument in a DataLoader for Flux.

"""

struct ImageDataContainer

    labels::AbstractVector

    filenames::AbstractVector{String}

    function ImageDataContainer(labels_df::DataFrame, img_dir::AbstractString)

        filenames = img_dir .* labels_df[!, 1] # first column should be the filenames

        labels = labels_df[!, 2] # second column should be the labels

        return new(labels, filenames)

    end

end

"Gets the number of observations for a given dataset."

function Base.length(dataset::ImageDataContainer)

    return length(dataset.labels)

end

"Gets the i-th to j-th observations (including labels) for a given dataset."

function Base.getindex(dataset::ImageDataContainer, idxs::Union{UnitRange,Vector})

    batch_imgs = map(idx -> load(dataset.filenames[idx]), idxs)

    batch_labels = map(idx -> dataset.labels[idx], idxs)

    "Applies necessary transforms and reshapings to batches and loads them onto GPU to be fed into a model."

    function transform_batch(imgs, labels)

        # convert imgs to 256×256×3×64 array (Height×Width×Color×Number) of floats (values between 0.0 and 1.0)

        # arrays need to be sent to gpu inside training loop for garbage collector to work properly

        batch_X = @pipe hcat(imgs...) |> reshape(_, (HEIGHT, WIDTH, length(labels))) |> channelview |> permutedims(_, (2, 3, 1, 4))

        batch_y = @pipe labels |> reshape(_, (1, length(labels)))

        return (batch_X, batch_y)

    end

    return transform_batch(batch_imgs, batch_labels)

end

本質(zhì)上,當(dāng)Flux試圖檢索一批圖像時(shí),它會(huì)調(diào)用getindex(dataloader, i:i+batchsize),這在Julia中相當(dāng)于dataloader[i:i+batchsize]。

因此,我們的自定義getindex函數(shù)獲取文件名列表,獲取適當(dāng)?shù)奈募,加載這些圖像,然后將其處理并重新塑造為適當(dāng)?shù)腍EIGHT × WIDTH × COLOR × NUMBER形狀。標(biāo)簽也是如此。

然后,我們的訓(xùn)練、驗(yàn)證和測(cè)試數(shù)據(jù)加載器可以非常容易地完成:

using Flux: Data.DataLoader

using CSV

using DataFrames

using MLUtils

# dataframes containing filenames for images and corresponding labels

const train_df = DataFrame(CSV.File(dataset_dir * "train_labels.csv"))

const test_df = DataFrame(CSV.File(dataset_dir * "test_labels.csv"))

# ImageDataContainer wrappers for dataframes

# gives interface for getting the actual images and labels as tensors

const train_dataset = ImageDataContainer(train_df, train_dir)

const test_dataset = ImageDataContainer(test_df, test_dir)

# randomly sort train dataset into training and validation sets

const train_set, val_set = splitobs(train_dataset, at=0.7, shuffle=true)

const train_loader = DataLoader(train_set, batchsize=BATCH_SIZE, shuffle=true)

const val_loader = DataLoader(val_set, batchsize=BATCH_SIZE, shuffle=true)

const test_loader = DataLoader(test_dataset, batchsize=BATCH_SIZE)

制作模型

數(shù)據(jù)加載器準(zhǔn)備就緒后,下一步是創(chuàng)建模型。首先是基于ResNet的遷移學(xué)習(xí)模型。事實(shí)證明,這項(xiàng)工作相對(duì)困難。

在Metalhead.jsl包中(包含用于遷移學(xué)習(xí)的計(jì)算機(jī)視覺(jué)Flux模型),創(chuàng)建具有預(yù)訓(xùn)練權(quán)重的ResNet18模型應(yīng)該與model = ResNet(18; pretrain = true)一樣簡(jiǎn)單。

然而,至少在編寫本文時(shí),創(chuàng)建預(yù)訓(xùn)練的模型會(huì)導(dǎo)致錯(cuò)誤。這很可能是因?yàn)镸etalhead.jsl仍在添加預(yù)訓(xùn)練的權(quán)重。

我終于在HuggingFace上找到了包含權(quán)重的.tar.gz文件:

https://huggingface.co/FluxML/resnet18

我們可以使用以下代碼加載權(quán)重,并創(chuàng)建我們自己的自定義Flux模型:

using Flux

using Metalhead

using Pipe

using BSON

# load in saved params from bson

resnet = ResNet(18)

@pipe joinpath(@__DIR__, "resnet18.bson") |> BSON.load(_)[:model] |> Flux.loadmodel!(resnet, _)

# last element of resnet18 is a chain

# since we're removing the last element, we just want to recreate it, but with different number of classes

# probably a more elegant, less hard-coded way to do this, but whatever

baseline_model = Chain(

    resnet.layers[1:end-1],

    Chain(

        AdaptiveMeanPool((1, 1)),

        Flux.flatten,

        Dense(512 => N_CLASSES)

    )

)

(注意:如果有比這更優(yōu)雅的方法來(lái)更改ResNet的最后一層,請(qǐng)告訴我。)

創(chuàng)建了預(yù)訓(xùn)練的遷移學(xué)習(xí)模型后,這只剩下兩個(gè)孿生網(wǎng)絡(luò)模型。然而,與遷移學(xué)習(xí)不同,我們必須學(xué)習(xí)如何手動(dòng)創(chuàng)建模型。(如果你習(xí)慣了PyTorch,這就是Flux與PyTorch的不同之處。)

使用Flux文檔和其他在線資源創(chuàng)建CNN相對(duì)容易。然而,F(xiàn)lux沒(méi)有內(nèi)置層來(lái)表示具有參數(shù)共享的Twin網(wǎng)絡(luò)。它最接近的是平行層,它不使用參數(shù)共享。

然而,F(xiàn)lux在這里有關(guān)于如何創(chuàng)建自定義多個(gè)輸入或輸出層的文檔。在我們的例子中,我們可以用來(lái)創(chuàng)建自定義Twin層的代碼如下:

using Flux

"Custom Flux NN layer which will create twin network from `path` with shared parameters and combine their output with `combine`."

struct Twin{T,F}

    combine::F

    path::T

end

# define the forward pass of the Twin layer

# feeds both inputs, X, through the same path (i.e., shared parameters)

# and combines their outputs

Flux.@functor Twin

(m::Twin)(Xs::Tuple) = m.combine(map(X -> m.path(X), Xs)...)

首先請(qǐng)注意,它以一個(gè)簡(jiǎn)單的結(jié)構(gòu)Twin開頭,包含兩個(gè)字段:combine和path。path是我們的兩個(gè)圖像輸入將經(jīng)過(guò)的網(wǎng)絡(luò),而combine是在最后將輸出組合在一起的函數(shù)。

使用Flux.@functor告訴Flux將我們的結(jié)構(gòu)像一個(gè)常規(guī)的Flux層一樣對(duì)待。(m::Twin)(Xs::Tuple) = m.combine(map(X -> m.path(X), Xs)…)定義了前向傳遞,其中元組Xs中的所有輸入X都通過(guò)path饋送,然后所有輸出都通過(guò)combine。

要使用自定義CNN架構(gòu)創(chuàng)建Twin網(wǎng)絡(luò),我們可以執(zhí)行以下操作:

using Flux

twin_model = Twin(

    # this layer combines the outputs of the twin CNNs

    Flux.Bilinear((32,32) => 1),

    

    # this is the architecture that forms the path of the twin network

    Chain(

        # layer 1

        Conv((5,5), 3 => 18, relu),

        MaxPool((3,3), stride=3),

        

        # layer 2

        Conv((5,5), 18 => 36, relu),

        MaxPool((2,2), stride=2),

        

        # layer 3

        Conv((3,3), 36 => 72, relu),

        MaxPool((2,2), stride=2),

        Flux.flatten,

        

        # layer 4

        Dense(19 * 19 * 72 => 64, relu),

        

        # Dropout(0.1),

        # output layer

        Dense(64 => 32, relu)

    )

)

在本例中,我們實(shí)際上使用Flux.Biliner層作為組合,這實(shí)質(zhì)上創(chuàng)建了一個(gè)連接到兩個(gè)獨(dú)立輸入的輸出層。上面,兩個(gè)輸入是路徑的輸出,即自定義CNN架構(gòu);蛘撸覀兛梢砸阅撤N方式使用hcat或vcat作為組合,然后在最后添加一個(gè)Dense層,但這個(gè)解決方案似乎更適合這個(gè)問(wèn)題。

現(xiàn)在,要使用ResNet創(chuàng)建Twin網(wǎng)絡(luò),我們可以執(zhí)行以下操作:

using Flux

using Metalhead

using Pipe

using BSON

# load in saved params from bson

resnet = ResNet(18)

@pipe joinpath(@__DIR__, "resnet18.bson") |> BSON.load(_)[:model] |> Flux.loadmodel!(resnet, _)

# create twin resnet model

twin_resnet = Twin(

    Flux.Bilinear((32,32) => 1),

    Chain(

        resnet.layers[1:end-1],

        Chain(

            AdaptiveMeanPool((1, 1)),

            Flux.flatten,

            Dense(512 => 32)

        )

    )

)

請(qǐng)注意,我們?nèi)绾问褂门c之前相同的技巧,并使用Flux.雙線性層作為組合,并使用與之前類似的技巧來(lái)使用預(yù)訓(xùn)練的ResNet作為路徑。

訓(xùn)練時(shí)間

現(xiàn)在我們的數(shù)據(jù)加載器和模型準(zhǔn)備就緒,剩下的就是訓(xùn)練了。通常,在Flux中,可以使用一個(gè)簡(jiǎn)單的一行代碼,@epochs 2 Flux.train!(loss, ps, dataset, opt),但我們確實(shí)有一些定制的事情要做。

首先,非孿生網(wǎng)絡(luò)的訓(xùn)練循環(huán):

using Flux

using Flux: Losses.logitbinarycrossentropy

using CUDA

using ProgressLogging

using Pipe

using BSON

"Stores the history through all the epochs of key training/validation performance metrics."

mutable struct TrainingMetrics

    val_acc::Vector{AbstractFloat}

    val_loss::Vector{AbstractFloat}

    TrainingMetrics(n_epochs::Integer) = new(zeros(n_epochs), zeros(n_epochs))

end

"Trains given model for a given number of epochs and saves the model that performs best on the validation set."

function train!(model, n_epochs::Integer, filename::String)

    model = model |> gpu

    optimizer = ADAM()

    params = Flux.params(model[end]) # transfer learning, so only training last layers

    metrics = TrainingMetrics(n_epochs)

    # zero init performance measures for epoch

    epoch_acc = 0.0

    epoch_loss = 0.0

    # so we can automatically save the model with best val accuracy

    best_acc = 0.0

    # X and y are already in the right shape and on the gpu

    # if they weren't, Zygote.jl would throw a fit because it needs to be able to differentiate this function

    loss(X, y) = logitbinarycrossentropy(model(X), y)

    @info "Beginning training loop..."

    for epoch_idx ∈ 1:n_epochs

        @info "Training epoch $(epoch_idx)..."

        # train 1 epoch, record performance

        @withprogress for (batch_idx, (imgs, labels)) ∈ enumerate(train_loader)

            X = @pipe imgs |> gpu |> float32.(_)

            y = @pipe labels |> gpu |> float32.(_)

            gradients = gradient(() -> loss(X, y), params)

            Flux.Optimise.update!(optimizer, params, gradients)

            @logprogress batch_idx / length(enumerate(train_loader))

        end

        # reset variables

        epoch_acc = 0.0

        epoch_loss = 0.0

        @info "Validating epoch $(epoch_idx)..."

        # val 1 epoch, record performance

        @withprogress for (batch_idx, (imgs, labels)) ∈ enumerate(val_loader)

            X = @pipe imgs |> gpu |> float32.(_)

            y = @pipe labels |> gpu |> float32.(_)

            # feed through the model to create prediction

            y? = model(X)

            # calculate the loss and accuracy for this batch, add to accumulator for epoch results

            batch_acc = @pipe ((((σ.(y?) .> 0.5) .* 1.0) .== y) .* 1.0) |> cpu |> reduce(+, _)

            epoch_acc += batch_acc

            batch_loss = logitbinarycrossentropy(y?, y)

            epoch_loss += (batch_loss |> cpu)

            @logprogress batch_idx / length(enumerate(val_loader))

        end

        # add acc and loss to lists

        metrics.val_acc[epoch_idx] = epoch_acc / length(val_set)

        metrics.val_loss[epoch_idx] = epoch_loss / length(val_set)

        # automatically save the model every time it improves in val accuracy

        if metrics.val_acc[epoch_idx] >= best_acc

            @info "New best accuracy: $(metrics.val_acc[epoch_idx])! Saving model out to $(filename).bson"

            BSON.@save joinpath(@__DIR__, "$(filename).bson")

            best_acc = metrics.val_acc[epoch_idx]

        end

    end

    return model, metrics

end

這里有很多要解開的東西,但本質(zhì)上這做了一些事情:

它創(chuàng)建了一個(gè)結(jié)構(gòu),用于跟蹤我們想要的任何驗(yàn)證度量。在這種情況下是每個(gè)epoch的損失和精度。

它只選擇要訓(xùn)練的最后一層參數(shù)。如果我們?cè)敢猓覀兛梢杂?xùn)練整個(gè)模型,但這在計(jì)算上會(huì)更費(fèi)力。這是不必要的,因?yàn)槲覀兪褂玫氖穷A(yù)訓(xùn)練的權(quán)重。

對(duì)于每個(gè)epoch,它都會(huì)遍歷要訓(xùn)練的訓(xùn)練集的所有批次。然后,它計(jì)算整個(gè)驗(yàn)證集(當(dāng)然是成批的)的準(zhǔn)確性和損失。如果提高了epoch的驗(yàn)證精度,則可以保存模型。如果沒(méi)有,它將繼續(xù)到下一個(gè)時(shí)代。

請(qǐng)注意,我們可以在這里做更多的工作,例如,提前停止,但以上內(nèi)容足以了解大致情況。

接下來(lái),Twin網(wǎng)絡(luò)的訓(xùn)練循環(huán)非常相似,但略有不同:

using Flux

using Flux: Losses.logitbinarycrossentropy

using CUDA

using ProgressLogging

using Pipe

using BSON

"Trains given twin model for a given number of epochs and saves the model that performs best on the validation set."

function train!(model::Twin, n_epochs::Integer, filename::String; is_resnet::Bool=false)

    model = model |> gpu

    optimizer = ADAM()

    params = is_resnet ? Flux.params(model.path[end:end], model.combine) : Flux.params(model) # if custom CNN, need to train all params

    metrics = TrainingMetrics(n_epochs)

    # zero init performance measures for epoch

    epoch_acc = 0.0

    epoch_loss = 0.0

    # so we can automatically save the model with best val accuracy

    best_acc = 0.0

    # X and y are already in the right shape and on the gpu

    # if they weren't, Zygote.jl would throw a fit because it needs to be able to differentiate this function

    loss(Xs, y) = logitbinarycrossentropy(model(Xs), y)

    @info "Beginning training loop..."

    for epoch_idx ∈ 1:n_epochs

        @info "Training epoch $(epoch_idx)..."

        # train 1 epoch, record performance

        @withprogress for (batch_idx, ((imgs?, labels?), (imgs?, labels?))) ∈ enumerate(zip(train_loader?, train_loader?))

            X? = @pipe imgs? |> gpu |> float32.(_)

            y? = @pipe labels? |> gpu |> float32.(_)

            X? = @pipe imgs? |> gpu |> float32.(_)

            y? = @pipe labels? |> gpu |> float32.(_)

            Xs = (X?, X?)

            y = ((y? .== y?) .* 1.0) # y represents if both images have the same label

            gradients = gradient(() -> loss(Xs, y), params)

            Flux.Optimise.update!(optimizer, params, gradients)

            @logprogress batch_idx / length(enumerate(train_loader?))

        end

        # reset variables

        epoch_acc = 0.0

        epoch_loss = 0.0

        @info "Validating epoch $(epoch_idx)..."

        # val 1 epoch, record performance

        @withprogress for (batch_idx, ((imgs?, labels?), (imgs?, labels?))) ∈ enumerate(zip(val_loader?, val_loader?))

            X? = @pipe imgs? |> gpu |> float32.(_)

            y? = @pipe labels? |> gpu |> float32.(_)

            X? = @pipe imgs? |> gpu |> float32.(_)

            y? = @pipe labels? |> gpu |> float32.(_)

            Xs = (X?, X?)

            y = ((y? .== y?) .* 1.0) # y represents if both images have the same label

            # feed through the model to create prediction

            y? = model(Xs)

            # calculate the loss and accuracy for this batch, add to accumulator for epoch results

            batch_acc = @pipe ((((σ.(y?) .> 0.5) .* 1.0) .== y) .* 1.0) |> cpu |> reduce(+, _)

            epoch_acc += batch_acc

            batch_loss = logitbinarycrossentropy(y?, y)

            epoch_loss += (batch_loss |> cpu)

            @logprogress batch_idx / length(enumerate(val_loader))

        end

        # add acc and loss to lists

        metrics.val_acc[epoch_idx] = epoch_acc / length(val_set)

        metrics.val_loss[epoch_idx] = epoch_loss / length(val_set)

        # automatically save the model every time it improves in val accuracy

        if metrics.val_acc[epoch_idx] >= best_acc

            @info "New best accuracy: $(metrics.val_acc[epoch_idx])! Saving model out to $(filename).bson"

            BSON.@save joinpath(@__DIR__, "$(filename).bson")

            best_acc = metrics.val_acc[epoch_idx]

        end

    end

    return model, metrics

end

首先注意,我們使用了一個(gè)同名函數(shù)train!,但具有稍微不同的函數(shù)簽名。這允許Julia根據(jù)我們正在訓(xùn)練的網(wǎng)絡(luò)類型來(lái)分配正確的功能。

還要注意,Twin ResNet模型凍結(jié)其預(yù)訓(xùn)練的參數(shù),而我們訓(xùn)練所有Twin自定義CNN參數(shù)。

除此之外,訓(xùn)練循環(huán)的其余部分基本相同,只是我們必須使用兩個(gè)訓(xùn)練數(shù)據(jù)加載器和兩個(gè)驗(yàn)證數(shù)據(jù)加載器。這些為我們提供了兩個(gè)輸入和每批兩組標(biāo)簽,我們將其適當(dāng)?shù)剌斎氲絋win模型中。

最后,請(qǐng)注意,Twin模型預(yù)測(cè)兩個(gè)輸入圖像是否具有相同的標(biāo)簽,而常規(guī)非Twin網(wǎng)絡(luò)僅直接預(yù)測(cè)標(biāo)簽。

這樣,為所有三個(gè)模型的測(cè)試集構(gòu)建測(cè)試循環(huán)應(yīng)該不會(huì)太難。因?yàn)檫@篇文章的目的是要解決我在網(wǎng)上找不到例子的主要痛點(diǎn),所以我將把測(cè)試部分作為練習(xí)留給讀者。

最后

最大的挑戰(zhàn)是縮小從相對(duì)簡(jiǎn)單的示例到更先進(jìn)的技術(shù)之間的差距,而這些技術(shù)缺乏示例。但這也揭示了Julia的優(yōu)勢(shì):因?yàn)樗旧砭秃芸,所以搜索包的源代碼以找到答案通常非常容易。

有幾次,我發(fā)現(xiàn)自己在瀏覽Flux源代碼,以了解一些東西是如何工作的。每一次我都能非常輕松快速地找到答案。我不確定我是否有勇氣為PyTorch嘗試類似的東西。

另一個(gè)挑戰(zhàn)是Metalhead.jsl的不成熟狀態(tài),這在Julia生態(tài)系統(tǒng)中肯定不是獨(dú)一無(wú)二的,因?yàn)樗墓δ懿煌暾?/p>

最后一個(gè)想法是,我發(fā)現(xiàn)Flux非常有趣和優(yōu)雅……一旦我掌握了它的竅門。我肯定會(huì)在未來(lái)與Flux一起進(jìn)行更深入的學(xué)習(xí)。

感謝閱讀!

參考引用

[1] M. Innes, Flux: Elegant Machine Learning with Julia (2018), Journal of Open Source Software

[2] Arun Pandian J. and G. Gopal, Data for: Identification of Plant Leaf Diseases Using a 9-layer Deep Convolutional Neural Network (2019), Mendeley Data

[3] S. S. Chouhan, A. Kaul, and U. P. Singh, A Database of Leaf Images: Practice towards Plant Conservation with Plant Pathology (2019), Mendely Data

[4] V. P. Kour and S. Arora, PlantaeK: A leaf database of native plants of Jammu and Kashmir (2019), Mendeley Data

       原文標(biāo)題 : 使用Flux.jl進(jìn)行圖像分類

聲明: 本文由入駐維科號(hào)的作者撰寫,觀點(diǎn)僅代表作者本人,不代表OFweek立場(chǎng)。如有侵權(quán)或其他問(wèn)題,請(qǐng)聯(lián)系舉報(bào)。

發(fā)表評(píng)論

0條評(píng)論,0人參與

請(qǐng)輸入評(píng)論內(nèi)容...

請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字

您提交的評(píng)論過(guò)于頻繁,請(qǐng)輸入驗(yàn)證碼繼續(xù)

  • 看不清,點(diǎn)擊換一張  刷新

暫無(wú)評(píng)論

暫無(wú)評(píng)論

人工智能 獵頭職位 更多
掃碼關(guān)注公眾號(hào)
OFweek人工智能網(wǎng)
獲取更多精彩內(nèi)容
文章糾錯(cuò)
x
*文字標(biāo)題:
*糾錯(cuò)內(nèi)容:
聯(lián)系郵箱:
*驗(yàn) 證 碼:

粵公網(wǎng)安備 44030502002758號(hào)