Alink's Talk (10): Предварительная обработка данных для реализации линейной регрессии
0x00 сводка
Alink — это платформа алгоритмов машинного обучения нового поколения, разработанная Alibaba на основе вычислительного движка реального времени Flink.Это первая в отрасли платформа машинного обучения, которая поддерживает как пакетные, так и потоковые алгоритмы. В этой и последующих статьях будет рассказано о том, как реализована линейная регрессия в Alink, и я надеюсь, что ее можно будет использовать в качестве дорожной карты, чтобы каждый мог увидеть код линейной регрессии.
Поскольку общедоступная информация Alink слишком мала, нижеследующее - все предположения, и обязательно будут упущения и ошибки. Я надеюсь, что все укажут, и я обновлю их в любое время.
В настоящее время в этой серии десять статей. Добро пожаловать, чтобы дать подсказки.
0x01 Концепция
1.1 Линейная регрессия
Линейная регрессия - это метод статистического анализа, который использует регрессионный анализ в математической статистике для определения взаимозависимых количественных отношений между двумя или более переменными и широко используется. Его выражение имеет вид y = w'x+e, где e — нормальное распределение со средним значением 0.
В линейной регрессии существует линейная корреляция между целевым значением и функцией. То есть предполагается, что это уравнение является линейным уравнением, многомерным линейным уравнением.
Базовая форма: учитывая пример, описанный атрибутами d, линейная модель пытается изучить функцию, которая делает прогнозы с помощью линейной комбинации атрибутов, а именно:
Где w — параметр, также известный как вес, который можно понимать как влияние x1, x2... и xd на f(x).
Общая форма:
Если мы предсказываем f (x) на основе этой формулы, x в формуле нам известен, но значения w и b неизвестны, Пока мы решаем значения w и b, модель можно определить. Мы можем делать прогнозы на основе этой формулы.
Так как же найти оптимальные значения w и b на основе обучающих данных? Суть в том, чтобы измерить разницу между f и y. Это включает в себя другое понятие:Функция потерь.
1.2 Модель оптимизации
Если есть модель f(x), как определить, является ли эта модель отличной? Это качественное суждение может быть измерено значением, известным как эмпирический риск ошибки, который представляет собой сумму ошибок E(x), сделанных моделью f по всем обучающим выборкам.
Мы обучаем модель, минимизируя эмпирические потери на обучающем наборе. Другими словами, регулируя параметр w функции f, эмпирический риск ошибки E(x) постоянно снижается, и когда он, наконец, достигает минимального значения, мы получаем «оптимальную» модель.
Однако, согласно приведенному выше определению, E(x) представляет собой сумму набора индикативных функций, поэтому это разрывная и невыводимая функция, которую нелегко оптимизировать. Для решения этой проблемы предлагается понятие «функция потерь». Функция потерь имеет определенную связь с функцией ошибок (например, верхняя граница функции ошибок), но обладает лучшими математическими свойствами (такими как непрерывность, дифференцирование, выпуклость и т. д.), и ее легче оптимизировать. Таким образом, мы можем оптимизировать функцию потерь.
Если функцию потерь можно вывести непрерывно, мы можем использовать алгоритмы первого порядка, такие как градиентный спуск, или алгоритмы второго порядка, такие как метод Ньютона и метод квазиньютона. Когда алгоритм оптимизации сходится, у нас есть хорошая модель. Если функция потерь является выпуклой функцией, мы можем получить оптимальную модель.
Типичные методы оптимизации:
алгоритм первого порядка | алгоритм второго порядка | |
---|---|---|
Детерминированные алгоритмы | Градиентный спуск Проекционный субградиентный спуск Проксимальный градиентный спуск Алгоритм Франка-Вольфа Ускоренный алгоритм Нестерова Координатный спуск Двухкоординатный подъем | Метод Ньютона, квазиньютоновский метод |
случайный алгоритм | Стохастический градиентный спуск Стохастический координатный спуск Стохастический двухкоординатный подъем Стохастический градиент уменьшения дисперсии | Стохастический квазиньютоновский метод |
Таким образом, мы можем знать, что средства оптимизации модели линейной регрессии f должны быть следующими: определить функцию потерь, использовать x, y в качестве обучения ввода, чтобы получить минимальное значение функции потерь, тем самым определяя параметр w для f. Процесс примерно такой:
-
Обработайте ввод, преобразовав x, y в формат, требуемый алгоритмом.
-
Найдите подходящую функцию предсказания, обычно выражаемую какhфункция, эта функция является функцией классификации, которую нам нужно найти, она используется для прогнозирования результата оценки входных данных.
-
Создайте функцию стоимости (функцию потерь), которая представляет прогнозируемый результат (h) и категорию обучающих данных (y), что может быть разницей между двумя (h-y) или в какой-либо другой форме. Учитывая «потерю» всех обучающих данных, стоимость суммируется или усредняется и записывается как функция **J(θ)**, которая представляет собой отклонение прогнозируемого значения всех обучающих данных от фактической категории.
-
Очевидно, что функция потерьJ(θ)Чем меньше значение функции, тем точнее функция предсказания (т.hфункция является более точной), поэтому на этом шаге необходимо найтиJ(θ)минимальное значение функции. Обратите внимание, что функция потерь примерноθФункция! То есть для функции потерьθБольше не параметр функции, а аргумент функции потерь!
-
Подготовьте метаданные модели и создайте модель.
1.3 Функция потерь и целевая функция
Кратко объясните:
- Функция потерь: вычисляет ошибку выборки;
- Функция стоимости: это среднее значение всех ошибок выборки на всем обучающем наборе, часто смешанное с функцией потерь;
- Целевая функция: функция стоимости + член регуляризации;
Объясните подробно:
Предположим, мы сопоставляем истинное значение Y с f(X). Выход f(X) и реальное значение Y могут быть одинаковыми или разными.Чтобы указать, насколько хорошо мы подходим, мы используем функцию для измерения степени соответствия. Эта функция называется функцией потерь или функцией стоимости.
Функция потерь используется для измерения работы алгоритма и для оценки несоответствия между прогнозируемым значением модели и реальным значением.Это неотрицательная функция с действительным знаком, обычно представляемая L(Y,f( Икс)). Чем меньше функция потерь, тем выше надежность модели. Функция потерьэмпирическая функция рискаосновная часть.
Целевая функция — родственное, но более широкое понятие, а ограниченная минимизация целевой функции — это функция потерь.
Поскольку f(x) может чрезмерно запоминать исторические данные, он не будет хорошо работать в реальных прогнозах, что называется переподгонкой. Полученная функция будет слишком сложной. Таким образом, мы хотим не только минимизировать эмпирический риск, но иминимизация структурных рисков. В настоящее время определена функция J(x), которая специально используется для измеренияСложность модели, также называемая регуляризацией в машинном обучении. Обычно используются нормы L1, L2.
Суть регуляризации L1 заключается в добавлении "Параметры модели подчиняются распределению Лапласа с нулевым средним.«Это предварительное знание.
Суть регуляризации L2 заключается в добавлении "Параметры модели подчиняются нормальному распределению с нулевым средним значением.«Это предварительное знание.
Регуляризация L1 увеличивает сумму абсолютных значений всех параметров веса w, чтобы заставить больше w быть равным нулю, то есть становиться разреженным (L2, потому что его производная также стремится к 0, скорость до нуля не так хороша, как L1 ). Внедрение регуляризации L1 должно завершить славную миссию автоматического выбора признаков, он научится удалять бесполезные признаки, то есть сбрасывать веса, соответствующие этим признакам, на 0.
При регуляризации L2 добавляется сумма квадратов всех весовых параметров w, заставляя все w быть как можно более нулевыми, но не нулевыми (производная L2 стремится к нулю). Поскольку, когда происходит переобучение без добавления регуляризации L2, функция подбора должна заботиться о каждой точке, а окончательная функция подбора сильно колеблется, В некоторых небольших интервалах значение функции резко меняется, то есть некоторые значения w очень велики. С этой целью добавление регуляризации L2 наказывает тенденцию к увеличению весов.
На данный момент мы можем сказать, что наша окончательная функция оптимизации: min(L(Y, f(x) + J(x)) , которая является оптимальным эмпирическим риском и структурным риском, и эта функция называетсяцелевая функция.
В задачах регрессии целевая функция используется для поиска оптимального решения, а функция стоимости квадрата ошибки (линейная регрессия методом наименьших квадратов) обычно используется. Функция потерь представляет собой квадрат функции потерь.
1.4 Метод наименьших квадратов
Среднеквадратическая ошибка является наиболее часто используемой мерой производительности в задачах регрессии, поэтому среднеквадратическая ошибка может быть сведена к минимуму. Метод решения модели, основанный на минимизации среднеквадратичной ошибки, называется «методом наименьших квадратов». В линейной регрессии метод наименьших квадратов заключается в нахождении прямой линии, которая минимизирует «сумму евклидовых расстояний» от всех выборок до прямой линии. Таким образом, функция потерь в линейной регрессии представляет собой квадрат функции потерь.
С этими основными понятиями давайте приступим к анализу кода Alink.
0x02 Пример кода
Сначала приведем пример линейной регрессии.
public class LinearRegressionExample {
static Row[] vecrows = new Row[] {
Row.of("$3$0:1.0 1:7.0 2:9.0", "1.0 7.0 9.0", 1.0, 7.0, 9.0, 16.8),
Row.of("$3$0:1.0 1:3.0 2:3.0", "1.0 3.0 3.0", 1.0, 3.0, 3.0, 6.7),
Row.of("$3$0:1.0 1:2.0 2:4.0", "1.0 2.0 4.0", 1.0, 2.0, 4.0, 6.9),
Row.of("$3$0:1.0 1:3.0 2:4.0", "1.0 3.0 4.0", 1.0, 3.0, 4.0, 8.0)
};
static String[] veccolNames = new String[] {"svec", "vec", "f0", "f1", "f2", "label"};
static BatchOperator vecdata = new MemSourceBatchOp(Arrays.asList(vecrows), veccolNames);
static StreamOperator svecdata = new MemSourceStreamOp(Arrays.asList(vecrows), veccolNames);
public static void main(String[] args) throws Exception {
String[] xVars = new String[] {"f0", "f1", "f2"};
String yVar = "label";
String vec = "vec";
String svec = "svec";
LinearRegression linear = new LinearRegression()
.setLabelCol(yVar) // 这里把变量都设置好了,后续会用到
.setFeatureCols(xVars)
.setPredictionCol("linpred");
Pipeline pl = new Pipeline().add(linear);
PipelineModel model = pl.fit(vecdata);
BatchOperator result = model.transform(vecdata).select(
new String[] {"label", "linpred"});
List<Row> data = result.collect();
}
}
вывод
svec|vec|f0|f1|f2|label|linpred
----|---|--|--|--|-----|-------
$3$0:1.0 1:7.0 2:9.0|1.0 7.0 9.0|1.0000|7.0000|9.0000|16.8000|16.8148
$3$0:1.0 1:3.0 2:4.0|1.0 3.0 4.0|1.0000|3.0000|4.0000|8.0000|7.8521
$3$0:1.0 1:3.0 2:3.0|1.0 3.0 3.0|1.0000|3.0000|3.0000|6.7000|6.7739
$3$0:1.0 1:2.0 2:4.0|1.0 2.0 4.0|1.0000|2.0000|4.0000|6.9000|6.959
Согласно предыдущей статье, мы можем знать, что в задаче регрессии оптимальное решение получается путем оптимизации целевой функции, и обычно используется функция стоимости квадрата ошибки (линейная регрессия по методу наименьших квадратов). Функция потерь представляет собой квадрат функции потерь.
В соответствии с Alink функцией оптимизации или оптимизатором является алгоритм L-BFGS квазиньютоновского метода, целевая функция — UnaryLossObjFunc, а функция потерь — SquareLossFunc. Общая логика обучения линейной регрессии — LinearRegTrainBatchOp. Поэтому мы обсудим их один за другим ниже.
0x03 Общий обзор
В обучении LinearRegression используется LinearRegTrainBatchOp, а базовым классом LinearRegTrainBatchOp является BaseLinearModelTrainBatchOp. Итак, давайте посмотрим на BaseLinearModelTrainBatchOp.
public class LinearRegression extends Trainer <LinearRegression, LinearRegressionModel> implements LinearRegTrainParams <LinearRegression>, LinearRegPredictParams <LinearRegression> {
@Override
protected BatchOperator train(BatchOperator in) {
return new LinearRegTrainBatchOp(this.getParams()).linkFrom(in);
}
}
Код BaseLinearModelTrainBatchOp.linkFrom выглядит следующим образом, а понятная логика приведена в комментариях:
Грубо:
- Получить параметры алгоритма, информацию о метках;
- Подготовить, преобразовать данные в формат Tuple3 ;
- получить статистику, такую как размер вектора, среднее значение и дисперсия;
- Нормализация и интерполяция обучающих данных;
- Используйте алгоритм L-BFGS для оптимизации модели путем минимизации функции потерь;
- Подготовить метаданные модели;
- Моделирование;
public T linkFrom(BatchOperator<?>... inputs) {
BatchOperator<?> in = checkAndGetFirst(inputs);
// Get parameters of this algorithm.
Params params = getParams();
// Get type of processing: regression or not
boolean isRegProc = getIsRegProc(params, linearModelType, modelName);
// Get label info : including label values and label type.
Tuple2<DataSet<Object>, TypeInformation> labelInfo = getLabelInfo(in, params, isRegProc);
// Transform data to Tuple3 format.//weight, label, feature vector.
DataSet<Tuple3<Double, Double, Vector>> initData = transform(in, params, labelInfo.f0, isRegProc);
// Get statistics variables : including vector size, mean and variance of train data.
Tuple2<DataSet<Integer>, DataSet<DenseVector[]>>
statInfo = getStatInfo(initData, params.get(LinearTrainParams.STANDARDIZATION));
// Do standardization and interception to train data.
DataSet<Tuple3<Double, Double, Vector>> trainData = preProcess(initData, params, statInfo.f1);
// Solve the optimization problem.
DataSet<Tuple2<DenseVector, double[]>> coefVectorSet = optimize(params, statInfo.f0,
trainData, linearModelType, MLEnvironmentFactory.get(getMLEnvironmentId()));
// Prepare the meta info of linear model.
DataSet<Params> meta = labelInfo.f0
.mapPartition(new CreateMeta(modelName, linearModelType, isRegProc, params))
.setParallelism(1);
// Build linear model rows, the format to be output.
DataSet<Row> modelRows;
String[] featureColTypes = getFeatureTypes(in, params.get(LinearTrainParams.FEATURE_COLS));
modelRows = coefVectorSet
.mapPartition(new BuildModelFromCoefs(labelInfo.f1,
params.get(LinearTrainParams.FEATURE_COLS),
params.get(LinearTrainParams.STANDARDIZATION),
params.get(LinearTrainParams.WITH_INTERCEPT), featureColTypes))
.withBroadcastSet(meta, META)
.withBroadcastSet(statInfo.f1, MEAN_VAR)
.setParallelism(1);
// Convert the model rows to table.
this.setOutput(modelRows, new LinearModelDataConverter(labelInfo.f1).getModelSchema());
return (T)this;
}
В будущем мы еще доработаем эту логику.
0x04 Основные функции
Сначала мы вводим соответствующие базовые функции и связанные с ними понятия, такие как функция потерь, целевая функция, градиент и т. д.
4.1 Функция потерь
Функция потерь включает в себя несколько понятий.
4.1.1 Производные и частные производные
Производные — это тоже функции, отношения между скоростью изменения функции и ее положением. Производная представляет собой отношение изменения значения функции к изменению независимой переменной, когда изменение независимой переменной стремится к бесконечно малому. Геометрический смысл - это касательная к этой точке. Физический смысл — это (мгновенная) скорость изменения в данный момент.
Производная отражает скорость изменения функции y=f(x) в определенной точке по положительной оси x. Интуитивно, то есть в определенной точке на оси x, если f'(x)>0, это означает, что значение функции f(x) имеет тенденцию к увеличению вдоль положительного направления оси x в точке x ; если f'(x))
Унарная производная характеризует отношение (скорость изменения, наклон) изменения унарной функции f(x) к независимой переменной x вокруг некоторой точки.
А если это многомерная функция? является частной производной. Частная производная - это производная, когда многомерная функция «вырождается» в функцию с одной переменной Здесь «вырожденный» означает фиксирование значения других переменных, сохранение только одной переменной и сохранение каждой переменной по очереди, тогда функция N переменных имеет N частных производных. Частная производная — это производная (наклон касательной) функции вдоль оси независимой переменной в каждом месте. Частная производная бинарной функции характеризует отношение (скорость изменения) изменения функции F(x,y) к независимой переменной x (или y) вокруг некоторой точки.
4.1.2 Производные по направлению
В определении производной и частной производной обсуждается скорость изменения функции вдоль положительного направления координатной оси. Затем, когда мы обсуждаем скорость изменения функции в каком-либо направлении, вводится также определение производной по направлению, то есть значение производной некоторой точки в определенном направлении приближения.
Производная по направлению является внутренним произведением составного вектора частных производных и вектора направления.. Суть производной по направлению — это числовое значение, которое просто определяется как скорость изменения функции в заданном направлении.
4.1.3 Матрица Гессе
В задаче о решении функции одной переменной мы можем успешно использовать метод Ньютона для нахождения стационарной точки. Но в задаче оптимизации машинного обучения мы хотим оптимизировать многомерные функции, и x часто является не действительным числом, а вектором, поэтому, когда метод корня Ньютона используется в машинном обучении, x — это вектор, а y — это вектор. также Вектор, после вывода x получается матрица, которая является матрицей Гессе.
В математике матрица Гессе (или Гессе) представляет собой квадратную матрицу частных производных второго порядка вещественных функций, аргументы которых являются векторами.Вторая производная функции многих переменных представляет собой матрицу Гессе..
4.1.4 Функция квадратичных потерь в Alink
Как упоминалось ранее, функция потерь в линейной регрессии представляет собой квадрат функции потерь. Посмотрим на реализацию. Последующие реализации будут называть такие потери и производные, и мы будем говорить об этом, когда столкнемся с ними.
UnaryLossFunc — это интерфейс, представляющий унарную функцию потерь. Каждая функция, которую она определяет, имеет два входа (eta и y), и Alink использует разницу между этими двумя входами как унарную переменную функции потерь. Основной API заключается в том, чтобы найти убыток, найти производную и найти вторую производную.
public interface UnaryLossFunc extends Serializable {
// Loss function.
double loss(double eta, double y);
// The derivative of loss function.
double derivative(double eta, double y);
// The second derivative of the loss function.
double secondDerivative(double eta, double y);
}
Функция квадрата потерь реализуется следующим образом:
public class SquareLossFunc implements UnaryLossFunc {
@Override
public double loss(double eta, double y) {
return 0.5 * (eta - y) * (eta - y);
}
@Override
public double derivative(double eta, double y) {
return eta - y;
}
@Override
public double secondDerivative(double eta, double y) {
return 1;
}
}
4.2 Целевая функция
Здесь задействована концепция градиента, градиентного спуска.
4.2.1 Градиент
Для оптимизации модели мы хотим выбрать оптимальное θ так, чтобы f(x) было ближе всего к истинному значению. Эта задача сводится к нахождению оптимального θ, при котором функция потерь J(θ) принимает минимальное значение. Так как же решить эту посттрансформационную проблему? Это включает в себя другое понятие:Лучистый спуск.
Итак, сначала мы должны рассмотреть градиент.
- Вектор определяется как величина с направлением и величиной.
- Градиент на самом деле является вектором, то есть имеет направление и величину; он определяется как: многомерная функция получает частные производные для своих независимых переменных, и вектор, составленный из этих частных производных, является градиентом функции.
- Градиент — это наибольшая производная функции по направлению в определенной точке, и функция имеет наибольшую скорость изменения вдоль направления градиента.
- Первое значение градиента — ** «максимальное значение производной по направлению»**.
- Текущее местоположениеНаправление градиента, для функции в этой позицииНаправление с наибольшей производной по направлению, что также является значением функцииСамый быстрый путь вверх, противоположное направление является самым быстрым нисходящим направлением;
- Геометрический смысл градиента заключается в том, что скорость изменения вдоль линии, где расположен вектор, наибольшая.
4.2.2 Градиентный спуск
Метод градиентного спуска представляет собой алгоритм оптимизации первого порядка, основная идея которого заключается в следующем: чтобы как можно быстрее найти локальный минимум функции, он должен находиться в направлении, противоположном (спуску) соответствующему «градиенту» (или приблизительный градиент) в текущей точке функции.Выполнить «итерационный» поиск с заданным размером шага.Двигайтесь в направлении, противоположном градиенту (наклону), то есть "Градиентный спуск".
Поскольку в определенной точке пространства переменных функция имеет наибольшую скорость изменения по направлению градиента, то при оптимизации целевой функции естественно уменьшать значение функции по направлению отрицательного градиента для достижения нашей цели оптимизации.
Спуск в градиентном спуске означает, что неизвестные функции перемещаются в направлении градиента. Каково направление градиента? Заносим эту точку в функцию градиента и результат положительный, затем уменьшаем значение этой точки, и одновременно уменьшаем градиент, когда эту точку заносим в функцию градиента, результат отрицательный, затем Увеличиваем значение этой точки.
Как уменьшить значение функции в направлении отрицательного градиента? Поскольку градиент представляет собой набор частных производных, а и градиент, и частные производные являются векторами, то, обращаясь к векторному алгоритму, мы можем уменьшить соответствующее значение переменной на каждой оси переменных.
Градиентный спуск — это процесс опускания всех частных производных в градиенте до самой низкой точки (выделено: спуск). Все опустилось до нижней точки, затем получено оптимальное решение каждой неизвестной (или размерности), так что это алгоритм решения задачи оптимизации функции.
«Минимальные квадраты» и «градиентный спуск», первый используется для «поиска наименьшей ошибки», последний используется для «поиска с самой высокой скоростью», эти два метода часто используются вместе. Настройка параметров метода наименьших квадратов превращается в задачу нахождения экстремального значения этой бинарной функции, то есть может применяться «метод градиентного спуска».
В функции наименьших квадратов существующими условиями являются некоторые точки выборки и результат точек выборки, то есть матрица X и значение метки y каждой выборки X. X — матрица, а y — вектор. Итак, нам нужно знать, что неизвестными для частных производных в градиентном спуске являются не x и y, а параметр w для x.
4.2.3 Целевая функция в Alink
Базовым классом целевой функции является OptimObjFunc, который предоставляет такие API-интерфейсы, как вычисление градиентов, потерь, гессиана и обновление градиентов и гессиана на основе точек выборки. Ниже приведены некоторые из его производных классов, а область использования можно увидеть из комментариев.
Мы видим норму регуляризации (regularization) L1, L2, которая является добавленным модулем по сравнению с функцией потерь.
public abstract class OptimObjFunc implements Serializable {
protected final double l1;
protected final double l2; // 正则化(regularization) L1, L2范数。
protected Params params;
.....
}
// Unary loss object function.
public class UnaryLossObjFunc extends OptimObjFunc
// The OptimObjFunc for multilayer perceptron.
public class AnnObjFunc extends OptimObjFunc
// Accelerated failure time Regression object function.
public class AftRegObjFunc extends OptimObjFunc
// Softmax object function.
public class SoftmaxObjFunc extends OptimObjFunc
Для линейной модели BaseLinearModelTrainBatchOp будет генерировать целевую функцию в соответствии с типом модели.Вы можете видеть, что при создании целевой функции также соответственно устанавливаются различные функции потерь, о которых SquareLossFunc мы упоминали ранее.
public static OptimObjFunc getObjFunction(LinearModelType modelType, Params params) {
OptimObjFunc objFunc;
// For different model type, we must set corresponding loss object function.
switch (modelType) {
case LinearReg:
// 我们这里!
objFunc = new UnaryLossObjFunc(new SquareLossFunc(), params);
break;
case SVR:
double svrTau = params.get(LinearSvrTrainParams.TAU);
objFunc = new UnaryLossObjFunc(new SvrLossFunc(svrTau), params);
break;
case LR:
objFunc = new UnaryLossObjFunc(new LogLossFunc(), params);
break;
case SVM:
objFunc = new UnaryLossObjFunc(new SmoothHingeLossFunc(), params);
break;
case Perceptron:
objFunc = new UnaryLossObjFunc(new PerceptronLossFunc(), params);
break;
case AFT:
objFunc = new AftRegObjFunc(params);
break;
default:
throw new RuntimeException("Not implemented yet!");
}
return objFunc;
}
4.2.4 Унарная целевая функция в Alink
Унарная целевая функция — это целевая функция, используемая в нашей линейной регрессии, и она имеет только одну новую переменную: unaryLossFunc. — унарная функция потерь.
/**
* Unary loss object function.
*/
public class UnaryLossObjFunc extends OptimObjFunc {
private UnaryLossFunc unaryLossFunc;
}
Унарная целевая функция предоставляет множество функций, основные из которых мы здесь используем:
- calcGradient : вычисляет градиент из набора точек выборки, который интегрируется из базового класса OptimObjFunc.
- updateGradient : обновить градиент в соответствии с точкой выборки;
- calcSearchValues: рассчитать потери для линейного поиска;
4.2.4.1 Вычисление градиента по набору точек выборки
В этой статье обновлен градиент функции потерь.
Опять же, функция потерь используется для измерения степени подгонки, чтобы оценить качество подгонки модели, обозначенной какJ(θ). Обратите внимание, что функция потерь примерноθФункция! То есть для функции потерьθБольше не параметр функции, а аргумент функции потерь!
Когда мы вычисляем потери, мы берем функции в каждой выборкеxiи соответствующее истинное значение целевой переменнойyiВведите функцию потерь, на данный момент осталась только функция потерь.θнеизвестно.
Градиент функции потерь равенθiНайдите частные производные, так как функция потерь составляет околоθфункция, следовательно,θРазные значения , и результирующие векторы градиента тоже разные. Чтобы объяснить, используя метафору «вниз с горы»,θРазличные значения эквивалентны разным положениям на горе, и для каждого положения будет рассчитан вектор градиента ▽J(θ).
Здесь l1, l2 – упомянутая ранее норма регуляризации (регуляризации) L1, L2.
/**
* Calculate gradient by a set of samples.
*
* @param labelVectors train data.
* @param coefVector coefficient of current time.
* @param grad gradient.
* @return weight sum
*/
public double calcGradient(Iterable<Tuple3<Double, Double, Vector>> labelVectors,
DenseVector coefVector, DenseVector grad) {
double weightSum = 0.0;
for (int i = 0; i < grad.size(); i++) {
grad.set(i, 0.0);
}
// 对输入的样本集合labelVectors逐个计算梯度
for (Tuple3<Double, Double, Vector> labelVector : labelVectors) {
if (labelVector.f2 instanceof SparseVector) {
((SparseVector)(labelVector.f2)).setSize(coefVector.size());
}
// 以这个样本为例
labelVector = {Tuple3@9895} "(1.0,16.8,1.0 1.0 1.4657097546055162 1.4770978917519928)"
f0 = {Double@9903} 1.0
f1 = {Double@9904} 16.8
f2 = {DenseVector@9905} "1.0 1.0 1.4657097546055162 1.4770978917519928"
weightSum += labelVector.f0; // labelVector.f0是权重
updateGradient(labelVector, coefVector, grad);
}
if (weightSum > 0.0) {
grad.scaleEqual(1.0 / weightSum);
}
// l2正则化
if (0.0 != this.l2) {
grad.plusScaleEqual(coefVector, this.l2 * 2);
}
// l1正则化
if (0.0 != this.l1) {
double[] coefArray = coefVector.getData();
for (int i = 0; i < coefVector.size(); i++) {
grad.add(i, Math.signum(coefArray[i]) * this.l1);
}
}
return weightSum;
}
4.2.4.2 Обновление градиента из точки выборки
Здесь labelVector.f0 — вес, labelVector.f1 — y, labelVector.f2 — четырехмерный вектор x-vec, а coefVector — вектор коэффициентов w.
- getEta — скалярное произведение, то есть скалярное произведение вектора x и текущего коэффициента w, который представляет собой рассчитанное в данный момент значение y.
- labelVector.f0 * unaryLossFunc.derivative(eta, labelVector.f1) — вызвать функцию SquareLossFunc.derivative для вычисления первой производной.
- updateGrad.plusScaleEqual(labelVector.f2, div); обновляет градиент на основе исходного градиента.
public class UnaryLossObjFunc extends OptimObjFunc {
/**
* Update gradient by one sample.
*
* @param labelVector a sample of train data.
* @param coefVector coefficient of current time.
* @param updateGrad gradient need to update.
*/
@Override
protected void updateGradient(Tuple3<Double, Double, Vector> labelVector, DenseVector coefVector, DenseVector updateGrad) {
// 点积,就是当前计算出来的y
double eta = getEta(labelVector, coefVector);
// 一阶导数。labelVector.f0是权重
double div = labelVector.f0 * unaryLossFunc.derivative(eta, labelVector.f1);
// 点乘之后还需要相加。labelVector.f2 就是x—vec,比如 1.0 1.0 1.4657097546055162 1.4770978917519928
updateGrad.plusScaleEqual(labelVector.f2, div);
}
private double getEta(Tuple3<Double, Double, Vector> labelVector, DenseVector coefVector) {
// 点积,表示第 i 次迭代中节点上的第 k 个特征向量与特征权重分量的点乘。coefVector中第 c 项表示为第 i 次迭代中特征权重向量在第 c 列节点上的分量
return MatVecOp.dot(labelVector.f2, coefVector);
}
}
/**
* Plus with another vector scaled by "alpha".
*/
public void plusScaleEqual(Vector other, double alpha) {
if (other instanceof DenseVector) {
BLAS.axpy(alpha, (DenseVector) other, this);
} else {
BLAS.axpy(alpha, (SparseVector) other, this);
}
}
4.3 Функция оптимизации
Alink предоставляет ряд параллельных функций оптимизации, таких как GD, SGD, LBFGS, OWLQN, метод NEWTON.
Его базовый класс — Optimizer.
public abstract class Optimizer {
protected final DataSet<?> objFuncSet; // 具体目标函数,计算梯度和损失
protected final DataSet<Tuple3<Double, Double, Vector>> trainData; //训练数据
protected final Params params; //参数
protected DataSet<Integer> coefDim; //dimension of features.
protected DataSet<DenseVector> coefVec = null; //最终系数w
.......
}
Линейная регрессия в основном использует алгоритм LBFGS.
public class Lbfgs extends Optimizer
Конкретный вызов выглядит следующим образом
public static DataSet<Tuple2<DenseVector, double[]>> optimize(.....) {
// Loss object function
DataSet<OptimObjFunc> objFunc = session.getExecutionEnvironment()
.fromElements(getObjFunction(modelType, params));
if (params.contains(LinearTrainParams.OPTIM_METHOD)) {
LinearTrainParams.OptimMethod method = params.get(LinearTrainParams.OPTIM_METHOD);
return OptimizerFactory.create(objFunc, trainData, coefficientDim, params, method).optimize();
} else if (params.get(HasL1.L_1) > 0) {
return new Owlqn(objFunc, trainData, coefficientDim, params).optimize();
} else {
// 我们的程序将运行到这里
return new Lbfgs(objFunc, trainData, coefficientDim, params).optimize();
}
}
Основная процедура оптимизации машинного обучения:
准备数据 ----> 优化函数 ----> 目标函数 ----> 损失函数
Нам отвечает вот это
BaseLinearModelTrainBatchOp.linkFrom(整体逻辑) -----> Lbfgs(继承Optimizer) ----> UnaryLossObjFunc(继承OptimObjFunc) ----> SquareLossFunc(继承UnaryLossFunc)
0x05 Подготовка данных
После прочтения основных функций мы снова возвращаемся к общему процессу линейной регрессии.
Подводя итог, основной процесс BaseLinearModelTrainBatchOp.linkFrom выглядит следующим образом: (Было обнаружено, что некоторые носители плохо поддерживают макет списка, поэтому добавьте серийные номера).
Сначала приведите пример ввода:Row.of("$3$0:1.0 1:7.0 2:9.0", "1.0 7.0 9.0", 1.0, 7.0, 9.0, 16.8),
Здесь соответствующие имена столбцов следующих 4 элементов:"f0", "f1", "f2", "label"
.
- 1) Получите информацию о метке, включая значение и тип метки. labelInfo = getLabelInfo() Здесь есть отдельная операция, поэтому дубликаты будут удалены. Наконец, получается возможный диапазон значений метки: 0, 1 и тип Double.
- 2) Используйте функцию преобразования, чтобы преобразовать входные данные в тройку Tuple3. В частности, три функции «f0», «f1», «f2» во входных данных будут преобразованы в вектор vec, который мы позже назовем x-vec. Дело в том, что признак становится вектором. Так что эту тройку можно рассматривать как.
- 3) Используйте statInfo = getStatInfo() для получения статистических переменных, включая размер вектора, среднее значение и дисперсию. Здесь процесс более сложный.
- 3.1) Используйте trainData.map{return value.f2;}, чтобы получить x-vec в обучающих данных.
- 3.2) Вызвать StatisticsHelper.summary для обработки x-vec
- 3.2.1) Сумматор вызовов
- 3.2.1.1) Вызов mapPartition (новый VectorSummarizerPartition (bCov))
- 3.2.1.1.1) Вызовите VectorSummarizerPartition.mapPartition, который проходит по списку, каждая переменная sv в списке является x-vec. srt = srt.visit(sv), count, sum, SquareSum, normL1.. будут пересчитываться для каждого нового ввода, таким образом получая эту статистику для каждого столбца ввода в этом разделе.
- 3.2.1.2) Вызовите reduce(VectorSummarizerUtil.merge(value1, value2)) для слияния результатов каждой секции.
- 3.2.1.1) Вызов mapPartition (новый VectorSummarizerPartition (bCov))
- 3.2.2) Карта вызовов (сумматор BaseVectorSummarizer), по сути, вызов DenseVectorSummarizer сводится к генерации вектора DenseVectorSummary, представляющего собой count, sum, SquareSum, normL1, min, max, numNonZero.
- 3.2.1) Сумматор вызовов
- 3.3) Коэффициент вызоваDim=summary.map
- 3.4) Вызвать meanVar = коэффициентDim.map и, наконец, получить Tuple2.of(coefficientDim, meanVar)
- 4) preProcess(initData, params, statInfo.f1) Использовать результат 3) для стандартизации и интерполяции входных данных Стандартизация и перехват. Среднее значение, полученное выше, будет передано в качестве параметра. Вот нормализация x-vec. Например, исходная строка ввода — «(1.0,16.8,1.0 7.0 9.0)», где x-vec — «1.0 7.0 9.0», после нормализации x-vec становится 4 элемента: { Первый элемент — фиксированное значение " 1.0", поэтому 4 элемента: "1.0 1.0 1.4657097546055162 1.4770978917519928" }, поэтому преобразованная строка "(1.0,16.8,1.0 1.0 1.4657097546055162 1.4770978917519928)". То есть вес равен 1,0, значение y равно 16,8, а следующие 4 — это x-vec.
- Вышеизложенное завершает обработку данных.
- 5) Вызовите optimise(params, statInfo.f0, trainData, linearModelType) для оптимизации модели путем минимизации функции потерь. (Применение алгоритма L-BFGS будет объяснено отдельно)
- 6) Вызовите mapPartition(new CreateMeta()) для подготовки метаданных модели.
- 7) Вызовите mapPartition (новый BuildModelFromCoefs) для построения модели.
Как видите, большую часть занимает подготовка данных, рассмотрим несколько шагов подготовки данных.
5.1 Получить информацию о этикетке
Код здесь соответствует 1) вышеуказанного базового процесса
Так как ранее была отдельная операция, она будет дедуплицирована. Наконец, получается возможный диапазон значений метки: 0, 1 и тип Double.
private Tuple2<DataSet<Object>, TypeInformation> getLabelInfo(BatchOperator in,
Params params,
boolean isRegProc) {
String labelName = params.get(LinearTrainParams.LABEL_COL);
// Prepare label values
DataSet<Object> labelValues;
TypeInformation<?> labelType = null;
if (isRegProc) {
// 因为是回归,所以是这里
labelType = Types.DOUBLE;
labelValues = MLEnvironmentFactory.get(in.getMLEnvironmentId())
.getExecutionEnvironment().fromElements(new Object());
} else {
.....
}
return Tuple2.of(labelValues, labelType);
}
5.2 Преобразование входных данных в триплеты
Код здесь соответствует 2) основного процесса выше.
Используйте функцию преобразования, чтобы преобразовать входные данные в тройку Tuple3. В частности, три функции «f0», «f1», «f2» во входных данных будут преобразованы в вектор vec, который мы позже назовем x-vec. Дело в том, что признак становится вектором. Так что эту тройку можно рассматривать как.
private DataSet<Tuple3<Double, Double, Vector>> transform(BatchOperator in,
Params params,
DataSet<Object> labelValues,
boolean isRegProc) {
......
// 获取Schema
TableSchema dataSchema = in.getSchema();
// 获取各种index
int labelIdx = TableUtil.findColIndexWithAssertAndHint(dataSchema.getFieldNames(), labelName);
......
int weightIdx = weightColName != null ? TableUtil.findColIndexWithAssertAndHint(in.getColNames(), weightColName) : -1;
int vecIdx = vectorColName != null ? TableUtil.findColIndexWithAssertAndHint(in.getColNames(), vectorColName) : -1;
// 用transform函数把输入转换成三元组Tuple3<weight, label, feature vector>
return in.getDataSet().map(new Transform(isRegProc, weightIdx, vecIdx, featureIndices, labelIdx)).withBroadcastSet(labelValues, LABEL_VALUES);
}
Здесь соответствующая переменная выводится как
params = {Params@2745} "Params {featureCols=["f0","f1","f2"], labelCol="label", predictionCol="linpred"}"
labelValues = {DataSource@2845}
isRegProc = true
featureColNames = {String[3]@2864}
0 = "f0"
1 = "f1"
2 = "f2"
labelName = "label"
weightColName = null
vectorColName = null
dataSchema = {TableSchema@2866} "root\n |-- svec: STRING\n |-- vec: STRING\n |-- f0: DOUBLE\n |-- f1: DOUBLE\n |-- f2: DOUBLE\n |-- label: DOUBLE\n"
featureIndices = {int[3]@2878}
0 = 2
1 = 3
2 = 4
labelIdx = 5
weightIdx = -1
vecIdx = -1
В частности, во время выполнения он войдет в функцию Transform.map. Мы видим, что три функции «f0», «f1», «f2» во входных данных преобразуются в вектор vec, который позже мы назовем x-vec.
private static class Transform extends RichMapFunction<Row, Tuple3<Double, Double, Vector>> {
@Override
public Tuple3<Double, Double, Vector> map(Row row) throws Exception {
// 获取权重
Double weight = weightIdx != -1 ? ((Number)row.getField(weightIdx)).doubleValue() : 1.0;
// 获取label
Double val = FeatureLabelUtil.getLabelValue(row, this.isRegProc,
labelIdx, this.positiveLableValueString);
if (featureIndices != null) {
// 获取x-vec
DenseVector vec = new DenseVector(featureIndices.length);
for (int i = 0; i < featureIndices.length; ++i) {
vec.set(i, ((Number)row.getField(featureIndices[i])).doubleValue());
}
// 构建三元组
return Tuple3.of(weight, val, vec);
} else {
Vector vec = VectorUtil.getVector(row.getField(vecIdx));
return Tuple3.of(weight, val, vec);
}
}
}
Если соответствует исходному входуRow.of("$3$0:1.0 1:7.0 2:9.0", "1.0 7.0 9.0", 1.0, 7.0, 9.0, 16.8),
, переменные в программе:
row = {Row@9723} "$3$0:1.0 1:7.0 2:9.0,1.0 7.0 9.0,1.0,7.0,9.0,16.8"
weight = {Double@9724} 1.0
val = {Double@9725} 16.8
vec = {DenseVector@9729} "1.0 7.0 9.0"
vecIdx = -1
featureIndices = {int[3]@9726}
0 = 2
1 = 3
2 = 4
5.3 Получение статистических переменных
Используйте getStatInfo() для стандартизации и интерполяции входных данных Стандартизация и перехват.
Код здесь соответствует 3) вышеописанного базового процесса.
- Используйте statInfo = getStatInfo() для получения статистических переменных, включая размер вектора, среднее значение и дисперсию. Здесь процесс более сложный.
- 3.1) Используйте trainData.map{return value.f2;}, чтобы получить x-vec в обучающих данных.
- 3.2) Вызвать StatisticsHelper.summary для обработки x-vec
- 3.2.1) Сумматор вызовов
- 3.2.1.1) Вызов mapPartition (новый VectorSummarizerPartition (bCov))
- 3.2.1.1.1) Вызовите VectorSummarizerPartition.mapPartition, который проходит по списку, каждая переменная sv в списке является x-vec. srt = srt.visit(sv), count, sum, SquareSum, normL1.. будут пересчитываться для каждого нового ввода, таким образом получая эту статистику для каждого столбца ввода в этом разделе.
- 3.2.1.2) Вызовите reduce(VectorSummarizerUtil.merge(value1, value2)) для слияния результатов каждой секции.
- 3.2.1.1) Вызов mapPartition (новый VectorSummarizerPartition (bCov))
- 3.2.2) Карта вызовов (сумматор BaseVectorSummarizer), по сути, вызов DenseVectorSummarizer сводится к генерации вектора DenseVectorSummary, представляющего собой count, sum, SquareSum, normL1, min, max, numNonZero.
- 3.2.1) Сумматор вызовов
- 3.3) Коэффициент вызоваDim=summary.map
- 3.4) Вызвать meanVar = коэффициентDim.map и, наконец, получить Tuple2.of(coefficientDim, meanVar)
private Tuple2<DataSet<Integer>, DataSet<DenseVector[]>> getStatInfo(
DataSet<Tuple3<Double, Double, Vector>> trainData, final boolean standardization) {
if (standardization) {
DataSet<BaseVectorSummary> summary = StatisticsHelper.summary(trainData.map(
new MapFunction<Tuple3<Double, Double, Vector>, Vector>() {
@Override
public Vector map(Tuple3<Double, Double, Vector> value) throws Exception {
return value.f2; //获取训练数据中的 x-vec
}
}).withForwardedFields());
DataSet<Integer> coefficientDim = summary.map(new MapFunction<BaseVectorSummary, Integer>() {
public Integer map(BaseVectorSummary value) throws Exception {
return value.vectorSize(); // 获取dimension
}
});
DataSet<DenseVector[]> meanVar = summary.map(new MapFunction<BaseVectorSummary, DenseVector[]>() {
public DenseVector[] map(BaseVectorSummary value) {
if (value instanceof SparseVectorSummary) {
// 计算min, max
DenseVector max = ((SparseVector)value.max()).toDenseVector();
DenseVector min = ((SparseVector)value.min()).toDenseVector();
for (int i = 0; i < max.size(); ++i) {
max.set(i, Math.max(Math.abs(max.get(i)), Math.abs(min.get(i))));
min.set(i, 0.0);
}
return new DenseVector[] {min, max};
} else {
// 计算standardDeviation
return new DenseVector[] {(DenseVector)value.mean(),
(DenseVector)value.standardDeviation()};
}
}
});
return Tuple2.of(coefficientDim, meanVar);
}
}
5.4 Нормализация и интерполяция входных данных
Это соответствует 4) основного процесса.
Стандартизация и интерполяция входных данных Стандартизация и перехват. Среднее значение, полученное выше, передается в качестве параметра. Вот нормализация x-vec.
Например, исходная строка ввода"(1.0,16.8,1.0 7.0 9.0)"
, где x-vec"1.0 7.0 9.0"
, после нормализации x-vec становится 4 элемента, первый элемент имеет фиксированное значение "1.0", а 4 элемента"1.0 1.0 1.4657097546055162 1.4770978917519928"
, поэтому преобразованная строка"(1.0,16.8,1.0 1.0 1.4657097546055162 1.4770978917519928)"
.
Почему первый элемент имеет фиксированное значение «1.0»? Так как согласно линейной моделиf(x)=w^Tx+b
, мы должны получить константу b, здесь мы устанавливаем «1.0», что является начальным значением b.
private DataSet<Tuple3<Double, Double, Vector>> preProcess(
return initData.map(
new RichMapFunction<Tuple3<Double, Double, Vector>, Tuple3<Double, Double, Vector>>() {
private DenseVector[] meanVar;
@Override
public Tuple3<Double, Double, Vector> map(Tuple3<Double, Double, Vector> value){
// value = {Tuple3@9791} "(1.0,16.8,1.0 7.0 9.0)"
Vector aVector = value.f2;
// aVector = {DenseVector@9792} "1.0 7.0 9.0"
if (aVector instanceof DenseVector) {
DenseVector bVector;
if (standardization) {
if (hasInterceptItem) {
bVector = new DenseVector(aVector.size() + 1);
bVector.set(0, 1.0); // 设定了固定值
for (int i = 0; i < aVector.size(); ++i) {
// 对输入数据做标准化和插值
bVector.set(i + 1, (aVector.get(i) - meanVar[0].get(i)) / meanVar[1].get(i));
}
}
}
// bVector = {DenseVector@9814} "1.0 1.0 1.4657097546055162 1.4770978917519928"
return Tuple3.of(value.f0, value.f1, bVector);
}
}
}).withBroadcastSet(meanVar, MEAN_VAR);
}
// 这里是对 x-vec 做标准化。比如原始输入Row是"(1.0,16.8,1.0 7.0 9.0)",其中 x-vec 是"1.0 7.0 9.0",进行标准化之后,x-vec 变成了 4 项,第一项是 "1.0 ",是 "1.0 1.0 1.4657097546055162 1.4770978917519928",所以转换后的Row是"(1.0,16.8,1.0 1.0 1.4657097546055162 1.4770978917519928)"
На этом обработка ввода завершена.
Например, исходная строка ввода — «(1,0,16,8,1,0 7,0 9,0)», где x-vec — «1,0 7,0 9,0».
После нормализации x-vec становится 4 элемента: {элемент 1 — фиксированное значение «1.0», поэтому элемент 4 — «1.0 1.0 1.4657097546055162 1.4770978917519928»},
Преобразованная строка "(1.0,16.8,1.0 1.0 1.4657097546055162 1.4770978917519928)". То есть вес равен 1,0, значение y равно 16,8, а следующие 4 — это x-vec.
Теперь мы можем приступить к оптимизации модели, так что следите за обновлениями.
ссылка 0xFF
Наконец-то поймите производные по направлению и градиенты
Введение в производные, направленные производные, градиент и градиентный спуск (неоригинал)
Градиентные векторы и градиентный спуск
Подробное объяснение процесса алгоритма градиентного спуска
Матрица Гессе и ее применение в изображениях
«Алгоритмы распределенного машинного обучения, теория и практика — Лю Теян»
zhuanlan.zhihu.com/p/29672873)
zhuanlan.zhihu.com/p/32821110)
Принцип поиска строки CRF L-BFGS и анализ кода
Размер шага и скорость обучения
Линейная регрессия, градиентный спуск
Серия машинного обучения (3) — целевая функция и функция потерь
★★★★★★Думая о жизни и технологиях★★★★★★
Публичный аккаунт WeChat: мысли Росси
Если вы хотите получать своевременные новости о статьях, написанных отдельными лицами, или хотите видеть технические материалы, рекомендованные отдельными лицами, обратите внимание.