目次
2.1 OpenFWI プロジェクト ファイル フレームワークの理解
3.2 データローダーでの num_workers-MemoryError の設定
I.はじめに
先週、OpenFWIのコードをいくつか学び、Datasetの書き換えを完了しました。
今週は、OpenFWI コードの InversionNet ネットワーク関連の部分をコピーして理解する予定です。
2. 完成状況
2.1 OpenFWI プロジェクト ファイル フレームワークの理解
OpenFWI に含まれるファイルは次のとおりです。
- data_files: データ セット (トレーニング セットとテスト セットを含む) を保存します。
- dataset.py: データの読み込み
- dataset_config.json: データ読み込み関連のパラメータ設定
- network.py: ネットワークの定義 (3 つのネットワークが含まれます。次の記事では InversionNet 関連のコードのみを紹介します)
- pytorch_ssim: SSIM包
- レインボー256.npy
- scheduler.py: 学習率の調整
- test.py: テストを実行する
- train.py: 一般的なネットワーク トレーニングを実行します。
- transforms.py: データ変換、データ処理
- utils.py: 損失の定義と評価の指標
- vis.py: 地震データと速度モデルの視覚的表示
2.2 InversionNet ネットワークを理解する
InversionNet ネットワークについて:ネットワーク フレームワーク学習のための InversionNet-CSDN ブログ
InversionNet (深層学習の反転実装) は、 を使用してエンコーダ/デコーダを構築します。 構造化畳み込みニューラル ネットワークは、地震データと地下速度構造の間の対応をシミュレートします。
ネットワークは次のように設定されます。
class InversionNet(nn.Module):
def __init__(self, dim1=32, dim2=64, dim3=128, dim4=256, dim5=512, **kwargs):
'''
Network architecture of InversionNet
:param dim1: Number of channels in the 1st layer
:param dim2: Number of channels in the 2nd layer
:param dim3: Number of channels in the 3rd layer
:param dim4: Number of channels in the 4th layer
:param dim5: Number of channels in the 5th layer
:param sample_spatial: Scale parameters for sampling in space
'''
super(InversionNet, self).__init__()
# ConvBlock中进行了封装,包括二维的卷积操作、批归一化BN以及Relu三个操作
self.convblock1 = ConvBlock(5, dim1, kernel_size=(7, 1), stride=(2, 1), padding=(3, 0))
self.convblock2_1 = ConvBlock(dim1, dim2, kernel_size=(3, 1), stride=(2, 1), padding=(1, 0))
self.convblock2_2 = ConvBlock(dim2, dim2, kernel_size=(3, 1), padding=(1, 0))
self.convblock3_1 = ConvBlock(dim2, dim2, kernel_size=(3, 1), stride=(2, 1), padding=(1, 0))
self.convblock3_2 = ConvBlock(dim2, dim2, kernel_size=(3, 1), padding=(1, 0))
self.convblock4_1 = ConvBlock(dim2, dim3, kernel_size=(3, 1), stride=(2, 1), padding=(1, 0))
self.convblock4_2 = ConvBlock(dim3, dim3, kernel_size=(3, 1)
self.convblock5_1 = ConvBlock(dim3, dim3, stride=2)
self.convblock5_2 = ConvBlock(dim3, dim3)
self.convblock6_1 = ConvBlock(dim3, dim4, stride=2)
self.convblock6_2 = ConvBlock(dim4, dim4)
self.convblock7_1 = ConvBlock(dim4, dim4, stride=2)
self.convblock7_2 = ConvBlock(dim4, dim4)
self.convblock8 = ConvBlock(dim4, dim5, kernel_size=(8, math.ceil(70 * 1.0 / 8)), padding=0)
self.deconv1_1 = DeconvBlock(dim5, dim5, kernel_size=5)
self.deconv1_2 = ConvBlock(dim5, dim5)
self.deconv2_1 = DeconvBlock(dim5, dim4, kernel_size=4, stride=2, padding=1)
self.deconv2_2 = ConvBlock(dim4, dim4)
self.deconv3_1 = DeconvBlock(dim4, dim3, kernel_size=4, stride=2, padding=1)
self.deconv3_2 = ConvBlock(dim3, dim3)
self.deconv4_1 = DeconvBlock(dim3, dim2, kernel_size=4, stride=2, padding=1)
self.deconv4_2 = ConvBlock(dim2, dim2)
self.deconv5_1 = DeconvBlock(dim2, dim1, kernel_size=4, stride=2, padding=1)
self.deconv5_2 = ConvBlock(dim1, dim1)
self.deconv6 = ConvBlock_Tanh(dim1, 1)
2.3 dataset.py について
独自に定義したデータセットを読み込むために、このモジュールで Dataset が書き換えられます。
# 重写Dataset
class FWIDataset(Dataset):
''' FWI dataset
For convenience, in this class, a batch refers to a npy file instead of the batch used during training.
Args:
anno: path to annotation file # 注释文件的路径
train: whether to load the whole dataset into memory # 是否将整个数据集加载到内存中
sample_ratio: downsample ratio for seismic data # 地震数据的下采样率
file_size: of samples in each npy file # 每个npy文件中的样本数
transform_data|label: transformation applied to data or label # 数据或标签的转换
'''
# 初始化函数:加载数据—初始化文件路径与文件名列表,完成初始化该类的一些基本参数
def __init__(self, anno, preload=True, sample_ratio=1, file_size=500, transform_data=None, transform_label=None):
# 判断文件路径是否存在
if not os.path.exists(anno):
print(f'Annotation file {anno} does not exists')
# 对继承自父类Dataset的属性进行初始化
super(FWIDataset, self).__init__()
self.preload = preload
self.sample_ratio = sample_ratio
self.file_size = file_size
self.transform_data = transform_data
self.transform_label = transform_label
# 以“只读”的方式打开文件,默认文件访问模式为“r”
with open(anno, 'r') as f:
# 读取所有行并返回列表(列表可由for...in...结构进行处理)
self.batches = f.readlines()
# 判断是否需要将整个数据集加载到内存中
if preload:
self.data_list, self.label_list = [], []
# 循环加载数据
for batch in self.batches:
# 读取每个batch中的数据与标签
data, label = self.load_every(batch)
# 将data添加到列表中
self.data_list.append(data)
if label is not None:
self.label_list.append(label)
# Load from one line
def load_every(self, batch):
# \t :表示空4个字符,类似于文档中的缩进功能,相当于按一个Tab键(txt文件需要根据这个进行分隔)
# split():通过置顶分隔符对字符串进行切片,并返回分割后的字符串列表(list)
# os.path.split():按照路径将文件名和路径分割开
batch = batch.split('\t')
# 切片:除最后一个全取
data_path = batch[0] if len(batch) > 1 else batch[0][:-1]
# np.load():读取数据
data = np.load(data_path)[:, :, ::self.sample_ratio, :]
# astype():转换数据的类型
data = data.astype('float32')
if len(batch) > 1:
label_path = batch[1][:-1]
label = np.load(label_path)
label = label.astype('float32')
else:
label = None
# 返回数据与标签
return data, label
# 对数据进行预处理并返回想要的信息
def __getitem__(self, idx): # 按照索引读取每个元素的具体内容
# //:向下取整
batch_idx, sample_idx = idx // self.file_size, idx % self.file_size
if self.preload:
data = self.data_list[batch_idx][sample_idx]
label = self.label_list[batch_idx][sample_idx] if len(self.label_list) != 0 else None
else:
data, label = self.load_every(self.batches[batch_idx])
data = data[sample_idx]
label = label[sample_idx] if label is not None else None
if self.transform_data:
# 将数据标签转换为Tensor
data = self.transform_data(data)
if self.transform_label and label is not None:
label = self.transform_label(label)
# return回哪些内容,那么我们在训练时循环读取每个batch时,就能获得哪些内容
return data, label if label is not None else np.array([])
# 初始化一些需要传入的参数及数据集的调用
def __len__(self):
# 返回的是数据集的长度,也就是多少张图片,要和loader的长度作区分
return len(self.batches) * self.file_size
データが正常にロードできるかどうかを確認します。
if __name__ == '__main__':
transform_data = Compose([
T.LogTransform(k=1),
T.MinMaxNormalize(T.log_transform(-61, k=1), T.log_transform(120, k=1))
])
transform_label = Compose([
T.MinMaxNormalize(2000, 6000)
])
data_set = FWIDataset(f'data_files/flatfault_a_train_invnet.txt', transform_data=transform_data, transform_label=transform_label,
file_size=1) # 训练集
data, label = data_set[0]
print(data.shape)
print(label is None)
3. 発生した問題とその解決策
3.1 データの読み込み
問題の説明:ファイルを読み取るときに、読み取るファイルが存在しないというメッセージが表示されますが、txt ファイル内のパスは正しいです。
解決策: ファイルの末尾に「y」を追加すると、問題が解決します。
考え方: コードのファイル読み取り部分 (処理対象) でエラーが発生したと考えてみましょう。
3.2 データローダーでの num_workers-MemoryError の設定
問題の説明:データローダーで MemoryError エラーが発生しました。
解決策:num_worker を 0 に設定します。
考え方:num_workers パラメータ: データのロードを実行するサブプロセスの数を指定するために使用されます。値が 0 の場合、メイン プロセスのみがデータ セットのロードに使用されます。値が 0 の場合、メイン プロセスのみがデータ セットのロードに使用されます。値が 0 より大きい場合、関連するデータ セットのロードを担当する対応する数の子プロセスが作成されます。
考えられるエラーの理由: 他のコードが実行されているため、メモリが不足しており、トレーニングを正常に完了できません。プログラムの実行時に CPU のメモリ使用率を確認できます。 (次回はコードを個別に実行してみてください)。
3.3 ランタイムエラー: CUDA のメモリ不足
問題の説明: train.py ファイルを実行すると、「RuntimeError: CUDA out of mememory」が表示されます
考えられる原因:
- Batch_size 設定が大きすぎます。
- データは引き続き GPU に保存されますが、解放されません。
- 他のコードが実行されています。
CUDA CPU メモリ管理の概要:
- GPU メモリの使用量は、保存されるデータのサイズと正の関係があり、データが大きいほど、より多くのメモリを消費します。
- GPU が使用されている限り、GPU は少なくとも x M のビデオ メモリを占有し、ビデオ メモリのこの部分を解放することはできません。
- メモリの一部が変数によって参照されなくなると、メモリはアクティブ メモリから非アクティブ メモリに変換されますが、データ キューにはまだ存在します。データがキューが特定のしきい値に達すると、CUDA はガベージ コレクション メカニズムをトリガーして、非アクティブなメモリをクリーンアップします
- torch.cuda.empty_cache() を実行して、デッドメモリを手動でクリーンアップします。
試すことができる解決策:
方法 1: 調整バッチ サイズ: 通常は 4 に設定すると問題を解決できます。問題が解決しない エラーが報告された場合は、他の方法を試してください。
方法 2:エラー部分にコードを挿入し、非アクティブなメモリを手動でクリーンアップします。
import torch,gc
gc.collect()
torch.cuda.empty_cache()
方法 3: 以下に示すように、テストおよびトレーニング フェーズの前にtorch.no_grad() を使用してコードを挿入します。
def train(model, criterion, dataloader, device, writer):
model.eval()
# 梯度清零
with torch.no_grad():
for data, label in metric_logger.log_every(dataloader, 20, header):
......
(1) Python の with について:
- with ステートメントはリソースへのアクセスに適しており、使用中に例外が発生しても、使用後のファイルの自動クローズやスレッドのロックの自動取得と解放など、リソースを解放するために必要な「クリーニング」操作が確実に実行されます。 。
(2)torch.no_grad():
- pytorch では、テンソルに require_grad パラメータがあります。True に設定すると、テンソルはバックプロパゲーション中に自動的に微分されます。属性 require_grad のデフォルトは False です。ノード (リーフ変数: 自分で作成したテンソル) require_grad が True に設定されている場合、それに依存するすべてのノード require_grad は True になります (他の依存ノードがtensor require_grad = False)。
- False に設定すると、バックプロパゲーション中に導出が自動的に実行されなくなり、ビデオ メモリやメモリが大幅に節約されます。
(3)torch.no_grad()あり:
- このモジュールでは、計算されたすべてのテンソルの require_grad が自動的に False に設定されます。
3.4 損失額の推移
4. 関連資料
(1) Windows 環境では、Datalodaer の num_workers の設定が 1 より大きく、その解決策と繰り返し操作によって Pytorch が発生します: Windows 環境では、Pytorch が発生しますDatalodaer の設定による num_workers が 1 より大きいです。異常なエラーと解決策、および繰り返される操作_numworkers の設定が高い場合、エラーが報告されます_Ye Huaisheng のブログ - CSDN ブログ
(2) CUDA のメモリ不足の原因と GPU メモリの解放方法を調べます:[解決済み] CUDA のメモリ不足の原因と GPU メモリの解放方法を調べます。 _cuda は指定された GPU メモリをクリーンアップします - CSDN ブログ
(3) torch.no_grad() の使用方法の詳細説明:[pytorch シリーズ] torch.no_grad() の使用方法: 使用方法の詳細説明 - CSDN ブログ
(4) Python での with open(file_abs,'r') as f: の使用法と意味:With open(file_abs, in) Python 39;r') as f: 使用法と意味 - CSDN ブログ
(5) split() 関数の使用法:split() 関数の使用法_split() 関数の使用法 - CSDN ブログ
5. まとめ
5.1 既存の疑問
- データが GPU に継続的に保存されているが解放されていないことを確認するにはどうすればよいですか?
- 損失額の変化は?
- データロード「y」問題の解決策?
5.2 来週の計画を立てる
来週も OpenFWI コードのコピーを続けます。コピー プロセス中に理解し、質問しながら学び、異なる結果が得られることを期待します。