基於飛槳復現 SRGAN 模型,對影像進行超解析度重構

飛槳PaddlePaddle發表於2020-10-30

SRGAN是一種用於影像超解析度(SR)的生成對抗網路(GAN),能夠推斷四倍放大因子的照片般逼真的自然影像。

文章來源:

2017 IEEE Conference on Computer Vision and Pattern Recognition (CVPR)

下載連結:

https://arxiv.org/pdf/1609.04802.pdf

AI Studio提供線上程式設計環境、免費GPU算力,給我這次論文復現提供了很大的幫助。飛槳豐富的API介面使我在進行復現時,減少了很多障礙,能夠比較輕鬆的完成模型搭建及訓練。如果你對本專案感興趣,也可以自己來嘗試一下,整個專案包括資料集與相關程式碼已公開在AI Studio上,歡迎小夥伴們Fork。
https://aistudio.baidu.com/aistudio/projectdetail/843989

前言

在GAN領域中,超解析度復原一直是計算機視覺領域一個十分熱門的研究方向,在商業上也有著很大的用武之地,隨著2014年lan J. Goodflew那篇驚世駭俗的GAN發表出來,GAN伴隨著CNN一起,可謂是乘風破浪,衍生出來琳琅滿目的各種應用。

SRGAN,2017年CVPR中備受矚目的超解析度論文,把超解析度的效果帶到了一個新的高度,而2017年超分大賽NTIRE的冠軍EDSR也是基於SRGAN的變體。對於此篇論文,據說這是第一篇將GAN網路應用到超分領域的論文,很多涉及到影像超解析度重構的技術部落格都有提到過它。其實它的難度並不大,但它的重構思想從學習的角度來說,是能夠讓我們有很大的收穫的。

影像的超解析度重構技術(Super-Resolution)指的是將給定的低解析度影像透過演算法恢復成相應的高解析度影像,其主要分為兩個大類:一類是使用單張低解析度影像進行高解析度影像的重建,一類是使用同一場景的多張低解析度影像進行高解析度影像的重建。此篇文章使用的是基於深度學習中的GAN網路對單張影像進行操作的超解析度重構方法,超分辨重構和去噪、去網格、去模糊等問題是類似的。對於一張低分辨影像,可能會有多張高分辨影像與之對應,因此通常在求解高解析度影像時會加一個先驗資訊進行規範化約束。在傳統的方法中,通常會透過加入一些先驗資訊來恢復高解析度影像,如,插值法、稀疏學習、還有基於迴歸方法的隨機森林等。而基於深度學習的SR方法,則是透過神經網路直接進行從低分辨影像到高分辨影像的端到端的學習。

SRGAN不同於普通的GAN是透過噪聲來生成一個真實圖片,SRGAN的目的在於將一個低解析度的圖片轉化為一個高解析度的圖片。利用感知損失(perceptual loss)和對抗損失(adversarial loss)來提升恢復出的圖片的真實感。感知損失是利用卷積神經網路(VGG19)提取出的特徵,透過比較生成圖片與目標圖片之間的特徵差別,使生成圖片和目標圖片在語義和風格上更相似。通俗來講,SRGAN所要完成的工作就是:透過G網路使低解析度的影像重建出一張高解析度的影像,再由D網路判斷拿到的生成圖與原圖之間的差別,當G網路的生成圖能夠很好的騙過D網路,使之相信此生成圖即為原資料集中的影像之一,那麼超解析度重構的網路就實現了。
基於飛槳復現 SRGAN 模型,對影像進行超解析度重構
作者認為,這篇文章之前,主要重建工作都集中在最小化均方重建誤差上,這篇文章是生成式對抗網路第一次應用於四倍下采樣影像的超分辨重建工作。由此得到的估計值具有較高的峰值訊雜比,但它們通常缺少高頻細節,並且在感覺上不令人滿意,因為它們無法匹配在更高解析度下預期的保真度。

為了達到能夠在四倍放大因子下推斷照片真實自然影像的目的,作者提出了一個由對抗性損失和內容損失組成的感知損失函式,該網路使用經過訓練的VGG19網路來區分超解析度影像和原始圖片的真實感,此外,在畫素空間中,又使用了一個由感知相似度驅動的內容丟失,而不是畫素空間中的相似性。作者的深度殘差網路能夠在公共基準上從大量減少取樣的影像中恢復照片真實感紋理。用SRGAN獲得的MOS分數比用任何最先進的方法得到的結果更接近原始高解析度影像。

網路結構

SRGAN網路結構如下圖(SRGAN還是用SRRESNET來進行超分工作 但增加了一個對抗網路來判斷生成的圖片是原圖還是超分出來的圖):

生成器網路的體系結構,每個卷積層對應的核心大小(k)、特徵對映數(n)和步長(s)。

基於飛槳復現 SRGAN 模型,對影像進行超解析度重構

在生成網路中,輸入是一個低解析度的影像,先進行卷積、relu,又為了能夠更好的網路架構和提取特徵,還引入了殘差模組,最後再透過特徵提取、特徵重構,得到輸出結果。
def SRGAN_g(t_image):      # Input-Conv-Relu      n = fluid.layers.conv2d(input=t_image, num_filters=64, filter_size=3, stride=1, padding='SAME', name='n64s1/c', data_format='NCHW')      n = fluid.layers.batch_norm(n, momentum=0.99, epsilon=0.001)      n = fluid.layers.relu(n, name=None)      temp = n      # B residual blocks      # Conv-BN-Relu-Conv-BN-Elementwise_add      for i in range(16):          nn = fluid.layers.conv2d(input=n, num_filters=64, filter_size=3, stride=1, padding='SAME', name='n64s1/c1/%s' % i, data_format='NCHW')          nn = fluid.layers.batch_norm(nn, momentum=0.99, epsilon=0.001, name='n64s1/b1/%s' % i)          nn = fluid.layers.relu(nn, name=None)          log = 'conv%2d' % (i+1)          nn = fluid.layers.conv2d(input=nn, num_filters=64, filter_size=3, stride=1, padding='SAME', name='n64s1/c2/%s' % i, data_format='NCHW')          nn = fluid.layers.batch_norm(nn, momentum=0.99, epsilon=0.001, name='n64s1/b2/%s' % i)          nn = fluid.layers.elementwise_add(n, nn, act=None, name='b_residual_add/%s' % i)          n = nn      n = fluid.layers.conv2d(input=n, num_filters=64, filter_size=3, stride=1, padding='SAME', name='n64s1/c/m', data_format='NCHW')      n = fluid.layers.batch_norm(n, momentum=0.99, epsilon=0.001, name='n64s1/b2/%s' % i)      n = fluid.layers.elementwise_add(n, temp, act=None, name='add3')      # B residual blacks end      # Conv-Pixel_shuffle-Conv-Pixel_shuffle-Conv      n = fluid.layers.conv2d(input=n, num_filters=256, filter_size=3, stride=1, padding='SAME', name='n256s1/1', data_format='NCHW')      n = fluid.layers.pixel_shuffle(n, upscale_factor=2)      n = fluid.layers.relu(n, name=None)      n = fluid.layers.conv2d(input=n, num_filters=256, filter_size=3, stride=1, padding='SAME', name='n256s1/2', data_format='NCHW')      n = fluid.layers.pixel_shuffle(n, upscale_factor=2)      n = fluid.layers.relu(n, name=None)      n = fluid.layers.conv2d(input=n, num_filters=3, filter_size=1, stride=1, padding='SAME', name='out', data_format='NCHW')      n = fluid.layers.tanh(n, name=None)      return n

鑑別器網路的體系結構,每個卷積層對應的核心大小(k)、特徵對映數(n)和步長(s)。
基於飛槳復現 SRGAN 模型,對影像進行超解析度重構

