суббота, 12 февраля 2022 г.

Машинное обучение с Python в маркетинге : предобработка данных

К предобработке данных для машинного обучения относят : Кодирование категориальных переменных , разделение данных на обучающую и тестовую части, нормализацию или стандартизацию.
Для иллюстрации методов, выполняющих такую предобработку создадим  учебный датафрейм с различными категориальными показателями : "Категория товара", в дальнейшем ее можно использовать как метку класса, "Размер", "Бренд", "Цвет" и "Пол". В дальнейшем добавим одну числовую характеристику - "Цена".


df = pd.DataFrame({
'Сategory':np.random.choice(['Cat 1', 'Cat 2', 'Cat 3'],10),    
'Size': np.random.choice(['XS', 'S', 'M', 'L', 'XL', 'XXL'], 10),
'Brand': np.random.choice(['Nike', 'Puma', 'Adidas', 'Le Coq','Reebok'], 10),
'Color': np.random.choice(['Blue', 'Yellow', 'Red'],10),
'Gender': np.random.choice(['F', 'M'],10)    
})

df

СategorySizeBrandColorGender
0Cat 1XXLAdidasRedF
1Cat 2XLAdidasYellowM
2Cat 3MPumaRedF
3Cat 2SLe CoqRedM
4Cat 1SPumaYellowF
5Cat 1MNikeYellowM
6Cat 3XXLReebokBlueF
7Cat 2XXLPumaRedF
8Cat 1XXLLe CoqYellowM
9Cat 1LNikeYellowF


Кодирование категориальных переменных


Когда речь идет о категориальных данных, мы должны дополнительно
проводить различие между именными и порядковыми признаками.
Порядковые признаки - это категориальные значения, которые
допускают сортировку или упорядочение. Например, размер футболки
был бы порядковым признаком, потому что мы можем определить порядок
XXL-XL > L > М>S. Напротив, именные признаки не подразумевают какого-либо
порядка и в контексте примера с футболками мы могли бы считать именным
признаком цвет футболки, т.к. обычно не имеет смысла говорить что-то вроде
того, что красный больше синего.



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

size_mapping = {'XS':1, 'S':2, 'M':3, 'L':4, 'XL':5, 'XXL':6}
df['Size [Ordinal Encoded]'] = df['Size'].map(size_mapping)

df


СategorySizeBrandColorGenderSize [Ordinal Encoded]
0Cat 1XXLAdidasRedF6
1Cat 2XLAdidasYellowM5
2Cat 3MPumaRedF3
3Cat 2SLe CoqRedM2
4Cat 1SPumaYellowF2
5Cat 1MNikeYellowM3
6Cat 3XXLReebokBlueF6
7Cat 2XXLPumaRedF6
8Cat 1XXLLe CoqYellowM6
9Cat 1LNikeYellowF4



Если на более поздней стадии целочисленные значения необходимо
трансформировать в первоначальное строковое представление, тогда мы
можем определить словарь обратного отображения inv_size mapping =
{ v: k for k, v in size_mapping.items () } . Затем он может применяться
при вызове метода map библиотеки pandas на трансформированном столбце
признака подобно использованному ранее словарю size_mapping:

inv_size_mapping = {v: k for k, v in size_mapping.items ()}
df['Size 0']=df['Size [Ordinal Encoded]'].map(inv_size_mapping)

df

СategorySizeBrandColorGenderSize [Ordinal Encoded]Size 0
0Cat 2XXLAdidasRedF6XXL
1Cat 3MReebokRedF3M
2Cat 3XXLNikeRedM6XXL
3Cat 2XSNikeBlueF1XS
4Cat 3XLLe CoqYellowF5XL
5Cat 2XLNikeBlueF5XL
6Cat 3XXLNikeYellowF6XXL
7Cat 1SReebokYellowF2S
8Cat 2MLe CoqYellowF3M
9Cat 1LLe CoqRedM4L


Для перевода метки классов (категория) в целочисленные значения используем класс LabelEncoder из библиотеке scikitleam.

