[Python, Программирование, Открытые данные, Машинное обучение] Датасет о мобильных приложениях
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
ВступлениеМоя основная работа связана с мобильной рекламой, и время от времени мне приходится работать с данными о мобильных приложениях. Я решил сделать некоторые данные общедоступными для тех, кто хочет попрактиковаться в построении моделей или получить представление о данных, которые можно собрать из открытых источников. Я считаю, что открытые наборы данных всегда полезны сообществу. Сбор данных часто бывает сложной и унылой работой, и не у всех есть возможность сделать это. В этой статье я представлю датасет и, используя его, построю одну модель.ДанныеДатасет опубликован на сайте Kaggle.DOI: 10.34740/KAGGLE/DSV/2107675.Для 293392 приложений (наиболее популярных) собраны токены описаний и сами данные приложений, включая оригинальное описание. В наборе данных нет имен приложений; их идентифицируют уникальные идентификаторы. Перед токенизацией большинство описаний были переведены на английский язык.В датасете 4 файла:
- bundles_desc.csv —содержит только описания;
- bundles_desc_tokens.csv — содержит токены и жанры;
- bundles_prop.csv, bundles_summary.csv — содержат рпзличные характеристики приложений и даты релиза/обновления.
EDAПрежде всего, давайте посмотрим, как данные распределяются по операционным системам.
Приложения для Android доминируют в данных. Скорее всего, это связано с тем, что создается больше приложений для Android. Учитывая, что набор данных содержит только самые популярные приложения, интересно посмотреть, как распределяется дата выпуска.histnorm ='probability' # type of normalization
Следующий график показывает, что большинство приложений обновляются регулярно.
Основные данные были собраны за короткий период времени в январе 2021 года.
Добавим новую фичу - количество месяцев между датой выпуска и последним обновлением.
df['bundle_update_period'] = \
(pd.to_datetime(
df['bundle_updated_at'], utc=True).dt.tz_convert(None).dt.to_period('M').astype('int') -
df['bundle_released_at'].dt.to_period('M').astype('int'))у
Интересно посмотреть, как распределены жанры приложений. Принимая во внимание дисбаланс ОС, я нормализую данные для гистограммы.
Мы видим, что жанры полностью не пересекаются. Особенно это заметно в играх. Для анализа такая ситуация крайне неприятна. Что мы можем с этим поделать? Самое очевидное - уменьшить количество жанров для Android и привести их к тому же виду, что и для iOS путем сведения всех игровых жанров к одному Games. Но я полагаю, что это не лучший вариант, так как будет потеря информации. Попробуем решить обратную задачу. Для этого нужно построить модель, которая будет предсказывать жанры приложений по их описанию.МодельЯ создал несколько дополнительных фичей, используя длину описания и количество токенов.
def get_lengths(df, columns=['tokens', 'description']):
lengths_df = pd.DataFrame()
for i, c in enumerate(columns):
lengths_df[f"{c}_len"] = df[c].apply(len)
if i > 0:
lengths_df[f"{c}_div"] = \
lengths_df.iloc[:, i-1] / lengths_df.iloc[:, i]
lengths_df[f"{c}_diff"] = \
lengths_df.iloc[:, i-1] - lengths_df.iloc[:, i]
return lengths_df
df = pd.concat([df, get_lengths(df)], axis=1, sort=False, copy=False)
В качестве еще одной фичи я взял количество месяцев, прошедших с даты выпуска приложения. Идея состоит в том, что на рынке в разные периоды могло быть какое-то предпочтение игровым жанрам.
Для обучения используются данные Android-приложений.
android_df = df[df['store_os']=='android']
ios_df = df[df['store_os']=='ios']
Окончательный список фичей модели выглядит следующим образом:
columns = [
'genre', 'tokens', 'bundle_update_period', 'tokens_len',
'description_len', 'description_div', 'description_diff',
'description', 'rating', 'reviews', 'score',
'released_at_month'
]
Я разделил данные на две части - train и validation. Обратите внимание, что разделение должно быть стратифицировано.
train_df, test_df = train_test_split(
android_df[columns], train_size=0.7, random_state=0, stratify=android_df['genre'])
y_train, X_train = train_df['genre'], train_df.drop(['genre'], axis=1)
y_test, X_test = test_df['genre'], test_df.drop(['genre'], axis=1)
В качестве библиотеки для модели я выбрал CatBoost. CatBoost - это высокопроизводительная библиотека для градиентного бустинга на деревьях решений с открытым исходным кодом. Основным преимуществом является то, что CatBoost может использовать категориальные и текстовые фичи без дополнительной предварительной обработки. Текстовые фичи для классификации поддерживаются начиная с версии 0.19.1В Нетрадиционный анализ тональности текста: BERT vs CatBoost я привожу пример того, как CatBoost работает с текстом и сравниваю его с BERT.
!pip install -U catboost
При работе с CatBoost рекомендую использовать Pool. Это удобная оболочка, объединяющая метки и другие метаданные, такие как категориальные и текстовые фичи. Бонусом идет снижение затрат памяти, так как не происходит дополнительная конвертация внутри библиотеки.
train_pool = Pool(
data=X_train,
label=y_train,
text_features=['tokens', 'description']
)
test_pool = Pool(
data=X_test,
label=y_test,
text_features=['tokens', 'description']
)
Напишем функцию для инициализации и обучения модели. Я не подбирал оптимальные параметры; пусть это будет еще одним домашним заданием.
def fit_model(train_pool, test_pool, **kwargs):
model = CatBoostClassifier(
random_seed=0,
task_type='GPU',
iterations=10000,
learning_rate=0.1,
eval_metric='Accuracy',
od_type='Iter',
od_wait=500,
**kwargs
)
return model.fit(
train_pool,
eval_set=test_pool,
verbose=1000,
plot=True,
use_best_model=True
)
Текстовые фичи используются для создания новых числовых фичей. Но для этого необходимо объяснить CatBoost, что именно мы хотим от него получить.CatBoostClassifier имеет несколько параметров:
- tokenizers — используемые для предварительной обработки фичей текстового типа перед созданием словаря;
- dictionaries — используется для предварительной обработки фичей текстового типа;
- feature_calcers —используется для расчета новых фичей;
- text_processing — общий JSON-конфиг для токенизаторов, словарей и вычислителей, который определяет, как текстовые фичи преобразуются в фичи с плавающей точкой.
Четвертый параметр заменяет первые три и, на мой взгляд, самый удобный, так как в одном указывается, как работать с текстом.
tpo = {
'tokenizers': [
{
'tokenizer_id': 'Sense',
'separator_type': 'BySense',
}
],
'dictionaries': [
{
'dictionary_id': 'Word',
'token_level_type': 'Word',
'occurrence_lower_bound': '10'
},
{
'dictionary_id': 'Bigram',
'token_level_type': 'Word',
'gram_order': '2',
'occurrence_lower_bound': '10'
},
{
'dictionary_id': 'Trigram',
'token_level_type': 'Word',
'gram_order': '3',
'occurrence_lower_bound': '10'
},
],
'feature_processing': {
'0': [
{
'tokenizers_names': ['Sense'],
'dictionaries_names': ['Word'],
'feature_calcers': ['BoW']
},
{
'tokenizers_names': ['Sense'],
'dictionaries_names': ['Bigram', 'Trigram'],
'feature_calcers': ['BoW']
},
],
'1': [
{
'tokenizers_names': ['Sense'],
'dictionaries_names': ['Word'],
'feature_calcers': ['BoW', 'BM25']
},
{
'tokenizers_names': ['Sense'],
'dictionaries_names': ['Bigram', 'Trigram'],
'feature_calcers': ['BoW']
},
]
}
}
Запустим обучение:
model_catboost = fit_model(
train_pool, test_pool,
text_processing = tpo
)
Accuracy
Loss
bestTest = 0.6454657601
Только две фичи имеют большое влияние на модель. Скорее всего, качество можно повысить за счет использования summary, но, поскольку этих данных нет в приложениях iOS, быстро применить не удастся. Можно использовать модель, которая может получить короткий абзац текста из описания. Я оставлю это задание в качестве домашнего задания читателям.Судя по цифрам, качество не очень высокое. Основная причина заключается в том, что приложения часто сложно отнести к одному конкретному жанру, и при указании жанра присутствует предвзятость разработчика. Требуется более объективная характеристика, отражающая несколько наиболее подходящих жанров для каждого приложения. Таким признаком может быть вектор вероятностей, где каждый элемент вектора соответствует вероятности отнесения к тому или иному жанру.Чтобы получить такой вектор, нам нужно усложнить процесс, используя предсказания OOF (Out-of-Fold). Не будем использовать сторонние библиотеки; попробуем написать простую функцию.
def get_oof(n_folds, x_train, y, x_test, text_features, seeds):
ntrain = x_train.shape[0]
ntest = x_test.shape[0]
oof_train = np.zeros((len(seeds), ntrain, 48))
oof_test = np.zeros((ntest, 48))
oof_test_skf = np.empty((len(seeds), n_folds, ntest, 48))
test_pool = Pool(data=x_test, text_features=text_features)
models = {}
for iseed, seed in enumerate(seeds):
kf = StratifiedKFold(
n_splits=n_folds,
shuffle=True,
random_state=seed)
for i, (tr_i, t_i) in enumerate(kf.split(x_train, y)):
print(f'\nSeed {seed}, Fold {i}')
x_tr = x_train.iloc[tr_i, :]
y_tr = y[tr_i]
x_te = x_train.iloc[t_i, :]
y_te = y[t_i]
train_pool = Pool(
data=x_tr, label=y_tr, text_features=text_features)
valid_pool = Pool(
data=x_te, label=y_te, text_features=text_features)
model = fit_model(
train_pool, valid_pool,
random_seed=seed,
text_processing = tpo
)
x_te_pool = Pool(
data=x_te, text_features=text_features)
oof_train[iseed, t_i, :] = \
model.predict_proba(x_te_pool)
oof_test_skf[iseed, i, :, :] = \
model.predict_proba(test_pool)
models[(seed, i)] = model
oof_test[:, :] = oof_test_skf.mean(axis=1).mean(axis=0)
oof_train = oof_train.mean(axis=0)
return oof_train, oof_test, models
Обучение трудозатратно, но в результате получили:
- oof_train — OOF-предсказания для Android приложений
- oof_test — OOF-предсказания для iOS приложений
- models — all OOF-модели для каждого фолда и сида
from sklearn.metrics import accuracy_score
accuracy_score(
android_df['genre'].values,
np.take(models[(0,0)].classes_, oof_train.argmax(axis=1)))
За счет фолдов и усреднения по нескольким случайным разбиениям качество немного улучшилось.
OOF accuracy: 0.6560790777135628
Я созданную фичу android_genre_vec, копируем значения из oof_train для приложений Android и oof_test для приложений iOS.
idx = df[df['store_os']=='ios'].index
df.loc[df['store_os']=='ios', 'android_genre_vec'] = \
pd.Series(list(oof_test), index=idx)
idx = df[df['store_os']=='android'].index
df.loc[df['store_os']=='android', 'android_genre_vec'] = \
pd.Series(list(oof_train), index=idx)
Дополнительно был добавлен android_genre, в котором находится жанр с максимальной вероятностью.
df.loc[df['store_os']=='ios', 'android_genre'] = \
np.take(models[(0,0)].classes_, oof_test.argmax(axis=1))
df.loc[df['store_os']=='android', 'android_genre'] = \
np.take(models[(0,0)].classes_, oof_train.argmax(axis=1))
После всех манипуляций, можно наконец-то посмотреть и сравнить распределение приложений по жанрам.
ИтогиВ статье:
- представлен новый бесплатный датасет;
- сделан небольшой EDA;
- созданы несколько новых фичей;
- создана модель для предсказания жанров приложений по описаниям.
Я надеюсь, что этот набор данных будет полезен сообществу и будет использоваться как в моделях, так и для дальнейшего изучения. По мере возможностей, я буду стараться его обновлять.Код из статьи можно посмотреть здесь.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Управление разработкой, Лайфхаки для гиков] Фишки IDEA. Часть 2
- [Программирование, SQL, Алгоритмы, ERP-системы] Множественные источники данных в интерфейсе — client-side «SQL»
- [Разработка веб-сайтов, JavaScript, Программирование, GitHub, Игры и игровые приставки] Разработчик сделал Doom Captcha — теперь можно проходить тест на робота играя
- [Программирование, Управление разработкой, Управление персоналом, Карьера в IT-индустрии] Ловим бандерлогов в офисе
- [Программирование] Именуйте классы, переменные и функции для людей, а не для машин
- [Разработка систем связи, Программирование микроконтроллеров] Power-line communication. Часть 3 — Основные блоки устройства
- [Машинное обучение, Исследования и прогнозы в IT, Учебный процесс в IT, IT-компании] Яндекс вручил премию им. Ильи Сегаловича молодым учёным и научным руководителям
- [PHP, Программирование, Проектирование и рефакторинг, ООП, Go] Prototype Design Pattern в Golang
- [Информационная безопасность, Программирование, Производство и разработка электроники, Гаджеты, Игры и игровые приставки] Часть 3: ESPboy2 — гаджет для ретро игр и экспериментов с IoT, новости проекта 2021
- [Системное программирование, Программирование микроконтроллеров, Компьютерное железо] Предельная скорость USB на STM32F103, чем она обусловлена?
Теги для поиска: #_python, #_programmirovanie (Программирование), #_otkrytye_dannye (Открытые данные), #_mashinnoe_obuchenie (Машинное обучение), #_machinelearning, #_catboost, #_mashinnoe_obuchenie (машинное обучение), #_dataset (датасет), #_datasajns (датасайнс), #_multiklassovaja_klassifikatsija (мультиклассовая классификация), #_gradientnyj_busting (градиентный бустинг), #_mobilnye_prilozhenija (мобильные приложения), #_python, #_programmirovanie (
Программирование
), #_otkrytye_dannye (
Открытые данные
), #_mashinnoe_obuchenie (
Машинное обучение
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 02:55
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
ВступлениеМоя основная работа связана с мобильной рекламой, и время от времени мне приходится работать с данными о мобильных приложениях. Я решил сделать некоторые данные общедоступными для тех, кто хочет попрактиковаться в построении моделей или получить представление о данных, которые можно собрать из открытых источников. Я считаю, что открытые наборы данных всегда полезны сообществу. Сбор данных часто бывает сложной и унылой работой, и не у всех есть возможность сделать это. В этой статье я представлю датасет и, используя его, построю одну модель.ДанныеДатасет опубликован на сайте Kaggle.DOI: 10.34740/KAGGLE/DSV/2107675.Для 293392 приложений (наиболее популярных) собраны токены описаний и сами данные приложений, включая оригинальное описание. В наборе данных нет имен приложений; их идентифицируют уникальные идентификаторы. Перед токенизацией большинство описаний были переведены на английский язык.В датасете 4 файла:
Приложения для Android доминируют в данных. Скорее всего, это связано с тем, что создается больше приложений для Android. Учитывая, что набор данных содержит только самые популярные приложения, интересно посмотреть, как распределяется дата выпуска.histnorm ='probability' # type of normalization Следующий график показывает, что большинство приложений обновляются регулярно. Основные данные были собраны за короткий период времени в январе 2021 года. Добавим новую фичу - количество месяцев между датой выпуска и последним обновлением. df['bundle_update_period'] = \
(pd.to_datetime( df['bundle_updated_at'], utc=True).dt.tz_convert(None).dt.to_period('M').astype('int') - df['bundle_released_at'].dt.to_period('M').astype('int'))у Интересно посмотреть, как распределены жанры приложений. Принимая во внимание дисбаланс ОС, я нормализую данные для гистограммы. Мы видим, что жанры полностью не пересекаются. Особенно это заметно в играх. Для анализа такая ситуация крайне неприятна. Что мы можем с этим поделать? Самое очевидное - уменьшить количество жанров для Android и привести их к тому же виду, что и для iOS путем сведения всех игровых жанров к одному Games. Но я полагаю, что это не лучший вариант, так как будет потеря информации. Попробуем решить обратную задачу. Для этого нужно построить модель, которая будет предсказывать жанры приложений по их описанию.МодельЯ создал несколько дополнительных фичей, используя длину описания и количество токенов. def get_lengths(df, columns=['tokens', 'description']):
lengths_df = pd.DataFrame() for i, c in enumerate(columns): lengths_df[f"{c}_len"] = df[c].apply(len) if i > 0: lengths_df[f"{c}_div"] = \ lengths_df.iloc[:, i-1] / lengths_df.iloc[:, i] lengths_df[f"{c}_diff"] = \ lengths_df.iloc[:, i-1] - lengths_df.iloc[:, i] return lengths_df df = pd.concat([df, get_lengths(df)], axis=1, sort=False, copy=False) В качестве еще одной фичи я взял количество месяцев, прошедших с даты выпуска приложения. Идея состоит в том, что на рынке в разные периоды могло быть какое-то предпочтение игровым жанрам. Для обучения используются данные Android-приложений. android_df = df[df['store_os']=='android']
ios_df = df[df['store_os']=='ios'] columns = [
'genre', 'tokens', 'bundle_update_period', 'tokens_len', 'description_len', 'description_div', 'description_diff', 'description', 'rating', 'reviews', 'score', 'released_at_month' ] train_df, test_df = train_test_split(
android_df[columns], train_size=0.7, random_state=0, stratify=android_df['genre']) y_train, X_train = train_df['genre'], train_df.drop(['genre'], axis=1) y_test, X_test = test_df['genre'], test_df.drop(['genre'], axis=1) !pip install -U catboost
train_pool = Pool(
data=X_train, label=y_train, text_features=['tokens', 'description'] ) test_pool = Pool( data=X_test, label=y_test, text_features=['tokens', 'description'] ) def fit_model(train_pool, test_pool, **kwargs):
model = CatBoostClassifier( random_seed=0, task_type='GPU', iterations=10000, learning_rate=0.1, eval_metric='Accuracy', od_type='Iter', od_wait=500, **kwargs ) return model.fit( train_pool, eval_set=test_pool, verbose=1000, plot=True, use_best_model=True )
tpo = {
'tokenizers': [ { 'tokenizer_id': 'Sense', 'separator_type': 'BySense', } ], 'dictionaries': [ { 'dictionary_id': 'Word', 'token_level_type': 'Word', 'occurrence_lower_bound': '10' }, { 'dictionary_id': 'Bigram', 'token_level_type': 'Word', 'gram_order': '2', 'occurrence_lower_bound': '10' }, { 'dictionary_id': 'Trigram', 'token_level_type': 'Word', 'gram_order': '3', 'occurrence_lower_bound': '10' }, ], 'feature_processing': { '0': [ { 'tokenizers_names': ['Sense'], 'dictionaries_names': ['Word'], 'feature_calcers': ['BoW'] }, { 'tokenizers_names': ['Sense'], 'dictionaries_names': ['Bigram', 'Trigram'], 'feature_calcers': ['BoW'] }, ], '1': [ { 'tokenizers_names': ['Sense'], 'dictionaries_names': ['Word'], 'feature_calcers': ['BoW', 'BM25'] }, { 'tokenizers_names': ['Sense'], 'dictionaries_names': ['Bigram', 'Trigram'], 'feature_calcers': ['BoW'] }, ] } } model_catboost = fit_model(
train_pool, test_pool, text_processing = tpo ) Accuracy Loss bestTest = 0.6454657601
Только две фичи имеют большое влияние на модель. Скорее всего, качество можно повысить за счет использования summary, но, поскольку этих данных нет в приложениях iOS, быстро применить не удастся. Можно использовать модель, которая может получить короткий абзац текста из описания. Я оставлю это задание в качестве домашнего задания читателям.Судя по цифрам, качество не очень высокое. Основная причина заключается в том, что приложения часто сложно отнести к одному конкретному жанру, и при указании жанра присутствует предвзятость разработчика. Требуется более объективная характеристика, отражающая несколько наиболее подходящих жанров для каждого приложения. Таким признаком может быть вектор вероятностей, где каждый элемент вектора соответствует вероятности отнесения к тому или иному жанру.Чтобы получить такой вектор, нам нужно усложнить процесс, используя предсказания OOF (Out-of-Fold). Не будем использовать сторонние библиотеки; попробуем написать простую функцию. def get_oof(n_folds, x_train, y, x_test, text_features, seeds):
ntrain = x_train.shape[0] ntest = x_test.shape[0] oof_train = np.zeros((len(seeds), ntrain, 48)) oof_test = np.zeros((ntest, 48)) oof_test_skf = np.empty((len(seeds), n_folds, ntest, 48)) test_pool = Pool(data=x_test, text_features=text_features) models = {} for iseed, seed in enumerate(seeds): kf = StratifiedKFold( n_splits=n_folds, shuffle=True, random_state=seed) for i, (tr_i, t_i) in enumerate(kf.split(x_train, y)): print(f'\nSeed {seed}, Fold {i}') x_tr = x_train.iloc[tr_i, :] y_tr = y[tr_i] x_te = x_train.iloc[t_i, :] y_te = y[t_i] train_pool = Pool( data=x_tr, label=y_tr, text_features=text_features) valid_pool = Pool( data=x_te, label=y_te, text_features=text_features) model = fit_model( train_pool, valid_pool, random_seed=seed, text_processing = tpo ) x_te_pool = Pool( data=x_te, text_features=text_features) oof_train[iseed, t_i, :] = \ model.predict_proba(x_te_pool) oof_test_skf[iseed, i, :, :] = \ model.predict_proba(test_pool) models[(seed, i)] = model oof_test[:, :] = oof_test_skf.mean(axis=1).mean(axis=0) oof_train = oof_train.mean(axis=0) return oof_train, oof_test, models
from sklearn.metrics import accuracy_score
accuracy_score( android_df['genre'].values, np.take(models[(0,0)].classes_, oof_train.argmax(axis=1))) OOF accuracy: 0.6560790777135628
idx = df[df['store_os']=='ios'].index
df.loc[df['store_os']=='ios', 'android_genre_vec'] = \ pd.Series(list(oof_test), index=idx) idx = df[df['store_os']=='android'].index df.loc[df['store_os']=='android', 'android_genre_vec'] = \ pd.Series(list(oof_train), index=idx) df.loc[df['store_os']=='ios', 'android_genre'] = \
np.take(models[(0,0)].classes_, oof_test.argmax(axis=1)) df.loc[df['store_os']=='android', 'android_genre'] = \ np.take(models[(0,0)].classes_, oof_train.argmax(axis=1)) ИтогиВ статье:
=========== Источник: habr.com =========== Похожие новости:
Программирование ), #_otkrytye_dannye ( Открытые данные ), #_mashinnoe_obuchenie ( Машинное обучение ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 02:55
Часовой пояс: UTC + 5