J'ai cherché sur la base du document officiel. La syntaxe est assez similaire à celle du chainer, mais sachez que certaines fonctions sont légèrement différentes.
Un cadre d'apprentissage en profondeur pour les images dirigé par Facebook et l'Université de New York. Il semble qu'elle ait été fourchue par le chainer. L'image de torch7 est également portée par Facebook et l'Université de New York.
Puisque torch7 est lua et peu abstraite, la fonction est exposée. pytorch est assez abstrait et réduit la quantité de codage.
Développeur à partir de mars 2017
Adam Paszke http://apaszke.github.io/posts.html Soumith Chintala http://soumith.ch/
En mars 2017. Je ne sais pas si l'excitation peut être mesurée avec le graphique git, mais j'étais curieux, alors je l'ai comparé. chainer pytorch keras tensorflow torch7 caffe caffe2 theano deeplearning4j
cntk
Comme prévu, caffe et torch7 n'ont pas beaucoup été mis à jour. Étonnamment cntk ...
2018/6 Il a été déclaré dans le forum que cntk, tensorflow, theano et mxnet étaient pour la plupart enveloppés dans des keras, mais pytorch est un cadre de haut niveau, il y a donc une haute théorie selon laquelle il ne sera pas enveloppé. J'ai été surpris quand l'auteur de keras a écrit "Voulez-vous envelopper cntk avec keras?" Dans le numéro de cntk, et que la personne cntk a trouvé un commentaire "like".
Le nombre de résultats dans la recherche git de diverses API. Pour ton information.
conda est recommandé. J'essaye de garder pip et numpy à jour. officiel http://pytorch.org/
conda install pytorch torchvision -c soumith
win
conda install -c peterjc123 pytorch
Si vous le souhaitez, vous pouvez étendre PyTorch en réutilisant des packages Python tels que numpy, scipy et Cython.
paquet | La description |
---|---|
torch | Bibliothèque Tensor avec un support GPU solide comme NumPy |
torch.autograd | Bibliothèque de différenciation automatique sur bande qui prend en charge toutes les opérations de tenseurs différentiables dans la torche |
torch.nn | Bibliothèque de réseaux neuronaux intégrée avec des fonctionnalités d'auto-différenciation conçues pour une flexibilité maximale |
torch.optim | Torche utilisant des techniques d'optimisation standard telles que SGD, RMSProp, LBFGS, Adam.Package optimisé pour une utilisation avec nn |
torch.multiprocessing | Permet le partage magique de la mémoire des tenseurs de torche tout au long du processus, plutôt que le multi-traitement Python. Utile pour le chargement de données et la formation au hogwald. |
torch.utils | DataLoader, Trainer et autres fonctions utilitaires |
torch.legacy(.nn/.optim) | Code hérité porté depuis la torche pour des raisons de compatibilité descendante |
PyTorch utilise souvent les fonctions ici. Je l'expliquerai plus tard.
requires_grad: vous pouvez spécifier si vous souhaitez calculer le dégradé. vers l'arrière: vous pouvez calculer le dégradé. nn.Module: hérite de ceci pour définir une classe de réseau. DataSet et DataLoader: Utilisés pour charger des données lot par lot. datasets.ImageFolder: Les images peuvent être facilement chargées en les organisant séparément pour chaque dossier. Après cela, vous pouvez le mettre dans DataLoader et le traiter séparément pour chaque lot. transforme: les données d'image peuvent être prétraitées. make_grid: Lorsque l'image est affichée, elle est affichée côte à côte dans la grille.
Si vous utilisez numpy, utilisez Tensors.
PyTorch fournit des Tensors qui résident sur le CPU ou le GPU, accélérant d'énormes quantités de calcul. Nous proposons une variété de routines de tenseurs pour accélérer et adapter vos besoins de calcul scientifique, y compris le découpage, l'indexation, les opérations mathématiques, l'algèbre linéaire et la réduction.
PyTorch a un moyen unique de créer un réseau neuronal qui est lu à l'aide d'un magnétophone.
La plupart des frameworks tels que TensorFlow, Theano, Caffe, CNTK, etc. Je regarde le monde de manière statique. Vous devez créer un réseau de neurones et réutiliser la même structure encore et encore. Changer le comportement d'un réseau signifie que vous devez recommencer depuis le début.
PyTorch vous permet de changer la façon dont votre réseau se comporte de manière arbitraire sans décalage ni surcharge en utilisant une technique appelée différenciation automatique en mode inverse. Notre inspiration vient de plusieurs articles de recherche sur ce sujet, ainsi que d'études actuelles et passées telles que autograd, autograd et chainer.
Puisqu'il est dérivé du chainer, il prétend être un réseau dynamique comme le prétend le chainer. Cela signifie-t-il que vous pouvez changer de réseau en cours de route?
Vérifiez la version.
python
import torch
print(torch.__version__)
Obtenez des données
train_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=args.batch_size, shuffle=True, **kwargs)
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=False, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=args.batch_size, shuffle=True, **kwargs)
Définition du modèle
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 = F.dropout(x, training=self.training)
x = F.relu(self.fc2(x))
return F.log_softmax(x)
Génération de modèle, réglage de la fonction d'optimisation
model = Net()
if args.cuda:
model.cuda()
optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum)
Apprentissage
def train(epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
if args.cuda:
data, target = data.cuda(), target.cuda()
data, target = Variable(data), Variable(target)
optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
if batch_idx % args.log_interval == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.data[0]))
Exécution de l'apprentissage
for epoch in range(1, args.epochs + 1):
train(epoch)
test(epoch)
C'est juste une chaîne, et il n'y a aucun coût d'étude.
https://github.com/pytorch/tutorials/blob/master/Introduction%20to%20PyTorch%20for%20former%20Torchies.ipynb
import torch
a = torch.FloatTensor(10, 20)
# creates tensor of size (10 x 20) with uninitialized memory
a = torch.randn(10, 20)
# initializes a tensor randomized with a normal distribution with mean=0, var=1
a.size()
Puisque torch.Size est en fait un taple, il prend en charge la même opération.
L'ajout de _ à la fin modifie le contenu de la variable d'origine.
a.fill_(3.5)
# a has now been filled with the value 3.5
b = a.add(4.0)
# a is still filled with 3.5
# new tensor b is returned with values 3.5 + 4.0 = 7.5
b = a[0,3] #1ère ligne et 4e colonne
b = a[:,3:5] #4e et 5e colonnes
Toutes les fonctions ne sont pas camelCase. Par exemple, indexAdd est index_add_.
x = torch.ones(5, 5)
print(x)
z = torch.Tensor(5, 2)
z[:,0] = 10
z[:,1] = 100
print(z)
x.index_add_(1, torch.LongTensor([4,0]), z)
print(x)
Conversion du tenseur de torche en tableau numpy
a = torch.ones(5)
b = a.numpy()
a.add_(1)
print(a)
print(b)
Convertir un tableau numpy en torche Tensor
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
print(b)
# let us run this cell only if CUDA is available
if torch.cuda.is_available():
# creates a LongTensor and transfers it
# to GPU as torch.cuda.LongTensor
a = torch.LongTensor(10).fill_(3).cuda()
print(type(a))
b = a.cpu()
# transfers it to CPU, back to
# being a torch.LongTensor
Autograd Autograd présente la classe Variable. Ceci est un wrapper Tensor.
from torch.autograd import Variable
x = Variable(torch.ones(2, 2), requires_grad = True)
x.data
x.grad
y = x + 2
z = y * y * 3
out = z.mean()
out.backward()
import torch.nn as nn
L'état n'est pas conservé dans le module, mais dans le graphe du réseau
Créer une classe
import torch.nn.functional as F
class MNISTConvNet(nn.Module):
def __init__(self):
super(MNISTConvNet, self).__init__()
self.conv1 = nn.Conv2d(1, 10, 5)
self.pool1 = nn.MaxPool2d(2,2)
self.conv2 = nn.Conv2d(10, 20, 5)
self.pool2 = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(320, 50)
self.fc2 = nn.Linear(50, 10)
def forward(self, input):
x = self.pool1(F.relu(self.conv1(input)))
x = self.pool2(F.relu(self.conv2(x)))
x = x.view(x.size(0), -1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return x
Créer une instance de la classe
net = MNISTConvNet()
print(net)
input = Variable(torch.randn(1, 1, 28, 28))
out = net(input)
print(out.size())
# define a dummy target label
target = Variable(torch.LongTensor([3]))
# create a loss function
loss_fn = nn.CrossEntropyLoss() # LogSoftmax + ClassNLL Loss
err = loss_fn(out, target)
print(err)
err.backward()
La sortie de ConvNet est une variable. Son utilisation pour calculer la perte entraîne err, qui est également une variable. Inversement, l'appel de err propage la gradation de l'ensemble du ConvNet aux poids.
Accédez aux pondérations et aux dégradés des couches individuelles.
print(net.conv1.weight.grad.size())
print(net.conv1.weight.data.norm()) # norm of the weight
print(net.conv1.weight.grad.data.norm()) # norm of the gradients
Nous avons examiné les poids et les gradients. Mais qu'en est-il de la vérification / modification de la sortie de la couche et de grad_output? Présentation des crochets à cet effet.
Vous pouvez enregistrer des fonctions dans des modules ou des variables. Le crochet peut être un crochet avant ou un crochet arrière. Les hooks de renvoi sont exécutés lorsqu'un appel de renvoi est effectué. Le crochet arrière fonctionne dans la phase arrière. Regardons un exemple.
# We register a forward hook on conv2 and print some information
def printnorm(self, input, output):
# input is a tuple of packed inputs
# output is a Variable. output.data is the Tensor we are interested
print('Inside ' + self.__class__.__name__ + ' forward')
print('')
print('input: ', type(input))
print('input[0]: ', type(input[0]))
print('output: ', type(output))
print('')
print('input size:', input[0].size())
print('output size:', output.data.size())
print('output norm:', output.data.norm())
net.conv2.register_forward_hook(printnorm)
out = net(input)
# We register a backward hook on conv2 and print some information
def printgradnorm(self, grad_input, grad_output):
print('Inside ' + self.__class__.__name__ + ' backward')
print('Inside class:' + self.__class__.__name__)
print('')
print('grad_input: ', type(grad_input))
print('grad_input[0]: ', type(grad_input[0]))
print('grad_output: ', type(grad_output))
print('grad_output[0]: ', type(grad_output[0]))
print('')
print('grad_input size:', grad_input[0].size())
print('grad_output size:', grad_output[0].size())
print('grad_input norm:', grad_input[0].data.norm())
net.conv2.register_backward_hook(printgradnorm)
out = net(input)
err = loss_fn(out, target)
err.backward()
Ensuite, voyons comment créer un réseau récursif avec PyTorch. L'état du réseau est conservé dans des couches et non dans des graphiques, vous pouvez donc simplement créer nn.Linear et le réutiliser encore et encore pour la récurrence.
class RNN(nn.Module):
# you can also accept arguments in your model constructor
def __init__(self, data_size, hidden_size, output_size):
super(RNN, self).__init__()
self.hidden_size = hidden_size
input_size = data_size + hidden_size
self.i2h = nn.Linear(input_size, hidden_size)
self.h2o = nn.Linear(hidden_size, output_size)
def forward(self, data, last_hidden):
input = torch.cat((data, last_hidden), 1)
hidden = self.i2h(input)
output = self.h2o(hidden)
return hidden, output
rnn = RNN(50, 20, 10)
loss_fn = nn.MSELoss()
batch_size = 10
TIMESTEPS = 5
# Create some fake data
batch = Variable(torch.randn(batch_size, 50))
hidden = Variable(torch.zeros(batch_size, 20))
target = Variable(torch.zeros(batch_size, 10))
loss = 0
for t in range(TIMESTEPS):
# yes! you can reuse the same network several times,
# sum up the losses, and call backward!
hidden, output = rnn(batch, hidden)
loss += loss_fn(output, target)
loss.backward()
Par défaut, PyTorch a une intégration transparente de CuDNN pour les ConvNets et les réseaux récurrents.
Le traitement parallèle des données consiste à diviser un mini-lot d'échantillons en plusieurs mini-lots plus petits et à effectuer les calculs pour chaque mini-lot en parallèle. Le traitement parallèle des données est implémenté à l'aide de torch.nn.DataParallel. Vous pouvez envelopper le module dans un DataParallel et le paralléliser sur plusieurs GPU dans une dimension de lot.
Parallèle de données
class DataParallelModel(nn.Module):
def __init__(self):
super().__init__()
self.block1=nn.Linear(10, 20)
# wrap block2 in DataParallel
self.block2=nn.Linear(20, 20)
self.block2 = nn.DataParallel(self.block2)
self.block3=nn.Linear(20, 20)
def forward(self, x):
x = self.block1(x)
x = self.block2(x)
x = self.block3(x)
return x
Pas besoin de changer de code en mode CPU.
En général, la primitive nn.parallel de pytorch peut être utilisée indépendamment. Implémentation d'une simple primitive de type MPI. répliquer: répliquer le module sur plusieurs appareils scatter: distribue l'entrée à la première dimension rassembler: collecter et concaténer les entrées de première dimension parallel_apply: applique un ensemble d'entrées déjà distribué à un ensemble de modèles déjà distribué.
Pour plus de clarté, voici la fonction data_parallel construite à partir de ces ensembles.
def data_parallel(module, input, device_ids, output_device=None):
if not device_ids:
return module(input)
if output_device is None:
output_device = device_ids[0]
replicas = nn.parallel.replicate(module, device_ids)
inputs = nn.parallel.scatter(input, device_ids)
replicas = replicas[:len(inputs)]
outputs = nn.parallel.parallel_apply(replicas, inputs)
return nn.parallel.gather(outputs, output_device)
Regardons un petit exemple d'implémentation d'un réseau, dont une partie se trouve sur le CPU et sur le GPU.
class DistributedModel(nn.Module):
def __init__(self):
super().__init__(
embedding=nn.Embedding(1000, 10),
rnn=nn.Linear(10, 10).cuda(0),
)
def forward(self, x):
# Compute embedding on CPU
x = self.embedding(x)
# Transfer to GPU
x = x.cuda(0)
# Compute RNN on GPU
x = self.rnn(x)
return x
Il existe un exemple de code tel qu'un système de génération d'images. Je veux dire, il y en avait plus du tout. Il y en avait tellement que je n'avais pas besoin d'écrire ici, donc si vous voulez l'utiliser, veuillez rechercher avec git. Le monde était vaste.
pix2pix https://github.com/mrzhu-cool/pix2pix-pytorch densenet https://github.com/bamos/densenet.pytorch animeGAN https://github.com/jayleicn/animeGAN yolo2 https://github.com/longcw/yolo2-pytorch gan https://github.com/devnag/pytorch-generative-adversarial-networks Liste des modèles générés https://github.com/wiseodd/generative-models functional model https://github.com/szagoruyko/functional-zoo Liste d'échantillons simple https://github.com/pytorch/examples/
https://github.com/pytorch/tutorials/blob/master/Deep%20Learning%20with%20PyTorch.ipynb
Installez les packages torch et torchvision.
conda install torchvision -c soumith
or
pip install torchvision
Les tensols sont similaires au ndarray de numpy, mais les tenseurs peuvent également être utilisés sur les GPU.
from __future__ import print_function
import torch
x = torch.Tensor(5, 3) # construct a 5x3 matrix, uninitialized
x = torch.rand(5, 3) # construct a randomly initialized matrix
x.size()
y = torch.rand(5, 3)
# addition: syntax 1
x + y
Conversion du tenseur de torche au tableau numpy
a = torch.ones(5)
b = a.numpy()
a.add_(1)
print(a)
print(b) # see how the numpy array changed in value
Convertir un tableau numpy en torche Tensor
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
print(b) # see how changing the np array changed the torch Tensor automatically
~ Rédaction ~
Type de données | dtype | Tenseur du processeur | Tenseur GPU |
---|---|---|---|
Virgule flottante 64 bits | torch.float64 ou torche.double | torch.DoubleTensor | torch.cuda.DoubleTensor |
Virgule flottante 32 bits | torch.float32 ou torche.float | torch.FloatTensor | torch.cuda.FloatTensor |
Virgule flottante 16 bits | torch.float16 ou torche.half | torch.HalfTensor | torch.cuda.HalfTensor |
Entier 8 bits (non signé) | torch.uint8 | torch.ByteTensor | torch.cuda.ByteTensor |
Entier 8 bits (signé) | torch.int8 | torch.CharTensor | torch.cuda.CharTensor |
Entier 16 bits (signé) | torch.int16 ou torche.short | torch.ShortTensor | torch.cuda.ShortTensor |
Entier 32 bits (signé) | torch.int32 ou torche.int | torch.IntTensor | torch.cuda.IntTensor |
Entier 64 bits (signé) | torch.int64 ou torche.long | torch.LongTensor | torch.cuda.LongTensor |
torch7 Exemple d'apprentissage de mnist https://github.com/torch/demos/blob/master/train-a-digit-classifier/train-on-mnist.lua
Définition du modèle
-- define model to train
model = nn.Sequential()
model:add(nn.Reshape(1024))
model:add(nn.Linear(1024,#classes))
model:add(nn.LogSoftMax())
L'acquisition des données. Générez un modèle.
criterion = nn.ClassNLLCriterion()
trainData = mnist.loadTrainSet(nbTrainingPatches, geometry)
trainData:normalizeGlobal(mean, std)
Définir l'apprentissage
-- training function
function train(dataset)
-- epoch tracker
epoch = epoch or 1
~ Abréviation ~
gradParameters:zero()
-- evaluate function for complete mini batch
local outputs = model:forward(inputs)
local f = criterion:forward(outputs, targets)
-- estimate df/dW
local df_do = criterion:backward(outputs, targets)
model:backward(inputs, df_do)
~ Abréviation ~
end
Effectuer l'apprentissage
while true do
-- train/test
train(trainData)
~ Abréviation ~
end
torchnet https://github.com/torchnet/torchnet/blob/master/example/mnist.lua
mnist.lua
-- load torchnet:
local tnt = require 'torchnet'
-- use GPU or not:
~ Abréviation ~
-- function that sets of dataset iterator:
local function getIterator(mode)
~ Abréviation ~
end
-- set up logistic regressor:
local net = nn.Sequential():add(nn.Linear(784,10))
local criterion = nn.CrossEntropyCriterion()
-- set up training engine:
local engine = tnt.SGDEngine()
~ Abréviation ~
end
-- set up GPU training:
~ Abréviation ~
-- train the model:
engine:train{
network = net,
iterator = getIterator('train'),
criterion = criterion,
lr = 0.2,
maxepoch = 5,
}
-- measure test loss and error:
~ Abréviation ~
print(string.format('test loss: %2.4f; test error: %2.4f',
meter:value(), clerr:value{k = 1}))
Je me demandais quoi faire quand je voulais transférer mon apprentissage avec une torche ou un pytorch. Je veux essayer un jour.
http://toxweblog.toxbe.com/2016/12/22/chainer-alexnet-fine-tuning/
conversion
#Chemin pour enregistrer le modèle caffe à charger et le fichier pkl
loadpath = "bvlc_alexnet.caffemodel"
savepath = "./chainermodels/alexnet.pkl"
from chainer.links.caffe import CaffeFunction
alexnet = CaffeFunction(loadpath)
import _pickle as pickle
pickle.dump(alexnet, open(savepath, 'wb'))
Lis
if ext == ".caffemodel":
print('Loading Caffe model file %s...' % args.model, file=sys.stderr)
func = caffe.CaffeFunction(args.model)
print('Loaded', file=sys.stderr)
elif ext == ".pkl":
print('Loading Caffe model file %s...' % args.model, file=sys.stderr)
func = pickle.load(open(args.model, 'rb'))
print('Loaded', file=sys.stderr)
def predict(x):
y, = func(inputs={'data': x}, outputs=['fc8'], train=False)
return F.softmax(y)
Enregistrer le modèle Keras
hogehoge_model.save_weights('model.h5', overwrite=True)
Chargement du modèle Keras
hogehoge_model.load_weights('model.h5')
Enregistrer sous pkl
import _pickle as pickle
pickle.dump(hogehoge_model, open('model.pkl', 'wb'))
Lire pkl
hogehoge_model = pickle.load(open('model.pkl', 'rb'))
https://github.com/ethereon/caffe-tensorflow
def convert(def_path, caffemodel_path, data_output_path, code_output_path, phase):
try:
transformer = TensorFlowTransformer(def_path, caffemodel_path, phase=phase)
print_stderr('Converting data...')
if caffemodel_path is not None:
data = transformer.transform_data()
print_stderr('Saving data...')
with open(data_output_path, 'wb') as data_out:
np.save(data_out, data)
if code_output_path:
print_stderr('Saving source...')
with open(code_output_path, 'wb') as src_out:
src_out.write(transformer.transform_source())
print_stderr('Done.')
except KaffeError as err:
fatal_error('Error encountered: {}'.format(err))
https://github.com/Cadene/tensorflow-model-zoo.torch
python3 inceptionv4/tensorflow_dump.py
th inceptionv4/torch_load.lua
or
python3 inceptionv4/pytorch_load.py
torch-hdf5 https://github.com/deepmind/torch-hdf5 Ce package vous permet de lire et d'écrire des données Torch vers et depuis des fichiers HDF5. Le format est rapide et flexible et est pris en charge par une large gamme d'autres logiciels, notamment MATLAB, Python et R.
Comment bouger https://github.com/deepmind/torch-hdf5/blob/master/doc/usage.md
Pour ubuntu14 et supérieur
python
sudo apt-get install libhdf5-serial-dev hdf5-tools
git clone https://github.com/deepmind/torch-hdf5
cd torch-hdf5
luarocks make hdf5-0-0.rockspec LIBHDF5_LIBDIR="/usr/lib/x86_64-linux-gnu/"
J'ai changé le code pour prendre un peu le benchmark et je l'ai déplacé.
python
require 'hdf5'
print("Size\t\t", "torch.save\t\t", "hdf5\t")
n = 1
local size = math.pow(2, n)
local data = torch.rand(size)
local t = torch.tic()
torch.save("out.t7", data)
local normalTime = torch.toc(t)
t = torch.tic()
local hdf5file = hdf5.open("out.h5", 'w')
hdf5file["foo"] = data
hdf5file:close()
local hdf5time = torch.toc(t)
print(n, "\t", normalTime,"\t", hdf5time)
Jenkins pour Pytorch https://github.com/pytorch/builder
QA
Essayez cuda (2) pour cuda () ou spécifiez-le avec torch.nn.DataParallel J'ai fait diverses choses, mais à la fin je me suis installé là-dessus. À propos, puisqu'il s'agit d'un partage GPU, il se comporte de la même manière dans d'autres bibliothèques telles que tensorflow. Une image qui masque le GPU car elle occupe une autre mémoire GPU sans autorisation telle que tensorflow.
CUDA_VISIBLE_DEVICES=2 python main.py
http://www.acceleware.com/blog/cudavisibledevices-masking-gpus http://qiita.com/kikusumk3/items/907565559739376076b9 http://qiita.com/ballforest/items/3f21bcf34cba8f048f1e S'il s'agit de 8 gpu ou plus, cela semble inutile à moins qu'il ne soit groupé. http://qiita.com/YusukeSuzuki@github/items/aa5fcc4b4d06c116c3e8
Recommended Posts