Что такое иерархия классов
Перейти к содержимому

Что такое иерархия классов

  • автор:

C# (ИТИП) / Теоретический материал по C# Microsoft / ИЕРАРХИЯ КЛАССОВ

ИЕРАРХИЯ КЛАССОВ Управлять большим количеством разрозненных классов довольно сложно. С этой проблемой можно справиться путем упорядочивания и ранжирования классов, то есть объединяя общие для нескольких классов свойства в одном классе и используя его в качестве базового. Эту возможность предоставляет механизм наследования. Наследование применяется для следующих взаимосвязанных целей: 1. исключения из программы повторяющихся фрагментов кода; 2. упрощения модификации программы; 3. упрощения создания новых программ на основе существующих. Более того, наследование является единственной возможностью использовать объекты, исходный код которых недоступен, но в которые требуется внести изменения. Кроме механизма наследования в данном разделе мы рассмотрим такие важные понятия ООП как полиморфизм и инкапсуляцию, которые также принимают участие в формировании иерархии классов. Наследование Вспомним синтаксис класса: [атрибуты] [спецификаторы] class имя_класса [: предок] < тело_класса >При описании класса имя его предка записывается в заголовке класса после двоеточия. Класс, который наследуется, называется базовым. Класс, который наследует, называется производным. Производный класс наследует все переменные, методы, свойства, операторы и индексаторы, определенные в базовом классе, кроме того, в производный класс могут быть добавлены уникальные элементы или переопределены существующие. Если имя предка явным образом не указано, то предком считается базовый класс всех типов данных в языке С#, т.е. тип оbject. Рассмотрим наследование классов на примере геометрических фигур. В качестве базового класса создадим класс PointPlane (точка на плоскости), в качестве производного класса от PointPlane класс PointSpace (точка в пространстве): using System; namespace MyProgram < public class PointPlane //Базовый класс - точка на плоскости < public int x; public int y; public void Show() < Console.WriteLine("(<0>, )», x, y); > > > using System; namespace MyProgram < public class PointSpace: PointPlane //Производный класс - точка в пространстве

