일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- Machine Learning
- cs231n
- Transformer
- CPP
- dropout
- Regularization
- 밑바닥부터 시작하는 딥러닝2
- SQLD
- marchine learning
- Algorithm
- C++
- Alexnet
- Python
- 딥러닝
- Optimization
- deep learning
- Generative Models
- RNN
- Baekjoon
- DFS
- assignment1
- Multi-Head Attention
- mask r-cnn
- BFS
- assignment2
- Adam
- do it! 알고리즘 코딩테스트: c++편
- 밑바닥부터 시작하는 딥러닝
- CNN
- computer vision
- Today
- Total
newhaneul
[Stanford Univ: CS231n] Spring 2025 Assignment2. Q5(Image Captioning with Vanilla RNNs) 본문
[Stanford Univ: CS231n] Spring 2025 Assignment2. Q5(Image Captioning with Vanilla RNNs)
뉴하늘 2025. 5. 23. 18:12본 포스팅은 Stanford University School of Engineering의 CS231n: Convolutional Neural Networks for Visual Recognition을 수강하고 공부한 내용을 정리하기 위한 포스팅입니다.
https://github.com/cs231n/cs231n.github.io/blob/master/assignments/2025/assignment2.md
cs231n.github.io/assignments/2025/assignment2.md at master · cs231n/cs231n.github.io
Public facing notes page. Contribute to cs231n/cs231n.github.io development by creating an account on GitHub.
github.com
https://github.com/KwonKiHyeok/CS231n/tree/main
GitHub - KwonKiHyeok/CS231n: This repository contains my solutions to the assignments of the CS231n course offered by Stanford U
This repository contains my solutions to the assignments of the CS231n course offered by Stanford University (Spring 2025). - KwonKiHyeok/CS231n
github.com
Q5. Image Captioning with Vanilla RNNs

def rnn_step_forward(x, prev_h, Wx, Wh, b):
"""Run the forward pass for a single timestep of a vanilla RNN using a tanh activation function.
The input data has dimension D, the hidden state has dimension H,
and the minibatch is of size N.
Inputs:
- x: Input data for this timestep, of shape (N, D)
- prev_h: Hidden state from previous timestep, of shape (N, H)
- Wx: Weight matrix for input-to-hidden connections, of shape (D, H)
- Wh: Weight matrix for hidden-to-hidden connections, of shape (H, H)
- b: Biases of shape (H,)
Returns a tuple of:
- next_h: Next hidden state, of shape (N, H)
"""
next_h = None
##############################################################################
# TODO: Implement a single forward step for the vanilla RNN. #
##############################################################################
next_h = torch.tanh(prev_h @ Wh + x @ Wx + b)
##############################################################################
# END OF YOUR CODE #
##############################################################################
return next_h
N, D, H = 3, 10, 4
x = torch.from_numpy(np.linspace(-0.4, 0.7, num=N*D).reshape(N, D))
prev_h = torch.from_numpy(np.linspace(-0.2, 0.5, num=N*H).reshape(N, H))
Wx = torch.from_numpy(np.linspace(-0.1, 0.9, num=D*H).reshape(D, H))
Wh = torch.from_numpy(np.linspace(-0.3, 0.7, num=H*H).reshape(H, H))
b = torch.from_numpy(np.linspace(-0.2, 0.4, num=H))
next_h = rnn_step_forward(x, prev_h, Wx, Wh, b).numpy()
expected_next_h = np.asarray([
[-0.58172089, -0.50182032, -0.41232771, -0.31410098],
[ 0.66854692, 0.79562378, 0.87755553, 0.92795967],
[ 0.97934501, 0.99144213, 0.99646691, 0.99854353]])
print('next_h error: ', rel_error(expected_next_h, next_h))
next_h error: 6.292421426471037e-09

from cs231n.rnn_layers_pytorch import rnn_step_forward
# Create test inputs
np.random.seed(231)
N, D, H = 4, 5, 6
x = torch.from_numpy(np.random.randn(N, D))
h = torch.from_numpy(np.random.randn(N, H))
Wx = torch.from_numpy(np.random.randn(D, H))
Wh = torch.from_numpy(np.random.randn(H, H))
b = torch.from_numpy(np.random.randn(H))
# Enable gradient tracking and do rnn forward pass
for tensor in [x, h, Wx, Wh, b]:
tensor.requires_grad_()
next_h = rnn_step_forward(x, h, Wx, Wh, b)
# Simulate random upstream gradients and do a backward pass using pytorch's
# autograd.
dnext_h = torch.from_numpy(np.random.randn(*next_h.shape))
next_h.backward(dnext_h)
# Collect gradient in separate numpy arrays
dx = x.grad.detach().numpy()
dh = h.grad.detach().numpy()
dWx = Wx.grad.detach().numpy()
dWh = Wh.grad.detach().numpy()
db = b.grad.detach().numpy()
dnext_h = dnext_h.detach().numpy()
# Also convert test inputs to numpy arrays
x = x.detach().numpy()
h = h.detach().numpy()
Wx = Wx.detach().numpy()
Wh = Wh.detach().numpy()
b = b.detach().numpy()
# Wrap our forward pass to support numpy array input and output. We use
# `torch.no_grad()` to explicitly disable gradient tracking.
def rnn_step_forward_numpy(x, h, Wx, Wh, b):
with torch.no_grad():
return rnn_step_forward(
torch.from_numpy(x),
torch.from_numpy(h),
torch.from_numpy(Wx),
torch.from_numpy(Wh),
torch.from_numpy(b),
).numpy()
# Compute numerical gradients and compare.
fx = lambda x: rnn_step_forward_numpy(x, h, Wx, Wh, b)
fh = lambda h: rnn_step_forward_numpy(x, h, Wx, Wh, b)
fWx = lambda Wx: rnn_step_forward_numpy(x, h, Wx, Wh, b)
fWh = lambda Wh: rnn_step_forward_numpy(x, h, Wx, Wh, b)
fb = lambda b: rnn_step_forward_numpy(x, h, Wx, Wh, b)
dx_num = eval_numerical_gradient_array(fx, x, dnext_h)
dh_num = eval_numerical_gradient_array(fh, h, dnext_h)
dWx_num = eval_numerical_gradient_array(fWx, Wx, dnext_h)
dWh_num = eval_numerical_gradient_array(fWh, Wh, dnext_h)
db_num = eval_numerical_gradient_array(fb, b, dnext_h)
# You should see errors on the order of 1e-9 or less
print('dx error: ', rel_error(dx_num, dx))
print('dh error: ', rel_error(dh_num, dh))
print('dWx error: ', rel_error(dWx_num, dWx))
print('dWh error: ', rel_error(dWh_num, dWh))
print('db error: ', rel_error(db_num, db))
dx error: 2.319932372313319e-10
dh error: 2.68283311648336e-10
dWx error: 8.820301273669344e-10
dWh error: 4.703191244726939e-10
db error: 1.5956895526227225e-11

def rnn_forward(x, h0, Wx, Wh, b):
"""Run a vanilla RNN forward on an entire sequence of data.
We assume an input sequence composed of T vectors, each of dimension D. The RNN uses a hidden
size of H, and we work over a minibatch containing N sequences. After running the RNN forward,
we return the hidden states for all timesteps.
Inputs:
- x: Input data for the entire timeseries, of shape (N, T, D)
- h0: Initial hidden state, of shape (N, H)
- Wx: Weight matrix for input-to-hidden connections, of shape (D, H)
- Wh: Weight matrix for hidden-to-hidden connections, of shape (H, H)
- b: Biases of shape (H,)
Returns a tuple of:
- h: Hidden states for the entire timeseries, of shape (N, T, H)
"""
h = None
##############################################################################
# TODO: Implement forward pass for a vanilla RNN running on a sequence of #
# input data. You should use the rnn_step_forward function that you defined #
# above. You can use a for loop to help compute the forward pass. #
##############################################################################
N, T, D = x.shape
_, H = h0.shape
h = torch.zeros(N, T, H, dtype = h0.dtype, device = h0.device)
prev_h = h0
for t in range(T):
x_t = x[:, t, :]
next_h = rnn_step_forward(x_t, prev_h, Wx, Wh, b)
h[:, t, :] = next_h
prev_h = next_h
##############################################################################
# END OF YOUR CODE #
##############################################################################
return h
from cs231n.rnn_layers_pytorch import rnn_forward
N, T, D, H = 2, 3, 4, 5
x = torch.from_numpy(np.linspace(-0.1, 0.3, num=N*T*D).reshape(N, T, D))
h0 = torch.from_numpy(np.linspace(-0.3, 0.1, num=N*H).reshape(N, H))
Wx = torch.from_numpy(np.linspace(-0.2, 0.4, num=D*H).reshape(D, H))
Wh = torch.from_numpy(np.linspace(-0.4, 0.1, num=H*H).reshape(H, H))
b = torch.from_numpy(np.linspace(-0.7, 0.1, num=H))
h = rnn_forward(x, h0, Wx, Wh, b).numpy()
expected_h = np.asarray([
[
[-0.42070749, -0.27279261, -0.11074945, 0.05740409, 0.22236251],
[-0.39525808, -0.22554661, -0.0409454, 0.14649412, 0.32397316],
[-0.42305111, -0.24223728, -0.04287027, 0.15997045, 0.35014525],
],
[
[-0.55857474, -0.39065825, -0.19198182, 0.02378408, 0.23735671],
[-0.27150199, -0.07088804, 0.13562939, 0.33099728, 0.50158768],
[-0.51014825, -0.30524429, -0.06755202, 0.17806392, 0.40333043]]])
print('h error: ', rel_error(expected_h, h))
h error: 7.728466151011529e-08

from cs231n.rnn_layers_pytorch import rnn_forward
# Create test inputs
np.random.seed(231)
N, D, T, H = 2, 3, 10, 5
x = torch.from_numpy(np.random.randn(N, T, D))
h0 = torch.from_numpy(np.random.randn(N, H))
Wx = torch.from_numpy(np.random.randn(D, H))
Wh = torch.from_numpy(np.random.randn(H, H))
b = torch.from_numpy(np.random.randn(H))
# Enable gradient tracking and do forward pass
for tensor in [x, h0, Wx, Wh, b]:
tensor.requires_grad_()
h = rnn_forward(x, h0, Wx, Wh, b)
# Simulate random upstream gradients and do a backward pass using pytorch's
# autograd.
dh = torch.from_numpy(np.random.randn(*h.shape))
h.backward(dh)
# Collect gradient in separate numpy arrays
dx = x.grad.detach().numpy()
dh0 = h0.grad.detach().numpy()
dWx = Wx.grad.detach().numpy()
dWh = Wh.grad.detach().numpy()
db = b.grad.detach().numpy()
dh = dh.detach().numpy()
# Also convert test inputs to numpy arrays
x = x.detach().numpy()
h0 = h0.detach().numpy()
Wx = Wx.detach().numpy()
Wh = Wh.detach().numpy()
b = b.detach().numpy()
# Wrap our forward pass to support numpy array input and output. We use
# `torch.no_grad()` to explicitly disable gradient tracking.
def rnn_forward_numpy(x, h0, Wx, Wh, b):
with torch.no_grad():
return rnn_forward(
torch.from_numpy(x),
torch.from_numpy(h0),
torch.from_numpy(Wx),
torch.from_numpy(Wh),
torch.from_numpy(b),
).numpy()
fx = lambda x: rnn_forward_numpy(x, h0, Wx, Wh, b)
fh0 = lambda h0: rnn_forward_numpy(x, h0, Wx, Wh, b)
fWx = lambda Wx: rnn_forward_numpy(x, h0, Wx, Wh, b)
fWh = lambda Wh: rnn_forward_numpy(x, h0, Wx, Wh, b)
fb = lambda b: rnn_forward_numpy(x, h0, Wx, Wh, b)
dx_num = eval_numerical_gradient_array(fx, x, dh)
dh0_num = eval_numerical_gradient_array(fh0, h0, dh)
dWx_num = eval_numerical_gradient_array(fWx, Wx, dh)
dWh_num = eval_numerical_gradient_array(fWh, Wh, dh)
db_num = eval_numerical_gradient_array(fb, b, dh)
# You should see errors on the order of 1e-6 or less
print('dx error: ', rel_error(dx_num, dx))
print('dh0 error: ', rel_error(dh0_num, dh0))
print('dWx error: ', rel_error(dWx_num, dWx))
print('dWh error: ', rel_error(dWh_num, dWh))
print('db error: ', rel_error(db_num, db))
dx error: 1.5402322184213243e-09
dh0 error: 3.38326056377258e-09
dWx error: 7.238345464511777e-09
dWh error: 1.3157180708657365e-07
db error: 1.5384079038256828e-10

def word_embedding_forward(x, W):
"""Forward pass for word embeddings.
We operate on minibatches of size N where
each sequence has length T. We assume a vocabulary of V words, assigning each
word to a vector of dimension D.
Inputs:
- x: Integer array of shape (N, T) giving indices of words. Each element idx
of x muxt be in the range 0 <= idx < V.
- W: Weight matrix of shape (V, D) giving word vectors for all words.
Returns a tuple of:
- out: Array of shape (N, T, D) giving word vectors for all input words.
"""
out = None
##############################################################################
# TODO: Implement the forward pass for word embeddings. #
# #
# HINT: This can be done in one line using Pytorch's array indexing. #
##############################################################################
N, T = x.shape
V, D = W.shape
# Inefficient
# out = torch.zeros(N, T, D, dtype = W.dtype, device = W.device)
# for n in range(N):
# for t in range(T):
# out[n, t, :] = W[x[n, t], :]
# out error: 1.0000000094736443e-08
out = W[x] # out error: 1.0000000094736443e-08
##############################################################################
# END OF YOUR CODE #
##############################################################################
return out
N, T, V, D = 2, 4, 5, 3
x = torch.from_numpy(np.asarray([[0, 3, 1, 2], [2, 1, 0, 3]]))
W = torch.from_numpy(np.linspace(0, 1, num=V*D).reshape(V, D))
out = word_embedding_forward(x, W).numpy()
expected_out = np.asarray([
[[ 0., 0.07142857, 0.14285714],
[ 0.64285714, 0.71428571, 0.78571429],
[ 0.21428571, 0.28571429, 0.35714286],
[ 0.42857143, 0.5, 0.57142857]],
[[ 0.42857143, 0.5, 0.57142857],
[ 0.21428571, 0.28571429, 0.35714286],
[ 0., 0.07142857, 0.14285714],
[ 0.64285714, 0.71428571, 0.78571429]]])
print('out error: ', rel_error(expected_out, out))
out error: 1.0000000094736443e-08

np.random.seed(231)
N, T, V, D = 50, 3, 5, 6
x = torch.from_numpy(np.random.randint(V, size=(N, T)))
W = torch.from_numpy(np.random.randn(V, D))
W.requires_grad_()
out = word_embedding_forward(x, W)
dout = torch.from_numpy(np.random.randn(*out.shape))
out.backward(dout)
dW = W.grad.detach().numpy()
x = x.detach().numpy()
W = W.detach().numpy()
dout = dout.detach().numpy()
def word_embedding_forward_numpy(x, W):
return word_embedding_forward(
torch.from_numpy(x),
torch.from_numpy(W),
).numpy()
f = lambda W: word_embedding_forward_numpy(x, W)
dW_num = eval_numerical_gradient_array(f, W, dout)
# You should see an error on the order of 1e-11 or less
print('dW error: ', rel_error(dW, dW_num))
dW error: 3.2774595693100364e-12

