from IPython.core.display import display, HTML
display(HTML("<style>.container { width:90% !important; }</style>"))
⛳ 나는 스케치를 너는 채색을..... ⛳¶
목차¶
내가 원하는 숫자 이미지 만들기 (1) Generator 구성하기
내가 원하는 숫자 이미지 만들기 (2) Discriminator 구성하기
내가 원하는 숫자 이미지 만들기 (3) 학습 및 테스트하기
3. 내가 원하는 숫자 이미지 만들기 (1) Generator 구성하기¶
import tensorflow_datasets as tfds
mnist, info = tfds.load(
"mnist", split="train", with_info=True
)
fig = tfds.show_examples(mnist, info)
Downloading and preparing dataset mnist/3.0.1 (download: 11.06 MiB, generated: 21.00 MiB, total: 32.06 MiB) to /root/tensorflow_datasets/mnist/3.0.1...
WARNING:absl:Dataset mnist is hosted on GCS. It will automatically be downloaded to your local data directory. If you'd instead prefer to read directly from our public GCS bucket (recommended if you're running on GCP), you can instead pass `try_gcs=True` to `tfds.load` or set `data_dir=gs://tfds-data/datasets`.
Dataset mnist downloaded and prepared to /root/tensorflow_datasets/mnist/3.0.1. Subsequent calls will reuse this data.
여러 개의 숫자 이미지와 그에 알맞은 레이블이 출력되었을 것이다. 이어서 아래 코드를 실행해 학습 전에 필요한 몇 가지 처리를 수행하는 함수를 정의한다. 이미지 픽셀 값을 -1~1 사이의 범위로 변경했고, 레이블 정보를 원-핫 인코딩(one-hot encoding)했다.
GAN과 cGAN 각각을 실험해 보기 위해 label 정보 사용 유무에 따라 gan_preprocessing()과 cgan_preprocessing() 두 가지 함수를 구성해 놓았다.
import tensorflow as tf
BATCH_SIZE = 128
def gan_preprocessing(data):
image = data["image"]
image = tf.cast(image, tf.float32)
image = (image / 127.5) - 1
return image
def cgan_preprocessing(data):
image = data["image"]
image = tf.cast(image, tf.float32)
image = (image / 127.5) - 1
label = tf.one_hot(data["label"], 10)
return image, label
gan_datasets = mnist.map(gan_preprocessing).shuffle(1000).batch(BATCH_SIZE)
cgan_datasets = mnist.map(cgan_preprocessing).shuffle(100).batch(BATCH_SIZE)
print("✅ check")
✅ check
원하는 대로 정확히 처리되었는지 한 개 데이터셋만 선택해 확인해 봅자. 이미지에 쓰인 숫자와 레이블이 일치해야 하고, 이미지 값의 범위가 -1~1 사이에 있어야 한다.
import matplotlib.pyplot as plt
for i,j in cgan_datasets : break
# 이미지 i와 라벨 j가 일치하는지 확인해 봅니다.
print("Label :", j[0])
print("Image Min/Max :", i.numpy().min(), i.numpy().max())
plt.imshow(i.numpy()[0,...,0], plt.cm.gray)
Label : tf.Tensor([0. 0. 0. 0. 0. 0. 0. 0. 0. 1.], shape=(10,), dtype=float32) Image Min/Max : -1.0 1.0
<matplotlib.image.AxesImage at 0x7f30985a5990>
원-핫 인코딩으로 표현된 Label과 출력된 이미지가 일치하는지 확인해보자.
0과 1로 이루어진 원-핫 벡터에는 각자 고유의 인덱스가 있다.
MNIST의 경우, 숫자 0은 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
,
숫자 6은 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
의 값을 가진다.
위 코드의 output을 확인해 이미지 i와 라벨 j가 일치하는지 확인해 보자.
GAN Generator 구성하기¶
이번 구현은 Tensorflow2의 Subclassing 방법을 이용할 것이다. Subclassing 방법은 tensorflow.keras.Model 을 상속받아 클래스를 만들며, 일반적으로 __init__()
메서드 안에서 레이어 구성을 정의하고, 구성된 레이어를 call()
메서드에서 사용해 forward propagation을 진행한다.
이러한 Subclassing 방법은 Pytorch의 모델 구성 방법과도 매우 유사하므로 이에 익숙해진다면 Pytorch의 모델 구성 방법도 빠르게 습득할 수 있다.
먼저 GAN의 Generator를 아래와 같이 구현해보자.
from tensorflow.keras import layers, Input, Model
class GeneratorGAN(Model):
def __init__(self):
super(GeneratorGAN, self).__init__()
self.dense_1 = layers.Dense(128, activation='relu')
self.dense_2 = layers.Dense(256, activation='relu')
self.dense_3 = layers.Dense(512, activation='relu')
self.dense_4 = layers.Dense(28*28*1, activation='tanh')
self.reshape = layers.Reshape((28, 28, 1))
def call(self, noise):
out = self.dense_1(noise)
out = self.dense_2(out)
out = self.dense_3(out)
out = self.dense_4(out)
return self.reshape(out)
print("✅ check")
✅ check
__init__()
메서드 안에서 사용할 모든 레이어를 정의했다. 4개의 fully-connected 레이어 중 한 개를 제외하고 모두 ReLU 활성화를 사용하는 것으로 확인된다.
call()
메서드에서는 노이즈를 입력받아 __init__()
에서 정의된 레이어들을 순서대로 통과한다.
Generator는 숫자가 쓰인 이미지를 출력해야 하므로 마지막 출력은 layers.Reshape()
을 이용해 (28,28,1) 크기로 변환된다.
class GeneratorCGAN(Model):
def __init__(self):
super(GeneratorCGAN, self).__init__()
self.dense_z = layers.Dense(256, activation='relu')
self.dense_y = layers.Dense(256, activation='relu')
self.combined_dense = layers.Dense(512, activation='relu')
self.final_dense = layers.Dense(28 * 28 * 1, activation='tanh')
self.reshape = layers.Reshape((28, 28, 1))
def call(self, noise, label):
noise = self.dense_z(noise)
label = self.dense_y(label)
out = self.combined_dense(tf.concat([noise, label], axis=-1))
out = self.final_dense(out)
return self.reshape(out)
print("✅ check")
✅ check
GAN의 Generator보다 구현이 복잡한듯하지만, 이전에 cGAN을 이해한 대로 두 구조의 차이점은 레이블 정보가 추가된다는 것뿐이다. 이번에는 여러분이 위 코드를 자세히 보고, 어떠한 연산이 이루어지는지 생각해 보자.
cGAN의 입력은 2개(노이즈 및 레이블 정보)라는 점을 기억해 두자. (이전 GAN 코드와 비교하여 잘 생각해 보자!)
Question 2.¶
위 코드로 생성한 모델에 대해 입력부터 출력까지 어떤 연산이 이루어지나? (
init()
메서드에서는 노이즈 및 레이블 입력 각각에 적용할 레이어를 생성했다.)
▶
노이즈 입력 및 레이블 입력은 각각 1개의 fully-connected 레이어와 ReLU 활성화를 통과한다. (dense_z, dense_y)
1번 문항의 각 결과가 서로 연결되어 다시 한번 1개의 fully-connected 레이어와 ReLU 활성화를 통과한다. (tf.concat, conbined_dense)
2번 문항의 결과가 1개의 fully-connected 레이어 및 Hyperbolic tangent 활성화를 거쳐 28x28 차원의 결과가 생성되고 (28,28,1) 크기의 이미지 형태로 변환되어 출력된다. (final_dense, reshape)
4. 내가 원하는 숫자 이미지 만들기 (2) Discriminator 구성하기¶
GAN Discriminator 구성하기¶
이전에 임의 노이즈 및 레이블 정보로부터 숫자 이미지를 생성하는 Generator를 구현했다. 이번에는 실제 이미지와 Generator가 생성한 이미지에 대해 진짜와 가짜를 식별하는 Discriminator를 구현해 보자.
먼저 GAN의 Discriminator를 아래와 같이 구현할 것이다.
class DiscriminatorGAN(Model):
def __init__(self):
super(DiscriminatorGAN, self).__init__()
self.flatten = layers.Flatten()
self.blocks = []
for f in [512, 256, 128, 1]:
self.blocks.append(
layers.Dense(f, activation=None if f==1 else "relu")
)
def call(self, x):
x = self.flatten(x)
for block in self.blocks:
x = block(x)
return x
print("✅ check")
✅ check
여기에서는
__init__()
에blocks
라는 리스트를 하나 만들어 놓고, for loop를 이용하여 필요한 레이어들을 차곡차곡 쌓아놓았다.이러한 방식을 이용하면 각각의 fully-connected 레이어를 매번 정의하지 않아도 되므로 많은 레이어가 필요할 때 편리하다.
Discriminator의 입력은 Generator가 생성한 (28,28,1) 크기의 이미지이며, 이를 fully-connected 레이어로 학습하기 위해
call()
에서는 가장 먼저layers.Flatten()
이 적용된다.이어서 레이어들이 쌓여있는
blocks
에 대해 for loop를 이용하여 레이어들을 순서대로 하나씩 꺼내 입력 데이터를 통과시키다.마지막 fully-connected 레이어를 통과하면 진짜 및 가짜 이미지를 나타내는 1개의 값이 출력된다.
cGAN Discriminator 구성하기¶
다음으로 구현할 cGAN의 Discriminator는 Maxout이라는 특별한 레이어가 사용된다. Maxout은 간단히 설명하면 두 레이어 사이를 연결할 때, 여러 개의 fully-connected 레이어를 통과시켜 그 중 가장 큰 값을 가져오도록 한다.
만약 2개의 fully-connected 레이어를 사용할 때 Maxout을 식으로 표현하면 아래와 같다.
$max(w_1^Tx+b_1, w_2^Tx+b_2)$
이렇게 fully-connected 레이어를 2개만 사용한다면 다차원 공간에서 2개의 면이 교차된 모양의 activation function처럼 작동할 것이다.
다차원 공간은 시각화 하기가 어려운데요. 차원을 낮춰 1차원 fully-connected 레이어라고 가정하면 아래처럼 2개의 직선으로 이루어진 activation function으로 나타낼 수 있다.
사용하는 fully-connected 레이어 갯수가 늘어난다면 점점 곡선 형태인 activation function이 될 수 있다.
차원을 늘리면 다차원 공간에 곡면을 나타낼 수 있을 것이지만, 시각화 하기는 쫌 어렵다.
Maxout에 대해 더 깊이 알아보는 것은 추천하지 않습니다만, 원한다면 아래 논문을 참고하자.
아래 코드와 같이 Maxout을 구성할 수 있다.
tensorflow.keras.layers.Layer
를 상속받아 레이어를 정의하였다.
이전에 모델을 정의한 것과 비슷하게 __init__(), call()
메서드를 구성한다.
class Maxout(layers.Layer):
def __init__(self, units, pieces):
super(Maxout, self).__init__()
self.dense = layers.Dense(units*pieces, activation="relu")
self.dropout = layers.Dropout(.5)
self.reshape = layers.Reshape((-1, pieces, units))
def call(self, x):
x = self.dense(x)
x = self.dropout(x)
x = self.reshape(x)
return tf.math.reduce_max(x, axis=2)
print("✅ check")
✅ check
Maxout
레이어를 구성할 때 units과 pieces의 설정이 필요하며, units
차원 수를 가진 fully-connected 레이어를 pieces
개만큼 만들고 그중 최댓값을 출력합니다.
예를 들어, 사용할 Maxout
레이어가 units=100
, pieces=10
으로 설정된다면 입력으로부터 100차원의 representation을 10개 만들고, 10개 중에서 최댓값을 가져와 최종 1개의 100차원 representation이 출력된다.
식으로 나타낸다면 아래와 같습니다. (위 예시에서는 각각의 wx+b가 모두 100차원이다)
$max(w_1^Tx+b_1, w_2^Tx+b_2, ...., w_9^Tx+b_9, w_{10}^Tx+b_{10}$
위에서 정의한 Maxout 레이어를 3번만 사용하면 아래와 같이 쉽게 cGAN의 Discriminator를 구성할 수 있다.
class DiscriminatorCGAN(Model):
def __init__(self):
super(DiscriminatorCGAN, self).__init__()
self.flatten = layers.Flatten()
self.image_block = Maxout(240, 5)
self.label_block = Maxout(50, 5)
self.combine_block = Maxout(240, 4)
self.dense = layers.Dense(1, activation=None)
def call(self, image, label):
image = self.flatten(image)
image = self.image_block(image)
label = self.label_block(label)
x = layers.Concatenate()([image, label])
x = self.combine_block(x)
return self.dense(x)
print("✅ check")
✅ check
GAN의 Discriminator와 마찬가지로 Generator가 생성한 (28,28,1) 크기의 이미지가 입력되므로, layers.Flatten()
이 적용된다.
그리고 이미지 입력 및 레이블 입력 각각은 Maxout
레이어를 한 번씩 통과한 후 서로 결합되어 Maxout
레이어를 한 번 더 통과한다.
마지막 fully-connected 레이어를 통과하면 진짜 및 가짜 이미지를 나타내는 1개의 값이 출력된다.
Question 3.¶
만약 위와 같은 cGAN의 Disciminator에 (28,28,1) 크기 이미지 및 (10,) 크기 레이블이 입력될 때, 연산의 순서를 다음과 같이 나타낼 수 있다.
- 이미지가 Maxout 레이어를 통과
- 레이블이 Maxout 레이어를 통과
- 1)과 2)결과로 나온 representation을 결합(concate) 후 Maxout 레이어를 통과
위 3개 과정의 각 결과 차원 수는 각각 몇일까?
▶ 240, 50, 240
5. 내가 원하는 숫자 이미지 만들기 (3) 학습 및 테스트하기¶
이전에 정의한 Generator 및 Discriminator를 이용해 MINST를 학습하고 각 모델로 직접 숫자 손글씨를 생성해 보자.
우선 GAN, cGAN 각각의 모델 학습에 공통적으로 필요한 loss function과 optimizer를 정의한다.
진짜 및 가짜를 구별하기 위해 Binary Cross Entropy
를 사용하고, Adam optimizer
를 이용해 학습할 것이다.
from tensorflow.keras import optimizers, losses
bce = losses.BinaryCrossentropy(from_logits=True)
def generator_loss(fake_output):
return bce(tf.ones_like(fake_output), fake_output)
def discriminator_loss(real_output, fake_output):
return bce(tf.ones_like(real_output), real_output) + bce(tf.zeros_like(fake_output), fake_output)
gene_opt = optimizers.Adam(1e-4)
disc_opt = optimizers.Adam(1e-4)
print("✅ check")
✅ check
GAN으로 MNIST 학습하기¶
이전 단계에서 구성한 GeneratorGAN 및 DiscriminatorGAN 모델 클래스를 이용할 것이다.
여기서는 입력으로 사용되는 노이즈를 100차원으로 설정했으며, 하나의 배치 크기 데이터로 모델을 업데이트하는 함수를 아래와 같이 작성했다.
gan_generator = GeneratorGAN()
gan_discriminator = DiscriminatorGAN()
@tf.function()
def gan_step(real_images):
noise = tf.random.normal([real_images.shape[0], 100])
with tf.GradientTape(persistent=True) as tape:
# Generator를 이용해 가짜 이미지 생성
fake_images = gan_generator(noise)
# Discriminator를 이용해 진짜 및 가짜이미지를 각각 판별
real_out = gan_discriminator(real_images)
fake_out = gan_discriminator(fake_images)
# 각 손실(loss)을 계산
gene_loss = generator_loss(fake_out)
disc_loss = discriminator_loss(real_out, fake_out)
# gradient 계산
gene_grad = tape.gradient(gene_loss, gan_generator.trainable_variables)
disc_grad = tape.gradient(disc_loss, gan_discriminator.trainable_variables)
# 모델 학습
gene_opt.apply_gradients(zip(gene_grad, gan_generator.trainable_variables))
disc_opt.apply_gradients(zip(disc_grad, gan_discriminator.trainable_variables))
return gene_loss, disc_loss
print("✅ check")
✅ check
위 함수를 이용해 우선 10 epoch만큼 학습을 진행해 보자.
100번의 반복마다 각 손실(loss)을 출력하도록 했다.
EPOCHS = 10
for epoch in range(1, EPOCHS+1):
for i, images in enumerate(gan_datasets):
gene_loss, disc_loss = gan_step(images)
if (i+1) % 100 == 0:
print(f"[{epoch}/{EPOCHS} EPOCHS, {i+1} ITER] G:{gene_loss}, D:{disc_loss}")
[1/10 EPOCHS, 100 ITER] G:2.2515852451324463, D:0.1287405788898468 [1/10 EPOCHS, 200 ITER] G:2.2890000343322754, D:0.13099785149097443 [1/10 EPOCHS, 300 ITER] G:2.133634567260742, D:0.16575263440608978 [1/10 EPOCHS, 400 ITER] G:2.9661834239959717, D:0.128274604678154 [2/10 EPOCHS, 100 ITER] G:2.6306405067443848, D:0.24565434455871582 [2/10 EPOCHS, 200 ITER] G:3.409572124481201, D:0.18991819024085999 [2/10 EPOCHS, 300 ITER] G:2.709719657897949, D:0.17417845129966736 [2/10 EPOCHS, 400 ITER] G:3.0918996334075928, D:0.10517352819442749 [3/10 EPOCHS, 100 ITER] G:3.785342216491699, D:0.3793538212776184 [3/10 EPOCHS, 200 ITER] G:3.5953054428100586, D:0.212539404630661 [3/10 EPOCHS, 300 ITER] G:4.1171369552612305, D:0.11359161138534546 [3/10 EPOCHS, 400 ITER] G:3.911190986633301, D:0.3180190920829773 [4/10 EPOCHS, 100 ITER] G:3.119790554046631, D:0.20514178276062012 [4/10 EPOCHS, 200 ITER] G:2.7880687713623047, D:0.10622812807559967 [4/10 EPOCHS, 300 ITER] G:4.060458660125732, D:0.05123147368431091 [4/10 EPOCHS, 400 ITER] G:4.594343185424805, D:0.08965888619422913 [5/10 EPOCHS, 100 ITER] G:4.120011806488037, D:0.09958939254283905 [5/10 EPOCHS, 200 ITER] G:3.952122211456299, D:0.2120838463306427 [5/10 EPOCHS, 300 ITER] G:3.9215612411499023, D:0.0569964237511158 [5/10 EPOCHS, 400 ITER] G:3.9171319007873535, D:0.13407683372497559 [6/10 EPOCHS, 100 ITER] G:5.434388637542725, D:0.06693001091480255 [6/10 EPOCHS, 200 ITER] G:4.144359588623047, D:0.08100318163633347 [6/10 EPOCHS, 300 ITER] G:4.63629150390625, D:0.04445254057645798 [6/10 EPOCHS, 400 ITER] G:3.706737995147705, D:0.09946707636117935 [7/10 EPOCHS, 100 ITER] G:5.024933815002441, D:0.1423494666814804 [7/10 EPOCHS, 200 ITER] G:3.4399828910827637, D:0.07919280230998993 [7/10 EPOCHS, 300 ITER] G:5.963953971862793, D:0.056987617164850235 [7/10 EPOCHS, 400 ITER] G:4.329241752624512, D:0.06528981029987335 [8/10 EPOCHS, 100 ITER] G:3.966810703277588, D:0.14003503322601318 [8/10 EPOCHS, 200 ITER] G:4.794124603271484, D:0.015029888600111008 [8/10 EPOCHS, 300 ITER] G:7.274913787841797, D:0.05179649218916893 [8/10 EPOCHS, 400 ITER] G:4.2219319343566895, D:0.034980617463588715 [9/10 EPOCHS, 100 ITER] G:4.729442596435547, D:0.041526589542627335 [9/10 EPOCHS, 200 ITER] G:4.527396202087402, D:0.022012624889612198 [9/10 EPOCHS, 300 ITER] G:3.9167797565460205, D:0.037876732647418976 [9/10 EPOCHS, 400 ITER] G:4.67197322845459, D:0.033214740455150604 [10/10 EPOCHS, 100 ITER] G:4.175716400146484, D:0.1171049252152443 [10/10 EPOCHS, 200 ITER] G:5.774648666381836, D:0.012229223735630512 [10/10 EPOCHS, 300 ITER] G:5.682581901550293, D:0.05668159946799278 [10/10 EPOCHS, 400 ITER] G:7.366000175476074, D:0.008218073286116123
짧은 시간 학습된 모델을 테스트해 보자.
100차원 노이즈 입력을 10개 사용하여 10개의 숫자 손글씨 데이터를 생성해 시각화해보자.
import numpy as np
noise = tf.random.normal([10, 100])
output = gan_generator(noise)
output = np.squeeze(output.numpy())
plt.figure(figsize=(15,6))
for i in range(1, 11):
plt.subplot(2,5,i)
plt.imshow(output[i-1])
결과를 보니 아마도 10 epoch의 학습만으로는 좋은 결과를 기대할 수 없나 보다.
위 구현을 그대로 500 epoch 학습한 가중치를 준비해 두었으니 한번 사용해 보자.
import os
weight_path = '/content/drive/MyDrive/AIFFEL/data/conditional_generation/gan/GAN_500'
noise = tf.random.normal([10, 100])
gan_generator = GeneratorGAN()
gan_generator.load_weights(weight_path)
output = gan_generator(noise)
output = np.squeeze(output.numpy())
plt.figure(figsize=(15,6))
for i in range(1, 11):
plt.subplot(2,5,i)
plt.imshow(output[i-1])
위에 보이는 10개의 결과 이미지는 서로 다른 숫자들이 시각화하였다.
이러한 방법으로는 내가 원하는 특정 숫자 하나를 출력하기 위해 수많은 입력을 넣어야 할 수 있다.
내가 원하는 숫자를 바로 얻어내기 위해 아래에서 cGAN을 학습 시켜 보자.
cgan_generator = GeneratorCGAN()
cgan_discriminator = DiscriminatorCGAN()
@tf.function()
def cgan_step(real_images, labels):
noise = tf.random.normal([real_images.shape[0], 100])
with tf.GradientTape(persistent=True) as tape:
fake_images = cgan_generator(noise, labels)
real_out = cgan_discriminator(real_images, labels)
fake_out = cgan_discriminator(fake_images, labels)
gene_loss = generator_loss(fake_out)
disc_loss = discriminator_loss(real_out, fake_out)
gene_grad = tape.gradient(gene_loss, cgan_generator.trainable_variables)
disc_grad = tape.gradient(disc_loss, cgan_discriminator.trainable_variables)
gene_opt.apply_gradients(zip(gene_grad, cgan_generator.trainable_variables))
disc_opt.apply_gradients(zip(disc_grad, cgan_discriminator.trainable_variables))
return gene_loss, disc_loss
EPOCHS = 1
for epoch in range(1, EPOCHS+1):
for i, (images, labels) in enumerate(cgan_datasets):
gene_loss, disc_loss = cgan_step(images, labels)
if (i+1) % 100 == 0:
print(f"[{epoch}/{EPOCHS} EPOCHS, {i} ITER] G:{gene_loss}, D:{disc_loss}")
[1/1 EPOCHS, 99 ITER] G:5.424193382263184, D:0.008188015781342983 [1/1 EPOCHS, 199 ITER] G:4.880199432373047, D:0.04384269937872887 [1/1 EPOCHS, 299 ITER] G:3.944230556488037, D:0.04270751774311066 [1/1 EPOCHS, 399 ITER] G:6.769749164581299, D:0.003161069704219699
학습 과정에서 Generator 및 Discriminator에 레이블 정보만 추가로 입력했다는 점을 제외하면 위에서 학습한 GAN과 큰 차이는 없다.
위 코드로 짧게나마 학습시켜봤는데, 충분한 학습이 되기엔 더 많은 시간을 투자해야 할 것이다.
number = 9 # TODO : 생성할 숫자를 입력해 주세요!!
weight_path = '/content/drive/MyDrive/AIFFEL/data/conditional_generation/cgan/CGAN_500'
noise = tf.random.normal([10, 100])
label = tf.one_hot(number, 10)
label = tf.expand_dims(label, axis=0)
label = tf.repeat(label, 10, axis=0)
generator = GeneratorCGAN()
generator.load_weights(weight_path)
output = generator(noise, label)
output = np.squeeze(output.numpy())
plt.figure(figsize=(15,6))
for i in range(1, 11):
plt.subplot(2,5,i)
plt.imshow(output[i-1])
출력된 10개 시각화 결과로 number에 입력한 숫자에 해당하는 손글씨가 시각화되었다.
cGAN을 사용해 조건을 주고 학습하면 이렇게 특정한 숫자를 만들어내기가 훨씬 쉬울것이다.
다음 $STEP$에서는 Pix2Pix로 이미지 변환을 해보자.
'인공지능' 카테고리의 다른 글
[Tip]Colab 노트북 바로 열기|Github (0) | 2022.03.22 |
---|---|
[Part3]난 스케치 넌 채색을... |GAN-Pix2Pix (0) | 2022.03.18 |
[Part1]난 스케치 넌 채색을... |GAN (0) | 2022.03.18 |
인공지능 퀴즈 풀기|KorQuAD_BERT (0) | 2022.02.25 |
트랜스포머로 만드는 대화형 챗봇|Transformer (0) | 2022.02.22 |