在鑑別網路中,都是些常規的 Cnov、BN、Leaky_Relu、fc,為了對生成網路生成的影像資料進行判斷,判斷其是否是真實的訓練資料中的資料。
def SRGAN_d(input_images):     # Conv-Leaky_Relu      net_h0 = fluid.layers.conv2d(input=input_images, num_filters=64, filter_size=4, stride=2, padding='SAME', name='h0/c', data_format='NCHW')     net_h0 = fluid.layers.leaky_relu(net_h0, alpha=0.2, name=None)     # h1 Cnov-BN-Leaky_Relu     net_h1 = fluid.layers.conv2d(input=net_h0, num_filters=128, filter_size=4, stride=2, padding='SAME', name='h1/c', data_format='NCHW')     net_h1 = fluid.layers.batch_norm(net_h1, momentum=0.99, epsilon=0.001, name='h1/bn')       net_h1 = fluid.layers.leaky_relu(net_h1, alpha=0.2, name=None)     # h2 Cnov-BN-Leaky_Relu     net_h2 = fluid.layers.conv2d(input=net_h1, num_filters=256, filter_size=4, stride=2, padding='SAME', name='h2/c', data_format='NCHW')     net_h2 = fluid.layers.batch_norm(net_h2, momentum=0.99, epsilon=0.001, name='h2/bn')     net_h2 = fluid.layers.leaky_relu(net_h2, alpha=0.2, name=None)     # h3 Cnov-BN-Leaky_Relu     net_h3 = fluid.layers.conv2d(input=net_h2, num_filters=512, filter_size=4, stride=2, padding='SAME', name='h3/c', data_format='NCHW')     net_h3 = fluid.layers.batch_norm(net_h3, momentum=0.99, epsilon=0.001, name='h3/bn')     net_h3 = fluid.layers.leaky_relu(net_h3, alpha=0.2, name=None)     # h4 Cnov-BN-Leaky_Relu     net_h4 = fluid.layers.conv2d(input=net_h3, num_filters=1024, filter_size=4, stride=2, padding='SAME', name='h4/c', data_format='NCHW')     net_h4 = fluid.layers.batch_norm(net_h4, momentum=0.99, epsilon=0.001, name='h4/bn')     net_h4 = fluid.layers.leaky_relu(net_h4, alpha=0.2, name=None)     # h5 Cnov-BN-Leaky_Relu     net_h5 = fluid.layers.conv2d(input=net_h4, num_filters=2048, filter_size=4, stride=2, padding='SAME', name='h5/c', data_format='NCHW')     net_h5 = fluid.layers.batch_norm(net_h5, momentum=0.99, epsilon=0.001, name='h5/bn')     net_h5 = fluid.layers.leaky_relu(net_h5, alpha=0.2, name=None)     # h6 Cnov-BN-Leaky_Relu     net_h6 = fluid.layers.conv2d(input=net_h5, num_filters=1024, filter_size=4, stride=2, padding='SAME', name='h6/c', data_format='NCHW')     net_h6 = fluid.layers.batch_norm(net_h6, momentum=0.99, epsilon=0.001, name='h6/bn')     net_h6 = fluid.layers.leaky_relu(net_h6, alpha=0.2, name=None)     # h7 Cnov-BN-Leaky_Relu     net_h7 = fluid.layers.conv2d(input=net_h6, num_filters=512, filter_size=4, stride=2, padding='SAME', name='h7/c', data_format='NCHW')     net_h7 = fluid.layers.batch_norm(net_h7, momentum=0.99, epsilon=0.001, name='h7/bn')     net_h7 = fluid.layers.leaky_relu(net_h7, alpha=0.2, name=None)     #修改原論文網路     net = fluid.layers.conv2d(input=net_h7, num_filters=128, filter_size=1, stride=1, padding='SAME', name='res/c', data_format='NCHW')     net = fluid.layers.batch_norm(net, momentum=0.99, epsilon=0.001, name='res/bn')     net = fluid.layers.leaky_relu(net, alpha=0.2, name=None)     net = fluid.layers.conv2d(input=net_h7, num_filters=128, filter_size=3, stride=1, padding='SAME', name='res/c2', data_format='NCHW')     net = fluid.layers.batch_norm(net, momentum=0.99, epsilon=0.001, name='res/bn2')     net = fluid.layers.leaky_relu(net, alpha=0.2, name=None)     net = fluid.layers.conv2d(input=net_h7, num_filters=512, filter_size=3, stride=1, padding='SAME', name='res/c3', data_format='NCHW')     net = fluid.layers.batch_norm(net, momentum=0.99, epsilon=0.001, name='res/bn3')     net = fluid.layers.leaky_relu(net, alpha=0.2, name=None)     net_h8 = fluid.layers.elementwise_add(net_h7, net, act=None, name='res/add')     net_h8 = fluid.layers.leaky_relu(net_h8, alpha=0.2, name=None)     #net_ho = fluid.layers.flatten(net_h8, axis=0, name='ho/flatten')     net_ho = fluid.layers.fc(input=net_h8, size=1024, name='ho/fc')     net_ho = fluid.layers.leaky_relu(net_ho, alpha=0.2, name=None)     net_ho = fluid.layers.fc(input=net_h8, size=1, name='ho/fc2')     # return     # logits = net_ho     net_ho = fluid.layers.sigmoid(net_ho, name=None)     return net_ho # , logits
為了儘可能地訓練出模型的效果,在本專案中直接使用了飛槳的 VGG19 網路實現程式碼,並使用飛槳官方提供的在ImageNet上預訓練好的VGG模型,該模型在ImageNet-2012驗證集合上的top-1和top-5精度分別為72.56%、90.93%,效能優越。在本專案的訓練過程中能夠起到精準提取影像特徵資訊的作用,縮小生成圖與原圖的差距,提升生成網路的生成影像效果。
def conv_block(input, num_filter, groups, name=None):     conv = input     for i in range(groups):         conv = fluid.layers.conv2d(             input=conv,             num_filters=num_filter,             filter_size=3,             stride=1,             padding=1,             act='relu',             param_attr=fluid.param_attr.ParamAttr(                 name=name + str(i + 1) + "_weights"),             bias_attr=False)     return fluid.layers.pool2d(         input=conv, pool_size=2, pool_type='max', pool_stride=2) def vgg19(input, class_dim=1000):     layers = 19     vgg_spec = {         11: ([1, 1, 2, 2, 2]),         13: ([2, 2, 2, 2, 2]),         16: ([2, 2, 3, 3, 3]),         19: ([2, 2, 4, 4, 4])     }     assert layers in vgg_spec.keys(), \         "supported layers are {} but input layer is {}".format(vgg_spec.keys(), layers)     nums = vgg_spec[layers]     conv1 = conv_block(input, 64, nums[0], name="vgg19_conv1_")     conv2 = conv_block(conv1, 128, nums[1], name="vgg19_conv2_")     conv3 = conv_block(conv2, 256, nums[2], name="vgg19_conv3_")     conv4 = conv_block(conv3, 512, nums[3], name="vgg19_conv4_")     conv5 = conv_block(conv4, 512, nums[4], name="vgg19_conv5_")     fc_dim = 4096     fc_name = ["fc6", "fc7", "fc8"]     fc1 = fluid.layers.fc(         input=conv5,         size=fc_dim,         act='relu',         param_attr=fluid.param_attr.ParamAttr(             name=fc_name[0] + "_weights"),         bias_attr=fluid.param_attr.ParamAttr(name=fc_name[0] + "_offset"))     fc1 = fluid.layers.dropout(x=fc1, dropout_prob=0.5)     fc2 = fluid.layers.fc(         input=fc1,         size=fc_dim,         act='relu',         param_attr=fluid.param_attr.ParamAttr(             name=fc_name[1] + "_weights"),         bias_attr=fluid.param_attr.ParamAttr(name=fc_name[1] + "_offset"))     fc2 = fluid.layers.dropout(x=fc2, dropout_prob=0.5)     out = fluid.layers.fc(         input=fc2,         size=class_dim,         param_attr=fluid.param_attr.ParamAttr(             name=fc_name[2] + "_weights"),         bias_attr=fluid.param_attr.ParamAttr(name=fc_name[2] + "_offset"))     return out, conv5

損失函式

論文中還給出了生成器和判別器的損失函式的形式:

生成器的損失函式為:

基於飛槳復現 SRGAN 模型,對影像進行超解析度重構
其中,基於飛槳復現 SRGAN 模型,對影像進行超解析度重構為本文所提出的感知損失函式,基於飛槳復現 SRGAN 模型,對影像進行超解析度重構

內容損失
基於飛槳復現 SRGAN 模型,對影像進行超解析度重構
訓練網路時使用均方差損失可以獲得較高的峰值訊雜比,一般的超解析度重建方法中,內容損失都選擇使用生成影像和目標影像的均方差損失(MSELoss),但是使用均方差損失恢復的影像會丟失很多高頻細節。因此,本文先將生成影像和目標影像分別輸入到VGG網路中,然後對他們經過VGG後得到的feature map求歐式距離,並將其作為VGG loss。

對抗損失
基於飛槳復現 SRGAN 模型,對影像進行超解析度重構
為了避免當判別器訓練較好時生成器出現梯度消失,本文將生成器的損失函式進行了修改。
  • 判別器的損失函式為:
與普通的生成對抗網路判別器的的損失函式類似。

訓練策略

先對 G 網路進行預訓練,再將 G 和 D 網路一起訓練。

# init t_image = fluid.layers.data(name='t_image',shape=[96, 96, 3],dtype='float32') t_target_image = fluid.layers.data(name='t_target_image',shape=[384, 384, 3],dtype='float32') vgg19_input = fluid.layers.data(name='vgg19_input',shape=[224, 224, 3],dtype='float32') step_num = int(len(train_hr_imgs) / batch_size) # initialize G for epoch in range(0, n_epoch_init + 1):     epoch_time = time.time()     np.random.shuffle(train_hr_imgs)     # real     sample_imgs_384 = random_crop(train_hr_imgs, 384)     sample_imgs_standardized_384 = standardized(sample_imgs_384)     # input     sample_imgs_96 = im_resize(sample_imgs_384,96,96)     sample_imgs_standardized_96 = standardized(sample_imgs_96)     # vgg19     sample_imgs_224 = im_resize(sample_imgs_384,224,224)     sample_imgs_standardized_224 = standardized(sample_imgs_224)     # loss     total_mse_loss, n_iter = 0, 0     for i in tqdm.tqdm(range(step_num)):         step_time = time.time()         imgs_384 = sample_imgs_standardized_384[i * batch_size:(i + 1) * batch_size]         imgs_384 = np.array(imgs_384, dtype='float32')         imgs_96 = sample_imgs_standardized_96[i * batch_size:(i + 1) * batch_size]         imgs_96 = np.array(imgs_96, dtype='float32')         # vgg19 data         imgs_224 = sample_imgs_standardized_224[i * batch_size:(i + 1) * batch_size]         imgs_224 = np.array(imgs_224, dtype='float32')         mse_loss_n = exe.run(SRGAN_g_program,                         feed={'t_image': imgs_96, 't_target_image': imgs_384, 'vgg19_input':imgs_224},                         fetch_list=[mse_loss])[0]         total_mse_loss += mse_loss_n         n_iter += 1     log = "[*] Epoch_init: [%2d/%2d] time: %4.4fs, mse: %.8f" % (epoch, n_epoch_init, time.time() - epoch_time, total_mse_loss / n_iter)     print(log)     if (epoch != 0) and (epoch % 10 == 0):         out = exe.run(SRGAN_g_program,                         feed={'t_image': imgs_96, 't_target_image': imgs_384, 'vgg19_input':imgs_224},                         fetch_list=[test_im])[0][0]         # generate img         im_G = np.array((out+1)*127.5, dtype=np.uint8)         im_96 = np.array((imgs_96[0]+1)*127.5, dtype=np.uint8)         im_384 = np.array((imgs_384[0]+1)*127.5, dtype=np.uint8)         cv2.imwrite('./output/epoch_init_{}_G.jpg'.format(epoch), cv2.cvtColor(im_G, cv2.COLOR_RGB2BGR))         cv2.imwrite('./output/epoch_init_{}_96.jpg'.format(epoch), cv2.cvtColor(im_96, cv2.COLOR_RGB2BGR))         cv2.imwrite('./output/epoch_init_{}_384.jpg'.format(epoch), cv2.cvtColor(im_384, cv2.COLOR_RGB2BGR)) # train GAN (SRGAN) for epoch in range(0, n_epoch + 1):     ## update learning rate     epoch_time = time.time()     # real     sample_imgs_384 = random_crop(train_hr_imgs, 384)     sample_imgs_standardized_384 = standardized(sample_imgs_384)     # input     sample_imgs_96 = im_resize(sample_imgs_384,96,96)     sample_imgs_standardized_96 = standardized(sample_imgs_96)     # vgg19     sample_imgs_224 = im_resize(sample_imgs_384,224,224)     sample_imgs_standardized_224 = standardized(sample_imgs_224)     # loss     total_d_loss, total_g_loss, n_iter = 0, 0, 0     for i in tqdm.tqdm(range(step_num)):         step_time = time.time()         imgs_384 = sample_imgs_standardized_384[i * batch_size:(i + 1) * batch_size]         imgs_384 = np.array(imgs_384, dtype='float32')         imgs_96 = sample_imgs_standardized_96[i * batch_size:(i + 1) * batch_size]         imgs_96 = np.array(imgs_96, dtype='float32')         # vgg19 data         imgs_224 = sample_imgs_standardized_224[i * batch_size:(i + 1) * batch_size]         imgs_224 = np.array(imgs_224, dtype='float32')         ## update D         errD = exe.run(SRGAN_d_program,                         feed={'t_image': imgs_96, 't_target_image': imgs_384},                         fetch_list=[d_loss])[0]         ## update G         errG = exe.run(SRGAN_g_program,                         feed={'t_image': imgs_96, 't_target_image': imgs_384, 'vgg19_input':imgs_224},                         fetch_list=[g_loss])[0]         total_d_loss += errD         total_g_loss += errG         n_iter += 1     log = "[*] Epoch: [%2d/%2d] time: %4.4fs, d_loss: %.8f g_loss: %.8f" % (epoch, n_epoch, time.time() - epoch_time, total_d_loss / n_iter, total_g_loss / n_iter)     print(log)     if (epoch != 0) and (epoch % 10 == 0):         out = exe.run(SRGAN_g_program,                         feed={'t_image': imgs_96, 't_target_image': imgs_384, 'vgg19_input':imgs_224},                         fetch_list=[test_im])[0][0]         # generate img         im_G = np.array((out + 1) * 127.5, dtype=np.uint8)         im_96 = np.array((imgs_96[0] + 1) * 127.5, dtype=np.uint8)         im_384 = np.array((imgs_384[0] + 1) * 127.5, dtype=np.uint8)         cv2.imwrite('./output/epoch_{}_G.jpg'.format(epoch), cv2.cvtColor(im_G, cv2.COLOR_RGB2BGR))         cv2.imwrite('./output/epoch_{}_96.jpg'.format(epoch), cv2.cvtColor(im_96, cv2.COLOR_RGB2BGR))         cv2.imwrite('./output/epoch_{}_384.jpg'.format(epoch), cv2.cvtColor(im_384, cv2.COLOR_RGB2BGR))     # save model     # d_models     save_pretrain_model_path_d = 'models/d_models/'     # delete old model files     shutil.rmtree(save_pretrain_model_path_d, ignore_errors=True)     # mkdir     os.makedirs(save_pretrain_model_path_d)     fluid.io.save_persistables(executor=exe, dirname=save_pretrain_model_path_d, main_program=SRGAN_g_program)     # g_models     save_pretrain_model_path_g = 'models/g_models/'     # delete old model files     shutil.rmtree(save_pretrain_model_path_g, ignore_errors=True)     # mkdir     os.makedirs(save_pretrain_model_path_g)     fluid.io.save_persistables(executor=exe, dirname=save_pretrain_model_path_g, main_program=SRGAN_g_program)

結果展示

import os from PIL import Image import matplotlib.pyplot as plt img0 = Image.open('./output/epoch_1780_96.jpg') img1 = Image.open('./output/epoch_1780_384.jpg') img2 = Image.open('./output/epoch_1780_G.jpg') plt.figure("Image Completion Result",dpi=384) # dpi = 384 顯示的是原圖大小 plt.subplot(2,3,1) plt.imshow(img0) plt.title('Low resolution',fontsize='xx-small',fontweight='heavy') plt.axis('off') plt.subplot(2,3,2) plt.imshow(img1) plt.title('Hing resolution',fontsize='xx-small',fontweight='heavy') plt.axis('off') plt.subplot(2,3,3) plt.imshow(img2) plt.title('Generate',fontsize='xx-small',fontweight='heavy') plt.axis('off') plt.show()

基於飛槳復現 SRGAN 模型,對影像進行超解析度重構

心得體會

在此篇文章之前,CNN網路在傳統的單幀超解析度重建上就取得了非常好的效果,但是當影像下采樣倍數較高時,重建的得到的圖片會過於平滑,丟失細節。此篇文章提出的利用GAN來進行超解析度重建的方法,是第一個能恢復四倍下采樣影像的框架。SRGAN這個網路的最大貢獻就是使用了生成對抗網路(Generative adversarial network)來訓練SRResNet,使其產生的HR影像看起來更加自然,有更好的視覺效果,更接近自然HR影像。


本專案使用了飛槳深度學習開源平臺開發,在此開發過程中,全程使用AI Studio上完成所有資料預處理及模型訓練的工作,非常感謝AI Studio給我們提供的GPU線上訓練環境,對於在深度學習道路上硬體條件上不足的學生來說簡直是非常大的幫助。

相關文章