Множество валидационное (Validation set)
Валидационным называется подмножество примеров обучающего набора данных, формирующееся независимо от обучающего и тестового множеств, и использующееся для проверки предсказательной способности модели после ее обучения и тестирования, а также оптимизации ее сложности.
Валидационное множество обеспечивает объективную оценку при настройке параметров архитектуры модели (например, количество скрытых нейронов в нейронной сети с целью уменьшения эффекта переобучения).
Одним из наиболее эффективных методов формирования и применения валидационных множеств является перекрёстная проверка.
Настройка гиперпараметров
Гиперпараметр (англ. hyperparameter) — параметр, который не настраивается во время обучения модели. Пример гиперпараметра — шаг градиентного спуска, он задается перед обучением. Пример параметров — веса градиентного спуска, они изменяются и настраиваются во время обучения.
Для подбора гиперпараметров необходимо разделить датасет на три части:
- тренировочный набор данных (англ. training set), для обучения модели
- валидационный набор данных (англ. validation set), для расчета ошибки и выбора наилучшей модели
- тестовый набор данных (англ. test set), для тестирования выбранной модели
Зачем нам нужен и валидационный, и тестовый набор? Дело в том, что модель может переучиться на валидационном наборе данных. Для выявления переобучения используется тестовый набор данных.
Рассмотрим модель KNeighborsClassifier из библиотеки sklearn. Все “параметры” данной модели (loss, penalty, alpha и т.д), с точки зрения машинного обучения, являются гиперпараметрами, так как задаются до начала обучения.
class sklearn.linear_model.SGDClassifier(loss='hinge', penalty='l2', alpha=0.0001, l1_ratio=0.15, fit_intercept=True, max_iter=1000, tol=0.001, shuffle=True, verbose=0, epsilon=0.1, n_jobs=None, random_state=None, learning_rate='optimal', eta0=0.0, power_t=0.5, early_stopping=False, validation_fraction=0.1, n_iter_no_change=5, class_weight=None, warm_start=False, average=False)
Поиск по сетке
Общая информация
Поиск по сетке (англ. Grid search) принимает на вход модель и различные значения гиперпараметров (сетку гиперпараметров). Далее, для каждого возможного сочетания значений гиперпараметров, метод считает ошибку и в конце выбирает сочетание, при котором ошибка минимальна.
Поиск по сетке в Sklearn: использование
Пример использования GridSearch из библиотеки scikit-learn:
- Создание экземпляра класса SGDClassifier (из sklearn)
- Создание сетки гиперпараметров. В данном случае будем подбирать коэффициент регуляризации, шаг градиентного спуска, количество итераций и параметр скорости обучения.
- Создание экземпляра класса кросс-валидации
- Создание экземпляра GridSearch (из sklearn). Первый параметр — модель, второй — сетка гиперпараметров, третий — функционал ошибки (используемый для контроля качества моделей по технике кросс-валидации), четвертый — кросс-валидация (можно задать количество фолдов, а можно передать экземпляр класса кросс — валидации)
- Запуск поиска по сетке.
classifier = linear_model.SGDClassifier(random_state = 0, tol=1e-3)
parameters_grid =
cv = model_selection.StratifiedShuffleSplit(n_splits=10, test_size = 0.2) grid_cv = model_selection.GridSearchCV(classifier, parameters_grid, scoring = 'accuracy', cv = cv) grid_cv.fit(train_data, test_data)
Out: GridSearchCV(cv=StratifiedShuffleSplit(n_splits=10, random_state=0, test_size=0.2, train_size=None), error_score=nan, estimator=SGDClassifier(alpha=0.0001, average=False, class_weight=None, early_stopping=False, epsilon=0.1, eta0=0.0, fit_intercept=True, l1_ratio=0.15, learning_rate='optimal', loss='hinge', max_iter=1000, n_iter_no_change=5, n_jobs=None, penalty='l2. 'eta0': array([1.00000000e-05, 1.64285714e-05, 2.28571429e-05, 2.92857143e-05, 3.57142857e-05, 4.21428571e-05, 4.85714286e-05, 5.50000000e-05, 6.14285714e-05, 6.78571429e-05, 7.42857143e-05, 8.07142857e-05, 8.71428571e-05, 9.35714286e-05, 1.00000000e-04]), 'learning_rate': ['optimal', 'constant', 'invscaling'], 'max_iter': array([5, 6, 7, 8, 9])>, pre_dispatch='2*n_jobs', refit=True, return_train_score=False, scoring='accuracy', verbose=0)
Поиск по сетке в Sklearn: важные атрибуты
- best_estimator_ — лучшая модель
- best_score_ — ошибка, полученная на лучшей модели.
- best_params_ — гиперпараметры лучшей модели
print(grid_cv.best_estimator_)
Out: SGDClassifier(alpha=4.857142857142857e-05, average=False, class_weight=None, early_stopping=False, epsilon=0.1, eta0=1e-05, fit_intercept=True, l1_ratio=0.15, learning_rate='optimal', loss='hinge', max_iter=6, n_iter_no_change=5, n_jobs=None, penalty='l2', power_t=0.5, random_state=0, shuffle=True, tol=0.001, validation_fraction=0.1, verbose=0, warm_start=False)
print(grid_cv.best_score_)
Out: 0.9099999999999999
print(grid_cv.best_params_)
Out:
- cv_results_ — результаты всех моделей.
print(grid_cv.cv_results_)
Out:
print(grid_cv.cv_results_['param_max_iter'].data)
Out: array([5, 6, 7, . 7, 8, 9], dtype=object)
Реализация поиска по сетке в библиотеках
Случайный поиск по сетке
Основная информация
Случайный поиск по сетке (англ. Random Grid Search) вместо полного перебора работает с некоторыми, случайным образом выбранными, комбинациями. На основе полученных результатов, происходит сужение области поиска.
Когда случайный поиск по сетке будет гораздо полезнее, чем просто поиск по сетке? В ситуации, когда гиперпараметров много, но сильно влияющих на конечную производительность алгоритма — мало.
Реализация случайного поиска по сетке
Последовательная оптимизация по модели
Основная информация
Последовательная оптимизация по модели (англ. Sequential Model-Based Optimization, SMBO) используются когда оптимизация целевой функции будет стоить очень «дорого». Главная идея SMBO — замена целевой функции «суррогатной» функцией.
На каждом шаге работы SMBO:
- Строится вероятностная модель (суррогатная функция) целевой функции.
- Подбираются гиперпараметры, которые лучше всего подходят для вероятностной модели.
- Подобранные гиперпараметры применяются к целевой функции.
- Вероятностная модель перестраивается (обновляется).
- Шаги 2-4 повторяются столько раз, сколько задал пользователь.
Существует четыре ключевые аспекта SMBO:
- Сетка значений гиперпараметров (область поиска).
- Целевая функция (выводит оценку, которую мы хотим минимизировать или максимизировать).
- Вероятностная модель целевой функции (суррогатная функция).
- Критерий, называемый функцией выбора (для выбора следующих гиперпараметры по текущей вероятностной модели).
Методы SMBO отличаются между собой вероятностными моделями и функциями выбора:
Популярные вероятностные модели (суррогатные функции):
- Гауссовские процессы
- Древовидный парзеновский оценщик
- Регрессия случайного леса
Древовидный парзеновский оценщик
Основная информация
Как было написано выше, методы SMBO отличаются тем, как они строят вероятностную модель [math] [/math] . В случае древовидного парзеновского оценщика (англ. Tree-structured Parzen Estimator, TPE), используется следующая функция:
[math] [/math] — распределение гиперпараметров, [math] y [/math] — значение целевой функции, [math] y* [/math] — пороговое начение
[math] p(x|y) = \begin l(x), & \mbox y \lt y* \\ g(x), & \mbox y \ge y* \end [/math]
В TPE задается два различных распределения гиперпараметров: первое при значениях целевой функции меньших, чем пороговое значение. Второе — при значениях целевой функции больших, чем пороговое значение.
Алгоритм
- На вход подается список пар (parameters, loss)
- По заданному порогу, происходит разбиение списка на 2 части
- Для каждого списка строится распределение
- Возвращается значение: [math] argmin_ \frac[/math]
Последовательная конфигурация алгоритма на основе модели
Основная информация
Последовательная конфигурация алгоритма на основе модели (англ. Sequential Model-based Algorithm Configuration, SMAC) расширяет подходы SMBO:
- Использует дискретные и условные пространства параметров.
- Обрабатывает негауссовский шум.
- Выделяет бюджет на общее время, доступное для настройки алгоритма, а не на количество оценок функций.
Кроме того, SMAC использует переданную ему модель для формирования списка перспективных конфигураций (сочетаний) параметров. Чтобы оценить перспективность конфигурация [math] \theta [/math] , SMAC строит распределение результатов модели для [math] \theta [/math] . С помощью этого распределения, а также информации, о текущей лучшей конфигурации, SMAC вычисляет ожидаемое положительное улучшение [math] EI(\theta) [/math] . После нахождения [math] EI(\theta) [/math] необходимо найти конфигурацию с наибольшим значением [math] EI(\theta) [/math] . Эта задача приводит к проблеме максимизация значения на всем пространстве конфигураций. Другие методы SMBO максимизируют значения а случайной выборке из пространства конфигураций, что достаточно плохо работает в случае высокомерного пространства. SMAC применяет немного другой подход: выполняется несколько локальных и поисков и среди них выбираются все конфигурации с максимальным [math] EI(\theta) [/math] . И уже среди них производится новый поиск и выбирается лучшая конфигурация.
Реализация
- SMBO: SMAC
- TPE: Hyperopt
- Гауссовские процессы: Spearmint, Scikit-optimize
См. также
- Автоматическое машинное обучение
- Бустинг, AdaBoost
- Кросс-валидация
- Поиск архитектуры нейронной сети
Примечания
Источники
- Algorithms for Hyper-Parameter Optimization
- Sequential Model-Based Optimization for General Algorithm Configuration
- Bayesian optimization
- Гауссовские процессы и байесовская оптимизация
- GridSearchCV sklearn
Валидация модели
Большую роль в приложении играет валидация модели или проверка вводимых данных на корректность. Например, у нас есть класс пользователя, в котором определено свойство для хранения возраста. И нам было бы нежелательно, чтобы пользователь вводил какое-либо отрицательное число или заведомо невозможный возвраст, например, миллион лет.
Например, пусть у нас есть проект консольного приложения, в котором есть клас User:
public class User < public string Name < get; set; >public int Age < get; set; >public User(string name, int age) < Name = name; Age = age; >>
В программе мы можем проверять вводимые данные с помощью условных конструкций:
CreateUser("Tom", 37); CreateUser("b", -4); CreateUser("", 130); void CreateUser(string name, int age) < User user = new User(name, age); // проверяем корректность значения свойства Name // если его длина в диапазоне от 3 до 50, то оно корректно if (user.Name.Length >= 3 && user.Name.Length "); else Console.WriteLine("Incorrect name!"); // проверяем корректность значения свойства Age // если оно в диапазоне от 1 до 100, то оно корректно if (age >= 1 && age \n"); else Console.WriteLine("Incorrect age!\n"); > public class User < public string Name < get; set; >public int Age < get; set; >public User(string name, int age) < Name = name; Age = age; >>
Здесь предполагается, что имя должно иметь больше 1 символа, а возраст должен быть в диапазоне от 1 до 100. Однако в классе может быть гораздо больше свойств, для которых надо осуществлять проверки. А это привет к тому, что увеличится значительно код программы за счет проверок. К тому же задача валидации данных довольно часто встречается в приложениях. Поэтому фреймворк .NET предлагает гораздо более удобный функционал в виде атрибутов из пространства имен System.ComponentModel.DataAnnotations .
Итак, изменим касс User следующим образом:
using System.ComponentModel.DataAnnotations; public class User < [Required] [StringLength(50, MinimumLength = 3)] public string Name < get; set; >[Range(1, 100)] public int Age < get; set; >public User(string name, int age) < Name = name; Age = age; >>
Все правила валидации модели в System.ComponentModel.DataAnnotations определяются в виде атрибутов. В данном случае используются три атрибута: классы RequiredAttribute, StringLengthAttribute и RangeAttribute. В коде необязательно использовать суффикс Attribute , поэтому он, как правило, отбрасывается. Атрибут Required требует обзательного наличия значения. Атрибут StringLength устанавливает максимальную и минимальную длину строки, а атрибут Range устанавливает диапазон приемлемых значений.
Теперь изменим код программы:
using System.ComponentModel.DataAnnotations; CreateUser("Tom", 37); CreateUser("b", -4); CreateUser("", 130); void CreateUser(string name, int age) < User user = new User(name, age); var context = new ValidationContext(user); var results = new List(); if (!Validator.TryValidateObject(user, context, results, true)) < Console.WriteLine("Не удалось создать объект User"); foreach (var error in results) < Console.WriteLine(error.ErrorMessage); >Console.WriteLine(); > else Console.WriteLine($"Объект User успешно создан. Name: \n"); >
Здесь определен метод CreateUser, который принимает два значения и с их помощью создает объект User. В этом методе используются классы ValidationResult, Validator и ValidationContext, которые предоставляются пространством имен System.ComponentModel.DataAnnotations и которые управляют валидацией.
Вначале мы создаем контекст валидации — объект ValidationContext . В качестве первого параметра в конструктор этого класса передается валидируемый объект, то есть в данном случае объект User.
var context = new ValidationContext(user);
Собственно валидацию производит класс Validator и его метод TryValidateObject() . В этот метод передается валидируемый объект (в данном случае объект user), контекст валидации, список объектов ValidationResult и булевый параметр, который указывает, надо ли валидировать все свойства.
var results = new List(); if (!Validator.TryValidateObject(user, context, results, true)) < //. >
Если метод Validator.TryValidateObject() возвращает false , значит объект не проходит валидацию. Если модель не проходит валидацию, то список объектов ValidationResult оказывается заполенным. А каждый объект ValidationResult содержит информацию о возникшей ошибке. Класс ValidationResult имеет два ключевых свойства: MemberNames — список свойств, для которых возникла ошибка, и ErrorMessage — собственно сообщение об ошибке.
Для тестирования три раза вызываем метод CreateUser, передавая в него сначала корректные, а потом некорректные данные:
CreateUser("Tom", 37); CreateUser("b", -4); CreateUser("", 130);
И мы получим следующий консольный вывод:
Объект User успешно создан. Name: Tom Не удалось создать объект User The field Name must be a string with a minimum length of 3 and a maximum length of 50. The field Age must be between 1 and 100. Не удалось создать объект User The Name field is required. The field Age must be between 1 and 100.
В первом вызове метода CreateUser передаются корректные данные, поэтому никаких ошибок при валидации не возникнет.
А во втором вызове CreateUser валидация завершится неудачно, так как свойствам User переданы некорректные значения. Например, свойству Name передается значение «b», что не соответствует правилам атрибута [StringLength(50, MinimumLength = 3)] . Также значение свойства Age — -4 не соответствует правилам атрибута [Range(1, 100)] . Соответственно консоль отобразит ошибки для свойств Name и Age.
В третьем вызове CreateUser валидация также завершится неудачно — значение свойства Age — 130 также не соответствует правилам атрибута [Range(1, 100)] . Но, кроме того, свойству Name передается пустая строка — значение, которое не соответствует правилу атрибута [Required] . Данный атрибут требует обязательного наличия значения.
Если применяются типы record, то атрибуты валидации можно указать непосредственно перед определением свойства:
using System.ComponentModel.DataAnnotations; public record class User( [property: Required] [property: StringLength(50, MinimumLength = 3)] string Name, [property: Range(1, 100)] int Age );
В этом случае перед названием атрибута указывается оператор property:
Таким образом, вместо кучи условных конструкций для проверки значений свойств модели мы можем использовать один метод Validator.TryValidateObject() , а все правила валидации определить в виде атрибутов.
Валидация HTTP-запросов — Веб-разработка на Go
Представим, что мы разрабатываем социальную сеть, и нам нужно реализовать функцию публикации поста. Если не проверять данные, которые вводит пользователь, то мы можем получить пост с пустым или слишком длинным текстом. Обычно такие ошибки приводят к некорректному поведению в приложениях. Это не хорошо, потому что пользователь не сможет понять, что произошло, и нам придется разбираться, почему пост не опубликовался.
В этом уроке мы разберем, как проверять HTTP-запросы в Go. Это важно, потому что проверки позволяют избежать ошибок и обеспечить безопасность нашего приложения.
Ручная проверка запросов в Go
Процесс проверки запросов на корректность перед последующей обработкой называется валидацией:
У разработчиков есть несколько вариантов реализации валидации в Go. В некоторых проектах придерживаются идеологии простоты чтения кода и реализуют валидацию вручную.
Например, валидация запроса на сохранение поста может выглядеть следующим образом:
package main import ( "errors" "fmt" "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" ) type CreatePostRequest struct UserID int64 `json:"user_id"` Text string `json:"text"` > func (req *CreatePostRequest) Validate() error if req.UserID 0 return errors.New("user ID cannot be less than 0") > if req.Text == "" return errors.New("text is empty") > if len(req.Text) > 140 return errors.New("text is too long") > return nil > func main() webApp := fiber.New() webApp.Post("/posts", func(ctx *fiber.Ctx) error // Парсинг JSON-строки из тела запроса в объект. var req CreatePostRequest if err := ctx.BodyParser(&req); err != nil return fmt.Errorf("body parser: %w", err) > // Проверка запроса на корректность. err := req.Validate() if err != nil return ctx.Status(fiber.StatusUnprocessableEntity).SendString(err.Error()) > // @TODO Сохранение поста в хранилище. return ctx.SendStatus(fiber.StatusOK) >) logrus.Fatal(webApp.Listen(":80")) >
Запускаем веб-приложение и отправляем запрос на создание поста с некорректными данными:
--location --request POST 'http://localhost/posts' \ --header 'Content-Type: application/json' \ --data-raw ''
В данном запросе мы указали некорректный идентификатор пользователя и пустой текст поста. В ответ получаем сообщение об ошибке:
Попробуем отправить корректные значения:
--location --request POST 'http://localhost/posts' \ --header 'Content-Type: application/json' \ --data-raw ''
В ответ приходит статус 200 OK, что означает успешное прохождение проверок.
Таким образом, мы настроили проверку запросов, которые приходят в наше веб-приложение. Если запрос на создание поста содержит некорректные данные, то мы возвращаем ошибку. В случае успешной валидации запроса мы возвращаем ответ со статусом 200 OK.
Со стороны кода в данной реализации все проверки описаны явно и легко читаются. Когда новый разработчик присоединится к проекту, он быстро поймет логику приложения, и как проверяются запросы.
У этого подхода также есть один значимый недочет: этот подход плохо масштабируется. Если у нас будет много различных методов с множеством полей, которые нужно проверить, то код станет громоздким. В итоге со временем может возникнуть много повторяющегося кода.
Второй недочет — мы выводим по одной ошибке в одном ответе. Например, структура запроса состоит из пяти полей. Все поля заполнены неверно. В текущей реализации пользователю придется сделать пять запросов, при этом исправлять по одному полю после каждого ответа. Это неудобно, поэтому мы бы хотели возвращать все ошибки в одном ответе.
Чтобы решить эти недочеты, следует использовать готовую библиотеку для валидации запросов. Мы рассмотрим самую часто используемую библиотеку в Go — go-playground/validator . Далее будем ее называть Validator.
Валидация запросов с помощью Validator
Библиотека Validator позволяет реализовать валидацию запросов с помощью аннотаций полей структур. Для каждого поля структуры мы описываем список правил проверок, которые необходимо осуществить. Например, валидация запроса на публикацию поста может выглядеть следующим образом:
package main import ( "fmt" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" ) type CreatePostRequest struct // Описываем правила валидации в аннотациях полей структуры. UserID int64 `json:"user_id" validate:"required,min=0"` Text string `json:"text" validate:"required,max=140"` > func main() webApp := fiber.New() validate := validator.New() webApp.Post("/posts", func(ctx *fiber.Ctx) error // Парсинг JSON-строки из тела запроса в объект. var req CreatePostRequest if err := ctx.BodyParser(&req); err != nil return fmt.Errorf("body parser: %w", err) > // Проверка запроса на корректность. err := validate.Struct(req) if err != nil return ctx.Status(fiber.StatusUnprocessableEntity).SendString(err.Error()) > // @TODO Сохранение поста в хранилище. return ctx.SendStatus(fiber.StatusOK) >) logrus.Fatal(webApp.Listen(":80")) >
Запускаем веб-приложение и отправляем запрос на создание поста с некорректными данными:
--location --request POST 'http://localhost/posts' \ --header 'Content-Type: application/json' \ --data-raw ''
В данном запросе мы указали некорректный идентификатор пользователя и пустой текст поста. В ответ получаем сообщение об ошибке:
'CreatePostRequest.UserID' Error:Field validation for 'UserID' failed on the 'min' tag Key: 'CreatePostRequest.Text' Error:Field validation for 'Text' failed on the 'required' tag
Попробуем отправить корректные значения:
--location --request POST 'http://localhost/posts' \ --header 'Content-Type: application/json' \ --data-raw ''
В ответ приходит статус 200 OK, что означает успешное прохождение проверок.
Так мы реализовали валидацию запросов с помощью библиотеки Validator. Когда мы передаем некорректные данные, то в ответ получаем сообщение об ошибке. Оно позволяет понять, какие данные необходимо исправить. Если все данные заполнены правильно, то мы получаем статус 200 OK.
Мы видим, что в данном случае код стал намного короче и легче читается. Мы можем описывать новые правила валидации и не добавлять новых функций и логики.
Однозначное преимущество по сравнению с ручной валидацией — библиотека уже содержит в себе множество готовых правил проверок, которые можно использовать в своих проектах. Например, мы можем проверить, что поле является корректной электронной почтой:
package main import ( "fmt" "github.com/go-playground/validator/v10" ) type User struct Email string `validate:"required,email"` > func main() v := validator.New() // Вывод: Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag fmt.Println(v.Struct(&User<>)) // Вывод: Key: 'User.Email' Error:Field validation for 'Email' failed on the 'email' tag fmt.Println(v.Struct(&UserEmail: "test">)) // Пустой вывод, так как ошибки нет. fmt.Println(v.Struct(&UserEmail: "test@gmail.com">)) >
Полный список правил проверок можно смотреть в документации .
Пользовательские валидаторы
Готовые правила обычно покрывают большинство нужд в валидации запросов. Но иногда нужно добавить свои правила с пользовательской логикой. Для этого можно использовать функцию validate.RegisterValidation() .
Например, мы хотим проверить, что в публикуемом посте отсутствуют слова-фильтры. Для этого мы напишем следующий код:
package main import ( "fmt" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" "log" "strings" ) type CreatePostRequest struct // Описываем правила валидации в аннотациях полей структуры. UserID int64 `json:"user_id" validate:"required,min=0"` Text string `json:"text" validate:"required,max=140,allowable_text"` > var forbiddenWords = []string "umbrella", "shinra", > func main() webApp := fiber.New() validate := validator.New() vErr := validate.RegisterValidation("allowable_text", func(fl validator.FieldLevel) bool // Проверяем, что текст не содержит запрещенных слов. text := fl.Field().String() for _, word := range forbiddenWords if strings.Contains(strings.ToLower(text), word) return false > > return true >) if vErr != nil log.Fatal("register validation ", vErr) > webApp.Post("/posts", func(ctx *fiber.Ctx) error // Парсинг JSON-строки из тела запроса в объект. var req CreatePostRequest if err := ctx.BodyParser(&req); err != nil return fmt.Errorf("body parser: %w", err) > // Проверка запроса на корректность. err := validate.Struct(req) if err != nil return ctx.Status(fiber.StatusUnprocessableEntity).SendString(err.Error()) > // @TODO Сохранение поста в хранилище. return ctx.SendStatus(fiber.StatusOK) >) logrus.Fatal(webApp.Listen(":80")) >
Запускаем веб-приложение и отправляем запрос на создание поста с текстом, который содержит слово-фильтр:
--location --request POST 'http://localhost/posts' \ --header 'Content-Type: application/json' \ --data-raw ''
В ответ получаем валидационную ошибку:
'CreatePostRequest.Text' Error:Field validation for 'Text' failed on the 'allowable_text' tag
Если же отправить запрос с текстом без запрещенных слов, то получим успешный ответ:
--location --request POST 'http://localhost/posts' \ --header 'Content-Type: application/json' \ --data-raw ''
В итоге мы описали пользовательское правило валидации по тегу allowable_text. Оно проверяет, что текстовое поле не содержит запрещенных слов.
Когда приходит запрос с запрещенным словом, валидация не проходит, а клиенту возвращается ошибка. Если в запросе передать корректный текст, то валидация пройдет успешно, и клиент получит ответ со статусом 200 OK.
Выводы
- В веб-приложениях следует проверять все запросы на корректность, чтобы избежать ошибок и уязвимостей
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях: