Merge pull request #9 from cloveranon/develop2

Develop2
This commit is contained in:
AccidentallyOnPurpose
2019-12-25 00:40:29 +00:00
committed by GitHub
4 changed files with 59 additions and 42 deletions
+21 -16
View File
@@ -1,4 +1,5 @@
import os
import itertools
import torch
import torch.nn.functional as F
@@ -65,7 +66,7 @@ def sample_sequence(
context = torch.tensor(context, dtype=torch.long, device=device)
context = context.unsqueeze(0).repeat(num_samples, 1)
generated = context
USE_PAST = False
USE_PAST = True
next_token = context
outputs = None
with torch.no_grad():
@@ -100,6 +101,11 @@ def sample_sequence(
generated = torch.cat((generated, next_token), dim=1)
return generated
def truncate_multiple_sequences(seqs, max_len=100):
"""Truncate multiple sequences, longest first, removing first."""
while sum(len(s) for s in seqs) > max_len:
longest = sorted(seqs, key=len, reverse=True)[0]
longest.pop(0)
class GPT2Generator:
def __init__(
@@ -114,8 +120,8 @@ class GPT2Generator:
self.dtype = torch.float32 if CPU else torch.float16
self.repetition_penalty = repetition_penalty
self.batch_size = 1
self.stop_token = None
self.max_history_tokens = 256
self.max_history_tokens = 1024 - generate_num
self.stop_token = '<|endoftext|>'
self.model_name = "pytorch-gpt2-xl-aid2-v5"
self.model_dir = "models"
@@ -131,7 +137,7 @@ class GPT2Generator:
model_class, tokenizer_class = MODEL_CLASSES["gpt2"]
self.tokenizer = tokenizer_class.from_pretrained(self.checkpoint_path)
self.model = model_class.from_pretrained(self.checkpoint_path)
self.model.to(self.device).to(self.dtype)
self.model.to(self.dtype).to(self.device)
self.model.eval()
def sample_sequence(self, context_tokens=None, generate_num=None, temperature=None):
@@ -183,14 +189,13 @@ class GPT2Generator:
return result
def generate_raw(self, prompt, generate_num=None, temperature=None):
context_tokens = self.tokenizer.encode(prompt, add_special_tokens=False)
# TODO instead of taking last 1024, take first X and last Y
# crop context to avoid going of the GPT2 max context size of 1024
if len(context_tokens) > self.max_history_tokens:
# FIXME it would be better to pass in a list of strings so we can cut some out, and a truncation strategy https://github.com/huggingface/transformers/blob/ce50305e5b8c8748b81b0c8f5539a337b6a995b9/src/transformers/tokenization_utils.py#L791
first = self.max_history_tokens // 4
last = self.max_history_tokens - first
context_tokens = context_tokens[:first] + context_tokens[-last:]
# the prompt is a list of strings, encode each one tok tokens, then truncate the longest ones
context_tokens = [self.tokenizer.encode(p, add_special_tokens=False, max_length=self.max_history_tokens) for p in prompt]
truncate_multiple_sequences(context_tokens, self.max_history_tokens)
context_tokens = list(itertools.chain(*context_tokens))
if os.environ.get("DEBUG_GPT2", False):
logger.debug("Text passing into model %s", self.tokenizer.decode(context_tokens, clean_up_tokenization_spaces=True, skip_special_tokens=True))
generated = 0
for _ in range(self.samples // self.batch_size):
@@ -202,7 +207,7 @@ class GPT2Generator:
out = out[:, len(context_tokens) :].tolist()
for o in out:
generated += 1
text = self.tokenizer.decode(o, clean_up_tokenization_spaces=True)
text = self.tokenizer.decode(o, clean_up_tokenization_spaces=True, skip_special_tokens=True)
if self.stop_token:
index = text.find(self.stop_token)
if index == -1:
@@ -212,7 +217,7 @@ class GPT2Generator:
def generate(self, prompt, options=None, seed=1):
prompt = self.prompt_replace(prompt)
prompt = [self.prompt_replace(p) for p in prompt]
logger.debug("Prompt is: `%s`", repr(prompt))
@@ -223,6 +228,6 @@ class GPT2Generator:
result = text
result = self.result_replace(result)
if len(result) == 0:
return self.generate(prompt)
logger.warn("Model generated empty text %s.", result)
# return self.generate(prompt) # Woah recursion!
return result
+14 -10
View File
@@ -109,8 +109,7 @@ class AIPlayer:
return clean_suggested_action(result_raw, min_length=settings.getint('action-min-length'))
def play():
generator = getGenerator()
def play(generator):
story_manager = UnconstrainedStoryManager(generator)
ai_player = AIPlayer(generator)
print("\n")
@@ -168,16 +167,14 @@ def play():
if settings.getint('action-alternatives') > 0:
#TODO change this to two messages for different colors
action_prompt = (
story_manager.story.results[-1]
if story_manager.story.results
else "\nWhat do you do now?"
) + "\n>"
suggested_actions = []
colPrint('Suggested actions:', colors['selection-value'])
action_suggestion_lines = 1
for i in range(settings.getint('action-alternatives')):
# FIXME it might be better to pass in a longer history
action_prompt = story_manager.story_context() # This should be within the loop as it has a random sampling element
action_prompt[-1] += '> '
logger.debug("action_prompt %s", action_prompt)
suggested_action = ai_player.get_action(action_prompt)
suggested_actions.append(suggested_action)
suggestion = '{}> {}'.format(i, suggested_action)
@@ -239,7 +236,12 @@ def play():
# Options to select a suggestion action
if action in [str(i) for i in range(len(suggested_actions))]:
action = suggested_actions[int(action)]
action = action.strip()
# Crop actions to a max length
action = action[:4096]
if action != "":
# Roll a 20 sided dice to make things interesting
@@ -260,7 +262,6 @@ def play():
else:
action = "You say " + action
else:
action = action.strip()
action = first_to_second_person(action)
if not action.lower().startswith("you ") and not action.lower().startswith("i "):
action = action[0].lower() + action[1:]
@@ -317,5 +318,8 @@ def play():
colPrint("Sorry about that...where were we?", colors["query"])
colPrint(result, colors["ai-text"])
#TODO: there's no reason for this to be enclosed in a function
play()
# This is here for rapid development, without reloading the model. You import play into a jupyternotebook with autoreload
if __name__ == "__main__":
generator = getGenerator()
play(generator)
+21 -14
View File
@@ -3,7 +3,7 @@ import os
import subprocess
import uuid
from subprocess import Popen
import random
from story.utils import *
@@ -64,7 +64,7 @@ class Story:
def add_to_story(self, action, story_block):
self.actions.append(action)
self.results.append(story_block)
if (len(str(self)) > 3900): # (Fix some mem errors. From RTech, max story of 3900 characters for GTX 2080 ti 11GB
if len(self.actions) > 10000:
self.actions.pop(1)
self.results.pop(1)
@@ -72,17 +72,24 @@ class Story:
mem_ind = self.memory
if len(self.results) < 2:
latest_result = self.story_start
latest_results = [self.story_start]
else:
latest_result = self.context
while mem_ind > 0:
latest_results = [self.context]
latest_result = ''
if len(self.results) >= mem_ind:
latest_result += self.actions[-mem_ind] + self.results[-mem_ind]
mem_ind -= 1
return latest_result
if mem_ind < len(self.results):
# When we have to much history we will take the last 10, and sample randomly from the rest
# first take last mem_ind//2
all_inds = list(range(len(self.results)))
first = all_inds[:-mem_ind//2]
last = all_inds[-mem_ind//2:]
inds = sorted(random.sample(first, mem_ind//2)+last)
else:
inds = range(len(self.results))
logger.debug("Using history indices %s", inds)
for i in inds:
latest_result += self.actions[i] + self.results[i]
return latest_results + [latest_result]
def __str__(self):
story_list = [self.story_start]
@@ -157,7 +164,7 @@ class StoryManager:
def start_new_story(
self, story_prompt, context="", game_state=None, upload_story=False
):
block = self.generator.generate(context + story_prompt)
block = self.generator.generate([context, story_prompt])
block = cut_trailing_sentence(block)
self.story = Story(
context + story_prompt + block,
@@ -203,7 +210,7 @@ class UnconstrainedStoryManager(StoryManager):
return result
def generate_result(self, action):
block = self.generator.generate(self.story_context() + action)
block = self.generator.generate(self.story_context()+[action])
return block
@@ -314,7 +321,7 @@ class ConstrainedStoryManager(StoryManager):
def generate_action_result(self, prompt, phrase, options=None):
action_result = (
phrase + " " + self.generator.generate(prompt + " " + phrase, options)
phrase + " " + self.generator.generate(prompt + [phrase], options)
)
action, result = split_first_sentence(action_result)
return action, result
+3 -2
View File
@@ -24,10 +24,11 @@ def console_print(text, width=75):
#TODO: get rid if pyjarowinker dependency
# (AOP) You could use a simpler method, but this has been reported by RebootTech as a much more accurate way to compare strings. It also helps clean up the history. So it will hurt ability to check for looping
def get_similarity(a, b):
if len(a)==0 or len(b)==0: return 1
return distance.get_jaro_distance(
a, b, winkler=True, scaling = 0.1
)
a, b, winkler=True, scaling = 0.1)
def get_num_options(num):