<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Publishing DTD v1.4 20241031//EN" "JATS-journalpublishing1-4.dtd">
<article xmlns:mml="http://www.w3.org/1998/Math/MathML" xmlns:xlink="http://www.w3.org/1999/xlink" article-type="research-article" dtd-version="1.4" xml:lang="en">
  <front>
    <journal-meta>
      <journal-id journal-id-type="publisher-id">jcc</journal-id>
      <journal-title-group>
        <journal-title>Journal of Computer and Communications</journal-title>
      </journal-title-group>
      <issn pub-type="epub">2327-5227</issn>
      <issn pub-type="ppub">2327-5219</issn>
      <publisher>
        <publisher-name>Scientific Research Publishing</publisher-name>
      </publisher>
    </journal-meta>
    <article-meta>
      <article-id pub-id-type="doi">10.4236/jcc.2026.1410002</article-id>
      <article-id pub-id-type="publisher-id">jcc-154675</article-id>
      <article-categories>
        <subj-group>
          <subject>Article</subject>
        </subj-group>
        <subj-group>
          <subject>Computer Science</subject>
          <subject>Communications</subject>
        </subj-group>
      </article-categories>
      <title-group>
        <article-title>Investigating the Effect of Temporal Sequence Length and Additive Attention Pooling on BiLSTM-Based Fencing Outcome Recognition</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <name name-style="western">
            <surname>Wu</surname>
            <given-names>Qiuhong</given-names>
          </name>
          <xref ref-type="aff" rid="aff1">1</xref>
        </contrib>
        <contrib contrib-type="author">
          <name name-style="western">
            <surname>Zhang</surname>
            <given-names>Yang</given-names>
          </name>
          <xref ref-type="aff" rid="aff1">1</xref>
        </contrib>
      </contrib-group>
      <aff id="aff1"><label>1</label> Shanghai Qingpu World Foreign Language High School, Shanghai, China </aff>
      <author-notes>
        <fn fn-type="conflict" id="fn-conflict">
          <p>The authors declare no conflicts of interest regarding the publication of this paper.</p>
        </fn>
      </author-notes>
      <pub-date pub-type="epub">
        <day>22</day>
        <month>10</month>
        <year>2026</year>
      </pub-date>
      <pub-date pub-type="collection">
        <month>10</month>
        <year>2026</year>
      </pub-date>
      <volume>14</volume>
      <issue>10</issue>
      <fpage>20</fpage>
      <lpage>54</lpage>
      <history>
        <date date-type="received">
          <day>03</day>
          <month>09</month>
          <year>2026</year>
        </date>
        <date date-type="accepted">
          <day>19</day>
          <month>10</month>
          <year>2026</year>
        </date>
        <date date-type="published">
          <day>22</day>
          <month>10</month>
          <year>2026</year>
        </date>
      </history>
      <permissions>
        <copyright-statement>© 2026 by the authors and Scientific Research Publishing Inc.</copyright-statement>
        <copyright-year>2026</copyright-year>
        <license license-type="open-access">
          <license-p> This article is an open access article distributed under the terms and conditions of the Creative Commons Attribution (CC BY) license ( <ext-link ext-link-type="uri" xlink:href="https://creativecommons.org/licenses/by/4.0/">https://creativecommons.org/licenses/by/4.0/</ext-link> ). </license-p>
        </license>
      </permissions>
      <self-uri content-type="doi" xlink:href="https://doi.org/10.4236/jcc.2026.1410002">https://doi.org/10.4236/jcc.2026.1410002</self-uri>
      <abstract>
        <p>We examine the impacts of temporal sequence length and additive temporal attention pooling on the automated classification performance of a Bidirectional Long Short-Term Memory (BiLSTM) model for fencing outcome recognition. Human pose data was modelled via the OpenPose BODY_25 keypoints, and the fencing outcomes were then labeled in each exchange (<italic>i</italic>.<italic>e</italic>., left-fencer score, right-fencer score, no valid score). A controlled 3 × 2 experimental design was employed with varying sequence lengths (25, 50, or 75 frames) with or without additive temporal attention. All six setups were evaluated on a single fixed source-disjoint training, validation and test split with five matched training seeds for each setup. Only the best validation accuracy was used for model selection. Our best performing model was an attention model with a sequence length of 25 frames and a mean best validation accuracy of 58.93%. Nevertheless, its 0.18-percentage-point improvement on the 25-frame no-attention model was both statistically and practically insignificant. For no-attention, increasing the sequence length did not yield meaningful performance gain. We see that introducing attention did not make much of a difference when we used 25 frames, but improved the mean best validation accuracy by 1.77 percentage points when we used 50 frames, and 3.23 percentage points when we used 75 frames, when compared to the no-attention version of the respective model. The test accuracy of the model, which was chosen at the best validation performance, was 57.35%, with a macro-F1 of 48.29% and a balanced accuracy of 51.14%. Performance on the underrepresented class of no valid score remained poor. This study suggests that we may not benefit from longer temporal windowing for fencing outcome recognition and additive temporal attention may become counterproductive at longer sequencing. The proposed classifier, hence, holds its value as an experimental model but is not yet effective as a refereeing tool.</p>
      </abstract>
      <kwd-group kwd-group-type="author-generated" xml:lang="en">
        <kwd>Fencing Outcome Recognition</kwd>
        <kwd>Human Pose Estimation</kwd>
        <kwd>OpenPose</kwd>
        <kwd>BiLSTM</kwd>
        <kwd>Temporal Sequence Length</kwd>
        <kwd>Additive Temporal Attention</kwd>
        <kwd>Imbalanced Classification</kwd>
      </kwd-group>
    </article-meta>
  </front>
  <body>
    <sec id="sec1">
      <title>1. Introduction</title>
      <p>Computer vision has begun to be applied automatically to analyse sports video for performance reporting and as an aid to judging. A difficult application is to fencing, where an exchange can unfold in a few seconds: two fencers are moving at the same time and who scored depends on a temporal chain of movements to prepare attack, attack and counter rather than a single pose. A pose-based classification system is far from a substitute for the proper fencing judging systems, but we can nonetheless ask whether one can be built that simply uses body movement to discriminate a left-fencer score, a right-fencer score and an exchange with no valid score.</p>
      <p>This article considers this question using a small video-processing pipeline. First a pose estimation model, OpenPose, is used to represent each frame as the coordinates for 25 anatomical keypoints for each fencer, stripping out background and colour information while preserving a description of the motion with an organised structure [<xref ref-type="bibr" rid="B1">1</xref>]. The resulting time series is then processed by a Bidirectional Long Short-Term Memory network (BiLSTM), which reads the full sequence once forwards and once backwards, before outputting its classification of the exchange [<xref ref-type="bibr" rid="B2">2</xref>][<xref ref-type="bibr" rid="B3">3</xref>].</p>
      <p>Two controllable characteristics of this pipeline will be the subject of this exploration. The first is length of the temporal sequence: longer sequences have the potential to maintain the development and final outcome of an interactive exchange, but are more computationally expensive and potentially contain less helpful initial movement. The second is the additive temporal attention pooling, which allows for weighted contributions of each frame. Attention may provide benefit when a few critical moments dominate a longer sequence, but evidence may be evenly split across the length of an action and attention unhelpful in that case. Testing both characteristics in combination therefore allows for the discovery not only of their independent impacts, but also if the usefulness of attention is modulated by sequence length.</p>
      <p>To test these decisions in a controlled way, each of the three input lengths (25, 50 and 75 frames), are tested with and without the attention pooling layer with five matched sets of random seeds. The six configurations are compared with their validation accuracy; architecture selection is based only on validation performance, while test results are reported for the validation-selected architecture. The research question is: <bold>To what extent do temporal sequence length and additive temporal attention pooling influence the classification accuracy of a Bidirectional LSTM model in automated fencing match outcome recognition?</bold></p>
    </sec>
    <sec id="sec2">
      <title>2. Key Terms</title>
      <p>The following terms are defined before the background information and methodology explanation so terms could be used consistently throughout the essay.</p>
      <p><bold>Temporal sequence length (</bold><italic><bold>T</bold></italic><bold>):</bold>The number of continuous pose frames given to the model. In this experiment, the values are 25, 50 and 75.<bold>Keypoint:</bold>A two-dimensional coordinate locating an anatomical landmark. BODY_25 provides 25 indexed landmarks for each detected person.<bold>BiLSTM:</bold>A bidirectional recurrent neural network where a sequence of input is sent forwards and backwards through the network. The results are then merged to generate the final output.<bold>Additive temporal attention pooling:</bold>An attentive (learned) scalar scoring function over BiLSTM time steps, which is normalized and then used in a weighted sum. Not equal to Transformer self-attention.<bold>Random seed:</bold>Initialization seed used for pseudorandom number generating operations (initialization of parameters, dropout masks, batch order, etc.). Same seed for repeated experiments ensures same initial state.Training set: Data used to update model parameters during optimisation.Validation set: Held-out development data used to select checkpoints and compare configurations without consulting test labels.Test set: Source-disjoint data used to estimate generalisation; test performance was not used for architecture selection.<bold>Macro-F1:</bold>The unweighted mean of the three class-specific F1 scores, so the rare class contributes equally to the result.<bold>Balanced accuracy:</bold>The arithmetic mean of recall over the three classes.</p>
    </sec>
    <sec id="sec3">
      <title>3. Background</title>
      <sec id="sec3dot1">
        <title>3.1. Human Pose Estimation</title>
        <p>Human pose estimation is defined as the task of predicting the location of anatomical landmarks from visual inputs. OpenPose is a human pose estimation algorithm that identifies each human in real time with a method called Part Affinity Fields [<xref ref-type="bibr" rid="B1">1</xref>]. BODY_25 is one such representation of each human as 25 joints indexed by numbers with bones between them, as in <xref ref-type="fig" rid="fig1">Figure 1</xref>. The idea behind this type of representation is to find a lower dimensioned feature that preserves as much of the body’s articulation, while decreasing sensitivity to factors like clothing, lighting and background. This very abstraction, however, also strips away some potentially dispositive details, such as a weapon-tip position, a blade in contact with something or scoring-box lights, an issue which we will consider later.</p>
      </sec>
      <sec id="sec3dot2">
        <title>3.2. RNNs, LSTMs and Bidirectionality</title>
        <p>In an RNN (recurrent neural network), a sequence of inputs is passed through and a hidden state is updated at every time-step. This allows a previous frame to affect how a later frame is interpreted. Recurrence thus better suits speech, language and action sequences where input order matters than a feed-forward model [<xref ref-type="bibr" rid="B4">4</xref>]. However, gradients can be shrinked by repeated multiplication during backpropagation, making a standard RNN unreliable at learning dependencies across many time-steps.</p>
        <p>LSTM (Long Short-Term Memory) networks can solve this issue with gated memory paths. Three gates—the input, forget and output gates—govern what is allowed to pass into memory, what is to be retained, and what is made available to the next layer. This grants information a more stable trajectory through time [<xref ref-type="bibr" rid="B2">2</xref>]. The LSTM thus matches sequences of 25 - 75 frames better in theory than a standard RNN, as indicated in <xref ref-type="fig" rid="fig2">Figure 2</xref>.</p>
        <fig id="fig1">
          <label>Figure 1</label>
          <graphic xlink:href="https://html.scirp.org/file/1733661-rId15.jpeg?20261022014852" />
        </fig>
        <p><bold>Figure 1.</bold>OpenPose BODY_25 skeleton: unlabeled (left) and indexed (right).</p>
        <fig id="fig2">
          <label>Figure 2</label>
          <graphic xlink:href="https://html.scirp.org/file/1733661-rId16.jpeg?20261022014852" />
        </fig>
        <p><bold>Figure 2</bold><bold>.</bold>Standard RNN update and the gated memory structure of an LSTM.</p>
        <p>A bidirectional LSTM simply places a forward and a backward LSTM together to allow both representations to draw on the context from either side of a given time step [<xref ref-type="bibr" rid="B3">3</xref>]. This seems well suited to the task at hand, since we can fully sample both the exchange and resolution before performing a classification: preparation can give us clues about what the eventual strike will be, and the resolution can provide context for an earlier strike. The bidirectional LSTMs would not be well suited for a strictly online prediction, where subsequent frames have not yet been recorded.</p>
      </sec>
      <sec id="sec3dot3">
        <title>3.3. Additive Temporal Attention</title>
        <p>Attention was introduced as an approach to prevent an entire sequence being condensed into a single encoder state [<xref ref-type="bibr" rid="B5">5</xref>]. Within this study a compact feed-forward network is used to give each 512-dimensional BiLSTM output <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> h </mml:mi><mml:mi> t </mml:mi></mml:msub></mml:mrow></mml:math></inline-formula> an unnormalised relevance score <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> e </mml:mi><mml:mi> t </mml:mi></mml:msub></mml:mrow></mml:math></inline-formula> . These are then transformed via a softmax function to produce attention weights <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> α </mml:mi><mml:mi> t </mml:mi></mml:msub></mml:mrow></mml:math></inline-formula> which are now normalised to one. The attention weigths <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> α </mml:mi><mml:mi> t </mml:mi></mml:msub></mml:mrow></mml:math></inline-formula> are then applied as a weighted sum of outputs to create a context vector <inline-formula><mml:math><mml:mi> c </mml:mi></mml:math></inline-formula> :</p>
        <disp-formula id="FD1">
          <mml:math>
            <mml:mrow>
              <mml:msub>
                <mml:mi>e</mml:mi>
                <mml:mi>t</mml:mi>
              </mml:msub>
              <mml:mo>=</mml:mo>
              <mml:msubsup>
                <mml:mi>w</mml:mi>
                <mml:mn>2</mml:mn>
                <mml:mtext>T</mml:mtext>
              </mml:msubsup>
              <mml:mi>tanh</mml:mi>
              <mml:mrow>
                <mml:mo>(</mml:mo>
                <mml:mrow>
                  <mml:msub>
                    <mml:mi>W</mml:mi>
                    <mml:mn>1</mml:mn>
                  </mml:msub>
                  <mml:msub>
                    <mml:mi>h</mml:mi>
                    <mml:mi>t</mml:mi>
                  </mml:msub>
                  <mml:mo>+</mml:mo>
                  <mml:msub>
                    <mml:mi>b</mml:mi>
                    <mml:mn>1</mml:mn>
                  </mml:msub>
                </mml:mrow>
                <mml:mo>)</mml:mo>
              </mml:mrow>
              <mml:mo>+</mml:mo>
              <mml:msub>
                <mml:mi>b</mml:mi>
                <mml:mn>2</mml:mn>
              </mml:msub>
            </mml:mrow>
          </mml:math>
        </disp-formula>
        <disp-formula id="FD2">
          <mml:math>
            <mml:mrow>
              <mml:msub>
                <mml:mi>α</mml:mi>
                <mml:mi>t</mml:mi>
              </mml:msub>
              <mml:mo>=</mml:mo>
              <mml:mfrac>
                <mml:mrow>
                  <mml:mi>exp</mml:mi>
                  <mml:mrow>
                    <mml:mo>(</mml:mo>
                    <mml:mrow>
                      <mml:msub>
                        <mml:mi>e</mml:mi>
                        <mml:mi>t</mml:mi>
                      </mml:msub>
                    </mml:mrow>
                    <mml:mo>)</mml:mo>
                  </mml:mrow>
                </mml:mrow>
                <mml:mrow>
                  <mml:msubsup>
                    <mml:mstyle mathsize="140%" displaystyle="true">
                      <mml:mo>∑</mml:mo>
                    </mml:mstyle>
                    <mml:mrow>
                      <mml:mi>k</mml:mi>
                      <mml:mo>=</mml:mo>
                      <mml:mn>1</mml:mn>
                    </mml:mrow>
                    <mml:mi>T</mml:mi>
                  </mml:msubsup>
                  <mml:mi>exp</mml:mi>
                  <mml:mrow>
                    <mml:mo>(</mml:mo>
                    <mml:mrow>
                      <mml:msub>
                        <mml:mi>e</mml:mi>
                        <mml:mi>k</mml:mi>
                      </mml:msub>
                    </mml:mrow>
                    <mml:mo>)</mml:mo>
                  </mml:mrow>
                </mml:mrow>
              </mml:mfrac>
              <mml:mo>,</mml:mo>
              <mml:mtext>
                 
              </mml:mtext>
              <mml:mtext>
                 
              </mml:mtext>
              <mml:mi>c</mml:mi>
              <mml:mo>=</mml:mo>
              <mml:munderover>
                <mml:mstyle mathsize="140%" displaystyle="true">
                  <mml:mo>∑</mml:mo>
                </mml:mstyle>
                <mml:mrow>
                  <mml:mi>t</mml:mi>
                  <mml:mo>=</mml:mo>
                  <mml:mn>1</mml:mn>
                </mml:mrow>
                <mml:mi>T</mml:mi>
              </mml:munderover>
              <mml:mtext>
                 
              </mml:mtext>
              <mml:msub>
                <mml:mi>α</mml:mi>
                <mml:mi>t</mml:mi>
              </mml:msub>
              <mml:msub>
                <mml:mi>h</mml:mi>
                <mml:mi>t</mml:mi>
              </mml:msub>
            </mml:mrow>
          </mml:math>
        </disp-formula>
        <p>where <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> h </mml:mi><mml:mi> t </mml:mi></mml:msub></mml:mrow></mml:math></inline-formula> is the hidden state of the bidirectional LSTM from frame <inline-formula><mml:math><mml:mi> t </mml:mi></mml:math></inline-formula> , <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> e </mml:mi><mml:mi> t </mml:mi></mml:msub></mml:mrow></mml:math></inline-formula> is the unnormalised score, <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> α </mml:mi><mml:mi> t </mml:mi></mml:msub></mml:mrow></mml:math></inline-formula> is the temporal attention weight, and <inline-formula><mml:math><mml:mi> c </mml:mi></mml:math></inline-formula> is the attention-pooled representation passed to the classification head. This is an experiment in seeing if you get any recognition boost by considering the weights to be not equal for the individual frames. It is temporal attention pooling as opposed to the self-attention mechanism in Transformer because the frames do not communicate with one another through query-key pairs.</p>
      </sec>
    </sec>
    <sec id="sec4">
      <title>4. Methodology</title>
      <p>Experiment setup, where <xref ref-type="fig" rid="fig3">Figure 3</xref> is adopted: The same preprocessed videos are assigned to the source-disjoint datasets, the two independent factors determine six controllable settings, the validation results determine the architecture to use, and the main text reports test performance for the validation-selected architecture. </p>
      <fig id="fig3">
        <label>Figure 3</label>
        <graphic xlink:href="https://html.scirp.org/file/1733661-rId41.jpeg?20261022014853" />
      </fig>
      <p><bold>Figure 3</bold><bold>.</bold>Experimental design and evaluation workflow.</p>
      <sec id="sec4dot1">
        <title>4.1. Dataset and Labels</title>
        <p>The published Sport Fencing Matches dataset contains AI, OpenPose, descriptor, and source URL information for publicly available fencing videos [<xref ref-type="bibr" rid="B6">6</xref>]. A preprocessing yielded 9370 clips: as 4846 structurally valid clips, and 4524 repaired clips. Each model input was derived from an array of (75, 2, 25, 2), <italic>i</italic>.<italic>e</italic>., a sequence of frames with 2 fencers and 25 BODY_25 landmarks and their x/y coordinates. The two pose streams were ordered by horizontal position, with the fencer having the smaller x-coordinate assigned to the visual-left stream and the other to the visual-right stream. During preprocessing, we used detection confidence to determine valid joints, but this was not fed to the model inputs. A source frame and its corresponding pose representation are linked in <xref ref-type="fig" rid="fig4">Figure 4</xref>. <xref ref-type="fig" rid="fig5">Figure 5</xref> shows that the coordinate-only information retain the posture of the body of the two fencers.</p>
        <p>Label assignment was straight forward: we used 1-0-0 as class 0 (left fencer scored), 0-0-1 as class 1 (right fencer scored), and 0-1-0 as class 2 (no valid score). A prefix of perfect_ and fixed_ was stripped before concatenating identifiers to the decriptor table. There are 4337, 4501, and 532 number of class-0, -1, and -2 clips from explicit checks; thus, class 2 accounted for only 5.68%.</p>
        <fig id="fig4">
          <label>Figure 4</label>
          <graphic xlink:href="https://html.scirp.org/file/1733661-rId42.jpeg?20261022014854" />
        </fig>
        <p><bold>Figure 4</bold><bold>.</bold>Original fencing frame and corresponding BODY_25 landmarks.</p>
        <fig id="fig5">
          <label>Figure 5</label>
          <graphic xlink:href="https://html.scirp.org/file/1733661-rId43.jpeg?20261022014854" />
        </fig>
        <p><bold>Figure 5.</bold>Coordinate-only reconstruction used to verify preprocessing.</p>
      </sec>
      <sec id="sec4dot2">
        <title>4.2. Independent Variables and Experimental Conditions</title>
        <p>Two experimental factors are the length of temporal sequence (25, 50 or 75 frames) and the presence/absence of additive temporal attention pooling, for a total of six experimental conditions, as listed in <bold>Table 1</bold>. All source video clips have 75 frames. For 50 and 25 inputs, the later two-thirds and one-third of the full sequence are sliced, respectively, with the effect that each of the shorter sequences is an embedded subset of each of the longer sequences. This method allows us to isolate and test the significance of additional early context while avoiding alterations in the rest of the frame sequence. The three frame lengths are experimental and relative to the annotated exchange itself; they are not universal optima for fencing. The start and end boundaries were inherited from the pre-segmented clips in the source dataset rather than manually re-annotated in this study. A similar treatment of temporal scope as a design parameter can be found in action-recognition methods which sample video data over time [<xref ref-type="bibr" rid="B7">7</xref>].</p>
        <p><bold>Table 1.</bold> Six experimental conditions defined by the two independent variables.</p>
        <table-wrap id="tbl1">
          <label>Table 1</label>
          <table>
            <tbody>
              <tr>
                <td>
                  <bold>Configuration</bold>
                </td>
                <td>
                  <bold>Sequence length (frames)</bold>
                </td>
                <td>
                  <bold>Additive attention</bold>
                </td>
              </tr>
              <tr>
                <td>C1</td>
                <td>25</td>
                <td>No</td>
              </tr>
              <tr>
                <td>C2</td>
                <td>25</td>
                <td>Yes</td>
              </tr>
              <tr>
                <td>C3</td>
                <td>50</td>
                <td>No</td>
              </tr>
              <tr>
                <td>C4</td>
                <td>50</td>
                <td>Yes</td>
              </tr>
              <tr>
                <td>C5</td>
                <td>75</td>
                <td>No</td>
              </tr>
              <tr>
                <td>C6</td>
                <td>75</td>
                <td>Yes</td>
              </tr>
            </tbody>
          </table>
        </table-wrap>
      </sec>
      <sec id="sec4dot3">
        <title>4.3. Preprocessing and Notation</title>
        <p>The five operations below form an ordered pipeline, summarized in <xref ref-type="fig" rid="fig6">Figure 6</xref>. Each stage was applied identically in all six configurations so that preprocessing did not become an uncontrolled variable.</p>
        <fig id="fig6">
          <label>Figure 6</label>
          <graphic xlink:href="https://html.scirp.org/file/1733661-rId44.jpeg?20261022014855" />
        </fig>
        <p><bold>Figure 6</bold><bold>.</bold>Ordered preprocessing pipeline applied to every clip.</p>
        <p>Frame fixing. Fencers with clips with less than 75 frames, repeated their last good frame, rather than having a sudden ending of all zeros. A 25 × 2 zero matrix signified an invalid fencer.</p>
        <p>Validity masking. Joint validity was indicated when its confidence of being detected was above zero, and its position was not [0, 0]. Undetected joint positions remained [0, 0] following normalisation rather than being projected to any arbitrary far location.</p>
        <p>Spatial normalisation. For frame <inline-formula><mml:math><mml:mi> t </mml:mi></mml:math></inline-formula> and fencer <inline-formula><mml:math><mml:mi> f </mml:mi></mml:math></inline-formula> the centre <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> c </mml:mi><mml:mrow><mml:mi> t </mml:mi><mml:mo> , </mml:mo><mml:mi> f </mml:mi></mml:mrow></mml:msub></mml:mrow></mml:math></inline-formula> was set as the mid hip keypoint when valid, the mid point between the two shoulders otherwise and the last valid centre otherwise. The scale <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> s </mml:mi><mml:mrow><mml:mi> t </mml:mi><mml:mo> , </mml:mo><mml:mi> f </mml:mi></mml:mrow></mml:msub></mml:mrow></mml:math></inline-formula> was set as the distance between the mid-hip and the neck keypoint when at least one pixel, and then the distance between the shoulders when valid, and the last valid scale otherwise. A fencer with no valid centre or scale had its positions masked with zeros.</p>
        <disp-formula id="FD3">
          <mml:math>
            <mml:mrow>
              <mml:msub>
                <mml:mover accent="true">
                  <mml:mi>p</mml:mi>
                  <mml:mo>^</mml:mo>
                </mml:mover>
                <mml:mrow>
                  <mml:mi>t</mml:mi>
                  <mml:mo>,</mml:mo>
                  <mml:mi>f</mml:mi>
                  <mml:mo>,</mml:mo>
                  <mml:mi>j</mml:mi>
                </mml:mrow>
              </mml:msub>
              <mml:mo>=</mml:mo>
              <mml:mfrac>
                <mml:mrow>
                  <mml:msub>
                    <mml:mi>p</mml:mi>
                    <mml:mrow>
                      <mml:mi>t</mml:mi>
                      <mml:mo>,</mml:mo>
                      <mml:mi>f</mml:mi>
                      <mml:mo>,</mml:mo>
                      <mml:mi>j</mml:mi>
                    </mml:mrow>
                  </mml:msub>
                  <mml:mo>−</mml:mo>
                  <mml:msub>
                    <mml:mi>c</mml:mi>
                    <mml:mrow>
                      <mml:mi>t</mml:mi>
                      <mml:mo>,</mml:mo>
                      <mml:mi>f</mml:mi>
                    </mml:mrow>
                  </mml:msub>
                </mml:mrow>
                <mml:mrow>
                  <mml:msub>
                    <mml:mi>s</mml:mi>
                    <mml:mrow>
                      <mml:mi>t</mml:mi>
                      <mml:mo>,</mml:mo>
                      <mml:mi>f</mml:mi>
                    </mml:mrow>
                  </mml:msub>
                </mml:mrow>
              </mml:mfrac>
            </mml:mrow>
          </mml:math>
        </disp-formula>
        <p>In the above formula, <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> p </mml:mi><mml:mrow><mml:mi> t </mml:mi><mml:mo> , </mml:mo><mml:mi> f </mml:mi><mml:mo> , </mml:mo><mml:mi> j </mml:mi></mml:mrow></mml:msub></mml:mrow></mml:math></inline-formula> denotes the untransformed two-dimensional location of joint <inline-formula><mml:math><mml:mi> j </mml:mi></mml:math></inline-formula> , and <inline-formula><mml:math><mml:mrow><mml:msub><mml:mover accent="true"><mml:mi> p </mml:mi><mml:mo> ^ </mml:mo></mml:mover><mml:mrow><mml:mi> t </mml:mi><mml:mo> , </mml:mo><mml:mi> f </mml:mi><mml:mo> , </mml:mo><mml:mi> j </mml:mi></mml:mrow></mml:msub></mml:mrow></mml:math></inline-formula> its normalised location. Subtracting <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> c </mml:mi><mml:mrow><mml:mi> t </mml:mi><mml:mo> , </mml:mo><mml:mi> f </mml:mi></mml:mrow></mml:msub></mml:mrow></mml:math></inline-formula> leads to less sensitivity to image position; dividing by <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> s </mml:mi><mml:mrow><mml:mi> t </mml:mi><mml:mo> , </mml:mo><mml:mi> f </mml:mi></mml:mrow></mml:msub></mml:mrow></mml:math></inline-formula> leads to less sensitivity to apparent body scale. Joint coordinates that were not valid were not transformed.</p>
        <p>Temporal slicing. Identical 75-frame array was applied across all conditions. The model was given the last <inline-formula><mml:math><mml:mi> T </mml:mi></mml:math></inline-formula> frames, where <inline-formula><mml:math><mml:mi> T </mml:mi></mml:math></inline-formula> equals to 25, 50 or 75. The end of the annotated exchange was thus kept the same across conditions.</p>
        <p>Feature reshaping and BatchNorm. The 2 × 25 × 2 spatial locations per frame were flattened to 100 features. We then applied batch norm on the feature dimension before passing the features to the BiLSTM:</p>
        <disp-formula id="FD4">
          <mml:math>
            <mml:mrow>
              <mml:mi>B</mml:mi>
              <mml:mi>N</mml:mi>
              <mml:mrow>
                <mml:mo>(</mml:mo>
                <mml:mrow>
                  <mml:msub>
                    <mml:mi>x</mml:mi>
                    <mml:mi>k</mml:mi>
                  </mml:msub>
                </mml:mrow>
                <mml:mo>)</mml:mo>
              </mml:mrow>
              <mml:mo>=</mml:mo>
              <mml:mfrac>
                <mml:mrow>
                  <mml:msub>
                    <mml:mi>γ</mml:mi>
                    <mml:mi>k</mml:mi>
                  </mml:msub>
                  <mml:mrow>
                    <mml:mo>(</mml:mo>
                    <mml:mrow>
                      <mml:msub>
                        <mml:mi>x</mml:mi>
                        <mml:mi>k</mml:mi>
                      </mml:msub>
                      <mml:mo>−</mml:mo>
                      <mml:msub>
                        <mml:mi>μ</mml:mi>
                        <mml:mrow>
                          <mml:mi>B</mml:mi>
                          <mml:mo>,</mml:mo>
                          <mml:mi>k</mml:mi>
                        </mml:mrow>
                      </mml:msub>
                    </mml:mrow>
                    <mml:mo>)</mml:mo>
                  </mml:mrow>
                </mml:mrow>
                <mml:mrow>
                  <mml:msqrt>
                    <mml:mrow>
                      <mml:msubsup>
                        <mml:mi>σ</mml:mi>
                        <mml:mrow>
                          <mml:mi>B</mml:mi>
                          <mml:mo>,</mml:mo>
                          <mml:mi>k</mml:mi>
                        </mml:mrow>
                        <mml:mn>2</mml:mn>
                      </mml:msubsup>
                      <mml:mo>+</mml:mo>
                      <mml:mi>ε</mml:mi>
                    </mml:mrow>
                  </mml:msqrt>
                </mml:mrow>
              </mml:mfrac>
              <mml:mo>+</mml:mo>
              <mml:msub>
                <mml:mi>β</mml:mi>
                <mml:mi>k</mml:mi>
              </mml:msub>
            </mml:mrow>
          </mml:math>
        </disp-formula>
        <p>where <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> μ </mml:mi><mml:mrow><mml:mi> B </mml:mi><mml:mo> , </mml:mo><mml:mi> k </mml:mi></mml:mrow></mml:msub></mml:mrow></mml:math></inline-formula> , <inline-formula><mml:math><mml:mrow><mml:msubsup><mml:mi> σ </mml:mi><mml:mrow><mml:mi> B </mml:mi><mml:mo> , </mml:mo><mml:mi> k </mml:mi></mml:mrow><mml:mn> 2 </mml:mn></mml:msubsup></mml:mrow></mml:math></inline-formula> denotes the mean and variance over the mini-batch for the feature <inline-formula><mml:math><mml:mi> k </mml:mi></mml:math></inline-formula> , <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> γ </mml:mi><mml:mi> k </mml:mi></mml:msub></mml:mrow></mml:math></inline-formula> , <inline-formula><mml:math><mml:mrow><mml:msub><mml:mi> β </mml:mi><mml:mi> k </mml:mi></mml:msub></mml:mrow></mml:math></inline-formula> are parameters with the same dimensions as the feature and epsilon is added for avoiding division-by-zero. Batch normalisation was applied in oder to stabilise the training process by normalizing the layer’s input during training [<xref ref-type="bibr" rid="B8">8</xref>].</p>
      </sec>
      <sec id="sec4dot4">
        <title>4.4. Group-Disjoint Train, Validation and Test Sets</title>
        <p>If we were to split the individual videos at random then sections of videos from the same Youtube match may end up in the training set and the test set, leading to an optimistic bias. Hence, we have considered the sources (URLs) as groups. A 7-fold stratified group cross-validation, then assigned 5 for training, and 1 for validation and one for testing preserving roughly the proportion of each class in the group. The resulting data had a total of 6683 clips belonging to 948 source videos, 1344 clips belonging to 192 sources and 1343 clips belonging to 192 sources as illustrated in <xref ref-type="fig" rid="fig7">Figure 7</xref>, with no URL present in more than one of the set.</p>
        <fig id="fig7">
          <label>Figure 7</label>
          <graphic xlink:href="https://html.scirp.org/file/1733661-rId81.jpeg?20261022014856" />
        </fig>
        <p><bold>Figure 7.</bold>Class counts in the source-disjoint training, validation and test sets.</p>
      </sec>
      <sec id="sec4dot5">
        <title>4.5. Model Architectures and Design Justification</title>
        <p><xref ref-type="fig" rid="fig8">Figure 8</xref>, both models used batch normalization of input layer, a pair of BiLSTM layers with 256 units in each (hidden) direction and 0.4 dropout between each recurrent layer, a 512-to-128-to-3 neuron classifier with batch normalization, ReLU and 0.5 dropout. We used LSTM because motion sequences are inherently sequential, and sometimes need to depend on several previous frames (up to 75), and bidirectional because the entire clip is available when it is time to make a classification decision. For all three conditions we used 256 units in order to avoid having model width be a third independent variate.</p>
        <p>The model without attention just took the final forward and backward state and concatenated them as the 512-dimensional context. The attention model scored all 512-dimensional outputs for each time step with a 512-to-128-to-1 additive network and then took their weighted sum. Both models had 2,376,651 and 2,442,444 trainable parameters respectively; we will state the extra 65,793 as a potential difference in model complexity, though the training outcome does not support the simple possibility that one is overfitting more than the other.</p>
        <fig id="fig8">
          <label>Figure 8</label>
          <graphic xlink:href="https://html.scirp.org/file/1733661-rId82.jpeg?20261022014857" />
        </fig>
        <p><bold>Figure 8</bold><bold>.</bold>Shared BiLSTM backbone and the single controlled architectural difference.</p>
      </sec>
      <sec id="sec4dot6">
        <title>4.6. Training Controls, Loss and Checkpoint Selection</title>
        <p>During training, a batch size of 32 was used with 20 epochs, an L2 weight decay of 1 × 10<sup>−</sup><sup>4</sup>, and the Adam optimizer with a learning rate of 0.001 for all models. The Adam optimizer utilizes estimates of the first- and second-order moments to perform adaptive updates [<xref ref-type="bibr" rid="B9">9</xref>] and dropout is used to prevent co-adaptation among units [<xref ref-type="bibr" rid="B10">10</xref>]. Instead of being specifically tuned to each condition, we fixed these hyperparameters for all conditions to maintain the controlled comparison.</p>
        <p>Because one of the classes was under-represented, we mitigated the issue by applying inverse-frequency weighting of the loss using a cross-entropy loss. The class weights were [0.720, 0.694, 5.909] given a training class count of [3095, 3211, 377], which means errors in the no-valid-score class weighed more heavily in the loss.</p>
        <disp-formula id="FD5">
          <mml:math>
            <mml:mrow>
              <mml:mi>L</mml:mi>
              <mml:mo>=</mml:mo>
              <mml:mo>−</mml:mo>
              <mml:msub>
                <mml:mi>w</mml:mi>
                <mml:mi>y</mml:mi>
              </mml:msub>
              <mml:mi>log</mml:mi>
              <mml:mrow>
                <mml:mo>(</mml:mo>
                <mml:mrow>
                  <mml:msub>
                    <mml:mi>p</mml:mi>
                    <mml:mi>y</mml:mi>
                  </mml:msub>
                </mml:mrow>
                <mml:mo>)</mml:mo>
              </mml:mrow>
            </mml:mrow>
          </mml:math>
        </disp-formula>
        <p>Each run had five random-number generators for Python, NumPy, PyTorch and CUDA; (42, 52, 62, 72 and 82) that were used to match the parameters initialisation, the dropout masks and the data-loader shuffling. In order to allow reproducible operations we asked for deterministic algorithms to be used and we turned off the cuDNN benchmarking. Using the same random-number generator for different conditions should reduce the randomness caused by the deep-learning process. Repeating the conditions with the same random number seeds will account for some variability that may occur when using deep learning, even when one method seems reproducible [<xref ref-type="bibr" rid="B11">11</xref>].</p>
        <p>The controlled training settings used across all configurations are summarized in <bold>Table 2</bold>.</p>
        <p><bold>Table 2.</bold>Controlled variables and training configuration.</p>
        <table-wrap id="tbl2">
          <label>Table 2</label>
          <table>
            <tbody>
              <tr>
                <td>
                  <bold>Controlled element</bold>
                </td>
                <td>
                  <bold>Setting used in every configuration</bold>
                </td>
              </tr>
              <tr>
                <td>Dataset split</td>
                <td>Same training set/validation set/testing set</td>
              </tr>
              <tr>
                <td>Training schedule</td>
                <td>20 epochs; batch size 32; Adam; learning rate 0.001</td>
              </tr>
              <tr>
                <td>Regularization</td>
                <td>LSTM dropout 0.4; classifier dropout 0.5; L2 weight decay 0.0001</td>
              </tr>
              <tr>
                <td>Loss</td>
                <td>Inverse-frequency weighted cross-entropy; weights 0.720, 0.694, 5.909</td>
              </tr>
              <tr>
                <td>Randomness</td>
                <td>Seeds 42, 52, 62, 72 and 82 applied to Python, NumPy, PyTorch, CUDA, initialization, dropout and data shuffling</td>
              </tr>
              <tr>
                <td>Evaluation</td>
                <td>Architecture selection based only on validation accuracy; test metrics not used for model selection</td>
              </tr>
            </tbody>
          </table>
        </table-wrap>
        <p>For each run, a checkpoint corresponding to the run with maximum validation accuracy was recorded. Validation and not test data, so not an exposure to the test labels. Training and validation accuracy for each final epoch was also recorded, to observe convergence characteristics. When comparing results between configurations, reported is the mean and the sample standard deviation of five results on matching seeds, so single unusually good runs don’t dictate the outcome.</p>
      </sec>
      <sec id="sec4dot7">
        <title>4.7. Dependent Variables and Statistical Analysis</title>
        <p>Overall, we choose best validation accuracy as the primary dependent variable: the maximum validation accuracy achieved during a given set of 20 training epochs for each configuration, averaged over the five matched seeds. We retain final epoch validation accuracy as a convergence measure, and final epoch training accuracy as a diagnostic of fit. The architecture selection is determined by validation, and not test, performance. In the main analysis, test-set results are reported for the validation-selected architecture using accuracy, macro-F1, balanced accuracy and class-wise precision/recall. Macro-F1 treats all classes equally while balanced accuracy gives equal weight to each class’s recall, thus ensuring that the 5.68% minority class does not get swallowed up in overall accuracy [<xref ref-type="bibr" rid="B12">12</xref>].</p>
        <p>Since the same seeds were run for the paired conditions, we use exploratory paired t-tests comparing validation accuracy with and without attention at each sequence length as well as selected comparisons of sequence-length conditions. Given only five pairs and several comparisons, the resulting p-values are descriptive rather than a basis for model selection.</p>
      </sec>
    </sec>
    <sec id="sec5">
      <title>5. Results and Analysis</title>
      <sec id="sec5dot1">
        <title>5.1. Aggregate Performance</title>
        <p>At the top there is a tight group in <xref ref-type="fig" rid="fig9">Figure 9</xref> and <bold>Table 3</bold>. The highest mean best validation accuracy score, 58.93%, is for the 25-frame attention models and has a sample standard deviation of 0.49 percentage points. The 25-frame and 50-frame no-attention models both scored 0.18 points worse at 58.75%. The margin between first and second is thus much smaller than the difference between seeds and should not be taken as evidence that attention is in general better.</p>
        <fig id="fig9">
          <label>Figure 9</label>
          <graphic xlink:href="https://html.scirp.org/file/1733661-rId85.jpeg?20261022014859" />
        </fig>
        <p><bold>Figure 9</bold><bold>.</bold>Mean best validation accuracy; error bars show one sample standard deviation.</p>
        <p><bold>Table 3.</bold>Mean (sample standard deviation) across five seeds; values are percentages.</p>
        <table-wrap id="tbl3">
          <label>Table 3</label>
          <table>
            <tbody>
              <tr>
                <td>
                  <bold>Frames</bold>
                </td>
                <td>
                  <bold>Attention</bold>
                </td>
                <td>
                  <bold>Best validation acc. (%)</bold>
                </td>
                <td>
                  <bold>Final validation acc. (%)</bold>
                </td>
                <td>
                  <bold>Final training acc. (%)</bold>
                </td>
              </tr>
              <tr>
                <td>25</td>
                <td>No</td>
                <td>58.75 (1.39)</td>
                <td>57.87 (0.93)</td>
                <td>75.77 (0.97)</td>
              </tr>
              <tr>
                <td>25</td>
                <td>Yes</td>
                <td>58.93 (0.49)</td>
                <td>57.54 (1.15)</td>
                <td>70.66 (1.45)</td>
              </tr>
              <tr>
                <td>50</td>
                <td>No</td>
                <td>58.75 (1.42)</td>
                <td>57.10 (2.32)</td>
                <td>72.96 (4.66)</td>
              </tr>
              <tr>
                <td>50</td>
                <td>Yes</td>
                <td>56.98 (0.56)</td>
                <td>55.67 (2.35)</td>
                <td>65.08 (2.86)</td>
              </tr>
              <tr>
                <td>75</td>
                <td>No</td>
                <td>58.42 (1.28)</td>
                <td>56.76 (3.05)</td>
                <td>71.58 (3.08)</td>
              </tr>
              <tr>
                <td>75</td>
                <td>Yes</td>
                <td>55.19 (1.88)</td>
                <td>54.81 (1.96)</td>
                <td>66.14 (3.52)</td>
              </tr>
            </tbody>
          </table>
        </table-wrap>
      </sec>
      <sec id="sec5dot2">
        <title>5.2. Effect of Temporal Sequence Length</title>
        <p>In the no-attention model, the average best validation accuracy did not differ between 25 and 50 frames (58.75%), and was very slightly decreased with 75 frames (58.42%). A test for a significant difference between sequence lengths gave 50 frames - 25 frames = 0.00 points (p = 1.000), and 75 frames - 50 frames = −0.33 points (p = 0.594). Thus, the adjusted validation evidence does not provide any support for the initial hypothesis that longer sequences are systematically better.</p>
        <p>A more conservative view is that 25 frames already include sufficient late-exchange evidence for this model and this dataset. Additional early frames might provide more helpful preparation context for a few video clips, but the overall advantage is outweighed by extra variability and only loosely relevant motion information. The impact of sequence length is thus at maximum a saturating small one with the conditions tested here.</p>
      </sec>
      <sec id="sec5dot3">
        <title>5.3. Effect of Additive Temporal Attention</title>
        <p>The within-seed changes in best validation accuracy after adding attention are shown in <xref ref-type="fig" rid="fig10">Figure 10</xref>.</p>
        <fig id="fig10">
          <label>Figure 10</label>
          <graphic xlink:href="https://html.scirp.org/file/1733661-rId86.jpeg?20261022014900" />
        </fig>
        <p><bold>Figure 10.</bold>Within-seed change in best validation accuracy when attention is added.</p>
        <p>As shown in <bold>Table 4</bold>, the descriptive pattern suggests that the effect of attention may differ by sequence length. For 25 frames, attention altered the mean validation accuracy by just +0.18 points and the paired differences were not consistent (p = 0.776). At 50 frames, attention caused the accuracy to drop by 1.77 points (p = 0.035); at 75 frames, the decrease was 3.23 points (p = 0.031). These p-values are exploratory and not adjusted for multiple comparisons, but the directionality is consistent between the longer two conditions.</p>
        <p>One possible explanation for this pattern is a mismatch between the attention pooling operation and evidence structure in fencing. A valid fencing outcome is a process rather than any particular key frame: approach, balance transfer, extension, and response and resolution all can provide subtle but cumulative evidence. The no-attention BiLSTM pools its full sequence of evidence in the final forward and backward state. Attention, however, allows frames to compete for a fixed total weight, which in longer sequences may dilute and suppress collectively useful but individually subtle context.</p>
        <p><bold>Table 4.</bold>Exploratory paired comparisons of best validation accuracy across matched seeds.</p>
        <table-wrap id="tbl4">
          <label>Table 4</label>
          <table>
            <tbody>
              <tr>
                <td>
                  <bold>Comparison</bold>
                </td>
                <td>
                  <bold>Mean validation change</bold>
                </td>
                <td>
                  <bold>Paired t-test p</bold>
                </td>
              </tr>
              <tr>
                <td>25 frames: attention - no attention</td>
                <td>+0.18 pp</td>
                <td>0.776</td>
              </tr>
              <tr>
                <td>50 frames: attention - no attention</td>
                <td>−1.77 pp</td>
                <td>0.035</td>
              </tr>
              <tr>
                <td>75 frames: attention - no attention</td>
                <td>−3.23 pp</td>
                <td>0.031</td>
              </tr>
              <tr>
                <td>No attention: 50 - 25 frames</td>
                <td>0.00 pp</td>
                <td>1.000</td>
              </tr>
              <tr>
                <td>No attention: 75 - 50 frames</td>
                <td>−0.33 pp</td>
                <td>0.594</td>
              </tr>
            </tbody>
          </table>
        </table-wrap>
        <p>The training evidence does not support a simple overfitting account. Attention introduced 65,793 additional parameters, but its final training accuracy was less than the corresponding no-attention model at every length (<bold>Table 3</bold>). If a model both fit the training data more strongly and generalised less well, this would be clearer evidence of overfitting. The observed pattern is more consistent with harder optimisation, or representational underfitting resulting from the pooling rule. This is only a hypothesis, however, as the attention weights were not saved for direct inspection.</p>
      </sec>
      <sec id="sec5dot4">
        <title>5.4. Training Behaviour and Class-Level Error</title>
        <p>Over 20 epochs, the validation accuracy for all cases improved with some fluctuations, as seen in <xref ref-type="fig" rid="fig11">Figure 11</xref>. The 50-frame and 75-frame attention conditions had consistently lower and more fluctuating validation accuracy. Checkpoint selection hence was done on the validation accuracy (as opposed to the last epoch). Each condition employed the same 20-epoch training schedule; hence this comparison is fair; however, separate tuning for the different architectures could result in different scores.</p>
        <fig id="fig11">
          <label>Figure 11</label>
          <graphic xlink:href="https://html.scirp.org/file/1733661-rId87.jpeg?20261022014901" />
        </fig>
        <p><bold>Figure 1</bold><bold>1</bold><bold>.</bold>Mean validation-learning curves; shading shows one standard deviation.</p>
      </sec>
      <sec id="sec5dot5">
        <title>5.5. Answer to the Research Question</title>
        <p><bold>Answer to the research question.</bold>Effect of temporal sequence length on validation accuracy (small and non-monotonic): no-attention conditions nearly tied between 25 and 75 frames. For additive temporal attention: the observed effect varied by sequence length, being negligible at 25 frames and negative at 50 and 75 frames. Under the pre-registered selection rule, the attention architecture with 25 frames was the top by mean best validation accuracy (58.93%), although its 0.18 point advantage over two of the no-attention alternatives was neither statistically nor practically significant.</p>
        <p>The independent test performance of the validation-selected 25-frame attention model is summarized in <bold>Table 5</bold>.</p>
        <p><bold>Table 5.</bold>Independent test performance of the validation-selected 25-frame attention model.</p>
        <table-wrap id="tbl5">
          <label>Table 5</label>
          <table>
            <tbody>
              <tr>
                <td>
                  <bold>Test metric</bold>
                </td>
                <td>
                  <bold>Mean (sample SD), %</bold>
                </td>
              </tr>
              <tr>
                <td>Accuracy</td>
                <td>57.35 (1.28)</td>
              </tr>
              <tr>
                <td>Macro-F1</td>
                <td>48.29 (1.99)</td>
              </tr>
              <tr>
                <td>Balanced accuracy</td>
                <td>51.14 (3.20)</td>
              </tr>
              <tr>
                <td>Class 2 precision</td>
                <td>17.89 (2.41)</td>
              </tr>
              <tr>
                <td>Class 2 recall</td>
                <td>36.15 (8.48)</td>
              </tr>
            </tbody>
          </table>
        </table-wrap>
        <p>According to the independent test, the choice of architecture has an accuracy better than the baseline of 48.04%, the majority class, but, as can be seen in <xref ref-type="fig" rid="fig12">Figure 12</xref>, it is not particularly reliable for the minority class of no valid score. For the five selected checkpoints, the average test accuracy was 57.35%, whereas the precision and recall for class 2 were 17.89% and 36.15%, respectively. This model can be considered as an experimental classifier, not ready for referee use.</p>
        <fig id="fig12">
          <label>Figure 12</label>
          <graphic xlink:href="https://html.scirp.org/file/1733661-rId88.jpeg?20261022014901" />
        </fig>
        <p><bold>Figure 12.</bold>Aggregated test confusion for the validation-selected 25-frame attention model.</p>
      </sec>
    </sec>
    <sec id="sec6">
      <title>6. Limitations and Future Work</title>
      <sec id="sec6dot1">
        <title>6.1. Pose-Only Evidence and Potential Label Leakage</title>
        <p>Pose and body-track positions are available to the model; the motion of the weapon’s tip, the hit of the blade on the target, the area and scoring-box lights are not. Thus, a visually interpretable motion might be not straightforwardly classifiable even after the official equipment emits an unambiguous verdict. The pose could be used together with the weapon and the target evidence identified independently for future applications. The scoring box lights should be eliminated from the classification window, or used as an external reference, since the visible verdict light would introduce a leakage in the labels, instead of testing the understanding of motion itself. More precise exchange-boundary annotation should therefore exclude frames that contain explicit post-outcome visual cues.</p>
      </sec>
      <sec id="sec6dot2">
        <title>6.2. Minority-Class Scarcity and Augmentation</title>
        <p>Among the total of 9370 video clips, only 532 were classified as no-valid-score instances. Inverse-frequency weighting successfully amplified this class’s contribution to the loss, but failed to generate the null exchanges variety, double touches and off-target contacts that it lacked; low class-2 precision is the resulting effect. In the future, the collection should give special attention to this class, while subclasses of meaningful value are preserved. The application of conservative pose augmentation may introduce small noise to coordinates, temporal warping, translation or scale, so long as the labels for left and right remain intact and realistic fencing biomechanics are maintained.</p>
      </sec>
      <sec id="sec6dot3">
        <title>6.3. Generalisability of the Source-Grouped Split</title>
        <p>Although grouping images by their source URL ensures that videos from the same uploaded game do not end up in both test and development sets, this does not necessarily ensure independence by competition, by camera angle or by venue, due to missing information, or by athlete. The frozen model may therefore improve by exploiting similarities across related source domains. A better sequel would be to test the frozen model on a different recorded competition not included in the source dataset, recorded with different camera and athlete conditions.</p>
      </sec>
      <sec id="sec6dot4">
        <title>6.4. Remaining Experimental Uncertainty</title>
        <p>Seeds five seeds are more dependable than one seed, although they are fairly modest when it comes to statistical significance testing, and they use one spilt of the data set. We also froze hyperparameters to maintain the comparison on the IVs, so attention might not have seen its ideal learning rate, regularisation, or training rounds for each task on its own. The next step should involve pre-registering one main measure for each experiment, running more seeds and source-group partitions, hyperparameter tuning of the architecture within nested validation, and holding out an external validation set for the final comparison. Although test metrics were computed for all configurations in the submitted implementation, architecture selection and all configuration comparisons in this study were based only on validation performance.</p>
      </sec>
      <sec id="sec6dot5">
        <title>6.5. Confidence and Missing-Keypoint Representation</title>
        <p>The validity masks were generated by the detection confidence and then dropped. This means that a zero coordinate might indicate that a joint is missing, or that it’s a legitimate normalised co-ordinate at the origin, so that missing limbs can look like regular pose change when occluded or when the weapons move quickly. An additional input to the next model would be the confidence or explicit visibility mask on top of the coordinates. A graph based network might be able to preserve the BODY_25 connectivity rather than flattening both fencers to 100 independent features.</p>
      </sec>
    </sec>
    <sec id="sec7">
      <title>7. Conclusions</title>
      <p>In this study, temporal sequence length and additive temporal attention were isolated in a controlled BiLSTM fencing classification experiments. All of the temporal sequence lengths were subarrays of the same 75-frame sequences; source video files were disjoint for training, validation and testing; six setups were repeated up to five times with matched seeds; and validation, not test, performance was used for comparing architectures.</p>
      <p>For the validation set, not all longer lengths improved performance. In the attention-free setups, there was no more than a 0.33 percentage point difference for all three lengths. Attention was unremarkable for 25-frame input length, but decreased the mean of the best validation performance by 1.77 points at 50 frames and 3.23 points at 75 frames. These findings are consistent with the hypothesis that fencing movement classification relies on the accumulation of temporal information, and competitive attention-based pooling of frame-level evidence is detrimental in longer sequences.</p>
      <p>According to the strict validation rule, 25-frame attention architecture should be used, with a mean best validated performance of 58.93%. The improvement of 0.18 points is too small to establish a good practical relationship. According to the results on an independent test set, the suggested 25-frame attention architecture obtained a 57.35% of mean (sample SD 1.28 points) test accuracy, 48.29% macro-F1, and 51.14% balanced accuracy. The findings are sufficient to answer the research question, but insufficient for practical deployment, as minority-class recognition is too poor, and any further advances will depend on richer evidence from the fencing weapons, more balanced training set and confidence-aware pose-related features.</p>
    </sec>
    <sec id="sec8">
      <title>Author Contributions</title>
      <p>Qiuhong Wu carried out all stages of the research and wrote the manuscript. Yang Zhang reviewed the final manuscript.</p>
    </sec>
    <sec id="sec9">
      <title>Appendix A. Reproducibility Record</title>
      <p>This appendix is excluded from the main word count. It contains the complete submitted Python code and a 30-run summary. The experiment folder also contains aggregate_results.csv, per-epoch histories, classification reports, confusion matrices, selected checkpoints and the source-group split manifest.</p>
      <sec id="sec9dot1">
        <title>Appendix A1. run_ee_experiments.py</title>
        <p>import copy</p>
        <p>import json</p>
        <p>import os</p>
        <p>import random</p>
        <p>from pathlib import Path</p>
        <p>import numpy as np</p>
        <p>import pandas as pd</p>
        <p>import torch</p>
        <p>import torch.nn as nn</p>
        <p>import torch.optim as optim</p>
        <p>from sklearn.metrics import (</p>
        <p> accuracy_score,</p>
        <p> balanced_accuracy_score,</p>
        <p> classification_report,</p>
        <p> confusion_matrix,</p>
        <p> f1_score,</p>
        <p>)</p>
        <p>from sklearn.model_selection import (</p>
        <p> StratifiedGroupKFold,</p>
        <p>)</p>
        <p>from torch.utils.data import DataLoader, Subset</p>
        <p>from dataset import FencingDataset</p>
        <p>from modelv1 import FencingLSTM as AttentionModel</p>
        <p>from modelv12 import FencingLSTM as NoAttentionModel</p>
        <p>BASE_DIR = Path(</p>
        <p> r"F:\PycharmProjects\Fencing-Testing"</p>
        <p>)</p>
        <p>NPY_DIR = (</p>
        <p> BASE_DIR</p>
        <p> / "fencing_features_npy_experiment_v2"</p>
        <p>)</p>
        <p>LABEL_CSV = (</p>
        <p> BASE_DIR</p>
        <p> / "dataset"</p>
        <p> / "ClipDescriptorKaggle.csv"</p>
        <p>)</p>
        <p>OUTPUT_DIR = (</p>
        <p> BASE_DIR</p>
        <p> / "ee_experiment_results_v2"</p>
        <p>)</p>
        <p>SEQUENCE_LENGTHS = [25, 50, 75]</p>
        <p>ATTENTION_OPTIONS = [False, True]</p>
        <p>SEEDS = [42, 52, 62, 72, 82]</p>
        <p>EPOCHS = 20</p>
        <p>BATCH_SIZE = 32</p>
        <p>LEARNING_RATE = 0.001</p>
        <p>WEIGHT_DECAY = 1e-4</p>
        <p>os.environ.setdefault(</p>
        <p> "CUBLAS_WORKSPACE_CONFIG",</p>
        <p> ":4096:8",</p>
        <p>)</p>
        <p>def set_seed(seed):</p>
        <p> os.environ["PYTHONHASHSEED"] = str(seed)</p>
        <p> random.seed(seed)</p>
        <p> np.random.seed(seed)</p>
        <p> torch.manual_seed(seed)</p>
        <p> torch.cuda.manual_seed_all(seed)</p>
        <p> torch.backends.cudnn.deterministic = True</p>
        <p> torch.backends.cudnn.benchmark = False</p>
        <p> torch.use_deterministic_algorithms(</p>
        <p> True,</p>
        <p> warn_only=True,</p>
        <p> )</p>
        <p>def create_split(dataset):</p>
        <p> splitter = StratifiedGroupKFold(</p>
        <p> n_splits=7,</p>
        <p> shuffle=True,</p>
        <p> random_state=42,</p>
        <p> )</p>
        <p> fold_ids = np.full(</p>
        <p> len(dataset),</p>
        <p> -1,</p>
        <p> dtype=np.int64,</p>
        <p> )</p>
        <p> for fold, (_, held_out_indices) in enumerate(</p>
        <p> splitter.split(</p>
        <p> X=np.zeros(len(dataset)),</p>
        <p> y=dataset.labels,</p>
        <p> groups=dataset.groups,</p>
        <p> )</p>
        <p> ):</p>
        <p> fold_ids[held_out_indices] = fold</p>
        <p> test_indices = np.flatnonzero(</p>
        <p> fold_ids == 0</p>
        <p> )</p>
        <p> validation_indices = np.flatnonzero(</p>
        <p> fold_ids == 1</p>
        <p> )</p>
        <p> training_indices = np.flatnonzero(</p>
        <p> fold_ids &gt;= 2</p>
        <p> )</p>
        <p> train_groups = set(</p>
        <p> dataset.groups[training_indices]</p>
        <p> )</p>
        <p> validation_groups = set(</p>
        <p> dataset.groups[validation_indices]</p>
        <p> )</p>
        <p> test_groups = set(</p>
        <p> dataset.groups[test_indices]</p>
        <p> )</p>
        <p> assert train_groups.isdisjoint(</p>
        <p> validation_groups</p>
        <p> )</p>
        <p> assert train_groups.isdisjoint(</p>
        <p> test_groups</p>
        <p> )</p>
        <p> assert validation_groups.isdisjoint(</p>
        <p> test_groups</p>
        <p> )</p>
        <p> split_names = np.full(</p>
        <p> len(dataset),</p>
        <p> "",</p>
        <p> dtype=object,</p>
        <p> )</p>
        <p> split_names[training_indices] = "train"</p>
        <p> split_names[validation_indices] = "validation"</p>
        <p> split_names[test_indices] = "test"</p>
        <p> manifest = pd.DataFrame(</p>
        <p> {</p>
        <p> "filename": dataset.file_names,</p>
        <p> "clip_id": dataset.clip_ids,</p>
        <p> "label": dataset.labels,</p>
        <p> "source_url": dataset.groups,</p>
        <p> "quality": dataset.qualities,</p>
        <p> "split": split_names,</p>
        <p> }</p>
        <p> )</p>
        <p> manifest.to_csv(</p>
        <p> OUTPUT_DIR / "split_manifest.csv",</p>
        <p> index=False,</p>
        <p> )</p>
        <p> for split_name, indices in [</p>
        <p> ("train", training_indices),</p>
        <p> ("validation", validation_indices),</p>
        <p> ("test", test_indices),</p>
        <p> ]:</p>
        <p> counts = np.bincount(</p>
        <p> dataset.labels[indices],</p>
        <p> minlength=3,</p>
        <p> )</p>
        <p> print(</p>
        <p> split_name,</p>
        <p> "samples:",</p>
        <p> len(indices),</p>
        <p> "class counts:",</p>
        <p> counts.tolist(),</p>
        <p> )</p>
        <p> return (</p>
        <p> training_indices,</p>
        <p> validation_indices,</p>
        <p> test_indices,</p>
        <p> )</p>
        <p>def make_loaders(</p>
        <p> dataset,</p>
        <p> training_indices,</p>
        <p> validation_indices,</p>
        <p> test_indices,</p>
        <p> seed,</p>
        <p>):</p>
        <p> generator = torch.Generator()</p>
        <p> generator.manual_seed(seed)</p>
        <p> training_loader = DataLoader(</p>
        <p> Subset(dataset, training_indices),</p>
        <p> batch_size=BATCH_SIZE,</p>
        <p> shuffle=True,</p>
        <p> generator=generator,</p>
        <p> num_workers=0,</p>
        <p> drop_last=True,</p>
        <p> )</p>
        <p> validation_loader = DataLoader(</p>
        <p> Subset(dataset, validation_indices),</p>
        <p> batch_size=BATCH_SIZE,</p>
        <p> shuffle=False,</p>
        <p> num_workers=0,</p>
        <p> )</p>
        <p> test_loader = DataLoader(</p>
        <p> Subset(dataset, test_indices),</p>
        <p> batch_size=BATCH_SIZE,</p>
        <p> shuffle=False,</p>
        <p> num_workers=0,</p>
        <p> )</p>
        <p> return (</p>
        <p> training_loader,</p>
        <p> validation_loader,</p>
        <p> test_loader,</p>
        <p> )</p>
        <p>def evaluate(</p>
        <p> model,</p>
        <p> loader,</p>
        <p> criterion,</p>
        <p> device,</p>
        <p>):</p>
        <p> model.eval()</p>
        <p> total_loss = 0.0</p>
        <p> total_samples = 0</p>
        <p> all_labels = []</p>
        <p> all_predictions = []</p>
        <p> with torch.no_grad():</p>
        <p> for features, labels in loader:</p>
        <p> features = features.to(device)</p>
        <p> labels = labels.to(device)</p>
        <p> logits = model(features)</p>
        <p> loss = criterion(logits, labels)</p>
        <p> batch_size = labels.size(0)</p>
        <p> total_loss += (</p>
        <p> loss.item() * batch_size</p>
        <p> )</p>
        <p> total_samples += batch_size</p>
        <p> predictions = logits.argmax(dim=1)</p>
        <p> all_labels.extend(</p>
        <p> labels.cpu().numpy().tolist()</p>
        <p> )</p>
        <p> all_predictions.extend(</p>
        <p> predictions.cpu().numpy().tolist()</p>
        <p> )</p>
        <p> accuracy = accuracy_score(</p>
        <p> all_labels,</p>
        <p> all_predictions,</p>
        <p> )</p>
        <p> macro_f1 = f1_score(</p>
        <p> all_labels,</p>
        <p> all_predictions,</p>
        <p> average="macro",</p>
        <p> zero_division=0,</p>
        <p> )</p>
        <p> balanced_accuracy = (</p>
        <p> balanced_accuracy_score(</p>
        <p> all_labels,</p>
        <p> all_predictions,</p>
        <p> )</p>
        <p> )</p>
        <p> report = classification_report(</p>
        <p> all_labels,</p>
        <p> all_predictions,</p>
        <p> labels=[0, 1, 2],</p>
        <p> output_dict=True,</p>
        <p> zero_division=0,</p>
        <p> )</p>
        <p> matrix = confusion_matrix(</p>
        <p> all_labels,</p>
        <p> all_predictions,</p>
        <p> labels=[0, 1, 2],</p>
        <p> )</p>
        <p> return {</p>
        <p> "loss": total_loss / total_samples,</p>
        <p> "accuracy": accuracy,</p>
        <p> "macro_f1": macro_f1,</p>
        <p> "balanced_accuracy": (</p>
        <p> balanced_accuracy</p>
        <p> ),</p>
        <p> "classification_report": report,</p>
        <p> "confusion_matrix": matrix.tolist(),</p>
        <p> }</p>
        <p>def train_one_run(</p>
        <p> sequence_length,</p>
        <p> use_attention,</p>
        <p> seed,</p>
        <p> training_indices,</p>
        <p> validation_indices,</p>
        <p> test_indices,</p>
        <p> device,</p>
        <p>):</p>
        <p> set_seed(seed)</p>
        <p> dataset = FencingDataset(</p>
        <p> npy_dir=str(NPY_DIR),</p>
        <p> label_csv=str(LABEL_CSV),</p>
        <p> sequence_length=sequence_length,</p>
        <p> )</p>
        <p> (</p>
        <p> training_loader,</p>
        <p> validation_loader,</p>
        <p> test_loader,</p>
        <p> ) = make_loaders(</p>
        <p> dataset,</p>
        <p> training_indices,</p>
        <p> validation_indices,</p>
        <p> test_indices,</p>
        <p> seed,</p>
        <p> )</p>
        <p> if use_attention:</p>
        <p> model = AttentionModel().to(device)</p>
        <p> model_type = "attention"</p>
        <p> else:</p>
        <p> model = NoAttentionModel().to(device)</p>
        <p> model_type = "no_attention"</p>
        <p> parameter_count = sum(</p>
        <p> parameter.numel()</p>
        <p> for parameter in model.parameters()</p>
        <p> )</p>
        <p> training_labels = (</p>
        <p> dataset.labels[training_indices]</p>
        <p> )</p>
        <p> class_counts = np.bincount(</p>
        <p> training_labels,</p>
        <p> minlength=3,</p>
        <p> )</p>
        <p> class_weights = (</p>
        <p> len(training_labels)</p>
        <p> / (3.0 * class_counts)</p>
        <p> )</p>
        <p> class_weights_tensor = torch.tensor(</p>
        <p> class_weights,</p>
        <p> dtype=torch.float32,</p>
        <p> device=device,</p>
        <p> )</p>
        <p> criterion = nn.CrossEntropyLoss(</p>
        <p> weight=class_weights_tensor</p>
        <p> )</p>
        <p> optimizer = optim.Adam(</p>
        <p> model.parameters(),</p>
        <p> lr=LEARNING_RATE,</p>
        <p> weight_decay=WEIGHT_DECAY,</p>
        <p> )</p>
        <p> best_validation_accuracy = -1.0</p>
        <p> best_epoch = -1</p>
        <p> best_state = None</p>
        <p> history = []</p>
        <p> first_training_accuracy = None</p>
        <p> first_validation_accuracy = None</p>
        <p> last_training_accuracy = None</p>
        <p> last_validation_accuracy = None</p>
        <p> for epoch in range(1, EPOCHS + 1):</p>
        <p> model.train()</p>
        <p> training_loss_sum = 0.0</p>
        <p> training_correct = 0</p>
        <p> training_samples = 0</p>
        <p> for features, labels in training_loader:</p>
        <p> features = features.to(device)</p>
        <p> labels = labels.to(device)</p>
        <p> optimizer.zero_grad()</p>
        <p> logits = model(features)</p>
        <p> loss = criterion(logits, labels)</p>
        <p> loss.backward()</p>
        <p> optimizer.step()</p>
        <p> batch_size = labels.size(0)</p>
        <p> training_loss_sum += (</p>
        <p> loss.item() * batch_size</p>
        <p> )</p>
        <p> training_samples += batch_size</p>
        <p> predictions = logits.argmax(dim=1)</p>
        <p> training_correct += (</p>
        <p> predictions == labels</p>
        <p> ).sum().item()</p>
        <p> training_loss = (</p>
        <p> training_loss_sum</p>
        <p> / training_samples</p>
        <p> )</p>
        <p> training_accuracy = (</p>
        <p> training_correct</p>
        <p> / training_samples</p>
        <p> )</p>
        <p> validation_metrics = evaluate(</p>
        <p> model,</p>
        <p> validation_loader,</p>
        <p> criterion,</p>
        <p> device,</p>
        <p> )</p>
        <p> if epoch == 1:</p>
        <p> first_training_accuracy = training_accuracy</p>
        <p> first_validation_accuracy = (</p>
        <p> validation_metrics["accuracy"]</p>
        <p> )</p>
        <p> last_training_accuracy = (</p>
        <p> training_accuracy</p>
        <p> )</p>
        <p> last_validation_accuracy = (</p>
        <p> validation_metrics["accuracy"]</p>
        <p> )</p>
        <p> history.append(</p>
        <p> {</p>
        <p> "seed": seed,</p>
        <p> "sequence_length": (</p>
        <p> sequence_length</p>
        <p> ),</p>
        <p> "attention": use_attention,</p>
        <p> "epoch": epoch,</p>
        <p> "training_loss": training_loss,</p>
        <p> "training_accuracy": (</p>
        <p> training_accuracy</p>
        <p> ),</p>
        <p> "validation_loss": (</p>
        <p> validation_metrics["loss"]</p>
        <p> ),</p>
        <p> "validation_accuracy": (</p>
        <p> validation_metrics[</p>
        <p> "accuracy"</p>
        <p> ]</p>
        <p> ),</p>
        <p> "validation_macro_f1": (</p>
        <p> validation_metrics[</p>
        <p> "macro_f1"</p>
        <p> ]</p>
        <p> ),</p>
        <p> }</p>
        <p> )</p>
        <p> if (</p>
        <p> validation_metrics["accuracy"]</p>
        <p> &gt; best_validation_accuracy</p>
        <p> ):</p>
        <p> best_validation_accuracy = (</p>
        <p> validation_metrics["accuracy"]</p>
        <p> )</p>
        <p> best_epoch = epoch</p>
        <p> best_state = copy.deepcopy(</p>
        <p> model.state_dict()</p>
        <p> )</p>
        <p> print(</p>
        <p> f"seed={seed} "</p>
        <p> f"frames={sequence_length} "</p>
        <p> f"attention={use_attention} "</p>
        <p> f"epoch={epoch:02d} "</p>
        <p> f"train={training_accuracy:.4f} "</p>
        <p> f"val={validation_metrics['accuracy']:.4f} "</p>
        <p> f"val_macro_f1="</p>
        <p> f"{validation_metrics['macro_f1']:.4f}"</p>
        <p> )</p>
        <p> model.load_state_dict(best_state)</p>
        <p> # The test set is evaluated only here,</p>
        <p> # after checkpoint selection is complete.</p>
        <p> test_metrics = evaluate(</p>
        <p> model,</p>
        <p> test_loader,</p>
        <p> criterion,</p>
        <p> device,</p>
        <p> )</p>
        <p> run_name = (</p>
        <p> f"frames_{sequence_length}_"</p>
        <p> f"{model_type}_seed_{seed}"</p>
        <p> )</p>
        <p> history_path = (</p>
        <p> OUTPUT_DIR</p>
        <p> / "histories"</p>
        <p> / f"{run_name}.csv"</p>
        <p> )</p>
        <p> pd.DataFrame(history).to_csv(</p>
        <p> history_path,</p>
        <p> index=False,</p>
        <p> )</p>
        <p> checkpoint_path = (</p>
        <p> OUTPUT_DIR</p>
        <p> / "checkpoints"</p>
        <p> / f"{run_name}.pt"</p>
        <p> )</p>
        <p> torch.save(</p>
        <p> {</p>
        <p> "model_state_dict": best_state,</p>
        <p> "sequence_length": sequence_length,</p>
        <p> "attention": use_attention,</p>
        <p> "seed": seed,</p>
        <p> "best_epoch": best_epoch,</p>
        <p> "parameter_count": parameter_count,</p>
        <p> "class_weights": (</p>
        <p> class_weights.tolist()</p>
        <p> ),</p>
        <p> },</p>
        <p> checkpoint_path,</p>
        <p> )</p>
        <p> details_path = (</p>
        <p> OUTPUT_DIR</p>
        <p> / "reports"</p>
        <p> / f"{run_name}.json"</p>
        <p> )</p>
        <p> with open(</p>
        <p> details_path,</p>
        <p> "w",</p>
        <p> encoding="utf-8",</p>
        <p> ) as file:</p>
        <p> json.dump(</p>
        <p> {</p>
        <p> "classification_report": (</p>
        <p> test_metrics[</p>
        <p> "classification_report"</p>
        <p> ]</p>
        <p> ),</p>
        <p> "confusion_matrix": (</p>
        <p> test_metrics[</p>
        <p> "confusion_matrix"</p>
        <p> ]</p>
        <p> ),</p>
        <p> },</p>
        <p> file,</p>
        <p> indent=2,</p>
        <p> )</p>
        <p> return {</p>
        <p> "seed": seed,</p>
        <p> "sequence_length": sequence_length,</p>
        <p> "attention": use_attention,</p>
        <p> "parameter_count": parameter_count,</p>
        <p> "class_weight_0": class_weights[0],</p>
        <p> "class_weight_1": class_weights[1],</p>
        <p> "class_weight_2": class_weights[2],</p>
        <p> "best_epoch": best_epoch,</p>
        <p> "best_validation_accuracy": (</p>
        <p> best_validation_accuracy</p>
        <p> ),</p>
        <p> "epoch_1_training_accuracy": (</p>
        <p> first_training_accuracy</p>
        <p> ),</p>
        <p> "epoch_1_validation_accuracy": (</p>
        <p> first_validation_accuracy</p>
        <p> ),</p>
        <p> "epoch_20_training_accuracy": (</p>
        <p> last_training_accuracy</p>
        <p> ),</p>
        <p> "epoch_20_validation_accuracy": (</p>
        <p> last_validation_accuracy</p>
        <p> ),</p>
        <p> "test_accuracy": (</p>
        <p> test_metrics["accuracy"]</p>
        <p> ),</p>
        <p> "test_macro_f1": (</p>
        <p> test_metrics["macro_f1"]</p>
        <p> ),</p>
        <p> "test_balanced_accuracy": (</p>
        <p> test_metrics[</p>
        <p> "balanced_accuracy"</p>
        <p> ]</p>
        <p> ),</p>
        <p> "test_class_0_precision": (</p>
        <p> test_metrics[</p>
        <p> "classification_report"</p>
        <p> ]["0"]["precision"]</p>
        <p> ),</p>
        <p> "test_class_0_recall": (</p>
        <p> test_metrics[</p>
        <p> "classification_report"</p>
        <p> ]["0"]["recall"]</p>
        <p> ),</p>
        <p> "test_class_1_precision": (</p>
        <p> test_metrics[</p>
        <p> "classification_report"</p>
        <p> ]["1"]["precision"]</p>
        <p> ),</p>
        <p> "test_class_1_recall": (</p>
        <p> test_metrics[</p>
        <p> "classification_report"</p>
        <p> ]["1"]["recall"]</p>
        <p> ),</p>
        <p> "test_class_2_precision": (</p>
        <p> test_metrics[</p>
        <p> "classification_report"</p>
        <p> ]["2"]["precision"]</p>
        <p> ),</p>
        <p> "test_class_2_recall": (</p>
        <p> test_metrics[</p>
        <p> "classification_report"</p>
        <p> ]["2"]["recall"]</p>
        <p> ),</p>
        <p> }</p>
        <p>def main():</p>
        <p> OUTPUT_DIR.mkdir(</p>
        <p> parents=True,</p>
        <p> exist_ok=True,</p>
        <p> )</p>
        <p> (OUTPUT_DIR / "histories").mkdir(</p>
        <p> exist_ok=True</p>
        <p> )</p>
        <p> (OUTPUT_DIR / "checkpoints").mkdir(</p>
        <p> exist_ok=True</p>
        <p> )</p>
        <p> (OUTPUT_DIR / "reports").mkdir(</p>
        <p> exist_ok=True</p>
        <p> )</p>
        <p> device = torch.device(</p>
        <p> "cuda"</p>
        <p> if torch.cuda.is_available()</p>
        <p> else "cpu"</p>
        <p> )</p>
        <p> print("Device:", device)</p>
        <p> reference_dataset = FencingDataset(</p>
        <p> npy_dir=str(NPY_DIR),</p>
        <p> label_csv=str(LABEL_CSV),</p>
        <p> sequence_length=75,</p>
        <p> )</p>
        <p> actual_counts = np.bincount(</p>
        <p> reference_dataset.labels,</p>
        <p> minlength=3,</p>
        <p> )</p>
        <p> expected_counts = np.asarray(</p>
        <p> [4337, 4501, 532]</p>
        <p> )</p>
        <p> if not np.array_equal(</p>
        <p> actual_counts,</p>
        <p> expected_counts,</p>
        <p> ):</p>
        <p> raise RuntimeError(</p>
        <p> f"Label counts are wrong: "</p>
        <p> f"{actual_counts.tolist()}; "</p>
        <p> f"expected "</p>
        <p> f"{expected_counts.tolist()}"</p>
        <p> )</p>
        <p> print(</p>
        <p> "Verified label counts:",</p>
        <p> actual_counts.tolist(),</p>
        <p> )</p>
        <p> (</p>
        <p> training_indices,</p>
        <p> validation_indices,</p>
        <p> test_indices,</p>
        <p> ) = create_split(reference_dataset)</p>
        <p> all_results = []</p>
        <p> for seed in SEEDS:</p>
        <p> for sequence_length in SEQUENCE_LENGTHS:</p>
        <p> for use_attention in (</p>
        <p> ATTENTION_OPTIONS</p>
        <p> ):</p>
        <p> result = train_one_run(</p>
        <p> sequence_length=(</p>
        <p> sequence_length</p>
        <p> ),</p>
        <p> use_attention=(</p>
        <p> use_attention</p>
        <p> ),</p>
        <p> seed=seed,</p>
        <p> training_indices=(</p>
        <p> training_indices</p>
        <p> ),</p>
        <p> validation_indices=(</p>
        <p> validation_indices</p>
        <p> ),</p>
        <p> test_indices=test_indices,</p>
        <p> device=device,</p>
        <p> )</p>
        <p> all_results.append(result)</p>
        <p> pd.DataFrame(</p>
        <p> all_results</p>
        <p> ).to_csv(</p>
        <p> OUTPUT_DIR</p>
        <p> / "run_results.csv",</p>
        <p> index=False,</p>
        <p> )</p>
        <p> results_df = pd.DataFrame(all_results)</p>
        <p> metric_columns = [</p>
        <p> "best_validation_accuracy",</p>
        <p> "epoch_1_training_accuracy",</p>
        <p> "epoch_1_validation_accuracy",</p>
        <p> "epoch_20_training_accuracy",</p>
        <p> "epoch_20_validation_accuracy",</p>
        <p> "test_accuracy",</p>
        <p> "test_macro_f1",</p>
        <p> "test_balanced_accuracy",</p>
        <p> "test_class_0_precision",</p>
        <p> "test_class_0_recall",</p>
        <p> "test_class_1_precision",</p>
        <p> "test_class_1_recall",</p>
        <p> "test_class_2_precision",</p>
        <p> "test_class_2_recall",</p>
        <p> ]</p>
        <p> aggregate = (</p>
        <p> results_df</p>
        <p> .groupby(</p>
        <p> [</p>
        <p> "sequence_length",</p>
        <p> "attention",</p>
        <p> ]</p>
        <p> )[metric_columns]</p>
        <p> .agg(["mean", "std"])</p>
        <p> .reset_index()</p>
        <p> )</p>
        <p> aggregate.columns = [</p>
        <p> "_".join(</p>
        <p> str(part)</p>
        <p> for part in column</p>
        <p> if str(part)</p>
        <p> )</p>
        <p> if isinstance(column, tuple)</p>
        <p> else column</p>
        <p> for column in aggregate.columns</p>
        <p> ]</p>
        <p> aggregate.to_csv(</p>
        <p> OUTPUT_DIR</p>
        <p> / "aggregate_results.csv",</p>
        <p> index=False,</p>
        <p> )</p>
        <p> print("\nCompleted all experiments.")</p>
        <p> print(aggregate.to_string(index=False))</p>
        <p>if __name__ == "__main__":</p>
        <p> main()</p>
      </sec>
      <sec id="sec9dot2">
        <title>Appendix A2. data_proprecessv1.py</title>
        <p>import os</p>
        <p>import json</p>
        <p>import numpy as np</p>
        <p>from tqdm import tqdm</p>
        <p>class FencingCategorizedProcessor:</p>
        <p> def __init__(self, input_dir, output_dir):</p>
        <p> self.input_dir = input_dir</p>
        <p> self.output_dir = output_dir</p>
        <p> if not os.path.exists(output_dir):</p>
        <p> os.makedirs(output_dir)</p>
        <p> self.stats = {"perfect": 0, "fixed": 0, "failed": 0}</p>
        <p> def process_all_files(self):</p>
        <p> files = sorted(</p>
        <p> f</p>
        <p> for f in os.listdir(self.input_dir)</p>
        <p> if f.endswith(".json")</p>
        <p> )</p>
        <p> print(f"Start processing {len(files)} document...")</p>
        <p> for filename in tqdm(files, desc="Process"):</p>
        <p> file_path = os.path.join(self.input_dir, filename)</p>
        <p> try:</p>
        <p> with open(file_path, 'r', encoding='utf-8') as f:</p>
        <p> clip_data = json.load(f)</p>
        <p> is_perfect = self.check_if_perfect(clip_data)</p>
        <p> processed_feature = self.normalize_clip(clip_data)</p>
        <p> if processed_feature is not None:</p>
        <p> if is_perfect:</p>
        <p> self.stats["perfect"] += 1</p>
        <p> prefix = "perfect_"</p>
        <p> else:</p>
        <p> self.stats["fixed"] += 1</p>
        <p> prefix = "fixed_"</p>
        <p> save_name = prefix + filename.replace(".json", ".npy")</p>
        <p> np.save(os.path.join(self.output_dir, save_name), processed_feature)</p>
        <p> except Exception as e:</p>
        <p> self.stats["failed"] += 1</p>
        <p> print(f"\n❌ {filename} error: {e}")</p>
        <p> print(f"\n" + "=" * 30)</p>
        <p> print(f"📊 Report:")</p>
        <p> print(f"✨ Perfect (Perfect): {self.stats['perfect']}")</p>
        <p> print(f"🩹 Fixed (Fixed): {self.stats['fixed']}")</p>
        <p> print(f"💀 Failed (Failed): {self.stats['failed']}")</p>
        <p> print("=" * 30)</p>
        <p> def check_if_perfect(self, clip_data):</p>
        <p> if len(clip_data) != 75: return False</p>
        <p> for frame in clip_data:</p>
        <p> if len(frame) != 2: return False</p>
        <p> for fencer in frame:</p>
        <p> if len(fencer) != 25: return False</p>
        <p> # if np.array(fencer)[:, 2].mean() &lt; 0.3: return False</p>
        <p> return True</p>
        <p> def normalize_clip(self, clip_data):</p>
        <p> if len(clip_data) == 0:</p>
        <p> return None</p>
        <p> clip_frames = []</p>
        <p> valid_frame_count = len(clip_data)</p>
        <p> # Store the last valid centre and scale</p>
        <p> # independently for both fencers.</p>
        <p> previous_centres = [None, None]</p>
        <p> previous_scales = [None, None]</p>
        <p> for frame_index in range(75):</p>
        <p> actual_index = min(</p>
        <p> frame_index,</p>
        <p> valid_frame_count - 1,</p>
        <p> )</p>
        <p> frame = clip_data[actual_index]</p>
        <p> normalized_fencers = []</p>
        <p> for fencer_index in range(2):</p>
        <p> valid_structure = (</p>
        <p> fencer_index &lt; len(frame)</p>
        <p> and isinstance(</p>
        <p> frame[fencer_index],</p>
        <p> list,</p>
        <p> )</p>
        <p> and len(frame[fencer_index]) == 25</p>
        <p> )</p>
        <p> if not valid_structure:</p>
        <p> normalized_fencers.append(</p>
        <p> np.zeros(</p>
        <p> (25, 2),</p>
        <p> dtype=np.float32,</p>
        <p> )</p>
        <p> )</p>
        <p> continue</p>
        <p> joints = np.asarray(</p>
        <p> frame[fencer_index],</p>
        <p> dtype=np.float32,</p>
        <p> )</p>
        <p> coords = joints[:, :2]</p>
        <p> if joints.shape[1] &gt;= 3:</p>
        <p> confidence = joints[:, 2]</p>
        <p> else:</p>
        <p> confidence = np.ones(</p>
        <p> 25,</p>
        <p> dtype=np.float32,</p>
        <p> )</p>
        <p> # A joint is valid only if it has</p>
        <p> # non-zero coordinates and confidence.</p>
        <p> valid_joints = (</p>
        <p> (confidence &gt; 0.0)</p>
        <p> &amp; np.any(coords != 0, axis=1)</p>
        <p> )</p>
        <p> centre = None</p>
        <p> scale = None</p>
        <p> # Prefer the hip as the centre.</p>
        <p> if valid_joints[8]:</p>
        <p> centre = coords[8].copy()</p>
        <p> # Fall back to the shoulder midpoint.</p>
        <p> elif (</p>
        <p> valid_joints[2]</p>
        <p> and valid_joints[5]</p>
        <p> ):</p>
        <p> centre = (</p>
        <p> coords[2] + coords[5]</p>
        <p> ) / 2.0</p>
        <p> # Otherwise reuse the previous centre.</p>
        <p> elif (</p>
        <p> previous_centres[</p>
        <p> fencer_index</p>
        <p> ] is not None</p>
        <p> ):</p>
        <p> centre = previous_centres[</p>
        <p> fencer_index</p>
        <p> ].copy()</p>
        <p> # Prefer neck-to-hip scale.</p>
        <p> if (</p>
        <p> valid_joints[1]</p>
        <p> and valid_joints[8]</p>
        <p> ):</p>
        <p> candidate_scale = np.linalg.norm(</p>
        <p> coords[1] - coords[8]</p>
        <p> )</p>
        <p> if candidate_scale &gt;= 1.0:</p>
        <p> scale = float(</p>
        <p> candidate_scale</p>
        <p> )</p>
        <p> # Fall back to shoulder width.</p>
        <p> if (</p>
        <p> scale is None</p>
        <p> and valid_joints[2]</p>
        <p> and valid_joints[5]</p>
        <p> ):</p>
        <p> candidate_scale = np.linalg.norm(</p>
        <p> coords[2] - coords[5]</p>
        <p> )</p>
        <p> if candidate_scale &gt;= 1.0:</p>
        <p> scale = float(</p>
        <p> candidate_scale</p>
        <p> )</p>
        <p> # Otherwise reuse the previous scale.</p>
        <p> if (</p>
        <p> scale is None</p>
        <p> and previous_scales[</p>
        <p> fencer_index</p>
        <p> ] is not None</p>
        <p> ):</p>
        <p> scale = previous_scales[</p>
        <p> fencer_index</p>
        <p> ]</p>
        <p> # If neither current nor previous</p>
        <p> # geometry is valid, mask the fencer.</p>
        <p> if centre is None or scale is None:</p>
        <p> normalized_fencers.append(</p>
        <p> np.zeros(</p>
        <p> (25, 2),</p>
        <p> dtype=np.float32,</p>
        <p> )</p>
        <p> )</p>
        <p> continue</p>
        <p> normalized_points = np.zeros(</p>
        <p> (25, 2),</p>
        <p> dtype=np.float32,</p>
        <p> )</p>
        <p> # Only transform detected joints.</p>
        <p> # Missing [0, 0] joints remain [0, 0].</p>
        <p> normalized_points[valid_joints] = (</p>
        <p> coords[valid_joints] - centre</p>
        <p> ) / scale</p>
        <p> previous_centres[</p>
        <p> fencer_index</p>
        <p> ] = centre</p>
        <p> previous_scales[</p>
        <p> fencer_index</p>
        <p> ] = scale</p>
        <p> normalized_fencers.append(</p>
        <p> normalized_points</p>
        <p> )</p>
        <p> clip_frames.append(</p>
        <p> normalized_fencers</p>
        <p> )</p>
        <p> return np.asarray(</p>
        <p> clip_frames,</p>
        <p> dtype=np.float32,</p>
        <p> )</p>
        <p>processor = FencingCategorizedProcessor(</p>
        <p> r"F:\PycharmProjects\Fencing-Testing"</p>
        <p> r"\dataset\FencersKeyPoints",</p>
        <p> r"F:\PycharmProjects\Fencing-Testing"</p>
        <p> r"\fencing_features_npy_experiment_v2",</p>
        <p>)</p>
        <p>processor.process_all_files()</p>
      </sec>
      <sec id="sec9dot3">
        <title>Appendix A3. dataset.py</title>
        <p>import os</p>
        <p>import re</p>
        <p>from pathlib import Path</p>
        <p>import numpy as np</p>
        <p>import pandas as pd</p>
        <p>import torch</p>
        <p>from torch.utils.data import Dataset</p>
        <p>LABEL_MAP = {</p>
        <p> "1-0-0": 0, # left fencer scores</p>
        <p> "0-0-1": 1, # right fencer scores</p>
        <p> "0-1-0": 2, # no valid score</p>
        <p>}</p>
        <p>def extract_clip_id(filename):</p>
        <p> stem = Path(filename).stem</p>
        <p> return re.sub(r"^(perfect_|fixed_)", "", stem)</p>
        <p>class FencingDataset(Dataset):</p>
        <p> def __init__(</p>
        <p> self,</p>
        <p> npy_dir,</p>
        <p> label_csv,</p>
        <p> sequence_length=75,</p>
        <p> file_list=None,</p>
        <p> transform=None,</p>
        <p> ):</p>
        <p> if sequence_length not in (25, 50, 75):</p>
        <p> raise ValueError(</p>
        <p> "sequence_length must be 25, 50, or 75"</p>
        <p> )</p>
        <p> self.npy_dir = npy_dir</p>
        <p> self.sequence_length = sequence_length</p>
        <p> self.transform = transform</p>
        <p> df = pd.read_csv(label_csv)</p>
        <p> df["ClipName"] = df["ClipName"].astype(str)</p>
        <p> if df["ClipName"].duplicated().any():</p>
        <p> duplicates = df.loc[</p>
        <p> df["ClipName"].duplicated(),</p>
        <p> "ClipName",</p>
        <p> ].tolist()</p>
        <p> raise ValueError(</p>
        <p> f"Duplicate ClipName values: {duplicates[:5]}"</p>
        <p> )</p>
        <p> metadata = df.set_index("ClipName")</p>
        <p> if file_list is None:</p>
        <p> file_names = sorted(</p>
        <p> f</p>
        <p> for f in os.listdir(npy_dir)</p>
        <p> if f.endswith(".npy")</p>
        <p> )</p>
        <p> else:</p>
        <p> file_names = sorted(file_list)</p>
        <p> records = []</p>
        <p> for filename in file_names:</p>
        <p> clip_id = extract_clip_id(filename)</p>
        <p> if clip_id not in metadata.index:</p>
        <p> raise KeyError(</p>
        <p> f"No CSV row for {filename}; "</p>
        <p> f"parsed ClipName={clip_id}"</p>
        <p> )</p>
        <p> row = metadata.loc[clip_id]</p>
        <p> raw_label = str(row["Hit_label"]).strip()</p>
        <p> if raw_label not in LABEL_MAP:</p>
        <p> raise ValueError(</p>
        <p> f"Unknown Hit_label {raw_label!r} "</p>
        <p> f"for {filename}"</p>
        <p> )</p>
        <p> records.append(</p>
        <p> {</p>
        <p> "filename": filename,</p>
        <p> "clip_id": clip_id,</p>
        <p> "label": LABEL_MAP[raw_label],</p>
        <p> "group": str(row["URL"]),</p>
        <p> "quality": (</p>
        <p> "perfect"</p>
        <p> if filename.startswith("perfect_")</p>
        <p> else "fixed"</p>
        <p> ),</p>
        <p> }</p>
        <p> )</p>
        <p> self.file_names = [</p>
        <p> record["filename"] for record in records</p>
        <p> ]</p>
        <p> self.clip_ids = [</p>
        <p> record["clip_id"] for record in records</p>
        <p> ]</p>
        <p> self.labels = np.asarray(</p>
        <p> [record["label"] for record in records],</p>
        <p> dtype=np.int64,</p>
        <p> )</p>
        <p> self.groups = np.asarray(</p>
        <p> [record["group"] for record in records],</p>
        <p> dtype=object,</p>
        <p> )</p>
        <p> self.qualities = [</p>
        <p> record["quality"] for record in records</p>
        <p> ]</p>
        <p> def __len__(self):</p>
        <p> return len(self.file_names)</p>
        <p> def __getitem__(self, idx):</p>
        <p> filename = self.file_names[idx]</p>
        <p> file_path = os.path.join(self.npy_dir, filename)</p>
        <p> data = np.load(file_path)</p>
        <p> expected_shape = (75, 2, 25, 2)</p>
        <p> if data.shape != expected_shape:</p>
        <p> raise ValueError(</p>
        <p> f"{filename}: expected {expected_shape}, "</p>
        <p> f"got {data.shape}"</p>
        <p> )</p>
        <p> # Always derive 25/50/75-frame inputs from</p>
        <p> # the same 75-frame base file.</p>
        <p> data = data[-self.sequence_length:]</p>
        <p> x = torch.from_numpy(data).float()</p>
        <p> y = torch.tensor(</p>
        <p> self.labels[idx],</p>
        <p> dtype=torch.long,</p>
        <p> )</p>
        <p> if self.transform is not None:</p>
        <p> x = self.transform(x)</p>
        <p> return x, y</p>
      </sec>
      <sec id="sec9dot4">
        <title>Appendix A4. modelv1.py (Attention Model)</title>
        <p>import torch</p>
        <p>import torch.nn as nn</p>
        <p>import torch.nn.functional as F</p>
        <p>class FencingLSTM(nn.Module):</p>
        <p> def __init__(self, input_size=100, hidden_size=256, num_layers=2, num_classes=3):</p>
        <p> super(FencingLSTM, self).__init__()</p>
        <p> self.bn_input = nn.BatchNorm1d(input_size)</p>
        <p> self.lstm = nn.LSTM(input_size, hidden_size, num_layers,</p>
        <p> batch_first=True, bidirectional=True, dropout=0.4)</p>
        <p> self.attention = nn.Sequential(</p>
        <p> nn.Linear(hidden_size * 2, 128),</p>
        <p> nn.Tanh(),</p>
        <p> nn.Linear(128, 1)</p>
        <p> )</p>
        <p> self.fc = nn.Sequential(</p>
        <p> nn.Linear(hidden_size * 2, 128),</p>
        <p> nn.BatchNorm1d(128),</p>
        <p> nn.ReLU(),</p>
        <p> nn.Dropout(0.5), </p>
        <p> nn.Linear(128, num_classes)</p>
        <p> )</p>
        <p> def forward(self, x):</p>
        <p> batch_size, seq_len, _, _, _ = x.size()</p>
        <p> x = x.view(batch_size, seq_len, -1).transpose(1, 2)</p>
        <p> x = self.bn_input(x).transpose(1, 2)</p>
        <p> lstm_out, _ = self.lstm(x)</p>
        <p> attn_weights = self.attention(lstm_out) # (batch, 75, 1)</p>
        <p> attn_weights = F.softmax(attn_weights, dim=1)</p>
        <p> context_vector = torch.sum(attn_weights * lstm_out, dim=1)</p>
        <p> logits = self.fc(context_vector)</p>
        <p> return logits</p>
      </sec>
      <sec id="sec9dot5">
        <title>Appendix A5. modelv12.py (No-Attention Model)</title>
        <p>import torch</p>
        <p>import torch.nn as nn</p>
        <p>class FencingLSTM(nn.Module):</p>
        <p> def __init__(self, input_size=100, hidden_size=256, num_layers=2, num_classes=3):</p>
        <p> super(FencingLSTM, self).__init__()</p>
        <p> self.bn_input = nn.BatchNorm1d(input_size)</p>
        <p> self.lstm = nn.LSTM(input_size, hidden_size, num_layers,</p>
        <p> batch_first=True, bidirectional=True, dropout=0.4)</p>
        <p> self.fc = nn.Sequential(</p>
        <p> nn.Linear(hidden_size * 2, 128),</p>
        <p> nn.BatchNorm1d(128),</p>
        <p> nn.ReLU(),</p>
        <p> nn.Dropout(0.5),</p>
        <p> nn.Linear(128, num_classes)</p>
        <p> )</p>
        <p> def forward(self, x):</p>
        <p> batch_size, seq_len, _, _, _ = x.size()</p>
        <p> x = x.view(batch_size, seq_len, -1).transpose(1, 2)</p>
        <p> x = self.bn_input(x).transpose(1, 2)</p>
        <p> _, (hidden_state, _) = self.lstm(x)</p>
        <p> forward_final = hidden_state[-2]</p>
        <p> backward_final = hidden_state[-1]</p>
        <p> context_vector = torch.cat(</p>
        <p> [forward_final, backward_final],</p>
        <p> dim=1,</p>
        <p> )</p>
        <p> logits = self.fc(context_vector)</p>
        <p> return logits</p>
      </sec>
      <sec id="sec9dot6">
        <title>Appendix A6. Complete 30-Run Summary</title>
        <p>The complete run-level validation and archived test results are reported in <bold>Table A1</bold>.</p>
        <p><bold>Table A1.</bold> Complete run-level validation and archived test results.</p>
        <table-wrap id="tbl6">
          <label>Table 6</label>
          <table>
            <tbody>
              <tr>
                <td>
                  <bold>Seed</bold>
                </td>
                <td>
                  <bold>Frames</bold>
                </td>
                <td>
                  <bold>Attention</bold>
                </td>
                <td>
                  <bold>Best epoch</bold>
                </td>
                <td>
                  <bold>Best val. (%)</bold>
                </td>
                <td>
                  <bold>Final val. (%)</bold>
                </td>
                <td>
                  <bold>Test acc. (%)</bold>
                </td>
              </tr>
              <tr>
                <td>42</td>
                <td>25</td>
                <td>No</td>
                <td>16</td>
                <td>56.92</td>
                <td>56.55</td>
                <td>56.29</td>
              </tr>
              <tr>
                <td>42</td>
                <td>25</td>
                <td>Yes</td>
                <td>19</td>
                <td>58.71</td>
                <td>57.37</td>
                <td>57.63</td>
              </tr>
              <tr>
                <td>42</td>
                <td>50</td>
                <td>No</td>
                <td>20</td>
                <td>58.71</td>
                <td>58.71</td>
                <td>59.27</td>
              </tr>
              <tr>
                <td>42</td>
                <td>50</td>
                <td>Yes</td>
                <td>19</td>
                <td>56.25</td>
                <td>55.36</td>
                <td>56.29</td>
              </tr>
              <tr>
                <td>42</td>
                <td>75</td>
                <td>No</td>
                <td>20</td>
                <td>59.82</td>
                <td>59.82</td>
                <td>60.83</td>
              </tr>
              <tr>
                <td>42</td>
                <td>75</td>
                <td>Yes</td>
                <td>18</td>
                <td>56.62</td>
                <td>56.47</td>
                <td>56.52</td>
              </tr>
              <tr>
                <td>52</td>
                <td>25</td>
                <td>No</td>
                <td>19</td>
                <td>58.04</td>
                <td>58.04</td>
                <td>57.19</td>
              </tr>
              <tr>
                <td>52</td>
                <td>25</td>
                <td>Yes</td>
                <td>19</td>
                <td>58.48</td>
                <td>56.18</td>
                <td>58.97</td>
              </tr>
              <tr>
                <td>52</td>
                <td>50</td>
                <td>No</td>
                <td>19</td>
                <td>56.40</td>
                <td>53.05</td>
                <td>56.96</td>
              </tr>
              <tr>
                <td>52</td>
                <td>50</td>
                <td>Yes</td>
                <td>14</td>
                <td>56.62</td>
                <td>51.79</td>
                <td>55.77</td>
              </tr>
              <tr>
                <td>52</td>
                <td>75</td>
                <td>No</td>
                <td>19</td>
                <td>56.55</td>
                <td>51.64</td>
                <td>57.56</td>
              </tr>
              <tr>
                <td>52</td>
                <td>75</td>
                <td>Yes</td>
                <td>16</td>
                <td>56.62</td>
                <td>55.58</td>
                <td>53.76</td>
              </tr>
              <tr>
                <td>62</td>
                <td>25</td>
                <td>No</td>
                <td>20</td>
                <td>58.56</td>
                <td>58.56</td>
                <td>59.27</td>
              </tr>
              <tr>
                <td>62</td>
                <td>25</td>
                <td>Yes</td>
                <td>18</td>
                <td>59.52</td>
                <td>57.51</td>
                <td>57.19</td>
              </tr>
              <tr>
                <td>62</td>
                <td>50</td>
                <td>No</td>
                <td>19</td>
                <td>60.19</td>
                <td>58.56</td>
                <td>60.24</td>
              </tr>
              <tr>
                <td>62</td>
                <td>50</td>
                <td>Yes</td>
                <td>19</td>
                <td>57.07</td>
                <td>56.25</td>
                <td>56.81</td>
              </tr>
              <tr>
                <td>62</td>
                <td>75</td>
                <td>No</td>
                <td>20</td>
                <td>57.89</td>
                <td>57.89</td>
                <td>58.23</td>
              </tr>
              <tr>
                <td>62</td>
                <td>75</td>
                <td>Yes</td>
                <td>18</td>
                <td>54.02</td>
                <td>53.94</td>
                <td>52.94</td>
              </tr>
              <tr>
                <td>72</td>
                <td>25</td>
                <td>No</td>
                <td>16</td>
                <td>60.34</td>
                <td>57.37</td>
                <td>59.87</td>
              </tr>
              <tr>
                <td>72</td>
                <td>25</td>
                <td>Yes</td>
                <td>20</td>
                <td>59.38</td>
                <td>59.38</td>
                <td>55.40</td>
              </tr>
              <tr>
                <td>72</td>
                <td>50</td>
                <td>No</td>
                <td>19</td>
                <td>59.30</td>
                <td>57.74</td>
                <td>59.94</td>
              </tr>
              <tr>
                <td>72</td>
                <td>50</td>
                <td>Yes</td>
                <td>20</td>
                <td>57.29</td>
                <td>57.29</td>
                <td>54.65</td>
              </tr>
              <tr>
                <td>72</td>
                <td>75</td>
                <td>No</td>
                <td>18</td>
                <td>59.30</td>
                <td>57.29</td>
                <td>59.57</td>
              </tr>
              <tr>
                <td>72</td>
                <td>75</td>
                <td>Yes</td>
                <td>20</td>
                <td>56.25</td>
                <td>56.25</td>
                <td>58.53</td>
              </tr>
              <tr>
                <td>82</td>
                <td>25</td>
                <td>No</td>
                <td>18</td>
                <td>59.90</td>
                <td>58.85</td>
                <td>57.78</td>
              </tr>
              <tr>
                <td>82</td>
                <td>25</td>
                <td>Yes</td>
                <td>18</td>
                <td>58.56</td>
                <td>57.29</td>
                <td>57.56</td>
              </tr>
              <tr>
                <td>82</td>
                <td>50</td>
                <td>No</td>
                <td>18</td>
                <td>59.15</td>
                <td>57.44</td>
                <td>60.01</td>
              </tr>
              <tr>
                <td>82</td>
                <td>50</td>
                <td>Yes</td>
                <td>20</td>
                <td>57.66</td>
                <td>57.66</td>
                <td>57.86</td>
              </tr>
              <tr>
                <td>82</td>
                <td>75</td>
                <td>No</td>
                <td>17</td>
                <td>58.56</td>
                <td>57.14</td>
                <td>59.57</td>
              </tr>
              <tr>
                <td>82</td>
                <td>75</td>
                <td>Yes</td>
                <td>11</td>
                <td>52.46</td>
                <td>51.79</td>
                <td>50.34</td>
              </tr>
            </tbody>
          </table>
        </table-wrap>
      </sec>
    </sec>
  </body>
  <back>
    <ref-list>
      <title>References</title>
      <ref id="B1">
        <label>1.</label>
        <citation-alternatives>
          <mixed-citation publication-type="confproc">Cao, Z., Simon, T., Wei, S. and Sheikh, Y. (2017). Realtime Multi-Person 2D Pose Estimation Using Part Affinity Fields. 2017 <italic>IEEE Conference on Computer Vision and Pattern Recognition</italic> ( <italic>CVPR</italic>), Honolulu, 21-26 July 2017, 7291-7299. https://doi.org/10.1109/cvpr.2017.143 <pub-id pub-id-type="doi">10.1109/cvpr.2017.143</pub-id><ext-link ext-link-type="uri" xlink:href="https://doi.org/10.1109/cvpr.2017.143">https://doi.org/10.1109/cvpr.2017.143</ext-link></mixed-citation>
          <element-citation publication-type="confproc">
            <person-group person-group-type="author">
              <string-name>Cao, Z.</string-name>
              <string-name>Simon, T.</string-name>
              <string-name>Wei, S.</string-name>
              <string-name>Sheikh, Y.</string-name>
            </person-group>
            <year>2017</year>
            <pub-id pub-id-type="doi">10.1109/cvpr.2017.143</pub-id>
          </element-citation>
        </citation-alternatives>
      </ref>
      <ref id="B2">
        <label>2.</label>
        <citation-alternatives>
          <mixed-citation publication-type="other">Hochreiter, S. and Schmidhuber, J. (1997) Long Short-Term Memory. <italic>Neural Computation</italic>, 9, 1735-1780. https://doi.org/10.1162/neco.1997.9.8.1735 <pub-id pub-id-type="doi">10.1162/neco.1997.9.8.1735</pub-id><pub-id pub-id-type="pmid">9377276</pub-id><ext-link ext-link-type="uri" xlink:href="https://doi.org/10.1162/neco.1997.9.8.1735">https://doi.org/10.1162/neco.1997.9.8.1735</ext-link></mixed-citation>
          <element-citation publication-type="other">
            <person-group person-group-type="author">
              <string-name>Hochreiter, S.</string-name>
              <string-name>Schmidhuber, J.</string-name>
            </person-group>
            <year>1997</year>
            <article-title>Long Short-Term Memory</article-title>
            <source>Neural Computation</source>
            <volume>9</volume>
            <pub-id pub-id-type="doi">10.1162/neco.1997.9.8.1735</pub-id>
            <pub-id pub-id-type="pmid">9377276</pub-id>
          </element-citation>
        </citation-alternatives>
      </ref>
      <ref id="B3">
        <label>3.</label>
        <citation-alternatives>
          <mixed-citation publication-type="other">Schuster, M. and Paliwal, K.K. (1997) Bidirectional Recurrent Neural Networks. <italic>IEEE Transactions on Signal Processing</italic>, 45, 2673-2681. https://doi.org/10.1109/78.650093 <pub-id pub-id-type="doi">10.1109/78.650093</pub-id><ext-link ext-link-type="uri" xlink:href="https://doi.org/10.1109/78.650093">https://doi.org/10.1109/78.650093</ext-link></mixed-citation>
          <element-citation publication-type="other">
            <person-group person-group-type="author">
              <string-name>Schuster, M.</string-name>
              <string-name>Paliwal, K.K.</string-name>
            </person-group>
            <year>1997</year>
            <article-title>Bidirectional Recurrent Neural Networks</article-title>
            <source>IEEE Transactions on Signal Processing</source>
            <volume>45</volume>
            <pub-id pub-id-type="doi">10.1109/78.650093</pub-id>
          </element-citation>
        </citation-alternatives>
      </ref>
      <ref id="B4">
        <label>4.</label>
        <citation-alternatives>
          <mixed-citation publication-type="book">Goodfellow, I. Bengio, Y. and Courville, A. (2016) Deep Learning. MIT Press.</mixed-citation>
          <element-citation publication-type="book">
            <person-group person-group-type="author">
              <string-name>Goodfellow, I.</string-name>
              <string-name>Bengio, Y.</string-name>
              <string-name>Courville, A.</string-name>
            </person-group>
            <year>2016</year>
            <article-title>Deep Learning</article-title>
          </element-citation>
        </citation-alternatives>
      </ref>
      <ref id="B5">
        <label>5.</label>
        <citation-alternatives>
          <mixed-citation publication-type="confproc">Bahdanau, D., Cho, K. and Bengio, Y. (2015) Neural Machine Translation by Jointly Learning to Align and Translate. <italic>Proceedings of the</italic>3 <italic>rd International Conference on Learning Representations</italic> ( <italic>ICLR</italic>), San Diego, 7-9 May 2015, 1-2.</mixed-citation>
          <element-citation publication-type="confproc">
            <person-group person-group-type="author">
              <string-name>Bahdanau, D.</string-name>
              <string-name>Cho, K.</string-name>
              <string-name>Bengio, Y.</string-name>
            </person-group>
            <year>2015</year>
            <article-title>Neural Machine Translation by Jointly Learning to Align and Translate</article-title>
            <source>Proceedings of the 3rd International Conference on Learning Representations (ICLR)</source>
            <volume>7</volume>
          </element-citation>
        </citation-alternatives>
      </ref>
      <ref id="B6">
        <label>6.</label>
        <mixed-citation publication-type="web">AlexPGD. Sport Fencing Matches AI. Kaggle. https://www.kaggle.com/datasets/alexpgd/sport-fencing-matches-ai/data</mixed-citation>
      </ref>
      <ref id="B7">
        <label>7.</label>
        <citation-alternatives>
          <mixed-citation publication-type="other">Wang, L., Xiong, Y., Wang, Z., Qiao, Y., Lin, D., Tang, X., <italic>et al</italic>. (2016) Temporal Segment Networks: Towards Good Practices for Deep Action Recognition. In: Leibe, B., <italic>et al</italic>., Eds., <italic>Computer Vision</italic>— <italic>ECCV</italic> 2016, Springer International Publishing, 20-36. https://doi.org/10.1007/978-3-319-46484-8_2 <pub-id pub-id-type="doi">10.1007/978-3-319-46484-8_2</pub-id><ext-link ext-link-type="uri" xlink:href="https://doi.org/10.1007/978-3-319-46484-8_2">https://doi.org/10.1007/978-3-319-46484-8_2</ext-link></mixed-citation>
          <element-citation publication-type="other">
            <person-group person-group-type="author">
              <string-name>Wang, L.</string-name>
              <string-name>Xiong, Y.</string-name>
              <string-name>Wang, Z.</string-name>
              <string-name>Qiao, Y.</string-name>
              <string-name>Lin, D.</string-name>
              <string-name>Tang, X.</string-name>
              <string-name>Leibe, B.</string-name>
            </person-group>
            <year>2016</year>
            <article-title>Temporal Segment Networks: Towards Good Practices for Deep Action Recognition</article-title>
            <source>In: Leibe</source>
            <volume>20</volume>
            <pub-id pub-id-type="doi">10.1007/978-3-319-46484-8_2</pub-id>
          </element-citation>
        </citation-alternatives>
      </ref>
      <ref id="B8">
        <label>8.</label>
        <citation-alternatives>
          <mixed-citation publication-type="confproc">Ioffe, S. and Szegedy, C. (2015) Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. <italic>Proceedings of the</italic>32 <italic>nd ICML</italic>, Vol. 37, 448-456.</mixed-citation>
          <element-citation publication-type="confproc">
            <person-group person-group-type="author">
              <string-name>Ioffe, S.</string-name>
              <string-name>Szegedy, C.</string-name>
              <string-name>ICML, V</string-name>
            </person-group>
            <year>2015</year>
            <article-title>Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift</article-title>
            <source>Proceedings of the 32nd ICML</source>
            <volume>448</volume>
          </element-citation>
        </citation-alternatives>
      </ref>
      <ref id="B9">
        <label>9.</label>
        <citation-alternatives>
          <mixed-citation publication-type="confproc">Kingma, D.P. and Ba, J. (2015) Adam: A Method for Stochastic Optimization. <italic>Proceedings of the</italic>3 <italic>rd International Conference on Learning Representations</italic> ( <italic>ICLR</italic>), San Diego, 7-9 May 2015, 2.</mixed-citation>
          <element-citation publication-type="confproc">
            <person-group person-group-type="author">
              <string-name>Kingma, D.P.</string-name>
              <string-name>Ba, J.</string-name>
            </person-group>
            <year>2015</year>
            <article-title>Adam: A Method for Stochastic Optimization</article-title>
            <source>Proceedings of the 3rd International Conference on Learning Representations (ICLR)</source>
            <volume>7</volume>
          </element-citation>
        </citation-alternatives>
      </ref>
      <ref id="B10">
        <label>10.</label>
        <citation-alternatives>
          <mixed-citation publication-type="journal">Srivastava, N., <italic>et al</italic>. (2014) Dropout: A Simple Way to Prevent Neural Networks from Overfitting. <italic>Journal of Machine Learning Research</italic>, 15, 1929-1958.</mixed-citation>
          <element-citation publication-type="journal">
            <person-group person-group-type="author">
              <string-name>Srivastava, N.</string-name>
            </person-group>
            <year>2014</year>
            <article-title>Dropout: A Simple Way to Prevent Neural Networks from Overfitting</article-title>
            <source>Journal of Machine Learning Research</source>
            <volume>15</volume>
          </element-citation>
        </citation-alternatives>
      </ref>
      <ref id="B11">
        <label>11.</label>
        <citation-alternatives>
          <mixed-citation publication-type="confproc">Bouthillier, X., Laurent, C. and Vincent, P. (2019) Unreproducible Research Is Reproducible. <italic>Proceedings of the</italic>36 <italic>th ICML</italic>, Vol. 97, 725-734.</mixed-citation>
          <element-citation publication-type="confproc">
            <person-group person-group-type="author">
              <string-name>Bouthillier, X.</string-name>
              <string-name>Laurent, C.</string-name>
              <string-name>Vincent, P.</string-name>
              <string-name>ICML, V</string-name>
            </person-group>
            <year>2019</year>
            <article-title>Unreproducible Research Is Reproducible</article-title>
            <source>Proceedings of the 36th ICML</source>
            <volume>725</volume>
          </element-citation>
        </citation-alternatives>
      </ref>
      <ref id="B12">
        <label>12.</label>
        <citation-alternatives>
          <mixed-citation publication-type="other">Sokolova, M. and Lapalme, G. (2009) A Systematic Analysis of Performance Measures for Classification Tasks. <italic>Information Processing &amp; Management</italic>, 45, 427-437. https://doi.org/10.1016/j.ipm.2009.03.002 <pub-id pub-id-type="doi">10.1016/j.ipm.2009.03.002</pub-id><ext-link ext-link-type="uri" xlink:href="https://doi.org/10.1016/j.ipm.2009.03.002">https://doi.org/10.1016/j.ipm.2009.03.002</ext-link></mixed-citation>
          <element-citation publication-type="other">
            <person-group person-group-type="author">
              <string-name>Sokolova, M.</string-name>
              <string-name>Lapalme, G.</string-name>
            </person-group>
            <year>2009</year>
            <article-title>A Systematic Analysis of Performance Measures for Classification Tasks</article-title>
            <source>Information Processing &amp; Management</source>
            <volume>45</volume>
            <pub-id pub-id-type="doi">10.1016/j.ipm.2009.03.002</pub-id>
          </element-citation>
        </citation-alternatives>
      </ref>
    </ref-list>
  </back>
</article>