Engee 文档
Notebook

利用机器学习和深入学习对雷达目标进行分类

本示例演示了使用机器和深度学习方法对雷达数据进行分类的方法。 以下方法用于解决问题:

  1. **机器学习:**支持向量机(SVM)。
  2. **深度学习:**SqueezeNet,LSTM

目标分类是雷达系统的一项重要任务。 在这个例子中,我们考虑确定哪个物体反射雷达信号的问题—圆柱体或圆锥体。 虽然该示例使用合成数据,但这种方法也可以应用于真实的雷达测量。

SVM培训

首先,您需要导入您正在使用的软件包

在文件中 install_packages.jl 有脚本所需的包。 它们被添加到工作环境中。 在文件中 import_packages 将为脚本导入所有已安装的软件包。 在下面的单元格中,我们运行它们的执行

In [ ]:
include("$(@__DIR__)/Install_packages.jl")
In [ ]:
include("$(@__DIR__)/import_packages.jl")
init()

加载数据

用于训练模型的数据取自雷达对象EPR建模的演示示例。

让我们写下数据所在的路径

In [ ]:
data_path = "$(@__DIR__)/gen_data.csv"

CSV文件包含锥体和圆柱体的数据。 前100个元件是圆柱体,后100个元件是圆锥体。 让我们将数据拆分为训练和测试数据集。

In [ ]:
# Допустим, ваш файл называется "data.csv"
df = CSV.read(data_path, DataFrame)
data = Matrix(df)
cyl_train = data[:, 1:75]    # первые 75 элементов
cyl_test  = data[:, 76:100]  # оставшиеся 25 элементов

# Для конусов:
cone_train = data[:, 101:175]  # 75 элементов
cone_test  = data[:, 176:200]  # 25 элементов
# Объединяем в тренировочную и тестовую выборки:
train_data = hcat(cyl_train, cone_train)
test_data  = hcat(cyl_test, cone_test);
In [ ]:
TrainFeatures_T = permutedims(train_data, (2,1))
TestFeatures_T = permutedims(test_data, (2, 1))

TrainLabels = reshape(vcat(fill(1, 75), fill(2, 75)), :, 1)
TestLabels = reshape(vcat(fill(1, 25), fill(2, 25)), :, 1);

下面的代码使用Morlaix连续小波函数变换从时间序列中提取特征。 首先,对所有数据应用小波变换,随后计算系数的绝对值,以便仅留下振幅。 然后沿时间轴对这些幅度进行平均,这降低了特征的维数。 之后,结果被"压缩"以删除不必要的尺寸。

所有这些都是为训练和测试数据集完成的。

In [ ]:
wavelet_cfg = wavelet(Morlet(π), averagingType=Dirac(), β=1.5)
train_features_cwt = dropdims(mean(abs.(cwt(TrainFeatures_T, wavelet_cfg)), dims=2), dims=2);
In [ ]:
wavelet_cfg = wavelet(Morlet(π), averagingType=Dirac(), β=1.5)
test_features_cwt = dropdims(mean(abs.(cwt(TestFeatures_T, wavelet_cfg)), dims=2), dims=2);

初始化包含类名的列表

In [ ]:
class_names = ["Cylinder","Cone"]

在训练分类器之前,必须将数据转换为与模型兼容的格式:训练特征转换为表格格式,标签转换为分类类型。

In [ ]:
X_train = MLJ.table(train_features_cwt)  # Преобразуем X_train в датафрейм
X_test = MLJ.table(test_features_cwt)  # Преобразуем X_test в датафрейм
y_train  = coerce(vec(TrainLabels), Multiclass)  # Преобразуем y_train в вектор и задаем тип  Multiclass
y_test = coerce(vec(TestLabels), Multiclass);  # Преобразуем y_test в вектор и задаем тип Multiclass

支持向量模型的初始化及其训练

接下来,我们通过初始化参数和交叉验证来配置支持向量模型。

In [ ]:
svm = (@MLJ.load SVC pkg=LIBSVM verbosity=true)()  # Загружаем и создаем модель SVM с использованием LIBSVM через MLJ

