Основано на классификации заголовков китайского новостного текста [PaddleNLP]

искусственный интеллект NLP

Регулярный сезон: классификация заголовков китайских новостей

1. Введение в программу

1.1 Введение в конкурс:

Классификация текста — это автоматическая классификация и маркировка наборов текстов (или других сущностей или объектов) по определенной системе классификации или стандарту с помощью компьютеров. Этот конкурс представляет собой классификацию текстов заголовков новостей. Участникам необходимо обучить модель классификации новостей на основе предоставленного текста заголовка новостей и меток категорий, а затем классифицировать текст заголовка новостей в тестовом наборе. Точность используется в качестве показателя оценки = правильное число классификаций / общее количество классификаций необходимое количество. В то же время участникам необходимо использовать платформу весла и основную библиотеку разработки PaddleNLP в текстовом поле весла PaddleNLP имеет простой и удобный в использовании полнофункциональный API в текстовом поле, примеры приложений в нескольких сценарии, а также очень богатую модель предварительного обучения, которая глубоко адаптирована к версии paddle.Framework 2.x.

1.2 Введение данных:

THUCNews создается путем фильтрации и фильтрации исторических данных канала подписки Sina News RSS с 2005 по 2011 год, включая 740 000 новостных документов (2,19 ГБ), все в текстовом формате UTF-8. На основе оригинальной системы классификации новостей Sina набор данных этого конкурса повторно интегрирован и разделен на 14 категорий классификации кандидатов: финансы, лотерея, недвижимость, акции, товары для дома, образование, технологии, общество, мода, текущие дела, спорт, созвездие, игры, развлечения. Всего предоставлено 832 471 единиц обучающих данных.

Формат набора данных, представленного на конкурсе: обучающий набор и формат проверочного набора: исходное название + \t + метка, формат тестового набора: исходное название.

1.3 Базовая идея:

Гонка по более чем одной классификации под названием задача более обычный краткий текст, проект в основном основан на тонкой настройке PaddleNLP на данных обучения, предоставленных моделью предварительного обучения. Роберт завершил обучение и оптимизацию модели классификации News 14. Наконец, используя эти модели для тестирования данные и отправить результаты для прогнозирования сгенерированных файлов.

Обратите внимание, что этот проект выполняется для выбора среды графического процессора Extreme Edition! Если вы не устарели, вам следует обратить внимание на BatchSize!

Дополнение к предварительным знаниям BERT:[Принцип] Классическая предтренировочная модель — BERT

2. Чтение и анализ данных

1. Анализ данных

# 进入比赛数据集存放目录
%cd /home/aistudio/data/data103654/
/home/aistudio/data/data103654
# 使用pandas读取数据集
import pandas as pd
train = pd.read_table('train.txt', sep='\t',header=None)  # 训练集
dev = pd.read_table('dev.txt', sep='\t',header=None)      # 验证集
test = pd.read_table('test.txt', sep='\t',header=None)    # 测试集
print(f"train数据集长度: {len(train)}\t dev数据集长度{len(dev)}\t test数据集长度{len(test)}")
train数据集长度: 752471	 dev数据集长度80000	 test数据集长度83599
# 添加列名便于对数据进行更好处理
train.columns = ["text_a",'label']
dev.columns = ["text_a",'label']
test.columns = ["text_a"]
# 拼接训练和验证集,便于统计分析
total = pd.concat([train,dev],axis=0)
#创建字体目录fonts
%cd ~
# !mkdir .fonts

# 复制字体文件到该路径
!cp data/data61659/simhei.ttf .fonts/
/home/aistudio
cp: cannot create regular file '.fonts/': Not a directory
# 总类别标签分布统计
print(total['label'].value_counts())
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt

#指定默认字体
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['font.family']='sans-serif'
#解决负号'-'显示为方块的问题
mpl.rcParams['axes.unicode_minus'] = False

total['label'].value_counts().plot.bar()
plt.show()
科技    162245
股票    153949
体育    130982
娱乐     92228
时政     62867
社会     50541
教育     41680
财经     36963
家居     32363
游戏     24283
房产     19922
时尚     13335
彩票      7598
星座      3515
Name: label, dtype: int64


/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/font_manager.py:1331: UserWarning: findfont: Font family ['sans-serif'] not found. Falling back to DejaVu Sans
  (prop.get_family(), self.defaultFamily[fontext]))

png

# 最大文本长度
max(total['text_a'].str.len())
48
# 文本长度统计分析,通过分析可以看出文本较短,最长为48
total['text_a'].map(len).describe()
count    832471.000000
mean         19.388112
std           4.097139
min           2.000000
25%          17.000000
50%          20.000000
75%          23.000000
max          48.000000
Name: text_a, dtype: float64
# 对测试集的长度统计分析,可以看出在长度上分布与训练数据相近
test['text_a'].map(len).describe()
count    83599.000000
mean        19.815022
std          3.883845
min          3.000000
25%         17.000000
50%         20.000000
75%         23.000000
max         84.000000
Name: text_a, dtype: float64

2. Сохранить данные

# 保存处理后的数据集文件
train.to_csv('train.csv', sep='\t', index=False)  # 保存训练集,格式为text_a,label
dev.to_csv('dev.csv', sep='\t', index=False)      # 保存验证集,格式为text_a,label
test.to_csv('test.csv', sep='\t', index=False)    # 保存测试集,格式为text_a

3. Создайте базовую модель на основе PaddleNLP

1 Подготовка среды PaddleNLP

# 下载最新版本的paddlenlp
# !pip install --upgrade paddlenlp
# 导入所需的第三方库
import math
import numpy as np
import os
import collections
from functools import partial
import random
import time
import inspect
import importlib
from tqdm import tqdm
import paddle
import paddle.nn as nn
import paddle.nn.functional as F
from paddle.io import IterableDataset
from paddle.utils.download import get_path_from_url
# 导入paddlenlp所需的相关包
import paddlenlp as ppnlp
from paddlenlp.data import JiebaTokenizer, Pad, Stack, Tuple, Vocab
from paddlenlp.datasets import MapDataset
from paddle.dataset.common import md5file
from paddlenlp.datasets import DatasetBuilder

2. Определение набора данных

# 定义要进行分类的14个类别
label_list=list(train.label.unique())
print(label_list)
['科技', '体育', '时政', '股票', '娱乐', '教育', '家居', '财经', '房产', '社会', '游戏', '彩票', '星座', '时尚']
# id 到 label转换
id_label_dict={}
for i in range(0,len(label_list)):
    id_label_dict[i]=label_list[i]
print(id_label_dict)
#################################################
# label到id转换
label_id_dict={}
for i in range(0,len(label_list)):
    label_id_dict[label_list[i]]=i
print(label_id_dict)
{0: '科技', 1: '体育', 2: '时政', 3: '股票', 4: '娱乐', 5: '教育', 6: '家居', 7: '财经', 8: '房产', 9: '社会', 10: '游戏', 11: '彩票', 12: '星座', 13: '时尚'}
{'科技': 0, '体育': 1, '时政': 2, '股票': 3, '娱乐': 4, '教育': 5, '家居': 6, '财经': 7, '房产': 8, '社会': 9, '游戏': 10, '彩票': 11, '星座': 12, '时尚': 13}
def read(pd_data):
    for index, item in pd_data.iterrows():       
        yield {'text_a': item['text_a'], 'label': label_id_dict[item['label']]}
# 训练集、测试集
from paddle.io import Dataset, Subset
from paddlenlp.datasets import MapDataset
from paddlenlp.datasets import load_dataset

train_dataset = load_dataset(read, pd_data=train,lazy=False)
dev_dataset = load_dataset(read, pd_data=dev,lazy=False)
for i in range(5):
    print(train_dataset[i])
{'text_a': '网易第三季度业绩低于分析师预期', 'label': 0}
{'text_a': '巴萨1年前地狱重现这次却是天堂 再赴魔鬼客场必翻盘', 'label': 1}
{'text_a': '美国称支持向朝鲜提供紧急人道主义援助', 'label': 2}
{'text_a': '增资交银康联 交行夺参股险商首单', 'label': 3}
{'text_a': '午盘:原材料板块领涨大盘', 'label': 3}

3 Загрузите предварительно обученную модель

