【1ビットの時代 – MNIST編】BitNet b1.58の基盤技術BitLinear/BitNetで画像認識

【1ビットの時代 - MNIST編】BitNet b1.58の基盤技術BitLinearで手書き数字画像認識

 

【動画で解説】MNIST編 – BitNet:BitLinearで手書き数字画像認識

 

 

【1ビットの時代 – BitNetでMNIST編】
1ビットLLM BitNet b1.58の基盤技術BitLinearで手書き数字画像認識


視聴時間:13分1秒

文字情報だけではわかりにくい場合に、BitNetの解説動画をご活用いただけますと幸いです。

 




 

【動画の内容】

0:00 はじめに
2:01 Google Colaboratoryの使い方
3:02 【ステップ1】事前準備編
4:55 【ステップ2】BitLinearを体験
5:45 【ステップ3】学習編
8:06 【ステップ4】画像認識編
11:56 おわりに

 

以下は、動画公開後に気がついた間違いです。

【訂正事項①】

6分40秒あたり
誤り:「参考までに”CPU”への負荷をチェックしてみましょう」
訂正後:「参考までに”メモリ”への負荷をチェックしてみましょう」

 

【訂正事項②】

6分55秒あたり
誤り:「”CPU”への負荷はシステムRAMのところを見ます」
訂正後:「”メモリ”への負荷はシステムRAMのところを見ます」

ちょっとしたことですが、残念ながら動画作成時には、気がつかないものですね…
基本的に動画では “CPU” と “メモリ” を勘違いして説明していますのでご注意ください。

 

【確認事象:2024年3月15日】

bitnetリポジトリのアップデートに伴い、エラーが出ていましたので、エラーが出ないように修正しておきました。

 

 

MNIST編:1ビット時代の到来

 

 

1ビットLLM(大規模言語モデル)について調べていく中で、手書き画像認識でよく使われるMNISTのデータセットで、BitNet b1.58(ビットネット・ビー・1.58)の基盤技術のBitLinear(ビットリニア)を使って学習させるためのプログラム

AlarioAI/bitnet(The MIT License) | GitHub
*2024年3月11日にリポジトリの名前の変更によりアクセスできない状態でしたのでリンクを更新し、チュートリアルコードも修正しておきました。
注:
チュートリアルコード公開当初に勘違いしていましたが、公開時点ではBitNet b1.58ではなくて、BitLinearを活用したプログラムのようでした。
注:
BitNet/BitNet b1.58のコードは、マイクロソフト公式では公開されていません。論文の情報をもとに、非公式でオープンソースの開発が行われている段階です。
追記:2024年3月13日時点の理解では、BitNetでMNISTの手書き数字画像を学習させているものと理解しました。
追記:2024年3月15日時点の理解では、bitnetのリポジトリの説明にあるように1.58ビットのニューラルネットワーク(いわゆるBitNet b1.58)でMNISTの手書き数字画像を学習させているものと理解しました。

を公開してくださっている方がいるようでしたので、早速試したところ、

Google Colaboratoryで使う際に、現在の初期設定を少し変更する必要がある

ということがわかりました。

注:
MNIST(エムニスト)- Modified National Institute of Standards and Technology databaseの略。
「0〜9」の手書き数字画像のデータセットです。

AI初学者の方の場合、いざ、プログラムを動かそうとした際に、開発者の方の説明にない追加の手順が必要になると、プログラムを動かせなくて悲しい思をされる確率が格段に高くなるのではないかと思います。

今回のチュートリアルコードでは、BitNet(ビットネット)を活用したMNISTのサンプルコードを公開してくださっているプログラムを日本人のAI初学者の方向けに、Google Colaboratoryのノートブックで実行できるようにしておきました。
その他にも、確認時点(2024年3月11日時点)で開発者の方のプログラムには実装されていない機能である

・追加機能①:学習モデルの保存
・追加機能②:学習モデルを活用して自前画像で手書き数字画像認識

などを実装しておきましたので、それなりに、楽しめるチュートリアルコードになったのではないかと思います。

一連の情報が、BitNet b1.58に関心のある日本人のAI初学者の方が、基盤技術であるBitLinearやBitNetのプログラムに触れるきっかけになることがありましたら幸いです。

それでは、「BitLinear」の知見を活用したプログラムをGoogle Colaboratoryで動かしてみましょう。

 

 

チュートリアルコードリンク・プログラムのライセンス

 

 

Google Colaboratoryのチュートリアルコード:
MNIST-BitNet-BitLinear-for-Japanese-AI-Beginners.ipynb(The MIT License)| Google Colaboratory

 

チュートリアルコード「MNIST-BitNet-BitLinear-for-Japanese-AI-Beginners.ipynb」のライセンス:

The MIT License

Copyright 2024 child programmer

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 

 

チュートリアルで使える画像認識用の手書き数字画像をダウンロード

 

 


*ダウンロードされる圧縮ファイルを解凍し、個別の画像ファイルをGoogle Colaboratoryにアップロードして使います。

 

 

【MNIST編】
BitNetで手書き数字画像認識 – 最終更新:2024年3月15日

 

 

BitNetで手書き数字画像の画像認識に挑戦してみましょう!

 

 

【ステップ1】事前準備 編

 

 

【事前準備①】Pythonのバージョンを指定

 

 

開発者の方が指定している「Python3.11系」をインストールし、このノートブックで使えるように有効化します。

実行コード

print('(このコード実行前のPythonのバージョンを確認)')
!python --version
print('\n\n')

# Python3.11系をインストール
!sudo apt install python3.11

# Python3.11.系を有効化
!sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1

# pipをインストール
!sudo apt install python3-pip

# 現在のPythonのバージョンを確認
print('\n\n(現在利用中のPythonのバージョンを確認)')
!python --version

 

出力結果

(このコード実行前のPythonのバージョンを確認)
Python 3.10.12

Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  libpython3.11-minimal libpython3.11-stdlib mailcap mime-support
  python3.11-minimal
Suggested packages:
  python3.11-venv binfmt-support
The following NEW packages will be installed:
  libpython3.11-minimal libpython3.11-stdlib mailcap mime-support python3.11
  python3.11-minimal
0 upgraded, 6 newly installed, 0 to remove and 35 not upgraded.

〜

(現在利用中のPythonのバージョンを確認)
Python 3.11.8

 

 

【事前準備②】bitnetのクローン

 

 

BitLinearを活用したMNISTのサンプルコードを公開してくださっている以下のGitHubリボジトリ

AlarioAI/bitnet(The MIT License)| GitHub

をクローンします。

実行コード

# BitNetのリポジトリをクローン
!git clone https://github.com/AlarioAI/bitnet

# bitnetフォルダをカレントフォルダに指定
%cd bitnet

 

出力結果

Cloning into 'BitNet'...
remote: Enumerating objects: 755, done.
remote: Counting objects: 100% (170/170), done.
remote: Compressing objects: 100% (94/94), done.
remote: Total 755 (delta 111), reused 101 (delta 69), pack-reused 585
Receiving objects: 100% (755/755), 37.39 MiB | 34.90 MiB/s, done.
Resolving deltas: 100% (420/420), done.
/content/bitnet

 

 

【事前準備③】各種インストール

 

 

確認時点では、コードを実行後に以下の表示

WARNING: Running pip as the ‘root’ user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: 〜

が出ましたが、そのままでも使えるようでした。

実行コード

# BitNetを使うのに必要な依存関係をインストール
!python3 -m pip install -e .

# PyTorchのバージョンの確認
print('\n\n(実行時点のバージョンです)') #2024年3月11日実行時点
import torch
print('PyTprch' + torch.__version__)

 

出力結果

Obtaining file:///content/bitnet
  Preparing metadata (setup.py) ... done
Collecting torch
  Downloading torch-2.2.1-cp311-cp311-manylinux1_x86_64.whl (755.6 MB)

〜

(実行時点のバージョンです)
PyTprch2.1.0+cu121

 

 

【ステップ2】BitLinearを体験

 

 

デモコードでBitLinear(BitNetの基盤技術)を体験してみましょう。

 

BitLinearの概念図
BitLinearの概念図・計算処理の流れ - 【1ビットの時代 - MNIST編】BitNet b1.58の基盤技術BitLinearで画像認識
* 画像をクリックすると拡大します。

・レイヤー正規化(Layer Norm:Layer Normalization)
・絶対値の最大値の量子化 – Absmax量子化関数(Absmax Quantization)
・テンソル積(⊗)
・1ビットの重み – 1ビット重み化(1-bit weights)
・逆量子化(Dequantization)

 

 

BitLinearのデモコード

 

 

input(入力)するテンソル(多次元配列)はランダムに生成されます。

実行コード

# 各種インポート
import torch
import torch.nn as nn
from torch import Tensor # 2024年3月13日以降版のコードのための追記
from PIL import Image
import PIL.ImageOps

# BitLinearの定義(2024年3月13日以降のコード)
class BitLinear(nn.Linear):
    def __init__(
        self,
        in_features: int,
        out_features: int,
        bias: bool = True,
        num_bits: int = 8,
    ):
        super().__init__(in_features, out_features, bias)
        self.eps:float = 1e-5
        self.quantization_range: int = 2 ** (num_bits - 1) # Q_b in the paper
        self.norm: nn.Module = nn.LayerNorm(in_features)


    def ste_weights(self, weights_gamma: float) -> Tensor:
        eps: float = 1e-7
        scaled_weights:Tensor = self.weight / (weights_gamma + eps)
        bin_weights_no_grad: Tensor = torch.clamp(torch.round(scaled_weights), min=-1, max=1)
        bin_weights_with_grad: Tensor = (bin_weights_no_grad - self.weight).detach() + self.weight
        return bin_weights_with_grad


    def binarize_weights(self, weights_gamma: float) -> Tensor:
        binarized_weights = self.ste_weights(weights_gamma)
        return binarized_weights


    def quantize_activations(self, _input:Tensor, input_gamma: float) -> Tensor:
        # Equation 4 BitNet paper
        quantized_input = torch.clamp(
                _input * self.quantization_range / input_gamma,
                -self.quantization_range + self.eps,
                self.quantization_range - self.eps,
            )
        return quantized_input


    def dequantize_activations(self, _input: Tensor, input_gamma: float, beta: float) -> Tensor:
        return _input * input_gamma * beta / self.quantization_range


    def forward(self, _input: Tensor) -> Tensor:
        normalized_input: Tensor = self.norm(_input)
        input_gamma: float = normalized_input.abs().max().item()
        weight_abs_mean: float = self.weight.abs().mean().item()

        binarized_weights = self.binarize_weights(weight_abs_mean)
        input_quant = self.quantize_activations(normalized_input, input_gamma)
        output = torch.nn.functional.linear(input_quant, binarized_weights, self.bias)
        output = self.dequantize_activations(output, input_gamma, weight_abs_mean)

        return output

# BitLinearの概念図の出力
input_image = '/content/bitnet/assets/main_image.png'
image = Image.open(input_image)
image = image.resize((700,230))
display(image)

# BitLinearのデモ
_input = torch.randn(10, 512)
print('【input(入力)したテンソル(多次元配列)】')
print(_input)
print('\n          ↓\n')
print('         BitLiner')
print('\n          ↓\n')
layer = BitLinear(512, 400)
output = layer(_input)
print('【output(出力)されたテンソル(多次元配列)】')
print(output)
print('\ninputに比べoutputの数値が変化していますね')

 

出力結果

【input(入力)したテンソル(多次元配列)】
tensor([[ 0.7256, -0.4908,  0.2553,  ..., -0.1919, -1.3351, -1.3375],
        [-0.3341,  1.3972, -0.6167,  ..., -0.4518, -0.2486, -1.2476],
        [-0.1120,  1.3242,  1.3087,  ..., -1.6434, -1.2767,  0.3947],
        ...,
        [-0.4056,  0.2554, -0.2930,  ..., -0.1409, -1.1402, -0.7819],
        [ 0.1866,  1.0919,  0.3436,  ..., -2.3544, -1.7272,  0.5688],
        [-2.1124, -0.8523, -0.2895,  ...,  0.4623,  1.0165, -0.2111]])

          ↓

         BitLiner

          ↓

【output(出力)されたテンソル(多次元配列)】
tensor([[ 0.1101,  0.4181,  0.1163,  ...,  0.3505, -0.2063, -0.1482],
        [ 0.1850, -0.1730, -0.5872,  ...,  0.3225,  0.0884, -0.1222],
        [ 0.7382, -0.4442,  0.8662,  ...,  0.1632,  0.2109,  0.4053],
        ...,
        [ 0.4774,  0.0900,  0.2738,  ..., -0.5555, -0.4715,  0.3727],
        [-0.6191,  0.7582,  1.0001,  ..., -0.2341,  0.4141, -0.1484],
        [ 0.5662, -0.1469,  0.6450,  ..., -0.5945, -0.4607, -0.3467]],
       grad_fn=)

