Alink Talk (9): Хэш функций/стандартизированное масштабирование разработки функций

машинное обучение

Alink Talk (9): Хэш функций/стандартизированное масштабирование разработки функций

0x00 сводка

Alink — это платформа алгоритмов машинного обучения нового поколения, разработанная Alibaba на основе вычислительного движка реального времени Flink.Это первая в отрасли платформа машинного обучения, которая поддерживает как пакетные, так и потоковые алгоритмы. В этой статье будет проанализирована соответствующая реализация кода части «разработка функций» Alink.

0x01 Связанные концепции

1.1 Разработка функций

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

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

Существует несколько типов входных данных для машинного обучения:

  • Числовые характеристики: включая целые числа, типы с плавающей запятой и т. д., которые могут иметь последовательные значения или неупорядоченные данные.
  • Категориальные характеристики: такие как ID, пол и т. д.
  • Характеристики времени: временные ряды, такие как месяц, год, квартал, дата, час и т. д.
  • Пространственные характеристики: широта, долгота и т. д. могут быть преобразованы в почтовый индекс, город и т. д.
  • Особенности текста: документы, естественный язык, предложения и т. д.

Методы проектирования признаков включают в себя:

  • Биннинг

  • Горячее кодирование

  • Трюк с хешированием

  • Встраивание

  • Логарифм (логарифмическое преобразование)

  • Масштабирование функций (Масштабирование)

  • Нормализация

  • Взаимодействие функций

В этой статье объясняется реализация масштабирования и хэширования функций.

1.2 Масштабирование функций (Масштабирование)

Масштабирование признаков — это метод, используемый для стандартизации диапазона независимых переменных или признаков данных. В обработке данных это также называется нормализацией данных и обычно выполняется на этапах предварительной обработки данных. Масштабирование объектов может ограничить широкий диапазон данных заданным диапазоном. Поскольку диапазон значений в необработанных данных сильно различается, в некоторых алгоритмах машинного обучения целевая функция не будет работать должным образом без нормализации. Например, большинство классификаторов вычисляют расстояние между двумя точками по евклидову расстоянию. Если одна из функций имеет широкий диапазон значений, расстояние будет определяться этой конкретной функцией. Следовательно, диапазон всех признаков должен быть нормализован так, чтобы каждый признак был примерно пропорционален конечному расстоянию.

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

  • Мин-макс масштабирование
  • Стандартное (Z) масштабирование

1.3 Трюк с хешированием

Входными требованиями для большинства алгоритмов машинного обучения являются реальные матрицы.Преобразование необработанных данных в реальные матрицы называется проектированием признаков, а хеширование признаков (также называемое трюком с хешированием) — методом проектирования признаков.

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

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

Например, мы будем выполнять хеширование признаков для героев Ляншаня, взяв за пример Гуань Шэна:

Имя: Гуань Шэн

Рейтинг: занять 5-е место

Родной город: Юньчэн (ныне провинция Шаньси — Юньчэн)

Прозвище: Большой нож

Оружие: Лунный меч лазурного дракона.

Звездочка: Тяньюнсин

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

Прототип: В начале династии Южная Сун Лю Юй служил префектом Цзинаня, и армия Цзинь напала на Цзинань. Лю Юй был заманен людьми Цзинь, убил защитника Гуань Шэна и сдал золото. Эта история была романтизирована Цин Чен Ченом и написана в «После водной маржи». Этот Гуань Шэн может быть прототипом в романе.

Внешний вид раунд: 063

Потомки: Гуань Лин, появившийся в «Биографии Саида Юэ Цюань», праведный брат Юэ Юня.

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

После преобразования получается следующее (вымышленное, просто чтобы показать использование ^_^):

// 假设结果是一个 30000 大小的稀疏向量,下面格式是:"index":"value"

"725":"0.8223484445229384" //姓 名
"1000":"0.8444219609970856" //排 名
"4995":"-0.18307661612028242 " //籍 贯
"8049":"0.060151616110215377" //绰 号
"8517":"-0.7340742756048447 " //武 器
"26798":":-0.734299689415312" //星 号
"24390":"0.545435" //相 貌
"25083":"0.4543543" //原 型
"25435":"-0.243432" //出场回合
"25721":"-0.7340742756048447" //后 代

Таким образом, Гуань Шэн становится вектором, который может быть обработан программой.

набор данных 0x02