< public int z; public void Show() < Console.WriteLine("(<0>, , )», x, y, z); > > > Рассмотрим на примере использование созданных классов: using System; namespace MyProgram < class Program < static void Main() < PointPlane pointP=new PointPlane(); pointP.x=10; pointP.y=10; pointP.Show(); PointSpace pointS=new PointSpace(); pointS.x=1; pointS.y=2; pointS.z=3; pointS.Show(); >> > Результат работы программы: (10, 10) (1, 2, 3) Экземпляр класса PointSpace с одинаковой легкостью использует как собственные поля, так и унаследованные от класса PointPlane. Но в двух классах определен метод Show, поэтому компилятором будет сгенерировано предупреждение: Чтобы избежать подобного предупреждения необходимо перед одноименным членом производного класса (в данном случае перед методом Show в классе PointSpace) поставить спецификатор new. Данный спецификатор скрывает одноименный член базового класса от производного, и позволяет полностью его переопределить. Использование защищенного доступа В нашем примере поля x и у базового класса были открыты для доступа (public). Если убрать public, то поля автоматически станут закрытыми для доступа (private), в том числе, и для доступа из производного класса. Решить проблему доступа к закрытым полям базового класса из производного можно двумя способами: используя свойства класса или спецификатор protected. При объявлении какого-то члена класса с помощью спецификатора protected, он становится закрытым для всех классов, кроме производных.

Наследование конструкторов В иерархии классов как базовые, так и производные классы могут иметь собственные конструкторы. При этом конструктор базового класса создает часть объекта, соответствующую базовому классу, а конструктор производного класса — часть объекта, соответствующую производному классу. Так как базовый класс не имеет доступа к элементам производного класса, то их конструкторы должны быть раздельными. В предыдущем примере классы создавались за счет автоматического вызова средствами С# конструктора по умолчанию. Добавим конструктор только в производный класс PointSpace. using System; namespace MyProgram < public class PointPlane //Базовый класс < //поля доступны только из производных классов protected int x; protected int y; public void Show() < Console.WriteLine("(<0>, )», x, y); > > > using System; namespace MyProgram < public class PointSpace:PointPlane < protected int z; //поле доступно только из производных классов public PointSpace(int x, int y, int z) //конструктор производного класса < this.x=x; this.y=y; this.z=z; >public new void Show() < Console.WriteLine("(<0>, , )», x, y, z); > > > В данном случае конструктор определяется только в производном классе, поэтому часть объекта, соответствующая базовому классу, создается автоматически с помощью конструктора по умолчанию, а часть объекта, соответствующая производному классу, создается собственным конструктором. Обратите внимание на то, что для производного класса возможно использовать параметр base, который действует подобно параметру this, за исключением того, что base всегда ссылается на базовый класс. В данном контексте параметр base позволяет получить доступ к члену базового класса, который скрыт за членом производного класса. Формат использования параметра:

base.член_класса В качестве член_класса можно указывать либо метод, либо поле экземпляра. Рассмотрим использование модифицированных классов на следующем примере: PointPlane pointP=new PointPlane(); pointP.Show(); PointSpace pointS=new PointSpace(1, 2, 3); pointS.Show(); Результат работы фрагмента программы: (0, 0) (1, 2, 3) Задания . 1) объясните, почему для объекта pointP были выведены координаты (0, 0); 2) объясните, допустима ли команда pointP.x=1; или PointS.x=1; и почему? 3) добавьте в классы PointPlane и PointSpace функциональные члены, позволяющие осуществлять доступ к закрытым полям. Теперь добавим конструктор в базовый класс. using System; namespace MyProgram < public class PointPlane < protected int x; protected int y; public PointPlane(int x, int y) //конструктор базового класса < this.x=x; this.y=y; >public void Show() < Console.WriteLine("(, )", x, y); > > > Если теперь мы попытаемся скомпилировать программу, то получим сообщение об ошибке. Дело в том, что в .NET объявление в классе конструктора с параметрами автоматически приводит к удалению конструктора по умолчанию, если он не был явно задан разработчиком. При этом конструктор класса-потомка автоматически пытается обратиться к конструктору по умолчанию, которого теперь больше нет. Для того, чтобы решить эту проблему, необходимо явно указать в конструкторе класса-потомка, какой конструктор класса-родителя необходимо вызвать. Это делается с помощью служебного слова base следующим образом: public PointSpace(int x, int y, int z) :base (x, y) //конструктор производного класса

