\documentclass[a4paper]{ctexart}
\usepackage[margin=1in]{geometry}
\usepackage{booktabs}
\usepackage{hyperref}
\usepackage{graphicx}
\usepackage[numbers]{gbt7714}
\RequirePackage[outputdir=./latex-output]{minted}
\setlength{\belowcaptionskip}{1em}

\title{《深度学习》实验报告}
\author{姓名：岳锦鹏\qquad 学号：10213903403\qquad 专业：统计学-计算机\qquad 学院：统计学院}
\date{2024年6月8日}
\ctexset {
    section = {
        name = {,、}, 
        format += \raggedright,
        number = \chinese{section},
    },
    subsection = {
        % name = {（,）},
        number = \arabic{subsection}
    }
}

\begin{document}
    \maketitle
    \section{实验环境}
    \noindent requirements.txt：
    \begin{minted}[frame=leftline, framesep=1em, framerule=1pt]{python}
numpy
paddlepaddle-gpu
scikit-learn
tqdm
    \end{minted}
    这些代码库的作用是什么已经显而易见了，其中 \mintinline{Python}{scikit-learn (sklearn)} 只是用来分训练验证集的。

    \section{实验过程}
    \subsection{实验思路}
    \subsubsection{确定首次召回个数}
    先尝试了一下完全不使用baseline的方法，直接从全部的几千个文档中召回3个文档，发现效果完全不如baseline，于是想到了采用二次召回，第一次采用baseline的方法，从几千个文档中召回一部分，第二次再从召回的这些文档中选出3个。关于第一次召回多少，我首先做了按照baseline的方式直接计算余弦相似度的top k召回率的实验，结果如 表 \ref{first retrieve num} 所示，可以看到当首次召回数量达到500个时已经有很高的召回率了，所以后续的实验都在首次召回500个文章下进行。
    \begin{table}[h]
        \centering \caption{直接计算余弦相似度的召回率} \label{first retrieve num} 
        \begin{tabular}{cc}
        \toprule
         Recall@100 & 0.75 \\
         Recall@500 & 0.93 \\
         Recall@5000 & 0.99 \\
        \bottomrule
        \end{tabular}
    \end{table}

    \subsubsection{二次召回方案选择}
    从500个里召回3个，仍然是一个复杂的任务，尝试过几种方案：
    \begin{enumerate}
        \item 将其当做分类问题，输入一个查询，500个文章，输出这500个文章的概率。缺点是效率可能较低，而且实验后发现效果也不好；
        \item 双塔编码(cross encoder)和point wise 对比学习\cite{jianshu}，给定一个查询和一个文章，模型给出一个得分，对于查询和对应的文章（正例），得分应该更高；对于查询和不对应的文章（负例），得分应该更低，这里的负例一般是从文章库中随机选取。实验后发现不管正负例的比例是多少，效果都不好；
        \item 单塔编码，query通过查询编码器，fact通过文章编码器，之后计算编码后的向量的余弦相似度，如果不使用对比学习会导致模型把所有的文章都编码得非常相似，所以这里还是需要使用对比学习。那么如何选取负例？实验过还是从文章库中随机选取负例，效果还是不好，于是查找资料，发现了这样一篇文章：\cite{aistudio}，里面提到了In-batch Negatives策略，即将一个批次内其他样本都作为负例，尝试后发现效果有很大提升，从baseline的0.2121提升到了0.4059。
    \end{enumerate}

    最终选择了单塔编码的方案，并且使用了In-batch Negatives策略。
    \subsection{数据预处理部分}
    先使用 \mintinline{Python}{sklearn} 中的  \mintinline{Python}{train_test_split} 按8:2的比例分出训练集和验证集。对于训练集和验证集，先把首次召回500个的工作全部完成，即对于每一个查询，先召回500个文章，存放在内存中，训练时在这500 $\times $ num of queries 个样本里训练。
    \subsection{模型构建}
    这里需要做的就是在单塔编码中，如何编码查询，如何编码文章。其实非常简单，就是多层全连接层，使用ReLU作为激活函数，并且使用了残差连接，如图  \ref{model structure} 所示。
    \begin{figure}[h]
        \centering
        \includegraphics[width=1\linewidth]{模型结构图.png}
        \caption{模型结构图}\label{model structure}
        （图中的N表示batch size，EMB表示嵌入维度。）
    \end{figure}

    由于query和fact的每一行是对应的，所以将query和fact通过编码器后的向量，在对角线的位置表示查询和文档对应时的相似度（正例），而非对角线的位置表示非对应时的相似度（负例），所以此矩阵应该和单位矩阵相近，所以可以用交叉熵损失。在实际代码中使用了小于1的margin来代替1形成对角阵。

    这里只使用简单的线性层加残差连接，是因为试了很多种结构，比如更深的带残差块的线性层，比如Transformer（Decoder only），效果都没有简单的线性层效果好。

    \section{实验结果}
    （以下召回率都是指Recall@3）

    % 训练集和验证集比例为8:2，batch size设为1024，训练370轮，优化器为Adam，在验证集上的召回率为0.48165760869565216，提交后（在测试集上）的召回率为0.452148。

    % 不区分训练集和验证集，batch size设为4096，训练1300轮，优化器为Adam，提交后（在测试集上）的召回率为0.529622。

    % 不区分训练集和验证集，batch size设为2048，训练2500步(iter steps)（不是轮(epochs)了），优化器为Adam，提交后（在测试集上）的召回率为0.523275。

    % 不区分训练集和验证集，batch size设为2048，训练2600步，优化器为AdamW，提交后（在测试集上）的召回率为0.523926。

    % 不区分训练集和验证集，batch size设为2048，训练5100步，优化器为AdamW，将模型参数与输入输出的数据类型从float32改为float64，提交后（在测试集上）的召回率为0.584635。

    结果如表  \ref{hyper-parameters} 所示。
    \begin{table}[h]
        \centering
        \caption{尝试不同的超参数}\label{hyper-parameters}
        \begin{tabular}{cccccccc}
        \toprule
         序号 & 是否有验证集 & batch size & epoch / step & 优化器 & 数据类型 & 验证集召回率 & 测试集召回率 \\
         \midrule
         0 & \multicolumn{4}{c}{baseline 无需训练} & float32 & / & 0.212077 \\
         1 & 是 & 1024 & 370 epochs & Adam & float32 & 0.481658 & 0.452148 \\
         2 & 否 & 4096 & 1300 epochs & Adam & float32 & / & 0.529622 \\
         3 & 否 & 2048 & 2500 steps & Adam & float32 & / & 0.523275 \\
         4 & 否 & 2048 & 2600 steps & AdamW & float32 & / & 0.523926 \\
         5 & 否 & 2048 & 5100 steps & AdamW & float64 & / & 0.584635 \\
         6 & 否 & 8192 & 9500 steps & AdamW & float64 & / & \textbf{0.606283} \\
        \bottomrule
        \end{tabular}
    \end{table}

    \bibliography{ref}

\end{document}