И наш набор данных, и код примера получены из FTRLExample.

Сначала посмотрите на набор данных.

String schemaStr 
                = "id string, click string, dt string, C1 string, banner_pos int, site_id string, site_domain string, "
                + "site_category string, app_id string, app_domain string, app_category string, device_id string, "
                + "device_ip string, device_model string, device_type string, device_conn_type string, C14 int, C15 int, "
                + "C16 int, C17 int, C18 int, C19 int, C20 int, C21 int";
                
//打印出前面几列看看
trainBatchData.firstN(5).print();

id|click|dt|C1|banner_pos|site_id|site_domain|site_category|app_id|app_domain|app_category|device_id|device_ip|device_model|device_type|device_conn_type|C14|C15|C16|C17|C18|C19|C20|C21
--|-----|--|--|----------|-------|-----------|-------------|------|----------|------------|---------|---------|------------|-----------|----------------|---|---|---|---|---|---|---|---
3199889859719711212|0|14102101|1005|0|1fbe01fe|f3845767|28905ebd|ecad2386|7801e8d9|07d7df22|a99f214a|cfa82746|c6263d8a|1|0|15708|320|50|1722|0|35|-1|79
3200127078337687811|0|14102101|1005|1|e5c60a05|7256c623|f028772b|ecad2386|7801e8d9|07d7df22|a99f214a|ffb0e59a|83ca6fdb|1|0|19771|320|50|2227|0|687|100075|48
3200382705425230287|1|14102101|1005|0|85f751fd|c4e18dd6|50e219e0|98fed791|d9b5648e|0f2161f8|a99f214a|f69683cc|f51246a7|1|0|20984|320|50|2371|0|551|-1|46
320073658191290816|0|14102101|1005|0|1fbe01fe|f3845767|28905ebd|ecad2386|7801e8d9|07d7df22|a99f214a|8e5b1a31|711ee120|1|0|15706|320|50|1722|0|35|100083|79
3200823995473818776|0|14102101|1005|0|f282ab5a|61eb5bc4|f028772b|ecad2386|7801e8d9|07d7df22|a99f214a|9cf693b4|8a4875bd|1|0|18993|320|50|2161|0|35|-1|157

0x03 Пример кода

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

String[] selectedColNames = new String[]{
                "C1", "banner_pos", "site_category", "app_domain",
                "app_category", "device_type", "device_conn_type",
                "C14", "C15", "C16", "C17", "C18", "C19", "C20", "C21",
                "site_id", "site_domain", "device_id", "device_model"};
String[] categoryColNames = new String[]{
                "C1", "banner_pos", "site_category", "app_domain",
                "app_category", "device_type", "device_conn_type",
                "site_id", "site_domain", "device_id", "device_model"};
String[] numericalColNames = new String[]{
                "C14", "C15", "C16", "C17", "C18", "C19", "C20", "C21"};

// setup feature engineering pipeline
Pipeline featurePipeline = new Pipeline()
        .add(   // 特征缩放  
                new StandardScaler()
                        .setSelectedCols(numericalColNames) // 对Double类型的列做变换
        )
        .add(   // 特征哈希
                new FeatureHasher()
                        .setSelectedCols(selectedColNames)
                        .setCategoricalCols(categoryColNames)
                        .setOutputCol(vecColName)
                        .setNumFeatures(numHashFeatures)
        );
// fit feature pipeline model
PipelineModel featurePipelineModel = featurePipeline.fit(trainBatchData);

0x04 Стандартизированное масштабирование StandardScaler

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

О преимуществах масштабирования функций хорошо сказано в онлайн-статье:

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

Однако, когда положительные и отрицательные числа x «похожи», направление изменения градиента можно «исправить», что ускоряет сходимость весов.

Давайте подумаем, что нам нужно делать, если мы делаем нормализованное масштабирование:

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

4.1 StandardScalerTrainBatchOp

Класс StandardScalerTrainBatchOp выполняет стандартную работу, связанную с масштабированием. Здесь преобразуются только столбцы числового типа.

/* StandardScaler transforms a dataset, normalizing each feature to have unit standard deviation and/or zero mean. */
public class StandardScalerTrainBatchOp extends BatchOperator<StandardScalerTrainBatchOp>
    implements StandardTrainParams<StandardScalerTrainBatchOp> {

    @Override
    public StandardScalerTrainBatchOp linkFrom(BatchOperator<?>... inputs) {
        BatchOperator<?> in = checkAndGetFirst(inputs);
        String[] selectedColNames = getSelectedCols();

        StandardScalerModelDataConverter converter = new StandardScalerModelDataConverter();
        converter.selectedColNames = selectedColNames;
        converter.selectedColTypes = new TypeInformation[selectedColNames.length];

        // 获取需要转换的列
        for (int i = 0; i < selectedColNames.length; i++) {
            converter.selectedColTypes[i] = Types.DOUBLE;
        }

//得到变量如下      
converter = {StandardScalerModelDataConverter@9229} 
 selectedColNames = {String[8]@9228} 
  0 = "C14"
  1 = "C15"
  2 = "C16"
  3 = "C17"
  4 = "C18"
  5 = "C19"
  6 = "C20"
  7 = "C21"
 selectedColTypes = {TypeInformation[8]@9231} 
  0 = {FractionalTypeInfo@9269} "Double"
  1 = {FractionalTypeInfo@9269} "Double"
  2 = {FractionalTypeInfo@9269} "Double"
  3 = {FractionalTypeInfo@9269} "Double"
  4 = {FractionalTypeInfo@9269} "Double"
  5 = {FractionalTypeInfo@9269} "Double"
  6 = {FractionalTypeInfo@9269} "Double"
  7 = {FractionalTypeInfo@9269} "Double"      
      
        // 用获取到的列信息通过 StatisticsHelper.summary 做总结,然后通过 BuildStandardScalerModel 进行操作
        DataSet<Row> rows = StatisticsHelper.summary(in, selectedColNames)
            .flatMap(new BuildStandardScalerModel(converter.selectedColNames,
                converter.selectedColTypes,
                getWithMean(),
                getWithStd()));

        this.setOutput(rows, converter.getModelSchema());

        return this;
}

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

summarizer:277, StatisticsHelper (com.alibaba.alink.operator.common.statistics)
summarizer:240, StatisticsHelper (com.alibaba.alink.operator.common.statistics)
summary:71, StatisticsHelper (com.alibaba.alink.operator.common.statistics)
linkFrom:49, StandardScalerTrainBatchOp (com.alibaba.alink.operator.batch.dataproc)
train:22, StandardScaler (com.alibaba.alink.pipeline.dataproc)
fit:34, Trainer (com.alibaba.alink.pipeline)
fit:117, Pipeline (com.alibaba.alink.pipeline)
main:59, FTRLExample (com.alibaba.alink)

План выполнения, созданный StandardScalerTrainBatchOp.linkFrom, логически:

  • 1) Получите информацию о столбце, которую необходимо преобразовать
  • 2) Используйте полученную информацию столбца, чтобы сделать сводку через StatisticsHelper.summary (класс StatisticsHelper — это класс инструментов для пакетного статистического расчета)
    • 2.1) Получить статистику таблицы с сумматором
      • 2.1.1) Используйте in = on.select(selected ColNames); чтобы получить данные, соответствующие столбцу, который необходимо настроить во входных данных
      • 2.1.2) Вызвать функцию суммирования с тем же именем, чтобы выполнить статистику в
        • 2.1.2.1) Вызовите TableSummarizerPartition для подсчета данных каждой секции.
          • 2.1.2.1.1) Вызовите TableSummarizer.visit, чтобы вычислить входящую строку этого раздела (т. е. все данные выше) и получить статистические данные, такие как SquareSum, min, max, normL1.
        • 2.1.2.2) Вернитесь к функции суммирования и вызовите функцию сокращения, чтобы суммировать статистику всех разделов.
    • 2.2) Вызовsummary.toSummary() для результата суммирования, чтобы сопоставить, чтобы получить TableSummary, который представляет собой простую статистику.
  • 3) Генерация операции модели/хранилища по результату StatisticsHelper.summary через flatMap(BuildStandardScalerModel)
    • 3.1) BuildStandardScalerModel.flatMap вызывает StandardScalerModelDataConverter.save
      • 3.1.1) data.add(JsonConverter.toJson(средства)); сохранить средства
      • 3.1.2) data.add(JsonConverter.toJson(stdDevs)); сохранить stdDevs

Конкретный код комбинации выглядит следующим образом

4.2 StatisticsHelper.summary

StatisticsHelper.summary сначала вызывает средство суммирования для суммирования исходной входной таблицы, соответствующей коду 2)

/* table summary, selectedColNames must be set. */
public static DataSet<TableSummary> summary(BatchOperator in, String[] selectedColNames) {
    return summarizer(in, selectedColNames, false) // 将会调用代码 2.1)  
        .map(new MapFunction<TableSummarizer, TableSummary>() {
            @Override
            public TableSummary map(TableSummarizer summarizer) throws Exception {
                return summarizer.toSummary(); // 对应代码 2.2)
            }
        }).name("toSummary");
}

summarizer(in, selectedColNames, false)Получите эти выбранные столбцы из исходного ввода, а затем продолжите вызывать другую функцию суммирования с тем же именем.

/**
 * table stat
 */
private static DataSet<TableSummarizer> summarizer(BatchOperator in, String[]  selectedColNames, boolean calculateOuterProduct) { // 对应代码 2.1)
    in = in.select(selectedColNames); // 对应代码2.1.1)  
    return summarizer(in.getDataSet(), calculateOuterProduct, getNumericalColIndices(in.getColTypes()), selectedColNames); //对应代码2.1.2) 
}

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

/* given data, return summary. numberIndices is the indices of cols which are number type in selected cols. */
private static DataSet<TableSummarizer> summarizer(DataSet<Row> data, boolean bCov, int[] numberIndices, String[] selectedColNames) {
    return data // mapPartition 对应代码 2.1.2.1)
        .mapPartition(new TableSummarizerPartition(bCov, numberIndices, selectedColNames))
        .reduce(new ReduceFunction<TableSummarizer>() { // reduce对应代码 2.1.2.2)
            @Override
            public TableSummarizer reduce(TableSummarizer left, TableSummarizer right) {
                return TableSummarizer.merge(left, right); //最终会merge所有的partition处理结果
            }
        });
}

Для каждой секции TableSummarizerPartition позволяет каждому работнику использовать TableSummarizer.visit для сводки таблицы, которая будет объединена позже. Соответствующий код 2.1.2.1.1).

/* It is table summary partition of one worker, will merge result later. */
public static class TableSummarizerPartition implements MapPartitionFunction<Row, TableSummarizer> {
    @Override
    public void mapPartition(Iterable<Row> iterable, Collector<TableSummarizer> collector) {
        TableSummarizer srt = new TableSummarizer(selectedColNames, numericalIndices, outerProduct);
        srt.colNames = selectedColNames;
        for (Row sv : iterable) {
            srt = (TableSummarizer) srt.visit(sv);
        }
        collector.collect(srt);
    }
}

// 变量如下
srt = {TableSummarizer@10742} "count: 0\n"
sv = {Row@10764} "15708,320,50,1722,0,35,-1,79"
srt.colNames = {String[8]@10733} 
 0 = "C14"
 1 = "C15"
 2 = "C16"
 3 = "C17"
 4 = "C18"
 5 = "C19"
 6 = "C20"
 7 = "C21"  

Мы видим, что в приведенном выше коде функция TableSummarizer.visit вызывается в цикле на итерируемом объекте. то есть черезvisitЧтобы выполнить кумулятивный расчет для каждого элемента ввода (этот элемент представляет собой набор столбцов, соответствующих srt.colNames для создания строки), вычислите, например, SquareSum, min, max, normL1 и т. д., которые отражаются в следующих переменных.

this = {TableSummarizer@10742} "count: 1\nsum: 15708.0 320.0 50.0 1722.0 0.0 35.0 -1.0 79.0\nsquareSum: 2.46741264E8 102400.0 2500.0 2965284.0 0.0 1225.0 1.0 6241.0\nmin: 15708.0 320.0 50.0 1722.0 0.0 35.0 -1.0 79.0\nmax: 15708.0 320.0 50.0 1722.0 0.0 35.0 -1.0 79.0"
 colNames = {String[8]@10733} 
 xSum = null
 xSquareSum = null
 xyCount = null
 numericalColIndices = {int[8]@10734} 
 numMissingValue = {DenseVector@10791} "0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0"
 sum = {DenseVector@10792} "15708.0 320.0 50.0 1722.0 0.0 35.0 -1.0 79.0"
 squareSum = {DenseVector@10793} "2.46741264E8 102400.0 2500.0 2965284.0 0.0 1225.0 1.0 6241.0"
 min = {DenseVector@10794} "15708.0 320.0 50.0 1722.0 0.0 35.0 -1.0 79.0"
 max = {DenseVector@10795} "15708.0 320.0 50.0 1722.0 0.0 35.0 -1.0 79.0"
 normL1 = {DenseVector@10796} "15708.0 320.0 50.0 1722.0 0.0 35.0 1.0 79.0"
 vals = {Double[8]@10797} 
 outerProduct = null
 count = 1
 calculateOuterProduct = false

4.3 BuildStandardScalerModel

Функция здесь заключается в создании моделей/магазинов.

/* table summary build model. */
public static class BuildStandardScalerModel implements FlatMapFunction<TableSummary, Row> {
    private String[] selectedColNames;
    private TypeInformation[] selectedColTypes;
    private boolean withMean;
    private boolean withStdDevs;

    @Override
    public void flatMap(TableSummary srt, Collector<Row> collector) throws Exception {
        if (null != srt) {
            StandardScalerModelDataConverter converter = new StandardScalerModelDataConverter();
            converter.selectedColNames = selectedColNames;
            converter.selectedColTypes = selectedColTypes;
            // 业务
            converter.save(new Tuple3<>(this.withMean, this.withStdDevs, srt), collector);
        }
    }
}

Функция сохранения вызывает StandardScalerModelDataConverter.save, и логика относительно ясна:

  1. хранить среднее значение
  2. хранить стандартные разработки
  3. Построить параметры метаданных
  4. Сериализация
  5. отправить сериализованный результат
/*
 * Serialize the model data to "Tuple3<Params, List<String>, List<Row>>".
 *
 * @param modelData The model data to serialize.
 * @return The serialization result.
 */
@Override
public Tuple3<Params, Iterable<String>, Iterable<Row>> serializeModel(Tuple3<Boolean, Boolean, TableSummary> modelData) {
    Boolean withMean = modelData.f0;
    Boolean withStandarDeviation = modelData.f1;
    TableSummary summary = modelData.f2;

    String[] colNames = summary.getColNames();
    double[] means = new double[colNames.length];
    double[] stdDevs = new double[colNames.length];

    for (int i = 0; i < colNames.length; i++) {
        means[i] = summary.mean(colNames[i]); // 1. 存储mean
        stdDevs[i] = summary.standardDeviation(colNames[i]); // 2. 存储stdDevs
    }

    for (int i = 0; i < colNames.length; i++) {
        if (!withMean) {
            means[i] = 0;
        }
        if (!withStandarDeviation) {
            stdDevs[i] = 1;
        }
    }

    // 3. 构建元数据Params
    Params meta = new Params()
        .set(StandardTrainParams.WITH_MEAN, withMean)
        .set(StandardTrainParams.WITH_STD, withStandarDeviation);

    // 4. 序列化
    List<String> data = new ArrayList<>();
    data.add(JsonConverter.toJson(means));
    data.add(JsonConverter.toJson(stdDevs));

    return new Tuple3<>(meta, data, new ArrayList<>());
}

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

save:68, RichModelDataConverter (com.alibaba.alink.common.model)
flatMap:84, StandardScalerTrainBatchOp$BuildStandardScalerModel (com.alibaba.alink.operator.batch.dataproc)
flatMap:63, StandardScalerTrainBatchOp$BuildStandardScalerModel (com.alibaba.alink.operator.batch.dataproc)
collect:80, ChainedFlatMapDriver (org.apache.flink.runtime.operators.chaining)
collect:35, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
collect:79, ChainedMapDriver (org.apache.flink.runtime.operators.chaining)
collect:35, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
run:152, AllReduceDriver (org.apache.flink.runtime.operators)
run:504, BatchTask (org.apache.flink.runtime.operators)
invoke:369, BatchTask (org.apache.flink.runtime.operators)
doRun:707, Task (org.apache.flink.runtime.taskmanager)
run:532, Task (org.apache.flink.runtime.taskmanager)
run:748, Thread (java.lang)
  
// 以下是输入  
modelData = {Tuple3@10723} 
 f0 = {Boolean@10726} true
 f1 = {Boolean@10726} true
 f2 = {TableSummary@10707} "colName|count|numMissingValue|numValidValue|sum|mean|variance|standardDeviation|min|max|normL1|normL2\r\n-------|-----|---------------|-------------|---|----|--------|-----------------|---|---|------|------\nC14|399999|0.0000|399999.0000|7257042877.0000|18142.6525|10993280.1107|3315.6116|375.0000|21705.0000|7257042877.0000|11664445.8724\nC15|399999|0.0000|399999.0000|127629988.0000|319.0758|411.3345|20.2814|120.0000|1024.0000|127629988.0000|202208.2328\nC16|399999|0.0000|399999.0000|22663266.0000|56.6583|1322.7015|36.3690|20.0000|768.0000|22663266.0000|42580.9842\nC17|399999|0.0000|399999.0000|809923879.0000|2024.8148|170166.5008|412.5124|112.0000|2497.0000|809923879.0000|1306909.3634\nC18|399999|0.0000|399999.0000|414396.0000|1.0360|1.5871|1.2598|0.0000|3.0000|414396.0000|1031.5736\nC19|399999|0.0000|399999.0000|77641159.0000|194.1034|73786.4929|271.6367|33.0000|1835.0000|77641159.0000|211151.2756\nC20|399999|0.0000|399999.0000|16665597769.0000|41664.0986|2434589745.2799|49341.5620|-1.0000|100"
  colNames = {String[8]@10728} 
  numMissingValue = {DenseVector@10729} "0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0"
  sum = {DenseVector@10730} "7.257042877E9 1.27629988E8 2.2663266E7 8.09923879E8 414396.0 7.7641159E7 1.6665597769E10 3.0589982E7"
  squareSum = {DenseVector@10731} "1.36059297509295E14 4.0888169392E10 1.813140212E9 1.708012084269E12 1064144.0 4.4584861175E10 1.668188137320503E15 3.044124336E9"
  min = {DenseVector@10732} "375.0 120.0 20.0 112.0 0.0 33.0 -1.0 13.0"
  max = {DenseVector@10733} "21705.0 1024.0 768.0 2497.0 3.0 1835.0 100248.0 195.0"
  normL1 = {DenseVector@10734} "7.257042877E9 1.27629988E8 2.2663266E7 8.09923879E8 414396.0 7.7641159E7 1.6666064771E10 3.0589982E7"
  numericalColIndices = {int[8]@10735} 
  count = 399999  
  
// 这是输出    
model = {Tuple3@10816} "(Params {withMean=true, withStd=true},[[18142.652549131373,319.07576768941925,56.658306645766615,2024.814759536899,1.035992589981475,194.1033827584569,41664.098582746454,76.47514618786548], [3315.6115741652725,20.281383913437733,36.36896282478844,412.51242496870356,1.259797591740416,271.6366927754722,49341.56204742555,41.974829196745965]],[])"
 f0 = {Params@10817} "Params {withMean=true, withStd=true}"
 f1 = {ArrayList@10820}  size = 2
  0 = "[18142.652549131373,319.07576768941925,56.658306645766615,2024.814759536899,1.035992589981475,194.1033827584569,41664.098582746454,76.47514618786548]"
  1 = "[3315.6115741652725,20.281383913437733,36.36896282478844,412.51242496870356,1.259797591740416,271.6366927754722,49341.56204742555,41.974829196745965]"
 f2 = {ArrayList@10818}  size = 0  

4.4 Преобразование картографа

После обучения при преобразовании каждая строка элемента сопоставляется, а ранее рассчитанные средние/стандартные отклонения используются для конкретной стандартизации.

@Override
public Row map(Row row) throws Exception {
    Row r = new Row(this.selectedColIndices.length);
    for (int i = 0; i < this.selectedColIndices.length; i++) {
        Object obj = row.getField(this.selectedColIndices[i]);
        if (null != obj) {
            if (this.stddevs[i] > 0) {
                double d = (((Number) obj).doubleValue() - this.means[i]) / this.stddevs[i];
                r.setField(i, d);
            } else {
                r.setField(i, 0.0);
            }
        }
    }
    return this.predResultColsHelper.getResultRow(row, r);
}

// means,stddevs 是对应那几列之前统计出来的总体数值,是根据这些来进行转换的。
this = {StandardScalerModelMapper@10909} 
 selectedColNames = {String[8]@10873} 
 selectedColTypes = {TypeInformation[8]@10874} 
 selectedColIndices = {int[8]@10912} 
 means = {double[8]@10913} 
  0 = 18142.652549131373
...
  7 = 76.47514618786548
 stddevs = {double[8]@10914} 
  0 = 3315.6115741652725
...
  7 = 41.974829196745965

Переменные следующие: Row — входные данные, r — данные, сгенерированные после преобразования данных, которые необходимо преобразовать.

После нормализации используйте OutputColsHelper.getResultRow для объединения Row и r.

row = {Row@10865} "3200382705425230287,1,14102101,1005,0,85f751fd,c4e18dd6,50e219e0,98fed791,d9b5648e,0f2161f8,a99f214a,f69683cc,f51246a7,1,0,20984,320,50,2371,0,551,-1,46"
其中 "20984,320,50,2371,0,551,-1,46" 是需要转换的数据。
    
r = {Row@10866} "0.8569602884149525,0.04557047559108551,-0.18307661612028242,0.8392116685682023,-0.8223484445229384,1.313874843618953,-0.8444219609970856,-0.7260338343491822"
这里是上面需要转换的数据进行标准化之后的结果

Стек выглядит следующим образом

getResultRow:177, OutputColsHelper (com.alibaba.alink.common.utils)
map:88, StandardScalerModelMapper (com.alibaba.alink.operator.common.dataproc)
map:43, ModelMapperAdapter (com.alibaba.alink.common.mapper)
map:18, ModelMapperAdapter (com.alibaba.alink.common.mapper)
run:103, MapDriver (org.apache.flink.runtime.operators)
run:504, BatchTask (org.apache.flink.runtime.operators)
invoke:369, BatchTask (org.apache.flink.runtime.operators)
doRun:707, Task (org.apache.flink.runtime.taskmanager)
run:532, Task (org.apache.flink.runtime.taskmanager)
run:748, Thread (java.lang)

0x05 FeatureHasher

FeatureHasher дополняет функцию хеширования признаков, которая сопоставляется без обучения. Конкретные детали таковы:

  • Проецируйте категориальные признаки или числовые признаки на векторы признаков данного домена.
  • Используйте алгоритм MurMurHash3.
  • Для категориальных признаков используйте для хеширования "colName=value". colName — это имя столбца функции, а значение — значение функции. Соответствующее значение хеш-функции равно 1,0.
  • Для числовых функций используйте "colName" для хеширования. Соответствующее хеш-значение является собственным значением
  • категориальные признаки или числовые признаки обнаруживаются автоматически.

Посмотрите на соответствующий код.

5.1 Разреженные матрицы

Наконец, генерируется матрица признаков размером 30000 с окончательным названием «vec». Вот разреженная матрица.

String vecColName = "vec";
int numHashFeatures = 30000;
// setup feature engineering pipeline
Pipeline featurePipeline = new Pipeline()
        .add(
                new StandardScaler()
                        .setSelectedCols(numericalColNames)
        )
        .add(
                new FeatureHasher()
                        .setSelectedCols(selectedColNames)
                        .setCategoricalCols(categoryColNames)
                        .setOutputCol(vecColName)
                        .setNumFeatures(numHashFeatures)
        );

5.2 FeatureHasherMapper

При передаче в функцию карты Row является «нормализованными данными исходных данных».

Пройдите по столбцу числовых признаков и выполните хеш-преобразование; пройдите по столбцу категориальных признаков и выполните хэш-преобразование.

public class FeatureHasherMapper extends Mapper {
    /**
     * Projects a number of categorical or numerical features into a feature vector of a specified dimension.
     *
     * @param row the input Row type data
     * @return the output row.
     */
    @Override
    public Row map(Row row) {
        TreeMap<Integer, Double> feature = new TreeMap<>();
        // 遍历数值特征列,进行哈希变换;
        for (int key : numericColIndexes) {
            if (null != row.getField(key)) {
                double value = ((Number)row.getField(key)).doubleValue();
                String colName = colNames[key];
                updateMap(colName, value, feature, numFeature);
            }
        }
        // 遍历categorical特征列,进行哈希转换
        for (int key : categoricalColIndexes) {
            if (null != row.getField(key)) {
                String colName = colNames[key];
                updateMap(colName + "=" + row.getField(key).toString(), 1.0, feature, numFeature);
            }
        }

        return outputColsHelper.getResultRow(row, Row.of(new SparseVector(numFeature, feature)));
    }  
}

//运行时候打印变量如下
selectedCols = {String[19]@9817} 
 0 = "C1"
 1 = "banner_pos"
 2 = "site_category"
 3 = "app_domain"
 4 = "app_category"
 5 = "device_type"
 6 = "device_conn_type"
 7 = "C14"
 8 = "C15"
 9 = "C16"
 10 = "C17"
 11 = "C18"
 12 = "C19"
 13 = "C20"
 14 = "C21"
 15 = "site_id"
 16 = "site_domain"
 17 = "device_id"
 18 = "device_model"
   
numericColIndexes = {int[8]@10789} 
 0 = 16
 1 = 17
 2 = 18
 3 = 19
 4 = 20
 5 = 21
 6 = 22
 7 = 23
   
categoricalColIndexes = {int[11]@10791} 
 0 = 3
 1 = 4
 2 = 7
 3 = 9
 4 = 10
 5 = 14
 6 = 15
 7 = 5
 8 = 6
 9 = 11
 10 = 13   

5.3 Операция хеширования updateMap

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

Используемая конкретная хэш-функцияorg.apache.flink.shaded.guava18.com.google.common.hash.

/* Update the treeMap which saves the key-value pair of the final vector, use the hash value of the string as key
 * and the accumulate the corresponding value.
 *
 * @param s     the string to hash
 * @param value the accumulated value */
private static void updateMap(String s, double value, TreeMap<Integer, Double> feature, int numFeature) {
    // HASH = {Murmur3_32HashFunction@10755} "Hashing.murmur3_32(0)" 
    int hashValue = Math.abs(HASH.hashUnencodedChars(s).asInt());

    int index = Math.floorMod(hashValue, numFeature);
    if (feature.containsKey(index)) {
        feature.put(index, feature.get(index) + value);
    } else {
        feature.put(index, value);
    }
}

Например, когда сделан следующий ввод, индекс равен 26798, поэтому значение будет установлено на 26798 в vec.

s = "C14"
value = 0.33428145187593655
feature = {TreeMap@10836}  size = 1
 {Integer@10895} 26798 -> {Double@10896} 0.33428145187593655
numFeature = 30000
hashValue = 23306798
index = 26798

После окончательного хэша функции результирующий vec будет добавлен к 25-му элементу в исходной строке (первоначально 24 элемента, теперь элемент добавляется в конец), что выглядит следующим образом.24 = {SparseVector@10932}.

row = {Row@10901} 
 fields = {Object[25]@10907} 
  0 = "3199889859719711212"
  1 = "0"
  2 = "14102101"
  3 = "1005"
  4 = {Integer@10912} 0
  5 = "1fbe01fe" // "device_type" 是这个数值,这个是原始输入,大家如果遗忘可以回头看看示例代码输出。
  6 = "f3845767"
  7 = "28905ebd"
  8 = "ecad2386"
  9 = "7801e8d9"
  10 = "07d7df22"
  11 = "a99f214a"
  12 = "cfa82746"
  13 = "c6263d8a"
  14 = "1"
  15 = "0"
  16 = {Double@10924} -0.734299689415312
  17 = {Double@10925} 0.04557047559108551
  18 = {Double@10926} -0.18307661612028242
  19 = {Double@10927} -0.7340742756048447
  20 = {Double@10928} -0.8223484445229384
  21 = {Double@10929} -0.5857212482334542
  22 = {Double@10930} -0.8444219609970856
  23 = {Double@10931} 0.060151616110215377
  24 = {SparseVector@10932} "$30000$725:-0.8223484445229384 1000:1.0 3044:-0.8444219609970856 4995:-0.18307661612028242 8049:0.060151616110215377 8517:1.0 10962:1.0 17954:1.0 18556:1.0 21430:1.0 23250:1.0 24010:1.0 24390:1.0 25083:0.04557047559108551 25435:-0.5857212482334542 25721:-0.7340742756048447 26169:1.0 26798:-0.734299689415312 29671:1.0"
    
// 30000 表示一共是30000大小的稀疏向量
// 725:-0.8223484445229384 表示第725的item中的数值是-0.8223484445229384,依次类推。    

ссылка 0xFF

Зачем использовать нулевое среднее в предварительной обработке изображений для глубокого обучения

Хеширование функций для обработки данных

Введение в технологии, связанные с проектированием признаков

Хеширование функций

★★★★★★Думая о жизни и технологиях★★★★★★

Публичный аккаунт WeChat: мысли Росси

Если вы хотите получать своевременные новости о статьях, написанных отдельными лицами, или хотите видеть технические материалы, рекомендованные отдельными лицами, обратите внимание.