В данном случае параметр base перед выполнением конструктора производного класса, вызывает конструктор базового класса, передавая ему в качестве параметров список переменных. Рассмотрим использование модифицированных классов на примере: PointPlane pointP=new PointPlane(10, 10); pointP.Show(); PointSpace pointS=new PointSpace(1, 2, 3); pointS.Show(); Результат работы фрагмента программы: (10, 10) (1, 2, 3) Задание . Объясните, допустима ли следующая команда и почему? PointPlane pointP=new PointPlane(); В общем случае с помощью параметра base можно вызвать конструктор любой формы, определенный в базовом классе. Реально же выполнится тот конструктор, список формальных параметров которого будет соответствовать списку аргументов, переданных base. Рассмотрим модификацию классов PointPlane и PointSpace, содержащих по несколько конструкторов: using System; namespace MyProgram < public class PointPlane < protected int x; protected int y; public PointPlane() < >public PointPlane(int a) < x=a; y=a; >public PointPlane(int x, int y) < this.x=x; this.y=y; >public void Show() < Console.WriteLine("(<0>, )», x, y); > > >

using System; namespace MyProgram < public class PointSpace:PointPlane < protected int z; public PointSpace() < >public PointSpace(int a) :base (a) < z=a; >public PointSpace(int x, int y, int z) :base (x, y) < this.z=z; >public new void Show() < Console.WriteLine("(<0>, , )», x, y, z); > > > Инициируем вызов различных конструкторов, и посмотрим, что из этого получится: PointPlane pointP1=new PointPlane(); pointP1.Show(); PointPlane pointP2=new PointPlane(1); pointP2.Show(); PointPlane pointP3=new PointPlane(2, 3); pointP3.Show(); PointSpace pointS1=new PointSpace(); pointS1.Show(); PointSpace pointS2=new PointSpace(4); pointS2.Show(); PointSpace pointS3=new PointSpace(5, 6, 7); pointS3.Show(); Результат работы фрагмента программы: (0, 0) (1, 1) (2, 3) (0, 0, 0) (4, 4, 4) (5, 6, 7) Задание . Объясните, как при вызове конструктора производного класса инициируется вызов конструктора базового класса.

Класс object Все С#-типы, включая размерные типы, выведены из класса object. Следовательно, ссылку типа object можно использовать в качестве ссылки на любой другой тип, в том числе, размерный. Если ссылка типа object указывает на значение нессылочного типа, то происходит приведение к объектному типу (boxing). В результате этого процесса значение нессылочного типа должно сохраниться подобно объекту, или экземпляру класса. Другими словами, «необъектное» значение помещается в объектную оболочку и размещается в динамической памяти. Такой «необъектный» объект можно затем использовать подобно любому другому объекту. Приведение к объектному типу происходит автоматически. Восстановление значения из «объектного образа» называется unboxing. Это действие выполняется с помощью операции приведения типа, т.е. приведения ссылки на объект класса object к значению желаемого типа. Рассмотрим простой пример, который иллюстрирует приведение значения к объектному типу и его восстановление. int x=1; object ob=x; //boxing int y=(int) ob; //unboxing Console.WriteLine(«y=<0>«, y); Результат работы фрагмента программы: y=1 Так как С# является языком со строгой типизацией, в нем требуется строгое соблюдение совместимости типов с учетом стандартных преобразований типов. Поэтому ссылка одного типа обычно не может ссылаться на объект другого ссылочного типа, за одним небольшим исключением – ссылочная переменная базового класса может ссылаться на объект любого производного класса. В нашем случае допустимой будет следующая команда: PointPlane pointS=new PointSpace(1,2,3); Более того, т.к. тип object является базовым для всех типов данных, становится допустимой следующая последовательность команд: object ob1=new PointPlane(4, 5); object ob2=new PointSpace(6, 7, 8); Ошибка возникнет при попытке обратиться к методу Show. Например, команда pointS.Show(); вместо ожидаемого (1, 2, 3) выведет нам (1, 2). А команда: ob1.Show(); вообще не сможет выполниться, т.к. для класса object метод Show не определен. Давайте разберемся, почему вместо (1, 2, 3) мы плолучили (1, 2). Как уже упоминалось ранее, все методы класса находятся в памяти в единственном экземпляре и используются всеми объектами одного класса совместно. Это решение вполне логично, так как если у нас есть 1000 объектов типа PointPlane, то 1000 раз дублировать код метода Show нет никакого смысла. В результате в случае с присваиванием ссылке на класс PointPlane объекта PointSpace возникает конфликт между двумя таблицами методов – базового класса и класса-потомка.

Так как потомков у базового класса может быть много (в том числе потомков третьего и более высоких уровней) и поиск по всем возможным таблицам методов является достаточно длительным процессом, компилятор считает, что если не сказано обратное, то какой метод будет вызываться, определяется типом ссылки на объект. Для того чтобы явно указать на необходимость поиска нужного метода по типу объекта, а не ссылки, используются спецификаторы virtual и abstract, а обозначенные ими методы принято называть виртуальными и абстрактными соответственно. Виртуальные методы Виртуальный метод – это метод, который объявлен в базовом классе с использованием ключевого слова virtual, и затем переопределен в производном классе с помощью ключевого слова override. При этом если реализовано наследование, в том числе и многоуровневое, то каждый производный класс может иметь свою собственную версию виртуального метода. Этот факт особенно полезен в случае, когда доступ к объекту производного класса осуществляется через ссылочную переменную базового класса. В этой ситуации CLR сама выбирает, какую версию виртуального метода нужно вызвать. Этот выбор производится по типу объекта, на который ссылается данная ссылка. Модифицируем методы Show в классах PointPlane и PointSpace с учетом сказанного.

public virtual void Show() //в классе PointPlane
Console.WriteLine(«(, )», x, y);
>
public override void Show() //в классе PointSpace

Console.WriteLine(«(, , )», x, y, z); > Теперь рассмотрим следующий фрагмент программы: PointPlane []array=new PointPlane [6]; array[0]=new PointPlane(); array[1]=new PointPlane(1); array[2]=new PointPlane(2, 3); array[3]=new PointSpace(); array[4]=new PointSpace(4); array[5]=new PointSpace(5, 6, 7); foreach (PointPlane item in array) < item.Show(); >Результат работы фрагмента программы: (0, 0) (1, 1) (2, 3) (0, 0, 0) (4, 4, 4) (5, 6, 7) Таким образом, благодаря полиморфизму через ссылочную переменную базового класса можно обращаться к объектам разного типа, а также с помощью одного и того же имени выполнять различные действия.

Задания . 1) Добавьте в базовый класс PointPlane виртуальный метод Distance, позволяющий вычислить расстояние от начала координат до заданной точки. 2) Переопределите его в производном классе PointSpace с учетом того, что точка задана в пространстве. 3) Продемонстрируйте работу данного метода. Абстрактные методы и классы Иногда полезно создать базовый класс, определяющий только своего рода «пустой бланк», который унаследуют все производные классы, причем каждый из них заполнит этот «бланк» собственной информацией. Такой класс определяет структуру методов, которые производные классы должны реализовать, но сам класс при этом не обеспечивает реализации этих методов. Подобная ситуация может возникнуть тогда, когда базовый класс попросту не в состоянии реализовать метод. В данной ситуации разрабатываются абстрактные методы или целые абстрактные классы . Абстрактный метод описывается с помощью спецификатора abstract. Он не имеет тела и, следовательно, не реализуется базовым классом, а производные классы должны его обязательно переопределить. Абстрактный метод автоматически является виртуальным и использовать спецификатор virtual не нужно. Более того, если вы попытаетесь использовать два спецификатора одновременно, abstract и virtual, то компилятор выдаст сообщение об ошибке. Задание . Подумайте, можно ли спецификатор abstract сочетать со спецификатором static. И почему? Если класс содержит один или несколько абстрактных методов , то его также нужно объявить как абстрактный, используя спецификатор abstract перед class. Поскольку абстрактный класс полностью не реализован, то невозможно создать экземпляр класса с помощью операции new. Например, если класс Demo определен как абстрактный, то попытка создать экземпляр класса Demo повлечет ошибку: Demo a = new Demo(); Однако можно создать массив ссылок, тип которых соответствует абстрактному классу: Demo [] Ob=new Demo[5]; Если производный класс наследует абстрактный, то он должен полностью переопределить все абстрактные методы базового класса или также быть объявлен как абстрактный. Таким образом, спецификатор abstract наследуется до тех пор, пока в производном классе не будут реализованы все абстрактные методы. Рассмотрим пример использования абстрактных методов и классов. using System; namespace MyProgram < abstract public class Point //абстрактный класс < abstract public void Show(); abstract public double Distance(); >> 9

Класс Point содержит объявление двух абстрактных методов, которые при наследовании необходимо будет реализовать. В общем случае абстрактный класс может содержать не только абстрактные методы. Используя абстрактный класс, модифицируем классы PointPlane и PointSpace. using System; namespace MyProgram < public class PointPlane: Point < protected int x; protected int y; public PointPlane() < >public PointPlane(int a) < x=a; y=a; >public PointPlane(int x, int y) < this.x=x; this.y=y; >public override void Show() //переопределяем абстрактный метод < Console.WriteLine("(<0>, )», x, y); > public override double Distance() //переопределяем абстрактный метод < return Math.Sqrt(x*x+y*y); >> > using System; namespace MyProgram < public class PointSpace:PointPlane < protected int z; public PointSpace() < >public PointSpace(int a):base (a) < z=a;

Вопрос 22: Иерархия классов. Производные классы — что это даёт. Функции-члены. Конструкторы и деструкторы. Пример иерархии классов.

Иерархия классов — это структура многочисленных связанных классов, определяющая, какие классы наследуют функции от других классов. Наследование функций порождается наследованием классов.

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

Иерархия классов позволяет определять новые классы на основе уже имеющихся. Имеющиеся классы обычно называют базовыми(иногда порождающими), а новые классы, формируемые на основе базовых, – производными (порожденными, классами-потомками или наследниками).

Производные классы «получают наследство» – данные и методы своих базовых классов, и могут пополняться собственными компонентами (данными и собственными методами). Наследуемые компоненты не перемещаются в производный класс, а остаются в базовых классах. Сообщение, обработку которого не могут выполнить методы производного класса, автоматически передается в базовый класс. Если для обработки сообщения нужны данные, отсутствующие в производном классе, то их пытаются отыскать автоматически в базовом классе.

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

Для порождения нового класса на основе существующего используется следующая общая форма

сlass Имя : МодификаторДоступа ИмяБазовогоКласса < объявление_членов; >; 

Модификотор доступа может быть:

Общее наследование означает, что порожденный класс – это подтип базового класса. Таким образом, порожденный класс представляет собой модификацию базового класса, которая наследует общие и защищенные члены базового класса.

Указатель на порожденный класс может быть неявно передан в указатель на базовый класс. При этом переменная-указатель m на базовый класс может указывать на объекты как базового, так и порожденного класса. Указатель на порожденный класс может указывать только на объекты порожденного класса. Неявные преобразования между порожденным и базовым классами называются предопределенными стандартными преобразованиями:

  • объект порожденного класса неявно преобразуется к объекту базового класса.
  • ссылка на порожденный класс неявно преобразуется к ссылке на базовый класс.
  • указатель на порожденный класс неявно преобразуется к указателю на базовый класс.

Частное наследование Порожденный класс может быть базовым для следующего порождения. При порождении private наследуемые члены базового класса, объявленные как protected и public, становятся членами порожденного класса с видимостью private. При этом члены базового класса с видимостью public и protected становятся недоступными для дальнейших порождений. Цель такого порождения — скрыть классы или элементы классов от использования в дальнейших порождениях. При порождении private не выполняются предопределенные стандартные преобразования.

Модификатор наследования → public protected private
Модификатор доступа ↓
public public protected private
protected protected protected private
private нет доступа нет доступа нет доступа

Конструкторы и деструкторы при наследовании Как базовый, так и производный классы могут иметь конструкторы и деструкторы. Если и у базового и у производного классов есть конструкторы и деструкторы, то конструкторы выполняются в порядке наследования, а деструкторы – в обратном порядке. То есть если А – базовый класс, В – производный из А, а С – производный из В (А-В-С), то при создании объекта класса С вызов конструкторов будет иметь следующий порядок:

  1. конструктор класса А
  2. конструктор класса В
  3. конструктор класса С.

Вызов деструкторов при удалении этого объекта произойдет в обратном порядке:

  1. деструктор класса С
  2. деструктор класса В
  3. деструктор класса А.

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

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

КонструкторПроизводногоКласса (СписокФормальныхАргументов) : КонструкторБазовогоКласса (СписокФактическихАргументов) < // тело конструктора производного класса > 

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

