【dlprog】機械学習でlossと一緒に進捗バーを表示するPythonライブラリの紹介

機械学習・深層学習モデルの実装で、学習時にlossと一緒に進捗バーを表示するライブラリを作りました。

## dlprog

- [PyPI](https://pypi.org/project/dlprog/)
- [GitHub](https://github.com/misya11p/dlprog/)
- [API reference](https://misya11p.github.io/dlprog/)

ループ中の進捗バーを表示するライブラリです。ループごとに出力される値を集計し、その平均値を共に表示します。深層学習モデルの学習時に役立ちます。進捗バーと一緒にlossなどをリアルタイムに表示できます。

![demo_dl.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235373/2e3f75cf-22a3-a87f-436e-13034e98d621.gif)


## 導入

```bash
pip install dlprog
```

初めに`Progress`オブジェクトを生成しておきます。

```python
from dlprog import Progress
prog = Progress()
```

## 基本的な使い方

ループの初めに`start()`メソッドを呼び出し、ループ数`n_iter`を指定します。ループ内では`update()`メソッドを呼び出し、値を渡します。

```python
import random
import time

n_iter = 30

prog.start(n_iter=n_iter)
for _ in range(n_iter):
    time.sleep(0.05)
    value = random.random()
    prog.update(value)
```

![demo_basic.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235373/90563fa2-be47-31fe-68be-1d562af73707.gif)


2重ループで使用することもできます。その際は`n_epochs`を指定すると良いです。また、値のラベル`label`を指定することもできます。

```python
n_iter = 30
n_epochs = 3

prog.start(n_iter=n_iter, n_epochs=n_epochs, label="value")
for _ in range(n_epochs):
    for _ in range(n_iter):
        time.sleep(0.05)
        value = random.random()
        prog.update(value)
```

![demo_basic_option.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235373/db13a547-5a61-a02f-5fb1-3e2bf435d5f0.gif)


## 深層学習での使用例

深層学習モデルの実装で使ってみましょう。PyTorchを使った例です。

```python
n_epochs = 3
n_iter = len(train_loader)

prog.start(n_epochs=n_epochs, n_iter=n_iter, label="loss")
for _ in range(n_epochs):
    for x, label in train_loader:
        optimizer.zero_grad()
        y = model(x)
        loss = loss_fn(y, label)
        loss.backward()
        optimizer.step()
        prog.update(loss.item())
```

![demo_dl.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2235373/2e3f75cf-22a3-a87f-436e-13034e98d621.gif)

## オプション

より細かい使い方の説明です。

### `width`

バーの幅を指定する引数です。デフォルトは`40`です。

```python
n_iter = 10
prog.start(n_iter=n_iter, width=10)
for _ in range(n_iter):
    time.sleep(0.1)
    value = random.random()
    prog.update(value)
```

```
1: ########## 100% [00:00:01.05] 0.58703
```

### `leave_freq`

ログを残す頻度を指定する引数です。デフォルトは`1`なので全てのログを残します。

```python
n_epochs = 3
n_iter = 10
prog.start(n_epochs=n_epochs, n_iter=n_iter, leave_freq=4)
for _ in range(n_epochs):
    for _ in range(n_iter):
        time.sleep(0.1)
        value = random.random()
        prog.update(value)
```

```
 4/12: ######################################## 100% [00:00:01.06] 0.34203 
 8/12: ######################################## 100% [00:00:01.05] 0.47886 
12/12: ######################################## 100% [00:00:01.05] 0.40241 
```

### `unit`

エポックの単位を指定する引数です。複数のエポックをまとめられます。

```python
n_epochs = 3
n_iter = 10
prog.start(n_epochs=n_epochs, n_iter=n_iter, leave_freq=4)
for _ in range(n_epochs):
    for _ in range(n_iter):
        time.sleep(0.1)
        value = random.random()
        prog.update(value)
```

```
  1-4/12: ######################################## 100% [00:00:04.21] 0.49179 
  5-8/12: ######################################## 100% [00:00:04.20] 0.51518 
 9-12/12: ######################################## 100% [00:00:04.18] 0.54546 
```

### `n_values`

管理する値の数を指定する引数です。デフォルトは`1`です。複数の値を指定した場合、`update()`には値のリストを渡します。

```python
n_iter = 10
prog.start(n_iter=n_iter, n_values=2)
for _ in range(n_iter):
    time.sleep(0.1)
    value1 = random.random()
    value2 = random.random() * 10
    prog.update([value1, value2])
```

```
1: ######################################## 100% [00:00:01.05] 0.47956, 4.96049 
```

`n_values`の代わりに`label`を複数指定することもできます。

```python
n_iter = 10
prog.start(n_iter=n_iter, label=['value1', 'value2'], width=20)
for _ in range(n_iter):
    time.sleep(0.1)
    value1 = random.random()
    value2 = random.random() * 10
    prog.update([value1, value2])
```

```
1: #################### 100% [00:00:01.04] value1: 0.65758, value2: 4.09566 
```

### `note`

バーに表示するメモなどの文字列を指定するする引数です。

```python
n_iter = 10
prog.start(n_iter=n_iter, note='This is a note')
for _ in range(n_iter):
    time.sleep(0.1)
    value = random.random()
    prog.update(value)
```

```
1: ######################################## 100% [00:00:01.05] 0.58703, This is a note 
```

### `memo()`

バーに表示するメモなどの文字列を指定するメソッドです。`note`と違い、ループ中に変更できます。  
`defer=True`とすると、イテレーションの最後に呼び出すことができるようになります。

```python
n_epochs = 3
prog.start(
    n_epochs=n_epochs,
    n_iter=len(trainloader),
    label='train_loss',
    defer=True,
    width=20,
)
for _ in range(n_epochs):
    for x, label in train_loader:
        optimizer.zero_grad()
        y = model(x)
        loss = loss_fn(y, label)
        loss.backward()
        optimizer.step()
        prog.update(loss.item())
    test_loss = eval_model(model)
    prog.memo(f'test_loss: {test_loss:.5f}')
```

```
1/3: #################### 100% [00:00:02.83] train_loss: 0.34094, test_loss: 0.18194 
2/3: #################### 100% [00:00:02.70] train_loss: 0.15433, test_loss: 0.12987 
3/3: #################### 100% [00:00:02.79] train_loss: 0.10651, test_loss: 0.09783 
```

:::note warn
この進捗バーは、`n_iter`回目の`update()`呼び出されたタイミングで次のエポックに進むため、イテレーションの最後（の`update()`の後）に`memo()`を呼び出すとその文字列は次のエポックのバーに表示されます。`defer=True`とすることで、エポックが進むタイミングが`memo()`の呼び出し時に変更されるため、その文字列表示されてからエポックが進みます。
:::

## オワリ

おわりです。その他細かい使い方は[API reference](https://misya11p.github.io/dlprog/)を参照してください。