2024年2月

本文是OFDM系统的不同QAM调制阶数的误码率与误比特率仿真,仅考虑在高斯白噪声信道下的情景,着重分析不同信噪比下的误码(符号)率性能曲线,不关心具体的调制与解调方案,仿真结果与理论的误码率曲线进行了对比。

考虑一个简单的OFDM系统,每个频域子载波承载一个QAM调制符号,在经过不同信噪比白噪声信道之后,每个QAM调制符号的解调性能如何,每个符号对应的比特解码性能如何?理论的误码性能如何?

  1. 代码
    clc;close all;clear
    
    %% Seting parameters
    EbN0_list = 0:1:10;
    Q_order_list = 2:2:10;
    loopNumber = 10;
    fprintf('Qm\t EbN0 \t \t EsN0 \t \t SNR_Cal \t \t ser \t\t ser_theory\t\t\t ber\t\t nloop \t\t \n');
    for iQorder = 1 : length(Q_order_list)
    for iEbN0 = 1 : length(EbN0_list)
    
    %% Frame structure
    N_Frame = 10;
    N_Symbol = 14;
    N_RB = 106;
    N_SC_perRB = 12;
    N_SC = N_RB * N_SC_perRB;
    N_Ant = 1;
    N_fft_order = floor(log2(N_RB * N_SC_perRB));
    N_fft = 2^(N_fft_order+1);
    N_cp = N_fft/8;
    EbN0 = EbN0_list(iEbN0);
    
    %% Modulation
    Q_order = Q_order_list(iQorder);
    Qm = 2^Q_order;
    N_bit = N_Frame * N_Symbol * N_RB * N_SC_perRB * Q_order;
    
    %% Noise Calculation
    SNR =  EbN0 + 10 * log10(Q_order);
    
    %% Loop
    for iloop = 1 :loopNumber
    data_bit_in = randi([0 1], 1, N_bit);
    dataSymbolsIn = bi2de(reshape(data_bit_in, Q_order, N_bit/Q_order).', 'left-msb'); 
    dataMod = qammod(dataSymbolsIn, Qm,'UnitAveragePower', true); 
    
    %% Show Constellation
    %scatterplotme(dataMod)
    
    %% Resource Mapping
    RE_Grid = zeros(N_RB * N_SC_perRB,N_Symbol * N_Frame);
    dataMod_tmp = reshape(dataMod,N_RB * N_SC_perRB,[]); %only data
    Power_Scale = 1;
    RE_Grid_all = Power_Scale * dataMod_tmp;
    
    %% IFFT add CP
    frame_mod_shift = ifftshift(RE_Grid_all); 
    ifft_data = ifft(frame_mod_shift,N_fft)*sqrt(N_fft); 
    %ifft_data = ifft(frame_mod_shift)*sqrt(1272); 
    Tx_cd = [ifft_data(N_fft-N_cp+1:end,:);ifft_data];
    time_signal = reshape(Tx_cd,[],1);
    
    %% Channel
    power_RE = sum(sum(abs(RE_Grid_all).^2)) / N_RB / N_SC_perRB / N_Symbol / N_Frame;
    power_tp = sum(sum(abs(ifft_data).^2)) / N_RB / N_SC_perRB / N_Symbol / N_Frame;  %IFFT zero padding averages the true RE Power
    N0 = power_RE .* 10.^(-SNR / 10);
    white_noise_starand = 1/sqrt(2)*(randn(size(time_signal)) + 1j * randn(size(time_signal)));
    TransmittedSignal = time_signal + sqrt(N0) * white_noise_starand;
    
    %% Receive and Sys
    ReceivedSignal = TransmittedSignal;
    
    %% FFT and Frame   
    frame_recieved_parallel = reshape(ReceivedSignal, N_fft + N_cp, []);
    frame_Received = frame_recieved_parallel(N_cp + 1:end,:);    
    frame_Grid_Received = fft(frame_Received,N_fft) / sqrt(N_fft);
    RE_Grid_all_Received = fftshift(frame_Grid_Received(1 : N_SC,:));
    
    %% Demodulation
    RE_PreDeMod = reshape(RE_Grid_all_Received,[],1);
    dataSymbolsOut = qamdemod(RE_PreDeMod, Qm,'UnitAveragePower', true); 
    data_bit_out = reshape((de2bi(dataSymbolsOut, 'left-msb')).',1,[]); 
    power_RE_receid = sum(sum(abs(RE_PreDeMod).^2)) / N_RB / N_SC_perRB / N_Symbol / N_Frame;
    snr_all(iQorder,iEbN0,iloop) = 10*log10(power_RE/(power_RE_receid - power_RE));
    %% Result: Ser and Ber
    %Ser
    sym_err = length(find(dataSymbolsOut - dataSymbolsIn));
    ser_all(iQorder,iEbN0,iloop) = sym_err / length(dataSymbolsOut);
    %Ber
    bit_error = sum(abs(data_bit_out - data_bit_in));
    ber_all(iQorder,iEbN0,iloop) = bit_error / length(data_bit_out);
    end
    sers = mean(ser_all,3);
    snrs = mean(snr_all,3);
    bers = mean(ber_all,3);
    sers_theory(iQorder,iEbN0) = QAM_SER_Theory(Qm,EbN0);
    
        fprintf('%dQAM\t%f\t %f\t %f\t %e\t\t%e\t\t%e\t\t%d\t\n', Qm, EbN0, SNR,snrs(iQorder,iEbN0),sers(iQorder,iEbN0),sers_theory(iQorder,iEbN0),bers(iQorder,iEbN0),loopNumber);
        end
    end
    
    figure(1)
    semilogy(EbN0_list, bers(1,:), 'k--+');
    hold on 
    grid on
    semilogy(EbN0_list, bers(2,:), 'r--o');
    semilogy(EbN0_list, bers(3,:), 'b--x');
    semilogy(EbN0_list, bers(4,:), 'g--s');
    xlabel('Eb/N0,dB');
    ylabel('BER');
    title('BER VERS SNR');
    legend('QPSK','16QAM','256QAM','1024QAM');
    
    
    figure(2)
    semilogy(EbN0_list, sers(1,:), 'k--+');
    hold on 
    grid on
    semilogy(EbN0_list, sers_theory(1,:), 'k-');
    semilogy(EbN0_list, sers(2,:), 'r--o');
    semilogy(EbN0_list, sers_theory(2,:), 'r-');
    semilogy(EbN0_list, sers(3,:), 'b--x');
    semilogy(EbN0_list, sers_theory(3,:), 'b-');
    semilogy(EbN0_list, sers(4,:), 'g--s');
    semilogy(EbN0_list, sers_theory(4,:), 'g-');
    xlabel('Eb/N0,dB');
    ylabel('SER');
    title('SER VERS SNR');
    %SML =  simulation, THR = theory
    legend('QPSK-SML','QPSK-THR','16QAM-SML','16QAM-THR','256QAM-SML','256QAM-THR','1024QAM-SML','1024QAM-THR');

    计算理论误码率的函数:

    function SER = QAM_SER_Theory(Qm,EbN0)
       %Reference https://dsplog.com/2012/01/01/symbol-error-rate-16qam-64qam-256qam/
       Q_order = log2(Qm);
       EsN0_DB =  EbN0 + 10 * log10(Q_order);
       EsN0 = 10.^( EsN0_DB/ 10);
       k = sqrt(3 / (2*(Qm - 1)));
       k_snr = k * sqrt(EsN0);
       cer = erfc(k_snr);
       SER = 2*(1 - 1/sqrt(Qm))*cer - (1 - 2/sqrt(Qm) + 1/Qm) * (cer.^2);
    %    cer = erfc(sqrt(EsN0/2));
    %    SER = cer - 1/4*cer.^2;
    end
    

    计算理论误比特率的函数需要参考文献,不过观察误码率与误比特率曲线,大体趋势相同,也许仅相差一个和调制阶数相关的常数(后来验证并非如此简单)。

  2. 仿真结果

    1:SER VERS SNR(该图理论(THR)误符号率曲线和实际仿真(SML)理论误符号率曲线基本重合)

    2:BER VERS SNR(未画出理论误码率曲线)

  3. 分析结论

    本仿真中应该重点关注信噪比的换算,包括Eb/N0(每bit的信噪比)到Es/N0(每QAM符号的信噪比),频域通过IFFT到时域前后计算SNR,特别是子载波个数与IFFT的点数不相同时,如何在时域加噪声,每个时域采样点的噪声功率N0应该加多大。

  4. 反思

    1.仅白噪声下的仿真结果,那么在多径信道下的仿真曲线如何呢?如何利用
    信道均衡
    来对抗多径带来的频率选择性衰落。
    2.在调制阶数越来越高的情况下,误码率与误比特率都随之升高,那么通信中是如何通过调制阶数的升高来提升系统的吞吐量的呢?
    信道编码
    的作用。
    3.如何利用多个天线MIMO技术来提高通信系统的有效性与可靠性?
    信道预编码与均衡

MS SQL Server简介

Microsoft SQL Server(简称SQL Server)是由微软公司开发的关系数据库管理系统,它是一个功能强大、性能卓越的企业级数据库平台,用于存储和处理大型数据集、支持高效查询和分析等操作。SQL Server 支持广泛的应用程序开发接口(API),包括 T-SQL、ADO.NET、ODBC、OLE DB 等,并支持多种操作系统,包括 Windows、Linux 和 Docker 等。SQL Server 包含许多高级功能和组件,如数据仓库、分析服务、报告服务、全文搜索等,可为企业提供全面的数据管理和分析解决方案。

基于Ubuntu的Microsoft SQL Server官方镜像

拉取Microsoft SQL Server 2022容器镜像

sudo docker pull mcr.microsoft.com/mssql/server:2022-latest

验证mssql镜像是否成功拉取到本地

使用以下命令来查看mssql镜像是否成功拉取到本地:

docker images

创建并运行一个mssql容器

使用以下命令启动容器(Docker 镜像启动后,将会自动启动 SQL Server),其中 Y.sa123456 为 SQL Server sa 用户的密码:

这个命令的含义是在 Docker 中以后台模式 (-d) 运行 Microsoft SQL Server 2022 的最新版本 (mcr.microsoft.com/mssql/server:2022-latest) 镜像,并将容器命名为 mssql2022 (--name mssql2022)。同时,通过 -p 1433:1433 参数将容器的 1433 端口映射到主机的 1433 端口上,使得可以通过主机的 1433 端口访问 SQL Server。在容器启动过程中,需要设置两个环境变量:ACCEPT_EULA=Y 表示接受使用条款。MSSQL_SA_PASSWORD=Y.sa123456 表示设置 SA 用户的密码为 "Y.sa123456" [一定要注意密码一点要严格设置不然有坑,我第一次设置的是sa123456就被坑到了]。

docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Y.sa123456" -p 1433:1433 --name mssql2022 -d mcr.microsoft.com/mssql/server:2022-latest

注意大坑使用Docker部署Sql Server容器后过几秒就停止了

SA_PASSWORD=Y.sa123456 为密码,要求是最少8位的强密码,要有大写字母,小写字母,数字以及特殊符号,不然会有一个大坑(Docker启动Sql Server容器后过几秒就停止了)。

验证容器是否创建成功

docker ps

如果使用docker ps查看不到在使用docker ps -a查看,如果docker ps -a可以查看到那就说明容器没有启动需要使用docker start name来启动容器!

设置 Docker 主机防火墙规则

默认情况下,Docker 带有一个内置的防火墙,需要开放 1433 端口才能让外部访问 SQL Server。使用以下命令开放 Docker 主机的 1433 端口:

sudo ufw allow 1433/tcp

重启防火墙,注意重启防火墙之后才会生效:

sudo ufw reload

服务器防火墙配置1433的开放端口

Navicat远程连接

参考文章


1、环境设置:
此环节将加载实现笔记本无缝功能的基本模块,包括NumPy、Pandas和TensorFlow等库。此外,它还建立了关键的环境常数,如图像尺寸和学习率,这对后续分析和模型训练至关重要。


# General
import os
import keras
import numpy as np
import pandas as pd
import tensorflow as tf

# Data
import plotly.express as px
import matplotlib.pyplot as plt

# Data Preprocessing
import tensorflow.data as tfds
from sklearn.model_selection import train_test_split

# Model
from keras.applications import VGG16
from keras.applications import Xception, InceptionV3
from keras.applications import ResNet50V2, ResNet152V2
from keras.applications import MobileNetV3Small, MobileNetV3Large

# Model training
from keras import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.layers import Flatten, GlobalAveragePooling2D
from keras.layers import InputLayer

# Model Callbacks
from keras.callbacks import EarlyStopping
from keras.callbacks import ModelCheckpoint

# Setting constants for reproducibility
np.random.seed(42)
tf.random.set_seed(42)

# Constants
BATCH_SIZE = 32
IMAGE_SIZE = 224

2、加载和处理UTKFace数据集
在Kaggle上发现的UTKFace数据集是一个全面的面部图像集合,专门用于年龄和性别识别任务。它包括2万多张人脸图像,并附有年龄、性别和种族的注释。这些图像是多样化的,代表了不同的种族、年龄和性别,使其成为机器学习和计算机视觉研究的广泛而有价值的资源。

UTKFace数据集中的每张图像都标有人的年龄,范围从0到116岁,以及他们的性别,分类为男性或女性。

此外,数据集还包括有关个人种族的信息,允许进行更细致的分析和应用。

研究人员和开发人员经常利用该数据集来训练和测试面部识别技术中年龄估计、性别分类和其他相关任务的算法。它的大尺寸和多样化的面部特征表示使其成为探索和开发计算机视觉领域模型的热门选择。

# Initialize the directory path
dir_path = "/kaggle/input/utkface-new/UTKFace/"
image_paths = os.listdir(dir_path)

# Initialize a Gender Mapping
gender_mapping = ["Male", "Female"]

# Choose and load an image randomly
rand_image_path = np.random.choice(image_paths)
rand_image = plt.imread(dir_path + rand_image_path)/255.
sample_age, sample_gender, *_ = rand_image_path.split("_")

print(f"Total number of images  : {len(image_paths)}")
print(f"Sample Image path       : {rand_image_path}")
print(f"Sample Age              : {sample_age}")
print(f"Sample Gender           : {gender_mapping[int(sample_gender)]}\n")

# Show the image
plt.figure(figsize = (5,5))
plt.title("Sample Image")
plt.imshow(rand_image)
plt.axis("off")
plt.show()

结果:
Total number of images : 23708
Sample Image path : 1_0_3_20161220222642427.jpg.chip.jpg
Sample Age : 1
Sample Gender : Male
处理23,708张图像需要使用诸如批处理或使用图像生成器之类的内存高效策略来避免压倒性的内存限制。此外,描绘年龄和性别的图像路径结构(第一部分表示年龄,第二部分表示性别(0表示男性,1表示女性))为后续分析和分类提供了重要信息。

仔细管理这些路径将有助于根据年龄和性别属性进行有针对性的数据处理。

# Initialize a male counter variable
male_count = 0

# Initialize variable to store all the ages.
ages = []

# Loop over the paths and check for male images.
for path in image_paths:
    path_split = path.split("_")
    if "0" == path_split[1]:
        male_count += 1
    ages.append(int(path_split[0]))

# Computee total female counts
female_count = len(image_paths) - male_count

# Visualizing The Class Imbalance
pie_chart = px.pie(
    names = gender_mapping,
    values = [male_count, female_count],
    hole = 0.4,
    title = "Gender Distribution (Donut Chart)",
    height = 500
)
pie_chart.show()

bar_graph = px.bar(
    y = gender_mapping,
    x = [male_count, female_count],
    title = "Gender Distribution (Bar Graph)",
    color = gender_mapping,
    height = 500
)
bar_graph.update_layout(
    yaxis_title = "Gender",
    xaxis_title = "Frequency Count"
)
bar_graph.show()


​​​​​​

我们的数据集似乎显示出轻微的类别不平衡,男性图像的数量高于女性图像——大约52%的男性和48%的女性代表。虽然这种不平衡不足以显著影响模型的准确性,但值得注意的是,要全面了解数据集对男性图像的偏见。管理这种轻微的偏差可以提高模型的稳健性和预测的公平性,尽管它目前对准确性的影响可能有限。

# Histogram
fig = px.histogram(sorted(ages), title = "Age Distribution")
fig.update_layout(
    xaxis_title = "Age",
    yaxis_title = "Value Counts"
)
fig.show()

# Violin Plot
fig = px.violin(x = sorted(ages), title = "Age Distribution")
fig.update_layout(
    xaxis_title = "Age",
    yaxis_title = "Distribution"
)
fig.show()

# Box Plot
fig = px.box(x = sorted(ages), notched=True, title = "Age Distribution")
fig.update_layout(
    xaxis_title = "Age",
)
fig.show()


直方图分析在我们的数据集分布中显示了两个突出的峰值。第一个簇包含年龄从0到12岁左右的人,而第二个簇包含16到45岁左右的人。

有趣的是,这些峰值表现出不同的强度,后一个峰值明显更强。具体来说,我们在1岁左右观察到大约1123张图像的峰值,而在26岁时观察到大约2197张图像的峰值。

值得注意的是,虽然我们的数据集确实包含了一些80岁以上的老年人的图像,但这些事件构成了较小的峰值。总的来说,这种分布表明了对年轻群体的潜在偏见,强调了数据集强调的是描绘早期和成年期个体的图像,而不是老年。

从小提琴图和箱形图中获得的见解证实了我们从直方图中得出的初步观察结果。数据明显分为两个子集:一个是非常小的孩子的图像,证实了直方图在1岁左右的峰值,另一个是26到45岁之间的成年人的大量集中。小提琴的情节特别强调这种双峰分布。

在仔细检查箱形图后,向左侧的明显挤压与我们在直方图中观察到的相呼应。这种压缩源于较年长年龄范围内的数值分布拉长和相对稀疏。它重申了我们的理解,即数据集显示出对年轻人的轻微倾斜。

这种固有的偏见可能确实会影响我们模型的准确性,因为它倾向于更有效地从大量的年轻人口统计数据中学习和概括,而可能忽略了老年群体中普遍存在的模式或细微差别。解决这一问题对于确保我们的模式在不同年龄段的公平和全面的学习至关重要。

# SHuffling the Images
np.random.shuffle(image_paths)

# Split data into training, testing and validation set
train_images, test_images = train_test_split(
    image_paths,
    train_size = 0.9,
    test_size = 0.1
)

train_images, valid_images = train_test_split(
    image_paths,
    train_size = 0.9,
    test_size = 0.1
)

print(f"Training Size : {len(train_images)}")
print(f"Testing Size  : {len(test_images)}")

# Extract age and gender
train_ages = [int(path.split("_")[0]) for path in train_images]
train_genders = [int(path.split("_")[1]) for path in train_images]

valid_ages = [int(path.split("_")[0]) for path in valid_images]
valid_genders = [int(path.split("_")[1]) for path in valid_images]

test_ages = [int(path.split("_")[0]) for path in test_images]
test_genders = [int(path.split("_")[1]) for path in test_images]
Training Size : 21337
Testing Size  : 2371

由于我们有大量的图像,因此使用Tensorflow数据集进行高效处理会更好。

def show_image(image, show=False):
    """
    Displays the provided image without axis.

    Args:
    - image (array-like): The image data to be displayed.
    - show (bool): If True, displays the image immediately. Defaults to False.

    Returns:
    - None
    """
    plt.imshow(image)
    plt.axis("off")

    if show:
        plt.show()


def preprocess_age_data(image_path, age, gender, dir_path=dir_path, IMAGE_SIZE = IMAGE_SIZE):
    """
    Preprocesses an image for analysis by extracting age and gender from the image path,
    loading and decoding the image, resizing it to (IMAGE_SIZE,IMAGE_SIZE), normalizing pixel values,
    and returning the preprocessed image along with age and gender labels.

    Args:
    - image_path (str): The path to the image file.
    - dir_path (str): The directory path where the image is located. Defaults to `dir_path`.

    Returns:
    - tuple: A tuple containing the preprocessed image as a TensorFlow tensor,
             the age (int), and the gender (int) extracted from the image path.
    """
    # Load the Image
    image = tf.io.read_file(dir_path + image_path)
    image = tf.io.decode_jpeg(image)

    # Resize and Normalize the Image
    image = tf.image.resize(image, (IMAGE_SIZE, IMAGE_SIZE))
    image = image / 255.
    image = tf.cast(image, tf.float32)

    return image, age

def preprocess_gender_data(image_path, age, gender, dir_path=dir_path, IMAGE_SIZE = IMAGE_SIZE):
    """
    Preprocesses an image for analysis by extracting age and gender from the image path,
    loading and decoding the image, resizing it to (IMAGE_SIZE,IMAGE_SIZE), normalizing pixel values,
    and returning the preprocessed image along with age and gender labels.

    Args:
    - image_path (str): The path to the image file.
    - dir_path (str): The directory path where the image is located. Defaults to `dir_path`.

    Returns:
    - tuple: A tuple containing the preprocessed image as a TensorFlow tensor,
             the age (int), and the gender (int) extracted from the image path.
    """
    # Load the Image
    image = tf.io.read_file(dir_path + image_path)
    image = tf.io.decode_jpeg(image)

    # Resize and Normalize the Image
    image = tf.image.resize(image, (IMAGE_SIZE, IMAGE_SIZE))
    image = image / 255.
    image = tf.cast(image, tf.float32)

    return image, gender
# Obtain training, testing and validation datasets
train_ds = tfds.Dataset.from_tensor_slices((train_images, train_ages, train_genders)).shuffle(2000)
train_age_ds = train_ds.map(preprocess_age_data, num_parallel_calls=BATCH_SIZE).batch(BATCH_SIZE).prefetch(tfds.AUTOTUNE)
train_gender_ds = train_ds.map(preprocess_gender_data, num_parallel_calls=BATCH_SIZE).batch(BATCH_SIZE).prefetch(tfds.AUTOTUNE)

valid_ds = tfds.Dataset.from_tensor_slices((valid_images, valid_ages, valid_genders)).shuffle(2000)
valid_age_ds = valid_ds.map(preprocess_age_data, num_parallel_calls=BATCH_SIZE).batch(BATCH_SIZE).prefetch(tfds.AUTOTUNE)
valid_gender_ds = valid_ds.map(preprocess_gender_data, num_parallel_calls=BATCH_SIZE).batch(BATCH_SIZE).prefetch(tfds.AUTOTUNE)

test_ds  = tfds.Dataset.from_tensor_slices((test_images, test_ages, test_genders)).shuffle(500)
test_age_ds = test_ds.map(preprocess_age_data, num_parallel_calls=BATCH_SIZE).batch(BATCH_SIZE).prefetch(tfds.AUTOTUNE)
test_gender_ds = test_ds.map(preprocess_gender_data, num_parallel_calls=BATCH_SIZE).batch(BATCH_SIZE).prefetch(tfds.AUTOTUNE)
plt.figure(figsize=(15, 10))
for images, ages, genders in train_ds.batch(BATCH_SIZE).take(1):

    for index in range(len(images)):
        image = tf.io.read_file(dir_path + images[index])
        image = tf.io.decode_jpeg(image)
        plt.subplot(4, 8, index + 1)
        plt.imshow(image)
        plt.title(f"Age: {ages[index]}\nGender: {gender_mapping[genders[index]]}")
        plt.axis("off")

plt.tight_layout()
plt.show()

3、骨干架构比较

当我们的目标是为最终模型建立一个最优的架构时,我们正在探索各种候选模型来作为基础架构。这个过程包括评估多个主干,以全面衡量它们的性能。

通过进行比较分析,我们试图辨别每个骨干架构的优点和缺点。我们的目标是确定最合适的骨干体系结构,它将作为我们最终模型的健壮基础。随后,将采用微调对选定的骨干进行细化和定制,确保开发出优化有效的模型。

# Initializing all the Backbones
backbones = [
    (
        "VGG16",
        VGG16(
            input_shape = (IMAGE_SIZE, IMAGE_SIZE, 3),
            weights = "imagenet",
            include_top = False
        )
    ),
    (
        "ResNet50V2",
        ResNet50V2(
            input_shape = (IMAGE_SIZE, IMAGE_SIZE, 3),
            weights = "imagenet",
            include_top = False
        )
    ),

    (
        "ResNet152V2",
        ResNet152V2(
            input_shape = (IMAGE_SIZE, IMAGE_SIZE, 3),
            weights = "imagenet",
            include_top = False
        )
    ),

    (
        "Xception",
        Xception(
            input_shape = (IMAGE_SIZE, IMAGE_SIZE, 3),
            weights = "imagenet",
            include_top = False
        )
    ),

    (
        "InceptionV3",
        InceptionV3(
            input_shape = (IMAGE_SIZE, IMAGE_SIZE, 3),
            weights = "imagenet",
            include_top = False
        )
    ),

    (
        "MobileNetV3Small",
        MobileNetV3Small(
            input_shape = (IMAGE_SIZE, IMAGE_SIZE, 3),
            weights = "imagenet",
            include_top = False
        )
    ),

    (
        "MobileNetV3Large",
        MobileNetV3Large(
            input_shape = (IMAGE_SIZE, IMAGE_SIZE, 3),
            weights = "imagenet",
            include_top = False
        )
    ),

为了找到最好的骨干架构,我们首先需要训练一个相当小的网络进行几次迭代,然后我们将看到哪个骨干架构将更快更好地带来鲁棒性能。

请注意

在进行了几次实验之后,很明显,为年龄和性别预测建立一个单一的模型并不能产生最佳结果。尽管在预测年龄方面取得了显著的成绩,但性别预测的准确性并不理想。因此,更有效的方法是采用两种不同的模型来预测年龄和性别。

在这个修改后的策略中,模型的主干保持不变。然而,重点是在年龄预测的基础上确定表现最好的骨干。由于准确的年龄预测涉及到回归,这是一个更复杂的任务,因此预期在年龄预测中表现出色的骨干在性别预测中也会表现良好。

通过采用不同的年龄和性别预测模型,我们可以根据每个任务提出的独特挑战来定制架构和训练策略。这种专门的方法允许对每个模型进行独立的优化,从而提高整体性能。这种任务的解耦承认了年龄和性别预测的微妙本质,并允许对模型进行微调,以解决与每种预测类型相关的具体挑战。

BACKBONE_HISTORIES = {}

for (name, backbone) in backbones:

    print(f"Testing : {name}")

    # Freeze the Model weights
    backbone.trainable = False

    # Creating a base model
    model = keras.Sequential([
        InputLayer((IMAGE_SIZE, IMAGE_SIZE, 3), name = "InputLayer"),
        backbone,
        Dropout(0.2, name = "SlightDropout"),
        Flatten(name = "FlattenEmbeddings"),
        Dense(1, name = "Age")
    ])

    # Train the model for few iterations
    model.compile(
        loss = ["mae"],
        optimizer = "adam",
        weighted_metrics=[]
    )

    history = model.fit(
        train_age_ds,
        validation_data = valid_age_ds,
        epochs = 5,
        batch_size = BATCH_SIZE
    )

    BACKBONE_HISTORIES[name] = pd.DataFrame(history.history)
    cls()
    print("\n")
metric = "loss"
plt.figure(figsize=(15, 5))
for i, sub in enumerate(['Train', 'Val']):

    plt.subplot(1, 2, i+1)
    plt.title(f"{sub} {metric} Plot")

    for name, history in BACKBONE_HISTORIES.items():
        plt.plot(history[metric] if sub=="Train" else history[f"val_{metric}"], label = name)
        plt.xlabel("Epochs")
        plt.ylabel(metric.title())
        plt.legend()
        plt.grid()

plt.tight_layout()
plt.show()

在仔细检查年龄模型学习曲线后,可以明确排除某些骨干,特别是与MobileNet相关的骨干。他们一贯的糟糕表现使他们不适合做候选人。

VGG16是最有希望的主干,在训练和验证损失方面都表现出稳定的下降,收敛在平均绝对误差8.0左右。这种一致性表明了鲁棒性,使VGG16成为一个有利的选择。

虽然其他一些模型,如Inception和exception,产生较低的训练损失,但它们在训练和验证曲线之间的相当大的差距表明潜在的不稳定性。考虑到速度和健壮性之间的权衡,盗梦空间作为一个值得称赞的选择脱颖而出。

其平滑的增长轨迹和相对的可预测性使其成为一个合适的折衷方案,特别是与异常的更不稳定的性能相比时。这种细致入微的评估指导了选择过程,确保选择的主干与所需的模型特征保持一致。

BACKBONE_HISTORIES = {}

for (name, backbone) in backbones:

    print(f"Testing : {name}")

    # Freeze the Model weights
    backbone.trainable = False

    # Creating a base model
    model = keras.Sequential([
        InputLayer((IMAGE_SIZE, IMAGE_SIZE, 3), name = "InputLayer"),
        backbone,
        Dropout(0.2, name = "SlightDropout"),
        Flatten(name = "FlattenEmbeddings"),
        Dense(1, activation="sigmoid", name = "Age")
    ])

    # Train the model for few iterations
    model.compile(
        loss = ["binary_crossentropy"],
        optimizer = "adam",
        metrics = ['accuracy'],
        weighted_metrics=[]
    )

    history = model.fit(
        train_gender_ds,
        validation_data = valid_gender_ds,
        epochs = 5,
        batch_size = BATCH_SIZE
    )

    BACKBONE_HISTORIES[name] = pd.DataFrame(history.history)
    cls()
    print("\n")
metric = "accuracy"
plt.figure(figsize=(15, 5))
for i, sub in enumerate(['Train', 'Val']):

    plt.subplot(1, 2, i+1)
    plt.title(f"{sub} {metric} Plot")

    for name, history in BACKBONE_HISTORIES.items():
        plt.plot(history[metric] if sub=="Train" else history[f"val_{metric}"], label = name)
        plt.xlabel("Epochs")
        plt.ylabel(metric.title())
        plt.legend()
        plt.grid()

plt.tight_layout()
plt.show()

在评估性别预测准确性的模型时,一个值得注意的消除包括MobileNet模型,它一直表现出较差的性能。

深入研究训练和验证精度曲线揭示了一个杰出的表演者:ResNet152V2模型,由独特的绿色曲线表示。该模型达到了最高的训练精度和验证精度,将其定位为一个引人注目的选择。

然而,必须解决所有模型的训练和验证精度之间的巨大差异。这强调了在最终模型创建期间需要更复杂的基线模型,合并多个层以更好地处理这些差异。

尽管考虑到这一点,ResNet152V2仍然保持着突出的地位,拥有出色的训练和验证性能。值得注意的是,ResNet50V2在训练性能上与ResNet152V2非常接近,尽管在验证精度上有所欠缺。


4、VGG16 Age Network

我们最近宣布了,来自 Google Deepmind 开放权重的语言模型
Gemma现已通过 Hugging Face 面向更广泛的开源社区开放
。该模型提供了两个规模的版本:20 亿和 70 亿参数,包括预训练版本和经过指令调优的版本。它在 Hugging Face 平台上提供支持,可在 Vertex Model Garden 和 Google Kubernetes Engine 中轻松部署和微调。

Gemma 模型系列同样非常适合利用 Colab 提供的免费 GPU 资源进行原型设计和实验。在这篇文章中,我们将简要介绍如何在 GPU 和 Cloud TPU 上,使用 Hugging Face Transformers 和 PEFT 库对 Gemma 模型进行参数高效微调(PEFT),这对想要在自己的数据集上微调 Gemma 模型的用户尤其有用。

为什么选择 PEFT?

即使对于中等大小的语言模型,常规的全参数训练也会非常占用内存和计算资源。对于依赖公共计算平台进行学习和实验的用户来说,如 Colab 或 Kaggle,成本可能过高。另一方面,对于企业用户来说,调整这些模型以适应不同领域的成本也是一个需要优化的重要指标。参数高效微调(PEFT)是一种以低成本实现这一目标的流行方法。


了解更多 PEFT 请参考文章:

image.png


01、树形结构数据

前端开发中会经常用到树形结构数据,如多级菜单、商品的多级分类等。数据库的设计和存储都是扁平结构,就会用到各种Tree树结构的转换操作,本文就尝试全面总结一下。

如下示例数据,关键字段
id
为唯一标识,
pid

父级id
,用来标识父级节点,实现任意多级树形结构。
"pid": 0
“0”标识为根节点,
orderNum
属性用于控制排序。

const data = [
{ "id": 1, "name": "用户中心", "orderNum": 1, "pid": 0 },
{ "id": 2, "name": "订单中心", "orderNum": 2, "pid": 0 },
{ "id": 3, "name": "系统管理", "orderNum": 3, "pid": 0 },
{ "id": 12, "name": "所有订单", "orderNum": 1, "pid": 2 },
{ "id": 14, "name": "待发货", "orderNum": 1.2, "pid": 2 },
{ "id": 15, "name": "订单导出", "orderNum": 2, "pid": 2 },
{ "id": 18, "name": "菜单设置", "orderNum": 1, "pid": 3 },
{ "id": 19, "name": "权限管理", "orderNum": 2, "pid": 3 },
{ "id": 21, "name": "系统权限", "orderNum": 1, "pid": 19 },
{ "id": 22, "name": "角色设置", "orderNum": 2, "pid": 19 },
];

在前端使用的时候,如树形菜单、树形列表、树形表格、下拉树形选择器等,需要把数据转换为树形结构数据,转换后的数据结效果图:

预期的树形数据结构:多了
children
数组存放子节点数据。

[
    { "id": 1, "name": "用户中心", "pid": 0 },
    {
        "id": 2, "name": "订单中心", "pid": 0,
        "children": [
            { "id": 12, "name": "所有订单", "pid": 2 },
            { "id": 14, "name": "待发货", "pid": 2 },
            { "id": 15, "name": "订单导出","pid": 2 }
        ]
    },
    {
        "id": 3, "name": "系统管理", "pid": 0,
        "children": [
            { "id": 18, "name": "菜单设置", "pid": 3 },
            {
                "id": 19, "name": "权限管理", "pid": 3,
                "children": [
                    { "id": 21, "name": "系统权限",  "pid": 19 },
                    { "id": 22, "name": "角色设置",  "pid": 19 }
                ]
            }
        ]
    }
]


02、列表转树-list2Tree

常用的算法有2种: