Grokay PyTorch

Halo, Habr!

Kami telah memesan sebelumnya buku yang sudah lama ditunggu-tunggu tentang perpustakaan PyTorch .



Karena Anda akan mempelajari semua materi dasar yang diperlukan tentang PyTorch dari buku ini, kami mengingatkan Anda tentang manfaat dari proses yang disebut "grokking" atau "pemahaman mendalam" dari topik yang ingin Anda pelajari. Dalam posting hari ini, kami akan memberi tahu Anda bagaimana Kai Arulkumaran membanting PyTorch (tidak ada gambar). Selamat datang di kucing.

PyTorch adalah kerangka pembelajaran dalam fleksibel yang secara otomatis membedakan antara objek menggunakan jaringan saraf dinamis (yaitu, jaringan menggunakan kontrol aliran dinamis, seperti if dan while loop). PyTorch mendukung akselerasi GPU, pelatihan terdistribusi , berbagai jenis optimasi dan banyak fitur menarik lainnya. Di sini saya mengemukakan beberapa pemikiran tentang bagaimana, menurut pendapat saya, harus menggunakan PyTorch; semua aspek perpustakaan dan praktik yang disarankan tidak dibahas di sini, tetapi semoga teks ini bermanfaat bagi Anda.

Jaringan saraf adalah subkelas dari grafik komputasi. Komputasi grafik menerima data sebagai input, maka data ini dirutekan (dan dapat dikonversi) pada node di mana mereka diproses. Dalam pembelajaran mendalam, neuron (node) biasanya mentransformasikan data dengan menerapkan parameter dan fungsi terdiferensiasi padanya, sehingga parameter dapat dioptimalkan untuk meminimalkan kerugian dengan metode gradient descent. Dalam arti yang lebih luas, saya perhatikan bahwa fungsi dapat menjadi stokastik dan grafik yang dinamis. Dengan demikian, sementara jaringan saraf cocok dengan paradigma pemrograman aliran data, API PyTorch berfokus pada paradigma pemrograman imperatif , dan cara menafsirkan program yang sedang dibuat ini jauh lebih akrab. Itulah sebabnya kode PyTorch lebih mudah dibaca, lebih mudah menilai desain program yang rumit, yang, bagaimanapun, tidak memerlukan kompromi serius pada kinerja: pada kenyataannya, PyTorch cukup cepat dan memberikan banyak optimisasi bahwa Anda, sebagai pengguna akhir, tidak dapat khawatir (Namun, jika Anda benar-benar tertarik pada mereka, Anda dapat menggali sedikit lebih dalam dan mengenal mereka).

Sisa dari artikel ini adalah analisis dari contoh resmi pada dataset MNIST . Di sini kita bermain PyTorch, jadi saya sarankan untuk memahami artikel hanya setelah berkenalan dengan manual pemula resmi . Untuk kenyamanan, kode disajikan dalam bentuk fragmen kecil yang dilengkapi dengan komentar, yaitu, tidak didistribusikan ke fungsi / file terpisah yang biasa Anda lihat dalam kode modular murni.

Impor


 import argparse import os import torch from torch import nn, optim from torch.nn import functional as F from torch.utils.data import DataLoader from torchvision import datasets, transforms 

Semua ini adalah impor standar, dengan pengecualian modul torchvision , yang secara khusus digunakan secara aktif untuk menyelesaikan tugas yang berkaitan dengan visi komputer.

Kustomisasi


 parser = argparse.ArgumentParser(description='PyTorch MNIST Example') parser.add_argument('--batch-size', type=int, default=64, metavar='N', help='input batch size for training (default: 64)') parser.add_argument('--epochs', type=int, default=10, metavar='N', help='number of epochs to train (default: 10)') parser.add_argument('--lr', type=float, default=0.01, metavar='LR', help='learning rate (default: 0.01)') parser.add_argument('--momentum', type=float, default=0.5, metavar='M', help='SGD momentum (default: 0.5)') parser.add_argument('--no-cuda', action='store_true', default=False, help='disables CUDA training') parser.add_argument('--seed', type=int, default=1, metavar='S', help='random seed (default: 1)') parser.add_argument('--save-interval', type=int, default=10, metavar='N', help='how many batches to wait before checkpointing') parser.add_argument('--resume', action='store_true', default=False, help='resume training from checkpoint') args = parser.parse_args() use_cuda = torch.cuda.is_available() and not args.no_cuda device = torch.device('cuda' if use_cuda else 'cpu') torch.manual_seed(args.seed) if use_cuda: torch.cuda.manual_seed(args.seed) 

argparse adalah cara standar untuk menangani argumen baris perintah dengan Python.

Jika Anda perlu menulis kode yang dirancang untuk bekerja pada perangkat yang berbeda (menggunakan akselerasi GPU, ketika tersedia, tetapi jika itu tidak digulirkan kembali ke perhitungan pada CPU), lalu pilih dan simpan alat torch.device sesuai, dengan mana Anda dapat menentukan di mana Anda harus tensor disimpan. Untuk informasi lebih lanjut tentang cara membuat kode seperti itu, lihat dokumentasi resmi . Pendekatan PyTorch adalah membawa pemilihan perangkat ke kontrol pengguna, yang mungkin tampak tidak diinginkan dalam contoh sederhana. Namun, pendekatan ini sangat menyederhanakan pekerjaan ketika Anda harus berurusan dengan tensor, yang a) nyaman untuk debugging b) memungkinkan Anda untuk secara efektif menggunakan perangkat secara manual.

Untuk reproduksibilitas percobaan, Anda perlu menetapkan nilai awal acak untuk semua komponen yang menggunakan pembuatan angka acak (termasuk random atau numpy , jika Anda juga menggunakannya). Harap dicatat: cuDNN menggunakan algoritma non-deterministik dan secara opsional dinonaktifkan menggunakan torch.backends.cudnn.enabled = False .

Data


 data_path = os.path.join(os.path.expanduser('~'), '.torch', 'datasets', 'mnist') train_data = datasets.MNIST(data_path, train=True, download=True, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])) test_data = datasets.MNIST(data_path, train=False, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])) train_loader = DataLoader(train_data, batch_size=args.batch_size, shuffle=True, num_workers=4, pin_memory=True) test_loader = DataLoader(test_data, batch_size=args.batch_size, num_workers=4, pin_memory=True) 


Karena model torchvision disimpan di bawah ~/.torch/models/ , saya lebih suka menyimpan set torchvision torchvision di bawah ~/.torch/datasets . Ini adalah perjanjian hak cipta saya, tetapi sangat nyaman digunakan dalam proyek yang dikembangkan atas dasar MNIST, CIFAR-10, dll. Secara umum, kumpulan data harus disimpan secara terpisah dari kode jika Anda bermaksud untuk menggunakan kembali beberapa kumpulan data.

torchvision.transforms berisi banyak opsi konversi yang nyaman untuk gambar individual, seperti pemotongan dan normalisasi.

Ada banyak opsi di batch_size , tetapi selain batch_size dan shuffle , Anda juga harus ingat num_workers dan pin_memory , mereka membantu meningkatkan efisiensi. num_workers > 0 menggunakan subproses untuk memuat data asinkron, dan tidak memblokir proses utama untuk ini. Kasus penggunaan yang umum adalah memuat data (misalnya, gambar) dari disk dan, mungkin, mengubahnya; semua ini dapat dilakukan secara paralel, bersamaan dengan pemrosesan data jaringan. Tingkat pemrosesan mungkin perlu disesuaikan untuk a) meminimalkan jumlah pekerja dan, akibatnya, jumlah CPU dan RAM yang digunakan (masing-masing pekerja memuat bets terpisah, daripada sampel individu yang termasuk dalam bets) b) meminimalkan lamanya waktu data menunggu di jaringan. pin_memory menggunakan memori yang disematkan (sebagai lawan dari paged) untuk mempercepat operasi transfer data dari RAM ke GPU (dan tidak melakukan apa-apa dengan kode khusus untuk CPU).

Model


 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 10, kernel_size=5) self.conv2 = nn.Conv2d(10, 20, kernel_size=5) self.conv2_drop = nn.Dropout2d() self.fc1 = nn.Linear(320, 50) self.fc2 = nn.Linear(50, 10) def forward(self, x): x = F.relu(F.max_pool2d(self.conv1(x), 2)) x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2)) x = x.view(-1, 320) x = F.relu(self.fc1(x)) x = self.fc2(x) return F.log_softmax(x, dim=1) model = Net().to(device) optimiser = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum) if args.resume: model.load_state_dict(torch.load('model.pth')) optimiser.load_state_dict(torch.load('optimiser.pth')) 

Inisialisasi jaringan biasanya meluas ke variabel anggota, lapisan yang berisi parameter pembelajaran dan, mungkin, parameter pembelajaran individu dan buffer yang tidak terlatih. Kemudian, dengan pass langsung, mereka digunakan dalam kombinasi dengan fungsi-fungsi dari F yang murni fungsional dan tidak mengandung parameter. Beberapa orang suka bekerja dengan jaringan yang berfungsi murni (mis. Menjaga parameter dan menggunakan F.conv2d alih-alih nn.Conv2d ) atau jaringan yang seluruhnya terdiri dari lapisan (mis. nn.ReLU alih-alih F.relu ).

.to(device) adalah cara mudah untuk mengirim parameter perangkat (dan buffer) ke GPU jika device diatur ke GPU, karena jika tidak (jika perangkat diatur ke CPU) tidak ada yang akan dilakukan. Penting untuk mentransfer parameter perangkat ke perangkat yang sesuai sebelum meneruskannya ke pengoptimal; jika tidak, pengoptimal tidak akan dapat melacak parameter dengan benar!

Baik neural networks ( nn.Module ) dan optimizer ( optim.Optimizer ) dapat menyimpan dan memuat keadaan internal mereka, dan disarankan untuk melakukan ini dengan .load_state_dict(state_dict) - perlu memuat ulang keadaan keduanya untuk melanjutkan pelatihan berdasarkan kamus yang disimpan sebelumnya. menyatakan. Menyimpan seluruh objek mungkin penuh dengan kesalahan . Jika Anda menyimpan tensor pada GPU dan ingin memuatnya ke CPU atau GPU lain, maka cara termudah adalah memuatnya langsung ke CPU menggunakan opsi map_location , mis. torch.load('model.pth' , map_location='cpu' ).

Berikut adalah beberapa poin lain yang tidak ditampilkan di sini, tetapi layak disebutkan, bahwa Anda dapat menggunakan aliran kontrol dengan operan langsung (misalnya, eksekusi if mungkin tergantung pada variabel anggota atau pada data itu sendiri. Selain itu, itu sangat valid di tengah proses untuk menghasilkan ( print ) tensor, yang sangat menyederhanakan debugging. Akhirnya, dengan pass langsung, banyak argumen dapat digunakan. Saya akan mengilustrasikan poin ini dengan daftar pendek yang tidak terkait dengan ide tertentu:

 def forward(self, x, hx, drop=False): hx2 = self.rnn(x, hx) print(hx.mean().item(), hx.var().item()) if hx.max.item() > 10 or self.can_drop and drop: return hx else: return hx2 

Pelatihan


 model.train() train_losses = [] for i, (data, target) in enumerate(train_loader): data = data.to(device=device, non_blocking=True) target = target.to(device=device, non_blocking=True) optimiser.zero_grad() output = model(data) loss = F.nll_loss(output, target) loss.backward() train_losses.append(loss.item()) optimiser.step() if i % 10 == 0: print(i, loss.item()) torch.save(model.state_dict(), 'model.pth') torch.save(optimiser.state_dict(), 'optimiser.pth') torch.save(train_losses, 'train_losses.pth') 

Modul jaringan dimasukkan ke dalam mode pelatihan secara default - yang sedikit banyak mempengaruhi operasi modul, sebagian besar - penipisan dan normalisasi batch. Dengan satu atau lain cara, lebih baik untuk mengatur hal-hal seperti itu secara manual menggunakan .train() , yang memfilter bendera "pelatihan" ke semua modul anak.

Di sini, metode .to() tidak hanya menerima perangkat, tetapi juga menetapkan non_blocking=True , sehingga memastikan penyalinan data yang tidak sinkron ke GPU dari memori yang dikomit, memungkinkan CPU untuk tetap beroperasi selama transfer data; jika tidak, non_blocking=True sekali bukan opsi.

Sebelum Anda membangun satu set gradien baru menggunakan loss.backward() dan optimiser.step() menggunakan optimiser.step() , Anda harus secara manual mengatur ulang gradien parameter yang akan dioptimalkan menggunakan optimiser.zero_grad() . Secara default, PyTorch mengakumulasi gradien, yang sangat nyaman jika Anda tidak memiliki sumber daya yang cukup untuk menghitung semua gradien yang Anda butuhkan dalam satu pass.

PyTorch menggunakan sistem "pita" gradien otomatis - ia mengumpulkan informasi tentang operasi apa dan dalam urutan apa yang dilakukan pada tensor, dan kemudian memutarnya dalam arah yang berlawanan untuk melakukan diferensiasi dalam urutan terbalik (reverse-mode diferensiation). Itu sebabnya sangat fleksibel dan memungkinkan grafik komputasi yang sewenang-wenang. Jika tidak ada dari tensor ini yang memerlukan gradien (Anda harus menetapkan requires_grad=True , membuat tensor untuk tujuan ini), maka tidak ada grafik yang disimpan! Namun, jaringan biasanya memiliki parameter yang memerlukan gradien, sehingga setiap perhitungan yang dilakukan berdasarkan output jaringan akan disimpan dalam grafik. Jadi, jika Anda ingin menyimpan data yang dihasilkan dari langkah ini, Anda harus menonaktifkan gradien secara manual atau (pendekatan yang lebih umum), menyimpan informasi ini sebagai nomor Python (menggunakan .item() dalam skalar PyTorch) atau array numpy . Baca lebih lanjut tentang autograd di dokumentasi resmi .

Salah satu cara untuk mengurangi grafik komputasi adalah dengan menggunakan .detach() ketika keadaan tersembunyi dilewatkan saat mempelajari RNN dengan versi terpotong dari backpropagation-through-time. Juga nyaman saat membedakan kerugian, ketika salah satu komponen adalah output dari jaringan lain, tetapi jaringan lain ini tidak boleh dioptimalkan sehubungan dengan kerugian. Sebagai contoh, saya akan mengajarkan bagian diskriminatif pada pembuatan bahan keluaran saat bekerja dengan GAN, atau pelatihan kebijakan dalam algoritme aktor-kritik menggunakan fungsi objektif sebagai fungsi dasar (misalnya, A2C). Teknik lain yang mencegah perhitungan gradien efektif dalam pelatihan GAN (pelatihan bagian pembangkit pada bahan diskriminan) dan tipikal dalam fine tuning adalah penghitungan siklus parameter jaringan yang param.requires_grad = False .

Penting tidak hanya untuk mencatat hasil dalam file konsol / log, tetapi juga untuk menetapkan titik kontrol dalam parameter model (dan status pengoptimal) untuk berjaga-jaga. Anda juga dapat menggunakan torch.save() untuk menyimpan objek Python biasa, atau menggunakan solusi standar lain - pickle .

Pengujian


 model.eval() test_loss, correct = 0, 0 with torch.no_grad(): for data, target in test_loader: data = data.to(device=device, non_blocking=True) target = target.to(device=device, non_blocking=True) output = model(data) test_loss += F.nll_loss(output, target, reduction='sum').item() pred = output.argmax(1, keepdim=True) correct += pred.eq(target.view_as(pred)).sum().item() test_loss /= len(test_data) acc = correct / len(test_data) print(acc, test_loss) 

Menanggapi .train() jaringan harus secara eksplisit dimasukkan ke mode evaluasi menggunakan .eval() .

Seperti disebutkan di atas, ketika menggunakan jaringan, grafik komputasi biasanya dikompilasi. Untuk mencegah hal ini, gunakan no_grad konteks no_grad dengan with torch.no_grad() .

Lagi


Ini adalah bagian tambahan, di mana saya membuat beberapa penyimpangan yang lebih bermanfaat.
Berikut adalah dokumentasi resmi yang menjelaskan bekerja dengan memori.

Kesalahan CUDA? Sulit untuk memperbaikinya, dan biasanya mereka terhubung dengan inkonsistensi logis, yang menurutnya pesan kesalahan yang lebih masuk akal ditampilkan pada CPU daripada pada GPU. Yang terbaik dari semuanya, jika Anda berencana untuk bekerja dengan GPU, Anda dapat dengan cepat beralih antara CPU dan GPU. Tip pengembangan yang lebih umum adalah mengatur kode sehingga dapat dengan cepat diperiksa sebelum memulai tugas penuh. Misalnya, siapkan dataset kecil atau sintetis, jalankan uji satu kereta + era, dll. Jika masalahnya adalah kesalahan CUDA, atau Anda tidak dapat beralih ke CPU sama sekali, setel CUDA_LAUNCH_BLOCKING = 1. Ini akan membuat kernel CUDA meluncurkan sinkron, dan Anda akan menerima pesan kesalahan yang lebih akurat.

Catatan tentang torch.multiprocessing atau hanya menjalankan beberapa skrip PyTorch secara bersamaan. Karena PyTorch menggunakan pustaka BLAS multi-threaded untuk mempercepat perhitungan aljabar linier pada CPU, biasanya beberapa core terlibat. Jika Anda ingin melakukan beberapa hal secara bersamaan, menggunakan pemrosesan multi-utas atau beberapa skrip, mungkin disarankan untuk mengurangi jumlah mereka secara manual dengan mengatur variabel lingkungan OMP_NUM_THREADS menjadi 1 atau nilai rendah lainnya. Dengan demikian, kemungkinan tergelincirnya prosesor berkurang. Dokumentasi resmi memiliki komentar lain mengenai pemrosesan multithread.

Source: https://habr.com/ru/post/id471228/


All Articles