mirror of
https://github.com/wassname/Clover-Edition.git
synced 2026-09-09 11:13:26 +08:00
+21
-16
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user