Я смотрел окончание церемонии вручения Оскара 2017 года, и, как бы болезненно это ни было, я был счастлив видеть победу «Лунного света». Что вообще такого хорошего в Ла-Ла-Ленд? Неуклюжая, молодая, красивая, но посредственно поющая актриса добилась успеха в Голливуде… похоже, что эта история уже была написана раньше. Это заставило меня задуматься, какие фильмы имеют хорошие оценки и обычно хорошо принимаются? Что ж, может быть, эта история и была написана раньше, но в академии может быть просто типаж. Это типаж мюзикл или Райан Гослинг?

Я решил посмотреть 250 лучших фильмов IMDB, чтобы увидеть, смогу ли я найти какую-то связь между этими фильмами. Это также был хороший шанс опробовать некоторые новые методы, которые я недавно освоил, как в обучении без учителя, так и в уменьшении размерности.

Моей целью было:

1. Используйте обучение без учителя, чтобы понять, какие типы естественных групп существуют в 250 лучших фильмах всех времен (согласно IMDB).

2. Используйте методы уменьшения размерности, чтобы попытаться улучшить результаты кластеризации для небольшого набора данных, в котором функций очень много по сравнению с количеством выборок.

Сбор и обработка данных

Я использовал данные непосредственно с веб-сайта IMDB, доступ к которому был удобен через API. Их веб-сайт содержал 250 их лучших фильмов:

API требовал ввода идентификатора фильма и возвращал соответствующие сведения о фильме. Сначала я просмотрел страницу 250 лучших фильмов на IMDB, чтобы получить список идентификаторов фильмов.

URL = "http://www.imdb.com/chart/top"
r = requests.get(URL)
soup = BeautifulSoup(r.content, "lxml")
entries=soup.findAll('div', class_="wlb_ribbon")
movie_ids=[]
for a in entries:
    movie_ids.append(a['data-tconst'])

Затем, используя этот список идентификаторов фильмов, я запросил API.

header = 'http://www.omdbapi.com/?i='
movie_info=[]
for i in movie_ids:
    url=header+i+"&"
    r=requests.get(url).json()
    movie=[]
    for a in r.keys():
        movie.append(r[a])
    movie_info.append(movie)
columns=r.keys()
df=pd.DataFrame(movie_info, columns=columns)

По каждому фильму были получены следующие данные:

Я также просмотрел бюджет и доходы каждого фильма, чтобы увидеть, смогу ли я определить, какой тип фильмов принес больше прибыли из 250 лучших.

content=[]
for a in movie_ids:
    URL = "http://www.imdb.com/title/" + a
    r = requests.get(URL)
    content2.append(r.content)
    print "done: " + str(n)
    n+=1
budget=[]
revenue=[]
for soups in contentsoup:
    entries=soups.findAll('div', class_="txt-block")
    try:
        budget.append(float((entries[9].text).split(":")[1].replace(",", "").replace("$", "").split("(")[0].replace(" ", "")))
    except:
        budget.append(np.NaN)
    try:
        revenue.append(float((entries[10].text).split(":")[1].replace(",", "").replace("$", "").split("(")[0].replace(" ", "")))
    except:
        revenue.append(np.NaN)

Затем мне пришлось очистить каждую колонку. Решил включить:

Сюжет, Страна, Сценарист, Режиссер, Актеры, Год, Жанр, Продолжительность

Остальные столбцы были либо слишком очевидными, например IMDB Rating, либо неактуальными, например IMDB ID. Для числовых столбцов Runtime и Year я сначала преобразовал Runtime в число с плавающей запятой за считанные минуты. Затем я преобразовал Год в двоичное значение - 1 для недавнего и 0 для не недавнего. Я выбрал 1990 год как точку отсчета для последних фильмов.

Для остальной части текстовых тяжелых столбцов мне пришлось вытащить наиболее часто встречающиеся слова и создать для них фиктивные столбцы. Сюжет был самой сложной колонкой, потому что в ней было много разных слов.

plots=list(df['Plot'])
temp=""
for p in plots:
        temp= temp + p
temp.replace(".", " ").replace(",", "")
temp=temp.split(" ")
temp=map(lambda x:x.lower(),temp)
import operator
freq={i:temp.count(i) for i in set(temp)}
sort = sorted(freq.items(), key=operator.itemgetter(1), reverse=True)
sort

Просматривая наиболее часто встречающиеся слова, большинство из них были местоимениями и причастиями, такими как «the», «a» и т. Д. Я просмотрел список слов и выбрал первые 20, которые были связаны с типами сюжета.

movie_words= ["young", "man", "help", "find", "life", "against", "war", "police", "family", "journey", 
             "jewish", "son", "boy", "world", "love", "save", "dark", "friends", "murder"]

Все эти слова встречаются как минимум 5 раз в сюжетах 250 лучших фильмов. Есть обычные подозреваемые в интригующих фильмах: «убийство», «любовь», «война». Я сразу замечаю другую закономерность: часто встречаются слова мужского рода, такие как «мужчина», «сын», «мальчик». Это подтверждает некоторую негативную реакцию, которая царила в академии в течение нескольких месяцев, предшествующих получению наград академии, из-за пренебрежения к фильмам с акцентом на меньшинства, усиливая победу Лунного света. Если фильмы естественным образом группируются вокруг этой мужской коннотации, мы надеемся увидеть ее проявление в нашем упражнении по кластеризации.

def plotcounts (a):
    temp=a
    temp.replace(".", " ").replace(",", "")
    temp=temp.split(" ")
    temp=map(lambda x:x.lower(),temp)
    words=[]
    for b in movie_words:
        if b in temp:
            words.append(b)
    return words
df['plotNew']=df['Plot'].apply(plotcounts)
def worddummy (a):
    if m in a:
        return 1
    else:
        return 0
for m in movie_words:
    df[m]=df['plotNew'].apply(worddummy)

Для остальных столбцов, таких как режиссер или актер, я взял верхнюю горстку появившихся актеров или режиссеров и создал для них фиктивные столбцы. Например, с актерами я выбрал 25 лучших актеров, которые появлялись по частоте. Для каждого фильма появлялось несколько актеров / актрис:

temp=[]
def countactors(a):
    temp.append(a.split(", "))
    return temp
b=df['Actors'].apply(countactors)
actors=[]
for a in b[0]:
    for d in a:
        actors.append(d)
freq={i:actors.count(i) for i in set(actors)}
sort = sorted(freq.items(), key=operator.itemgetter(1), reverse=True)
top_actors=[]
for a in sort[0:25]:
    top_actors.append(a[0])

Это было повторено для всех остальных текстовых столбцов. В результате у меня остался фрейм данных с 250 записями и 141 функцией. Это непростая задача для моделирования: с таким количеством функций, относящихся к точкам данных, будет сложно извлечь релевантную информацию, особенно из-за проклятия размерности. По сути, по мере роста набора функций пространство функций растет экспоненциально, увеличивая относительное разделение между точками данных для достижения паритета. Это означает, что набор функций перестает обеспечивать предсказательную силу для группировки похожих точек, потому что все точки одинаково различны. Вы можете думать об этом явлении как о разнице между попыткой затоптать несколько муравьев на полу и ловлей мух в своей комнате. У мух просто больше места в трех измерениях, чем у муравьев в двух измерениях. Точно так же в n-мерном пространстве существуют чрезвычайно большие расстояния между точками, а относительная разница между одной точкой и другой точкой приближается к бесконечности и, следовательно, неотличима друг от друга. Мне пришлось бы использовать уменьшение размерности, чтобы эффективно анализировать этот набор данных.



Моделирование данных

Теперь, когда данные собраны и скомпилированы, я сгруппировал типы фильмов. «Искупление Шоушенка» был признан лучшим фильмом всех времен, но чем он отличается от других фильмов в топ-250?

Анализ основных компонентов:

Перед тем, как приспособить какой-либо алгоритм кластеризации к модели, я уменьшил количество функций с помощью анализа основных компонентов или PCA. Это метод разложения большой матрицы на ее ортогональные составляющие векторы с использованием собственных векторов и собственных значений этой матрицы.



from sklearn.decomposition import PCA
pca = PCA().fit(X)
top_PCA=["%.2f" % a for a in pca.explained_variance_ratio_ if a >0.01]
len(top_PCA)sumall=sum(pca.explained_variance_ratio_)
pca24 = PCA(n_components=24).fit(X)
sum24=sum(pca24.explained_variance_ratio_)
print sum24/sumall

На первые два компонента приходилось ›25%, но остальные компоненты снизились с точки зрения объясненной дисперсии. Я решил взять все компоненты больше 1%, и в результате получилось 24 компонента из 141 функции, с которой я начал. Эти 24 основных компонента объясняют ~ 75% общей дисперсии наших данных.

Я решил глубже изучить наш первый компонент - какие переменные были наиболее важны для первого компонента?

first_comp = pca.components_[0]
first_comps = pd.DataFrame(zip(first_comp, X.columns), columns=['weights', 'features'])
first_comps['abs_weights']=first_comps['weights'].apply(lambda x: np.abs(x))
first_comps.sort('abs_weights', ascending=False)

RuntimeMin появляется первым с наибольшим разбросом. Это единственная непрерывная переменная, поэтому это вызывает беспокойство. Имеет смысл, что время выполнения имеет наибольшую вариацию, закодированную в нем, потому что оно непрерывно, но тот факт, что его вес значительно выше, чем у всех других функций, означает, что, возможно, PCA не так хорошо работает с набором данных, который преимущественно является фиктивным. столбцы. Это хорошая идея, которую следует иметь в виду для будущего использования PCA. Возможно, также поможет выполнение всех фиктивных столбцов вместо того, чтобы оставлять 1 как непрерывную.

Интересны и другие переменные, которые возникают: важно быть драматическим, историческим или биографическим, но не важно, чтобы быть комедией. Это согласуется с нашим пониманием того, что большинство фильмов с самым высоким рейтингом на самом деле серьезны, а не юмористичны.

Кластеризация KMeans:

Xpca24=pca24.transform(X)
from sklearn.cluster import KMeans
for n in range(2,50):
    KM=KMeans(n_clusters=n)
    KM.fit(Xpca24)
    print str(n) + ": "+ str(silhouette_score(Xpca24, KM.labels_, metric='euclidean'))
KM5=KMeans(n_clusters=5)
KM5.fit(Xpca24)
kmeanlabels=KM5.labels_

Теперь, когда у меня было сокращенное пространство функций с 24 функциями, я мог попробовать некоторые алгоритмы кластеризации.

Сначала я протестировал диапазон количества кластеров для алгоритма KMeans. Это n определяет количество кластеров, которые алгоритм пытается определить при моделировании данных. Я смотрел на результат оценки силуэта, когда перебирал буквы n. Похоже, что оценка силуэта существенно не улучшилась для любого конкретного n.

Это затрудняет выбор правильного n, поскольку алгоритм KMeans сильно зависит от этого параметра. Вместо этого я решил попробовать DBScan - алгоритм, который самостоятельно пытается определить подходящее количество кластеров.

Кластеризация сканирования БД:



for eps in [0.1,0.5,1,2,3,4,5,6,7,8,9,10]:
    for min_samples in range(1,20):
        db = DBSCAN(eps=eps, min_samples=min_samples)
        db.fit(Xpca24)
        if len(set(db.labels_))>4 and len(set(db.labels_))<249:
            print str(eps) + " " + str(min_samples) + ": "+ str(silhouette_score(X, db.labels_, metric='euclidean'))
dbOPT = DBSCAN(eps=2, min_samples=5)
dbOPT.fit(Xpca24)
dbscanlabels=dbOPT.labels_
X['kmean']=kmeanlabels
X['dbscan']=dbscanlabels

Точно так же я перебирал различные значения epsilon и min_samples, чтобы найти хорошую оценку силуэта. К сожалению, оценки силуэтов на этом выходе оказались не такими высокими, как я надеялся. Я взял этикетку для наиболее эффективной модели для eps = 2 и min_samples = 5.

Примечание: min_samples не устанавливает количество кластеров, возвращаемых из модели, а скорее минимальное количество выборок рядом с определенной точкой для обозначения кластера.

Оказывается, этот наиболее эффективный алгоритм DBScan сгруппировал данные в 5 классов. Я вернулся к алгоритму KMeans и использовал модель с n_clusters = 5. Используя этикетки обеих моделей, я смог сделать ряд выводов.

Ключевые выводы и заключение

Проблема с уменьшением размерности и кластеризацией - это визуализация и интерпретируемость. Когда используется так много функций, трудно понять результат. Еще больше усложняет ситуацию тот факт, что PCA трансформирует наше пространство функций, так что трудно разобрать, на что разлагается каждый компонент, по сравнению с нашим исходным набором функций.

Я решил просмотреть столбцы в группах, основанных на исходных функциях - директорах, актерах и т. Д. Я построил среднее значение функции на основе различных меток, которые я получил от DBScan. Путем сравнения среднего значения - т.е. если среднее значение столбца для драмы выше для класса 1 по сравнению с классом 2, я могу подразумевать, что класс 1 - это группа, которая имеет большой вес для драматических фильмов. Точно так же, если класс 1 имеет высокое среднее как для драмы, так и для Мартина Скорсезе, я могу сделать вывод, что эта группа должна быть драмами Скорсезе и другими подобными типами фильмов.

Ярлыки для dbscan варьируются от 0 до 4, создавая 5 классов. Те, которые классифицируются как (-1), являются выбросами и не относятся ни к одному кластеру. Давайте посмотрим, какие фильмы попадают в каждую из следующих групп. Я помещаю те, которые помечены как -1, как кластер 5, и те, которые помечены как 0, как кластер 6:

Кластер 1:

Кластер 2:

Кластер 3:

Кластер 4:

Кластер 5:

Кластер 6:

Атрибуты для группы 1:

Сюжет: Помощь, Спасение, Темный

Режиссер: Кристофер Нолан

В ролях: Кристиан Бэйл, Лео Ди Каприо, Том Харди, Майкл Кейн, Хью Джекман

Жанр: драма, приключения, боевик, фантастика

Рейтинг и эра: PG-13, Недавнее

Атрибуты для группы 2:

Сюжет: Помощь, Еврей, Любовь

Актеры: Чарли Чаплин

Жанр: комедия

Атрибуты для группы 3:

Жанр - приключения, мультфильм, семейный

Сюжет- Молодой, Находка, Путешествие, Мир

Режиссер - Хияо Миядзаки

Актер - Тоширо Мифуне

Страна: Япония

Атрибуты для группы 4:

Сюжет: Человек, Война, Мир

Режиссер: Стэнли Кубрик

Актер: Уильям Холден

Жанр: приключения, биография, история

Страна: Великобритания

Время выполнения: ›на 1,5 SD больше среднего

Атрибуты для группы 5:

Сюжет: Против, Война, Мир

Режиссеры: Питер Джексон, Клинт Иствуд, Серхио Леоне, Акира Куросава

Жанр: приключения, вестерн, боевик, фэнтези

Время выполнения: ›на 1 SD больше среднего

Атрибуты для группы 6:

Остальные фильмы, не вошедшие в предыдущие кластеры

__________________________________________________________________

Это непросто сломать, но изучение этих групп помогает нам понять свойства внутри каждой группы. Просматривая их характеристики, я могу более или менее назвать эти группы:

Группа 1 - Вселенная Кристофера Нолана: он обычно работает с одними и теми же актерами (Бейл) и снимается в недавних фильмах PG-13, в драматических и научно-фантастических фильмах. Они говорят о фильмах о Бэтмене. Но это также включает другие его фильмы, такие как «Начало» и «Интерстеллар».

Группа 2 - Комедии Чарли Чаплина: монополизирует большинство комедий из 250 лучших.

Группа 3 - Анимация Хияо Миядзаки: из Японии, включающая приключения и семью с сюжетами о находках, путешествиях и мире.

Группа 4 - Длинные биографические эпопеи. Разнообразные биографические фильмы, обычно во время войны, которые очень продолжительны по продолжительности.

Группа 5. Конфликты в боевиках и приключениях. Разнообразные фильмы о самураях, вестернах и военных фильмах, реальные или фантастические по своей природе.

Группа 6 - Все остальные фильмы. Все остальное, что не попадает в группы ниже, достаточно схожи, поэтому все они объединены в одну группу.

