Fixed entities remove in tokenizer + added code explanation
This commit is contained in:
parent
edd2f5d49d
commit
312d6b7635
|
@ -12,12 +12,13 @@ level=INFO
|
|||
|
||||
[grammar]
|
||||
chain_len=2
|
||||
separator=\x02
|
||||
sep=\x02
|
||||
stop_word=\x00
|
||||
max_wrds=30
|
||||
max_msgs=5
|
||||
endsent=.....!!?
|
||||
endsen=.....!!?
|
||||
garbage=«<{([.!?;\-—"/*&^#$|#$%^&*(№;%:?*])}>»
|
||||
garbage_entities=mention,text_mention,bot_command,url,email
|
||||
|
||||
[media_checker]
|
||||
lifetime=28800.0
|
||||
|
|
|
@ -6,7 +6,7 @@ encoding = 'utf-8'
|
|||
|
||||
sections = {
|
||||
'bot': ['token', 'name', 'anchors', 'god_mode', 'purge_interval', 'default_chance', 'spam_stickers'],
|
||||
'grammar': ['chain_length', 'separator', 'stop_word', 'end_sentence', 'all'],
|
||||
'grammar': ['chain_len', 'sep', 'stop_word', 'max_wrds', 'max_msgs', 'endsen', 'garbage', 'garbage_entities'],
|
||||
'logging': ['level'],
|
||||
'updates': ['mode'],
|
||||
'media_checker': ['lifetime', 'messages'],
|
||||
|
|
|
@ -15,7 +15,7 @@ class ReplyGenerator:
|
|||
self.max_msgs = config.getint('grammar', 'max_msgs')
|
||||
|
||||
self.stop_word = config['grammar']['stop_word']
|
||||
self.separator = config['grammar']['separator']
|
||||
self.sep = config['grammar']['sep']
|
||||
self.endsen = config['grammar']['endsen']
|
||||
|
||||
def generate(self, message):
|
||||
|
@ -25,15 +25,25 @@ class ReplyGenerator:
|
|||
:param message: Message
|
||||
:return:
|
||||
- response (a message)
|
||||
- empty string (if response == message)
|
||||
- None (if response == message or no message chains was generated)
|
||||
"""
|
||||
|
||||
words = self.tokenizer.extract_words(message)
|
||||
|
||||
# TODO explain this
|
||||
"""
|
||||
Преобразовываем триграммы в пары слов, чтобы было проще составлять цепочку.
|
||||
Например [('hello', 'how', 'do'), ('do', 'you', 'feel'), ('feel', 'today', None)] станет
|
||||
вот таким [('hello', 'how'), ('do', 'you'), ('feel', 'today')]
|
||||
"""
|
||||
pairs = [trigram[:-1] for trigram in self.tokenizer.split_to_trigrams(words)]
|
||||
|
||||
|
||||
# TODO explain why it returns what it returns
|
||||
"""
|
||||
Генерирует цепочку для КАЖДОЙ пары слов.
|
||||
Причины для возвращения самого длинного сообщения из всех - нет,
|
||||
на тот момент казалось что это наилучший вариант
|
||||
"""
|
||||
messages = [self.__generate_best_message(chat_id=message.chat_id, pair=pair) for pair in pairs]
|
||||
longest_message = max(messages, key=len) if len(messages) else None
|
||||
|
||||
|
@ -44,34 +54,54 @@ class ReplyGenerator:
|
|||
|
||||
def __generate_best_message(self, chat_id, pair):
|
||||
# TODO explain this method
|
||||
"""
|
||||
Метод генерирует несколько (максимум self.max_msgs) различных предложений,
|
||||
и оставляет лишь то, которое длиннее всех
|
||||
"""
|
||||
best_message = ''
|
||||
for _ in range(self.max_msgs):
|
||||
generated = self.__generate_sentence(chat_id=chat_id, pair=pair)
|
||||
if len(generated) > len(best_message):
|
||||
best_message = generated
|
||||
# TODO explain the concept of the BEST message
|
||||
# TODO explain the concept of the BEST message
|
||||
"""
|
||||
У кого длиннее - тот и победил. Самый тупой алгоритм на свете
|
||||
"""
|
||||
return best_message
|
||||
|
||||
def __generate_sentence(self, chat_id, pair):
|
||||
# TODO explain this method
|
||||
gen_words = []
|
||||
key = self.separator.join(pair)
|
||||
key = self.sep.join(pair)
|
||||
|
||||
# TODO explain this loop
|
||||
"""
|
||||
Например приходит пара (привет, бот)
|
||||
Мы ее преобразуем в строку 'привет$бот' и пишем в key.
|
||||
Затем итерируемся максимум 50 раз. На каждой итерации:
|
||||
|
||||
1. Преобразуем ключ обратно в пару, 'привет$бот' станет вновь (привет, бот)
|
||||
2. Добавляем в gen_words второе или первое слово из пары, в зависимости от размера gen_words (Вот это как раз и формирует результат)
|
||||
3. Получаем случайное слово используя пару привет$бот.
|
||||
4.1. Если слово не нашлось, то прерываем цикл, это означает что у этой пары нет соотношений в базе
|
||||
4.2. Если слово нашлось, например 'пидор' то формируем новый ключ из пары, например тут станет бот$пидор (key ВСЕГДА будет состоять из 2-х слов) и повторяем
|
||||
"""
|
||||
for _ in range(self.max_wrds):
|
||||
words = key.split(self.separator)
|
||||
words = key.split(self.sep)
|
||||
|
||||
gen_words.append(words[1] if len(gen_words) == 0 else words[1])
|
||||
# Исправил ошибку тут !!!!!!
|
||||
gen_words.append(words[0] if len(gen_words) == 0 else words[1])
|
||||
|
||||
next_word = self.trigram_repository.get_random_reply(chat_id, key)
|
||||
if next_word is None:
|
||||
break
|
||||
|
||||
key = self.separator.join(words[1:] + [next_word])
|
||||
key = self.sep.join(words[1:] + [next_word])
|
||||
|
||||
# TODO explain what's last word
|
||||
# Самое последнее слово в сегенирированном ключе
|
||||
# Append last word unless it is in the list already
|
||||
last_word = key.split(self.separator)[-1]
|
||||
last_word = key.split(self.sep)[-1]
|
||||
if last_word not in gen_words:
|
||||
gen_words.append(last_word)
|
||||
|
||||
|
@ -86,8 +116,18 @@ class ReplyGenerator:
|
|||
sentence = ' '.join(gen_words).strip()
|
||||
if sentence[-1:] not in self.endsen:
|
||||
# TODO explain this pls:
|
||||
"""
|
||||
Если в конце сформированного предложения нету пунктуации (?!.) (мы ведь не вырезаем эти символы),
|
||||
то добавляем ему случайную (?!.) из config.grammar.endsen
|
||||
"""
|
||||
sentence += self.tokenizer.random_end_sentence_token()
|
||||
# sentence = capitalize(sentence)
|
||||
# sentence = capitalize(sentence)
|
||||
"""
|
||||
Чем плохо начало предложения с большой буквы?
|
||||
"""
|
||||
# TODO my intuition tells me we shouldn't return fun(obj), but IDK really
|
||||
"""
|
||||
Не знал о таком стандарте в питоне или что это плохо, зачем лишняя переменная?
|
||||
"""
|
||||
|
||||
return sentence
|
||||
|
|
|
@ -7,49 +7,66 @@ class Tokenizer:
|
|||
def __init__(self):
|
||||
self.chain_len = config.getint('grammar', 'chain_len')
|
||||
self.stop_word = config['grammar']['stop_word']
|
||||
self.endsent = config['grammar']['endsent']
|
||||
self.endsen = config['grammar']['endsen']
|
||||
self.garbage = config['grammar']['garbage']
|
||||
# https://core.telegram.org/bots/api#messageentity
|
||||
self.garbage_entities = config.getlist('grammar', 'garbage_entities')
|
||||
|
||||
def split_to_trigrams(self, src_words):
|
||||
if len(src_words) <= self.chain_len:
|
||||
def split_to_trigrams(self, words_list):
|
||||
if len(words_list) <= self.chain_len:
|
||||
yield from ()
|
||||
|
||||
words = [self.stop_word]
|
||||
|
||||
for word in src_words:
|
||||
for word in words_list:
|
||||
words.append(word)
|
||||
if word[-1] in self.endsent:
|
||||
if word[-1] in self.endsen:
|
||||
words.append(self.stop_word)
|
||||
if words[-1] != self.stop_word:
|
||||
words.append(self.stop_word)
|
||||
|
||||
for i in range(len(words) - self.chain_len):
|
||||
j = i + self.chain_len + 1
|
||||
yield words[i : j]
|
||||
yield words[i:j]
|
||||
|
||||
def extract_words(self, message):
|
||||
symbols = list(re.sub('\s', ' ', message.text))
|
||||
symbols = list(re.sub('\s', ' ', self.remove_garbage_entities(message)))
|
||||
|
||||
for entity in message.entities:
|
||||
# TODO: explain the code
|
||||
# TODO: validate the formula
|
||||
symbols[entity.offset : (entity.length+entity.offset)] = ' ' * entity.length
|
||||
|
||||
return list(filter(None, map(self.__prettify, ''.join(symbols).split(' '))))
|
||||
return list(filter(None, map(self.prettify, ''.join(symbols).split(' '))))
|
||||
|
||||
def random_end_sentence_token(self):
|
||||
return random_element(list(self.endsent))
|
||||
return random_element(list(self.endsen))
|
||||
|
||||
def __prettify(self, word):
|
||||
def remove_garbage_entities(self, message):
|
||||
encoding = 'utf-16-le'
|
||||
utf16bytes = message.text.encode(encoding)
|
||||
result = bytearray()
|
||||
cur_pos = 0
|
||||
|
||||
for e in message.entities:
|
||||
start_pos = e.offset * 2
|
||||
end_pos = (e.offset + e.length) * 2
|
||||
|
||||
result += utf16bytes[cur_pos:start_pos]
|
||||
if e.type not in self.garbage_entities:
|
||||
result += utf16bytes[start_pos:end_pos]
|
||||
|
||||
cur_pos = end_pos
|
||||
|
||||
result += utf16bytes[cur_pos:]
|
||||
|
||||
return utf16bytes.decode(encoding)
|
||||
|
||||
def prettify(self, word):
|
||||
lowercase_word = word.lower().strip()
|
||||
last_symbol = lowercase_word[-1:]
|
||||
if last_symbol not in self.endsent:
|
||||
if last_symbol not in self.endsen:
|
||||
last_symbol = ''
|
||||
pretty_word = lowercase_word.strip(self.garbage_tokens)
|
||||
pretty_word = lowercase_word.strip(self.garbage)
|
||||
|
||||
if pretty_word != '' and len(pretty_word) > 2:
|
||||
return pretty_word + last_symbol
|
||||
elif lowercase_word in self.garbage_tokens:
|
||||
elif lowercase_word in self.garbage:
|
||||
return None
|
||||
|
||||
return lowercase_word
|
||||
|
|
Loading…
Reference in New Issue