您的位置:首頁 > 軟件教程 > 教程 > 機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(下)

機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(下)

來源:好特整理 | 時(shí)間:2024-12-04 10:06:51 | 閱讀:179 |  標(biāo)簽: 網(wǎng)絡(luò) 機(jī)器   | 分享到:

在上一篇文章《機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(上)》中討論了線性層、激活函數(shù)以及損失函數(shù)層的構(gòu)建方式,本節(jié)中將進(jìn)一步討論網(wǎng)絡(luò)構(gòu)建方式,并完整的搭建一個(gè)簡(jiǎn)單的分類器網(wǎng)絡(luò)。

簡(jiǎn)介

在上一篇文章《 機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(上) 》中討論了線性層、激活函數(shù)以及損失函數(shù)層的構(gòu)建方式,本節(jié)中將進(jìn)一步討論網(wǎng)絡(luò)構(gòu)建方式,并完整的搭建一個(gè)簡(jiǎn)單的分類器網(wǎng)絡(luò)。

目錄

  1. 網(wǎng)絡(luò)Network
  2. 數(shù)據(jù)集管理器 DatasetManager
  3. 優(yōu)化器 Optimizer
  4. 代碼測(cè)試

網(wǎng)絡(luò)Network

網(wǎng)絡(luò)定義


在設(shè)計(jì)神經(jīng)網(wǎng)絡(luò)時(shí),其基本結(jié)構(gòu)是由一層層的神經(jīng)元組成的,這些層可以是輸入層、隱藏層和輸出層。為了實(shí)現(xiàn)這一結(jié)構(gòu),通常會(huì)使用向量(vector)容器來存儲(chǔ)這些層,因?yàn)閷拥臄?shù)量是可變的,可能根據(jù)具體任務(wù)的需求而變化。

即使在網(wǎng)絡(luò)已經(jīng)進(jìn)行了預(yù)訓(xùn)練并具有一定的參數(shù)的情況下,對(duì)于特定的任務(wù),通常還是需要進(jìn)行模型微調(diào)。這是因?yàn)椴煌娜蝿?wù)可能有不同的數(shù)據(jù)分布和要求,因此訓(xùn)練是構(gòu)建高性能神經(jīng)網(wǎng)絡(luò)模型的重要步驟。

在訓(xùn)練過程中,有三個(gè)關(guān)鍵組件:

  1. 損失函數(shù) :神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)目標(biāo),通過最小化損失函數(shù)來優(yōu)化模型參數(shù)。選擇合適的損失函數(shù)對(duì)于確保模型能夠?qū)W習(xí)到有效的特征表示至關(guān)重要。

  2. 優(yōu)化器 :優(yōu)化器負(fù)責(zé)調(diào)整模型的參數(shù)以最小化損失函數(shù)。除了基本的參數(shù)更新功能外,優(yōu)化器還可以提供更高級(jí)的功能,如學(xué)習(xí)率調(diào)整和參數(shù)凍結(jié),這些功能有助于提高訓(xùn)練效率和模型性能。

  3. 數(shù)據(jù)集管理器 :負(fù)責(zé)在訓(xùn)練過程中有效地管理和提供數(shù)據(jù),包括數(shù)據(jù)的加載、預(yù)處理和批處理,以確保數(shù)據(jù)被充分利用。

對(duì)于網(wǎng)絡(luò)的外部接口(公有方法),主要有以下幾類:

  1. 網(wǎng)絡(luò)設(shè)置 :添加網(wǎng)絡(luò)層、設(shè)置損失函數(shù)、優(yōu)化器和數(shù)據(jù)集等操作,用于配置網(wǎng)絡(luò)的結(jié)構(gòu)和訓(xùn)練參數(shù)。
  2. 網(wǎng)絡(luò)推理 :前向傳播和反向傳播方法,用于在訓(xùn)練和測(cè)試過程中進(jìn)行預(yù)測(cè)和參數(shù)更新。
  3. 網(wǎng)絡(luò)訓(xùn)練 :使用配置好的數(shù)據(jù)集和訓(xùn)練方法,執(zhí)行指定次數(shù)的訓(xùn)練迭代,以優(yōu)化網(wǎng)絡(luò)參數(shù)。