from sklearn.preprocessing import LabelEncoder

class_le = LabelEncoder ()
df['Cat num']=class_le.fit_transform(df['Сategory'].values)

df

СategorySizeBrandColorGenderSize [Ordinal Encoded]Size 0Cat num
0Cat 2XXLAdidasRedF6XXL1
1Cat 3MReebokRedF3M2
2Cat 3XXLNikeRedM6XXL2
3Cat 2XSNikeBlueF1XS1
4Cat 3XLLe CoqYellowF5XL2
5Cat 2XLNikeBlueF5XL1
6Cat 3XXLNikeYellowF6XXL2
7Cat 1SReebokYellowF2S0
8Cat 2MLe CoqYellowF3M1
9Cat 1LLe CoqRedM4L0

А с помощью метода inverse transform целочисленные метки классов можно трансформировать обратно в первоначальное строковое представление:

df['Cat 0']=class_le.inverse_transform(df['Cat num'])

df


СategorySizeBrandColorGenderSize [Ordinal Encoded]Size 0Cat numCat 0
0Cat 2XXLAdidasRedF6XXL1Cat 2
1Cat 3MReebokRedF3M2Cat 3
2Cat 3XXLNikeRedM6XXL2Cat 3
3Cat 2XSNikeBlueF1XS1Cat 2
4Cat 3XLLe CoqYellowF5XL2Cat 3
5Cat 2XLNikeBlueF5XL1Cat 2
6Cat 3XXLNikeYellowF6XXL2Cat 3
7Cat 1SReebokYellowF2S0Cat 1
8Cat 2MLe CoqYellowF3M1Cat 2
9Cat 1LLe CoqRedM4L0Cat 1


Также с помощью метода LabelEncoder кодируем пол

df['Gender Enc']=class_le.fit_transform(df['Gender'].values)

df

СategorySizeBrandColorGenderSize [Ordinal Encoded]Size 0Cat numCat 0Gender Enc
0Cat 2XXLAdidasRedF6XXL1Cat 20
1Cat 3MReebokRedF3M2Cat 30
2Cat 3XXLNikeRedM6XXL2Cat 31
3Cat 2XSNikeBlueF1XS1Cat 20
4Cat 3XLLe CoqYellowF5XL2Cat 30
5Cat 2XLNikeBlueF5XL1Cat 20
6Cat 3XXLNikeYellowF6XXL2Cat 30
7Cat 1SReebokYellowF2S0Cat 10
8Cat 2MLe CoqYellowF3M1Cat 20
9Cat 1LLe CoqRedM4L0Cat 11

Одним из наиболее используемых методов работы с категориальными признаками является подход называемым One Hot Encoding. Основная стратегия состоит в том, чтобы преобразовать значение каждой категории в новую колонку и присваивает значение 1 или 0 (True / False) значение для столбца. 

Есть несколько способов использовать этот метод. Мы представим, как сделать это с Pands, используя функцию get_dammies. Эта функция  создает фиктивные переменные со значениями 0 или 1. Сделаем такое кодирование для цвета. Созданы три дополнительных столбца, по одному для каждого уникальных значений цвета: color_blue, color_red и color_yellow. В зависимости от значения цвета, только один из трех фиктивных колонок имеет значение 1.

df['Color_cat'] = df.Color
df = pd.get_dummies(df, columns=["Color_cat"], prefix=["Color"])
df

СategorySizeBrandColorGenderColor_BlueColor_RedColor_Yellow
0Cat 3LNikeBlueF100
1Cat 1LNikeYellowM001
2Cat 2XSAdidasRedM010
3Cat 1MLe CoqRedM010
4Cat 2SLe CoqBlueF100
5Cat 1XXLPumaRedF010
6Cat 2MAdidasRedM010
7Cat 3MAdidasBlueM100
8Cat 1SReebokBlueM100
9Cat 2XSNikeYellowM001

Поскольку «get_dummies» заменит оригинальный столбец  фиктивными столбцами, мы дублируем колонку цвет, чтобы сохранить исходные значения. Задаем prefix = [« Color»] , чтобы определить имена фиктивных колонок с префиксом «Color». Без этого аргумента фиктивные колонки будут названы значениями цвета, т.е.  имена будут «Blue», «Red» и «Yellow».

Полезные замечания экспертов :

При использовании такого кодирования мы должны помнить, что оно привносит мультиколлинеарность, которая может стать проблемой для определенных методов (например, методов, требующих обращения матриц). Если признаки сильно взаимосвязаны, тогда обращать матрицы будет трудно в вычислительном плане, что может привести к получению численно неустойчивых оценок. Чтобы сократить взаимосвязь между переменными, мы можем просто удалить один столбец признака из
закодированного таким кодом массива. При этом мы не теряем какую-то важную информацию. Скажем, после удаления столбца color_blue информация признака
все равно сохраняется, т.к. из наблюдения color_green=0 и color_ red=0 вытекает, что цветом должен быть blue.

Если мы применяем функцию get dummies, то можем удалить первый
столбец, указав True для параметра drop_first, как показано в
следующем коде:


df['Color_cat'] = df.Color
df = pd.get_dummies(df, columns=["Color_cat"], prefix=["Color"],drop_first=True)
df

СategorySizeBrandColorGenderColor_RedColor_Yellow
0Cat 3XXLPumaRedM10
1Cat 2XLPumaBlueM00
2Cat 2XXLLe CoqYellowM01
3Cat 3XSNikeRedM10
4Cat 1LLe CoqYellowF01
5Cat 2XLPumaYellowF01
6Cat 3MPumaRedF10
7Cat 3XXLLe CoqBlueM00
8Cat 1XSLe CoqBlueM00
9Cat 1LAdidasBlueM00


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

df = pd.DataFrame({
'Сategory':np.random.choice(['Cat 1', 'Cat 2', 'Cat 3'],10),    
'Size': np.random.choice(['XS', 'S', 'M', 'L', 'XL', 'XXL'], 10),
'Brand': np.random.choice(['Nike', 'Puma', 'Adidas', 'Le Coq','Reebok'], 10),
'Color': np.random.choice(['Blue', 'Yellow', 'Red'],10),
'Gender': np.random.choice(['F', 'M'],10),    
'Price': np.random.choice(range(50,200),10)        
})

df

СategorySizeBrandColorGenderPrice
0Cat 2XSNikeYellowM80
1Cat 1LAdidasRedF178
2Cat 2LPumaYellowM83
3Cat 1MPumaBlueF117
4Cat 1SNikeYellowF169
5Cat 1SNikeRedM165
6Cat 3LLe CoqRedM84
7Cat 2XXLNikeBlueF117
8Cat 2SNikeYellowF96
9Cat 1LAdidasYellowM127



df['Price1'] = df['Price'].apply(lambda x : 1 if x < 100 else 0)
df['Price2'] = df['Price'].apply(lambda x : 1 if (x >= 100) & (x < 150) else 0)
df['Price3'] = df['Price'].apply(lambda x : 1 if x >= 150  else 0)

df

СategorySizeBrandColorGenderPricePrice1Price2Price3
0Cat 2XLPumaRedF160001
1Cat 1SNikeYellowM86100
2Cat 2LPumaBlueF180001
3Cat 3MAdidasBlueF194001
4Cat 1XLReebokBlueM164001
5Cat 2MNikeBlueM134010
6Cat 2MPumaRedF191001
7Cat 3XSPumaRedF134010
8Cat 3LNikeBlueM184001
9Cat 2SAdidasBlueF168001


Разбиение набора данных на отдельные обучающий и тестовые наборы

Хороший способ случайного разбиения набора данных на отдельные  обучающий и тестовый поднаборы предусматривает применение функции train_test_split из подмодуля model_selection библиотеки scikit-learn.

from sklearn.model_selection import train_test_split

X,y= df.iloc[:,1:].values,df.iloc[:,0].values
X_train, X_test, y_train, y_test = train_test_split(X, y,test_size=0.3,random_state=0,stratify=y)
print(y_train)
print(y_test)