# Задаем параметры для модели SVM
svm.kernel = LIBSVM.Kernel.Polynomial         # Тип ядра: полиномиальное (Polynomial)
svm.degree = 2                                # Степень полинома для полиномиального ядра
svm.gamma = 0.1                               # Параметр γ, контролирующий влияние каждой обучающей точки
svm.cost = 1.0                                # Параметр регуляризации 

# Создаем "машину" (machine) для связывания модели с данными
mach = machine(svm, X_train, y_train)

# Настраиваем кросс-валидацию с 5 фолдами
cv = CV(nfolds=5);

我们使用交叉验证来训练模型,并计算训练的准确性

In [ ]:
@info "Load model config, cross-valid"

cv_results = evaluate!(mach; resampling=cv, measures=[accuracy], verbosity=0) # Выполняем кросс-валидацию модели
println("Точность модели: $(cv_results.measurement[1] * 100)" , "%")

训练模型的评估

让我们根据测试数据评估训练好的模型

In [ ]:
@info "Predict test"

y_pred_SVM = MLJ.predict(mach, X_test)   # Выполняеем предсказания модели на тестовых данных

accuracy_score_SVM = accuracy(y_pred_SVM, y_test)  # Вычисляем точность модели на тестовом наборе

println("Точность модели на тесте: ", Int(accuracy_score_SVM * 100), "%")

接下来,我们将构造一个误差矩阵来评估模型-函数的分类质量 plot_confusion_matrix 执行此任务

In [ ]:
function plot_confusion_matrix(C)
    # Создаем heatmap с большими шрифтами и контрастными цветами
    heatmap(
        C,
        title = "Confusion Matrix",
        xlabel = "Predicted",
        ylabel = "True",
        xticks = (1:length(class_names), class_names),
        yticks = (1:length(class_names), class_names),
        c = :viridis,  # Более контрастная цветовая схема
        colorbar_title = "Count",
        size = (600, 400)
    )

    # Добавим значения в ячейки для улучшенной наглядности
    for i in 1:size(C, 1)
        for j in 1:size(C, 2)
            annotate!(j, i, text(C[i, j], :white, 12, :bold)) 
        end
    end

    # Явно отображаем график
    display(current())
end;
In [ ]:
# Пример использования функции
conf_matrix = CM.confmat(y_pred_SVM, y_test, levels=[2, 1])
conf_matrix = CM.matrix(conf_matrix)
plot_confusion_matrix(conf_matrix)

从构造的误差矩阵可以看出,模型很好地对圆锥进行了分类,但圆柱经常与圆锥混淆。

挤压训练

接下来,我们将训练深度学习网络-SqueezeNet。 SqueezeNet是2016年提出的一种紧凑卷积神经网络,以显着更小的尺寸实现AlexNet性能。 它使用包含挤压层(1x1卷积以减少通道数量)和扩展(1x1和3x3卷积以恢复维度)的Fire模块,这减少了参数数量而不会损失质量。 由于其紧凑性,它适用于嵌入式设备。

所需参数

初始化模型训练和数据准备中涉及的参数

In [ ]:
batch_size = 2              # Размер батча для обучения
num_classes = 2             # Количество классов в задаче классификации
lr = 1e-4                   # Скорость обучения (learning rate)
Epochs = 15                 # Количество эпох для обучения
Classes = 1:num_classes;     # Список индексов классов, например, 1 и 2

创建数据集

首先,有必要为网络训练准备数据。 为了获得信号的时频特性,需要对信号进行连续小波变换和构造。 小波被"压缩"以定位具有高时间精度的短期突发,并被"拉伸"以捕获信号结构中的平滑变化。

辅助功能 save_wavelet_images 获得连续小波变换 (CWT) 对于每个雷达信号,它将结果转换为与计算机视觉模型兼容的格式,并将频谱图保存为图像。

初始化几个辅助函数

In [ ]:
# Функция для нормализации значений
function rescale(img)
    min_val = minimum(img)
    max_val = maximum(img)
    return (img .- min_val) ./ (max_val - min_val)
end

# Применение colormap jet и преобразование в RGB
function apply_colormap(data, cmap)
    h, w = size(data)
    rgb_image = [RGB(get(cmap, val)) for val in Iterators.flatten(eachrow(data))]
    return reshape(rgb_image, w, h)
end
# Преобразование непрерывного вейвлет-преобразования в изображение
function apply_image(wt)
    rescaled_data = rescale(abs.(wt))
    colored_image = apply_colormap(rescaled_data, ColorSchemes.inferno)
    resized_image = imresize(colored_image, (224, 224))
    flipped_image = reverse(resized_image, dims=1)
    return flipped_image
end;
In [ ]:
function save_wavelet_images(features_matrix, wavelet_filter, save_path, is_train=true)
    # Определение количества примеров для каждого класса
    
    class1_count = is_train ? 75 : 25  # Количество примеров для класса cone
    class2_count = size(features_matrix, 1) - class1_count  # Количество примеров для класса cylinder
    println(class1_count)
    # Создание папок для каждого класса
    cone_path = joinpath(save_path, "cone")  # cone — класс 1
    cylinder_path = joinpath(save_path, "cylinder")  # cylinder — класс 2
    mkpath(cone_path)
    mkpath(cylinder_path)

    # Обработка строк матрицы для класса cylinder
    for i in 1:class1_count
        res = cwt(features_matrix[i, :], wavelet_filter)
        image_wt = apply_image(res)
        img_filename = joinpath(cylinder_path, "sample_$(i).png")
        save(img_filename, image_wt)

    end

    # Обработка строк матрицы для класса cone
    for i in class1_count+1:class1_count+class2_count
        res = cwt(features_matrix[i, :], wavelet_filter)
        image_wt = apply_image(res)
        img_filename = joinpath(cone_path, "sample_$(i - class1_count).png")
        save(img_filename, image_wt)
    end
end;

一个对象 c 它是一种基于Morlaix小波的可配置小波变换器

In [ ]:
c = wavelet(Morlet(π), averagingType=NoAve(), β=1);

我们将得到一个图像数据库,用于训练神经网络。

In [ ]:
save_wavelet_images(TrainFeatures_T, c, "$(@__DIR__)/New_imgs/train")
save_wavelet_images(TestFeatures_T, c, "$(@__DIR__)/New_imgs/test", false)

让我们来看看生成的图像的一个实例

In [ ]:
i = Images.load("$(@__DIR__)/New_imgs/train/cylinder/sample_2.png")
Out[0]:
No description has been provided for this image

初始化用于数据增广的函数:它负责将图像缩小到224x224的大小,并将数据转换为张量

In [ ]:
function Augment_func(img)
    resized_img = imresize(img, 224, 224)               #Изменение размеров изображения до 224х224
    tensor_image = channelview(resized_img);            #Представление данных в виде тензора
    permutted_tensor = permutedims(tensor_image, (2, 3, 1));        #Изменение порядка размерности до формата (H, W, C)
    permutted_tensor = Float32.(permutted_tensor)                   #Преобразование в тип Float32
    return permutted_tensor
end;

功能 Create_dataset 通过处理图像所在的目录来创建训练数据集。

In [ ]:
function Create_dataset(path)
    img_train = []
    img_test = []
    label_train = []
    label_test = []

    train_path = joinpath(path, "train");
    test_path = joinpath(path, "test");

    # Функция для обработки изображений в заданной директории
    function process_directory(directory, img_array, label_array, label_idx)
        for file in readdir(directory)
            if endswith(file, ".jpg") || endswith(file, ".png")
                file_path = joinpath(directory, file);
                img = Images.load(file_path);
                img = Augment_func(img);
                push!(img_array, img)
                push!(label_array, label_idx)
            end
        end
    end


    # Обработка папки train
    for (idx, label) in enumerate(readdir(train_path))
        println("Processing label in train: ", label)
        label_dir = joinpath(train_path, label)
        process_directory(label_dir, img_train, label_train, idx);
    end

    # Обработка папки test
    for (idx, label) in enumerate(readdir(test_path))
        println("Processing label in test: ", label)
        label_dir = joinpath(test_path, label)
        process_directory(label_dir, img_test, label_test, idx);
    end

    return img_train, img_test, label_train, label_test;
end;

在代码的下一个单元格中,我们将执行创建训练集和测试集的功能。

In [ ]:
path_to_data = "$(@__DIR__)/New_imgs"

img_train, img_test, label_train, label_test = Create_dataset(path_to_data);

我们创建一个DataLoader,将图像批量馈送到模型输入。 我们将它们传输到GPU

**重要提示:**模型是在GPU上训练的,因为这会使学习过程加快很多倍。 如果您需要使用GPU,请联系我们的经理,您将获得GPU的访问权限。 工作目录将包含传输到CPU的已经预训练的网络的权重。 在训练主网络之后,您可以通过将适当的权重加载到模型中来查看CPU格式的网络。

In [ ]:
train_loader_Snet = DataLoader((data=img_train, label=label_train), batchsize=batch_size, shuffle=true, collate=true)
test_loader_Snet = DataLoader((data=img_test, label=label_test), batchsize=batch_size, shuffle=true, collate=true)
train_loader_Snet = gpu.(train_loader_Snet)
test_loader_Snet = gpu.(test_loader_Snet)
@info "loading succes"
[ Info: loading succes

培训准备

通过将模型传输到GPU来初始化模型

In [ ]:
Net = SqueezeNet(;  pretrain=false,
           nclasses = num_classes) |>gpu;

初始化优化器,损失函数

In [ ]:
optimizer_Snet = Flux.Adam(lr, (0.9, 0.99));   
lossSnet(x, y) = Flux.Losses.logitcrossentropy(Net(x), y);

挤压训练

让我们描述一下负责为一个时代训练模型的函数。 功能 train_one_epoch 在单个epoch上执行模型训练,通过加载程序中的所有数据批次 Loader. 稍后,该函数将用于训练模型。 LSTM. 这个函数有一个参数 type_model 这将决定我们正在训练哪个特定模型-卷积或循环

In [ ]:
function train_one_epoch(model, Loader, Tloss, correct_sample, TSamples, loss_function, Optimizer, type_model)
    for (i, (x, y)) in enumerate(Loader) 
         
        if type_model == "Conv"
            TSamples += length(y) 
            gs = gradient(() -> loss_function(x, onehotbatch(y, Classes)), Flux.params(model))          # Рассчитываем градиенты
        elseif type_model == "Recurrent"
            TSamples += size(y, 2) 
            gs = gradient(() -> loss_function(x, y), Flux.params(model))                                # Рассчитываем градиенты
        end
        Flux.update!(Optimizer, Flux.params(model), gs)                                                 # Обновляем оптимизатор
        y_pred = model(x)                                                                               # Делаем предсказание модели
        # Далее вычисляем точность и ошибку нашей моделе на эпохе
        if type_model == "Conv" 
            preds = onecold(y_pred, Classes)
            correct_sample += sum(preds .== y)
            Tloss += loss_function(x, onehotbatch(y, Classes))   
        elseif type_model == "Recurrent"
            Tloss += loss_function(x, y)
            predicted_classes = onecold(y_pred)  
            true_classes = onecold(y)         
            correct_sample += sum(predicted_classes .== true_classes)  
        end
    end
    return Tloss, TSamples, correct_sample
end;

启动SqueezeNet学习流程

In [ ]:
@info "Starting training loop"

for epoch in 1:10 
    total_loss = 0.0 
    train_running_correct = 0  
    total_samples = 0   
    @info "Epoch $epoch"

    total_loss, total_samples, train_running_correct = train_one_epoch(Net, train_loader_Snet, total_loss, 
                                                train_running_correct, total_samples, lossSnet, optimizer_Snet, "Conv")



    epoch_loss = total_loss / total_samples
    epoch_acc = 100.0 * (train_running_correct / total_samples)
    println("loss: $epoch_loss, accuracy: $epoch_acc")  
end

保存我们训练好的模型

In [ ]:
mkdir("$(@__DIR__)/models")
Out[0]:
"/user/nn/radar_classification_using_ML_DL/models"
In [ ]:
cpu(Net)
@save "$(@__DIR__)/models/SNET.bson" Net

训练好的SqueezeNet模型的评估

我们来评估训练好的模型。 功能 evaluate_model_accuracy 负责计算模型的精度

In [ ]:
function evaluate_model_accuracy(loader, model, classes, loss_function, type_model)
    total_loss, correct_predictions, total_samples = 0.0, 0, 0
    all_preds = []  
    True_labels = []
    for (x, y) in loader
        # Накопление потерь
        total_loss += type_model == "Conv" ? loss_function(x, onehotbatch(y, classes)) : loss_function(x, y)

        # Предсказания и вычисление точности
        y_pred = model(x)
        
        preds = type_model == "Conv" ? onecold(y_pred, classes) : onecold(y_pred)
        true_classes = type_model == "Conv" ? y : onecold(y)
        append!(all_preds, preds)
        append!(True_labels, true_classes)
        correct_predictions += sum(preds .== true_classes)
        total_samples += type_model == "Conv" ? length(y) : size(y, 2)
    end
    # Вычисление точности
    accuracy = 100.0 * correct_predictions / total_samples
    return accuracy, all_preds, True_labels
end;
In [ ]:
accuracy_score_Snet, all_predsSnet, true_predS = evaluate_model_accuracy(test_loader_Snet, Net, Classes, lossSnet, "Conv");
println("Accuracy trained model:", accuracy_score_Snet, "%")
Accuracy trained model:100.0%

正如您在上面看到的,模型的准确性是100%。 这清楚地表明,模型完美地将两个类彼此分开。 让我们来看一个模型预测的具体例子。

让我们使用函数构造误差矩阵 plot_confusion_matrix

In [ ]:
preds_for_CM = map(x -> x[1], all_predsSnet);
conf_matrix = CM.confmat(preds_for_CM, true_predS, levels=[1, 2])
conf_matrix = CM.matrix(conf_matrix)
plot_confusion_matrix(conf_matrix)

模型预测

从测试数据集上传图像

In [ ]:
path = "$(@__DIR__)/New_imgs/test/cone/sample_14.png";
img = Images.load(path)  # Загружаем изображение
img_aug = Augment_func(img);
img_res = reshape(img_aug, size(img_aug, 1), size(img_aug, 2), size(img_aug, 3), 1);

加载模型的权重

In [ ]:
model_data = BSON.load("$(@__DIR__)/models/SNET.bson")
snet_cpu = model_data[:Net] |> cpu;

做出预测

In [ ]:
y_pred = (snet_cpu(img_res))
pred = onecold(y_pred, Classes)
# pred = cpu(preds)  # Переносим предсказания на CPU
predicted_class_name = class_names[pred]  # Получаем название предсказанного класса 
println("Предсказанный класс: $predicted_class_name")
Предсказанный класс: ["Cone"]

因此,该模型应对其任务。

LSTM

本示例的最后一节介绍了LSTM工作流。 首先,确定LSTM水平:

参数的初始化

初始化模型训练和数据准备中涉及的参数

In [ ]:
MaxEpochs = 50;
BatchSize = 100;
learningrate = 0.01;
n_features = 1;
num_classes = 2;

资料收集

提交给输入的符号是在脚本开始时定义的。 标签在某种程度上被重新定义

In [ ]:
Trainlabels = vcat(fill(1, 75), fill(2, 75));
Testlabels = vcat(fill(1, 25), fill(2, 25));

Trainlabels = CategoricalArray(Trainlabels; levels=[1, 2]);
Testlabels = CategoricalArray(Testlabels; levels=[1, 2]);

接下来,将数据简化为lstm网络在输入端所需的形式。

In [ ]:
train_features = reshape(train_data, 1, size(train_data, 1), size(train_data, 2))
test_features = reshape(test_data, 1, size(test_data, 1), size(test_data, 2))

# TrainFeatures = permutedims(TrainFeatures, (2, 1))
TrainLabels = onehotbatch(Trainlabels, 1:num_classes)
TestLabels = onehotbatch(Testlabels, 1:num_classes)
Out[0]:
2×50 OneHotMatrix(::Vector{UInt32}) with eltype Bool:
 1  1  1  1  1  1  1  1  1  1  1  1  1  …  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅
 ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅  ⋅     1  1  1  1  1  1  1  1  1  1  1  1

让我们将训练和测试数据放入类型 DataLoader 并将它们传输到GPU。

**重要提示:**模型是在GPU上训练的,因为这会使学习过程加快很多倍。 如果您需要使用GPU,请联系我们的经理,您将获得GPU的访问权限。 工作目录将包含传输到CPU的已经预训练的网络的权重。 在训练主网络之后,您可以通过将适当的权重加载到模型中来查看CPU格式的网络。

In [ ]:
train_loader_lstm = DataLoader((data=train_features, label=TrainLabels), batchsize=BatchSize, shuffle=true);
train_loader_lstm = gpu.(train_loader_lstm);
test_loader_lstm = DataLoader((data=test_features, label=TestLabels), batchsize=BatchSize, shuffle=true);
test_loader_lstm = gpu.(test_loader_lstm);

初始化模型

初始化我们将训练的模型。 在这个例子中,我们的模型是一个相互连接的层链。

In [ ]:
model_lstm = Chain(
  LSTM(n_features, 100),
  x -> x[:, end, :], 
  Dense(100, num_classes),
  Flux.softmax) |> gpu;

初始化优化器,损失函数

In [ ]:
optLSTM = Flux.Adam(learningrate, (0.9, 0.99));   
lossLSTM(x, y) = Flux.Losses.crossentropy(model_lstm(x), y);

培训课程

接下来是模型的训练周期

In [ ]:
for epoch in 1:MaxEpochs
    total_loss = 0.0
    correct_predictions = 0
    total_samples = 0

    total_loss, total_samples, correct_predictions = train_one_epoch(model_lstm, train_loader_lstm, total_loss, 
                                                correct_predictions, total_samples, lossLSTM, optLSTM, "Recurrent")

    # Вычисление точности
    accuracy = 100.0 * correct_predictions / total_samples

    println("Epoch $epoch, Loss: $(total_loss), Accuracy: $(accuracy)%")
end

保存模型

In [ ]:
cpu(model_lstm)
@save "$(@__DIR__)/models/lstm.bson" model_lstm

训练模型的评估

让我们通过计算测试数据集的准确性来评估我们的模型。

In [ ]:
accuracy_score_LSTM, all_predsLSTMm, true_predS_LSTM = evaluate_model_accuracy(test_loader_lstm, model_lstm, classes, lossLSTM, "Recurrent");
println("Accuracy trained model:", accuracy_score_LSTM, "%")
Accuracy trained model:84.0%

现在让我们构建一个误差矩阵,用于模型的可视化评估。

In [ ]:
preds_for_CM_LSTM = map(x -> x[1], all_predsLSTMm);
conf_matrix = CM.confmat(preds_for_CM_LSTM, true_predS_LSTM, levels=[1, 2])
conf_matrix = CM.matrix(conf_matrix)
plot_confusion_matrix(conf_matrix)

让我们在一个特定的观测值上测试模型

In [ ]:
random_index = rand(1:size(test_features, 3))  
random_sample = test_features[:, :, random_index] 
random_label = onecold(TestLabels[:, random_index]) 
random_sample = cpu(random_sample);
In [ ]:
model_data = BSON.load("$(@__DIR__)/models/lstm.bson")
cpu_lstm = model_data[:model_lstm] |>cpu

predicted_probs = cpu_lstm(random_sample)
predicted_class = onecold(predicted_probs) 
# Вывод результата
println("Random Sample Index: $random_index")
println("True Label: $random_label")
println("Predicted Probabilities: $predicted_probs")
println("Predicted Class: $predicted_class")
Random Sample Index: 12
True Label: 1
Predicted Probabilities: Float32[0.99999905; 9.580198f-7;;]
Predicted Class: [1]

从上面的结果可以看出,模型正确地对提供给它的实例进行了分类。

结论

此示例显示了使用机器学习和深度学习技术执行雷达目标分类的工作流程。 虽然该示例使用合成数据进行训练和测试,但可以轻松扩展以考虑雷达的实际结果。