student :: student(char *f, char *s, char *n) < strcpy(fac, f); strcpy(spec, s); strcpy(name, n); > grad_student :: grad_student(char *f, char *s, char *n, char *w, int y) : student(f,s,n) < year = y; strcpy(work, w); > 

Пример реализации

class student < protected: char fac[20]; char spec[30]; char name[15]; public: student(char *f, char *s, char *n); void print(); >; class grad_student : public student < protected: int year; char work[30]; public: grad_student(char *f, char *s, char *n, char *w, int y); void print(); >; 

Как работать с иерархической структурой классов

Задача классификации — одна из самых известных в машинном обучении. Очень многие проблемы, решаемые с помощью ML, так или иначе сводятся к классификации — распознавание изображений, например. И все выглядит просто и понятно, когда нам нужно определить объект в один из нескольких классов. А что если у нас не плоская структура из нескольких классов, а сложная разветвленная иерархия на 683 категории? Именно о таком случае мы сегодня и поговорим. Под катом — рассказ о том, зачем в задачах классификации нужны сложные иерархии и как с ними жить.

В любой задаче классификации есть набор классов и есть модель, которая умеет предсказывать класс для новых данных. Допустим, модель бинарной классификации Спам/не спам, модель классификации настроения текста Хорошо/нейтрально/плохо. Классы обычно являются абстракцией реальности, а абстракция — это всегда упрощение.

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

Это звучит понятно в школьном учебнике, но в контексте машинного обучения возникает множество вопросов:

  • Как с этим всем обращаться?
  • Какие классы предсказывать?
  • Сколько моделей тренировать для решения задачи?
  • Как работать с данными?
  • Как вносить изменения в иерархию классов и как реагировать на эти изменения с точки зрения модели?

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

Постановка задачи

Мы работаем с чеками. В каждом чеке может встретиться много разных товаров, которые можно сгруппировать множеством разных способов. На данный момент нам интересно группировать эти товары с помощью дерева категорий, которое мы будем называть KPC (Khajiit Product Classification). Это здоровенное дерево, состоящее из 683 категорий.

Для этих категорий у нас есть Golden Set, наш размеченный набор данных (штрихкод — категория KPC) для обучения. В датасете почти три миллиона записей и мы постоянно работаем над его дополнением и улучшением разметки.

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

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

Разметка данных

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

Так появились статусы категорий. Они помогают понять, в каком состоянии категория прямо сейчас. Благодаря статусам мы можем отдельно обрабатывать только что появившиеся категории или категории, которые скоро будут удалены. На текущий момент мы выделяем четыре статуса категорий:

  • Активный.
  • Запланированный — категория, на которую мы хотим классифицировать, но пока не можем (не хватает данных или не почистили разметку).
  • Архивный — категория, которую решено было удалить, но товары из категории еще не переразметили.
  • Удаленный.

В зависимости от статуса мы решаем, что делать с конкретной категорией на каждом из этапов классификации.

Также стоит упомянуть две отдельные группы категорий:

  • «свалка» (например «Одежда (свалка)») — группа для логического удаления некоторых товаров, которые невозможно категоризировать. Например, у нас есть товар «Полное тестирование Тест 2 10шт», у которого из какого-то источника данных стоит категория Одежда. Наш аналитик данных понимает, что по факту категории у такого товара нет, поэтому такой товар отправляется в свалку, которая при обучении не рассматривается.
  • «другое/другие» (например «Молочные товары, сыры и яйцо (другое)») — группа для товаров, которые относятся к родительской категории, но не относятся (возможно, пока) ни к одной из дочерних.

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

Добавление категории

Добавить категорию мы можем в любое время, но для того, чтобы товары начали в неё попадать, необходимо:

  • Добавить категорию в KPC.
  • Переразметить обучающую выборку в соответствии с новой категорией (если товары новой категории раньше относились к другой категории — проверить, что теперь они относятся к правильной).
  • Переобучить модель, чтобы она научилась классифицировать товары в новую категорию.
Удаление категории

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

Для удаления категории необходимо:

  • Перевести категорию в статус Архивная.
  • Решить, что мы делаем с товарами, которые относились к удаляемой и дочерним категориям.
  • Заменить удаляемую категорию у товаров в Golden Set.
  • Указать дочерним категориям новую родительскую категорию или её отсутствие (если дочерняя категория должна стать категорией верхнего уровня).
  • Переобучить модель, чтобы она перестала классифицировать товары в удаляемую категорию (и начала классифицировать эти товары в новые категории).
  • Перевести категорию в статус Удаленная.
Разбиение категории

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

  • Обновить категории в Golden Set, чтобы отнести товары к новым категориям.
  • Переобучить модель, чтобы она научилась классифицировать товары в новые категории.

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

Обучение модели

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

Такие коллизии плохо влияют на обучение — товар может одновременно оказаться в обеих категориях и это может смутить нашу модель. Чтобы избежать таких случаев, перед обучением добавлен этап разрешения конфликтов дерева категорий (KPC collisions resolving).

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

Алгоритм разрешения конфликтов

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

Для разрешения этих ситуаций мы будем маппить категории (то есть переносить все товары из одной категории в другую), а после обновлять наше дерево категорий (оставлять в рассмотрении только те категории, которые представлены в Golden set), проверяя, остались ли коллизии.

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

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

Future/Active на схеме — это статусы категорий Запланированная/Активная, а present/NOT present in GS — представлена ли категория в Golden set.

Еще один вопрос, который важно разобрать — что мы хотим делать с Запланированными категориями? И что мы хотим делать с их детьми?

Есть несколько вариантов. Мы можем:

  • Использовать эти категории в классификации.
  • Не классифицировать и выбросить категории из GS.
  • Переразмечать эти категории в категорию-родителя.
  • Переразмечать эти товары в категорию «другое/другие» (например «Молочные продукты, сыры и яйцо (другое)»)

После разбора таких случаев, ответов на архитектурные вопросы и итеративной разработки мы пришли к следующему алгоритму.

  1. Убрать удаленные, редко встречающиеся (меньше 10 товаров в golden set) и те категории, у которых в названии есть «свалка».
  2. Если все дети категории в статусе «Запланированный», то дочерние категории маппятся в родителя. Это происходит итеративно, так как после первого маппинга может возникнуть аналогичная ситуация на другом уровне дерева.
  3. Смаппить запланированные категории в sibling-категорию с «другое/другие» в названии, если такая есть.
  4. Удалить из Golden Set категории, у которых есть категории-потомки с товарами в Golden Set. Здесь происходит то самое разрешение конфликтов.

На каждом этапе мы рассматриваем уже обновленное дерево категорий. Важность этого пункта вытекла из разбора неочевидных ситуаций. Например, если не обновлять дерево (не убирать категории без товаров в GS и без потомков с товарами), то правило Смаппить всех Запланированных детей к родителю может не сработать из-за пустого, но активного ребенка.

Валидация модели

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

В первую очередь мы сравниваем базовые метрики — accuracy (по всем классам) и accuracy/coverage. Необходимо следить за тем, чтобы баланс покрытия и точности не нарушался, а также за возможностью подобрать threshold для новой модели, при котором этот баланс соответствует нашим требованиям.

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

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

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

‘errors’ — sum of errors of confusing two labels,
‘label_1_confuse’ — count(true=label_1, pred=label_2) / ‘errors’,
‘label_2_confuse’ — count(true=label_2, pred=label_1) / ‘errors’,
‘fraction_of_error_to_label_1’ — count(true=label_1, pred=label_2) / total_label_1,
‘fraction_of_error_to_label_2’ — count(true=label_2, pred=label_1) / total_label_2,
‘fraction_of_all_errors’ — ‘errors’ / total_errors,
‘fraction_of_all_errors_cumulative’

