How do you prevent Streamlit from repeatedly training the model, each time you modify the input parameters on the website? Can it not simply predict instead of retraining each time? I noticed that st.cache doesn’t work when applied to class. Is the only way training the model separately and then simply import the weights to run the prediction within the streamlit code?
Hi @ciao -
Can you provide the code you are using, so that we can provide more detailed help?
In general, yes, people usually load their models in Streamlit rather than have the training step embedded. But there should be a way to have the training step cached, so that it only happens once / on-demand.
Best,
Randy
Sure, below are two files. One is called RNNSL.py and the other is called utils.py. (I noticed that I cannot simply attach the files.)
If you also have a simple example of loading model method, that would be really helpful to reference, preferably with PyTorch. I guess, loading model method is recommended?
RNNSL.py
import streamlit as st #!
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
from utils import ALL_LETTERS, N_LETTERS
from utils import load_data, letter_to_tensor, line_to_tensor, random_training_example
class RNN(nn.Module):
# nn.RNN
def __init__(self, input_size, hidden_size, output_size):
super(RNN, self).__init__()
self.hidden_size = hidden_size
self.i2h = nn.Linear(input_size + hidden_size, hidden_size)
self.i2o = nn.Linear(input_size + hidden_size, output_size)
self.softmax = nn.LogSoftmax(dim=1) #! ??? along dim_1
def forward(self, input_tensor, hidden_tensor):
combined = torch.cat((input_tensor, hidden_tensor), 1) #! ?
# 1 signifies that we concatenate along dim_1.
hidden = self.i2h(combined)
output = self.i2o(combined)
output = self.softmax(output)
return output, hidden
def init_hidden(self):
return torch.zeros(1, self.hidden_size)
# category_lines is a dictionary
# all_categories is a list
category_lines, all_categories = load_data()
n_categories = len(all_categories)
#print(n_categories) # 18
n_hidden = 128
rnn = RNN(N_LETTERS, n_hidden, n_categories) # 57, 128, 18
# one step
input_tensor = letter_to_tensor('A') # 1 hot vector
hidden_tensor = rnn.init_hidden()
#print(input_tensor[0])
output, next_hidden = rnn(input_tensor, hidden_tensor)
#print(output.size()) # 1, 18
#print(next_hidden.size()) # 1, 128
# whole sequence/name
input_tensor = line_to_tensor('Albert')
hidden_tensor = rnn.init_hidden()
#print(input_tensor[0])
output, next_hidden = rnn(input_tensor[0], hidden_tensor)
#print(output.size()) # 1, 18
#print(next_hidden.size()) # 1, 128
def category_from_output(output):
category_idx = torch.argmax(output).item()
return all_categories[category_idx]
#print(category_from_output(output))
criterion = nn.NLLLoss()
learning_rate = 0.005
optimizer = torch.optim.SGD(rnn.parameters(), lr=learning_rate)
def train(line_tensor, category_tensor):
hidden = rnn.init_hidden()
for i in range(line_tensor.size()[0]): # name length
output, hidden = rnn(line_tensor[i], hidden) # 1 char at a time
# note, that output is written over
# is it because we only care about the final output???
loss = criterion(output, category_tensor) #! ???category_tensor
optimizer.zero_grad()
loss.backward()
optimizer.step()
return output, loss.item()
current_loss = 0
all_losses = []
plot_steps, print_steps = 1000, 5000
n_iters = 10000 #100000
for i in range(n_iters):
category, line, category_tensor, line_tensor = random_training_example(category_lines, all_categories)
output, loss = train(line_tensor, category_tensor)
current_loss += loss
if (i+1) % plot_steps == 0:
all_losses.append(current_loss / plot_steps)
current_loss = 0
if (i+1) % print_steps == 0:
guess = category_from_output(output)
correct = "CORRECT" if guess == category else f"WRONG ({category})"
print(f"{i+1} {(i+1)/n_iters*100} {loss:.4f} {line} / {guess} {correct}")
plt.figure()
plt.plot(all_losses)
#plt.show()
st.pyplot() #!
def predict(input_line):
print(f"\n> {input_line}")
with torch.no_grad():
line_tensor = line_to_tensor(input_line)
hidden = rnn.init_hidden()
for i in range(line_tensor.size()[0]):
output, hidden = rnn(line_tensor[i], hidden)
guess = category_from_output(output)
return guess
while True:
name = st.text_input('Input:')
if st.button('submit'):
if name == "quit":
break
guess = predict(name)
st.write(guess)
#res = sentence.title()
#st.success(res)
#st.write(res)
======================
utils.py
import io
import os
import unicodedata
import string
import glob
import torch
import random
ALL_LETTERS = string.ascii_letters + " .,;'"
N_LETTERS = len(ALL_LETTERS) # 57
# Turn a Unicode string to plain ASCII, thanks to https://stackoverflow.com/a/518232/2809427
def unicode_to_ascii(s):
return ''.join(
c for c in unicodedata.normalize('NFD', s)
if unicodedata.category(c) != 'Mn'
and c in ALL_LETTERS
)
def load_data():
# Build the category_lines dictionary, a list of names per language
category_lines = {}
all_categories = []
def find_files(path):
return glob.glob(path)
# Read a file and split into lines
def read_lines(filename):
lines = io.open(filename, encoding='utf-8').read().strip().split('\n')
return [unicode_to_ascii(line) for line in lines]
for filename in find_files('data/names/*.txt'):
# print(filename) # data/names/French.txt
category = os.path.splitext(os.path.basename(filename))[0]
# print(category) # French
all_categories.append(category)
# print(all_categories) # ['Czech', 'German']
lines = read_lines(filename)
# print(lines)
category_lines[category] = lines
#if category == 'French':
# print(category_lines[category])
return category_lines, all_categories
"""
To represent a single letter, we use a “one-hot vector” of
size <1 x n_letters>. A one-hot vector is filled with 0s
except for a 1 at index of the current letter, e.g. "b" = <0 1 0 0 0 ...>.
To make a word we join a bunch of those into a
2D matrix <line_length x 1 x n_letters>.
That extra 1 dimension is because PyTorch assumes
everything is in batches - we’re just using a batch size of 1 here.
"""
# Find letter index from all_letters, e.g. "a" = 0
def letter_to_index(letter):
return ALL_LETTERS.find(letter)
# Just for demonstration, turn a letter into a <1 x n_letters> Tensor
def letter_to_tensor(letter):
tensor = torch.zeros(1, N_LETTERS)
tensor[0][letter_to_index(letter)] = 1
return tensor
# Turn a line into a <line_length x 1 x n_letters>,
# or an array of one-hot letter vectors
def line_to_tensor(line):
tensor = torch.zeros(len(line), 1, N_LETTERS)
for i, letter in enumerate(line):
tensor[i][0][letter_to_index(letter)] = 1
return tensor
def random_training_example(category_lines, all_categories):
def random_choice(a):
random_idx = random.randint(0, len(a) - 1)
return a[random_idx]
category = random_choice(all_categories)
line = random_choice(category_lines[category])
#print(all_categories.index(category))
# index of the list
category_tensor = torch.tensor([all_categories.index(category)], dtype=torch.long)
line_tensor = line_to_tensor(line)
return category, line, category_tensor, line_tensor
#! category_tensor = label expressed as numerical index
#! line_tensor = name in the form of list of 1 hot vectors
if __name__ == '__main__':
#print(ALL_LETTERS)
#print(N_LETTERS) # 57
#print(unicode_to_ascii('Ślusàrski'))
category_lines, all_categories = load_data()
#print(category_lines['Japanese'][:5])
# # ['Abe', 'Abukara', 'Adachi', 'Aida', 'Aihara']
#print(all_categories)
# ['Czech', 'German', 'Arabic', 'Japanese', 'Chinese', 'Vietnamese', 'Russian', 'French', 'Irish', 'English', 'Spanish', 'Greek', 'Italian', 'Portuguese', 'Scottish', 'Dutch', 'Korean', 'Polish']
#print(letter_to_tensor('b')) # [1, 57]
# tensor([[0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
#print(line_to_tensor('Jones').size())
# torch.Size([5, 1, 57])
#print(line_to_tensor('ab'))
# tensor([[[1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
# [[0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
category, line, category_tensor, line_tensor = random_training_example(category_lines, all_categories)
#print(category)
#print(line)
#print(category_tensor)
#print(line_tensor) # 5 x 1 x 57
This is generally how we see people do this sort of thing (uses Tensorflow, not PyTorch):