import numpy as np
import torch
from ..rnn_layers_pytorch import *
class CaptioningRNN:
"""
A CaptioningRNN produces captions from image features using a recurrent
neural network.
The RNN receives input vectors of size D, has a vocab size of V, works on
sequences of length T, has an RNN hidden dimension of H, uses word vectors
of dimension W, and operates on minibatches of size N.
Note that we don't use any regularization for the CaptioningRNN.
"""
def __init__(
self,
word_to_idx,
input_dim=512,
wordvec_dim=128,
hidden_dim=128,
cell_type="rnn",
dtype=torch.float32,
):
"""
Construct a new CaptioningRNN instance.
Inputs:
- word_to_idx: A dictionary giving the vocabulary. It contains V entries,
and maps each string to a unique integer in the range [0, V).
- input_dim: Dimension D of input image feature vectors.
- wordvec_dim: Dimension W of word vectors.
- hidden_dim: Dimension H for the hidden state of the RNN.
- cell_type: What type of RNN to use; either 'rnn' or 'lstm'.
- dtype: numpy datatype to use; use float32 for training and float64 for
numeric gradient checking.
"""
if cell_type not in {"rnn", "lstm"}:
raise ValueError('Invalid cell_type "%s"' % cell_type)
self.cell_type = cell_type
self.dtype = dtype
self.word_to_idx = word_to_idx
self.idx_to_word = {i: w for w, i in word_to_idx.items()}
self.params = {}
vocab_size = len(word_to_idx)
self._null = word_to_idx["<NULL>"]
self._start = word_to_idx.get("<START>", None)
self._end = word_to_idx.get("<END>", None)
# Initialize word vectors
self.params["W_embed"] = torch.randn(vocab_size, wordvec_dim)
self.params["W_embed"] /= 100
# Initialize CNN -> hidden state projection parameters
self.params["W_proj"] = torch.randn(input_dim, hidden_dim)
self.params["W_proj"] /= np.sqrt(input_dim)
self.params["b_proj"] = torch.zeros(hidden_dim)
# Initialize parameters for the RNN
dim_mul = {"lstm": 4, "rnn": 1}[cell_type]
self.params["Wx"] = torch.randn(wordvec_dim, dim_mul * hidden_dim)
self.params["Wx"] /= np.sqrt(wordvec_dim)
self.params["Wh"] = torch.randn(hidden_dim, dim_mul * hidden_dim)
self.params["Wh"] /= np.sqrt(hidden_dim)
self.params["b"] = torch.zeros(dim_mul * hidden_dim)
# Initialize output to vocab weights
self.params["W_vocab"] = torch.randn(hidden_dim, vocab_size)
self.params["W_vocab"] /= np.sqrt(hidden_dim)
self.params["b_vocab"] = torch.zeros(vocab_size)
# Cast parameters to correct dtype
for k, v in self.params.items():
self.params[k] = v.to(self.dtype)
def loss(self, features, captions):
"""
Compute training-time loss for the RNN. We input image features and
ground-truth captions for those images, and use an RNN (or LSTM) to compute
loss and gradients on all parameters.
Inputs:
- features: Input image features, of shape (N, D)
- captions: Ground-truth captions; an integer array of shape (N, T + 1) where
each element is in the range 0 <= y[i, t] < V
Returns a tuple of:
- loss: Scalar loss
"""
# Cut captions into two pieces: captions_in has everything but the last word
# and will be input to the RNN; captions_out has everything but the first
# word and this is what we will expect the RNN to generate. These are offset
# by one relative to each other because the RNN should produce word (t+1)
# after receiving word t. The first element of captions_in will be the START
# token, and the first element of captions_out will be the first word.
captions_in = captions[:, :-1]
captions_out = captions[:, 1:]
# You'll need this
mask = captions_out != self._null
# Weight and bias for the affine transform from image features to initial
# hidden state
W_proj, b_proj = self.params["W_proj"], self.params["b_proj"]
# Word embedding matrix
W_embed = self.params["W_embed"]
# Input-to-hidden, hidden-to-hidden, and biases for the RNN
Wx, Wh, b = self.params["Wx"], self.params["Wh"], self.params["b"]
# Weight and bias for the hidden-to-vocab transformation.
W_vocab, b_vocab = self.params["W_vocab"], self.params["b_vocab"]
loss = 0.0
############################################################################
# TODO: Implement the forward pass for the CaptioningRNN. #
# In the forward pass you will need to do the following: #
# (1) Use an affine transformation to compute the initial hidden state #
# from the image features. This should produce an array of shape (N, H)#
# (2) Use a word embedding layer to transform the words in captions_in #
# from indices to vectors, giving an array of shape (N, T, W). #
# (3) Use either a vanilla RNN or LSTM (depending on self.cell_type) to #
# process the sequence of input word vectors and produce hidden state #
# vectors for all timesteps, producing an array of shape (N, T, H). #
# (4) Use a (temporal) affine transformation to compute scores over the #
# vocabulary at every timestep using the hidden states, giving an #
# array of shape (N, T, V). #
# (5) Use (temporal) softmax to compute loss using captions_out, ignoring #
# the points where the output word is <NULL> using the mask above. #
# #
# Do not worry about regularizing the weights or their gradients! #
# #
# You also don't have to implement the backward pass. #
############################################################################
# (1) image feature -> initial hidden state
h0 = affine_forward(features, W_proj, b_proj) # shape (N, H)
# (2) word -> word vector
word_vector = word_embedding_forward(captions_in, W_embed) # shape (N, T, W)
# (3) RNN or LSTM -> hidden state
if self.cell_type == "rnn":
hidden_state = rnn_forward(word_vector, h0, Wx, Wh, b) # shape (N, T, H)
elif self.cell_type == "lstm":
pass
# (4) hidden state -> vocabulary scores
scores = temporal_affine_forward(hidden_state, W_vocab, b_vocab) # shape (N, T, V)
# (5) vocabulary scores -> loss
loss = temporal_softmax_loss(scores, captions_out, mask)
############################################################################
# END OF YOUR CODE #
############################################################################
return loss
def sample(self, features, max_length=30):
"""
Run a test-time forward pass for the model, sampling captions for input
feature vectors.
At each timestep, we embed the current word, pass it and the previous hidden
state to the RNN to get the next hidden state, use the hidden state to get
scores for all vocab words, and choose the word with the highest score as
the next word. The initial hidden state is computed by applying an affine
transform to the input image features, and the initial word is the <START>
token.
For LSTMs you will also have to keep track of the cell state; in that case
the initial cell state should be zero.
Inputs:
- features: Array of input image features of shape (N, D).
- max_length: Maximum length T of generated captions.
Returns:
- captions: Array of shape (N, max_length) giving sampled captions,
where each element is an integer in the range [0, V). The first element
of captions should be the first sampled word, not the <START> token.
"""
N = features.shape[0]
captions = self._null * torch.ones((N, max_length), dtype=torch.long)
# Unpack parameters
W_proj, b_proj = self.params["W_proj"], self.params["b_proj"]
W_embed = self.params["W_embed"]
Wx, Wh, b = self.params["Wx"], self.params["Wh"], self.params["b"]
W_vocab, b_vocab = self.params["W_vocab"], self.params["b_vocab"]
###########################################################################
# TODO: Implement test-time sampling for the model. You will need to #
# initialize the hidden state of the RNN by applying the learned affine #
# transform to the input image features. The first word that you feed to #
# the RNN should be the <START> token; its value is stored in the #
# variable self._start. At each timestep you will need to do to: #
# (1) Embed the previous word using the learned word embeddings #
# (2) Make an RNN step using the previous hidden state and the embedded #
# current word to get the next hidden state. #
# (3) Apply the learned affine transformation to the next hidden state to #
# get scores for all words in the vocabulary #
# (4) Select the word with the highest score as the next word, writing it #
# (the word index) to the appropriate slot in the captions variable #
# #
# For simplicity, you do not need to stop generating after an <END> token #
# is sampled, but you can if you want to. #
# #
# HINT: You will not be able to use the rnn_forward or lstm_forward #
# functions; you'll need to call rnn_step_forward or lstm_step_forward in #
# a loop. #
# #
# NOTE: we are still working over minibatches in this function. Also if #
# you are using an LSTM, initialize the first cell state to zeros. #
###########################################################################
# (1) initial hidden state & <START> token
prev_h = affine_forward(features, W_proj, b_proj)
captions[:, 0] = self._start
for t in range(1, max_length):
# (2) complete next hidden state
word_embed = W_embed[captions[:, t-1]] # shape (N, W)
if self.cell_type == "rnn":
next_h = rnn_step_forward(word_embed, prev_h, Wx, Wh, b) # next hidden state, shape(N, H)
elif self.cell_type == "lstm":
pass
# (3) complete scores
scores = affine_forward(next_h, W_vocab, b_vocab) # shape (N, V)
# (4) select highest score
next_word = torch.argmax(scores, dim = 1) # shape (N, )
captions[:, t] = next_word
prev_h = next_h
############################################################################
# END OF YOUR CODE #
############################################################################
return captions
N, D, W, H = 10, 20, 30, 40
word_to_idx = {'<NULL>': 0, 'cat': 2, 'dog': 3}
V = len(word_to_idx)
T = 13
model = CaptioningRNN(
word_to_idx,
input_dim=D,
wordvec_dim=W,
hidden_dim=H,
cell_type='rnn',
dtype=torch.float64
)
# Set all model parameters to fixed values
for k, v in model.params.items():
model.params[k] = torch.from_numpy(
np.linspace(-1.4, 1.3, num=v.numel()).reshape(*v.shape))
features = torch.from_numpy(np.linspace(-1.5, 0.3, num=(N * D)).reshape(N, D))
captions = torch.from_numpy((np.arange(N * T) % V).reshape(N, T))
loss = model.loss(features, captions).item()
expected_loss = 9.83235591003
print('loss: ', loss)
print('expected loss: ', expected_loss)
print('difference: ', abs(loss - expected_loss))
loss: 9.832355910027388
expected loss: 9.83235591003
difference: 2.611244553918368e-12

np.random.seed(231)
torch.manual_seed(231)
batch_size = 2
timesteps = 3
input_dim = 4
wordvec_dim = 5
hidden_dim = 6
word_to_idx = {'<NULL>': 0, 'cat': 2, 'dog': 3}
vocab_size = len(word_to_idx)
captions = torch.from_numpy(np.random.randint(vocab_size, size=(batch_size, timesteps)))
features = torch.from_numpy(np.random.randn(batch_size, input_dim))
model = CaptioningRNN(
word_to_idx,
input_dim=input_dim,
wordvec_dim=wordvec_dim,
hidden_dim=hidden_dim,
cell_type='rnn',
dtype=torch.float64,
)
for k, v in model.params.items():
v.requires_grad_()
loss = model.loss(features, captions)
loss.backward()
grads = {k: v.grad.detach().numpy() for k, v in model.params.items()}
for k, v in model.params.items():
v.requires_grad_(False)
for param_name in sorted(grads.keys()):
def fn(val):
model.params[param_name] = torch.from_numpy(val)
ret = model.loss(features, captions).numpy()
return ret
param_grad_num = eval_numerical_gradient(
fn, model.params[param_name].numpy(), verbose=False, h=1e-6)
e = rel_error(param_grad_num, grads[param_name])
print('%s relative error: %e' % (param_name, e))
W_embed relative error: 1.673286e-09
W_proj relative error: 6.544743e-08
W_vocab relative error: 3.580914e-09
Wh relative error: 7.937870e-09
Wx relative error: 1.908687e-06
b relative error: 5.717390e-09
b_proj relative error: 2.844069e-10
b_vocab relative error: 1.976021e-10