['Cat 2' 'Cat 2' 'Cat 2' 'Cat 3' 'Cat 3' 'Cat 1' 'Cat 2']
['Cat 1' 'Cat 2' 'Cat 3']

Сначала мы присваиваем переменной Х представление в виде массива
NumPy столбцов признаков, а переменной у - метки классов из  первого столбца. Затем мы используем функцию train_ test_split для случайного разделения Х и у на обучающий и испытательный наборы данных. Установкой test_size=0.3 мы присваиваем 30% образцов данных Х test и у_ test, а оставшиеся 70% образцов  - Х_train и у_train. Предоставление массива меток классов у как аргумента для stratify гарантирует, что и обучающий, и испытательный набор данных имеют такие же доли классов, как у исходного набора данных.

Полезные замечания экспертов :

На практике самыми часто применяемыми разделениями являются 60:40, 70:30 и 80:20 в зависимости от  размера первоначального набора данных. Однако для крупных наборов данных разделение 90:10 или 99:1 на обучающий и испытательный наборы также оказываются допустимым. Если набор данных содержит свыше 100 000 обучающих образцов, то может оказаться неплохим решение удержать только 10000 образцов для испытаний, чтобы получить хорошую оценку эффективности обобщения.

Приведение признаков к одному масштабу


Полезные замечания экспертов :

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

Масштабирование данных в Scikit-Learn может принимать несколько форм, мы введем два из них для показателя Price.

X_train[:,4],X_test[:,4]

(array([180, 168, 134, 194, 184, 164, 191], dtype=object),
 array([86, 160, 134], dtype=object))

Стандартизация: данные масштабируются, чтобы иметь нулевую среднюю и единицу (то есть, одну) дисперсию.

from sklearn.preprocessing import StandardScaler

ss = StandardScaler()

ss.fit(X_train[:,4].reshape(-1, 1))

df_train_ss = ss.transform(X_train[:,4].reshape(-1, 1))
df_test_ss = ss.transform(X_test[:,4].reshape(-1, 1))

df_train_ss,df_test_ss

(array([[ 0.33617681],
        [-0.29135323],
        [-2.06935501],
        [ 1.06829519],
        [ 0.54535349],
        [-0.50052991],
        [ 0.91141268]]),
 array([[-4.57947517],
        [-0.70970659],
        [-2.06935501]]))


Нормализация: данные масштабируются, чтобы охватить определенный диапазон, такой как [0,1].

from sklearn.preprocessing import MinMaxScaler


mms = MinMaxScaler()

mms.fit(X_train[:,4].reshape(-1, 1))

df_train_mms = mms.transform(X_train[:,4].reshape(-1, 1))
df_test_mms = mms.transform(X_test[:,4].reshape(-1, 1))

df_train_mms,df_test_mms

(array([[0.76666667],
        [0.56666667],
        [0.        ],
        [1.        ],
        [0.83333333],
        [0.5       ],
        [0.95      ]]),
 array([[-0.8       ],
        [ 0.43333333],
        [ 0.        ]]))


Полезные замечания экспертов :

Несмотря на то что нормализация посредством масштабирования по
минимаксу является ходовым приемом, который полезен, когда необходимы
значения в ограниченном интервале, стандартизация может быть более
практичной для многих алгоритмов МО, особенно для алгоритмов оптимизации
наподобие градиентного спуска. Причина в том, что многие линейные
модели, такие как  логистическая регрессия и SVM, инициализируют веса нулями или небольшими случайными значениями, близкими к нулю. С помощью стандартизации мы центрируем столбцы признаков относительно среднего значения 0 со стандартным отклонением 1, так что столбцы признаков имеют те же параметры, как нормальное распределение (нулевое среднее и единичная дисперсия), облегчая выяснение
весов. Кроме того, стандартизация сохраняет полезную информацию о выбросах и делает алгоритм менее чувствительным к ним в отличие от масштабирования по минимаксу, которое приводит данные к ограниченному диапазону значений.








Комментариев нет:

Отправить комментарий