-
-
Save chris-chris/b13e1a0d68f0a5335a31bf464818f24b to your computer and use it in GitHub Desktop.
Minimal character-level language model with a Vanilla Recurrent Neural Network, in Python/numpy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Minimal character-level Vanilla RNN model. Written by Andrej Karpathy (@karpathy) | |
BSD License | |
Korean translation | |
최소 버전의 단어 레벨 바닐라 RNN 모델. Andrej Karpathy (@karpathy) 작성 | |
- Korean comments | |
- Python3 compatibility update | |
""" | |
import numpy as np | |
def xrange(x): | |
return iter(range(x)) | |
# 데이터 입출력 | |
# https://www.shakespeare.org.uk/explore-shakespeare/shakespedia/shakespeares-plays/midsummer-nights-dream/ | |
# 한 여름밤의 꿈 | |
data = open('input.txt', 'r').read() # 읽어들일 파일은 plain text file이어야 한다 | |
chars = list(set(data)) | |
data_size, vocab_size = len(data), len(chars) | |
print('data has %d characters, %d unique.' % (data_size, vocab_size)) | |
char_to_ix = {ch: i for i, ch in enumerate(chars)} | |
ix_to_char = {i: ch for i, ch in enumerate(chars)} | |
# 하이퍼 파라미터 | |
hidden_size = 100 # 뉴런의 히든레이어 사이즈 | |
seq_length = 25 # number of steps to unroll the RNN for | |
learning_rate = 1e-1 | |
# 모델 파라미터 | |
Wxh = np.random.randn(hidden_size, vocab_size) * 0.01 # input to hidden | |
Whh = np.random.randn(hidden_size, hidden_size) * 0.01 # hidden to hidden | |
Why = np.random.randn(vocab_size, hidden_size) * 0.01 # hidden to output | |
bh = np.zeros((hidden_size, 1)) # hidden bias | |
by = np.zeros((vocab_size, 1)) # output bias | |
def lossFun(inputs, targets, hprev): | |
""" | |
inputs, targets 는 모두 정수의 배열입니다. | |
hprev 는 최초의 히든 스테이트의 Hx1입니다. | |
loss, gradients, 그리고 마지막 hidden state 를 리턴합니다. | |
""" | |
xs, hs, ys, ps = {}, {}, {}, {} | |
hs[-1] = np.copy(hprev) | |
loss = 0 | |
# forward pass 순방향 연산 | |
for t in xrange(len(inputs)): | |
xs[t] = np.zeros((vocab_size, 1)) # 1-of-k 형식으로 인코딩 (원핫인코딩) encode in 1-of-k representation | |
xs[t][inputs[t]] = 1 | |
hs[t] = np.tanh(np.dot(Wxh, xs[t]) + np.dot(Whh, hs[t - 1]) + bh) # 히든 스테이트 | |
ys[t] = np.dot(Why, hs[t]) + by # 다음 문자를 위한 정규화되지 않은 로그 확률 | |
ps[t] = np.exp(ys[t]) / np.sum(np.exp(ys[t])) # 다음 문자를 위한 확률 | |
loss += -np.log(ps[t][targets[t], 0]) # 소프트맥스 (cross-entropy loss) | |
# backward pass, 역방향 연산: 뒤로 보낼 기울기들을 구하는 과정 | |
dWxh, dWhh, dWhy = np.zeros_like(Wxh), np.zeros_like(Whh), np.zeros_like(Why) | |
dbh, dby = np.zeros_like(bh), np.zeros_like(by) | |
dhnext = np.zeros_like(hs[0]) | |
for t1 in xrange(len(inputs)): | |
t = len(inputs) - t1 - 1 | |
dy = np.copy(ps[t]) | |
dy[ | |
targets[t]] -= 1 # backprop into y. see http://cs231n.github.io/neural-networks-case-study/#grad if confused here | |
dWhy += np.dot(dy, hs[t].T) | |
dby += dy | |
dh = np.dot(Why.T, dy) + dhnext # backprop into h | |
dhraw = (1 - hs[t] * hs[t]) * dh # backprop through tanh nonlinearity | |
dbh += dhraw | |
dWxh += np.dot(dhraw, xs[t].T) | |
dWhh += np.dot(dhraw, hs[t - 1].T) | |
dhnext = np.dot(Whh.T, dhraw) | |
for dparam in [dWxh, dWhh, dWhy, dbh, dby]: | |
np.clip(dparam, -5, 5, out=dparam) # 그라디언트 폭발을 방지하기 위해 clip을 적용합니다. | |
return loss, dWxh, dWhh, dWhy, dbh, dby, hs[len(inputs) - 1] | |
def sample(h, seed_ix, n): | |
""" | |
모델에서 정수의 배열을 샘플링합니다. | |
h는 메모리 스테이트, seed_ix는 첫번째 스텝을 위한 seed letter입니다. | |
""" | |
x = np.zeros((vocab_size, 1)) | |
x[seed_ix] = 1 | |
ixes = [] | |
for t in xrange(n): | |
h = np.tanh(np.dot(Wxh, x) + np.dot(Whh, h) + bh) | |
y = np.dot(Why, h) + by | |
p = np.exp(y) / np.sum(np.exp(y)) | |
ix = np.random.choice(range(vocab_size), p=p.ravel()) | |
x = np.zeros((vocab_size, 1)) | |
x[ix] = 1 | |
ixes.append(ix) | |
return ixes | |
n, p = 0, 0 | |
mWxh, mWhh, mWhy = np.zeros_like(Wxh), np.zeros_like(Whh), np.zeros_like(Why) | |
mbh, mby = np.zeros_like(bh), np.zeros_like(by) # memory variables for Adagrad | |
smooth_loss = -np.log(1.0 / vocab_size) * seq_length # loss at iteration 0 | |
while True: | |
# 인풋을 준비합니다. (우리는 왼쪽부터 오른쪽까지 배열의 길이만큼 옮겨갈 것입니다.) | |
if p + seq_length + 1 >= len(data) or n == 0: | |
hprev = np.zeros((hidden_size, 1)) # reset RNN memory | |
p = 0 # 데이터의 시작점으로부터 시작합니다. | |
inputs = [char_to_ix[ch] for ch in data[p:p + seq_length]] | |
targets = [char_to_ix[ch] for ch in data[p + 1:p + seq_length + 1]] | |
# 모델로부터 샘플링합니다. | |
if n % 100 == 0: | |
sample_ix = sample(hprev, inputs[0], 200) | |
txt = ''.join(ix_to_char[ix] for ix in sample_ix) | |
print('----\n %s \n----' % (txt,)) | |
# 배열 길이 만큼의 문자들을 네트워크를 통해 포워드하고 그라디언트를 fetch합니다. | |
loss, dWxh, dWhh, dWhy, dbh, dby, hprev = lossFun(inputs, targets, hprev) | |
smooth_loss = smooth_loss * 0.999 + loss * 0.001 | |
if n % 100 == 0: print('iter %d, loss: %f' % (n, smooth_loss)) # progress 출력 | |
# Adagrad를 활용해 파라미터를 업데이트합니다. | |
for param, dparam, mem in zip([Wxh, Whh, Why, bh, by], | |
[dWxh, dWhh, dWhy, dbh, dby], | |
[mWxh, mWhh, mWhy, mbh, mby]): | |
mem += dparam * dparam | |
param += -learning_rate * dparam / np.sqrt(mem + 1e-8) # adagrad update | |
p += seq_length # 데이터 포인터를 이동시킵니다. | |
n += 1 # 이터레이션을 카운팅합니다. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment