フェデレーテッド ラーニングに基づく MNIST 手書き数字認識 - Pytorch 実装

参考: MNIST フェデレーテッド ラーニングに基づく手書き数字認識 - Pytorch の実装 - グレー レター ネットワーク (ソフトウェア開発ブログの集合体) (freesion.com)


1. MNIST データセット

多数の参考文献: MNIST データ セット_keep sane 802 のブログ-CSDN blog_mnist データ セット

1. データのロード

train_set = torchvision.datasets.MNIST(
    root="./data",
    train=True,
    transform=transforms.ToTensor(),
    download=False)
  • transform=transforms.ToTensor() は、形状 (H, W, C) の np.ndarray または img を (C, H, W) のテンソルに変換し、各値を [0, 1] に正規化するために使用されます。
train_data_loader = torch.utils.data.DataLoader(
    dataset=train_set,
    batch_size=64,
    shuffle=True,
    drop_last=True)
  • dataset: ロードする MNIST データセットを指定します。
  • batch_size: 各バッチにロードされるデータ画像の数を 64 に設定します。
  • shuffle: True に設定すると、読み込み時にデータをランダムにシャッフルします。これは、マルチバッチ モデルのトレーニングによく使用されます。
  • drop_last: True に設定すると、データ セットのサイズをbatch_sizeで割り切れない場合は最後のbatch_sizeが削除され、それ以外の場合は削除されません。

2. データのプレビュー

images, labels = next(iter(train_data_loader))
img = torchvision.utils.make_grid(images, padding=0)
img = img.numpy().transpose(1, 2, 0)
plt.imshow(img)
plt.show()
  • iter(): dataloader は本質的に iter() を使用してアクセスできる反復可能なオブジェクトです。iter(dataloader) はイテレータを返し、そのイテレータには next() を使用してアクセスできます。
  • next(): イテレータの次の項目を返します。
  • make_grid(): 画像を構成するネットワークは、実際には複数の画像を 1 つの画像に結合します。
  • img.numpy().transpose(1,2,0): pytorch のネットワーク入力形式は (チャンネル数, 高さ, 幅) ですが、numpy の画像形状は (高さ, 幅, チャンネル数)です。

効果は次のとおりです。


2. 導入プロセス

1. データのロード

import torch
import torchvision
import torchvision.transforms as transforms
import torch.utils.data.dataloader as dataloader
from torch.utils.data import Subset
import torch.nn as nn
import torch.optim as optim
from torch.nn.parameter import Parameter

        ライブラリをインポートした後、Subset 機能を使用して学習データセットを分割します (機関は ABC の合計 3 つあり、各機関の学習セット数は 1000)。次に、トレーニング データ セットをデータローダーに配置します。

        ここではトレーニングセット全体をbatch_sizeとして使用しているため、シャッフルする必要はありません。

# 训练集
train_set = torchvision.datasets.MNIST(
    root="./data",
    train=True,
    transform=transforms.ToTensor(),
    download=False)
train_set_A = Subset(train_set, range(0, 1000))
train_set_B = Subset(train_set, range(1000, 2000))
train_set_C = Subset(train_set, range(2000, 3000))
train_loader_A = dataloader.DataLoader(dataset=train_set_A, batch_size=1000, shuffle=False)
train_loader_B = dataloader.DataLoader(dataset=train_set_B, batch_size=1000, shuffle=False)
train_loader_C = dataloader.DataLoader(dataset=train_set_C, batch_size=1000, shuffle=False)
# 测试集
test_set = torchvision.datasets.MNIST(
    root="./data", 
    train=False, 
    transform=transforms.ToTensor(), 
    download=False)
transform=transforms.ToTensor(), download=False)
test_set = Subset(test_set, range(0, 2000))
test_loader = dataloader.DataLoader(dataset=test_set, shuffle=True)

2. 一般研修

        まずニューラル ネットワークの種類を定義します。ここでは最も単純な 3 層ニューラル ネットワークが使用されます (入力層を除いて 2 層とも言えます)。入力層は 28×28、隠れ層には 12 個のニューロンがあります。 、出力層には 10 個のニューロンがあります。

class NeuralNet(nn.Module):
    def __init__(self, input_num, hidden_num, output_num):
        super(NeuralNet, self).__init__()
        self.fc1 = nn.Linear(input_num, hidden_num)  # 服从正态分布的权重w
        self.fc2 = nn.Linear(hidden_num, output_num)
        nn.init.normal_(self.fc1.weight)
        nn.init.normal_(self.fc2.weight)
        nn.init.constant_(self.fc1.bias, val=0)  # 初始化bias为0
        nn.init.constant_(self.fc2.bias, val=0)
        self.relu = nn.ReLU()  # Relu激励函数
    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        y = self.fc2(x)
        return y
  • class NeuralNet(nn.Module): モデルをカスタマイズし、 nn.Module クラスを継承して実装し、__init__ コンストラクターで各層の定義を宣言し実際の普及の前方処理である forward で層間の接続関係を実装します。
  • def __init__(self, input_num, hidden_​​num, Output_num): init メソッドを継承します。パラメータには self が必要で、3 つのパラメータが必要です。
  • super(NeuralNet, self).__init__(): コンストラクターには次のものが必要です。
  • nn.Linear(): 線形レイヤー
  • nn.init.normal_(self.fc1.weight): 線形層の重みを正規分布データで埋めることを意味します。デフォルトの平均は 0、標準偏差は 1 です。
  • nn.init.constant_(self.fc1.bias, val=0): self.fc1.bias を 0 で埋めます。これは線形層の b を意味します。
  • forward(self, x): 完全な計算を行うフィードフォワード関数
def train_and_test_1(train_loader, test_loader):
    class NeuralNet(nn.Module):...

    epoches = 20  # 迭代20轮
    lr = 0.01  # 学习率,即步长
    input_num = 784
    hidden_num = 12
    output_num = 10
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    model = NeuralNet(input_num, hidden_num, output_num)
    model.to(device)
    loss_func = nn.CrossEntropyLoss()  # 损失函数的类型:交叉熵损失函数
    optimizer = optim.Adam(model.parameters(), lr=lr)  # Adam优化,也可以用SGD随机梯度下降法
    # optimizer = optim.SGD(model.parameters(), lr=lr)
    for epoch in range(epoches):
        flag = 0
        for images, labels in train_loader:
            images = images.reshape(-1, 28 * 28).to(device)
            labels = labels.to(device)
            output = model(images)

            loss = loss_func(output, labels)
            optimizer.zero_grad()
            loss.backward()  # 误差反向传播,计算参数更新值
            optimizer.step()  # 将参数更新值施加到net的parameters上

            # 以下两步可以看每轮损失函数具体的变化情况
            # if (flag + 1) % 10 == 0:
            # print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch + 1, epoches, loss.item()))
            flag += 1

    params = list(model.named_parameters())  # 获取模型参数

    # 测试,评估准确率
    correct = 0
    total = 0
    for images, labels in test_loader:
        images = images.reshape(-1, 28 * 28).to(device)
        labels = labels.to(device)
        output = model(images)
        values, predicte = torch.max(output, 1)  # 0是每列的最大值,1是每行的最大值
        total += labels.size(0)
        # predicte == labels 返回每张图片的布尔类型
        correct += (predicte == labels).sum().item()
    print("The accuracy of total {} images: {}%".format(total, 100 * correct / total))
    return params
  • エポック: 反復回数
  • lr: 学習率、つまりステップサイズ
  • input_num、hidden_​​num、output_num: ニューラル ネットワークの各層のユニットの数
  • torch.device(): 計算のために指定されたコンピューティング デバイスにテンソルをデプロイします。ここに GPU があります。
  • model.to(device): デバイスを指定するときは、モデルを対応するデバイスにロードする必要があります。
  • loss_func: ここでは、クロスエントロピー損失関数を指定します。損失を計算するには、出力とラベルを渡します。損失を表示するには、loss.item() を呼び出す必要があります。
  • optimizer: Adam 最適化が指定されています
  • ループ反復最適化の場合: フォー キング コングを思い出し、損失関数を計算 → 勾配をクリア → バックプロパゲーション → 更新

上記は、単純なニューラル ネットワークを更新し、その後にテストを行うプロセスです。

  • params = list(model.named_pa​​rameters()): 複数のリストの形式で表示されるモデル パラメーターを取得します。
  • for ループは、モデルの精度率を計算します。各バッチの出力は、softmax によって計算され (ここでの操作は、max 関数を使用して予測値を返すことです)、同じラベル比較値 (1 ~ 0エラー)。

最後に、関数全体は、この時点でフェデレーション集計に使用されるモデルのパラメーター params を返します。

3. 連盟後のトレーニング

        1 つ目は、ニューラル ネットワークを定義することです。

class NeuralNet(nn.Module):
    def __init__(self, input_num, hidden_num, output_num, com_para_fc1, com_para_fc2):
        super(NeuralNet, self).__init__()
        self.fc1 = nn.Linear(input_num, hidden_num)
        self.fc2 = nn.Linear(hidden_num, output_num)
        self.fc1.weight = Parameter(com_para_fc1)
        self.fc2.weight = Parameter(com_para_fc2)
        nn.init.constant_(self.fc1.bias, val=0)
        nn.init.constant_(self.fc2.bias, val=0)
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        y = self.fc2(x)
        return y

        以前のネットワークと比較すると、次のような違いがあります。

  • 入力パラメーターによって初期化される 2 つの線形レイヤーの重み
def train_and_test_2(train_loader, test_loader, com_para_fc1, com_para_fc2):
    class NeuralNet(nn.Module):...

    epoches = 20
    lr = 0.01
    input_num = 784
    hidden_num = 12
    output_num = 10
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    model = NeuralNet(input_num, hidden_num, output_num, com_para_fc1, com_para_fc2)
    model.to(device)
    loss_func = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=lr)
    # optimizer = optim.SGD(model.parameters(), lr=lr)

    for epoch in range(epoches):
        flag = 0
        for images, labels in train_loader:
            # (images, labels) = data
            images = images.reshape(-1, 28 * 28).to(device)
            labels = labels.to(device)
            output = model(images)

            loss = loss_func(output, labels)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            # if (flag + 1) % 10 == 0:
            # print('Epoch [{}/{}], Loss: {:.4f}'.format(epoch + 1, epoches, loss.item()))
            flag += 1
    params = list(model.named_parameters())  # get the index by debuging

    correct = 0
    total = 0
    for images, labels in test_loader:
        images = images.reshape(-1, 28 * 28).to(device)
        labels = labels.to(device)
        output = model(images)
        values, predicte = torch.max(output, 1)
        total += labels.size(0)
        correct += (predicte == labels).sum().item()
    print("The accuracy of total {} images: {}%".format(total, 100 * correct / total))
    return params

        ネットワーク以外は通常のトレーニングと同じです。

4.連邦平均

        ここでは w のみを平均します。

def combine_params(para_A, para_B, para_C, para_D, para_E):
    fc1_wA = para_A[0][1].data
    fc1_wB = para_A[0][1].data
    fc1_wC = para_A[0][1].data

    fc2_wA = para_A[2][1].data
    fc2_wB = para_A[2][1].data
    fc2_wC = para_A[2][1].data

    com_para_fc1 = (fc1_wA + fc1_wB + fc1_wC) / 3
    com_para_fc2 = (fc2_wA + fc2_wB + fc2_wC) / 3
    return com_para_fc1, com_para_fc2
  • para_A[0][1] および para_A[2][1]: 前述したように、抽出するネットワーク パラメーターはリストであり、タプルのタプルで構成されます。各タプルは重み (str) と重みデータの名前です。 (パラメータ)。最初の線形層の w と b、2 番目の線形層の w と b を含みます。
  • para_A[0][1].data: 直接操作できるようにするには、パラメータ タイプからデータ メソッドを呼び出してテンソル データに変換する必要があります。

        ここでの平均は、3 つのモデルの 2 つの線形層の w 部分の平均にすぎないことがわかります。最後に、2 つの線形層の平均係数が返されます。

5.主な機能

if __name__ == '__main__':
    print('\033[31m'+'Start training model ABC at 1st time...'+'\033[0m')
    para_A = train_and_test_1(train_loader_A, test_loader)
    para_B = train_and_test_1(train_loader_B, test_loader)
    para_C = train_and_test_1(train_loader_C, test_loader)
    for i in range(6):
        print('\033[31m'+'The {} round to be federated!!!'.format(i + 1)+'\033[0m')
        com_para_fc1, com_para_fc2 = combine_params(para_A, para_B, para_C)
        para_A = train_and_test_2(train_loader_A, test_loader, com_para_fc1, com_para_fc2)
        para_B = train_and_test_2(train_loader_B, test_loader, com_para_fc1, com_para_fc2)
        para_C = train_and_test_2(train_loader_C, test_loader, com_para_fc1, com_para_fc2)
  • 印刷時に「\033[31m」を追加すると、後続の文字が赤色に変わります

        ここでは、最初に初期瞬間がシミュレートされ、各マシンが独自のモデルをトレーニングし、次にループ内ですべてのモデルが集約され、集約されたモデルが受信され、ローカル データがトレーニングに使用され続けます。


3. 研修結果

 (トレーニングの最初のラウンド、フェデレーテッド最適化なし)

  (トレーニングの 6 ラウンド目、フェデレーテッド最適化)

おすすめ

転載: blog.csdn.net/m0_51562349/article/details/127392119