- •Аннотация
- •Оглавление
- •Глава 1.Интеллектуальный анализ данных и Data Mining 8
- •Глава 2. Обзор алгоритмов обобщения с «учителем» 29
- •Глава 3. Программная реализация алгоритма cn2 и abcn2 49
- •Глава 4. Тестирование работы алгоритма и исследование полученных результатов 60
- •Введение
- •Глава 1.Интеллектуальный анализ данных и Data Mining
- •1.1. Задачи Data Mining
- •1.1.1. Задача классификации и регрессии
- •1.1.2. Задача кластеризации
- •1.1.3. Задача поиска ассоциативных правил
- •1.2. Модели Data Mining
- •1.3. Методы Data Mining
- •1.4. Общий подход в прогнозирующих методах
- •1.5. Классификация алгоритмов качественного обобщения по признакам
- •1.5.1.Способы представления исходной информации в интеллектуальных системах
- •1.5.2.Варианты реализации систем обобщения
- •1.5.2.1 Обучение "без учителя": основные понятия
- •1.5.2.2. Обучение "с учителем": постановка задачи
- •1.6 Выводы
- •Глава 2. Обзор алгоритмов обобщения с «учителем»
- •2.1.1 Алгоритм древ
- •2.1.2. Описание алгоритма древ
- •2.2. Алгоритм id3(индукция решающих деревьев)
- •2.3 Алгоритм aq
- •2.3.1 Описание алгоритма
- •2.3.2 Псевдокод алгоритма
- •2.4 Алгоритм cn2
- •2.4.1 Описание алгоритма
- •2.4.2 Псевдокод алгоритма
- •2.5 Алгоритм abcn2
- •2.5.1 Особенности аргументированного обучения
- •2.5.2 Аргументирование примеров
- •2.5.3 Оценка условий формируемых правил
- •2.5.4 Алгоритм извлечения продукционных правил
- •2.6 Выводы
- •Глава 3. Программная реализация алгоритма cn2 и abcn2
- •3.1 Постановка задачи
- •3.2 Требования к системе
- •3.3 Среда разработки
- •3.4. Форматы входных и выходных данных
- •3.5 Интерфейс
- •3.6. Ввод информации об атрибутах
- •3.7.Ввод обучающей выборки и построение аргументированных продукционных правил
- •3.8.Получение набора аргументированных продукционных правил
- •3.9.Вкладка «Экзамен»
- •3.10. Выводы
- •Глава 4. Тестирование работы алгоритма и исследование полученных результатов
- •4.1. Исследование правильности построения продукционных правил
- •4.2. Оценка точности классификации примеров(с помощью алгоритма cn2)
- •4.2.1 Результат классификации в задаче monk1(cn2)
- •4.2.2 Результат классификации в задаче monk2(cn2)
- •4.2.3 Результат классификации в задаче monk3(cn2)
- •4.3. Оценка точности классификации примеров (с помощью алгоритма abcn2)
- •4.3.1 Результат классификации в задаче monk1(abcn2)
- •Ниже представлены аналогичные результаты тестирования для задачи monk3.
- •4.3.2 Результат классификации в задаче monk3(abcn2)
- •4.4 Выводы
- •Заключение
- •Список литературы
- •Пространство имён DataStructures
- •Пространство имён uc
- •Пространство имён PercentageConverter.Cs
- •Пространство имён AttrinuteTypesView
- •Пространство имён LearningExamplesView
- •Пространство имён ArguedLearningExamplesView
- •Пространство имён ExamineExamolesView
- •Пространство имён Log
- •Пространство имён NumericUpDown
- •Проект WpfApp
Пространство имён NumericUpDown
NumericUpDown.xaml
<UserControl x:Class="CN2.UC.NumericUpDown.NumericUpDown"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CN2.UC.NumericUpDown"
xmlns:converters="clr-namespace:CN2.UC.Converters"
mc:Ignorable="d" Height="20">
<Grid x:Name="gridTextBox" MinHeight="20" MinWidth="40">
<TextBox x:Name="textBoxValue" Padding="0,0,0,0" TextWrapping="Wrap" Text="1" HorizontalAlignment="Stretch" Margin="0,0,20,0" TextChanged="textBoxValue_TextChanged"/>
<Button x:Name="buttonUp" Padding="0" Height="{Binding Path=ActualHeight, ElementName=gridTextBox, Converter={converters:PercentageConverter}, ConverterParameter='0,5'}" Width="20" VerticalAlignment="Top" HorizontalAlignment="Right" Click="buttonUp_Click">
<Label FontFamily="Marlett" FontSize="10" Content="5" Margin="0,0,0,0" Padding="0,0,0,0" VerticalAlignment="Bottom"/>
</Button>
<Button x:Name="buttonDown" Padding="0" Height="{Binding Path=ActualHeight, ElementName=gridTextBox, Converter={converters:PercentageConverter}, ConverterParameter='0,5'}" Width="20" VerticalAlignment="Bottom" HorizontalAlignment="Right" Click="buttonDown_Click">
<Label FontFamily="Marlett" FontSize="10" Content="6" Margin="0,-1,0,0" Padding="0,0,0,0" VerticalAlignment="Bottom"/>
</Button>
</Grid>
</UserControl>
NumericUpDown.xaml.cs
Проект WpfApp
MainWindow.xaml
<Window
x:Name="mainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp"
xmlns:learningExamplesView="clr-namespace:CN2.UC.LearningExamplesView;assembly=CN2"
xmlns:attrinuteTypesView="clr-namespace:CN2.UC.AttrinuteTypesView;assembly=CN2"
xmlns:log="clr-namespace:CN2.UC.Log;assembly=CN2"
xmlns:examineExamolesView="clr-namespace:CN2.UC.ExamineExamolesView;assembly=CN2"
xmlns:arguedLearningExamplesView="clr-namespace:CN2.UC.ArguedLearningExamplesView;assembly=CN2"
x:Class="WpfApp.MainWindow"
mc:Ignorable="d"
Title="Машинное обучение" Height="720" Width="1280">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="20" Width="*"/>
<ColumnDefinition MinWidth="20" Width="*"/>
</Grid.ColumnDefinitions>
<Grid Background="#FFE5E5E5" Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="20" Margin="0,0,3,0">
<Grid.RowDefinitions>
<RowDefinition MinHeight="20"/>
<RowDefinition Height="150" MinHeight="20"/>
</Grid.RowDefinitions>
<Label x:Name="cn2Label" Content="Без аргументации примеров" Padding="1,1,1,1" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="0" MouseDoubleClick="label_MouseDoubleClick"/>
<TabControl x:Name="cn2TabControl" Margin="0,18,2,5" Grid.Row="0">
<TabItem x:Name="cn2AttributeTypesTabItem" Header="Типы данных" Width="140">
<Grid Background="#FFE5E5E5">
<attrinuteTypesView:AttributeTypesView x:Name="cn2AttributeTypesView"/>
</Grid>
</TabItem>
<TabItem x:Name="cn2LearningExamplesTabItem" Header="Обучающая выборка" Width="140" IsEnabled="False">
<Grid Background="#FFE5E5E5">
<learningExamplesView:LearningExamplesView x:Name="cn2LearningExamplesView"/>
</Grid>
</TabItem>
<TabItem x:Name="cn2ExamineExamplesTabItem" Header="Экзамен" Width="140" IsEnabled="False">
<Grid Background="#FFE5E5E5">
<examineExamolesView:ExamineExamplesView x:Name="cn2ExamineExamplesView"/>
</Grid>
</TabItem>
</TabControl>
<GridSplitter Grid.Row="0" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="LightGray"/>
<log:Log x:Name="log" Grid.Row="1"/>
</Grid>
<GridSplitter Grid.Column="0" Width="5" HorizontalAlignment="Right" VerticalAlignment="Stretch" Background="LightGray"/>
<Grid Background="#FFE5E5E5" Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="20">
<Grid.RowDefinitions>
<RowDefinition MinHeight="20"/>
<RowDefinition Height="150" MinHeight="20"/>
</Grid.RowDefinitions>
<Label x:Name="arguedCn2Label" Content="С аргументацией примеров" Padding="1,1,1,1" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Row="0" MouseDoubleClick="label_MouseDoubleClick"/>
<TabControl x:Name="arguedCN2TabControl" Grid.Row="0" Margin="0,18,0,5">
<TabItem x:Name="ArguedCN2AttributeTypesTabItem" Header="Типы данных" Width="140">
<Grid Background="#FFE5E5E5">
<attrinuteTypesView:AttributeTypesView x:Name="arguedCN2AttributeTypesView"/>
</Grid>
</TabItem>
<TabItem x:Name="arguedCN2LearningExamplesTabItem" Header="Обучающая выборка" Width="140" IsEnabled="False">
<Grid Background="#FFE5E5E5">
<arguedLearningExamplesView:ArguedLearningExamplesView x:Name="arguedCN2LearningExamplesView"/>
</Grid>
</TabItem>
<TabItem x:Name="arguedCB2ExamineExamplesTabItem" Header="Экзамен" Width="140" IsEnabled="False">
<Grid Background="#FFE5E5E5">
<examineExamolesView:ExamineExamplesView x:Name="arguedCN2ExamineExamplesView"/>
</Grid>
</TabItem>
</TabControl>
<GridSplitter Grid.Row="0" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="LightGray"/>
<log:Log x:Name="arguedLog" Grid.Row="1"/>
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using CN2.Core.Algorithms;
using CN2.Core.DataStructures;
using CN2.UC.ExamineExamolesView;
namespace WpfApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private List<CN2.Core.Algorithms.CN2> _cn2Iqs;
private List<CN2.Core.Algorithms.CN2> _arguedCN2Iqs;
private bool _isRandom;
public MainWindow()
{
InitializeComponent();
cn2AttributeTypesView.OnErrorOccured += ErrorOccured;
cn2AttributeTypesView.OnFileSaved += FileSavedHandler;
cn2AttributeTypesView.OnFileLoaded += FileLoadedHandler;
cn2AttributeTypesView.OnAttributeTypesChanged += cn2AttributeTypesView_AttributeTypesChangedHandler;
cn2LearningExamplesView.OnErrorOccured += ErrorOccured;
cn2LearningExamplesView.OnFileSaved += FileSavedHandler;
cn2LearningExamplesView.OnFileLoaded += FileLoadedHandler;
cn2LearningExamplesView.OnLearn += cn2LearningExamplesView_LearnHandler;
cn2LearningExamplesView.OnCoversCountChanged += cn2LearningExamplesView_OnCoversCountChangedHandler;
cn2ExamineExamplesView.OnErrorOccured += ErrorOccured;
cn2ExamineExamplesView.OnFileSaved += FileSavedHandler;
cn2ExamineExamplesView.OnFileLoaded += FileLoadedHandler;
cn2ExamineExamplesView.OnExamine += cn2ExamineExamplesView_ExamineHandler;
_cn2Iqs = new List<CN2.Core.Algorithms.CN2>();
arguedCN2AttributeTypesView.OnErrorOccured += ArguedErrorOccured;
arguedCN2AttributeTypesView.OnFileSaved += ArguedFileSavedHandler;
arguedCN2AttributeTypesView.OnFileLoaded += ArguedFileLoadedHandler;
arguedCN2AttributeTypesView.OnAttributeTypesChanged += arguedCN2AttributeTypesView_AttributeTypesChangedHandler;
arguedCN2LearningExamplesView.OnErrorOccured += ArguedErrorOccured;
arguedCN2LearningExamplesView.OnFileSaved += ArguedFileSavedHandler;
arguedCN2LearningExamplesView.OnFileLoaded += ArguedFileLoadedHandler;
arguedCN2LearningExamplesView.OnLearn += arguedCN2LearningExamplesView_LearnHandler;
arguedCN2LearningExamplesView.OnCoversCountChanged += arguedCN2LearningExamplesView_OnCoversCountChangedHandler;
arguedCN2ExamineExamplesView.OnErrorOccured += ArguedErrorOccured;
arguedCN2ExamineExamplesView.OnFileSaved += ArguedFileSavedHandler;
arguedCN2ExamineExamplesView.OnFileLoaded += ArguedFileLoadedHandler;
arguedCN2ExamineExamplesView.OnExamine += arguedCN2ExamineExamplesView_ExamineHandler;
_arguedCN2Iqs = new List<CN2.Core.Algorithms.CN2>();
_isRandom = true;
mainWindow.ToolTip = "наугад";
arguedCn2Label.ToolTip = "наугад";
}
#region обработчики событий
private void ErrorOccured(object sender, string message)
{
log.WriteError(message);
}
private void ArguedErrorOccured(object sender, string message)
{
arguedLog.WriteError(message);
}
private void FileSavedHandler(object sender, string message)
{
log.Write(message);
}
private void ArguedFileSavedHandler(object sender, string message)
{
arguedLog.Write(message);
}
private void FileLoadedHandler(object sender, string message)
{
log.Write(message);
}
private void ArguedFileLoadedHandler(object sender, string message)
{
arguedLog.Write(message);
}
private void cn2AttributeTypesView_AttributeTypesChangedHandler(object sender)
{
try
{
AttributeTypeSet attributeTypeSet = cn2AttributeTypesView.GetTypeSet();
cn2LearningExamplesView.SetAttributeTypes(attributeTypeSet);
cn2LearningExamplesTabItem.IsEnabled = attributeTypeSet.IsValid();
cn2ExamineExamplesView.SetAttributeTypes(attributeTypeSet);
}
catch (Exception exception)
{
cn2LearningExamplesTabItem.IsEnabled = false;
ErrorOccured(this, exception.Message);
}
}
private void arguedCN2AttributeTypesView_AttributeTypesChangedHandler(object sender)
{
try
{
AttributeTypeSet attributeTypeSet = arguedCN2AttributeTypesView.GetTypeSet();
arguedCN2LearningExamplesView.SetAttributeTypes(attributeTypeSet);
arguedCN2LearningExamplesTabItem.IsEnabled = attributeTypeSet.IsValid();
arguedCN2ExamineExamplesView.SetArguedAttributeTypes(attributeTypeSet);
}
catch (Exception exception)
{
arguedCN2LearningExamplesTabItem.IsEnabled = false;
ArguedErrorOccured(this, exception.Message);
}
}
private void cn2LearningExamplesView_LearnHandler(object sender, EventArgs e)
{
try
{
_cn2Iqs.Clear();
int coversCount = cn2LearningExamplesView.GetCoversCount();
log.WriteSection("Обучение по алгоритму CN2 с построением " + coversCount + " наборов правил.");
var attributeTypeSet = cn2AttributeTypesView.GetTypeSet();
log.Write("Решающий атрибут: " + attributeTypeSet.DecisiveAttributeType + ".");
log.Write("Предсказывающие атрибуты: ");
for (int i = 0; i < attributeTypeSet.PredictiveAttributeTypes.Count; i++)
{
log.Write(i + 1 + ". " + attributeTypeSet.PredictiveAttributeTypes[i] + ".");
}
for (int i = 0; i < coversCount; i++)
{
log.WriteSection("Определение " + (i + 1).ToString() + " набора правил.");
CN2.Core.Algorithms.CN2 iq = new CN2.Core.Algorithms.CN2();
iq.Learn(cn2LearningExamplesView.GetExamples(), cn2LearningExamplesView.GetStarSize(),
cn2LearningExamplesView.GetHeapSize(), _isRandom);
_cn2Iqs.Add(iq);
for (int j = 0; j < iq.Cover.Count; j++)
{
log.Write(j + 1 + ". Если " + iq.Cover[j].Condition.GetValueString() + ", то " +
iq.Cover[j].Result.GetValueString() + ".");
}
log.WriteSuccess("Набор продукционных правил " + (i + 1).ToString() + " определён.");
}
List<Tuple<ProductionRule, int>> productionRulesChart = new List<Tuple<ProductionRule, int>>();
foreach (var iq in _cn2Iqs)
{
foreach (var productionRule in iq.Cover)
{
bool isFounded = false;
for (int i = 0; i < productionRulesChart.Count; i++)
{
if (productionRule.Equals(productionRulesChart[i].Item1))
{
productionRulesChart[i] = new Tuple<ProductionRule, int>(productionRulesChart[i].Item1,
productionRulesChart[i].Item2 + 1);
isFounded = true;
}
}
if (!isFounded)
{
productionRulesChart.Add(new Tuple<ProductionRule, int>(productionRule, 1));
}
}
}
for (int i = 0; i < productionRulesChart.Count; i++)
{
for (int j = i + 1; j < productionRulesChart.Count; j++)
{
if (productionRulesChart[i].Item2 < productionRulesChart[j].Item2)
{
var buffer = productionRulesChart[i];
productionRulesChart[i] = productionRulesChart[j];
productionRulesChart[j] = buffer;
}
}
}
log.WriteSection("Сводка правил.");
for (int i = 0; i < productionRulesChart.Count; i++)
{
log.Write((i + 1).ToString() + ". " + productionRulesChart[i].Item1 + ". Встречается " +
productionRulesChart[i].Item2 + " раз.");
}
log.WriteSuccess("Обучение произведено.");
cn2ExamineExamplesView.SetCoversCount(cn2LearningExamplesView.GetCoversCount());
cn2ExamineExamplesTabItem.IsEnabled = true;
}
catch (Exception exception)
{
cn2ExamineExamplesTabItem.IsEnabled = false;
ErrorOccured(this, exception.Message);
}
}
private void cn2LearningExamplesView_OnCoversCountChangedHandler(object sender, int coversCount)
{
cn2ExamineExamplesView.SetCoversCount(coversCount);
}
private void arguedCN2LearningExamplesView_LearnHandler(object sender, EventArgs e)
{
try
{
_arguedCN2Iqs.Clear();
int coversCount = arguedCN2LearningExamplesView.GetCoversCount();
arguedLog.WriteSection("Обучение по алгоритму CN2 с аргументированными примерами с построением " + coversCount +
" наборов правил.");
var attributeTypeSet = arguedCN2AttributeTypesView.GetTypeSet();
arguedLog.Write("Решающий атрибут: " + attributeTypeSet.DecisiveAttributeType + ".");
arguedLog.Write("Предсказывающие атрибуты: ");
for (int i = 0; i < attributeTypeSet.PredictiveAttributeTypes.Count; i++)
{
arguedLog.Write(i + 1 + ". " + attributeTypeSet.PredictiveAttributeTypes[i] + ".");
}
for (int i = 0; i < coversCount; i++)
{
arguedLog.WriteSubSection1("Определение " + (i + 1).ToString() + " набора правил.");
CN2.Core.Algorithms.CN2 iq = new CN2.Core.Algorithms.CN2();
iq.Learn(arguedCN2LearningExamplesView.GetExamples(), arguedCN2LearningExamplesView.GetStarSize(),
arguedCN2LearningExamplesView.GetHeapSize(), _isRandom);
_arguedCN2Iqs.Add(iq);
for (int j = 0; j < iq.Cover.Count; j++)
{
arguedLog.Write(j + 1 + ". Если " + iq.Cover[j].Condition.GetValueString() + ", то " +
iq.Cover[j].Result.GetValueString() + ".");
}
arguedLog.WriteSubSection2("Аргументированные правила:");
for (int j = 0; j < iq.ArguedCover.Count; j ++)
{
arguedLog.Write(j + 1 + ". Если " + iq.ArguedCover[j].Item1.Condition.GetValueString() + ", то " +
iq.ArguedCover[j].Item1.Result.GetValueString() + " потому что: " +
iq.ArguedCover[j].Item2.BecauseExpression + ".");
}
arguedLog.WriteSuccess("Набор продукционных правил " + (i + 1).ToString() + " определён.");
}
List<Tuple<ProductionRule, int>> productionRulesChart = new List<Tuple<ProductionRule, int>>();
foreach (var iq in _arguedCN2Iqs)
{
foreach (var productionRule in iq.Cover)
{
bool isFounded = false;
for (int i = 0; i < productionRulesChart.Count; i++)
{
if (productionRule.Equals(productionRulesChart[i].Item1))
{
productionRulesChart[i] = new Tuple<ProductionRule, int>(productionRulesChart[i].Item1,
productionRulesChart[i].Item2 + 1);
isFounded = true;
}
}
if (!isFounded)
{
productionRulesChart.Add(new Tuple<ProductionRule, int>(productionRule, 1));
}
}
}
for (int i = 0; i < productionRulesChart.Count; i++)
{
for (int j = i + 1; j < productionRulesChart.Count; j++)
{
if (productionRulesChart[i].Item2 < productionRulesChart[j].Item2)
{
var buffer = productionRulesChart[i];
productionRulesChart[i] = productionRulesChart[j];
productionRulesChart[j] = buffer;
}
}
}
arguedLog.WriteSection("Сводка правил.");
for (int i = 0; i < productionRulesChart.Count; i++)
{
arguedLog.Write((i + 1).ToString() + ". " + productionRulesChart[i].Item1 + ". Встречается " +
productionRulesChart[i].Item2 +
(productionRulesChart[i].Item2 >= 2 && productionRulesChart[i].Item2 <= 4
? " раза."
: " раз."));
}
arguedLog.WriteSuccess("Обучение произведено.");
arguedCN2ExamineExamplesView.SetCoversCount(arguedCN2LearningExamplesView.GetCoversCount());
arguedCB2ExamineExamplesTabItem.IsEnabled = true;
}
catch (Exception exception)
{
arguedCB2ExamineExamplesTabItem.IsEnabled = false;
ArguedErrorOccured(this, exception.Message);
}
}
private void arguedCN2LearningExamplesView_OnCoversCountChangedHandler(object sender, int coversCount)
{
arguedCN2ExamineExamplesView.SetCoversCount(coversCount);
}
private void cn2ExamineExamplesView_ExamineHandler(object sender, EventArgs e)
{
try
{
log.WriteSection("Экзамен по алгоритму CN2 для " + (cn2ExamineExamplesView.CoverId + 1).ToString() + " набора продукционных правил.");
var iq = _cn2Iqs[cn2ExamineExamplesView.CoverId];
List<ExaminableExample> examineResults = iq.Examine(cn2ExamineExamplesView.GetExamples()).Select(example=>example.Item1).ToList();
cn2ExamineExamplesView.SetExaminedExamples(examineResults);
log.Write("Процент правильно рассчитанных примеров: " + iq.PCRE + ".");
log.WriteSuccess("Экзамен проведён.");
}
catch (Exception exception)
{
ErrorOccured(this, exception.Message);
}
}
private void arguedCN2ExamineExamplesView_ExamineHandler(object sender, EventArgs e)
{
try
{
arguedLog.WriteSection("Экзамен по алгоритму CN2 с аргументацией примеров для "+ (arguedCN2ExamineExamplesView.CoverId + 1).ToString() + " набора продукционных правил.");
var arguedIq = _arguedCN2Iqs[arguedCN2ExamineExamplesView.CoverId];
List<Tuple<ExaminableExample, ProductionRule>> examineResults = arguedIq.Examine(arguedCN2ExamineExamplesView.GetExamples());
arguedCN2ExamineExamplesView.SetExaminedExamples(examineResults);
arguedLog.Write("Процент правильно рассчитанных примеров: " + arguedIq.PCRE + ".");
arguedLog.WriteSuccess("Экзамен проведён.");
}
catch (Exception exception)
{
ArguedErrorOccured(this, exception.Message);
}
}
}
}