inputに比べoutputの数値が変化していますね

 

BitLinearのデモコード:出力例 - 【1ビットの時代 - MNIST編】BitNet b1.58ニューラルネットワークで手書き数字画像認識
* 画像をクリックすると拡大されます。

 

 

【ステップ3】学習 編

 

 

MNISTのデータセットを使って、BitNetで学習してみましょう。

 

 

1.58ビットニューラルネットワークでMNISTを学習

 

 

学習回数」と「学習モデル名」を指定後に「実行コード」を実行します。

実行コード

# 各種インポート
import torch
from torch import nn
from torchvision import transforms, datasets
from bitnet.nn.bitlinear import BitLinear as Linear # 2024年3月15日以降のbitnetリポジトリに合わせて修正

# ハイパーパラメータの定義
input_size = 784
hidden_size = 100
num_classes = 10
learning_rate = 0.001
# @markdown 学習する回数(エポック数)を半角英数で指定します。
num_epochs = 5 # @param {type:"integer"}

# transformsによるデータの前処理の定義
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])

# MNISTデータセットをロード
train_dataset = datasets.MNIST('./mnist_data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST('./mnist_data', train=False, download=True, transform=transform)

# データローダー(DataLoader)の作成
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=64, shuffle=False)

# ニューラルネットワークの定義
class Net(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super(Net, self).__init__()
        self.fc1 = Linear(input_size, hidden_size)
        self.fc2 = Linear(hidden_size, num_classes)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.log_softmax(self.fc2(x), dim=1)
        return x

# モデルとオプティマイザーの作成
model = Net(input_size, hidden_size, num_classes)
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

# GPUの際にはcudaを利用。CPUの際にはCPUを利用
model = model.to( "cuda" if torch.cuda.is_available() else "cpu")

# 損失関数の定義
criterion = nn.NLLLoss()

# 学習
print("学習を開始します。\n\n")
for epoch in range(num_epochs):
    for i, (images, labels) in enumerate(train_loader):
        # Forwardのパス
        outputs = model(images.view(-1, 28 * 28))
        loss = criterion(outputs, labels)

        # Backwardのパスとoptimize
        optimizer.zero_grad()
        loss.backward(retain_graph=False)
        optimizer.step()

        # 学習状況の表示
        if (i + 1) % 100 == 0:
            print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{len(train_loader)}], Loss: {loss.item():.4f}')

# 学習済みモデルを評価
with torch.no_grad():
    correct = 0
    total = 0
    for images, labels in test_loader:
        outputs = model(images.view(-1, 28 * 28))
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
    print(f'\n\n10000枚のテスト画像に対する精度:\n\n「 {100 * correct / total:.2f}% 」(100%に近いほど精度が良いという意味です)')
    print('* Loss - 損失関数の値が0に近いほど精度が良くなります')

# 学習済みモデルの出力
# @markdown 保存する学習モデルの名前を半角英数で指定します。
output_model_name = "mnist_BitNet.pth" # @param {type:"string"}
torch.save(model.state_dict(), output_model_name)
print('\n\n「BitNet」フォルダ内に学習モデルを保存しました。')

 

出力結果
(ランタイムのタイプ:CPU ・ 所要時間:1分40秒)

Downloading http:〜
Downloading http:〜 to ./mnist_data/MNIST/raw/train-images-idx3-ubyte.gz

100%|██████████| 9912422/9912422 [00:00 00:00, 71117854.76it/s]

Extracting ./mnist_data/MNIST/raw/train-images-idx3-ubyte.gz to ./mnist_data/MNIST/raw

〜

学習を開始します。


Epoch [1/5], Step [100/938], Loss: 0.3660
Epoch [1/5], Step [200/938], Loss: 0.2942
Epoch [1/5], Step [300/938], Loss: 0.2351
Epoch [1/5], Step [400/938], Loss: 0.3920
Epoch [1/5], Step [500/938], Loss: 0.1505
Epoch [1/5], Step [600/938], Loss: 0.1133
Epoch [1/5], Step [700/938], Loss: 0.3057
Epoch [1/5], Step [800/938], Loss: 0.1459
Epoch [1/5], Step [900/938], Loss: 0.2958

〜

Epoch [5/5], Step [100/938], Loss: 0.0248
Epoch [5/5], Step [200/938], Loss: 0.0595
Epoch [5/5], Step [300/938], Loss: 0.0904
Epoch [5/5], Step [400/938], Loss: 0.0318
Epoch [5/5], Step [500/938], Loss: 0.0392
Epoch [5/5], Step [600/938], Loss: 0.1708
Epoch [5/5], Step [700/938], Loss: 0.0293
Epoch [5/5], Step [800/938], Loss: 0.0222
Epoch [5/5], Step [900/938], Loss: 0.0336


10000枚のテスト画像に対する精度:

「 96.96% 」(100%に近いほど精度が良いという意味です)
* Loss - 損失関数の値が0に近いほど精度が良くなります


「bitnet」フォルダ内に学習モデルを保存しました。

 

 

おまけコード:1.58ビットニューラルネットワークでMNISTを学習

 

 

2024年3月13日時点の「AlarioAI/bitnet(The MIT License)| GitHub」のリポジトリのBitNetでMNISTを学習させるサンプルコードです。
Google Colaboratory上で動かせるようにコードを一部改変してあります。
サンプルコードでは、「BitNet」(BitLinearを有効化したニューラルネット)と「FloatNet」(BitLinearを無効化したニューラルネット)の結果を比較できるようでした。
* このコードでは、学習モデルは保存しません。学習の経過と結果だけ表示されます。

 

学習回数」を指定後に「実行コード」を実行します。

実行コード

import torch
from torch import Tensor
from torch import nn
from torchvision import transforms, datasets
from torch.utils.data import DataLoader

from tqdm import tqdm

from bitnet.nn.bitlinear import BitLinear as Linear # 2024年3月15日以降のbitnetリポジトリに合わせて修正
import random
import numpy as np
# from seed import set_seed


device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')


class Net(nn.Module):
    def __init__(
            self,
            layer: callable,
            input_size: int,
            hidden_size: int,
            num_classes: int
        ) -> None:
        super(Net, self).__init__()
        self.layer = layer
        self.fc1 = layer(input_size, hidden_size, bias=False)
        self.fc2 = layer(hidden_size, num_classes, bias=False)


    @property
    def __name__(self) -> str:
        return "BitNet" if self.layer == BitLinear else "FloatNet"


    def forward(self, _input: Tensor) -> Tensor:
        out = _input.flatten(start_dim=1)
        out = torch.relu(self.fc1(out))
        out = self.fc2(out)
        return out


def train_model(
        model: nn.Module,
        train_loader: DataLoader,
        optimizer: torch.optim,
        criterion: nn.CrossEntropyLoss,
        num_epochs: int) -> None:
    for epoch in range(num_epochs):
        pbar = tqdm(total=len(train_loader), desc=f"Training {model.__name__}")
        running_loss: float = 0.0
        for i, (images, labels) in enumerate(train_loader):
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            loss = criterion(outputs, labels)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            running_loss += loss.item()
            pbar.set_description(
                f'Model: {model.__name__} - Epoch [{epoch+1}], Loss: {running_loss / (i+1):.4f}'
            )
            pbar.update(1)
        pbar.close()


def test_model(model: nn.Module, test_loader: DataLoader):
    model.eval()
    correct: int = 0
    total: int = 0
    with torch.no_grad():
        for images, labels in test_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    accuracy = 100 * correct / total
    print(f'Accuracy of {model.__name__}: {accuracy:.2f}%')



def set_seed(seed_value: int = 53):
    """Sets the seed for reproducibility."""
    torch.manual_seed(seed_value)
    torch.cuda.manual_seed(seed_value)
    torch.cuda.manual_seed_all(seed_value)
    np.random.seed(seed_value)
    random.seed(seed_value)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False


def main():
    input_size: int         = 784
    hidden_size: int        = 100
    num_classes: int        = 10
    learning_rate: float    = 1e-3
    # @markdown 学習する回数(エポック数)を半角英数で指定します。
    num_epochs: int         = 5 # @param {type:"integer"}
    batch_size: int         = 128

    transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.1307,), (0.3081,))
    ])

    bitnet = Net(BitLinear, input_size, hidden_size, num_classes).to(device)
    floatnet = Net(nn.Linear, input_size, hidden_size, num_classes).to(device)

    bitnet_optimizer = torch.optim.Adam(bitnet.parameters(), lr=learning_rate)
    floatnet_optimizer = torch.optim.Adam(floatnet.parameters(), lr=learning_rate)

    criterion = nn.CrossEntropyLoss()

    train_dataset = datasets.MNIST('./mnist_data', train=True, download=True, transform=transform)
    test_dataset = datasets.MNIST('./mnist_data', train=False, download=True, transform=transform)

    set_seed()
    train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
    test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
    train_model(bitnet, train_loader, bitnet_optimizer, criterion, num_epochs)
    test_model(bitnet, test_loader)

    set_seed()
    train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
    test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
    train_model(floatnet, train_loader, floatnet_optimizer, criterion, num_epochs)
    test_model(floatnet, test_loader)


if __name__ == "__main__":
    main()

 

出力結果
(ランタイムのタイプ:CPU ・ 所要時間:3分48秒)

Model: BitNet - Epoch [1], Loss: 0.3346: 100%|██████████| 469/469
Model: BitNet - Epoch [2], Loss: 0.1420: 100%|██████████| 469/469 
Model: BitNet - Epoch [3], Loss: 0.1049: 100%|██████████| 469/469 
Model: BitNet - Epoch [4], Loss: 0.0848: 100%|██████████| 469/469
Model: BitNet - Epoch [5], Loss: 0.0717: 100%|██████████| 469/469 
Accuracy of BitNet: 96.90%
Model: FloatNet - Epoch [1], Loss: 0.3189: 100%|██████████| 469/469 
Model: FloatNet - Epoch [2], Loss: 0.1492: 100%|██████████| 469/469 
Model: FloatNet - Epoch [3], Loss: 0.1048: 100%|██████████| 469/469 
Model: FloatNet - Epoch [4], Loss: 0.0793: 100%|██████████| 469/469
Model: FloatNet - Epoch [5], Loss: 0.0642: 100%|██████████| 469/469
Accuracy of FloatNet: 97.63%

 

 

【ステップ4】画像認識 編

 

 

作成した学習モデルを使って、どうすればPyTorchで画像認識ができるのか?しばらく試行錯誤したところ、(奇跡的に)動かすことができました😭
それでは、自前画像で手書き数字画像認識をしてみましょう。

 

 

作成した学習モデルで手書き数字認識に挑戦

 

 

今回作成した学習済みモデルを利用して、手書き数字の画像認識をしてみましょう。
まずは、手書き数字画像をGoogle Colaboratoryにアップロードします。
その後、「学習モデル名」と「画像のパス」を指定後に「実行コード」を実行します。

実行コード

#各種インポート
import torch
import torch.nn as nn
import torch.nn.functional as f
import torchvision
from torchvision import datasets, transforms
from bitnet.nn.bitlinear import BitLinear as Linear # 2024年3月15日以降のbitnetリポジトリに合わせて修正
from PIL import Image, ImageOps
import PIL.ImageOps

# ハイパーパラメータの定義
input_size = 784
hidden_size = 100
num_classes = 10

# モデルの定義
class Net(nn.Module):
    def __init__(self, input_size):
        super(Net, self).__init__()
        self.fc1 = Linear(input_size, hidden_size)
        self.fc2 = Linear(hidden_size, num_classes)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.log_softmax(self.fc2(x), dim=1)
        return x

# GPUの際にはcudaを利用。CPUの際にはCPUを利用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 学習済みモデルのロード
# @markdown 使用する学習モデルの名前を半角英数で指定します。
model_name = "mnist_bitnet_b158.pth" # @param {type:"string"}
model.load_state_dict(torch.load(model_name, map_location=lambda storage, loc: storage))
model = model.eval()

# 画像ファイルを読み込み
# 白背景・黒文字を黒背景・白文字に変換し画像認識
# @markdown 画像認識したい画像のパスを半角英数で指定します。
input_image = '/content/bitnet/5.png' # @param {type:"string"}
image = Image.open(input_image)
print('【AIが画像認識した画像】\n\n')
# 読み込んだ画像の表示
display(image)
# 画像のネガポジ変換
image = PIL.ImageOps.invert(image.convert('RGB'))
# 画像のリサイズ縦横「28」ピクセル
image = image.convert('L').resize((28,28))

# データの前処理の定義(モデル作成時の平均値・標準偏差で正規化)
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])

# 学習モデルに合わせて次元を追加
image = transform(image).unsqueeze(0)

# 予測を実施
output = model(image.view(-1, 28 * 28))
_, predicted = torch.max(output.data, 1)

# 画像認識結果の出力
print('\n\n【画像認識結果】')
print("\nAIによる画像認識結果は「 ", str(predicted[0].item())," 」です。\n\n")

 

出力結果

【AIが画像認識した画像】

(画像の表示)

【画像認識結果】

AIによる画像認識結果は「  0  」です。

 

実行例:作成した学習モデルで手書き数字認識に挑戦 BitNet b1.58
*一例:学習回数:5回 ・ Loss: 0.0336 ・ 10000枚のテスト画像に対する精度:96.96%

 

試した範囲では、5回学習させた10000枚のテスト画像に対する精度 96.96%の学習モデルを使うことで、サンプル画像の「0〜9」を全ての画像認識に成功することができました。
(間違えた数字の画像を、縦横28ピクセルにして書き直してみると正答になりやすい印象を受けました)

わずか1分ほどのCPUの学習で…
いい感じのモデルができましたので、すごいですね。
(1ビットニューラルネットの影響なのか?の考察は、まだ深くはできていません・・・)

 

 

おわりに

 

 

1ビットLLMに挑戦する前に、手始めとしてBitNetで1ビットニューラルネットを活用したMNISTの画像認識のチュートリアルコードを作成できてほっとしています。
(公開当初、よくわからずにBitNet b1.58と勘違いしていましたが…)

実のところ、PyTorchを活用した画像認識に関しては、食わず嫌いの苦手意識を持ち続けて今日まできましたが、今回のチュートリアルコードを作成する中で、少し苦手意識が軽減したような気がします。
(気が…)

このチュートリアルコード公開時点(2024年3月11日)では、まだまだ、1ビットLLMの入口を眺めている状況ではありますが、今後、少しずつ1ビットLLMの作成に近づけられるように学んでいきたいと思います。

 

 

1ビット:BitNet・BitNet b1.58関連論文

 

 

【1ビットTransformers論文】BitNet: Scaling 1-bit Transformers for Large Language Models

 

 

1ビットTransformers – BitNetの論文:
BitNet: Scaling 1-bit Transformers for Large Language Models – Microsoft Research et al(v1:17 Oct 2023)| arxiv
(BitNet: 大規模な言語モデル向けの 1ビットTransformersのスケーリング 2023年10月17日公開 – マイクロソフトリサーチ 他)

 

 

【1ビットLLM論文】The Era of 1-bit LLMs: All Large Language Models are in 1.58 Bits

 

 

1ビットLLM – BitNet b1.58の論文:
The Era of 1-bit LLMs: All Large Language Models are in 1.58 Bits – Microsoft Research et al(v1:27 Feb 2024)| arxiv
(1ビットLLMの時代:すべての大規模言語モデルは1.58ビットです 2024年2月27日公開 – マイクロソフトリサーチ 他)

 

1ビットLLM – BitNet b1.58の追加論文:
microsoft/unilm:bitnet/The-Era-of-1-bit-LLMs__Training_Tips_Code_FAQ.pdf – Mar 20, 2024 | GitHub
(1ビットLLMの時代: トレーニングのヒント、コード、FAQ – 2024年3月20日公開)

 

 

by 子供プログラマー

 

 

BitNetで実験をしてみましょう
【1ビットの時代 – MNIST編】BitNet b1.58とBitNet(1-bit)を比較

日本語LLMをファインチューニングし自分だけのカスタム対話型AIに挑戦
日本語LLMのファインチューニング入門 – 自作・Hugging Face公開データセット対応

気軽にチャットAIが始められるおすすめの拡張機能です。会員登録やログイン不要で使えるチャットAIもあります。
【使い方】ChatHub入門 – チャットAIをはじめよう