Colab 使用方法

Source

Colab使用教程(超级详细版)及Colab Pro/Pro+评测 - 知乎 (zhihu.com)

Google Colab 的正确使用姿势 - 知乎 (zhihu.com)

Colab Pro 值得花 9.9$/mon 订阅吗?来看这篇完整评测 - 知乎 (zhihu.com)

Argparse: https://colab.research.google.com/github/anthony-agbay/introduction-to-python/blob/main/modules/introduction-argparse-pt1/introduction-argparse-pt1.ipynb#scrollTo=70ApDftOmCKk

Takeaway

性价比较高的组合是:每月 $3 的 Google Drive + 每月 $11 Colab Pro。

Why Colab

雖然我自己有 GTX1080 8GB 和 RTX3060 12GB, 但有兩個問題 (1) 自己的 GPU 還是太慢; (2) 如果在外面使用 Mac Book Air, 還是需要雲的 GPU 處理。我看了一下選擇 Google Colab Pro ($10.49 / month). 好處是可以使用 Nvidia V100 和 A100 (比較難連上) 以及更大的 DRAM (50GB?),可以節省不少時間。

因此我的計劃:

  • Prototype 就用 GTX1080 8GB or RTX3060 12GB。
  • 一旦 debug 完畢, 使用 Colab GPU 執行。

image-20240324215712170

Colab 介紹

Colab = Colaboratory(即合作实验室),是谷歌提供的一个在线工作平台,用户可以直接通过浏览器执行python代码并与他人分享合作。Colab的主要功能在提供的GPU。

Jupyter Notebook:在Colab中,python代码的执行是基于.ipynb文件,也就是Jupyter Notebook格式的python文件。这种笔记本文件与普通.py文件的区别是可以分块执行代码并立刻得到输出,同时也可以很方便地添加注释,这种互动式操作十分适合一些轻量的任务。

代码执行程序:代码执行程序就是Colab在云端的”服务器”。简单来说,我们先在笔记本写好需要运行的代码,连接到代码执行程序,然后Colab会在云端执行代码,最后把结果传回浏览器。

实例空间:连接到代码执行程序后,Colab需要为其分配实例空间(Instance),可以简单理解为运行笔记本而创建的”虚拟机”,其中包含了执行ipynb文件时的默认配置、环境变量、自带的库等等。

会话:当笔记本连接到代码执行程序并分配到实例空间后,就成为了一个会话(Session),用户能开启的回话数量是有限的

image-20240324225241863

在打开笔记本后,我们默认的文件路径是“/content”,这个路径也是执行笔记本时的路径,同时我们一般把用到的各种文件也保存在这个路径下。在点击”..”后即可返回查看根目录”/”(如下图),可以看到根目录中保存的是一些虚拟机的环境变量和预装的库等等。

不要随意修改根目录中的内容,以避免运行出错,我们所有的操作都应在“/content”中进行。

image-20240324225429141

Colab from Scratch

最簡單的方法就是直接創建 jupyter notebook.

新建笔记本

有两种方法可以新建一个笔记本,第一种是在在云端硬盘中右键创建。

image-20240324224958194

第二种方法是直接在浏览器中输入https://colab.research.google.com,进入Colab的页面后点击新建笔记本即可。使用这种方法新建的笔记本时,会在云端硬盘的根目录自动创建一个叫Colab Notebook的文件夹,新创建的笔记本就保存在这个文件夹中。

上傳所有的 jupyter notebook and data.

image-20240325160823858

不過真的太麻煩。比較適合几个轻量的模块,也不打算使用git进行版本管理,则直接将这些模块上传到实例空间即可。或是從下載網路的 notebook.

Google Drive + Colab

Colab一般配合Google Drive使用(下文会提到这一点)。因此如有必要,我建议拓展谷歌云端硬盘的储存空间,个人认为性价比较高的是基本版 (100GB, NTD 65/month) 。

执行代码块

notebook文件通过的代码块来执行代码,同时支持通过”!的方式来执行UNIX终端命令(比如“!ls”可以查看当前目录下的文件)。Colab已经预装了大多数常见的深度学习库,比如pytorch,tensorflow等等,如果有需要额外安装的库可以通过*“!pip3 install "*命令来安装。下面是一些常见的命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 加载云端硬盘
from google.colab import drive
drive.mount('/content/drive')

# 查看分配到的GPU
gpu_info = !nvidia-smi
gpu_info = '\n'.join(gpu_info)
if gpu_info.find('failed') >= 0:
  print('Not connected to a GPU')
else:
  print(gpu_info)

# 安装python包
!pip3 install <package>

Let’s try an example.

1
2
from google.colab import drive
drive.mount('/content/drive')

加载数据集

在深度学习中,我们常常需要加载超大量的数据集,如何在Colab上快速加载这些数据?

  1. 将整个数据集从本地上传到实例空间

理论可行但实际不可取。经过作者实测,无论是上传压缩包还是文件夹,这种方法都非常的浪费时间,对于较大的数据集不具备可操作性。

  1. 将整个数据集上传到谷歌云盘,挂载谷歌云盘的之后直接读取云盘内的数据集

理论可行但风险较大。根据谷歌的说明,Colab读取云盘的I/O次数也是有限制的,太琐碎的I/O会导致出现“配额限制”。而且云盘的读取效率也低于直接读取实例空间中的数据的效率。

为什么云端硬盘操作有时会因配额问题而失败?research.google.com/colaboratory/faq.html#drive-quota

  1. 将数据集以压缩包形式上传到谷歌云盘,然后解压到Colab实例空间

实测可行。挂载云盘不消耗时间,解压所需的时间远远小于上传数据集的时间。

此外,由于实例空间会定期释放,因此模型训练完成后的日志也应该储存在谷歌云盘上。综上所述,谷歌云盘是使用Colab必不可少的一环,由于免费的云盘只有15个G,因此建议至少拓展到基本版。

如何執行 Python?

有些時候我們希望直接執行 python file. 可以利用下列方法:

1
!python3 .....

image-20240325145723888

如何让代码有“断点续传”的能力?

由于Colab随时有可能断开连接,在Colab上训练模型的代码必须要有可恢复性(能载入上一次训练的结果)。我把两个分别实现保存和加载checkpoint的函数附在下方,给大家作参考(基于pytorch)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
def save_checkpoint(path: Text,
                    epoch: int,
                    modules: Union[nn.Module, Sequence[nn.Module]],
                    optimizers: Union[opt.Optimizer, Sequence[opt.Optimizer]],
                    safe_replacement: bool = True):
    """
    Save a checkpoint of the current state of the training, so it can be resumed.
    This checkpointing function assumes that there are no learning rate schedulers or gradient scalers for automatic
    mixed precision.
    :param path:
        Path for your checkpoint file
    :param epoch:
        Current (completed) epoch
    :param modules:
        nn.Module containing the model or a list of nn.Module objects
    :param optimizers:
        Optimizer or list of optimizers
    :param safe_replacement:
        Keep old checkpoint until the new one has been completed
    :return:
    """

    # This function can be called both as
    # save_checkpoint('/my/checkpoint/path.pth', my_epoch, my_module, my_opt)
    # or
    # save_checkpoint('/my/checkpoint/path.pth', my_epoch, [my_module1, my_module2], [my_opt1, my_opt2])
    if isinstance(modules, nn.Module):
        modules = [modules]
    if isinstance(optimizers, opt.Optimizer):
        optimizers = [optimizers]
 
    # Data dictionary to be saved
    data = {
        'epoch': epoch,
        # Current time (UNIX timestamp)
        'time': time.time(),
        # State dict for all the modules
        'modules': [m.state_dict() for m in modules],
        # State dict for all the optimizers
        'optimizers': [o.state_dict() for o in optimizers]
    }

    # Safe replacement of old checkpoint
    temp_file = None
    if os.path.exists(path) and safe_replacement:
        # There's an old checkpoint. Rename it!
        temp_file = path + '.old'
        os.rename(path, temp_file)

    # Save the new checkpoint
    with open(path, 'wb') as fp:
        torch.save(data, fp)
        # Flush and sync the FS
        fp.flush()
        os.fsync(fp.fileno())

    # Remove the old checkpoint
    if temp_file is not None:
        os.unlink(path + '.old')


def load_checkpoint(path: Text,
                    default_epoch: int,
                    modules: Union[nn.Module, Sequence[nn.Module]],
                    optimizers: Union[opt.Optimizer, Sequence[opt.Optimizer]],
                    verbose: bool = True):
    """
    Try to load a checkpoint to resume the training.
    :param path:
        Path for your checkpoint file
    :param default_epoch:
        Initial value for "epoch" (in case there are not snapshots)
    :param modules:
        nn.Module containing the model or a list of nn.Module objects. They are assumed to stay on the same device
    :param optimizers:
        Optimizer or list of optimizers
    :param verbose:
        Verbose mode
    :return:
        Next epoch
    """
    if isinstance(modules, nn.Module):
        modules = [modules]
    if isinstance(optimizers, opt.Optimizer):
        optimizers = [optimizers]

    # If there's a checkpoint
    if os.path.exists(path):
        # Load data
        data = torch.load(path, map_location=next(modules[0].parameters()).device)

        # Inform the user that we are loading the checkpoint
        if verbose:
            print(f"Loaded checkpoint saved at {datetime.fromtimestamp(data['time']).strftime('%Y-%m-%d %H:%M:%S')}. "
                  f"Resuming from epoch {data['epoch']}")

        # Load state for all the modules
        for i, m in enumerate(modules):
            modules[i].load_state_dict(data['modules'][i])

        # Load state for all the optimizers
        for i, o in enumerate(optimizers):
            optimizers[i].load_state_dict(data['optimizers'][i])

        # Next epoch
        return data['epoch'] + 1
    else:
        return default_epoch

在主程序train.py正式开始训练前,添加下面的语句:

if args.resume: # args.resume是命令行输入的参数,用于指示要不要加载上次训练的结果
    first_epoch = load_checkpoint(checkpoint_path, first_epoch, net_list, optims_list)

在每个epoch训练结束后,保存checkpoint:

# Save checkpoint
 save_checkpoint(checkpoint_path, epoch, net_list, optims_list)

net_list是需要保存的网络列表,optims_list是需要保存的优化器列表

这里没有记录scheduler的列表,如果代码里用到了scheduler,那也要保存scheduler的列表。

Command-Line Argpase

可以利用以下方法和 argparse 共用 or for debugging purpose.

In addition to positional arguments, there is second type of argument: the optional argument. As the name suggests, optional arguments do not need to be passed when a script is called. Instead, you use an optional argument by passing in a name-value pair (e.g., -arg value). To indicate that an argument is optional, you append a - to a short name (e.g., a single letter) and a -- to a long name (e.g., word). By convention, you should have both a short and long name for an optional argument.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
%%writefile parsing.py
##### SCRIPT STARTS HERE #####
#!usr/bin/bash python

import argparse

# Define the ArgumentParser
parser = argparse.ArgumentParser()

# Add arguments

parser.add_argument("arg1", type=int)
parser.add_argument("-arg2", "--argument2")

# Indicate end of argument definitions and parse args
args = parser.parse_args()

# Access positional by using dot syntax with their name
print("Argument 1:", args.arg1)

# Access optional arguments by the long name
# An optional argument will have a None value if no argument is passed
# so you can use an if statement directly
if args.argument2:
    print("Argument 2:", args.argument2)
1
2
3
4
5
# You don't have to include optional arguments
!python3 parsing.py 5

# Use short or long name and then the value for optional arguments
!python3 parsing.py 5 -arg2 "Optional Argument"

Argument 1: 5

Argument 1: 5

Argument 2: Optional Argument

Reference

WSL2: VSCode + Conda + Formatter使用設定: https://medium.com/@mh.yang/wsl2-vscode-conda-formatter%E4%BD%BF%E7%94%A8%E8%A8%AD%E5%AE%9A-acca390e94c8

(知乎) win10+wsl2+vs code+python平台搭建笔记

(知乎) WSL2: VSCode + Virtualenv的使用与配置 (知乎)

https://code.visualstudio.com/blogs/2019/09/03/wsl2