Методы, данные и процессы
Используем корпус фильмов, подготовленный университетом Калифорнии в Санта-Круз. Этот корпус разбит по жанрам и содержит диалоги из 960 фильмов. Диалоги отделены от описания сцен.
Очищаем и обрабатываем данные с Pandas, разбив их по персонажам. Затем отфильтровываем по следующему условию: сто строк и не менее трёх слов в каждой, чтобы получить главных героев вместе с диалогами, которые ярко их характеризуют.
Затем создаём базу данных с иерархической структурой, содержащую жанр, название, персонажа и диалоги для всех жанров:
def process_df():
for genre, titles in sorted_genre_dict_items:
print(genre)
genre_file_char_dict[genre] = {}
titles = sorted(titles)
for title in titles:
print('t' + title)
genre_file_char_dict[genre][title] = {}
os.chdir(genre)
with codecs.open(title + '_dialog.txt', 'r', 'utf8') as fp:
f = fp.read()
text = clean_text(f)
speaker = re.findall(r"([A-Z]+s?[A-Z]+[^a-z0-9W])",text)
dialogue = re.compile('[A-Z]+s?[A-Z]+[^a-z0-9W]').split(text)[1:]
movie_df = pd.DataFrame(list(zip(speaker, dialogue)), columns=['Character', 'Dialogue'])
movie_df['Dialogue'] = movie_df['Dialogue'].str.lower()
movie_df = movie_df.groupby('Character').filter(lambda x: len(x) > 100)
movie_df = movie_df[movie_df.Dialogue.str.count(' ') > 3]
char_list = movie_df.Character.unique()
os.chdir('../')
for idx, char in enumerate(char_list):
if char in good_char_list:
char_df = movie_df[movie_df['Character'] == char] #отдельный датафрейм для каждого персонажа
genre_file_char_dict[genre][title][char] = char_df
process_df()
Затем пропускаем каждого персонажа через IBM Watson, чтобы получить психологические портреты. Учтите, что Watson нуждается в отдельном конфигурировании, а ниже написана функция, чтобы пропустить данные через Watson:
for genre, titles in genre_file_char_dict.items():
#print(genre)
for title in titles:
#print('t' + title)
chars = genre_file_char_dict[genre][title]
for char, df in chars.items():
#print('tt' + char)
text = df['Dialogue'].values
combined_text = ''
for line in text:
combined_text += line + 'n'
if len(combined_text.split(' ')) >= 100:
try:
profile = service.profile(combined_text, accept='application/json').get_result()
except Timeout:
todo = dict(genre=genre, title=title, char=char, combined_text=combined_text, profile=profile)
timeout_todo.append(todo)
genre_file_char_dict[genre][title][char] = {'text': combined_text,
В базе оказалось более трёх тысяч персонажей. Затем используем Tweepy, чтобы получить последние 200 твитов наших пользователей и составить их психологический портрет с помощью Watson:
def analyze(handle):
#Доступы к API твиттера
consumer_key = 'yourconsumerkey'
consumer_secret = 'yourkey'
access_token = 'yourkey'
access_token_secret = 'yourkey'
# Авторизация
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
api = tweepy.API(auth)
#Последние 200 твитов пользователя
tweets = api.user_timeline(screen_name=handle,
# 200 is the maximum allowed count
count=200,
include_rts = False,
# Necessary to keep full_text
# otherwise only the first 140 words are extracted
tweet_mode = 'extended'
)
#Все твиты в одну строку text.
text = ""
for s in tweets:
if (s.lang =='en'):
text += s.full_text
#Доступы к IBM Watson
url = 'https://gateway.watsonplatform.net/personality-insights/api'
apikey = 'yourapikey'
service = PersonalityInsightsV3(url=url, iam_apikey=apikey, version='2017-10-13' )
#Анализ твитов Watson PI API
profile = service.profile(text, accept='application/json').get_result()
#Результаты
return profile
Затем сравниваем профили из Твиттера с профилями персонажей фильмов, используя коэффициент Отиаи и находим наиболее похожего на пользователя персонажа. Используем эту функцию:
def most_similar_character(twitter_handle, user_defined_genre):
user_personality = analyze(twitter_handle)['personality']
user_personality_vector = [0,0,0,0,0]
for idx, trait in enumerate(user_personality):
user_personality_vector[idx] = trait['percentile']
# print(trait['name'], trait['percentile'])
best_char_personality_vector = [0,0,0,0,0]
best_char_title = 'No Title Match'
best_char_name = 'No Character Match'
best_similarity_score = 0
titles = genre_file_char_dict[user_defined_genre].keys()
for title in titles:
chars = genre_file_char_dict[user_defined_genre][title].keys()
for char in chars:
try:
profile = genre_file_char_dict[user_defined_genre][title][char]['profile']
personality = profile['personality']
char_personality_vector = [0,0,0,0,0]
for idx, trait in enumerate(personality):
char_personality_vector[idx] = trait['percentile']
similarity_score = round(cosine_similarity((user_personality_vector,
char_personality_vector))[0][1], 3)
if similarity_score > best_similarity_score:
best_char_personality_vector = char_personality_vector
best_char_title = title
best_char_name = char
best_similarity_score = similarity_score
except:
continue
# print(genre, title, char)
# print('n')
print('Genre: ' + genre + 'n' +
'Title: ' + best_char_title + 'n'
+ 'Character Name: ' + best_char_name + 'n'
+ 'Similarity Score: ' + str(best_similarity_score) + '%')
Наконец, задействуем OpenAI GPT-2, чтобы сгенерировать короткий твит от лица персонажа. Эта нейросеть была обучена с помощью GPT-2 Simple индивидуально на каждом диалоге персонажа в корпусе фильмов и настроена так, чтобы текст не превышал длину твита.
Предварительные результаты
Результаты всей этой работы были обнадеживающими! Проверяем её с помощью ряда твиттеров людей в группе, а также с некоторыми известными профилями, такими как Илона Маска (он оказался похож на Спока из Star Trek), и почувствуем, что результаты в большинстве случаев имеют смысл. Также создадим небольшое веб-приложение на Flask, ниже результат для Дональда Трампа:
Трамп на 99% похож на Чарли Крокера из “Ограбления по-итальянски”. Читаем описание персонажа: “Вышедший из тюрьмы лидер криминальной группировки, который намеревается ограбить инкассаторский грузовик с 500 килограммами золота”.