# 此次使用在中文领域效果较优的roberta-wwm-ext-large模型,预训练模型一般“大力出奇迹”,选用大的预训练模型可以取得比base模型更优的效果
MODEL_NAME = "roberta-wwm-ext-large"
# 只需指定想要使用的模型名称和文本分类的类别数即可完成Fine-tune网络定义,通过在预训练模型后拼接上一个全连接网络(Full Connected)进行分类
model = ppnlp.transformers.RobertaForSequenceClassification.from_pretrained(MODEL_NAME, num_classes=14) # 此次分类任务为14分类任务,故num_classes设置为14
# 定义模型对应的tokenizer,tokenizer可以把原始输入文本转化成模型model可接受的输入数据格式。需注意tokenizer类要与选择的模型相对应,具体可以查看PaddleNLP相关文档
tokenizer = ppnlp.transformers.RobertaTokenizer.from_pretrained(MODEL_NAME)

PaddleNLP поддерживает не только модели предварительного обучения RoBERTa, но и такие модели предварительного обучения, как ERNIE, BERT и Electra. В частности, вы можете просмотреть:Весло НЛП модель

В следующей таблице приведены различные модели предварительного обучения, которые в настоящее время поддерживаются PaddleNLP. Пользователи могут использовать модели, предоставляемые PaddleNLP, для выполнения таких задач, как ответы на вопросы, классификация последовательностей и классификация токенов. В то же время пользователям предоставляется 22 вида предварительно обученных весов параметров, в том числе 11 видов предварительно обученных весов моделей китайского языка.

Model Tokenizer Supported Task Model Name
BERT BertTokenizer BertModel
BertForQuestionAnswering
BertForSequenceClassification
BertForTokenClassification
bert-base-uncased
bert-large-uncased
bert-base-multilingual-uncased
bert-base-cased
bert-base-chinese
bert-base-multilingual-cased
bert-large-cased
bert-wwm-chinese
bert-wwm-ext-chinese
ERNIE ErnieTokenizer
ErnieTinyTokenizer
ErnieModel
ErnieForQuestionAnswering
ErnieForSequenceClassification
ErnieForTokenClassification
ernie-1.0
ernie-tiny
ernie-2.0-en
ernie-2.0-large-en
RoBERTa RobertaTokenizer RobertaModel
RobertaForQuestionAnswering
RobertaForSequenceClassification
RobertaForTokenClassification
roberta-wwm-ext
roberta-wwm-ext-large
rbt3
rbtl3
ELECTRA ElectraTokenizer ElectraModel
ElectraForSequenceClassification
ElectraForTokenClassification
electra-small
electra-base
electra-large
chinese-electra-small
chinese-electra-base

Примечание. Китайские модели предварительной подготовкиbert-base-chinese, bert-wwm-chinese, bert-wwm-ext-chinese, ernie-1.0, ernie-tiny, roberta-wwm-ext, roberta-wwm-ext-large, rbt3, rbtl3, chinese-electra-base, chinese-electra-smallЖдать.

4. Определите функции обработки данных

# 定义数据加载和处理函数
def convert_example(example, tokenizer, max_seq_length=128, is_test=False):
    qtconcat = example["text_a"]
    encoded_inputs = tokenizer(text=qtconcat, max_seq_len=max_seq_length)  # tokenizer处理为模型可接受的格式 
    input_ids = encoded_inputs["input_ids"]
    token_type_ids = encoded_inputs["token_type_ids"]

    if not is_test:
        label = np.array([example["label"]], dtype="int64")
        return input_ids, token_type_ids, label
    else:
        return input_ids, token_type_ids

# 定义数据加载函数dataloader
def create_dataloader(dataset,
                      mode='train',
                      batch_size=1,
                      batchify_fn=None,
                      trans_fn=None):
    if trans_fn:
        dataset = dataset.map(trans_fn)

    shuffle = True if mode == 'train' else False
    # 训练数据集随机打乱,测试数据集不打乱
    if mode == 'train':
        batch_sampler = paddle.io.DistributedBatchSampler(
            dataset, batch_size=batch_size, shuffle=shuffle)
    else:
        batch_sampler = paddle.io.BatchSampler(
            dataset, batch_size=batch_size, shuffle=shuffle)

    return paddle.io.DataLoader(
        dataset=dataset,
        batch_sampler=batch_sampler,
        collate_fn=batchify_fn,
        return_list=True)

4. Модельное обучение

1. Настройки гиперпараметров

# 参数设置:
# 批处理大小,显存如若不足的话可以适当改小该值  
batch_size = 360
# 文本序列最大截断长度,需要根据文本具体长度进行确定,最长不超过512。 通过文本长度分析可以看出文本长度最大为48,故此处设置为48
max_seq_length = max(total['text_a'].str.len())

2. Обработка данных

# 将数据处理成模型可读入的数据格式
trans_func = partial(
    convert_example,
    tokenizer=tokenizer,
    max_seq_length=max_seq_length)

batchify_fn = lambda samples, fn=Tuple(
    Pad(axis=0, pad_val=tokenizer.pad_token_id),  # input_ids
    Pad(axis=0, pad_val=tokenizer.pad_token_type_id),  # token_type_ids
    Stack()  # labels
): [data for data in fn(samples)]

# 训练集迭代器
train_data_loader = create_dataloader(
    train_dataset,
    mode='train',
    batch_size=batch_size,
    batchify_fn=batchify_fn,
    trans_fn=trans_func)

# 验证集迭代器
dev_data_loader = create_dataloader(
    dev_dataset,
    mode='dev',
    batch_size=batch_size,
    batchify_fn=batchify_fn,
    trans_fn=trans_func)

3. Установите показатели оценки

Скорость обучения, подходящая для моделей Transformer, таких как BERT, представляет собой динамическую скорость обучения при прогреве.

# 定义超参,loss,优化器等
from paddlenlp.transformers import LinearDecayWithWarmup


# 定义训练过程中的最大学习率
learning_rate = 4e-5
# 训练轮次
epochs = 32
# 学习率预热比例
warmup_proportion = 0.1
# 权重衰减系数,类似模型正则项策略,避免模型过拟合
weight_decay = 0.01

num_training_steps = len(train_data_loader) * epochs
lr_scheduler = LinearDecayWithWarmup(learning_rate, num_training_steps, warmup_proportion)

# AdamW优化器
optimizer = paddle.optimizer.AdamW(
    learning_rate=lr_scheduler,
    parameters=model.parameters(),
    weight_decay=weight_decay,
    apply_decay_param_fun=lambda x: x in [
        p.name for n, p in model.named_parameters()
        if not any(nd in n for nd in ["bias", "norm"])
    ])

criterion = paddle.nn.loss.CrossEntropyLoss()  # 交叉熵损失函数
metric = paddle.metric.Accuracy()              # accuracy评价指标

4. Оценка модели

PS: во время обучения модели вы можете проверить использование видеопамяти, введя команду nvidia-smi в терминале или щелкнув параметр «Мониторинг производительности» внизу, и соответствующим образом отрегулируйте размер пакета, чтобы предотвратить неожиданную приостановку из-за нехватки видеопамяти. .

# 定义模型训练验证评估函数
@paddle.no_grad()
def evaluate(model, criterion, metric, data_loader):
    model.eval()
    metric.reset()
    losses = []
    for batch in data_loader:
        input_ids, token_type_ids, labels = batch
        logits = model(input_ids, token_type_ids)
        loss = criterion(logits, labels)
        losses.append(loss.numpy())
        correct = metric.compute(logits, labels)
        metric.update(correct)
        accu = metric.accumulate()
    print("eval loss: %.8f, accu: %.8f" % (np.mean(losses), accu))  # 输出验证集上评估效果
    model.train()
    metric.reset()
    return  np.mean(losses), accu  # 返回准确率

5. Модельное обучение

# 固定随机种子便于结果的复现
# seed = 1024
seed = 512
random.seed(seed)
np.random.seed(seed)
paddle.seed(seed)
%cd ~
/home/aistudio

PS: Во время обучения модели вы можете проверить использование видеопамяти, введя команду nvidia-smi в терминале или через опцию мониторинга производительности в правом нижнем углу.Если видеопамяти недостаточно, вы должны соответствующим образом настроить значение batchsize.

# 模型训练:
import paddle.nn.functional as F
from visualdl import LogWriter

save_dir='./'
writer = LogWriter("./log")
tic_train = time.time()
global_step = 0
best_val_acc=0
tic_train = time.time()
accu=0

for epoch in range(1, epochs + 1):
    for step, batch in enumerate(train_data_loader, start=1):
        input_ids, segment_ids, labels = batch
        logits = model(input_ids, segment_ids)
        loss = criterion(logits, labels)
        probs = F.softmax(logits, axis=1)
        correct = metric.compute(probs, labels)
        metric.update(correct)
        acc = metric.accumulate()
        global_step+=1
        if global_step % 40 == 0:
            print(
                "global step %d, epoch: %d, batch: %d, loss: %.8f, accu: %.8f, speed: %.2f step/s"
                % (global_step, epoch, step, loss, acc,
                    40 / (time.time() - tic_train)))
            tic_train = time.time()

        loss.backward()
        optimizer.step()
        lr_scheduler.step()
        optimizer.clear_grad()
        
        # 大于100次再eval,或者再大一点eval,太小没有eval的意义
        if global_step % 200 == 0 and global_step>=3000:
            # 评估当前训练的模型
            eval_loss, eval_accu = evaluate(model, criterion, metric, dev_data_loader)
            print("eval  on dev  loss: {:.8}, accu: {:.8}".format(eval_loss, eval_accu))
            # 加入eval日志显示
            writer.add_scalar(tag="eval/loss", step=global_step, value=eval_loss)
            writer.add_scalar(tag="eval/acc", step=global_step, value=eval_accu)
            # 加入train日志显示
            writer.add_scalar(tag="train/loss", step=global_step, value=loss)
            writer.add_scalar(tag="train/acc", step=global_step, value=acc)
            save_dir = "best_checkpoint"
            # 加入保存       
            if eval_accu>best_val_acc:
                if not os.path.exists(save_dir):
                    os.mkdir(save_dir)
                best_val_acc=eval_accu
                print(f"模型保存在 {global_step} 步, 最佳eval准确度为{best_val_acc:.8f}!")
                save_param_path = os.path.join(save_dir, 'best_model.pdparams')
                paddle.save(model.state_dict(), save_param_path)
                fh = open('best_checkpoint/best_model.txt', 'w', encoding='utf-8')
                fh.write(f"模型保存在 {global_step} 步, 最佳eval准确度为{best_val_acc:.8f}!")
                fh.close()

tokenizer.save_pretrained(save_dir)
global step 40, epoch: 1, batch: 40, loss: 2.66168261, accu: 0.03826389, speed: 0.61 step/s
global step 80, epoch: 1, batch: 80, loss: 2.55217147, accu: 0.06201389, speed: 0.60 step/s
global step 120, epoch: 1, batch: 120, loss: 2.35012722, accu: 0.13446759, speed: 0.60 step/s
save_dir='./'
tokenizer.save_pretrained(save_dir)
# 测试最优模型参数在验证集上的分数
evaluate(model, criterion, metric, dev_data_loader)

5. Прогноз

перезапустить здесь илиkillall -9 pythonчтобы освободить кеш

1. Импорт различных библиотек классов

# 导入所需的第三方库
import math
import numpy as np
import os
import collections
from functools import partial
import random
import time
import inspect
import importlib
from tqdm import tqdm
import paddle
import paddle.nn as nn
import paddle.nn.functional as F
from paddle.io import IterableDataset
from paddle.utils.download import get_path_from_url
# 导入paddlenlp所需的相关包
import paddlenlp as ppnlp
from paddlenlp.data import JiebaTokenizer, Pad, Stack, Tuple, Vocab
from paddlenlp.datasets import MapDataset
from paddle.dataset.common import md5file
from paddlenlp.datasets import DatasetBuilder

2. Загрузите модель

# 此次使用在中文领域效果较优的roberta-wwm-ext-large模型,预训练模型一般“大力出奇迹”,选用大的预训练模型可以取得比base模型更优的效果
MODEL_NAME = "roberta-wwm-ext-large"
# 只需指定想要使用的模型名称和文本分类的类别数即可完成Fine-tune网络定义,通过在预训练模型后拼接上一个全连接网络(Full Connected)进行分类
model = ppnlp.transformers.RobertaForSequenceClassification.from_pretrained(MODEL_NAME, num_classes=14) # 此次分类任务为14分类任务,故num_classes设置为14
# 定义模型对应的tokenizer,tokenizer可以把原始输入文本转化成模型model可接受的输入数据格式。需注意tokenizer类要与选择的模型相对应,具体可以查看PaddleNLP相关文档
tokenizer = ppnlp.transformers.RobertaTokenizer.from_pretrained(MODEL_NAME)
# 加载在验证集上效果最优的一轮的模型参数
import os
import paddle

seed = 1024
random.seed(seed)
np.random.seed(seed)
paddle.seed(seed)

params_path = '88.73671/best_checkpoint/best_model.pdparams'
if params_path and os.path.isfile(params_path):
    # 加载模型参数
    state_dict = paddle.load(params_path)
    model.set_dict(state_dict)
    print("Loaded parameters from %s" % params_path)

3. Загрузите тестовый набор данных

# 读取要进行预测的测试集文件
import pandas as pd
test = pd.read_csv('~/data/data103654/test.txt', header=None, names=['text_a'])  
print(max(test['text_a'].str.len()))
label_list=['科技', '体育', '时政', '股票', '娱乐', '教育', '家居', '财经', '房产', '社会', '游戏', '彩票', '星座', '时尚']
print(label_list)
# 定义要进行分类的类别
id_label_dict={}
for i in range(0,len(label_list)):
    id_label_dict[i]=label_list[i]
print(id_label_dict)
!head -n5 ~/data/data103654/test.txt

5. Обработка данных

# 定义数据加载和处理函数
def convert_example(example, tokenizer, max_seq_length=48, is_test=False):
    qtconcat = example["text_a"]
    encoded_inputs = tokenizer(text=qtconcat, max_seq_len=max_seq_length)  # tokenizer处理为模型可接受的格式 
    input_ids = encoded_inputs["input_ids"]
    token_type_ids = encoded_inputs["token_type_ids"]

    if not is_test:
        label = np.array([example["label"]], dtype="int64")
        return input_ids, token_type_ids, label
    else:
        return input_ids, token_type_ids

# 定义模型预测函数
def predict(model, data, tokenizer, label_map, batch_size=1):
    examples = []
    # 将输入数据(list格式)处理为模型可接受的格式
    for text in data:
        input_ids, segment_ids = convert_example(
            text,
            tokenizer,
            max_seq_length=48,
            is_test=True)
        examples.append((input_ids, segment_ids))

    batchify_fn = lambda samples, fn=Tuple(
        Pad(axis=0, pad_val=tokenizer.pad_token_id),  # input id
        Pad(axis=0, pad_val=tokenizer.pad_token_id),  # segment id
    ): fn(samples)

    # Seperates data into some batches.
    batches = []
    one_batch = []
    for example in examples:
        one_batch.append(example)
        if len(one_batch) == batch_size:
            batches.append(one_batch)
            one_batch = []
    if one_batch:
        # The last batch whose size is less than the config batch_size setting.
        batches.append(one_batch)

    results = []
    model.eval()
    for batch in batches:
        input_ids, segment_ids = batchify_fn(batch)
        input_ids = paddle.to_tensor(input_ids)
        segment_ids = paddle.to_tensor(segment_ids)
        logits = model(input_ids, segment_ids)
        probs = F.softmax(logits, axis=1)
        idx = paddle.argmax(probs, axis=1).numpy()
        idx = idx.tolist()
        labels = [label_map[i] for i in idx]
        results.extend(labels)
    return results  # 返回预测结果
# 定义对数据的预处理函数,处理为模型输入指定list格式
def preprocess_prediction_data(data):
    examples = []
    for text_a in data:
        examples.append({"text_a": text_a})
    return examples

# 对测试集数据进行格式处理
data1 = list(test.text_a)
examples = preprocess_prediction_data(data1)

6. Начните прогнозировать

# 对测试集进行预测
results = predict(model, examples, tokenizer, id_label_dict, batch_size=128)   
print(results)
# 将list格式的预测结果存储为txt文件,提交格式要求:每行一个类别
def write_results(labels, file_path):
    with open(file_path, "w", encoding="utf8") as f:
        f.writelines("\n".join(labels))

write_results(results, "./result.txt")
# 因格式要求为zip,故需要将结果文件压缩为submission.zip提交文件
!zip 'submission.zip' 'result.txt'
!head result.txt

Следует отметить, что требуемый формат отправки — zip, а сгенерированный файл можно найти в основной директории.submission.zipЗагрузите файл локально и отправьте его на страницу конкурса!