np.random.seed(231)
torch.manual_seed(231)
small_data = load_coco_data(max_train=50)
small_rnn_model = CaptioningRNN(
cell_type='rnn',
word_to_idx=data['word_to_idx'],
input_dim=data['train_features'].shape[1],
hidden_dim=512,
wordvec_dim=256,
)
small_rnn_solver = CaptioningSolverPytorch(
small_rnn_model, small_data,
num_epochs=50,
batch_size=25,
learning_rate=5e-3,
verbose=True, print_every=10,
)
small_rnn_solver.train()
# Plot the training losses.
plt.plot(small_rnn_solver.loss_history)
plt.xlabel('Iteration')
plt.ylabel('Loss')
plt.title('Training loss history')
plt.show()
base dir /content/drive/My Drive/cs231n/assignments/assignment2/cs231n/datasets/coco_captioning
(Iteration 1 / 100) loss: 80.027161
(Iteration 11 / 100) loss: 25.580671
(Iteration 21 / 100) loss: 3.890780
(Iteration 31 / 100) loss: 0.641121
(Iteration 41 / 100) loss: 0.133080
(Iteration 51 / 100) loss: 0.057650
(Iteration 61 / 100) loss: 0.028645
(Iteration 71 / 100) loss: 0.022370
(Iteration 81 / 100) loss: 0.018542
(Iteration 91 / 100) loss: 0.016733