На самом деле из этих группировок я могу сделать несколько важных выводов. Прежде всего, три доминирующих режиссера монополизировали лучшие фильмы и заняли очень специфические ниши - Нолан, Чаплин и Миядзаки. Их можно считать тремя из самых плодовитых и любимых режиссеров / писателей нашего времени. Эти три выдающихся создателя имеют свои собственные группы фильмов в пределах 250 лучших, отделяя себя от других фильмов в пределах 250 лучших по своей тематике (Вселенные Нолана, характеризующиеся тьмой, и герой, спасающий мир), тип (приключенческие анимации Миядзаки), и жанровый (комедии Чаплина). Есть и другие режиссеры Стивен Спилберг, Форд Фрэнсис Кополла и т. Д., Которые также были столь же успешны в топ-250, но другие особенности их фильмов недостаточно четко определены, чтобы их можно было сгруппировать.

Группа 4 состоит из длинных биографических эпосов, а группа 5 - это боевики / приключенческие фильмы. В группу 4 входят биографии, в которых есть сюжеты о «человеке», «войне» и «мире». Эти фильмы в среднем на 1,5 SD выше среднего по времени выполнения, поэтому они выделяются как самые продолжительные фильмы из 250 лучших. Это, как правило, исторические и биографические эпопеи с длительным временем выполнения. Этот кластер объединяет фильмы разных эпох и стран, но все они длинные и биографические, такие как Ганди, Барри Линдон и Лоуренс Аравийский. Это обнадеживает в том, как наша кластеризация позволяет идентифицировать похожие типы фильмов с разными предметами.

Группа 5 включает в себя боевики / приключенческие фильмы из фэнтези, такие как сериал «Властелин колец» и «Безумный Макс», Вторая мировая война (Пианист, Бесславные ублюдки), вестерны («Однажды на Западе», «Хорошие, плохие и уродливые») и японский Самарай. фильмы (Семь Самариев, Йохимбо). Эти два кластера интересны тем, что объединяют в одну группу, казалось бы, разных фильмов и режиссеров. Группа 5 считалась «выпадающей» моделью сканирования БД, но даже этот результат дал полезную информацию. Поскольку они были исключениями, они отделились от Группы 6, в которую входили остальные фильмы, не включенные в другие кластеры. Но эти, казалось бы, несопоставимые фильмы в кластере 5 также обнаруживают некоторые скрытые связи - работы Питера Джексона, Клинта Иствуда, Серджио Леоне и Акиры Куросавы более похожи, чем на первый взгляд.



«Ремейки и фильмы под влиянием произведений Куросавы
Фильмы Акиры Куросавы были адаптированы напрямую, а еще большее количество - это… акиракуросава. Информация"



Фактически, Куросава часто считается отцом многих других поджанров, включая некоторые современные вестерны, такие как «Великолепная семерка» и «Кулак, полный долларов». Обнадеживает то, что алгоритм кластеризации смог выявить лежащие в основе связи между вестернами и самарайскими фильмами, а также влияние между этими главными режиссерами.

Из этого анализа можно также извлечь некоторые уроки. Набор данных, который: 1) содержит большой набор функций, связанных с точками данных, и 2) полный фиктивных столбцов может быть проблемой для создания согласованных, интерпретируемых кластеров. Это важные соображения для будущего анализа. Результаты этого анализа полезно сопоставить с более крупной базой данных фильмов, чтобы увидеть, какие другие фильмы попадают в эти категории и какие типы новых кластеров могут появиться с новыми данными.

Однако использование этой методологии оказалось на удивление полезным и полезным. Этот вывод предлагает схему аналогичной методологии, которую мы видим в алгоритмах прогнозирования фильмов, таких как используемые Netflix. Чтобы рекомендовать похожие фильмы пользователю, использование кластеризации может выявить очевидные сходства - Кристофер Нолан создал массу потрясающих фильмов о Бэтмене - и менее очевидные - есть группа потрясающих боевиков / приключенческие фильмы разных режиссеров и стран, которые очень похожи друг на друга. Неконтролируемое обучение и уменьшение размерности - два мощных инструмента в наборе инструментов машинного обучения, с помощью которых мы можем делать выводы на основе набора данных, даже если нет четкой целевой переменной для прогнозирования.