以下是代碼示例:

class Network {
private:
    vector> layers;

    shared_ptr lossFunction;
    shared_ptr optimizer;
    shared_ptr datasetManager;

public:
    void addLayer(shared_ptr layer);

    void setLossFunction(shared_ptr lossFunc);
    void setOptimizer(shared_ptr opt);
    void setDatasetManager(shared_ptr manager);

    MatrixXd forward(const MatrixXd& input);
    void backward(const MatrixXd& outputGrad);

    double train(size_t epochs, size_t batchSize);
};

使用shared_ptr的好處:
存儲(chǔ)方式vector >和vector 相比,如果直接存儲(chǔ) Layer 對(duì)象,需要手動(dòng)管理內(nèi)存,包括分配和釋放內(nèi)存,這不僅容易出錯(cuò),還可能導(dǎo)致內(nèi)存泄漏或懸掛指針的問題。而使用 std::shared_ptr 可以大大簡(jiǎn)化內(nèi)存管理,提高代碼的健壯性和可維護(hù)性。

網(wǎng)絡(luò)訓(xùn)練


網(wǎng)絡(luò)的訓(xùn)練函數(shù)通常包含兩個(gè)輸入?yún)?shù),訓(xùn)練的集數(shù)和批尺寸:

  • 集數(shù) epochs :指訓(xùn)練集被完整的迭代的次數(shù)。在每一個(gè)epoch中,網(wǎng)絡(luò)會(huì)使用訓(xùn)練集中的所有樣本進(jìn)行參數(shù)更新。

  • 批尺寸 batchSize :指在一次迭代中用于更新模型參數(shù)的樣本數(shù)量。在每次迭代中,模型會(huì)計(jì)算這些樣本的總梯度,并據(jù)此調(diào)整模型的參數(shù)。

因此,網(wǎng)絡(luò)的訓(xùn)練函數(shù)由兩層循環(huán)結(jié)構(gòu)組成,外層循環(huán)結(jié)構(gòu)表示完整迭代的次數(shù),直至完成所有迭代時(shí)停止。內(nèi)層循環(huán)表示訓(xùn)練集中樣本被網(wǎng)絡(luò)調(diào)取的進(jìn)度,直至訓(xùn)練集中的所有數(shù)據(jù)被調(diào)用時(shí)停止。

網(wǎng)絡(luò)的訓(xùn)練過程是由多次的參數(shù)迭代(更新)完成的。而參數(shù)的的迭代是以批(Batch)為單位的。具體來說,一次迭代包含如下步驟:

  1. 獲取數(shù)據(jù) :從數(shù)據(jù)集管理器中獲取一批的數(shù)據(jù)(包含輸入和輸出)
  2. 前向傳播 :采用網(wǎng)絡(luò)對(duì)數(shù)據(jù)進(jìn)行推理,得到預(yù)測(cè)結(jié)果,依據(jù)預(yù)測(cè)結(jié)果評(píng)估損失。
  3. 反向傳播 :計(jì)算損失函數(shù)關(guān)于各層參數(shù)的梯度。
  4. 參數(shù)更新 :依據(jù)損失、梯度等信息,更新各層梯度。
  5. 日志更新 :計(jì)算并輸出每個(gè)epoch的累積誤差。

代碼設(shè)計(jì)如下:

double Network::train(size_t epochs, size_t batchSize) {
    double totalLoss = 0.0;
    size_t sampleCount = datasetManager->getTrainSampleCount();

    for (size_t epoch = 0; epoch < epochs; ++epoch) {
        datasetManager->shuffleTrainSet();
        totalLoss = 0.0;
        for (size_t i = 0; i < sampleCount; i += batchSize) {
            // 獲取一個(gè)小批量樣本
            auto batch = datasetManager->getTrainBatch(batchSize, i / batchSize);
            MatrixXd batchInput = batch.first;
            MatrixXd batchLabel = batch.second;

            // 前向傳播
            MatrixXd predicted = forward(batchInput);
            double loss = lossFunction->computeLoss(predicted, batchLabel);

            // 反向傳播
            MatrixXd outputGrad = lossFunction->computeGradient(predicted, batchLabel);
            backward(outputGrad);

            // 參數(shù)更新
            optimizer->update(layers);

            // 累計(jì)損失
            totalLoss += loss;
        }
        totalLoss /= datasetManager->getTrainSampleCount();
        // 輸出每個(gè)epoch的損失等信息
        std::cout << "Epoch " << epoch << ", totalLoss = " << totalLoss << "\n";
    }
    return totalLoss / (epochs * (sampleCount / batchSize)); // 返回平均損失(簡(jiǎn)化示例)
}

網(wǎng)絡(luò)的其它公有方法


下面的代碼給出了網(wǎng)絡(luò)的其它公有方法的代碼實(shí)現(xiàn):

void Network::addLayer(std::shared_ptr layer) {
    layers.push_back(layer);
}

void Network::setLossFunction(std::shared_ptr lossFunc) {
    lossFunction = lossFunc;
}

void Network::setOptimizer(std::shared_ptr opt) {
    optimizer = opt;
}

void Network::setDatasetManager(std::shared_ptr manager) {
    datasetManager = manager;
}

MatrixXd Network::forward(const MatrixXd& input) {
    MatrixXd currentInput = input;
    for (const auto& layer : layers) {
        currentInput = layer->forward(currentInput);
    }
    return currentInput;
}

void Network::backward(const MatrixXd& outputGrad) {
    MatrixXd currentGrad = outputGrad;
    for (auto it = layers.rbegin(); it != layers.rend(); ++it) {
        currentGrad = (*it)->backward(currentGrad);
    }
}

forward 方法除了作為訓(xùn)練時(shí)的步驟之一,還經(jīng)常用于網(wǎng)絡(luò)推理(預(yù)測(cè)),因此聲明為公有方法

backward 方法只在訓(xùn)練時(shí)使用,在正常的使用用途中,不會(huì)被外部調(diào)用,因此,其可以聲明為私有方法。

數(shù)據(jù)集管理器 DatasetManager


數(shù)據(jù)集管理器本質(zhì)目的是提高網(wǎng)絡(luò)對(duì)數(shù)據(jù)的利用率,其主要職能有:

  1. 保存數(shù)據(jù):提供更為安全可靠的數(shù)據(jù)管理。
  2. 數(shù)據(jù)打亂:以避免順序偏差,同時(shí)提升模型的泛化能力。
  3. 數(shù)據(jù)集劃分:講數(shù)據(jù)劃分為訓(xùn)練集、驗(yàn)證集和測(cè)試集。
  4. 數(shù)據(jù)接口:使得外部可以輕松的獲取批量數(shù)據(jù)。
    class DatasetManager {
    private:
        MatrixXd input;
        MatrixXd label;
        std::vector trainIndices;
        std::vector valIndices;
        std::vector testIndices;

    public:
        // 設(shè)置數(shù)據(jù)集的方法
        void setDataset(const MatrixXd& inputData, const MatrixXd& labelData);

        // 劃分?jǐn)?shù)據(jù)集為訓(xùn)練集、驗(yàn)證集和測(cè)試集
        void splitDataset(double trainRatio = 0.8, double valRatio = 0.1, double testRatio = 0.1);

        // 獲取訓(xùn)練集、驗(yàn)證集和測(cè)試集的小批量數(shù)據(jù)
        std::pair getBatch(std::vector& indices, size_t batchSize, size_t offset = 0);

        // 隨機(jī)打亂訓(xùn)練集
        void shuffleTrainSet();

        // 獲取批量數(shù)據(jù)
        std::pair getTrainBatch(size_t batchSize, size_t offset = 0);
        std::pair getValidationBatch(size_t batchSize, size_t offset = 0);
        std::pair getTestBatch(size_t batchSize, size_t offset = 0);

        // 獲取樣本數(shù)量的方法
        size_t getSampleCount() const;
        size_t getTrainSampleCount() const;
        size_t getValidationSampleCount() const;
        size_t getTestSampleCount() const;
    };

數(shù)據(jù)集初始化


數(shù)據(jù)集初始化分為三步:數(shù)據(jù)集設(shè)置、數(shù)據(jù)集劃分、數(shù)據(jù)集打亂。

// 設(shè)置數(shù)據(jù)集
void  ML::DatasetManager::setDataset(const MatrixXd& inputData, const MatrixXd& labelData) {
    input = inputData;
    label = labelData;

    trainIndices.resize(input.rows());
    std::iota(trainIndices.begin(), trainIndices.end(), 0);
    valIndices.clear();
    testIndices.clear();
}

// 打亂訓(xùn)練集
void ML::DatasetManager::shuffleTrainSet() {
    std::shuffle(trainIndices.begin(), trainIndices.end(), std::mt19937{ std::random_device{}() });
}

// 劃分?jǐn)?shù)據(jù)集為訓(xùn)練集、驗(yàn)證集和測(cè)試集
void ML::DatasetManager::splitDataset(double trainRatio, double valRatio, double testRatio) {
    size_t totalSamples = input.rows();
    size_t trainSize = static_cast(totalSamples * trainRatio);
    size_t valSize = static_cast(totalSamples * valRatio);
    size_t testSize = totalSamples - trainSize - valSize;

    shuffleTrainSet();

    valIndices.assign(trainIndices.begin() + trainSize, trainIndices.begin() + trainSize + valSize);
    testIndices.assign(trainIndices.begin() + trainSize + valSize, trainIndices.end());
    trainIndices.resize(trainSize);
}

對(duì)于打亂操作較頻繁的場(chǎng)景,打亂索引是更為高效的操作;而對(duì)于不經(jīng)常打亂的場(chǎng)景,直接在數(shù)據(jù)集上打亂更為高效。本例中僅給出打亂索引的代碼示例。

數(shù)據(jù)獲取


在獲取數(shù)據(jù)時(shí),首先明確所需數(shù)據(jù)集的類型(訓(xùn)練集或驗(yàn)證集)。然后,根據(jù)預(yù)設(shè)的批次大。˙atchsize),從索引列表中提取相應(yīng)數(shù)量的索引,并將這些索引對(duì)應(yīng)的數(shù)據(jù)存儲(chǔ)到臨時(shí)矩陣中。最后,導(dǎo)出數(shù)據(jù),完成讀取操作。

// 獲取訓(xùn)練集、驗(yàn)證集和測(cè)試集的小批量數(shù)據(jù)
std::pair ML::DatasetManager::getBatch(std::vector& indices, size_t batchSize, size_t offset) {
    size_t start = offset * batchSize;
    size_t end = std::min(start + batchSize, indices.size());
    MatrixXd batchInput = MatrixXd::Zero(end - start, input.cols());
    MatrixXd batchLabel = MatrixXd::Zero(end - start, label.cols());

    for (size_t i = start; i < end; ++i) {
        batchInput.row(i - start) = input.row(indices[i]);
        batchLabel.row(i - start) = label.row(indices[i]);
    }

    return std::make_pair(batchInput, batchLabel);
}

// 獲取訓(xùn)練集的批量數(shù)據(jù)
std::pair ML::DatasetManager::getTrainBatch(size_t batchSize, size_t offset) {
    return getBatch(trainIndices, batchSize, offset);
}

// 獲取驗(yàn)證集的批量數(shù)據(jù)
std::pair ML::DatasetManager::getValidationBatch(size_t batchSize, size_t offset) {
    return getBatch(valIndices, batchSize, offset);
}

// 獲取測(cè)試集的批量數(shù)據(jù)
std::pair ML::DatasetManager::getTestBatch(size_t batchSize, size_t offset) {
    return getBatch(testIndices, batchSize, offset);
}

數(shù)據(jù)集尺寸的外部接口


為便于代碼開發(fā),需要為數(shù)據(jù)集管理器設(shè)計(jì)外部接口,以便于外部可以獲取各個(gè)數(shù)據(jù)集的尺寸。

size_t ML::DatasetManager::getSampleCount() const {
    return input.rows();
}

size_t ML::DatasetManager::getTrainSampleCount() const {
    return trainIndices.size();
}

size_t ML::DatasetManager::getValidationSampleCount() const {
    return valIndices.size();
}

size_t ML::DatasetManager::getTestSampleCount() const {
    return testIndices.size();
}

優(yōu)化器 Optimizer


隨機(jī)梯度下降是一種優(yōu)化算法,用于最小化損失函數(shù)以訓(xùn)練模型參數(shù)。與批量梯度下降(Batch Gradient Descent)不同,SGD在每次更新參數(shù)時(shí)只使用一個(gè)樣本(或一個(gè)小批量的樣本),而不是整個(gè)訓(xùn)練集。這使得SGD在計(jì)算上更高效,且能夠更快地收斂,尤其是在處理大規(guī)模數(shù)據(jù)時(shí)。以下為隨機(jī)梯度下降的代碼示例:

class Optimizer {
public:
    virtual void update(std::vector>& layers) = 0;
    virtual ~Optimizer() {}
};

class SGDOptimizer : public Optimizer {
private:
    double learningRate;
public:
    SGDOptimizer(double learningRate) : learningRate(learningRate) {}
    void update(std::vector>& layers) override;
};

void SGDOptimizer::update(std::vector>& layers) {
    for (auto& layer : layers) {
        layer->update(learningRate);
    }
}

代碼測(cè)試


如果你希望測(cè)試這些代碼,首先可以從本篇文章,以及 上一篇文章 中復(fù)制代碼,并參考下述圖片構(gòu)建你的解決方案。
機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(下)
如果你有遇到問題,歡迎聯(lián)系作者!

示例1:線性回歸


下述代碼為線性回歸的測(cè)試樣例:

namespace LNR{
    // linear_regression
    void gen(MatrixXd& X, MatrixXd& y);
    void test();
}

void LNR::gen(MatrixXd& X, MatrixXd& y) {
    MatrixXd w(X.cols(), 1);

    X.setRandom();
    w.setRandom();

    X.rowwise() -= X.colwise().mean();
    X.array().rowwise() /= X.array().colwise().norm();

    y = X * w;
}

void LNR::test() {
    std::cout << std::fixed << std::setprecision(2);

    size_t input_dim = 10;
    size_t sample_num = 2000;

    MatrixXd X(sample_num, input_dim);
    MatrixXd y(sample_num, 1);

    gen(X, y);

    ML::DatasetManager dataset;
    dataset.setDataset(X, y);

    ML::Network net;

    net.addLayer(std::make_shared(input_dim, 1));

    net.setLossFunction(std::make_shared());
    net.setOptimizer(std::make_shared(0.25));
    net.setDatasetManager(std::make_shared(dataset));

    size_t epochs = 600;
    size_t batch_size = 50;
    net.train(epochs, batch_size);

    MatrixXd error(sample_num, 1);

    error = net.forward(X) - y;

    std::cout << "error=\n" << error << "\n";
}

詳細(xì)解釋

  1. gen 函數(shù):用以生成測(cè)試數(shù)據(jù)。
  2. 網(wǎng)絡(luò)結(jié)構(gòu):本例的網(wǎng)絡(luò)結(jié)構(gòu)中只包含一個(gè)線性層,其中輸入尺寸為特征維度,輸出尺寸為1。
  3. 損失函數(shù):采用MSE均方根誤差作為損失函數(shù)。

輸出展示

完成訓(xùn)練后,網(wǎng)絡(luò)預(yù)測(cè)值與真實(shí)值的誤差如下圖;容易發(fā)現(xiàn),網(wǎng)絡(luò)具有較好的預(yù)測(cè)精度。

機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(下)

示例2:邏輯回歸


下述代碼為邏輯回歸的測(cè)試樣例:

namespace LC {
    // Linear classification
    void gen(MatrixXd& X, MatrixXd& y);
    void test();
}

void LC::gen(MatrixXd& X, MatrixXd& y) {
    MatrixXd w(X.cols(), 1);

    X.setRandom();
    w.setRandom();

    X.rowwise() -= X.colwise().mean();
    X.array().rowwise() /= X.array().colwise().norm();

    y = X * w;

    y = y.unaryExpr([](double x) { return x > 0.0 ? 1.0 : 0.0; });
}

void LC::test() {
    std::cout << std::fixed << std::setprecision(3);

    size_t input_dim = 10;
    size_t sample_num = 2000;

    MatrixXd X(sample_num, input_dim);
    MatrixXd y(sample_num, 1);

    gen(X, y);

    ML::DatasetManager dataset;
    dataset.setDataset(X, y);

    ML::Network net;

    net.addLayer(std::make_shared(input_dim, 1));
    net.addLayer(std::make_shared());

    net.setLossFunction(std::make_shared());
    net.setOptimizer(std::make_shared(0.05));
    net.setDatasetManager(std::make_shared(dataset));

    size_t epochs = 200;
    size_t batch_size = 25;
    net.train(epochs, batch_size);

    MatrixXd predict(sample_num, 1);

    predict = net.forward(X);

    predict = predict.unaryExpr([](double x) { return x > 0.5 ? 1.0 : 0.0; });

    MatrixXd error(sample_num, 1);

    error = y - predict;

    error = error.unaryExpr([](double x) {return (x < 0.01 && x>-0.01) ? 1.0 : 0.0; });

    std::cout << "正確率=\n" << error.sum() / sample_num << "\n";
}

詳細(xì)解釋

  1. gen 函數(shù):用以生成測(cè)試數(shù)據(jù)。
  2. 網(wǎng)絡(luò)結(jié)構(gòu):本例的網(wǎng)絡(luò)結(jié)構(gòu)中包含一個(gè)線性層及一個(gè)激活函數(shù)層,其中:線性層輸入尺寸為特征維度,輸出尺寸為1。
  3. 損失函數(shù):采用對(duì)數(shù)誤差作為損失函數(shù)。

輸出展示
下圖反映了網(wǎng)絡(luò)預(yù)測(cè)過程中的損失變化,可以看到損失逐漸下降的趨勢(shì)。
機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(下)

完成訓(xùn)練后,輸出網(wǎng)絡(luò)的預(yù)測(cè)結(jié)果的正確率?梢园l(fā)現(xiàn),網(wǎng)絡(luò)具有較好的預(yù)測(cè)精度。
機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(下)

小編推薦閱讀

好特網(wǎng)發(fā)布此文僅為傳遞信息,不代表好特網(wǎng)認(rèn)同期限觀點(diǎn)或證實(shí)其描述。

相關(guān)視頻攻略

更多

掃二維碼進(jìn)入好特網(wǎng)手機(jī)版本!

掃二維碼進(jìn)入好特網(wǎng)微信公眾號(hào)!

本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請(qǐng)發(fā)郵件[email protected]

湘ICP備2022002427號(hào)-10 湘公網(wǎng)安備:43070202000427號(hào)© 2013~2025 haote.com 好特網(wǎng)