Final loss: 0.013376469

# If you get an error, the URL just no longer exists, so don't worry!
# You can re-sample as many times as you want.
for split in ['train', 'val']:
minibatch = sample_coco_minibatch(small_data, split=split, batch_size=2)
gt_captions, features, urls = minibatch
gt_captions = decode_captions(gt_captions, data['idx_to_word'])
sample_captions = small_rnn_model.sample(torch.from_numpy(features)).numpy()
sample_captions = decode_captions(sample_captions, data['idx_to_word'])
for gt_caption, sample_caption, url in zip(gt_captions, sample_captions, urls):
img = image_from_url(url)
# Skip missing URLs.
if img is None: continue
plt.imshow(img)
plt.title('%s\n%s\nGT:%s' % (split, sample_caption, gt_caption))
plt.axis('off')
plt.show()




문자 단위 RNN의 한 가지 장점은, 알파벳에 해당하는 소수의 문자에 대해서만 임베딩을 생성하면 되기 때문에 파라미터 수가 크게 줄어든다는 점이다. 또한 고정된 단어 사전에 의존하지 않기 때문에, 훈련 중 보지 못한 단어라도 문자 단위로 조합하여 새롭게 생성할 수 있는 유연성을 가진다.
반면, 주요 단점은 문장을 훨씬 더 긴 시퀀스로 처리해야 한다는 점이다. 이는 장기 의존성을 학습하기 어렵게 만들어 성능 저하로 이어질 수 있다. 문자 기반 시퀀스는 더 길어지기 때문에, RNN에서 흔히 발생하는 vanishing gradient 또는 exploding gradient 문제가 더욱 심각해질 수 있다. 또한, 각 타임스텝에서 가능한 출력 수는 적더라도, 의미론적인 문맥이 부족하여 오류율이 높아질 수 있다. 게다가 한 글자씩 순차적으로 생성해야 하므로 출력 속도 또한 느려진다.
'2. Artificial Intelligence > Stanford Univ. CS231n' 카테고리의 다른 글
[Stanford Univ: CS231n] Lecture 14. Reinforcement Learning (3) | 2025.05.29 |
---|---|
[Stanford Univ: CS231n] Lecture 13. Generative Models (3) | 2025.05.25 |
[Stanford Univ: CS231n] Lecture 12. Visualizing and Understanding (1) | 2025.05.23 |
[Stanford Univ: CS231n] Spring 2025 Assignment2. Q4(PyTorch on CIFAR-10) (1) | 2025.05.21 |
[Stanford Univ: CS231n] Lecture 11. Detection and Segmentation (2) | 2025.05.17 |