Выводы

  1. Для удобной итеративной работы с деревом категорий полезно ввести статусы категорий. Такие статусы позволят обрабатывать категории в разном состоянии разными способами.
  2. При валидации стоит агрегировать метрики для родительских категорий, чтобы иметь возможность сравнить две модели с разными наборами классов.
  3. Важно следить, чтобы в обучающей выборке не смешивались сэмплы из родительского и из дочернего классов. Также важно продумать желаемый результат разрешения таких конфликтов в разных конфигурациях дерева, статусов и состояния разметки категорий.
  • classification
  • hierarchy
  • hierarchical data
  • классификация
  • иерархия
  • машинное обучение
  • мультиклассовая классификация
  • иерархия классов

Иерархия классов

Также как и в таксономии (классификации видов) иерархия классов в информатике означает классификацию объектных типов, рассматривая объекты как реализацию классов (класс похож на заготовку, а объект — это то, что строится на основе этой заготовки) и связывая различные классы отношениями наподобие «наследует», «расширяет», «является его абстракцией», «определение интерфейса».

Отношения, установленные в области объектно-ориентированного проектирования и стандартах интерфейса объектов, определяются наиболее распространенным использованием, создателями языков (Java, C++, Smalltalk, Visual Prolog) и комитетами по стандартизации, как например, Object Management Group.

  • Объектно-ориентированное программирование

Wikimedia Foundation . 2010 .

  • Иерархическая временная память
  • Иеровоам II

Смотреть что такое «Иерархия классов» в других словарях:

  • Иерархия — У этого термина существуют и другие значения, см. Gerarchia. Иерархия (от др. греч. ἱεραρχία, из ἱερός «священный» и ἀρχή «правление») порядок подчинённости низших звеньев высшим, организация их в структуру типа дерево; принцип управления в … Википедия
  • Иерархия (духовная) — Великая цепь бытия, 1579 год Духовная Иерархия (от др. греч. ἱεραρχία, из … Википедия
  • Иерархия компьютерной памяти — концепция построения взаимосвязи классов разных уровней компьютерной памяти на основании иерархической структуры. Иерархия оперативной памяти реализуемая в вычислительной системе на базе процессора … Википедия
  • ИЕРАРХИЯ — классификация тех или иных математич. объектов в соответствии с их сложностью. Первые И. были построены в дескриптивной теории множеств (см. [3]). В этих И. переход к более сложному классу множеств осуществляется путем применения теоретико… … Математическая энциклопедия
  • ИЕРАРХИЯ — (hierarchy, от гр. hierarchia от hieros священный, arche власть) строго определенная система подчинения нижестоящих органов и должностных лиц вышестоящим, последовательное расположение чинов и званий в порядке следования от низших к высшим, так… … Власть. Политика. Государственная служба. Словарь
  • Полиномиальная иерархия — В теории сложности полиномиальная иерархия это иерархия классов сложности которая обобщает классы P, NP, co NP до вычислений с оракулом. Определение Существует множество эквивалентных определений классов полиномиальной иерархии. Приведём… … Википедия
  • Духовная иерархия — Великая цепь бытия, 1579 год Духовная Иерархия (от др. греч … Википедия
  • Класс сложности — В теории алгоритмов классами сложности называются множества вычислительных задач, примерно одинаковых по сложности вычисления. Говоря более узко, классы сложности это множества предикатов (функций, получающих на вход слово и возвращающих ответ 0… … Википедия
  • Объектно-ориентированное программирование — Эта статья во многом или полностью опирается на неавторитетные источники. Информация из таких источников не соответствует требованию проверяемости представленной информации, и такие ссылки не показывают значимость темы статьи. Статью можно… … Википедия
  • ООАП — Объектно ориентированное программирование (ООП) парадигма программирования, в которой основными концепциями являются понятия объектов и классов (либо, в менее известном варианте языков с прототипированием прототипов). Класс это тип, описывающий… … Википедия

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *