chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
# Input-guided Aggressive Decoding
|
||||
Codes (originally from https://github.com/AutoTemp/Shallow-Aggressive-Decoding) for Input-guided Aggressive Decoding (IAD) that is originally proposed in the paper "Instantaneous Grammatical Error Correction with Shallow Aggressive Decoding" (ACL-IJCNLP 2021)
|
||||

|
||||
|
||||
## Results
|
||||
<table>
|
||||
<caption> The performance and online inference efficiency evaluation of baseline and our approach in CoNLL-14. </caption>
|
||||
<tr>
|
||||
<th> Model </th>
|
||||
<th> P </th>
|
||||
<th> R </th>
|
||||
<th> F<sub>0.5</sub> </th>
|
||||
<th> Speedup </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> Transformer-big (beam=5) </th>
|
||||
<th> 73.0 </th>
|
||||
<th> 38.1 </th>
|
||||
<th> 61.6 </th>
|
||||
<th> 1.0x </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> Our approach (9+3) </th>
|
||||
<th> 73.3 </th>
|
||||
<th> 41.3 </th>
|
||||
<th> 63.5 </th>
|
||||
<th> 10.3x </th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th> Our approach (12+2 BART-Init) </th>
|
||||
<th> 71.0 </th>
|
||||
<th> 52.8 </th>
|
||||
<th> 66.4 </th>
|
||||
<th> 9.6x </th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<caption> For reference, the beam=1 and beam=5 results of the state-of-the-art 12+2 (BART-Init) are: </caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>12+2 BART-Init</th>
|
||||
<th colspan="3">CoNLL-14</th>
|
||||
<th colspan="3">BEA-19</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Beam</th>
|
||||
<th>P</th>
|
||||
<th>R</th>
|
||||
<th>F<sub>0.5</sub></th>
|
||||
<th>P</th>
|
||||
<th>R</th>
|
||||
<th>F<sub>0.5</sub></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>1</td>
|
||||
<th>71.0</th>
|
||||
<th>52.8</th>
|
||||
<th>66.4</th>
|
||||
<th>74.7</th>
|
||||
<th>66.4</th>
|
||||
<th>72.9</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>5</th>
|
||||
<th>71.4</td>
|
||||
<th>52.8</td>
|
||||
<th>66.7</td>
|
||||
<th>75.8</td>
|
||||
<th>66.3</td>
|
||||
<th>73.7</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
The above models are all single models without ensemble.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
conda create -n IAD python=3.6
|
||||
conda activate IAD
|
||||
conda install pytorch==1.5.1 torchvision==0.6.1 cudatoolkit=10.2 -c pytorch
|
||||
cd fairseq
|
||||
pip install --editable .
|
||||
```
|
||||
|
||||
## Usage
|
||||
This section explains how to decode in different ways.
|
||||
```
|
||||
PTPATH=/to/path/checkpoint*.pt # path to model file
|
||||
BINDIR=/to/path/bin_data # directory containing src and tgt dictionaries
|
||||
INPPATH=/to/path/conll*.bpe.txt # path to eval file
|
||||
OUTPATH=/to/path/conll*.out.txt # path to output file
|
||||
BATCH=xxx
|
||||
BEAM=xxx
|
||||
```
|
||||
|
||||
## Directly use fairseq's interactive.py to decode:
|
||||
|
||||
```
|
||||
bash interactive.sh $PTPATH $BATCH $BEAM $INPPATH $BINDIR $OUTPATH
|
||||
```
|
||||
|
||||
## use Input-guided Aggressive Decoding:
|
||||
|
||||
```
|
||||
python inference.py --checkpoint-path $PTPATH --bin-data $BINDIR --input-path $INPPATH --output-path $OUTPATH --aggressive
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
## 👉 [Please follow one of these issue templates](https://github.com/pytorch/fairseq/issues/new/choose) 👈
|
||||
|
||||
Note: to keep the backlog clean and actionable, issues may be immediately closed if they do not follow one of the above issue templates.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: 🐛 Bug Report
|
||||
about: Submit a bug report to help us improve
|
||||
labels: 'bug, needs triage'
|
||||
---
|
||||
|
||||
## 🐛 Bug
|
||||
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
### To Reproduce
|
||||
|
||||
Steps to reproduce the behavior (**always include the command you ran**):
|
||||
|
||||
1. Run cmd '....'
|
||||
2. See error
|
||||
|
||||
<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->
|
||||
|
||||
|
||||
#### Code sample
|
||||
<!-- Ideally attach a minimal code sample to reproduce the decried issue.
|
||||
Minimal means having the shortest code but still preserving the bug. -->
|
||||
|
||||
### Expected behavior
|
||||
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
### Environment
|
||||
|
||||
- fairseq Version (e.g., 1.0 or master):
|
||||
- PyTorch Version (e.g., 1.0)
|
||||
- OS (e.g., Linux):
|
||||
- How you installed fairseq (`pip`, source):
|
||||
- Build command you used (if compiling from source):
|
||||
- Python version:
|
||||
- CUDA/cuDNN version:
|
||||
- GPU models and configuration:
|
||||
- Any other relevant information:
|
||||
|
||||
### Additional context
|
||||
|
||||
<!-- Add any other context about the problem here. -->
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: 📚 Documentation/Typos
|
||||
about: Report an issue related to documentation or a typo
|
||||
labels: 'documentation, needs triage'
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
For typos and doc fixes, please go ahead and:
|
||||
|
||||
1. Create an issue.
|
||||
2. Fix the typo.
|
||||
3. Submit a PR.
|
||||
|
||||
Thanks!
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: 🚀 Feature Request
|
||||
about: Submit a proposal/request for a new feature
|
||||
labels: 'enhancement, help wanted, needs triage'
|
||||
---
|
||||
|
||||
## 🚀 Feature Request
|
||||
<!-- A clear and concise description of the feature proposal -->
|
||||
|
||||
### Motivation
|
||||
|
||||
<!-- Please outline the motivation for the proposal. Is your feature request related to a problem? e.g., I'm always frustrated when [...]. If this is related to another GitHub issue, please link here too -->
|
||||
|
||||
### Pitch
|
||||
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
### Alternatives
|
||||
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered, if any. -->
|
||||
|
||||
### Additional context
|
||||
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: ❓ Questions/Help
|
||||
about: If you have questions, please first search existing issues and docs
|
||||
labels: 'question, needs triage'
|
||||
---
|
||||
|
||||
## ❓ Questions and Help
|
||||
|
||||
### Before asking:
|
||||
1. search the issues.
|
||||
2. search the docs.
|
||||
|
||||
<!-- If you still can't find what you need: -->
|
||||
|
||||
#### What is your question?
|
||||
|
||||
#### Code
|
||||
|
||||
<!-- Please paste a code snippet if your question requires it! -->
|
||||
|
||||
#### What have you tried?
|
||||
|
||||
#### What's your environment?
|
||||
|
||||
- fairseq Version (e.g., 1.0 or master):
|
||||
- PyTorch Version (e.g., 1.0)
|
||||
- OS (e.g., Linux):
|
||||
- How you installed fairseq (`pip`, source):
|
||||
- Build command you used (if compiling from source):
|
||||
- Python version:
|
||||
- CUDA/cuDNN version:
|
||||
- GPU models and configuration:
|
||||
- Any other relevant information:
|
||||
@@ -0,0 +1,16 @@
|
||||
# Before submitting
|
||||
|
||||
- [ ] Was this discussed/approved via a Github issue? (no need for typos, doc improvements)
|
||||
- [ ] Did you read the [contributor guideline](https://github.com/pytorch/fairseq/blob/master/CONTRIBUTING.md)?
|
||||
- [ ] Did you make sure to update the docs?
|
||||
- [ ] Did you write any new necessary tests?
|
||||
|
||||
## What does this PR do?
|
||||
Fixes # (issue).
|
||||
|
||||
## PR review
|
||||
Anyone in the community is free to review the PR once the tests have passed.
|
||||
If we didn't discuss your PR in Github issues there's a high chance it will not be merged.
|
||||
|
||||
## Did you have fun?
|
||||
Make sure you had fun coding 🙃
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
# Mostly copied from github.com/facebook/react/blob/master/.github/stale.yml
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 90
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- bug
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
issues:
|
||||
# Comment to post when marking an issue as stale.
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale.
|
||||
**If this issue is still affecting you, please leave any comment** (for example, "bump"), and we'll keep it open.
|
||||
We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
|
||||
# Comment to post when closing a stale issue.
|
||||
closeComment: >
|
||||
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!
|
||||
pulls:
|
||||
# Comment to post when marking a pull request as stale.
|
||||
markComment: >
|
||||
This pull request has been automatically marked as stale.
|
||||
**If this pull request is still relevant, please leave any comment** (for example, "bump"), and we'll keep it open.
|
||||
We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated.
|
||||
# Comment to post when closing a stale pull request.
|
||||
closeComment: >
|
||||
Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you!
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
# Trigger the workflow on push to master or any pull request
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest]
|
||||
python-version: [3.6, 3.7]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Conditionally install pytorch
|
||||
if: matrix.platform == 'windows-latest'
|
||||
run: pip3 install torch -f https://download.pytorch.org/whl/torch_stable.html
|
||||
|
||||
- name: Install locally
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
git submodule update --init --recursive
|
||||
python setup.py build_ext --inplace
|
||||
python -m pip install --editable .
|
||||
|
||||
- name: Install optional test requirements
|
||||
run: |
|
||||
python -m pip install fairscale iopath transformers
|
||||
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
pip install flake8
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --extend-exclude fairseq/model_parallel/megatron
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --extend-exclude fairseq/model_parallel/megatron
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
python setup.py test
|
||||
@@ -0,0 +1,41 @@
|
||||
name: build_wheels
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- v[0-9]+.[0-9]+.[x0-9]+
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build_wheels:
|
||||
name: Build wheels on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
|
||||
- name: Install cibuildwheel
|
||||
run: |
|
||||
python -m pip install cibuildwheel
|
||||
|
||||
- name: Build wheels for CPython
|
||||
run: |
|
||||
python -m cibuildwheel --output-dir dist
|
||||
env:
|
||||
CIBW_BUILD: "cp36-*64 cp37-*64 cp38-*64"
|
||||
CIBW_MANYLINUX_X86_64_IMAGE: manylinux1
|
||||
CIBW_BEFORE_BUILD: git submodule update --init --recursive && pip install .
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wheels
|
||||
path: ./dist/*.whl
|
||||
@@ -0,0 +1,136 @@
|
||||
# JetBrains PyCharm IDE
|
||||
.idea/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# macOS dir files
|
||||
.DS_Store
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Checkpoints
|
||||
checkpoints
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
|
||||
# Generated files
|
||||
/fairseq/temporal_convolution_tbc
|
||||
/fairseq/modules/*_layer/*_forward.cu
|
||||
/fairseq/modules/*_layer/*_backward.cu
|
||||
/fairseq/version.py
|
||||
|
||||
# data
|
||||
data-bin/
|
||||
|
||||
# reranking
|
||||
/examples/reranking/rerank_data
|
||||
|
||||
# Cython-generated C++ source files
|
||||
/fairseq/data/data_utils_fast.cpp
|
||||
/fairseq/data/token_block_utils_fast.cpp
|
||||
|
||||
# VSCODE
|
||||
.vscode/ftp-sync.json
|
||||
.vscode/settings.json
|
||||
|
||||
# Experimental Folder
|
||||
experimental/*
|
||||
|
||||
# Weights and Biases logs
|
||||
wandb/
|
||||
@@ -0,0 +1,4 @@
|
||||
[submodule "fairseq/model_parallel/megatron"]
|
||||
path = fairseq/model_parallel/megatron
|
||||
url = https://github.com/ngoyal2707/Megatron-LM
|
||||
branch = fairseq
|
||||
@@ -0,0 +1,77 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at <conduct@pytorch.org>. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Contributing to Facebook AI Research Sequence-to-Sequence Toolkit (fairseq)
|
||||
We want to make contributing to this project as easy and transparent as
|
||||
possible.
|
||||
|
||||
## Pull Requests
|
||||
We actively welcome your pull requests.
|
||||
|
||||
1. Fork the repo and create your branch from `master`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've changed APIs, update the documentation.
|
||||
4. Ensure the test suite passes.
|
||||
5. Make sure your code lints.
|
||||
6. If you haven't already, complete the Contributor License Agreement ("CLA").
|
||||
|
||||
## Contributor License Agreement ("CLA")
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need
|
||||
to do this once to work on any of Facebook's open source projects.
|
||||
|
||||
Complete your CLA here: <https://code.facebook.com/cla>
|
||||
|
||||
## Issues
|
||||
We use GitHub issues to track public bugs. Please ensure your description is
|
||||
clear and has sufficient instructions to be able to reproduce the issue.
|
||||
|
||||
## License
|
||||
By contributing to Facebook AI Research Sequence-to-Sequence Toolkit (fairseq),
|
||||
you agree that your contributions will be licensed under the LICENSE file in
|
||||
the root directory of this source tree.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Facebook, Inc. and its affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,5 @@
|
||||
The repo is based on [fairseq@1164a7fc432a188d401895018eaa85175fb06f9d](https://github.com/pytorch/fairseq/tree/1164a7fc432a188d401895018eaa85175fb06f9d).
|
||||
|
||||
The original README.md is renamed to README_FAIRSEQ.md.
|
||||
|
||||
See the [README_FAIRSEQ.md](https://github.com/AutoTemp/Shallow-Aggressive-Decoding/blob/main/fairseq/README_FAIRSEQ.md) or [fairseq](https://github.com/pytorch/fairseq) for requirements and installation.
|
||||
@@ -0,0 +1,216 @@
|
||||
<p align="center">
|
||||
<img src="docs/fairseq_logo.png" width="150">
|
||||
<br />
|
||||
<br />
|
||||
<a href="https://github.com/pytorch/fairseq/blob/master/LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue.svg" /></a>
|
||||
<a href="https://github.com/pytorch/fairseq/releases"><img alt="Latest Release" src="https://img.shields.io/github/release/pytorch/fairseq.svg" /></a>
|
||||
<a href="https://github.com/pytorch/fairseq/actions?query=workflow:build"><img alt="Build Status" src="https://github.com/pytorch/fairseq/workflows/build/badge.svg" /></a>
|
||||
<a href="https://fairseq.readthedocs.io/en/latest/?badge=latest"><img alt="Documentation Status" src="https://readthedocs.org/projects/fairseq/badge/?version=latest" /></a>
|
||||
</p>
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Fairseq(-py) is a sequence modeling toolkit that allows researchers and
|
||||
developers to train custom models for translation, summarization, language
|
||||
modeling and other text generation tasks.
|
||||
|
||||
We provide reference implementations of various sequence modeling papers:
|
||||
|
||||
<details><summary>List of implemented papers</summary><p>
|
||||
|
||||
* **Convolutional Neural Networks (CNN)**
|
||||
+ [Language Modeling with Gated Convolutional Networks (Dauphin et al., 2017)](examples/language_model/conv_lm/README.md)
|
||||
+ [Convolutional Sequence to Sequence Learning (Gehring et al., 2017)](examples/conv_seq2seq/README.md)
|
||||
+ [Classical Structured Prediction Losses for Sequence to Sequence Learning (Edunov et al., 2018)](https://github.com/pytorch/fairseq/tree/classic_seqlevel)
|
||||
+ [Hierarchical Neural Story Generation (Fan et al., 2018)](examples/stories/README.md)
|
||||
+ [wav2vec: Unsupervised Pre-training for Speech Recognition (Schneider et al., 2019)](examples/wav2vec/README.md)
|
||||
* **LightConv and DynamicConv models**
|
||||
+ [Pay Less Attention with Lightweight and Dynamic Convolutions (Wu et al., 2019)](examples/pay_less_attention_paper/README.md)
|
||||
* **Long Short-Term Memory (LSTM) networks**
|
||||
+ Effective Approaches to Attention-based Neural Machine Translation (Luong et al., 2015)
|
||||
* **Transformer (self-attention) networks**
|
||||
+ Attention Is All You Need (Vaswani et al., 2017)
|
||||
+ [Scaling Neural Machine Translation (Ott et al., 2018)](examples/scaling_nmt/README.md)
|
||||
+ [Understanding Back-Translation at Scale (Edunov et al., 2018)](examples/backtranslation/README.md)
|
||||
+ [Adaptive Input Representations for Neural Language Modeling (Baevski and Auli, 2018)](examples/language_model/README.adaptive_inputs.md)
|
||||
+ [Lexically constrained decoding with dynamic beam allocation (Post & Vilar, 2018)](examples/constrained_decoding/README.md)
|
||||
+ [Transformer-XL: Attentive Language Models Beyond a Fixed-Length Context (Dai et al., 2019)](examples/truncated_bptt/README.md)
|
||||
+ [Adaptive Attention Span in Transformers (Sukhbaatar et al., 2019)](examples/adaptive_span/README.md)
|
||||
+ [Mixture Models for Diverse Machine Translation: Tricks of the Trade (Shen et al., 2019)](examples/translation_moe/README.md)
|
||||
+ [RoBERTa: A Robustly Optimized BERT Pretraining Approach (Liu et al., 2019)](examples/roberta/README.md)
|
||||
+ [Facebook FAIR's WMT19 News Translation Task Submission (Ng et al., 2019)](examples/wmt19/README.md)
|
||||
+ [Jointly Learning to Align and Translate with Transformer Models (Garg et al., 2019)](examples/joint_alignment_translation/README.md )
|
||||
+ [Multilingual Denoising Pre-training for Neural Machine Translation (Liu et at., 2020)](examples/mbart/README.md)
|
||||
+ [Neural Machine Translation with Byte-Level Subwords (Wang et al., 2020)](examples/byte_level_bpe/README.md)
|
||||
+ [Unsupervised Quality Estimation for Neural Machine Translation (Fomicheva et al., 2020)](examples/unsupervised_quality_estimation/README.md)
|
||||
+ [wav2vec 2.0: A Framework for Self-Supervised Learning of Speech Representations (Baevski et al., 2020)](examples/wav2vec/README.md)
|
||||
+ [Generating Medical Reports from Patient-Doctor Conversations Using Sequence-to-Sequence Models (Enarvi et al., 2020)](examples/pointer_generator/README.md)
|
||||
+ [Linformer: Self-Attention with Linear Complexity (Wang et al., 2020)](examples/linformer/README.md)
|
||||
+ [Cross-lingual Retrieval for Iterative Self-Supervised Training (Tran et al., 2020)](examples/criss/README.md)
|
||||
+ [Deep Transformers with Latent Depth (Li et al., 2020)](examples/latent_depth/README.md)
|
||||
* **Non-autoregressive Transformers**
|
||||
+ Non-Autoregressive Neural Machine Translation (Gu et al., 2017)
|
||||
+ Deterministic Non-Autoregressive Neural Sequence Modeling by Iterative Refinement (Lee et al. 2018)
|
||||
+ Insertion Transformer: Flexible Sequence Generation via Insertion Operations (Stern et al. 2019)
|
||||
+ Mask-Predict: Parallel Decoding of Conditional Masked Language Models (Ghazvininejad et al., 2019)
|
||||
+ [Levenshtein Transformer (Gu et al., 2019)](examples/nonautoregressive_translation/README.md)
|
||||
* **Finetuning**
|
||||
+ [Better Fine-Tuning by Reducing Representational Collapse (Aghajanyan et al. 2020)](examples/rxf/README.md)
|
||||
|
||||
</p></details>
|
||||
|
||||
### What's New:
|
||||
|
||||
* December 2020: [GottBERT model and code released](examples/gottbert/README.md)
|
||||
* November 2020: Adopted the [Hydra](https://github.com/facebookresearch/hydra) configuration framework
|
||||
* [see documentation explaining how to use it for new and existing projects](docs/hydra_integration.md)
|
||||
* November 2020: [fairseq 0.10.0 released](https://github.com/pytorch/fairseq/releases/tag/v0.10.0)
|
||||
* October 2020: [Added R3F/R4F (Better Fine-Tuning) code](examples/rxf/README.md)
|
||||
* October 2020: [Deep Transformer with Latent Depth code released](examples/latent_depth/README.md)
|
||||
* October 2020: [Added CRISS models and code](examples/criss/README.md)
|
||||
* September 2020: [Added Linformer code](examples/linformer/README.md)
|
||||
* September 2020: [Added pointer-generator networks](examples/pointer_generator/README.md)
|
||||
* August 2020: [Added lexically constrained decoding](examples/constrained_decoding/README.md)
|
||||
* August 2020: [wav2vec2 models and code released](examples/wav2vec/README.md)
|
||||
* July 2020: [Unsupervised Quality Estimation code released](examples/unsupervised_quality_estimation/README.md)
|
||||
|
||||
<details><summary>Previous updates</summary><p>
|
||||
|
||||
* May 2020: [Follow fairseq on Twitter](https://twitter.com/fairseq)
|
||||
* April 2020: [Monotonic Multihead Attention code released](examples/simultaneous_translation/README.md)
|
||||
* April 2020: [Quant-Noise code released](examples/quant_noise/README.md)
|
||||
* April 2020: [Initial model parallel support and 11B parameters unidirectional LM released](examples/megatron_11b/README.md)
|
||||
* March 2020: [Byte-level BPE code released](examples/byte_level_bpe/README.md)
|
||||
* February 2020: [mBART model and code released](examples/mbart/README.md)
|
||||
* February 2020: [Added tutorial for back-translation](https://github.com/pytorch/fairseq/tree/master/examples/backtranslation#training-your-own-model-wmt18-english-german)
|
||||
* December 2019: [fairseq 0.9.0 released](https://github.com/pytorch/fairseq/releases/tag/v0.9.0)
|
||||
* November 2019: [VizSeq released (a visual analysis toolkit for evaluating fairseq models)](https://facebookresearch.github.io/vizseq/docs/getting_started/fairseq_example)
|
||||
* November 2019: [CamemBERT model and code released](examples/camembert/README.md)
|
||||
* November 2019: [BART model and code released](examples/bart/README.md)
|
||||
* November 2019: [XLM-R models and code released](examples/xlmr/README.md)
|
||||
* September 2019: [Nonautoregressive translation code released](examples/nonautoregressive_translation/README.md)
|
||||
* August 2019: [WMT'19 models released](examples/wmt19/README.md)
|
||||
* July 2019: fairseq relicensed under MIT license
|
||||
* July 2019: [RoBERTa models and code released](examples/roberta/README.md)
|
||||
* June 2019: [wav2vec models and code released](examples/wav2vec/README.md)
|
||||
|
||||
</p></details>
|
||||
|
||||
### Features:
|
||||
|
||||
* multi-GPU training on one machine or across multiple machines (data and model parallel)
|
||||
* fast generation on both CPU and GPU with multiple search algorithms implemented:
|
||||
+ beam search
|
||||
+ Diverse Beam Search ([Vijayakumar et al., 2016](https://arxiv.org/abs/1610.02424))
|
||||
+ sampling (unconstrained, top-k and top-p/nucleus)
|
||||
+ [lexically constrained decoding](examples/constrained_decoding/README.md) (Post & Vilar, 2018)
|
||||
* [gradient accumulation](https://fairseq.readthedocs.io/en/latest/getting_started.html#large-mini-batch-training-with-delayed-updates) enables training with large mini-batches even on a single GPU
|
||||
* [mixed precision training](https://fairseq.readthedocs.io/en/latest/getting_started.html#training-with-half-precision-floating-point-fp16) (trains faster with less GPU memory on [NVIDIA tensor cores](https://developer.nvidia.com/tensor-cores))
|
||||
* [extensible](https://fairseq.readthedocs.io/en/latest/overview.html): easily register new models, criterions, tasks, optimizers and learning rate schedulers
|
||||
* [flexible configuration](docs/hydra_integration.md) based on [Hydra](https://github.com/facebookresearch/hydra) allowing a combination of code, command-line and file based configuration
|
||||
|
||||
We also provide [pre-trained models for translation and language modeling](#pre-trained-models-and-examples)
|
||||
with a convenient `torch.hub` interface:
|
||||
|
||||
``` python
|
||||
en2de = torch.hub.load('pytorch/fairseq', 'transformer.wmt19.en-de.single_model')
|
||||
en2de.translate('Hello world', beam=5)
|
||||
# 'Hallo Welt'
|
||||
```
|
||||
|
||||
See the PyTorch Hub tutorials for [translation](https://pytorch.org/hub/pytorch_fairseq_translation/)
|
||||
and [RoBERTa](https://pytorch.org/hub/pytorch_fairseq_roberta/) for more examples.
|
||||
|
||||
# Requirements and Installation
|
||||
|
||||
* [PyTorch](http://pytorch.org/) version >= 1.5.0
|
||||
* Python version >= 3.6
|
||||
* For training new models, you'll also need an NVIDIA GPU and [NCCL](https://github.com/NVIDIA/nccl)
|
||||
* **To install fairseq** and develop locally:
|
||||
|
||||
``` bash
|
||||
git clone https://github.com/pytorch/fairseq
|
||||
cd fairseq
|
||||
pip install --editable ./
|
||||
|
||||
# on MacOS:
|
||||
# CFLAGS="-stdlib=libc++" pip install --editable ./
|
||||
|
||||
# to install the latest stable release (0.10.1)
|
||||
# pip install fairseq==0.10.1
|
||||
```
|
||||
|
||||
* **For faster training** install NVIDIA's [apex](https://github.com/NVIDIA/apex) library:
|
||||
|
||||
``` bash
|
||||
git clone https://github.com/NVIDIA/apex
|
||||
cd apex
|
||||
pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" \
|
||||
--global-option="--deprecated_fused_adam" --global-option="--xentropy" \
|
||||
--global-option="--fast_multihead_attn" ./
|
||||
```
|
||||
|
||||
* **For large datasets** install [PyArrow](https://arrow.apache.org/docs/python/install.html#using-pip): `pip install pyarrow`
|
||||
* If you use Docker make sure to increase the shared memory size either with `--ipc=host` or `--shm-size`
|
||||
as command line options to `nvidia-docker run` .
|
||||
|
||||
# Getting Started
|
||||
|
||||
The [full documentation](https://fairseq.readthedocs.io/) contains instructions
|
||||
for getting started, training new models and extending fairseq with new model
|
||||
types and tasks.
|
||||
|
||||
# Pre-trained models and examples
|
||||
|
||||
We provide pre-trained models and pre-processed, binarized test sets for several tasks listed below,
|
||||
as well as example training and evaluation commands.
|
||||
|
||||
* [Translation](examples/translation/README.md): convolutional and transformer models are available
|
||||
* [Language Modeling](examples/language_model/README.md): convolutional and transformer models are available
|
||||
|
||||
We also have more detailed READMEs to reproduce results from specific papers:
|
||||
|
||||
* [Cross-lingual Retrieval for Iterative Self-Supervised Training (Tran et al., 2020)](examples/criss/README.md)
|
||||
* [wav2vec 2.0: A Framework for Self-Supervised Learning of Speech Representations (Baevski et al., 2020)](examples/wav2vec/README.md)
|
||||
* [Unsupervised Quality Estimation for Neural Machine Translation (Fomicheva et al., 2020)](examples/unsupervised_quality_estimation/README.md)
|
||||
* [Training with Quantization Noise for Extreme Model Compression ({Fan*, Stock*} et al., 2020)](examples/quant_noise/README.md)
|
||||
* [Neural Machine Translation with Byte-Level Subwords (Wang et al., 2020)](examples/byte_level_bpe/README.md)
|
||||
* [Multilingual Denoising Pre-training for Neural Machine Translation (Liu et at., 2020)](examples/mbart/README.md)
|
||||
* [Reducing Transformer Depth on Demand with Structured Dropout (Fan et al., 2019)](examples/layerdrop/README.md)
|
||||
* [Jointly Learning to Align and Translate with Transformer Models (Garg et al., 2019)](examples/joint_alignment_translation/README.md)
|
||||
* [Levenshtein Transformer (Gu et al., 2019)](examples/nonautoregressive_translation/README.md)
|
||||
* [Facebook FAIR's WMT19 News Translation Task Submission (Ng et al., 2019)](examples/wmt19/README.md)
|
||||
* [RoBERTa: A Robustly Optimized BERT Pretraining Approach (Liu et al., 2019)](examples/roberta/README.md)
|
||||
* [wav2vec: Unsupervised Pre-training for Speech Recognition (Schneider et al., 2019)](examples/wav2vec/README.md)
|
||||
* [Mixture Models for Diverse Machine Translation: Tricks of the Trade (Shen et al., 2019)](examples/translation_moe/README.md)
|
||||
* [Pay Less Attention with Lightweight and Dynamic Convolutions (Wu et al., 2019)](examples/pay_less_attention_paper/README.md)
|
||||
* [Understanding Back-Translation at Scale (Edunov et al., 2018)](examples/backtranslation/README.md)
|
||||
* [Classical Structured Prediction Losses for Sequence to Sequence Learning (Edunov et al., 2018)](https://github.com/pytorch/fairseq/tree/classic_seqlevel)
|
||||
* [Hierarchical Neural Story Generation (Fan et al., 2018)](examples/stories/README.md)
|
||||
* [Scaling Neural Machine Translation (Ott et al., 2018)](examples/scaling_nmt/README.md)
|
||||
* [Convolutional Sequence to Sequence Learning (Gehring et al., 2017)](examples/conv_seq2seq/README.md)
|
||||
* [Language Modeling with Gated Convolutional Networks (Dauphin et al., 2017)](examples/language_model/README.conv.md)
|
||||
|
||||
# Join the fairseq community
|
||||
|
||||
* Twitter: https://twitter.com/fairseq
|
||||
* Facebook page: https://www.facebook.com/groups/fairseq.users
|
||||
* Google group: https://groups.google.com/forum/#!forum/fairseq-users
|
||||
|
||||
# License
|
||||
|
||||
fairseq(-py) is MIT-licensed.
|
||||
The license applies to the pre-trained models as well.
|
||||
|
||||
# Citation
|
||||
|
||||
Please cite as:
|
||||
|
||||
``` bibtex
|
||||
@inproceedings{ott2019fairseq,
|
||||
title = {fairseq: A Fast, Extensible Toolkit for Sequence Modeling},
|
||||
author = {Myle Ott and Sergey Edunov and Alexei Baevski and Angela Fan and Sam Gross and Nathan Ng and David Grangier and Michael Auli},
|
||||
booktitle = {Proceedings of NAACL-HLT 2019: Demonstrations},
|
||||
year = {2019},
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = python -msphinx
|
||||
SPHINXPROJ = fairseq
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@@ -0,0 +1,9 @@
|
||||
.wy-table-responsive table td kbd {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.wy-table-responsive table td {
|
||||
white-space: normal !important;
|
||||
}
|
||||
.wy-table-responsive {
|
||||
overflow: visible !important;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
.. _Command-line Tools:
|
||||
|
||||
Command-line Tools
|
||||
==================
|
||||
|
||||
Fairseq provides several command-line tools for training and evaluating models:
|
||||
|
||||
- :ref:`fairseq-preprocess`: Data pre-processing: build vocabularies and binarize training data
|
||||
- :ref:`fairseq-train`: Train a new model on one or multiple GPUs
|
||||
- :ref:`fairseq-generate`: Translate pre-processed data with a trained model
|
||||
- :ref:`fairseq-interactive`: Translate raw text with a trained model
|
||||
- :ref:`fairseq-score`: BLEU scoring of generated translations against reference translations
|
||||
- :ref:`fairseq-eval-lm`: Language model evaluation
|
||||
|
||||
|
||||
.. _fairseq-preprocess:
|
||||
|
||||
fairseq-preprocess
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
.. automodule:: fairseq_cli.preprocess
|
||||
|
||||
.. argparse::
|
||||
:module: fairseq.options
|
||||
:func: get_preprocessing_parser
|
||||
:prog: fairseq-preprocess
|
||||
|
||||
|
||||
.. _fairseq-train:
|
||||
|
||||
fairseq-train
|
||||
~~~~~~~~~~~~~
|
||||
.. automodule:: fairseq_cli.train
|
||||
|
||||
.. argparse::
|
||||
:module: fairseq.options
|
||||
:func: get_training_parser
|
||||
:prog: fairseq-train
|
||||
|
||||
|
||||
.. _fairseq-generate:
|
||||
|
||||
fairseq-generate
|
||||
~~~~~~~~~~~~~~~~
|
||||
.. automodule:: fairseq_cli.generate
|
||||
|
||||
.. argparse::
|
||||
:module: fairseq.options
|
||||
:func: get_generation_parser
|
||||
:prog: fairseq-generate
|
||||
|
||||
|
||||
.. _fairseq-interactive:
|
||||
|
||||
fairseq-interactive
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
.. automodule:: fairseq_cli.interactive
|
||||
|
||||
.. argparse::
|
||||
:module: fairseq.options
|
||||
:func: get_interactive_generation_parser
|
||||
:prog: fairseq-interactive
|
||||
|
||||
|
||||
.. _fairseq-score:
|
||||
|
||||
fairseq-score
|
||||
~~~~~~~~~~~~~
|
||||
.. automodule:: fairseq_cli.score
|
||||
|
||||
.. argparse::
|
||||
:module: fairseq_cli.score
|
||||
:func: get_parser
|
||||
:prog: fairseq-score
|
||||
|
||||
|
||||
.. _fairseq-eval-lm:
|
||||
|
||||
fairseq-eval-lm
|
||||
~~~~~~~~~~~~~~~
|
||||
.. automodule:: fairseq_cli.eval_lm
|
||||
|
||||
.. argparse::
|
||||
:module: fairseq.options
|
||||
:func: get_eval_lm_parser
|
||||
:prog: fairseq-eval-lm
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# fairseq documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Aug 17 21:45:30 2018.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from fairseq import __version__
|
||||
|
||||
|
||||
# source code directory, relative to this file, for sphinx-autobuild
|
||||
sys.path.insert(0, os.path.abspath(".."))
|
||||
|
||||
source_suffix = [".rst"]
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinxarg.ext",
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = "fairseq"
|
||||
copyright = "Facebook AI Research (FAIR)"
|
||||
author = "Facebook AI Research (FAIR)"
|
||||
|
||||
github_doc_root = "https://github.com/pytorch/fairseq/tree/master/docs/"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = __version__
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = __version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = "sphinx"
|
||||
highlight_language = "python"
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ["_static"]
|
||||
|
||||
html_context = {
|
||||
"css_files": [
|
||||
"_static/theme_overrides.css", # override wide tables in RTD theme
|
||||
],
|
||||
}
|
||||
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# This is required for the alabaster theme
|
||||
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
|
||||
# html_sidebars = {
|
||||
# '**': [
|
||||
# 'about.html',
|
||||
# 'navigation.html',
|
||||
# 'relations.html', # needs 'show_related': True theme option to display
|
||||
# 'searchbox.html',
|
||||
# 'donate.html',
|
||||
# ]
|
||||
# }
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {
|
||||
"numpy": ("http://docs.scipy.org/doc/numpy/", None),
|
||||
"python": ("https://docs.python.org/", None),
|
||||
"torch": ("https://pytorch.org/docs/master/", None),
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
.. role:: hidden
|
||||
:class: hidden-section
|
||||
|
||||
.. _Criterions:
|
||||
|
||||
Criterions
|
||||
==========
|
||||
|
||||
Criterions compute the loss function given the model and batch, roughly::
|
||||
|
||||
loss = criterion(model, batch)
|
||||
|
||||
.. automodule:: fairseq.criterions
|
||||
:members:
|
||||
|
||||
.. autoclass:: fairseq.criterions.FairseqCriterion
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. autoclass:: fairseq.criterions.adaptive_loss.AdaptiveLoss
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.criterions.composite_loss.CompositeLoss
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.criterions.cross_entropy.CrossEntropyCriterion
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.criterions.label_smoothed_cross_entropy.LabelSmoothedCrossEntropyCriterion
|
||||
:members:
|
||||
:undoc-members:
|
||||
@@ -0,0 +1,58 @@
|
||||
.. role:: hidden
|
||||
:class: hidden-section
|
||||
|
||||
.. module:: fairseq.data
|
||||
|
||||
Data Loading and Utilities
|
||||
==========================
|
||||
|
||||
.. _datasets:
|
||||
|
||||
Datasets
|
||||
--------
|
||||
|
||||
**Datasets** define the data format and provide helpers for creating
|
||||
mini-batches.
|
||||
|
||||
.. autoclass:: fairseq.data.FairseqDataset
|
||||
:members:
|
||||
.. autoclass:: fairseq.data.LanguagePairDataset
|
||||
:members:
|
||||
.. autoclass:: fairseq.data.MonolingualDataset
|
||||
:members:
|
||||
|
||||
**Helper Datasets**
|
||||
|
||||
These datasets wrap other :class:`fairseq.data.FairseqDataset` instances and
|
||||
provide additional functionality:
|
||||
|
||||
.. autoclass:: fairseq.data.BacktranslationDataset
|
||||
:members:
|
||||
.. autoclass:: fairseq.data.ConcatDataset
|
||||
:members:
|
||||
.. autoclass:: fairseq.data.ResamplingDataset
|
||||
:members:
|
||||
.. autoclass:: fairseq.data.RoundRobinZipDatasets
|
||||
:members:
|
||||
.. autoclass:: fairseq.data.TransformEosDataset
|
||||
:members:
|
||||
|
||||
|
||||
Dictionary
|
||||
----------
|
||||
|
||||
.. autoclass:: fairseq.data.Dictionary
|
||||
:members:
|
||||
|
||||
|
||||
Iterators
|
||||
---------
|
||||
|
||||
.. autoclass:: fairseq.data.CountingIterator
|
||||
:members:
|
||||
.. autoclass:: fairseq.data.EpochBatchIterator
|
||||
:members:
|
||||
.. autoclass:: fairseq.data.GroupedIterator
|
||||
:members:
|
||||
.. autoclass:: fairseq.data.ShardedIterator
|
||||
:members:
|
||||
@@ -0,0 +1,2 @@
|
||||
[writers]
|
||||
option-limit=0
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
@@ -0,0 +1,216 @@
|
||||
Evaluating Pre-trained Models
|
||||
=============================
|
||||
|
||||
First, download a pre-trained model along with its vocabularies:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> curl https://dl.fbaipublicfiles.com/fairseq/models/wmt14.v2.en-fr.fconv-py.tar.bz2 | tar xvjf -
|
||||
|
||||
This model uses a `Byte Pair Encoding (BPE)
|
||||
vocabulary <https://arxiv.org/abs/1508.07909>`__, so we'll have to apply
|
||||
the encoding to the source text before it can be translated. This can be
|
||||
done with the
|
||||
`apply\_bpe.py <https://github.com/rsennrich/subword-nmt/blob/master/subword_nmt/apply_bpe.py>`__
|
||||
script using the ``wmt14.en-fr.fconv-cuda/bpecodes`` file. ``@@`` is
|
||||
used as a continuation marker and the original text can be easily
|
||||
recovered with e.g. ``sed s/@@ //g`` or by passing the ``--remove-bpe``
|
||||
flag to :ref:`fairseq-generate`. Prior to BPE, input text needs to be tokenized
|
||||
using ``tokenizer.perl`` from
|
||||
`mosesdecoder <https://github.com/moses-smt/mosesdecoder>`__.
|
||||
|
||||
Let's use :ref:`fairseq-interactive` to generate translations interactively.
|
||||
Here, we use a beam size of 5 and preprocess the input with the Moses
|
||||
tokenizer and the given Byte-Pair Encoding vocabulary. It will automatically
|
||||
remove the BPE continuation markers and detokenize the output.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> MODEL_DIR=wmt14.en-fr.fconv-py
|
||||
> fairseq-interactive \
|
||||
--path $MODEL_DIR/model.pt $MODEL_DIR \
|
||||
--beam 5 --source-lang en --target-lang fr \
|
||||
--tokenizer moses \
|
||||
--bpe subword_nmt --bpe-codes $MODEL_DIR/bpecodes
|
||||
| loading model(s) from wmt14.en-fr.fconv-py/model.pt
|
||||
| [en] dictionary: 44206 types
|
||||
| [fr] dictionary: 44463 types
|
||||
| Type the input sentence and press return:
|
||||
Why is it rare to discover new marine mammal species?
|
||||
S-0 Why is it rare to discover new marine mam@@ mal species ?
|
||||
H-0 -0.0643349438905716 Pourquoi est-il rare de découvrir de nouvelles espèces de mammifères marins?
|
||||
P-0 -0.0763 -0.1849 -0.0956 -0.0946 -0.0735 -0.1150 -0.1301 -0.0042 -0.0321 -0.0171 -0.0052 -0.0062 -0.0015
|
||||
|
||||
This generation script produces three types of outputs: a line prefixed
|
||||
with *O* is a copy of the original source sentence; *H* is the
|
||||
hypothesis along with an average log-likelihood; and *P* is the
|
||||
positional score per token position, including the
|
||||
end-of-sentence marker which is omitted from the text.
|
||||
|
||||
Other types of output lines you might see are *D*, the detokenized hypothesis,
|
||||
*T*, the reference target, *A*, alignment info, *E* the history of generation steps.
|
||||
|
||||
See the `README <https://github.com/pytorch/fairseq#pre-trained-models>`__ for a
|
||||
full list of pre-trained models available.
|
||||
|
||||
Training a New Model
|
||||
====================
|
||||
|
||||
The following tutorial is for machine translation. For an example of how
|
||||
to use Fairseq for other tasks, such as :ref:`language modeling`, please see the
|
||||
``examples/`` directory.
|
||||
|
||||
Data Pre-processing
|
||||
-------------------
|
||||
|
||||
Fairseq contains example pre-processing scripts for several translation
|
||||
datasets: IWSLT 2014 (German-English), WMT 2014 (English-French) and WMT
|
||||
2014 (English-German). To pre-process and binarize the IWSLT dataset:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> cd examples/translation/
|
||||
> bash prepare-iwslt14.sh
|
||||
> cd ../..
|
||||
> TEXT=examples/translation/iwslt14.tokenized.de-en
|
||||
> fairseq-preprocess --source-lang de --target-lang en \
|
||||
--trainpref $TEXT/train --validpref $TEXT/valid --testpref $TEXT/test \
|
||||
--destdir data-bin/iwslt14.tokenized.de-en
|
||||
|
||||
This will write binarized data that can be used for model training to
|
||||
``data-bin/iwslt14.tokenized.de-en``.
|
||||
|
||||
Training
|
||||
--------
|
||||
|
||||
Use :ref:`fairseq-train` to train a new model. Here a few example settings that work
|
||||
well for the IWSLT 2014 dataset:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> mkdir -p checkpoints/fconv
|
||||
> CUDA_VISIBLE_DEVICES=0 fairseq-train data-bin/iwslt14.tokenized.de-en \
|
||||
--optimizer nag --lr 0.25 --clip-norm 0.1 --dropout 0.2 --max-tokens 4000 \
|
||||
--arch fconv_iwslt_de_en --save-dir checkpoints/fconv
|
||||
|
||||
By default, :ref:`fairseq-train` will use all available GPUs on your machine. Use the
|
||||
``CUDA_VISIBLE_DEVICES`` environment variable to select specific GPUs and/or to
|
||||
change the number of GPU devices that will be used.
|
||||
|
||||
Also note that the batch size is specified in terms of the maximum
|
||||
number of tokens per batch (``--max-tokens``). You may need to use a
|
||||
smaller value depending on the available GPU memory on your system.
|
||||
|
||||
Generation
|
||||
----------
|
||||
|
||||
Once your model is trained, you can generate translations using
|
||||
:ref:`fairseq-generate` **(for binarized data)** or
|
||||
:ref:`fairseq-interactive` **(for raw text)**:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> fairseq-generate data-bin/iwslt14.tokenized.de-en \
|
||||
--path checkpoints/fconv/checkpoint_best.pt \
|
||||
--batch-size 128 --beam 5
|
||||
| [de] dictionary: 35475 types
|
||||
| [en] dictionary: 24739 types
|
||||
| data-bin/iwslt14.tokenized.de-en test 6750 examples
|
||||
| model fconv
|
||||
| loaded checkpoint trainings/fconv/checkpoint_best.pt
|
||||
S-721 danke .
|
||||
T-721 thank you .
|
||||
...
|
||||
|
||||
To generate translations with only a CPU, use the ``--cpu`` flag. BPE
|
||||
continuation markers can be removed with the ``--remove-bpe`` flag.
|
||||
|
||||
Advanced Training Options
|
||||
=========================
|
||||
|
||||
Large mini-batch training with delayed updates
|
||||
----------------------------------------------
|
||||
|
||||
The ``--update-freq`` option can be used to accumulate gradients from
|
||||
multiple mini-batches and delay updating, creating a larger effective
|
||||
batch size. Delayed updates can also improve training speed by reducing
|
||||
inter-GPU communication costs and by saving idle time caused by variance
|
||||
in workload across GPUs. See `Ott et al.
|
||||
(2018) <https://arxiv.org/abs/1806.00187>`__ for more details.
|
||||
|
||||
To train on a single GPU with an effective batch size that is equivalent
|
||||
to training on 8 GPUs:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> CUDA_VISIBLE_DEVICES=0 fairseq-train --update-freq 8 (...)
|
||||
|
||||
Training with half precision floating point (FP16)
|
||||
--------------------------------------------------
|
||||
|
||||
.. note::
|
||||
|
||||
FP16 training requires a Volta GPU and CUDA 9.1 or greater
|
||||
|
||||
Recent GPUs enable efficient half precision floating point computation,
|
||||
e.g., using `Nvidia Tensor Cores
|
||||
<https://docs.nvidia.com/deeplearning/sdk/mixed-precision-training/index.html>`__.
|
||||
Fairseq supports FP16 training with the ``--fp16`` flag:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> fairseq-train --fp16 (...)
|
||||
|
||||
Distributed training
|
||||
--------------------
|
||||
|
||||
Distributed training in fairseq is implemented on top of ``torch.distributed``.
|
||||
The easiest way to launch jobs is with the `torch.distributed.launch
|
||||
<https://pytorch.org/docs/stable/distributed.html#launch-utility>`__ tool.
|
||||
|
||||
For example, to train a large English-German Transformer model on 2 nodes each
|
||||
with 8 GPUs (in total 16 GPUs), run the following command on each node,
|
||||
replacing ``node_rank=0`` with ``node_rank=1`` on the second node and making
|
||||
sure to update ``--master_addr`` to the IP address of the first node:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> python -m torch.distributed.launch --nproc_per_node=8 \
|
||||
--nnodes=2 --node_rank=0 --master_addr="192.168.1.1" \
|
||||
--master_port=12345 \
|
||||
$(which fairseq-train) data-bin/wmt16_en_de_bpe32k \
|
||||
--arch transformer_vaswani_wmt_en_de_big --share-all-embeddings \
|
||||
--optimizer adam --adam-betas '(0.9, 0.98)' --clip-norm 0.0 \
|
||||
--lr-scheduler inverse_sqrt --warmup-init-lr 1e-07 --warmup-updates 4000 \
|
||||
--lr 0.0005 \
|
||||
--dropout 0.3 --weight-decay 0.0 --criterion label_smoothed_cross_entropy --label-smoothing 0.1 \
|
||||
--max-tokens 3584 \
|
||||
--max-epoch 70 \
|
||||
--fp16
|
||||
|
||||
On SLURM clusters, fairseq will automatically detect the number of nodes and
|
||||
GPUs, but a port number must be provided:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> salloc --gpus=16 --nodes 2 (...)
|
||||
> srun fairseq-train --distributed-port 12345 (...).
|
||||
|
||||
Sharding very large datasets
|
||||
----------------------------
|
||||
|
||||
It can be challenging to train over very large datasets, particularly if your
|
||||
machine does not have much system RAM. Most tasks in fairseq support training
|
||||
over "sharded" datasets, in which the original dataset has been preprocessed
|
||||
into non-overlapping chunks (or "shards").
|
||||
|
||||
For example, instead of preprocessing all your data into a single "data-bin"
|
||||
directory, you can split the data and create "data-bin1", "data-bin2", etc.
|
||||
Then you can adapt your training command like so:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> fairseq-train data-bin1:data-bin2:data-bin3 (...)
|
||||
|
||||
Training will now iterate over each shard, one by one, with each shard
|
||||
corresponding to an "epoch", thus reducing system memory usage.
|
||||
@@ -0,0 +1,284 @@
|
||||
## Hydra
|
||||
|
||||
[Hydra](https://github.com/facebookresearch/hydra) is an open-source Python
|
||||
framework that simplifies the development of research and other complex
|
||||
applications. The key feature is the ability to dynamically create a
|
||||
hierarchical configuration by composition and override it through config files
|
||||
and the command line. The name Hydra comes from its ability to run multiple
|
||||
similar jobs - much like a Hydra with multiple heads.
|
||||
|
||||
## Motivation
|
||||
|
||||
Until recently, all components in fairseq were configured through a shared
|
||||
`args` namespace that was created at application startup. Components declared
|
||||
their own `add_args` method to update the argparse parser, hoping that the names
|
||||
would not clash with arguments from other components. While this model works for
|
||||
smaller applications, as fairseq grew and became integrated into other
|
||||
applications, this became problematic. In order to determine how to configure
|
||||
each component, one needed to a) examine what args were added by this component,
|
||||
and b) read the code to figure out what shared arguments it is using that were
|
||||
added in other places. Reproducing models involved sharing commands that often
|
||||
contained dozens of command line switches.
|
||||
|
||||
The model described above is still supported by fairseq for backward
|
||||
compatibility, but will be deprecated some time in the future.
|
||||
|
||||
New components in fairseq should now create a dataclass that encapsulates all
|
||||
parameters required to configure this component. The dataclass is registered
|
||||
along with the component, and fairseq takes care of constructing and providing
|
||||
this configuration object to the component's constructor. Note that sharing
|
||||
parameters can optionally still work, but one has to explicitly point to the
|
||||
"source of truth" (see inheritance example below). These changes make components
|
||||
in fairseq more independent and re-usable by other applications: all that is
|
||||
needed to create a component is to initialize its dataclass and overwrite some
|
||||
of the defaults.
|
||||
|
||||
While configuring fairseq through command line (using either the legacy argparse
|
||||
based or the new Hydra based entry points) is still fully supported, you can now
|
||||
take advantage of configuring fairseq completely or piece-by-piece through
|
||||
hierarchical YAML configuration files. These files can also be shipped as
|
||||
examples that others can use to run an identically configured job.
|
||||
|
||||
Additionally, Hydra has a rich and growing [library of
|
||||
plugins](https://github.com/facebookresearch/hydra/tree/master/plugins) that
|
||||
provide functionality such as hyperparameter sweeping (including using bayesian
|
||||
optimization through the [Ax](https://github.com/facebook/Ax) library), job
|
||||
launching across various platforms, and more.
|
||||
|
||||
## Creating or migrating components
|
||||
|
||||
In general, each new (or updated) component should provide a companion
|
||||
[dataclass](https://www.python.org/dev/peps/pep-0557/). These dataclass are
|
||||
typically located in the same file as the component and are passed as arguments
|
||||
to the `register_*()` functions. Top-level configs that should be present in
|
||||
every fairseq application are placed in the
|
||||
[global](fairseq/dataclass/configs.py) config file and added to the
|
||||
`FairseqConfig` object.
|
||||
|
||||
Each dataclass is a plain-old-data object, similar to a `NamedTuple`. These
|
||||
classes are decorated with a `@dataclass` decorator, and typically inherit from
|
||||
`FairseqDataclass` (which adds some functionality for backward compatibility).
|
||||
Each field must have a type, and generally has metadata (such as a help string)
|
||||
and a default value. Only primitive types or other config objects are allowed as
|
||||
data types for each field.
|
||||
|
||||
#### Example:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass, field
|
||||
from fairseq.dataclass import FairseqDataclass
|
||||
|
||||
@dataclass
|
||||
class InteractiveConfig(FairseqDataclass):
|
||||
buffer_size: int = field(
|
||||
default=0,
|
||||
metadata={
|
||||
"help": "read this many sentences into a buffer before processing them"
|
||||
},
|
||||
)
|
||||
input: str = field(
|
||||
default="-",
|
||||
metadata={"help": "file to read from; use - for stdin"},
|
||||
)
|
||||
```
|
||||
|
||||
### Inherting values
|
||||
|
||||
Some components require sharing a value. For example, a learning rate scheduler
|
||||
and an optimizer may both need to know the initial learning rate value. One can
|
||||
declare a field that, by default, will inherit its value from another config
|
||||
node in the same hierarchy:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
FairseqAdamConfig(FairseqDataclass):
|
||||
...
|
||||
lr: List[float] = II("optimization.lr")
|
||||
...
|
||||
```
|
||||
|
||||
`II("optimization.lr")` is syntactic sugar for `"${optimization.lr}"`, which is
|
||||
the value one can use in a YAML config file or through command line to achieve
|
||||
the same effect. Note that this assumes that there is an "optimization" config
|
||||
object in the root config and it has a field called "lr".
|
||||
|
||||
### Tasks and Models
|
||||
|
||||
Creating Tasks and Models works same as before, except that legacy
|
||||
implementations now inherit from `LegacyFairseq*` base classes, while new
|
||||
components inherit from `FairseqTask` and `FairseqModel` and provide a dataclass
|
||||
to the `register_*()` functions.
|
||||
|
||||
#### Task example:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class LanguageModelingConfig(FairseqDataclass):
|
||||
data: Optional[str] = field(
|
||||
default=None, metadata={"help": "path to data directory"}
|
||||
)
|
||||
...
|
||||
|
||||
@register_task("language_modeling", dataclass=LanguageModelingConfig)
|
||||
class LanguageModelingTask(LegacyFairseqTask):
|
||||
...
|
||||
@classmethod
|
||||
def setup_task(cls, cfg: LanguageModelingConfig):
|
||||
...
|
||||
```
|
||||
|
||||
#### Model example:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class TransformerLanguageModelConfig(FairseqDataclass):
|
||||
activation_fn: ChoiceEnum(utils.get_available_activation_fns()) = field(
|
||||
default="relu", metadata={"help": "activation function to use"}
|
||||
)
|
||||
dropout: float = field(default=0.1, metadata={"help": "dropout probability"})
|
||||
...
|
||||
|
||||
@register_model("transformer_lm", dataclass=TransformerLanguageModelConfig)
|
||||
class TransformerLanguageModel(FairseqLanguageModel):
|
||||
...
|
||||
@classmethod
|
||||
def build_model(cls, cfg: TransformerLanguageModelConfig, task: FairseqTask):
|
||||
...
|
||||
```
|
||||
|
||||
### Other components
|
||||
|
||||
Other components work as before, but they now take their configuration dataclass
|
||||
as the only constructor argument:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class MosesTokenizerConfig(FairseqDataclass):
|
||||
source_lang: str = field(default="en", metadata={"help": "source language"})
|
||||
...
|
||||
|
||||
@register_tokenizer("moses", dataclass=MosesTokenizerConfig)
|
||||
class MosesTokenizer(object):
|
||||
def __init__(self, cfg: MosesTokenizerConfig):
|
||||
...
|
||||
```
|
||||
|
||||
Note that if you are adding a new registry for a new set of components, you need
|
||||
to add it to the `FairseqConfig` object in `fairseq/dataclass/configs.py`:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class FairseqConfig(object):
|
||||
...
|
||||
my_new_registry: Any = None
|
||||
```
|
||||
|
||||
## Training with `fairseq-hydra-train`
|
||||
|
||||
To fully take advantage of configuration flexibility offered by Hydra, you may
|
||||
want to train new models using the `fairseq-hydra-train` entry point. Legacy CLI
|
||||
tools such as `fairseq-train` will remain supported for the foreseeable future
|
||||
but will be deprecated eventually.
|
||||
|
||||
On startup, Hydra will create a configuration object that contains a hierarchy
|
||||
of all the necessary dataclasses populated with their default values in the
|
||||
code. The default values are overwritten by values found in YAML files in
|
||||
`fairseq/config` directory (which currently sets minimal defaults) and then
|
||||
further overwritten by values provided through command line arguments.
|
||||
|
||||
Some of the most common use cases are shown below:
|
||||
|
||||
### 1. Override default values through command line:
|
||||
|
||||
```shell script
|
||||
$ fairseq-hydra-train \
|
||||
distributed_training.distributed_world_size=1 \
|
||||
dataset.batch_size=2 \
|
||||
task.data=data-bin \
|
||||
model=transformer_lm/transformer_lm_gpt \
|
||||
task=language_modeling \
|
||||
optimization.max_update=5000
|
||||
```
|
||||
|
||||
Note that along with explicitly providing values for parameters such as
|
||||
`dataset.batch_size`, this also tells Hydra to overlay configuration found in
|
||||
`fairseq/config/model/transformer_lm/transformer_lm_gpt.yaml` over the default
|
||||
values in the dataclass. If you want to train a model without specifying a
|
||||
particular architecture you can simply specify `model=transformer_lm`. This only
|
||||
works for migrated tasks and models.
|
||||
|
||||
### 2. Replace bundled configs with an external config:
|
||||
|
||||
```shell script
|
||||
$ fairseq-hydra-train \
|
||||
--config-dir /path/to/external/configs \
|
||||
--config-name wiki103
|
||||
```
|
||||
|
||||
where `/path/to/external/configs/wiki103.yaml` contains:
|
||||
|
||||
```yaml
|
||||
# @package _group_
|
||||
|
||||
model:
|
||||
_name: transformer_lm
|
||||
distributed_training:
|
||||
distributed_world_size: 1
|
||||
dataset:
|
||||
batch_size: 2
|
||||
task:
|
||||
_name: language_modeling
|
||||
data: /path/to/data
|
||||
add_bos_token: false
|
||||
max_target_positions: 1024
|
||||
optimization:
|
||||
max_update: 50000
|
||||
lr: [ 0.25 ]
|
||||
criterion: cross_entropy
|
||||
optimizer: adam
|
||||
lr_scheduler:
|
||||
_name: cosine
|
||||
```
|
||||
|
||||
Note that here bundled configs from `fairseq/config` directory are not used,
|
||||
however the defaults from each dataclass will still be used (unless overwritten
|
||||
by your external config).
|
||||
|
||||
Additionally you can choose to break up your configs by creating a directory
|
||||
structure in the same location as your main config file, with the names of the
|
||||
top-level fields (such as "model", "dataset", etc), and placing config files
|
||||
with meaningful names that would populate that specific section of your
|
||||
top-level config file (for example, you might have
|
||||
`model/small_transformer_lm.yaml`, `model/big_transformer_lm.yaml`, etc). You
|
||||
can then specify the correct configuration via command line, defaults in the
|
||||
main config, or even launch all of them as a sweep (see Hydra documentation on
|
||||
how to do this).
|
||||
|
||||
### 3. Add an external config directory to Hydra search path:
|
||||
|
||||
This allows combining default configuration (including using any bundled config
|
||||
files), while specifying your own config files for some parts of the
|
||||
configuration.
|
||||
|
||||
```shell script
|
||||
$ fairseq-hydra-train \
|
||||
distributed_training.distributed_world_size=1 \
|
||||
dataset.batch_size=2 \
|
||||
task.data=/path/to/data/ \
|
||||
model=transformer_lm/2_layers \
|
||||
task=language_modeling \
|
||||
optimization.max_update=5000 \
|
||||
--config-dir /path/to/external/configs
|
||||
```
|
||||
|
||||
where `/path/to/external/configs` has the following structure:
|
||||
```
|
||||
.
|
||||
+-- model
|
||||
| +-- transformer_lm
|
||||
| | +-- 2_layers.yaml
|
||||
```
|
||||
|
||||
and `2_layers.yaml` contains a copy of `transformer_lm_gpt.yaml` but with
|
||||
`decoder_layers` set to 2. You can add other configs to configure other
|
||||
components as well.
|
||||
@@ -0,0 +1,49 @@
|
||||
.. fairseq documentation master file, created by
|
||||
sphinx-quickstart on Fri Aug 17 21:45:30 2018.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
:github_url: https://github.com/pytorch/fairseq
|
||||
|
||||
|
||||
fairseq documentation
|
||||
=====================
|
||||
|
||||
Fairseq is a sequence modeling toolkit written in `PyTorch
|
||||
<http://pytorch.org/>`_ that allows researchers and developers to
|
||||
train custom models for translation, summarization, language modeling and other
|
||||
text generation tasks.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Getting Started
|
||||
|
||||
getting_started
|
||||
command_line_tools
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Extending Fairseq
|
||||
|
||||
overview
|
||||
tutorial_simple_lstm
|
||||
tutorial_classifying_names
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Library Reference
|
||||
|
||||
tasks
|
||||
models
|
||||
criterions
|
||||
optim
|
||||
lr_scheduler
|
||||
data
|
||||
modules
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`search`
|
||||
@@ -0,0 +1,34 @@
|
||||
.. role:: hidden
|
||||
:class: hidden-section
|
||||
|
||||
.. _Learning Rate Schedulers:
|
||||
|
||||
Learning Rate Schedulers
|
||||
========================
|
||||
|
||||
Learning Rate Schedulers update the learning rate over the course of training.
|
||||
Learning rates can be updated after each update via :func:`step_update` or at
|
||||
epoch boundaries via :func:`step`.
|
||||
|
||||
.. automodule:: fairseq.optim.lr_scheduler
|
||||
:members:
|
||||
|
||||
.. autoclass:: fairseq.optim.lr_scheduler.FairseqLRScheduler
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. autoclass:: fairseq.optim.lr_scheduler.cosine_lr_scheduler.CosineSchedule
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.optim.lr_scheduler.fixed_schedule.FixedSchedule
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.optim.lr_scheduler.inverse_square_root_schedule.InverseSquareRootSchedule
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.optim.lr_scheduler.reduce_lr_on_plateau.ReduceLROnPlateau
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.optim.lr_scheduler.triangular_lr_scheduler.TriangularSchedule
|
||||
:members:
|
||||
:undoc-members:
|
||||
@@ -0,0 +1,36 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=python -msphinx
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
set SPHINXPROJ=fairseq
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The Sphinx module was not found. Make sure you have Sphinx installed,
|
||||
echo.then set the SPHINXBUILD environment variable to point to the full
|
||||
echo.path of the 'sphinx-build' executable. Alternatively you may add the
|
||||
echo.Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
||||
@@ -0,0 +1,104 @@
|
||||
.. role:: hidden
|
||||
:class: hidden-section
|
||||
|
||||
.. module:: fairseq.models
|
||||
|
||||
.. _Models:
|
||||
|
||||
Models
|
||||
======
|
||||
|
||||
A Model defines the neural network's ``forward()`` method and encapsulates all
|
||||
of the learnable parameters in the network. Each model also provides a set of
|
||||
named *architectures* that define the precise network configuration (e.g.,
|
||||
embedding dimension, number of layers, etc.).
|
||||
|
||||
Both the model type and architecture are selected via the ``--arch``
|
||||
command-line argument. Once selected, a model may expose additional command-line
|
||||
arguments for further configuration.
|
||||
|
||||
.. note::
|
||||
|
||||
All fairseq Models extend :class:`BaseFairseqModel`, which in turn extends
|
||||
:class:`torch.nn.Module`. Thus any fairseq Model can be used as a
|
||||
stand-alone Module in other PyTorch code.
|
||||
|
||||
|
||||
Convolutional Neural Networks (CNN)
|
||||
-----------------------------------
|
||||
|
||||
.. module:: fairseq.models.fconv
|
||||
.. autoclass:: fairseq.models.fconv.FConvModel
|
||||
:members:
|
||||
.. autoclass:: fairseq.models.fconv.FConvEncoder
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.models.fconv.FConvDecoder
|
||||
:members:
|
||||
|
||||
|
||||
Long Short-Term Memory (LSTM) networks
|
||||
--------------------------------------
|
||||
|
||||
.. module:: fairseq.models.lstm
|
||||
.. autoclass:: fairseq.models.lstm.LSTMModel
|
||||
:members:
|
||||
.. autoclass:: fairseq.models.lstm.LSTMEncoder
|
||||
:members:
|
||||
.. autoclass:: fairseq.models.lstm.LSTMDecoder
|
||||
:members:
|
||||
|
||||
|
||||
Transformer (self-attention) networks
|
||||
-------------------------------------
|
||||
|
||||
.. module:: fairseq.models.transformer
|
||||
.. autoclass:: fairseq.models.transformer.TransformerModel
|
||||
:members:
|
||||
.. autoclass:: fairseq.models.transformer.TransformerEncoder
|
||||
:members:
|
||||
.. autoclass:: fairseq.models.transformer.TransformerEncoderLayer
|
||||
:members:
|
||||
.. autoclass:: fairseq.models.transformer.TransformerDecoder
|
||||
:members:
|
||||
.. autoclass:: fairseq.models.transformer.TransformerDecoderLayer
|
||||
:members:
|
||||
|
||||
|
||||
Adding new models
|
||||
-----------------
|
||||
|
||||
.. currentmodule:: fairseq.models
|
||||
.. autofunction:: fairseq.models.register_model
|
||||
.. autofunction:: fairseq.models.register_model_architecture
|
||||
.. autoclass:: fairseq.models.BaseFairseqModel
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.models.FairseqEncoderDecoderModel
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.models.FairseqEncoderModel
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.models.FairseqLanguageModel
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.models.FairseqMultiModel
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.models.FairseqEncoder
|
||||
:members:
|
||||
.. autoclass:: fairseq.models.CompositeEncoder
|
||||
:members:
|
||||
.. autoclass:: fairseq.models.FairseqDecoder
|
||||
:members:
|
||||
|
||||
|
||||
.. _Incremental decoding:
|
||||
|
||||
Incremental decoding
|
||||
--------------------
|
||||
|
||||
.. autoclass:: fairseq.models.FairseqIncrementalDecoder
|
||||
:members:
|
||||
:undoc-members:
|
||||
@@ -0,0 +1,9 @@
|
||||
Modules
|
||||
=======
|
||||
|
||||
Fairseq provides several stand-alone :class:`torch.nn.Module` classes that may
|
||||
be helpful when implementing a new :class:`~fairseq.models.BaseFairseqModel`.
|
||||
|
||||
.. automodule:: fairseq.modules
|
||||
:members:
|
||||
:undoc-members:
|
||||
@@ -0,0 +1,38 @@
|
||||
.. role:: hidden
|
||||
:class: hidden-section
|
||||
|
||||
.. _optimizers:
|
||||
|
||||
Optimizers
|
||||
==========
|
||||
|
||||
Optimizers update the Model parameters based on the gradients.
|
||||
|
||||
.. automodule:: fairseq.optim
|
||||
:members:
|
||||
|
||||
.. autoclass:: fairseq.optim.FairseqOptimizer
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
.. autoclass:: fairseq.optim.adadelta.Adadelta
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.optim.adagrad.Adagrad
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.optim.adafactor.FairseqAdafactor
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.optim.adam.FairseqAdam
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.optim.fp16_optimizer.FP16Optimizer
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.optim.nag.FairseqNAG
|
||||
:members:
|
||||
:undoc-members:
|
||||
.. autoclass:: fairseq.optim.sgd.SGD
|
||||
:members:
|
||||
:undoc-members:
|
||||
@@ -0,0 +1,74 @@
|
||||
Overview
|
||||
========
|
||||
|
||||
Fairseq can be extended through user-supplied `plug-ins
|
||||
<https://en.wikipedia.org/wiki/Plug-in_(computing)>`_. We support five kinds of
|
||||
plug-ins:
|
||||
|
||||
- :ref:`Models` define the neural network architecture and encapsulate all of the
|
||||
learnable parameters.
|
||||
- :ref:`Criterions` compute the loss function given the model outputs and targets.
|
||||
- :ref:`Tasks` store dictionaries and provide helpers for loading/iterating over
|
||||
Datasets, initializing the Model/Criterion and calculating the loss.
|
||||
- :ref:`Optimizers` update the Model parameters based on the gradients.
|
||||
- :ref:`Learning Rate Schedulers` update the learning rate over the course of
|
||||
training.
|
||||
|
||||
**Training Flow**
|
||||
|
||||
Given a ``model``, ``criterion``, ``task``, ``optimizer`` and ``lr_scheduler``,
|
||||
fairseq implements the following high-level training flow::
|
||||
|
||||
for epoch in range(num_epochs):
|
||||
itr = task.get_batch_iterator(task.dataset('train'))
|
||||
for num_updates, batch in enumerate(itr):
|
||||
task.train_step(batch, model, criterion, optimizer)
|
||||
average_and_clip_gradients()
|
||||
optimizer.step()
|
||||
lr_scheduler.step_update(num_updates)
|
||||
lr_scheduler.step(epoch)
|
||||
|
||||
where the default implementation for ``task.train_step`` is roughly::
|
||||
|
||||
def train_step(self, batch, model, criterion, optimizer, **unused):
|
||||
loss = criterion(model, batch)
|
||||
optimizer.backward(loss)
|
||||
return loss
|
||||
|
||||
**Registering new plug-ins**
|
||||
|
||||
New plug-ins are *registered* through a set of ``@register`` function
|
||||
decorators, for example::
|
||||
|
||||
@register_model('my_lstm')
|
||||
class MyLSTM(FairseqEncoderDecoderModel):
|
||||
(...)
|
||||
|
||||
Once registered, new plug-ins can be used with the existing :ref:`Command-line
|
||||
Tools`. See the Tutorial sections for more detailed walkthroughs of how to add
|
||||
new plug-ins.
|
||||
|
||||
**Loading plug-ins from another directory**
|
||||
|
||||
New plug-ins can be defined in a custom module stored in the user system. In
|
||||
order to import the module, and make the plugin available to *fairseq*, the
|
||||
command line supports the ``--user-dir`` flag that can be used to specify a
|
||||
custom location for additional modules to load into *fairseq*.
|
||||
|
||||
For example, assuming this directory tree::
|
||||
|
||||
/home/user/my-module/
|
||||
└── __init__.py
|
||||
|
||||
with ``__init__.py``::
|
||||
|
||||
from fairseq.models import register_model_architecture
|
||||
from fairseq.models.transformer import transformer_vaswani_wmt_en_de_big
|
||||
|
||||
@register_model_architecture('transformer', 'my_transformer')
|
||||
def transformer_mmt_big(args):
|
||||
transformer_vaswani_wmt_en_de_big(args)
|
||||
|
||||
it is possible to invoke the :ref:`fairseq-train` script with the new architecture with::
|
||||
|
||||
fairseq-train ... --user-dir /home/user/my-module -a my_transformer --task translation
|
||||
@@ -0,0 +1,2 @@
|
||||
sphinx<3.0.4
|
||||
sphinx-argparse
|
||||
@@ -0,0 +1,61 @@
|
||||
.. role:: hidden
|
||||
:class: hidden-section
|
||||
|
||||
.. module:: fairseq.tasks
|
||||
|
||||
.. _Tasks:
|
||||
|
||||
Tasks
|
||||
=====
|
||||
|
||||
Tasks store dictionaries and provide helpers for loading/iterating over
|
||||
Datasets, initializing the Model/Criterion and calculating the loss.
|
||||
|
||||
Tasks can be selected via the ``--task`` command-line argument. Once selected, a
|
||||
task may expose additional command-line arguments for further configuration.
|
||||
|
||||
Example usage::
|
||||
|
||||
# setup the task (e.g., load dictionaries)
|
||||
task = fairseq.tasks.setup_task(args)
|
||||
|
||||
# build model and criterion
|
||||
model = task.build_model(args)
|
||||
criterion = task.build_criterion(args)
|
||||
|
||||
# load datasets
|
||||
task.load_dataset('train')
|
||||
task.load_dataset('valid')
|
||||
|
||||
# iterate over mini-batches of data
|
||||
batch_itr = task.get_batch_iterator(
|
||||
task.dataset('train'), max_tokens=4096,
|
||||
)
|
||||
for batch in batch_itr:
|
||||
# compute the loss
|
||||
loss, sample_size, logging_output = task.get_loss(
|
||||
model, criterion, batch,
|
||||
)
|
||||
loss.backward()
|
||||
|
||||
|
||||
Translation
|
||||
-----------
|
||||
|
||||
.. autoclass:: fairseq.tasks.translation.TranslationTask
|
||||
|
||||
.. _language modeling:
|
||||
|
||||
Language Modeling
|
||||
-----------------
|
||||
|
||||
.. autoclass:: fairseq.tasks.language_modeling.LanguageModelingTask
|
||||
|
||||
|
||||
Adding new tasks
|
||||
----------------
|
||||
|
||||
.. autofunction:: fairseq.tasks.register_task
|
||||
.. autoclass:: fairseq.tasks.FairseqTask
|
||||
:members:
|
||||
:undoc-members:
|
||||
@@ -0,0 +1,415 @@
|
||||
Tutorial: Classifying Names with a Character-Level RNN
|
||||
======================================================
|
||||
|
||||
In this tutorial we will extend fairseq to support *classification* tasks. In
|
||||
particular we will re-implement the PyTorch tutorial for `Classifying Names with
|
||||
a Character-Level RNN <https://pytorch.org/tutorials/intermediate/char_rnn_classification_tutorial.html>`_
|
||||
in fairseq. It is recommended to quickly skim that tutorial before beginning
|
||||
this one.
|
||||
|
||||
This tutorial covers:
|
||||
|
||||
1. **Preprocessing the data** to create dictionaries.
|
||||
2. **Registering a new Model** that encodes an input sentence with a simple RNN
|
||||
and predicts the output label.
|
||||
3. **Registering a new Task** that loads our dictionaries and dataset.
|
||||
4. **Training the Model** using the existing command-line tools.
|
||||
5. **Writing an evaluation script** that imports fairseq and allows us to
|
||||
interactively evaluate our model on new inputs.
|
||||
|
||||
|
||||
1. Preprocessing the data
|
||||
-------------------------
|
||||
|
||||
The original tutorial provides raw data, but we'll work with a modified version
|
||||
of the data that is already tokenized into characters and split into separate
|
||||
train, valid and test sets.
|
||||
|
||||
Download and extract the data from here:
|
||||
`tutorial_names.tar.gz <https://dl.fbaipublicfiles.com/fairseq/data/tutorial_names.tar.gz>`_
|
||||
|
||||
Once extracted, let's preprocess the data using the :ref:`fairseq-preprocess`
|
||||
command-line tool to create the dictionaries. While this tool is primarily
|
||||
intended for sequence-to-sequence problems, we're able to reuse it here by
|
||||
treating the label as a "target" sequence of length 1. We'll also output the
|
||||
preprocessed files in "raw" format using the ``--dataset-impl`` option to
|
||||
enhance readability:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> fairseq-preprocess \
|
||||
--trainpref names/train --validpref names/valid --testpref names/test \
|
||||
--source-lang input --target-lang label \
|
||||
--destdir names-bin --dataset-impl raw
|
||||
|
||||
After running the above command you should see a new directory,
|
||||
:file:`names-bin/`, containing the dictionaries for *inputs* and *labels*.
|
||||
|
||||
|
||||
2. Registering a new Model
|
||||
--------------------------
|
||||
|
||||
Next we'll register a new model in fairseq that will encode an input sentence
|
||||
with a simple RNN and predict the output label. Compared to the original PyTorch
|
||||
tutorial, our version will also work with batches of data and GPU Tensors.
|
||||
|
||||
First let's copy the simple RNN module implemented in the `PyTorch tutorial
|
||||
<https://pytorch.org/tutorials/intermediate/char_rnn_classification_tutorial.html#creating-the-network>`_.
|
||||
Create a new file named :file:`fairseq/models/rnn_classifier.py` with the
|
||||
following contents::
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
class RNN(nn.Module):
|
||||
|
||||
def __init__(self, input_size, hidden_size, output_size):
|
||||
super(RNN, self).__init__()
|
||||
|
||||
self.hidden_size = hidden_size
|
||||
|
||||
self.i2h = nn.Linear(input_size + hidden_size, hidden_size)
|
||||
self.i2o = nn.Linear(input_size + hidden_size, output_size)
|
||||
self.softmax = nn.LogSoftmax(dim=1)
|
||||
|
||||
def forward(self, input, hidden):
|
||||
combined = torch.cat((input, hidden), 1)
|
||||
hidden = self.i2h(combined)
|
||||
output = self.i2o(combined)
|
||||
output = self.softmax(output)
|
||||
return output, hidden
|
||||
|
||||
def initHidden(self):
|
||||
return torch.zeros(1, self.hidden_size)
|
||||
|
||||
We must also *register* this model with fairseq using the
|
||||
:func:`~fairseq.models.register_model` function decorator. Once the model is
|
||||
registered we'll be able to use it with the existing :ref:`Command-line Tools`.
|
||||
|
||||
All registered models must implement the :class:`~fairseq.models.BaseFairseqModel`
|
||||
interface, so we'll create a small wrapper class in the same file and register
|
||||
it in fairseq with the name ``'rnn_classifier'``::
|
||||
|
||||
from fairseq.models import BaseFairseqModel, register_model
|
||||
|
||||
# Note: the register_model "decorator" should immediately precede the
|
||||
# definition of the Model class.
|
||||
|
||||
@register_model('rnn_classifier')
|
||||
class FairseqRNNClassifier(BaseFairseqModel):
|
||||
|
||||
@staticmethod
|
||||
def add_args(parser):
|
||||
# Models can override this method to add new command-line arguments.
|
||||
# Here we'll add a new command-line argument to configure the
|
||||
# dimensionality of the hidden state.
|
||||
parser.add_argument(
|
||||
'--hidden-dim', type=int, metavar='N',
|
||||
help='dimensionality of the hidden state',
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def build_model(cls, args, task):
|
||||
# Fairseq initializes models by calling the ``build_model()``
|
||||
# function. This provides more flexibility, since the returned model
|
||||
# instance can be of a different type than the one that was called.
|
||||
# In this case we'll just return a FairseqRNNClassifier instance.
|
||||
|
||||
# Initialize our RNN module
|
||||
rnn = RNN(
|
||||
# We'll define the Task in the next section, but for now just
|
||||
# notice that the task holds the dictionaries for the "source"
|
||||
# (i.e., the input sentence) and "target" (i.e., the label).
|
||||
input_size=len(task.source_dictionary),
|
||||
hidden_size=args.hidden_dim,
|
||||
output_size=len(task.target_dictionary),
|
||||
)
|
||||
|
||||
# Return the wrapped version of the module
|
||||
return FairseqRNNClassifier(
|
||||
rnn=rnn,
|
||||
input_vocab=task.source_dictionary,
|
||||
)
|
||||
|
||||
def __init__(self, rnn, input_vocab):
|
||||
super(FairseqRNNClassifier, self).__init__()
|
||||
|
||||
self.rnn = rnn
|
||||
self.input_vocab = input_vocab
|
||||
|
||||
# The RNN module in the tutorial expects one-hot inputs, so we can
|
||||
# precompute the identity matrix to help convert from indices to
|
||||
# one-hot vectors. We register it as a buffer so that it is moved to
|
||||
# the GPU when ``cuda()`` is called.
|
||||
self.register_buffer('one_hot_inputs', torch.eye(len(input_vocab)))
|
||||
|
||||
def forward(self, src_tokens, src_lengths):
|
||||
# The inputs to the ``forward()`` function are determined by the
|
||||
# Task, and in particular the ``'net_input'`` key in each
|
||||
# mini-batch. We'll define the Task in the next section, but for
|
||||
# now just know that *src_tokens* has shape `(batch, src_len)` and
|
||||
# *src_lengths* has shape `(batch)`.
|
||||
bsz, max_src_len = src_tokens.size()
|
||||
|
||||
# Initialize the RNN hidden state. Compared to the original PyTorch
|
||||
# tutorial we'll also handle batched inputs and work on the GPU.
|
||||
hidden = self.rnn.initHidden()
|
||||
hidden = hidden.repeat(bsz, 1) # expand for batched inputs
|
||||
hidden = hidden.to(src_tokens.device) # move to GPU
|
||||
|
||||
for i in range(max_src_len):
|
||||
# WARNING: The inputs have padding, so we should mask those
|
||||
# elements here so that padding doesn't affect the results.
|
||||
# This is left as an exercise for the reader. The padding symbol
|
||||
# is given by ``self.input_vocab.pad()`` and the unpadded length
|
||||
# of each input is given by *src_lengths*.
|
||||
|
||||
# One-hot encode a batch of input characters.
|
||||
input = self.one_hot_inputs[src_tokens[:, i].long()]
|
||||
|
||||
# Feed the input to our RNN.
|
||||
output, hidden = self.rnn(input, hidden)
|
||||
|
||||
# Return the final output state for making a prediction
|
||||
return output
|
||||
|
||||
Finally let's define a *named architecture* with the configuration for our
|
||||
model. This is done with the :func:`~fairseq.models.register_model_architecture`
|
||||
function decorator. Thereafter this named architecture can be used with the
|
||||
``--arch`` command-line argument, e.g., ``--arch pytorch_tutorial_rnn``::
|
||||
|
||||
from fairseq.models import register_model_architecture
|
||||
|
||||
# The first argument to ``register_model_architecture()`` should be the name
|
||||
# of the model we registered above (i.e., 'rnn_classifier'). The function we
|
||||
# register here should take a single argument *args* and modify it in-place
|
||||
# to match the desired architecture.
|
||||
|
||||
@register_model_architecture('rnn_classifier', 'pytorch_tutorial_rnn')
|
||||
def pytorch_tutorial_rnn(args):
|
||||
# We use ``getattr()`` to prioritize arguments that are explicitly given
|
||||
# on the command-line, so that the defaults defined below are only used
|
||||
# when no other value has been specified.
|
||||
args.hidden_dim = getattr(args, 'hidden_dim', 128)
|
||||
|
||||
|
||||
3. Registering a new Task
|
||||
-------------------------
|
||||
|
||||
Now we'll register a new :class:`~fairseq.tasks.FairseqTask` that will load our
|
||||
dictionaries and dataset. Tasks can also control how the data is batched into
|
||||
mini-batches, but in this tutorial we'll reuse the batching provided by
|
||||
:class:`fairseq.data.LanguagePairDataset`.
|
||||
|
||||
Create a new file named :file:`fairseq/tasks/simple_classification.py` with the
|
||||
following contents::
|
||||
|
||||
import os
|
||||
import torch
|
||||
|
||||
from fairseq.data import Dictionary, LanguagePairDataset
|
||||
from fairseq.tasks import FairseqTask, register_task
|
||||
|
||||
|
||||
@register_task('simple_classification')
|
||||
class SimpleClassificationTask(LegacyFairseqTask):
|
||||
|
||||
@staticmethod
|
||||
def add_args(parser):
|
||||
# Add some command-line arguments for specifying where the data is
|
||||
# located and the maximum supported input length.
|
||||
parser.add_argument('data', metavar='FILE',
|
||||
help='file prefix for data')
|
||||
parser.add_argument('--max-positions', default=1024, type=int,
|
||||
help='max input length')
|
||||
|
||||
@classmethod
|
||||
def setup_task(cls, args, **kwargs):
|
||||
# Here we can perform any setup required for the task. This may include
|
||||
# loading Dictionaries, initializing shared Embedding layers, etc.
|
||||
# In this case we'll just load the Dictionaries.
|
||||
input_vocab = Dictionary.load(os.path.join(args.data, 'dict.input.txt'))
|
||||
label_vocab = Dictionary.load(os.path.join(args.data, 'dict.label.txt'))
|
||||
print('| [input] dictionary: {} types'.format(len(input_vocab)))
|
||||
print('| [label] dictionary: {} types'.format(len(label_vocab)))
|
||||
|
||||
return SimpleClassificationTask(args, input_vocab, label_vocab)
|
||||
|
||||
def __init__(self, args, input_vocab, label_vocab):
|
||||
super().__init__(args)
|
||||
self.input_vocab = input_vocab
|
||||
self.label_vocab = label_vocab
|
||||
|
||||
def load_dataset(self, split, **kwargs):
|
||||
"""Load a given dataset split (e.g., train, valid, test)."""
|
||||
|
||||
prefix = os.path.join(self.args.data, '{}.input-label'.format(split))
|
||||
|
||||
# Read input sentences.
|
||||
sentences, lengths = [], []
|
||||
with open(prefix + '.input', encoding='utf-8') as file:
|
||||
for line in file:
|
||||
sentence = line.strip()
|
||||
|
||||
# Tokenize the sentence, splitting on spaces
|
||||
tokens = self.input_vocab.encode_line(
|
||||
sentence, add_if_not_exist=False,
|
||||
)
|
||||
|
||||
sentences.append(tokens)
|
||||
lengths.append(tokens.numel())
|
||||
|
||||
# Read labels.
|
||||
labels = []
|
||||
with open(prefix + '.label', encoding='utf-8') as file:
|
||||
for line in file:
|
||||
label = line.strip()
|
||||
labels.append(
|
||||
# Convert label to a numeric ID.
|
||||
torch.LongTensor([self.label_vocab.add_symbol(label)])
|
||||
)
|
||||
|
||||
assert len(sentences) == len(labels)
|
||||
print('| {} {} {} examples'.format(self.args.data, split, len(sentences)))
|
||||
|
||||
# We reuse LanguagePairDataset since classification can be modeled as a
|
||||
# sequence-to-sequence task where the target sequence has length 1.
|
||||
self.datasets[split] = LanguagePairDataset(
|
||||
src=sentences,
|
||||
src_sizes=lengths,
|
||||
src_dict=self.input_vocab,
|
||||
tgt=labels,
|
||||
tgt_sizes=torch.ones(len(labels)), # targets have length 1
|
||||
tgt_dict=self.label_vocab,
|
||||
left_pad_source=False,
|
||||
# Since our target is a single class label, there's no need for
|
||||
# teacher forcing. If we set this to ``True`` then our Model's
|
||||
# ``forward()`` method would receive an additional argument called
|
||||
# *prev_output_tokens* that would contain a shifted version of the
|
||||
# target sequence.
|
||||
input_feeding=False,
|
||||
)
|
||||
|
||||
def max_positions(self):
|
||||
"""Return the max input length allowed by the task."""
|
||||
# The source should be less than *args.max_positions* and the "target"
|
||||
# has max length 1.
|
||||
return (self.args.max_positions, 1)
|
||||
|
||||
@property
|
||||
def source_dictionary(self):
|
||||
"""Return the source :class:`~fairseq.data.Dictionary`."""
|
||||
return self.input_vocab
|
||||
|
||||
@property
|
||||
def target_dictionary(self):
|
||||
"""Return the target :class:`~fairseq.data.Dictionary`."""
|
||||
return self.label_vocab
|
||||
|
||||
# We could override this method if we wanted more control over how batches
|
||||
# are constructed, but it's not necessary for this tutorial since we can
|
||||
# reuse the batching provided by LanguagePairDataset.
|
||||
#
|
||||
# def get_batch_iterator(
|
||||
# self, dataset, max_tokens=None, max_sentences=None, max_positions=None,
|
||||
# ignore_invalid_inputs=False, required_batch_size_multiple=1,
|
||||
# seed=1, num_shards=1, shard_id=0, num_workers=0, epoch=1,
|
||||
# data_buffer_size=0, disable_iterator_cache=False,
|
||||
# ):
|
||||
# (...)
|
||||
|
||||
|
||||
4. Training the Model
|
||||
---------------------
|
||||
|
||||
Now we're ready to train the model. We can use the existing :ref:`fairseq-train`
|
||||
command-line tool for this, making sure to specify our new Task (``--task
|
||||
simple_classification``) and Model architecture (``--arch
|
||||
pytorch_tutorial_rnn``):
|
||||
|
||||
.. note::
|
||||
|
||||
You can also configure the dimensionality of the hidden state by passing the
|
||||
``--hidden-dim`` argument to :ref:`fairseq-train`.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> fairseq-train names-bin \
|
||||
--task simple_classification \
|
||||
--arch pytorch_tutorial_rnn \
|
||||
--optimizer adam --lr 0.001 --lr-shrink 0.5 \
|
||||
--max-tokens 1000
|
||||
(...)
|
||||
| epoch 027 | loss 1.200 | ppl 2.30 | wps 15728 | ups 119.4 | wpb 116 | bsz 116 | num_updates 3726 | lr 1.5625e-05 | gnorm 1.290 | clip 0% | oom 0 | wall 32 | train_wall 21
|
||||
| epoch 027 | valid on 'valid' subset | valid_loss 1.41304 | valid_ppl 2.66 | num_updates 3726 | best 1.41208
|
||||
| done training in 31.6 seconds
|
||||
|
||||
The model files should appear in the :file:`checkpoints/` directory.
|
||||
|
||||
|
||||
5. Writing an evaluation script
|
||||
-------------------------------
|
||||
|
||||
Finally we can write a short script to evaluate our model on new inputs. Create
|
||||
a new file named :file:`eval_classifier.py` with the following contents::
|
||||
|
||||
from fairseq import checkpoint_utils, data, options, tasks
|
||||
|
||||
# Parse command-line arguments for generation
|
||||
parser = options.get_generation_parser(default_task='simple_classification')
|
||||
args = options.parse_args_and_arch(parser)
|
||||
|
||||
# Setup task
|
||||
task = tasks.setup_task(args)
|
||||
|
||||
# Load model
|
||||
print('| loading model from {}'.format(args.path))
|
||||
models, _model_args = checkpoint_utils.load_model_ensemble([args.path], task=task)
|
||||
model = models[0]
|
||||
|
||||
while True:
|
||||
sentence = input('\nInput: ')
|
||||
|
||||
# Tokenize into characters
|
||||
chars = ' '.join(list(sentence.strip()))
|
||||
tokens = task.source_dictionary.encode_line(
|
||||
chars, add_if_not_exist=False,
|
||||
)
|
||||
|
||||
# Build mini-batch to feed to the model
|
||||
batch = data.language_pair_dataset.collate(
|
||||
samples=[{'id': -1, 'source': tokens}], # bsz = 1
|
||||
pad_idx=task.source_dictionary.pad(),
|
||||
eos_idx=task.source_dictionary.eos(),
|
||||
left_pad_source=False,
|
||||
input_feeding=False,
|
||||
)
|
||||
|
||||
# Feed batch to the model and get predictions
|
||||
preds = model(**batch['net_input'])
|
||||
|
||||
# Print top 3 predictions and their log-probabilities
|
||||
top_scores, top_labels = preds[0].topk(k=3)
|
||||
for score, label_idx in zip(top_scores, top_labels):
|
||||
label_name = task.target_dictionary.string([label_idx])
|
||||
print('({:.2f})\t{}'.format(score, label_name))
|
||||
|
||||
Now we can evaluate our model interactively. Note that we have included the
|
||||
original data path (:file:`names-bin/`) so that the dictionaries can be loaded:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> python eval_classifier.py names-bin --path checkpoints/checkpoint_best.pt
|
||||
| [input] dictionary: 64 types
|
||||
| [label] dictionary: 24 types
|
||||
| loading model from checkpoints/checkpoint_best.pt
|
||||
|
||||
Input: Satoshi
|
||||
(-0.61) Japanese
|
||||
(-1.20) Arabic
|
||||
(-2.86) Italian
|
||||
|
||||
Input: Sinbad
|
||||
(-0.30) Arabic
|
||||
(-1.76) English
|
||||
(-4.08) Russian
|
||||
@@ -0,0 +1,518 @@
|
||||
Tutorial: Simple LSTM
|
||||
=====================
|
||||
|
||||
In this tutorial we will extend fairseq by adding a new
|
||||
:class:`~fairseq.models.FairseqEncoderDecoderModel` that encodes a source
|
||||
sentence with an LSTM and then passes the final hidden state to a second LSTM
|
||||
that decodes the target sentence (without attention).
|
||||
|
||||
This tutorial covers:
|
||||
|
||||
1. **Writing an Encoder and Decoder** to encode/decode the source/target
|
||||
sentence, respectively.
|
||||
2. **Registering a new Model** so that it can be used with the existing
|
||||
:ref:`Command-line tools`.
|
||||
3. **Training the Model** using the existing command-line tools.
|
||||
4. **Making generation faster** by modifying the Decoder to use
|
||||
:ref:`Incremental decoding`.
|
||||
|
||||
|
||||
1. Building an Encoder and Decoder
|
||||
----------------------------------
|
||||
|
||||
In this section we'll define a simple LSTM Encoder and Decoder. All Encoders
|
||||
should implement the :class:`~fairseq.models.FairseqEncoder` interface and
|
||||
Decoders should implement the :class:`~fairseq.models.FairseqDecoder` interface.
|
||||
These interfaces themselves extend :class:`torch.nn.Module`, so FairseqEncoders
|
||||
and FairseqDecoders can be written and used in the same ways as ordinary PyTorch
|
||||
Modules.
|
||||
|
||||
|
||||
Encoder
|
||||
~~~~~~~
|
||||
|
||||
Our Encoder will embed the tokens in the source sentence, feed them to a
|
||||
:class:`torch.nn.LSTM` and return the final hidden state. To create our encoder
|
||||
save the following in a new file named :file:`fairseq/models/simple_lstm.py`::
|
||||
|
||||
import torch.nn as nn
|
||||
from fairseq import utils
|
||||
from fairseq.models import FairseqEncoder
|
||||
|
||||
class SimpleLSTMEncoder(FairseqEncoder):
|
||||
|
||||
def __init__(
|
||||
self, args, dictionary, embed_dim=128, hidden_dim=128, dropout=0.1,
|
||||
):
|
||||
super().__init__(dictionary)
|
||||
self.args = args
|
||||
|
||||
# Our encoder will embed the inputs before feeding them to the LSTM.
|
||||
self.embed_tokens = nn.Embedding(
|
||||
num_embeddings=len(dictionary),
|
||||
embedding_dim=embed_dim,
|
||||
padding_idx=dictionary.pad(),
|
||||
)
|
||||
self.dropout = nn.Dropout(p=dropout)
|
||||
|
||||
# We'll use a single-layer, unidirectional LSTM for simplicity.
|
||||
self.lstm = nn.LSTM(
|
||||
input_size=embed_dim,
|
||||
hidden_size=hidden_dim,
|
||||
num_layers=1,
|
||||
bidirectional=False,
|
||||
batch_first=True,
|
||||
)
|
||||
|
||||
def forward(self, src_tokens, src_lengths):
|
||||
# The inputs to the ``forward()`` function are determined by the
|
||||
# Task, and in particular the ``'net_input'`` key in each
|
||||
# mini-batch. We discuss Tasks in the next tutorial, but for now just
|
||||
# know that *src_tokens* has shape `(batch, src_len)` and *src_lengths*
|
||||
# has shape `(batch)`.
|
||||
|
||||
# Note that the source is typically padded on the left. This can be
|
||||
# configured by adding the `--left-pad-source "False"` command-line
|
||||
# argument, but here we'll make the Encoder handle either kind of
|
||||
# padding by converting everything to be right-padded.
|
||||
if self.args.left_pad_source:
|
||||
# Convert left-padding to right-padding.
|
||||
src_tokens = utils.convert_padding_direction(
|
||||
src_tokens,
|
||||
padding_idx=self.dictionary.pad(),
|
||||
left_to_right=True
|
||||
)
|
||||
|
||||
# Embed the source.
|
||||
x = self.embed_tokens(src_tokens)
|
||||
|
||||
# Apply dropout.
|
||||
x = self.dropout(x)
|
||||
|
||||
# Pack the sequence into a PackedSequence object to feed to the LSTM.
|
||||
x = nn.utils.rnn.pack_padded_sequence(x, src_lengths, batch_first=True)
|
||||
|
||||
# Get the output from the LSTM.
|
||||
_outputs, (final_hidden, _final_cell) = self.lstm(x)
|
||||
|
||||
# Return the Encoder's output. This can be any object and will be
|
||||
# passed directly to the Decoder.
|
||||
return {
|
||||
# this will have shape `(bsz, hidden_dim)`
|
||||
'final_hidden': final_hidden.squeeze(0),
|
||||
}
|
||||
|
||||
# Encoders are required to implement this method so that we can rearrange
|
||||
# the order of the batch elements during inference (e.g., beam search).
|
||||
def reorder_encoder_out(self, encoder_out, new_order):
|
||||
"""
|
||||
Reorder encoder output according to `new_order`.
|
||||
|
||||
Args:
|
||||
encoder_out: output from the ``forward()`` method
|
||||
new_order (LongTensor): desired order
|
||||
|
||||
Returns:
|
||||
`encoder_out` rearranged according to `new_order`
|
||||
"""
|
||||
final_hidden = encoder_out['final_hidden']
|
||||
return {
|
||||
'final_hidden': final_hidden.index_select(0, new_order),
|
||||
}
|
||||
|
||||
|
||||
Decoder
|
||||
~~~~~~~
|
||||
|
||||
Our Decoder will predict the next word, conditioned on the Encoder's final
|
||||
hidden state and an embedded representation of the previous target word -- which
|
||||
is sometimes called *teacher forcing*. More specifically, we'll use a
|
||||
:class:`torch.nn.LSTM` to produce a sequence of hidden states that we'll project
|
||||
to the size of the output vocabulary to predict each target word.
|
||||
|
||||
::
|
||||
|
||||
import torch
|
||||
from fairseq.models import FairseqDecoder
|
||||
|
||||
class SimpleLSTMDecoder(FairseqDecoder):
|
||||
|
||||
def __init__(
|
||||
self, dictionary, encoder_hidden_dim=128, embed_dim=128, hidden_dim=128,
|
||||
dropout=0.1,
|
||||
):
|
||||
super().__init__(dictionary)
|
||||
|
||||
# Our decoder will embed the inputs before feeding them to the LSTM.
|
||||
self.embed_tokens = nn.Embedding(
|
||||
num_embeddings=len(dictionary),
|
||||
embedding_dim=embed_dim,
|
||||
padding_idx=dictionary.pad(),
|
||||
)
|
||||
self.dropout = nn.Dropout(p=dropout)
|
||||
|
||||
# We'll use a single-layer, unidirectional LSTM for simplicity.
|
||||
self.lstm = nn.LSTM(
|
||||
# For the first layer we'll concatenate the Encoder's final hidden
|
||||
# state with the embedded target tokens.
|
||||
input_size=encoder_hidden_dim + embed_dim,
|
||||
hidden_size=hidden_dim,
|
||||
num_layers=1,
|
||||
bidirectional=False,
|
||||
)
|
||||
|
||||
# Define the output projection.
|
||||
self.output_projection = nn.Linear(hidden_dim, len(dictionary))
|
||||
|
||||
# During training Decoders are expected to take the entire target sequence
|
||||
# (shifted right by one position) and produce logits over the vocabulary.
|
||||
# The *prev_output_tokens* tensor begins with the end-of-sentence symbol,
|
||||
# ``dictionary.eos()``, followed by the target sequence.
|
||||
def forward(self, prev_output_tokens, encoder_out):
|
||||
"""
|
||||
Args:
|
||||
prev_output_tokens (LongTensor): previous decoder outputs of shape
|
||||
`(batch, tgt_len)`, for teacher forcing
|
||||
encoder_out (Tensor, optional): output from the encoder, used for
|
||||
encoder-side attention
|
||||
|
||||
Returns:
|
||||
tuple:
|
||||
- the last decoder layer's output of shape
|
||||
`(batch, tgt_len, vocab)`
|
||||
- the last decoder layer's attention weights of shape
|
||||
`(batch, tgt_len, src_len)`
|
||||
"""
|
||||
bsz, tgt_len = prev_output_tokens.size()
|
||||
|
||||
# Extract the final hidden state from the Encoder.
|
||||
final_encoder_hidden = encoder_out['final_hidden']
|
||||
|
||||
# Embed the target sequence, which has been shifted right by one
|
||||
# position and now starts with the end-of-sentence symbol.
|
||||
x = self.embed_tokens(prev_output_tokens)
|
||||
|
||||
# Apply dropout.
|
||||
x = self.dropout(x)
|
||||
|
||||
# Concatenate the Encoder's final hidden state to *every* embedded
|
||||
# target token.
|
||||
x = torch.cat(
|
||||
[x, final_encoder_hidden.unsqueeze(1).expand(bsz, tgt_len, -1)],
|
||||
dim=2,
|
||||
)
|
||||
|
||||
# Using PackedSequence objects in the Decoder is harder than in the
|
||||
# Encoder, since the targets are not sorted in descending length order,
|
||||
# which is a requirement of ``pack_padded_sequence()``. Instead we'll
|
||||
# feed nn.LSTM directly.
|
||||
initial_state = (
|
||||
final_encoder_hidden.unsqueeze(0), # hidden
|
||||
torch.zeros_like(final_encoder_hidden).unsqueeze(0), # cell
|
||||
)
|
||||
output, _ = self.lstm(
|
||||
x.transpose(0, 1), # convert to shape `(tgt_len, bsz, dim)`
|
||||
initial_state,
|
||||
)
|
||||
x = output.transpose(0, 1) # convert to shape `(bsz, tgt_len, hidden)`
|
||||
|
||||
# Project the outputs to the size of the vocabulary.
|
||||
x = self.output_projection(x)
|
||||
|
||||
# Return the logits and ``None`` for the attention weights
|
||||
return x, None
|
||||
|
||||
|
||||
2. Registering the Model
|
||||
------------------------
|
||||
|
||||
Now that we've defined our Encoder and Decoder we must *register* our model with
|
||||
fairseq using the :func:`~fairseq.models.register_model` function decorator.
|
||||
Once the model is registered we'll be able to use it with the existing
|
||||
:ref:`Command-line Tools`.
|
||||
|
||||
All registered models must implement the
|
||||
:class:`~fairseq.models.BaseFairseqModel` interface. For sequence-to-sequence
|
||||
models (i.e., any model with a single Encoder and Decoder), we can instead
|
||||
implement the :class:`~fairseq.models.FairseqEncoderDecoderModel` interface.
|
||||
|
||||
Create a small wrapper class in the same file and register it in fairseq with
|
||||
the name ``'simple_lstm'``::
|
||||
|
||||
from fairseq.models import FairseqEncoderDecoderModel, register_model
|
||||
|
||||
# Note: the register_model "decorator" should immediately precede the
|
||||
# definition of the Model class.
|
||||
|
||||
@register_model('simple_lstm')
|
||||
class SimpleLSTMModel(FairseqEncoderDecoderModel):
|
||||
|
||||
@staticmethod
|
||||
def add_args(parser):
|
||||
# Models can override this method to add new command-line arguments.
|
||||
# Here we'll add some new command-line arguments to configure dropout
|
||||
# and the dimensionality of the embeddings and hidden states.
|
||||
parser.add_argument(
|
||||
'--encoder-embed-dim', type=int, metavar='N',
|
||||
help='dimensionality of the encoder embeddings',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--encoder-hidden-dim', type=int, metavar='N',
|
||||
help='dimensionality of the encoder hidden state',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--encoder-dropout', type=float, default=0.1,
|
||||
help='encoder dropout probability',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--decoder-embed-dim', type=int, metavar='N',
|
||||
help='dimensionality of the decoder embeddings',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--decoder-hidden-dim', type=int, metavar='N',
|
||||
help='dimensionality of the decoder hidden state',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--decoder-dropout', type=float, default=0.1,
|
||||
help='decoder dropout probability',
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def build_model(cls, args, task):
|
||||
# Fairseq initializes models by calling the ``build_model()``
|
||||
# function. This provides more flexibility, since the returned model
|
||||
# instance can be of a different type than the one that was called.
|
||||
# In this case we'll just return a SimpleLSTMModel instance.
|
||||
|
||||
# Initialize our Encoder and Decoder.
|
||||
encoder = SimpleLSTMEncoder(
|
||||
args=args,
|
||||
dictionary=task.source_dictionary,
|
||||
embed_dim=args.encoder_embed_dim,
|
||||
hidden_dim=args.encoder_hidden_dim,
|
||||
dropout=args.encoder_dropout,
|
||||
)
|
||||
decoder = SimpleLSTMDecoder(
|
||||
dictionary=task.target_dictionary,
|
||||
encoder_hidden_dim=args.encoder_hidden_dim,
|
||||
embed_dim=args.decoder_embed_dim,
|
||||
hidden_dim=args.decoder_hidden_dim,
|
||||
dropout=args.decoder_dropout,
|
||||
)
|
||||
model = SimpleLSTMModel(encoder, decoder)
|
||||
|
||||
# Print the model architecture.
|
||||
print(model)
|
||||
|
||||
return model
|
||||
|
||||
# We could override the ``forward()`` if we wanted more control over how
|
||||
# the encoder and decoder interact, but it's not necessary for this
|
||||
# tutorial since we can inherit the default implementation provided by
|
||||
# the FairseqEncoderDecoderModel base class, which looks like:
|
||||
#
|
||||
# def forward(self, src_tokens, src_lengths, prev_output_tokens):
|
||||
# encoder_out = self.encoder(src_tokens, src_lengths)
|
||||
# decoder_out = self.decoder(prev_output_tokens, encoder_out)
|
||||
# return decoder_out
|
||||
|
||||
Finally let's define a *named architecture* with the configuration for our
|
||||
model. This is done with the :func:`~fairseq.models.register_model_architecture`
|
||||
function decorator. Thereafter this named architecture can be used with the
|
||||
``--arch`` command-line argument, e.g., ``--arch tutorial_simple_lstm``::
|
||||
|
||||
from fairseq.models import register_model_architecture
|
||||
|
||||
# The first argument to ``register_model_architecture()`` should be the name
|
||||
# of the model we registered above (i.e., 'simple_lstm'). The function we
|
||||
# register here should take a single argument *args* and modify it in-place
|
||||
# to match the desired architecture.
|
||||
|
||||
@register_model_architecture('simple_lstm', 'tutorial_simple_lstm')
|
||||
def tutorial_simple_lstm(args):
|
||||
# We use ``getattr()`` to prioritize arguments that are explicitly given
|
||||
# on the command-line, so that the defaults defined below are only used
|
||||
# when no other value has been specified.
|
||||
args.encoder_embed_dim = getattr(args, 'encoder_embed_dim', 256)
|
||||
args.encoder_hidden_dim = getattr(args, 'encoder_hidden_dim', 256)
|
||||
args.decoder_embed_dim = getattr(args, 'decoder_embed_dim', 256)
|
||||
args.decoder_hidden_dim = getattr(args, 'decoder_hidden_dim', 256)
|
||||
|
||||
|
||||
3. Training the Model
|
||||
---------------------
|
||||
|
||||
Now we're ready to train the model. We can use the existing :ref:`fairseq-train`
|
||||
command-line tool for this, making sure to specify our new Model architecture
|
||||
(``--arch tutorial_simple_lstm``).
|
||||
|
||||
.. note::
|
||||
|
||||
Make sure you've already preprocessed the data from the IWSLT example in the
|
||||
:file:`examples/translation/` directory.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> fairseq-train data-bin/iwslt14.tokenized.de-en \
|
||||
--arch tutorial_simple_lstm \
|
||||
--encoder-dropout 0.2 --decoder-dropout 0.2 \
|
||||
--optimizer adam --lr 0.005 --lr-shrink 0.5 \
|
||||
--max-tokens 12000
|
||||
(...)
|
||||
| epoch 052 | loss 4.027 | ppl 16.30 | wps 420805 | ups 39.7 | wpb 9841 | bsz 400 | num_updates 20852 | lr 1.95313e-05 | gnorm 0.218 | clip 0% | oom 0 | wall 529 | train_wall 396
|
||||
| epoch 052 | valid on 'valid' subset | valid_loss 4.74989 | valid_ppl 26.91 | num_updates 20852 | best 4.74954
|
||||
|
||||
The model files should appear in the :file:`checkpoints/` directory. While this
|
||||
model architecture is not very good, we can use the :ref:`fairseq-generate` script to
|
||||
generate translations and compute our BLEU score over the test set:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> fairseq-generate data-bin/iwslt14.tokenized.de-en \
|
||||
--path checkpoints/checkpoint_best.pt \
|
||||
--beam 5 \
|
||||
--remove-bpe
|
||||
(...)
|
||||
| Translated 6750 sentences (153132 tokens) in 17.3s (389.12 sentences/s, 8827.68 tokens/s)
|
||||
| Generate test with beam=5: BLEU4 = 8.18, 38.8/12.1/4.7/2.0 (BP=1.000, ratio=1.066, syslen=139865, reflen=131146)
|
||||
|
||||
|
||||
4. Making generation faster
|
||||
---------------------------
|
||||
|
||||
While autoregressive generation from sequence-to-sequence models is inherently
|
||||
slow, our implementation above is especially slow because it recomputes the
|
||||
entire sequence of Decoder hidden states for every output token (i.e., it is
|
||||
``O(n^2)``). We can make this significantly faster by instead caching the
|
||||
previous hidden states.
|
||||
|
||||
In fairseq this is called :ref:`Incremental decoding`. Incremental decoding is a
|
||||
special mode at inference time where the Model only receives a single timestep
|
||||
of input corresponding to the immediately previous output token (for teacher
|
||||
forcing) and must produce the next output incrementally. Thus the model must
|
||||
cache any long-term state that is needed about the sequence, e.g., hidden
|
||||
states, convolutional states, etc.
|
||||
|
||||
To implement incremental decoding we will modify our model to implement the
|
||||
:class:`~fairseq.models.FairseqIncrementalDecoder` interface. Compared to the
|
||||
standard :class:`~fairseq.models.FairseqDecoder` interface, the incremental
|
||||
decoder interface allows ``forward()`` methods to take an extra keyword argument
|
||||
(*incremental_state*) that can be used to cache state across time-steps.
|
||||
|
||||
Let's replace our ``SimpleLSTMDecoder`` with an incremental one::
|
||||
|
||||
import torch
|
||||
from fairseq.models import FairseqIncrementalDecoder
|
||||
|
||||
class SimpleLSTMDecoder(FairseqIncrementalDecoder):
|
||||
|
||||
def __init__(
|
||||
self, dictionary, encoder_hidden_dim=128, embed_dim=128, hidden_dim=128,
|
||||
dropout=0.1,
|
||||
):
|
||||
# This remains the same as before.
|
||||
super().__init__(dictionary)
|
||||
self.embed_tokens = nn.Embedding(
|
||||
num_embeddings=len(dictionary),
|
||||
embedding_dim=embed_dim,
|
||||
padding_idx=dictionary.pad(),
|
||||
)
|
||||
self.dropout = nn.Dropout(p=dropout)
|
||||
self.lstm = nn.LSTM(
|
||||
input_size=encoder_hidden_dim + embed_dim,
|
||||
hidden_size=hidden_dim,
|
||||
num_layers=1,
|
||||
bidirectional=False,
|
||||
)
|
||||
self.output_projection = nn.Linear(hidden_dim, len(dictionary))
|
||||
|
||||
# We now take an additional kwarg (*incremental_state*) for caching the
|
||||
# previous hidden and cell states.
|
||||
def forward(self, prev_output_tokens, encoder_out, incremental_state=None):
|
||||
if incremental_state is not None:
|
||||
# If the *incremental_state* argument is not ``None`` then we are
|
||||
# in incremental inference mode. While *prev_output_tokens* will
|
||||
# still contain the entire decoded prefix, we will only use the
|
||||
# last step and assume that the rest of the state is cached.
|
||||
prev_output_tokens = prev_output_tokens[:, -1:]
|
||||
|
||||
# This remains the same as before.
|
||||
bsz, tgt_len = prev_output_tokens.size()
|
||||
final_encoder_hidden = encoder_out['final_hidden']
|
||||
x = self.embed_tokens(prev_output_tokens)
|
||||
x = self.dropout(x)
|
||||
x = torch.cat(
|
||||
[x, final_encoder_hidden.unsqueeze(1).expand(bsz, tgt_len, -1)],
|
||||
dim=2,
|
||||
)
|
||||
|
||||
# We will now check the cache and load the cached previous hidden and
|
||||
# cell states, if they exist, otherwise we will initialize them to
|
||||
# zeros (as before). We will use the ``utils.get_incremental_state()``
|
||||
# and ``utils.set_incremental_state()`` helpers.
|
||||
initial_state = utils.get_incremental_state(
|
||||
self, incremental_state, 'prev_state',
|
||||
)
|
||||
if initial_state is None:
|
||||
# first time initialization, same as the original version
|
||||
initial_state = (
|
||||
final_encoder_hidden.unsqueeze(0), # hidden
|
||||
torch.zeros_like(final_encoder_hidden).unsqueeze(0), # cell
|
||||
)
|
||||
|
||||
# Run one step of our LSTM.
|
||||
output, latest_state = self.lstm(x.transpose(0, 1), initial_state)
|
||||
|
||||
# Update the cache with the latest hidden and cell states.
|
||||
utils.set_incremental_state(
|
||||
self, incremental_state, 'prev_state', latest_state,
|
||||
)
|
||||
|
||||
# This remains the same as before
|
||||
x = output.transpose(0, 1)
|
||||
x = self.output_projection(x)
|
||||
return x, None
|
||||
|
||||
# The ``FairseqIncrementalDecoder`` interface also requires implementing a
|
||||
# ``reorder_incremental_state()`` method, which is used during beam search
|
||||
# to select and reorder the incremental state.
|
||||
def reorder_incremental_state(self, incremental_state, new_order):
|
||||
# Load the cached state.
|
||||
prev_state = utils.get_incremental_state(
|
||||
self, incremental_state, 'prev_state',
|
||||
)
|
||||
|
||||
# Reorder batches according to *new_order*.
|
||||
reordered_state = (
|
||||
prev_state[0].index_select(1, new_order), # hidden
|
||||
prev_state[1].index_select(1, new_order), # cell
|
||||
)
|
||||
|
||||
# Update the cached state.
|
||||
utils.set_incremental_state(
|
||||
self, incremental_state, 'prev_state', reordered_state,
|
||||
)
|
||||
|
||||
Finally, we can rerun generation and observe the speedup:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# Before
|
||||
|
||||
> fairseq-generate data-bin/iwslt14.tokenized.de-en \
|
||||
--path checkpoints/checkpoint_best.pt \
|
||||
--beam 5 \
|
||||
--remove-bpe
|
||||
(...)
|
||||
| Translated 6750 sentences (153132 tokens) in 17.3s (389.12 sentences/s, 8827.68 tokens/s)
|
||||
| Generate test with beam=5: BLEU4 = 8.18, 38.8/12.1/4.7/2.0 (BP=1.000, ratio=1.066, syslen=139865, reflen=131146)
|
||||
|
||||
# After
|
||||
|
||||
> fairseq-generate data-bin/iwslt14.tokenized.de-en \
|
||||
--path checkpoints/checkpoint_best.pt \
|
||||
--beam 5 \
|
||||
--remove-bpe
|
||||
(...)
|
||||
| Translated 6750 sentences (153132 tokens) in 5.5s (1225.54 sentences/s, 27802.94 tokens/s)
|
||||
| Generate test with beam=5: BLEU4 = 8.18, 38.8/12.1/4.7/2.0 (BP=1.000, ratio=1.066, syslen=139865, reflen=131146)
|
||||
@@ -0,0 +1,2 @@
|
||||
!*/*.sh
|
||||
!*/*.md
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
try:
|
||||
from fairseq.version import __version__ # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
@@ -0,0 +1,90 @@
|
||||
# Adaptive Span
|
||||
|
||||
Adaptive Span is a novel self-attention mechanism that can learn its optimal
|
||||
attention span. This allows us to extend significantly the maximum context size
|
||||
used in Transformer, while maintaining control over their memory footprint
|
||||
and computational time. It uses the Truncated BPTT technique for training,
|
||||
as in [transformerXL](https://github.com/pytorch/fairseq/blob/master/examples/truncated_bptt/README.md).
|
||||
|
||||
Adaptive Span was introduced by paper:
|
||||
[Adaptive Attention Span in Transformers](https://arxiv.org/abs/1905.07799),
|
||||
which achieved state-of-the-art language modeling results at the time of publication.
|
||||
|
||||
We manage to reproduce their result in fairseq and keep most of the
|
||||
[original implementation](https://github.com/facebookresearch/adaptive-span) untouched.
|
||||
You can refer to the their sweep file as well if any combination of hyperparameter is not clear.
|
||||
|
||||
##### 0. Setup
|
||||
|
||||
First you need to process the Enwik8 dataset, we use the pre-tokenized dataset
|
||||
from [adaptive span paper](https://github.com/facebookresearch/adaptive-span/blob/master/get_data.sh).
|
||||
You can download the dataset, and then run:
|
||||
```bash
|
||||
fairseq-preprocess --only-source --trainpref ~/data/enwik8/train.txt \
|
||||
--validpref ~/data/enwik8/valid.txt --testpref ~/data/enwik8/test.txt \
|
||||
--destdir ~/data/enwik8/data-bin/ --joined-dictionary --workers 20
|
||||
```
|
||||
|
||||
##### 1. Train a Adaptive Span model on Enwik8
|
||||
|
||||
We will train a 12-layer Adaptive Span model following the [hyperparameters
|
||||
used in the original
|
||||
paper](https://github.com/facebookresearch/adaptive-span/blob/master/experiments/enwik8.sh).
|
||||
|
||||
The following command assumes 4 GPUs, so that the total batch size is 64
|
||||
sequences (4 x 16). Training should take 2-3 days on 4 V100 GPUs:
|
||||
```bash
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3 fairseq-train \
|
||||
--user-dir examples/adaptive_span \
|
||||
--data ~/data/enwik8/data-bin/ \
|
||||
--fp16 --fp16-no-flatten-grads --max-update 600000 \
|
||||
--task truncated_bptt_lm --tokens-per-sample 512 --arch adaptive_span \
|
||||
--n-layer 12 --d-model 512 --n-head 8 --d-inner 2048 --dropout 0.3 \
|
||||
--attn-span 8192 --optimizer adagrad_with_grad_clip --adagrad-clip 0.03 \
|
||||
--validate-interval-updates 1000 \
|
||||
--lr-scheduler fixed --warmup-updates 32000 --batch-size-valid 32 \
|
||||
--lr 0.07 --criterion adaptive_span_loss --batch-size 16 --update-freq 1 \
|
||||
--seed 2 --log-format json --log-interval 25 --aux-loss-scaler 5e-07
|
||||
```
|
||||
This should land around 1.05 on validation, 1.03 on test. You can lower the
|
||||
--aux-loss-scaler for better performance (longer span). It gives ~0.03 bpc
|
||||
improvement to the transformerXL baseline here.
|
||||
If training on a single GPU, set `--update-freq=4` to accumulate 4x gradients
|
||||
and simulate training on 4 GPUs.
|
||||
You can also reproduce the transformerXL result on enwik8 using this code base.
|
||||
It should land around 1.06 on test,matching the [original paper](https://github.com/kimiyoung/transformer-xl/blob/master/pytorch/run_enwik8_base.sh).
|
||||
You can try by
|
||||
```bash
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3 fairseq-train \
|
||||
--user-dir examples/truncated_bptt \
|
||||
~/data/enwik8/data-bin/ \
|
||||
--task truncated_bptt_lm --fp16 --max-update 400000 \
|
||||
--tokens-per-sample 512 --arch transformer_xl --n-layer 12 \
|
||||
--d-model 512 --n-head 8 --d-head 64 --d-inner 2048 --dropout 0.1 \
|
||||
--dropatt 0.0 --mem-len 512 --optimizer adam --clip-norm 0.25 \
|
||||
--lr-scheduler cosine --warmup-updates 0 \
|
||||
--lr 0.0 --lr 0.00025 --batch-size 15 \
|
||||
--update-freq 1 --seed 2 --log-format json --log-interval 25 \
|
||||
--fp16
|
||||
```
|
||||
|
||||
##### 2. Evaluate
|
||||
For Adaptive Span:
|
||||
```bash
|
||||
fairseq-eval-lm ~/data/enwik8/data-bin/ --path model/checkpoint_best.pt \
|
||||
--user-dir examples/adaptive_span \
|
||||
--task truncated_bptt_lm --batch-size 8 --tokens-per-sample 512 --gen-subset test
|
||||
```
|
||||
For Transformer-XL evaluation:
|
||||
```bash
|
||||
fairseq-eval-lm ~/data/enwik8/data-bin/ --path model/checkpoint_best.pt \
|
||||
--user-dir examples/truncated_bptt/ --task truncated_bptt_lm --batch-size 8 \
|
||||
--tokens-per-sample 80 \
|
||||
--model-overrides '{"mem_len":2100,"clamp_len":820,"same_length":True}' \
|
||||
--gen-subset valid
|
||||
```
|
||||
|
||||
*Note:* During training the model saw 512 tokens of context
|
||||
(``--tokens-per-sample=512``), with batch size 8. These settings match the evaluation
|
||||
settings from [the original
|
||||
paper](https://github.com/facebookresearch/adaptive-span/blob/master/experiments/enwik8.sh).
|
||||
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import importlib
|
||||
import os
|
||||
|
||||
# automatically import any Python files in the current directory
|
||||
cur_dir = os.path.dirname(__file__)
|
||||
for file in os.listdir(cur_dir):
|
||||
path = os.path.join(cur_dir, file)
|
||||
if (
|
||||
not file.startswith("_")
|
||||
and not file.startswith(".")
|
||||
and (file.endswith(".py") or os.path.isdir(path))
|
||||
):
|
||||
mod_name = file[: file.find(".py")] if file.endswith(".py") else file
|
||||
module = importlib.import_module(__name__ + "." + mod_name)
|
||||
@@ -0,0 +1,128 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from torch.optim import Adagrad
|
||||
|
||||
from fairseq.optim import LegacyFairseqOptimizer, register_optimizer
|
||||
|
||||
|
||||
@register_optimizer("adagrad_with_grad_clip")
|
||||
class FairseqAdagradWithGradClip(LegacyFairseqOptimizer):
|
||||
def __init__(self, args, params):
|
||||
super().__init__(args)
|
||||
self._optimizer = AdagradWithGradClip(params, **self.optimizer_config)
|
||||
|
||||
@staticmethod
|
||||
def add_args(parser):
|
||||
"""Add optimizer-specific arguments to the parser."""
|
||||
# fmt: off
|
||||
parser.add_argument('--weight-decay', '--wd', default=0.0, type=float, metavar='WD',
|
||||
help='weight decay')
|
||||
parser.add_argument('--adagrad-clip', default=0.0, type=float, metavar='D',
|
||||
help='internal grad clip')
|
||||
# fmt: on
|
||||
|
||||
@property
|
||||
def optimizer_config(self):
|
||||
"""
|
||||
Return a kwarg dictionary that will be used to override optimizer
|
||||
args stored in checkpoints. This allows us to load a checkpoint and
|
||||
resume training using a different set of optimizer args, e.g., with a
|
||||
different learning rate.
|
||||
"""
|
||||
return {
|
||||
"lr": self.args.lr[0],
|
||||
"weight_decay": self.args.weight_decay,
|
||||
"grad_clip": self.args.adagrad_clip,
|
||||
}
|
||||
|
||||
@property
|
||||
def supports_flat_params(self):
|
||||
return False
|
||||
|
||||
|
||||
def _clip_grad(clr, grad, group_grad_clip):
|
||||
if group_grad_clip > 0:
|
||||
norm = grad.norm(2).item()
|
||||
if norm > group_grad_clip:
|
||||
clr *= group_grad_clip / (norm + 1e-10)
|
||||
return clr
|
||||
|
||||
|
||||
class AdagradWithGradClip(Adagrad):
|
||||
"""Adagrad algorithm with custom gradient clipping"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
params,
|
||||
lr=1e-2,
|
||||
lr_decay=0,
|
||||
weight_decay=0,
|
||||
initial_accumulator_value=0,
|
||||
grad_clip=0,
|
||||
):
|
||||
Adagrad.__init__(
|
||||
self,
|
||||
params,
|
||||
lr=lr,
|
||||
lr_decay=lr_decay,
|
||||
weight_decay=weight_decay,
|
||||
initial_accumulator_value=initial_accumulator_value,
|
||||
)
|
||||
self.defaults["grad_clip"] = grad_clip
|
||||
self.param_groups[0].setdefault("grad_clip", grad_clip)
|
||||
|
||||
def step(self, closure=None):
|
||||
loss = None
|
||||
if closure is not None:
|
||||
loss = closure()
|
||||
|
||||
for group in self.param_groups:
|
||||
for p in group["params"]:
|
||||
if p.grad is None:
|
||||
continue
|
||||
|
||||
grad = p.grad.data
|
||||
state = self.state[p]
|
||||
|
||||
state["step"] += 1
|
||||
|
||||
if group["weight_decay"] != 0:
|
||||
if p.grad.data.is_sparse:
|
||||
raise RuntimeError(
|
||||
"weight_decay option is "
|
||||
"not compatible with sparse "
|
||||
"gradients"
|
||||
)
|
||||
grad = grad.add(group["weight_decay"], p.data)
|
||||
|
||||
clr = group["lr"] / (1 + (state["step"] - 1) * group["lr_decay"])
|
||||
|
||||
# clip
|
||||
clr = _clip_grad(clr=clr, grad=grad, group_grad_clip=group["grad_clip"])
|
||||
|
||||
if grad.is_sparse:
|
||||
# the update is non-linear so indices must be unique
|
||||
grad = grad.coalesce()
|
||||
grad_indices = grad._indices()
|
||||
grad_values = grad._values()
|
||||
size = grad.size()
|
||||
|
||||
def make_sparse(values):
|
||||
constructor = grad.new
|
||||
if grad_indices.dim() == 0 or values.dim() == 0:
|
||||
return constructor().resize_as_(grad)
|
||||
return constructor(grad_indices, values, size)
|
||||
|
||||
state["sum"].add_(make_sparse(grad_values.pow(2)))
|
||||
std = state["sum"]._sparse_mask(grad)
|
||||
std_values = std._values().sqrt_().add_(1e-10)
|
||||
p.data.add_(-clr, make_sparse(grad_values / std_values))
|
||||
else:
|
||||
state["sum"].addcmul_(1, grad, grad)
|
||||
std = state["sum"].sqrt().add_(1e-10)
|
||||
p.data.addcdiv_(-clr, grad, std)
|
||||
|
||||
return loss
|
||||
@@ -0,0 +1,160 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
class AdaptiveMask(nn.Module):
|
||||
"""Soft masking function for adaptive size.
|
||||
It masks out the last K values of an input. The masking value
|
||||
goes from 1 to 0 gradually, so K can be learned with
|
||||
back-propagation.
|
||||
Args:
|
||||
max_size: maximum size (i.e. input dimension)
|
||||
ramp_size: size of the ramp going from 0 to 1
|
||||
init_val: initial size proportion not to be masked out
|
||||
shape: learn multiple sizes independent of each other
|
||||
"""
|
||||
|
||||
def __init__(self, max_size, ramp_size, init_val=0, shape=(1,)):
|
||||
nn.Module.__init__(self)
|
||||
self._max_size = max_size
|
||||
self._ramp_size = ramp_size
|
||||
self.current_val = nn.Parameter(torch.zeros(*shape) + init_val)
|
||||
mask_template = torch.linspace(1 - max_size, 0, steps=max_size)
|
||||
self.register_buffer("mask_template", mask_template)
|
||||
|
||||
def forward(self, x):
|
||||
mask = self.mask_template.float() + self.current_val.float() * self._max_size
|
||||
mask = mask / self._ramp_size + 1
|
||||
mask = mask.clamp(0, 1)
|
||||
if x.size(-1) < self._max_size:
|
||||
# the input could have been trimmed beforehand to save computation
|
||||
mask = mask.narrow(-1, self._max_size - x.size(-1), x.size(-1))
|
||||
x = (x * mask).type_as(x)
|
||||
return x
|
||||
|
||||
def get_current_max_size(self, include_ramp=True):
|
||||
current_size = math.ceil(self.current_val.max().item() * self._max_size)
|
||||
if include_ramp:
|
||||
current_size += self._ramp_size
|
||||
current_size = max(0, min(self._max_size, current_size))
|
||||
return current_size
|
||||
|
||||
def get_current_avg_size(self, include_ramp=True):
|
||||
current_size = math.ceil(
|
||||
self.current_val.float().mean().item() * self._max_size
|
||||
)
|
||||
if include_ramp:
|
||||
current_size += self._ramp_size
|
||||
current_size = max(0, min(self._max_size, current_size))
|
||||
return current_size
|
||||
|
||||
def clamp_param(self):
|
||||
"""this need to be called after each update"""
|
||||
self.current_val.data.clamp_(0, 1)
|
||||
|
||||
|
||||
class AdaptiveSpan(nn.Module):
|
||||
"""Adaptive attention span for Transformerself.
|
||||
This module learns an attention span length from data for each
|
||||
self-attention head.
|
||||
Args:
|
||||
attn_span: maximum attention span
|
||||
adapt_span_loss: loss coefficient for the span length
|
||||
adapt_span_ramp: length of the masking ramp
|
||||
adapt_span_init: initial size ratio
|
||||
adapt_span_cache: adapt cache size to reduce memory usage
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
attn_span,
|
||||
adapt_span_ramp,
|
||||
adapt_span_init,
|
||||
n_head,
|
||||
adapt_span_layer,
|
||||
**kargs
|
||||
):
|
||||
nn.Module.__init__(self)
|
||||
self._max_span = attn_span
|
||||
self._n_head = n_head
|
||||
self._adapt_span_layer = adapt_span_layer
|
||||
if self._adapt_span_layer:
|
||||
self._mask = AdaptiveMask(
|
||||
max_size=self._max_span,
|
||||
ramp_size=adapt_span_ramp,
|
||||
init_val=adapt_span_init,
|
||||
)
|
||||
else:
|
||||
self._mask = AdaptiveMask(
|
||||
max_size=self._max_span,
|
||||
ramp_size=adapt_span_ramp,
|
||||
init_val=adapt_span_init,
|
||||
shape=(n_head, 1, 1),
|
||||
)
|
||||
|
||||
def forward(self, attn, normalize=True):
|
||||
"""mask attention with the right span"""
|
||||
# batch and head dimensions are merged together, so separate them first
|
||||
self.clamp_param()
|
||||
if self._adapt_span_layer:
|
||||
attn = self._mask(attn)
|
||||
else:
|
||||
B = attn.size(0) # batch size
|
||||
M = attn.size(1) # block size
|
||||
attn = attn.reshape(B // self._n_head, self._n_head, M, -1)
|
||||
attn = self._mask(attn)
|
||||
attn = attn.view(B, M, -1)
|
||||
return attn
|
||||
|
||||
def get_trim_len(self):
|
||||
"""how much of memory can be trimmed to reduce computation"""
|
||||
L = self._max_span
|
||||
trim_len = min(L - 1, L - self._mask.get_current_max_size())
|
||||
# too fine granularity might be bad for the memory management
|
||||
trim_len = math.floor(trim_len / 64) * 64
|
||||
return trim_len
|
||||
|
||||
def trim_memory(self, query, key, value, key_pe):
|
||||
"""trim out unnecessary memory beforehand to reduce computation"""
|
||||
trim_len = self.get_trim_len()
|
||||
cache_size = key.size(1) - query.size(1)
|
||||
trim_len_cache = trim_len - (self._max_span - cache_size)
|
||||
if trim_len_cache > 0:
|
||||
key = key[:, trim_len_cache:, :]
|
||||
value = value[:, trim_len_cache:, :]
|
||||
elif trim_len_cache < 0:
|
||||
# cache is too short! this happens when validation resumes
|
||||
# after a lot of updates.
|
||||
key = F.pad(key, [0, 0, -trim_len_cache, 0])
|
||||
value = F.pad(value, [0, 0, -trim_len_cache, 0])
|
||||
if trim_len > 0:
|
||||
if key_pe is not None:
|
||||
key_pe = key_pe[:, :, trim_len:]
|
||||
return key, value, key_pe
|
||||
|
||||
def get_cache_size(self):
|
||||
"""determine how long the cache should be"""
|
||||
trim_len = self.get_trim_len()
|
||||
# give a buffer of 64 steps since a span might increase
|
||||
# in future updates
|
||||
return min(self._max_span, self._max_span - trim_len + 64)
|
||||
|
||||
def get_loss(self):
|
||||
"""a loss term for regularizing the span length"""
|
||||
return self._max_span * self._mask.current_val.float().mean()
|
||||
|
||||
def get_current_max_span(self):
|
||||
return self._mask.get_current_max_size()
|
||||
|
||||
def get_current_avg_span(self):
|
||||
return self._mask.get_current_avg_size()
|
||||
|
||||
def clamp_param(self):
|
||||
self._mask.clamp_param()
|
||||
@@ -0,0 +1,106 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch.nn.functional as F
|
||||
from fairseq import metrics, utils
|
||||
from fairseq.criterions import register_criterion
|
||||
from fairseq.criterions.cross_entropy import CrossEntropyCriterion
|
||||
from fairseq.dataclass import FairseqDataclass
|
||||
from omegaconf import II
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdaptiveSpanCriterionConfig(FairseqDataclass):
|
||||
sentence_avg: bool = II("optimization.sentence_avg")
|
||||
|
||||
|
||||
@register_criterion("adaptive_span_loss", dataclass=AdaptiveSpanCriterionConfig)
|
||||
class AdaptiveSpanCriterion(CrossEntropyCriterion):
|
||||
def __init__(self, task, sentence_avg):
|
||||
super().__init__(task, sentence_avg)
|
||||
|
||||
def forward(self, model, sample, reduce=True):
|
||||
"""Compute the loss for the given sample.
|
||||
|
||||
Returns a tuple with three elements:
|
||||
1) the loss here is summed, different from the adaptive span code
|
||||
2) the sample size, which is used as the denominator for the gradient
|
||||
3) logging outputs to display while training
|
||||
"""
|
||||
net_output = model(**sample["net_input"])
|
||||
loss, aux_loss, avg_span, max_span = self.compute_loss(
|
||||
model, net_output, sample, reduce=reduce
|
||||
)
|
||||
sample_size = (
|
||||
sample["target"].size(0) if self.sentence_avg else sample["ntokens"]
|
||||
)
|
||||
loss /= sample_size
|
||||
total_loss = loss + aux_loss
|
||||
sample_size = 1
|
||||
|
||||
logging_output = {
|
||||
"loss": loss.data,
|
||||
"ntokens": sample["ntokens"],
|
||||
"nsentences": sample["target"].size(0),
|
||||
"sample_size": sample_size,
|
||||
"total_loss": total_loss.data,
|
||||
"avg_span": avg_span * sample_size,
|
||||
"max_span": max_span * sample_size,
|
||||
}
|
||||
return total_loss, sample_size, logging_output
|
||||
|
||||
def compute_loss(self, model, net_output, sample, reduce=True):
|
||||
loss, _ = super().compute_loss(model, net_output, sample, reduce)
|
||||
aux_loss = model.get_aux_loss()
|
||||
avg_span = model.get_current_avg_span()
|
||||
max_span = model.get_current_max_span()
|
||||
return loss, aux_loss, avg_span, max_span
|
||||
|
||||
@staticmethod
|
||||
def reduce_metrics(logging_outputs) -> None:
|
||||
"""Aggregate logging outputs from data parallel training."""
|
||||
loss_sum = sum(log.get("loss", 0) for log in logging_outputs)
|
||||
ntokens = sum(log.get("ntokens", 0) for log in logging_outputs)
|
||||
sample_size = sum(log.get("sample_size", 0) for log in logging_outputs)
|
||||
total_loss_sum = sum(log.get("total_loss", 0) for log in logging_outputs)
|
||||
avg_span_sum = sum(log.get("avg_span", 0) for log in logging_outputs)
|
||||
max_span_sum = sum(log.get("max_span", 0) for log in logging_outputs)
|
||||
|
||||
# we divide by log(2) to convert the loss from base e to base 2
|
||||
metrics.log_scalar(
|
||||
"loss", loss_sum / sample_size / math.log(2), sample_size, round=3
|
||||
)
|
||||
metrics.log_scalar("avg_span", avg_span_sum / sample_size, sample_size, round=3)
|
||||
metrics.log_scalar("max_span", max_span_sum / sample_size, sample_size, round=3)
|
||||
# total loss contains the L1 norm on adaptive-span
|
||||
metrics.log_scalar(
|
||||
"total_loss",
|
||||
total_loss_sum / sample_size / math.log(2),
|
||||
sample_size,
|
||||
round=3,
|
||||
)
|
||||
if sample_size != ntokens:
|
||||
metrics.log_scalar(
|
||||
"nll_loss", loss_sum / ntokens / math.log(2), ntokens, round=3
|
||||
)
|
||||
metrics.log_derived(
|
||||
"ppl", lambda meters: utils.get_perplexity(meters["nll_loss"].avg)
|
||||
)
|
||||
else:
|
||||
metrics.log_derived(
|
||||
"ppl", lambda meters: utils.get_perplexity(meters["loss"].avg)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def logging_outputs_can_be_summed() -> bool:
|
||||
"""
|
||||
Whether the logging outputs returned by `forward` can be summed
|
||||
across workers prior to calling `reduce_metrics`. Setting this
|
||||
to True will improves distributed training speed.
|
||||
"""
|
||||
return True
|
||||
@@ -0,0 +1,263 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from fairseq.modules.layer_norm import LayerNorm
|
||||
|
||||
from .adaptive_span_attention import AdaptiveSpan
|
||||
|
||||
# Size notations:
|
||||
# B = batch_size, H = d_model, M = block_size, L = attn_span
|
||||
|
||||
|
||||
def _skew(X, pad_value):
|
||||
"""shift every row 1 step to right"""
|
||||
# X = B x M x L
|
||||
B, M, L = X.size()
|
||||
X = F.pad(X, (0, M + 1), value=pad_value) # B x M x (L+M+1)
|
||||
X = X.view(B, -1) # B x ML+MM+M
|
||||
X = X[:, :-M] # B x ML+MM
|
||||
X = X.view(B, M, M + L) # B x M x L+M
|
||||
return X
|
||||
|
||||
|
||||
def _unskew(X):
|
||||
"""reverse _skew operation"""
|
||||
# X = B x M x L+M
|
||||
B, M, L = X.size()
|
||||
L -= M
|
||||
X = X.view(B, -1) # B x ML+MM
|
||||
X = F.pad(X, (0, M)) # B x ML+MM+M
|
||||
X = X.view(B, M, M + L + 1) # B x M x L+M+1
|
||||
X = X[:, :, :L] # B x M x L
|
||||
return X
|
||||
|
||||
|
||||
class SeqAttention(nn.Module):
|
||||
"""Sequential self-attention layer.
|
||||
Each token will attend to its previous fixed number of steps.
|
||||
Note that attention doesn't include the current step itself.
|
||||
"""
|
||||
|
||||
def __init__(self, d_model, n_head, attn_span, dropout, adapt_span_layer, **kargs):
|
||||
nn.Module.__init__(self)
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
self.d_model = d_model # size of a single head
|
||||
self.attn_span = attn_span
|
||||
self.adaptive_span = AdaptiveSpan(
|
||||
attn_span=attn_span,
|
||||
n_head=n_head,
|
||||
adapt_span_layer=adapt_span_layer,
|
||||
**kargs
|
||||
)
|
||||
|
||||
def forward(self, query, key, value, key_pe):
|
||||
# query size = B x M x H
|
||||
# key, value sizes = B x (M+L) x H
|
||||
|
||||
key, value, key_pe = self.adaptive_span.trim_memory(query, key, value, key_pe)
|
||||
|
||||
# compute attention from context
|
||||
# B x M (dest) x (M+L) (src)
|
||||
attn_cont = torch.matmul(query, key.transpose(-1, -2))
|
||||
attn_cont = _unskew(attn_cont) # B x M x L
|
||||
|
||||
# compute the effect of position embedding
|
||||
attn_pos = torch.matmul(query, key_pe) # B x M x L_pos
|
||||
attn = attn_cont + attn_pos
|
||||
|
||||
attn = attn / math.sqrt(self.d_model) # B x M X L_pos
|
||||
|
||||
attn = F.softmax(attn.float(), dim=-1).type_as(attn)
|
||||
|
||||
# trim attention lengths according to the learned span
|
||||
attn = self.adaptive_span(attn)
|
||||
|
||||
attn = self.dropout(attn) # B x M X L_pos
|
||||
|
||||
attn_cont = _skew(attn, 0) # B x M X (L+M)
|
||||
out = torch.matmul(attn_cont, value) # B x M x H
|
||||
return out
|
||||
|
||||
def get_cache_size(self):
|
||||
return self.adaptive_span.get_cache_size()
|
||||
|
||||
|
||||
class MultiHeadSeqAttention(nn.Module):
|
||||
def __init__(self, d_model, n_head, **kargs):
|
||||
nn.Module.__init__(self)
|
||||
assert d_model % n_head == 0
|
||||
self.n_head = n_head
|
||||
self.head_dim = d_model // n_head
|
||||
self.attn = SeqAttention(d_model=self.head_dim, n_head=n_head, **kargs)
|
||||
self.proj_query = nn.Linear(d_model, d_model, bias=False)
|
||||
nn.init.xavier_normal_(self.proj_query.weight)
|
||||
self.proj_out = nn.Linear(d_model, d_model, bias=False)
|
||||
nn.init.xavier_normal_(self.proj_out.weight)
|
||||
self.proj_val = nn.Linear(d_model, d_model, bias=False)
|
||||
nn.init.xavier_normal_(self.proj_val.weight)
|
||||
self.proj_key = nn.Linear(d_model, d_model, bias=False)
|
||||
nn.init.xavier_normal_(self.proj_key.weight)
|
||||
|
||||
def head_reshape(self, x):
|
||||
K = self.n_head
|
||||
D = self.head_dim
|
||||
x = x.view(x.size()[:-1] + (K, D)) # B x (M+L) x K x D
|
||||
x = x.transpose(1, 2).contiguous() # B x K x (M+L) x D
|
||||
x = x.view(-1, x.size(-2), x.size(-1)) # B_K x (M+L) x D
|
||||
return x
|
||||
|
||||
def forward(self, query, key, value, key_pe):
|
||||
B = query.size(0)
|
||||
K = self.n_head
|
||||
D = self.head_dim
|
||||
M = query.size(1)
|
||||
|
||||
query = self.proj_query(query)
|
||||
query = self.head_reshape(query)
|
||||
value = self.proj_val(value)
|
||||
value = self.head_reshape(value)
|
||||
key = self.proj_key(key)
|
||||
key = self.head_reshape(key)
|
||||
|
||||
out = self.attn(query, key, value, key_pe) # B_K x M x D
|
||||
out = out.view(B, K, M, D) # B x K x M x D
|
||||
out = out.transpose(1, 2).contiguous() # B x M x K x D
|
||||
out = out.view(B, M, -1) # B x M x K_D
|
||||
out = self.proj_out(out)
|
||||
return out
|
||||
|
||||
|
||||
class FeedForwardLayer(nn.Module):
|
||||
def __init__(self, d_model, d_inner, dropout, **kargs):
|
||||
nn.Module.__init__(self)
|
||||
self.fc1 = nn.Linear(d_model, d_inner)
|
||||
self.fc2 = nn.Linear(d_inner, d_model)
|
||||
nn.init.xavier_uniform_(self.fc1.weight)
|
||||
nn.init.xavier_uniform_(self.fc2.weight)
|
||||
self.dropout = nn.Dropout(dropout)
|
||||
|
||||
def forward(self, h):
|
||||
h1 = F.relu(self.fc1(h))
|
||||
h1 = self.dropout(h1)
|
||||
h2 = self.fc2(h1)
|
||||
return h2
|
||||
|
||||
|
||||
class TransformerSeqLayer(nn.Module):
|
||||
def __init__(self, d_model, **kargs):
|
||||
nn.Module.__init__(self)
|
||||
self.attn = MultiHeadSeqAttention(d_model=d_model, **kargs)
|
||||
self.norm1 = LayerNorm(d_model)
|
||||
self.ff = FeedForwardLayer(d_model=d_model, **kargs)
|
||||
self.norm2 = LayerNorm(d_model)
|
||||
|
||||
def forward(self, h, h_cache, key_pe):
|
||||
# h = B x M x H
|
||||
# h_cache = B x L x H
|
||||
h_all = torch.cat([h_cache, h], dim=1) # B x (M+L) x H
|
||||
attn_out = self.attn(h, h_all, h_all, key_pe)
|
||||
h = self.norm1(h + attn_out) # B x M x H
|
||||
if self.ff is not None:
|
||||
ff_out = self.ff(h)
|
||||
out = self.norm2(h + ff_out) # B x M x H
|
||||
else:
|
||||
out = h
|
||||
return out
|
||||
|
||||
def get_cache_size(self):
|
||||
return self.attn.attn.get_cache_size()
|
||||
|
||||
|
||||
class TransformerSeq(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size,
|
||||
d_model,
|
||||
n_head,
|
||||
n_layer,
|
||||
attn_span,
|
||||
emb_dropout,
|
||||
aux_loss_scaler,
|
||||
adapt_span_layer,
|
||||
**kargs
|
||||
):
|
||||
nn.Module.__init__(self)
|
||||
# token embeddings
|
||||
self.in_emb = nn.Embedding(vocab_size, d_model)
|
||||
nn.init.normal_(self.in_emb.weight, mean=0, std=d_model ** -0.5)
|
||||
self.out_emb = nn.Linear(d_model, vocab_size)
|
||||
self.aux_loss_scaler = aux_loss_scaler
|
||||
if emb_dropout > 0:
|
||||
self.emb_dropout = nn.Dropout(emb_dropout)
|
||||
else:
|
||||
self.emb_dropout = None
|
||||
# position embeddings
|
||||
self.key_pe = nn.Parameter(torch.randn(1, d_model // n_head, attn_span))
|
||||
|
||||
self.layers = nn.ModuleList()
|
||||
self.layers.extend(
|
||||
TransformerSeqLayer(
|
||||
d_model=d_model,
|
||||
n_head=n_head,
|
||||
attn_span=attn_span,
|
||||
adapt_span_layer=adapt_span_layer,
|
||||
**kargs
|
||||
)
|
||||
for _ in range(n_layer)
|
||||
)
|
||||
|
||||
def forward(self, x, h_cache, target=None):
|
||||
# x size = B x M
|
||||
block_size = x.size(1)
|
||||
h = self.in_emb(x) # B x M x H
|
||||
if self.emb_dropout is not None:
|
||||
h = self.emb_dropout(h)
|
||||
|
||||
h_cache_next = []
|
||||
for l, layer in enumerate(self.layers):
|
||||
cache_size = layer.attn.attn.get_cache_size()
|
||||
if cache_size > block_size:
|
||||
h_cache_next_l = torch.cat(
|
||||
[h_cache[l][:, -cache_size + block_size :, :], h], dim=1
|
||||
).detach()
|
||||
else:
|
||||
h_cache_next_l = h[:, -cache_size:, :].detach()
|
||||
h_cache_next.append(h_cache_next_l)
|
||||
h = layer(h, h_cache[l], self.key_pe) # B x M x H
|
||||
|
||||
if self.emb_dropout is not None:
|
||||
h = self.emb_dropout(h)
|
||||
|
||||
out = F.log_softmax(self.out_emb(h).float(), dim=-1).type_as(h)
|
||||
dummy_loss = None
|
||||
|
||||
return out, h_cache_next, dummy_loss
|
||||
|
||||
def get_aux_loss(self):
|
||||
loss = 0.0
|
||||
for layer in self.layers:
|
||||
loss += layer.attn.attn.adaptive_span.get_loss()
|
||||
return self.aux_loss_scaler * loss
|
||||
|
||||
def get_current_max_span(self):
|
||||
max_span = 0.0
|
||||
for layer in self.layers:
|
||||
max_span = max(
|
||||
max_span, layer.attn.attn.adaptive_span.get_current_max_span()
|
||||
)
|
||||
return max_span
|
||||
|
||||
def get_current_avg_span(self):
|
||||
avg_span = 0.0
|
||||
for layer in self.layers:
|
||||
avg_span += layer.attn.attn.adaptive_span.get_current_avg_span()
|
||||
return avg_span / len(self.layers)
|
||||
@@ -0,0 +1,145 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import torch
|
||||
from fairseq.dataclass import FairseqDataclass
|
||||
from fairseq.models import (
|
||||
FairseqIncrementalDecoder,
|
||||
FairseqLanguageModel,
|
||||
register_model,
|
||||
)
|
||||
from .adaptive_span_model import TransformerSeq as AdaptiveSpanTransformerModel
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdaptiveSpanSmallConfig(FairseqDataclass):
|
||||
# defaults come from https://github.com/facebookresearch/adaptive-span/blob/master/experiments/enwik8_small.sh
|
||||
vocab_size: int = 50
|
||||
d_model: int = 256
|
||||
n_head: int = 4
|
||||
d_inner: int = 1024
|
||||
n_layer: int = 8
|
||||
attn_span: int = 1024
|
||||
dropout: float = 0.0
|
||||
emb_dropout: float = 0.0
|
||||
adapt_span_ramp: int = 32
|
||||
adapt_span_init: float = 0.0
|
||||
aux_loss_scaler: float = 0.000002
|
||||
adapt_span_layer: bool = False
|
||||
|
||||
|
||||
@register_model("adaptive_span", dataclass=AdaptiveSpanSmallConfig)
|
||||
class AdaptiveSpanTransformer(FairseqLanguageModel):
|
||||
@classmethod
|
||||
def build_model(cls, cfg: AdaptiveSpanSmallConfig, task):
|
||||
return cls(AdaptiveSpanDecoder(cfg, task))
|
||||
|
||||
def get_aux_loss(self):
|
||||
return self.decoder.get_aux_loss()
|
||||
|
||||
def get_current_max_span(self):
|
||||
return self.decoder.get_current_max_span()
|
||||
|
||||
def get_current_avg_span(self):
|
||||
return self.decoder.get_current_avg_span()
|
||||
|
||||
|
||||
class AdaptiveSpanDecoder(FairseqIncrementalDecoder):
|
||||
def __init__(self, cfg, task):
|
||||
|
||||
super().__init__(task.target_dictionary)
|
||||
|
||||
self.config = cfg
|
||||
config = AdaptiveSpanSmallConfig(
|
||||
vocab_size=len(task.target_dictionary),
|
||||
d_model=cfg.d_model,
|
||||
n_head=cfg.n_head,
|
||||
d_inner=cfg.d_inner,
|
||||
n_layer=cfg.n_layer,
|
||||
attn_span=cfg.attn_span,
|
||||
dropout=cfg.dropout,
|
||||
emb_dropout=cfg.emb_dropout,
|
||||
adapt_span_ramp=cfg.adapt_span_ramp,
|
||||
adapt_span_init=cfg.adapt_span_init,
|
||||
aux_loss_scaler=cfg.aux_loss_scaler,
|
||||
adapt_span_layer=cfg.adapt_span_layer,
|
||||
)
|
||||
logger.info(config)
|
||||
self.model = AdaptiveSpanTransformerModel(**config.__dict__)
|
||||
|
||||
self._mems = None
|
||||
|
||||
def forward(
|
||||
self,
|
||||
src_tokens,
|
||||
incremental_state: Optional[Dict[str, List[torch.Tensor]]] = None,
|
||||
encoder_out=None,
|
||||
):
|
||||
bsz = src_tokens.size(0)
|
||||
if incremental_state is not None: # used during inference
|
||||
mems = self.get_incremental_state("mems")
|
||||
src_tokens = src_tokens[:, -1:] # only keep the most recent token
|
||||
else:
|
||||
mems = self._mems
|
||||
|
||||
if mems is None:
|
||||
# first time init
|
||||
mems = self.init_hid_cache(bsz)
|
||||
output = self.model(x=src_tokens, h_cache=mems,)
|
||||
if incremental_state is not None:
|
||||
self.set_incremental_state(incremental_state, "mems", output[1])
|
||||
else:
|
||||
self._mems = output[1]
|
||||
return (output[0],)
|
||||
|
||||
def max_positions(self):
|
||||
return self.config.attn_span
|
||||
|
||||
def init_hid_cache(self, batch_sz):
|
||||
hid = []
|
||||
for layer in self.model.layers:
|
||||
param = next(self.model.parameters())
|
||||
h = torch.zeros(
|
||||
batch_sz,
|
||||
layer.get_cache_size(),
|
||||
self.config.d_model,
|
||||
dtype=param.dtype,
|
||||
device=param.device,
|
||||
)
|
||||
hid.append(h)
|
||||
return hid
|
||||
|
||||
def get_aux_loss(self):
|
||||
return self.model.get_aux_loss()
|
||||
|
||||
def get_current_max_span(self):
|
||||
return self.model.get_current_max_span()
|
||||
|
||||
def get_current_avg_span(self):
|
||||
return self.model.get_current_avg_span()
|
||||
|
||||
def reorder_incremental_state(
|
||||
self,
|
||||
incremental_state: Dict[str, Dict[str, Optional[torch.Tensor]]],
|
||||
new_order: torch.Tensor,
|
||||
):
|
||||
"""Reorder incremental state.
|
||||
|
||||
This will be called when the order of the input has changed from the
|
||||
previous time step. A typical use case is beam search, where the input
|
||||
order changes between time steps based on the selection of beams.
|
||||
"""
|
||||
raise NotImplementedError("This is required for generation/beam search")
|
||||
# mems = self.get_incremental_state(incremental_state, "mems")
|
||||
# if mems is not None:
|
||||
# new_mems = [mems_i.index_select(1, new_order) for mems_i in mems]
|
||||
# self.set_incremental_state(incremental_state, "mems", new_mems)
|
||||
@@ -0,0 +1,280 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import torch
|
||||
from fairseq import distributed_utils as dist_utils, utils
|
||||
from fairseq.data import (
|
||||
Dictionary,
|
||||
TokenBlockDataset,
|
||||
data_utils,
|
||||
iterators,
|
||||
)
|
||||
from fairseq.dataclass import FairseqDataclass
|
||||
from fairseq.tasks import FairseqTask, register_task
|
||||
from omegaconf import II
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TruncatedBPTTLMConfig(FairseqDataclass):
|
||||
data: str = field(default="???", metadata={"help": "path to data directory"})
|
||||
tokens_per_sample: int = field(
|
||||
default=1024,
|
||||
metadata={"help": "max number of tokens per sequence"},
|
||||
)
|
||||
batch_size: int = II("dataset.batch_size")
|
||||
# Some models use *max_target_positions* to know how many positional
|
||||
# embeddings to learn. We use II(...) to make it default to
|
||||
# *tokens_per_sample*, but in principle there could be more positional
|
||||
# embeddings than tokens in a single batch. This may also be irrelevant for
|
||||
# custom model implementations.
|
||||
max_target_positions: int = II("task.tokens_per_sample")
|
||||
# these will be populated automatically if not provided
|
||||
data_parallel_rank: Optional[int] = None
|
||||
data_parallel_size: Optional[int] = None
|
||||
|
||||
|
||||
@register_task("truncated_bptt_lm", dataclass=TruncatedBPTTLMConfig)
|
||||
class TruncatedBPTTLMTask(FairseqTask):
|
||||
def __init__(self, cfg: TruncatedBPTTLMConfig):
|
||||
super().__init__(cfg)
|
||||
|
||||
if cfg.data_parallel_rank is None or cfg.data_parallel_size is None:
|
||||
if torch.distributed.is_initialized():
|
||||
cfg.data_parallel_rank = dist_utils.get_data_parallel_rank()
|
||||
cfg.data_parallel_size = dist_utils.get_data_parallel_world_size()
|
||||
else:
|
||||
cfg.data_parallel_rank = 0
|
||||
cfg.data_parallel_size = 1
|
||||
|
||||
# load the dictionary
|
||||
paths = utils.split_paths(cfg.data)
|
||||
assert len(paths) > 0
|
||||
self.dictionary = Dictionary.load(os.path.join(paths[0], "dict.txt"))
|
||||
logger.info("dictionary: {} types".format(len(self.dictionary)))
|
||||
|
||||
def load_dataset(self, split, epoch=1, combine=False, **kwargs):
|
||||
"""Load a given dataset split (e.g., train, valid, test)"""
|
||||
|
||||
# support sharded datasets
|
||||
paths = utils.split_paths(self.cfg.data)
|
||||
assert len(paths) > 0
|
||||
data_path = paths[(epoch - 1) % len(paths)]
|
||||
split_path = os.path.join(data_path, split)
|
||||
|
||||
# each element of *data* will be a tensorized line from the original
|
||||
# text dataset, similar to ``open(split_path).readlines()``
|
||||
data = data_utils.load_indexed_dataset(
|
||||
split_path, self.dictionary, combine=combine
|
||||
)
|
||||
if data is None:
|
||||
raise FileNotFoundError(
|
||||
"Dataset not found: {} ({})".format(split, split_path)
|
||||
)
|
||||
|
||||
# this is similar to ``data.view(-1).split(tokens_per_sample)``
|
||||
data = TokenBlockDataset(
|
||||
data,
|
||||
data.sizes,
|
||||
block_size=self.cfg.tokens_per_sample,
|
||||
pad=None, # unused
|
||||
eos=None, # unused
|
||||
break_mode="none",
|
||||
)
|
||||
|
||||
self.datasets[split] = TruncatedBPTTDataset(
|
||||
data=data,
|
||||
bsz_per_shard=self.cfg.batch_size,
|
||||
shard_id=self.cfg.data_parallel_rank,
|
||||
num_shards=self.cfg.data_parallel_size,
|
||||
)
|
||||
|
||||
def dataset(self, split):
|
||||
return self.datasets[split]
|
||||
|
||||
def get_batch_iterator(
|
||||
self, dataset, num_workers=0, epoch=1, data_buffer_size=0, **kwargs
|
||||
):
|
||||
return iterators.EpochBatchIterator(
|
||||
dataset=dataset,
|
||||
collate_fn=self._collate_fn,
|
||||
num_workers=num_workers,
|
||||
epoch=epoch,
|
||||
buffer_size=data_buffer_size,
|
||||
# we don't use the batching functionality from EpochBatchIterator;
|
||||
# instead every item in *dataset* is a whole batch
|
||||
batch_sampler=[[i] for i in range(len(dataset))],
|
||||
disable_shuffling=True,
|
||||
)
|
||||
|
||||
def _collate_fn(self, items: List[List[torch.Tensor]]):
|
||||
# we don't use fairseq's batching functionality, so we expect a single
|
||||
# Tensor of type List[torch.Tensor]
|
||||
assert len(items) == 1
|
||||
|
||||
# item will have shape B x T (the last batch may have length < T)
|
||||
id, item = items[0]
|
||||
item = data_utils.collate_tokens(item, pad_idx=self.source_dictionary.pad())
|
||||
B, T = item.size()
|
||||
|
||||
# shift item one position over and append a padding token for the target
|
||||
target = torch.nn.functional.pad(
|
||||
item[:, 1:], (0, 1, 0, 0), value=self.target_dictionary.pad()
|
||||
)
|
||||
|
||||
# fairseq expects batches to have the following structure
|
||||
return {
|
||||
"id": torch.tensor([id]*item.size(0)),
|
||||
"net_input": {
|
||||
"src_tokens": item,
|
||||
},
|
||||
"target": target,
|
||||
"nsentences": item.size(0),
|
||||
"ntokens": item.numel(),
|
||||
}
|
||||
|
||||
def build_dataset_for_inference(
|
||||
self, src_tokens: List[torch.Tensor], src_lengths: List[int], **kwargs
|
||||
) -> torch.utils.data.Dataset:
|
||||
eos = self.source_dictionary.eos()
|
||||
dataset = TokenBlockDataset(
|
||||
src_tokens,
|
||||
src_lengths,
|
||||
block_size=None, # ignored for "eos" break mode
|
||||
pad=self.source_dictionary.pad(),
|
||||
eos=eos,
|
||||
break_mode="eos",
|
||||
)
|
||||
|
||||
class Dataset(torch.utils.data.Dataset):
|
||||
def __getitem__(self, i):
|
||||
item = dataset[i]
|
||||
if item[-1] == eos:
|
||||
# remove eos to support generating with a prefix
|
||||
item = item[:-1]
|
||||
return (i, [item])
|
||||
|
||||
def __len__(self):
|
||||
return len(dataset)
|
||||
|
||||
return Dataset()
|
||||
|
||||
def inference_step(
|
||||
self, generator, models, sample, prefix_tokens=None, constraints=None
|
||||
):
|
||||
with torch.no_grad():
|
||||
if constraints is not None:
|
||||
raise NotImplementedError
|
||||
|
||||
# SequenceGenerator doesn't use *src_tokens* directly, we need to
|
||||
# pass the *prefix_tokens* argument instead.
|
||||
if prefix_tokens is None and sample["net_input"]["src_tokens"].nelement():
|
||||
prefix_tokens = sample["net_input"]["src_tokens"]
|
||||
|
||||
# begin generation with the end-of-sentence token
|
||||
bos_token = self.source_dictionary.eos()
|
||||
|
||||
return generator.generate(
|
||||
models, sample, prefix_tokens=prefix_tokens, bos_token=bos_token
|
||||
)
|
||||
|
||||
def eval_lm_dataloader(
|
||||
self,
|
||||
dataset,
|
||||
max_tokens: Optional[int] = 36000,
|
||||
batch_size: Optional[int] = None,
|
||||
max_positions: Optional[int] = None,
|
||||
num_shards: int = 1,
|
||||
shard_id: int = 0,
|
||||
num_workers: int = 1,
|
||||
data_buffer_size: int = 10,
|
||||
context_window: int = 0,
|
||||
):
|
||||
if context_window > 0:
|
||||
raise NotImplementedError(
|
||||
"Transformer-XL doesn't need --context-window, try "
|
||||
"--model-overrides '{\"mem_len\":42}' instead "
|
||||
)
|
||||
return self.get_batch_iterator(
|
||||
dataset=dataset,
|
||||
max_tokens=max_tokens,
|
||||
max_sentences=batch_size,
|
||||
max_positions=max_positions,
|
||||
ignore_invalid_inputs=True,
|
||||
num_shards=num_shards,
|
||||
shard_id=shard_id,
|
||||
num_workers=num_workers,
|
||||
data_buffer_size=data_buffer_size,
|
||||
).next_epoch_itr(shuffle=False)
|
||||
|
||||
@property
|
||||
def source_dictionary(self):
|
||||
return self.dictionary
|
||||
|
||||
@property
|
||||
def target_dictionary(self):
|
||||
return self.dictionary
|
||||
|
||||
|
||||
class TruncatedBPTTDataset(torch.utils.data.Dataset):
|
||||
def __init__(
|
||||
self,
|
||||
data: List[torch.Tensor], # ordered list of items
|
||||
bsz_per_shard, # number of items processed per GPUs per forward
|
||||
shard_id, # current GPU ID
|
||||
num_shards, # number of GPUs
|
||||
):
|
||||
super().__init__()
|
||||
self.data = data
|
||||
|
||||
def batchify(data, bsz):
|
||||
# Work out how cleanly we can divide the dataset into bsz parts.
|
||||
nbatch = data.size(0) // bsz
|
||||
# Trim off any extra elements that wouldn't cleanly fit (remainders).
|
||||
data = data.narrow(0, 0, nbatch * bsz)
|
||||
# Evenly divide the data across the bsz batches.
|
||||
data = data.view(bsz, -1).contiguous()
|
||||
return data
|
||||
|
||||
# total number of sequences processed by all GPUs in each forward pass
|
||||
global_batch_size = bsz_per_shard * num_shards
|
||||
|
||||
"""
|
||||
With a 16 item dataset, bsz_per_shard=2 and num_shards=3,
|
||||
*indices* might look like:
|
||||
|
||||
indices = [[0, 1],
|
||||
[2, 3],
|
||||
[4, 5],
|
||||
[6, 7],
|
||||
[8, 9],
|
||||
[10, 11]]
|
||||
|
||||
The size of the TruncatedBPTTDataset instance will be 2,
|
||||
and shard 1 will see items:
|
||||
|
||||
[(0, [data[4], data[6]]),
|
||||
(1, [data[5], data[7]])]
|
||||
"""
|
||||
indices = batchify(torch.arange(len(data)), global_batch_size)
|
||||
assert indices.size(0) == global_batch_size
|
||||
|
||||
self.my_indices = indices[
|
||||
shard_id * bsz_per_shard : (shard_id + 1) * bsz_per_shard
|
||||
]
|
||||
assert self.my_indices.size(0) == bsz_per_shard
|
||||
|
||||
def __len__(self):
|
||||
return self.my_indices.size(1)
|
||||
|
||||
def __getitem__(self, i) -> Tuple[int, List[torch.Tensor]]:
|
||||
return (i, [self.data[idx] for idx in self.my_indices[:, i]])
|
||||
@@ -0,0 +1,297 @@
|
||||
# Understanding Back-Translation at Scale (Edunov et al., 2018)
|
||||
|
||||
This page includes pre-trained models from the paper [Understanding Back-Translation at Scale (Edunov et al., 2018)](https://arxiv.org/abs/1808.09381).
|
||||
|
||||
## Pre-trained models
|
||||
|
||||
Model | Description | Dataset | Download
|
||||
---|---|---|---
|
||||
`transformer.wmt18.en-de` | Transformer <br> ([Edunov et al., 2018](https://arxiv.org/abs/1808.09381)) <br> WMT'18 winner | [WMT'18 English-German](http://www.statmt.org/wmt18/translation-task.html) | [download (.tar.gz)](https://dl.fbaipublicfiles.com/fairseq/models/wmt18.en-de.ensemble.tar.gz) <br> See NOTE in the archive
|
||||
|
||||
## Example usage (torch.hub)
|
||||
|
||||
We require a few additional Python dependencies for preprocessing:
|
||||
```bash
|
||||
pip install subword_nmt sacremoses
|
||||
```
|
||||
|
||||
Then to generate translations from the full model ensemble:
|
||||
```python
|
||||
import torch
|
||||
|
||||
# List available models
|
||||
torch.hub.list('pytorch/fairseq') # [..., 'transformer.wmt18.en-de', ... ]
|
||||
|
||||
# Load the WMT'18 En-De ensemble
|
||||
en2de_ensemble = torch.hub.load(
|
||||
'pytorch/fairseq', 'transformer.wmt18.en-de',
|
||||
checkpoint_file='wmt18.model1.pt:wmt18.model2.pt:wmt18.model3.pt:wmt18.model4.pt:wmt18.model5.pt',
|
||||
tokenizer='moses', bpe='subword_nmt')
|
||||
|
||||
# The ensemble contains 5 models
|
||||
len(en2de_ensemble.models)
|
||||
# 5
|
||||
|
||||
# Translate
|
||||
en2de_ensemble.translate('Hello world!')
|
||||
# 'Hallo Welt!'
|
||||
```
|
||||
|
||||
## Training your own model (WMT'18 English-German)
|
||||
|
||||
The following instructions can be adapted to reproduce the models from the paper.
|
||||
|
||||
|
||||
#### Step 1. Prepare parallel data and optionally train a baseline (English-German) model
|
||||
|
||||
First download and preprocess the data:
|
||||
```bash
|
||||
# Download and prepare the data
|
||||
cd examples/backtranslation/
|
||||
bash prepare-wmt18en2de.sh
|
||||
cd ../..
|
||||
|
||||
# Binarize the data
|
||||
TEXT=examples/backtranslation/wmt18_en_de
|
||||
fairseq-preprocess \
|
||||
--joined-dictionary \
|
||||
--source-lang en --target-lang de \
|
||||
--trainpref $TEXT/train --validpref $TEXT/valid --testpref $TEXT/test \
|
||||
--destdir data-bin/wmt18_en_de --thresholdtgt 0 --thresholdsrc 0 \
|
||||
--workers 20
|
||||
|
||||
# Copy the BPE code into the data-bin directory for future use
|
||||
cp examples/backtranslation/wmt18_en_de/code data-bin/wmt18_en_de/code
|
||||
```
|
||||
|
||||
(Optionally) Train a baseline model (English-German) using just the parallel data:
|
||||
```bash
|
||||
CHECKPOINT_DIR=checkpoints_en_de_parallel
|
||||
fairseq-train --fp16 \
|
||||
data-bin/wmt18_en_de \
|
||||
--source-lang en --target-lang de \
|
||||
--arch transformer_wmt_en_de_big --share-all-embeddings \
|
||||
--dropout 0.3 --weight-decay 0.0 \
|
||||
--criterion label_smoothed_cross_entropy --label-smoothing 0.1 \
|
||||
--optimizer adam --adam-betas '(0.9, 0.98)' --clip-norm 0.0 \
|
||||
--lr 0.001 --lr-scheduler inverse_sqrt --warmup-updates 4000 \
|
||||
--max-tokens 3584 --update-freq 16 \
|
||||
--max-update 30000 \
|
||||
--save-dir $CHECKPOINT_DIR
|
||||
# Note: the above command assumes 8 GPUs. Adjust `--update-freq` if you have a
|
||||
# different number of GPUs.
|
||||
```
|
||||
|
||||
Average the last 10 checkpoints:
|
||||
```bash
|
||||
python scripts/average_checkpoints.py \
|
||||
--inputs $CHECKPOINT_DIR \
|
||||
--num-epoch-checkpoints 10 \
|
||||
--output $CHECKPOINT_DIR/checkpoint.avg10.pt
|
||||
```
|
||||
|
||||
Evaluate BLEU:
|
||||
```bash
|
||||
# tokenized BLEU on newstest2017:
|
||||
bash examples/backtranslation/tokenized_bleu.sh \
|
||||
wmt17 \
|
||||
en-de \
|
||||
data-bin/wmt18_en_de \
|
||||
data-bin/wmt18_en_de/code \
|
||||
$CHECKPOINT_DIR/checkpoint.avg10.pt
|
||||
# BLEU4 = 29.57, 60.9/35.4/22.9/15.5 (BP=1.000, ratio=1.014, syslen=63049, reflen=62152)
|
||||
# compare to 29.46 in Table 1, which is also for tokenized BLEU
|
||||
|
||||
# generally it's better to report (detokenized) sacrebleu though:
|
||||
bash examples/backtranslation/sacrebleu.sh \
|
||||
wmt17 \
|
||||
en-de \
|
||||
data-bin/wmt18_en_de \
|
||||
data-bin/wmt18_en_de/code \
|
||||
$CHECKPOINT_DIR/checkpoint.avg10.pt
|
||||
# BLEU+case.mixed+lang.en-de+numrefs.1+smooth.exp+test.wmt17+tok.13a+version.1.4.3 = 29.0 60.6/34.7/22.4/14.9 (BP = 1.000 ratio = 1.013 hyp_len = 62099 ref_len = 61287)
|
||||
```
|
||||
|
||||
|
||||
#### Step 2. Back-translate monolingual German data
|
||||
|
||||
Train a reverse model (German-English) to do the back-translation:
|
||||
```bash
|
||||
CHECKPOINT_DIR=checkpoints_de_en_parallel
|
||||
fairseq-train --fp16 \
|
||||
data-bin/wmt18_en_de \
|
||||
--source-lang de --target-lang en \
|
||||
--arch transformer_wmt_en_de_big --share-all-embeddings \
|
||||
--dropout 0.3 --weight-decay 0.0 \
|
||||
--criterion label_smoothed_cross_entropy --label-smoothing 0.1 \
|
||||
--optimizer adam --adam-betas '(0.9, 0.98)' --clip-norm 0.0 \
|
||||
--lr 0.001 --lr-scheduler inverse_sqrt --warmup-updates 4000 \
|
||||
--max-tokens 3584 --update-freq 16 \
|
||||
--max-update 30000 \
|
||||
--save-dir $CHECKPOINT_DIR
|
||||
# Note: the above command assumes 8 GPUs. Adjust `--update-freq` if you have a
|
||||
# different number of GPUs.
|
||||
```
|
||||
|
||||
Let's evaluate the back-translation (BT) model to make sure it is well trained:
|
||||
```bash
|
||||
bash examples/backtranslation/sacrebleu.sh \
|
||||
wmt17 \
|
||||
de-en \
|
||||
data-bin/wmt18_en_de \
|
||||
data-bin/wmt18_en_de/code \
|
||||
$CHECKPOINT_DIR/checkpoint_best.py
|
||||
# BLEU+case.mixed+lang.de-en+numrefs.1+smooth.exp+test.wmt17+tok.13a+version.1.4.3 = 34.9 66.9/41.8/28.5/19.9 (BP = 0.983 ratio = 0.984 hyp_len = 63342 ref_len = 64399)
|
||||
# compare to the best system from WMT'17 which scored 35.1: http://matrix.statmt.org/matrix/systems_list/1868
|
||||
```
|
||||
|
||||
Next prepare the monolingual data:
|
||||
```bash
|
||||
# Download and prepare the monolingual data
|
||||
# By default the script samples 25M monolingual sentences, which after
|
||||
# deduplication should be just over 24M sentences. These are split into 25
|
||||
# shards, each with 1M sentences (except for the last shard).
|
||||
cd examples/backtranslation/
|
||||
bash prepare-de-monolingual.sh
|
||||
cd ../..
|
||||
|
||||
# Binarize each shard of the monolingual data
|
||||
TEXT=examples/backtranslation/wmt18_de_mono
|
||||
for SHARD in $(seq -f "%02g" 0 24); do \
|
||||
fairseq-preprocess \
|
||||
--only-source \
|
||||
--source-lang de --target-lang en \
|
||||
--joined-dictionary \
|
||||
--srcdict data-bin/wmt18_en_de/dict.de.txt \
|
||||
--testpref $TEXT/bpe.monolingual.dedup.${SHARD} \
|
||||
--destdir data-bin/wmt18_de_mono/shard${SHARD} \
|
||||
--workers 20; \
|
||||
cp data-bin/wmt18_en_de/dict.en.txt data-bin/wmt18_de_mono/shard${SHARD}/; \
|
||||
done
|
||||
```
|
||||
|
||||
Now we're ready to perform back-translation over the monolingual data. The
|
||||
following command generates via sampling, but it's possible to use greedy
|
||||
decoding (`--beam 1`), beam search (`--beam 5`),
|
||||
top-k sampling (`--sampling --beam 1 --sampling-topk 10`), etc.:
|
||||
```bash
|
||||
mkdir backtranslation_output
|
||||
for SHARD in $(seq -f "%02g" 0 24); do \
|
||||
fairseq-generate --fp16 \
|
||||
data-bin/wmt18_de_mono/shard${SHARD} \
|
||||
--path $CHECKPOINT_DIR/checkpoint_best.pt \
|
||||
--skip-invalid-size-inputs-valid-test \
|
||||
--max-tokens 4096 \
|
||||
--sampling --beam 1 \
|
||||
> backtranslation_output/sampling.shard${SHARD}.out; \
|
||||
done
|
||||
```
|
||||
|
||||
After BT, use the `extract_bt_data.py` script to re-combine the shards, extract
|
||||
the back-translations and apply length ratio filters:
|
||||
```bash
|
||||
python examples/backtranslation/extract_bt_data.py \
|
||||
--minlen 1 --maxlen 250 --ratio 1.5 \
|
||||
--output backtranslation_output/bt_data --srclang en --tgtlang de \
|
||||
backtranslation_output/sampling.shard*.out
|
||||
|
||||
# Ensure lengths are the same:
|
||||
# wc -l backtranslation_output/bt_data.{en,de}
|
||||
# 21795614 backtranslation_output/bt_data.en
|
||||
# 21795614 backtranslation_output/bt_data.de
|
||||
# 43591228 total
|
||||
```
|
||||
|
||||
Binarize the filtered BT data and combine it with the parallel data:
|
||||
```bash
|
||||
TEXT=backtranslation_output
|
||||
fairseq-preprocess \
|
||||
--source-lang en --target-lang de \
|
||||
--joined-dictionary \
|
||||
--srcdict data-bin/wmt18_en_de/dict.en.txt \
|
||||
--trainpref $TEXT/bt_data \
|
||||
--destdir data-bin/wmt18_en_de_bt \
|
||||
--workers 20
|
||||
|
||||
# We want to train on the combined data, so we'll symlink the parallel + BT data
|
||||
# in the wmt18_en_de_para_plus_bt directory. We link the parallel data as "train"
|
||||
# and the BT data as "train1", so that fairseq will combine them automatically
|
||||
# and so that we can use the `--upsample-primary` option to upsample the
|
||||
# parallel data (if desired).
|
||||
PARA_DATA=$(readlink -f data-bin/wmt18_en_de)
|
||||
BT_DATA=$(readlink -f data-bin/wmt18_en_de_bt)
|
||||
COMB_DATA=data-bin/wmt18_en_de_para_plus_bt
|
||||
mkdir -p $COMB_DATA
|
||||
for LANG in en de; do \
|
||||
ln -s ${PARA_DATA}/dict.$LANG.txt ${COMB_DATA}/dict.$LANG.txt; \
|
||||
for EXT in bin idx; do \
|
||||
ln -s ${PARA_DATA}/train.en-de.$LANG.$EXT ${COMB_DATA}/train.en-de.$LANG.$EXT; \
|
||||
ln -s ${BT_DATA}/train.en-de.$LANG.$EXT ${COMB_DATA}/train1.en-de.$LANG.$EXT; \
|
||||
ln -s ${PARA_DATA}/valid.en-de.$LANG.$EXT ${COMB_DATA}/valid.en-de.$LANG.$EXT; \
|
||||
ln -s ${PARA_DATA}/test.en-de.$LANG.$EXT ${COMB_DATA}/test.en-de.$LANG.$EXT; \
|
||||
done; \
|
||||
done
|
||||
```
|
||||
|
||||
|
||||
#### 3. Train an English-German model over the combined parallel + BT data
|
||||
|
||||
Finally we can train a model over the parallel + BT data:
|
||||
```bash
|
||||
CHECKPOINT_DIR=checkpoints_en_de_parallel_plus_bt
|
||||
fairseq-train --fp16 \
|
||||
data-bin/wmt18_en_de_para_plus_bt \
|
||||
--upsample-primary 16 \
|
||||
--source-lang en --target-lang de \
|
||||
--arch transformer_wmt_en_de_big --share-all-embeddings \
|
||||
--dropout 0.3 --weight-decay 0.0 \
|
||||
--criterion label_smoothed_cross_entropy --label-smoothing 0.1 \
|
||||
--optimizer adam --adam-betas '(0.9, 0.98)' --clip-norm 0.0 \
|
||||
--lr 0.0007 --lr-scheduler inverse_sqrt --warmup-updates 4000 \
|
||||
--max-tokens 3584 --update-freq 16 \
|
||||
--max-update 100000 \
|
||||
--save-dir $CHECKPOINT_DIR
|
||||
# Note: the above command assumes 8 GPUs. Adjust `--update-freq` if you have a
|
||||
# different number of GPUs.
|
||||
```
|
||||
|
||||
Average the last 10 checkpoints:
|
||||
```bash
|
||||
python scripts/average_checkpoints.py \
|
||||
--inputs $CHECKPOINT_DIR \
|
||||
--num-epoch-checkpoints 10 \
|
||||
--output $CHECKPOINT_DIR/checkpoint.avg10.pt
|
||||
```
|
||||
|
||||
Evaluate BLEU:
|
||||
```bash
|
||||
# tokenized BLEU on newstest2017:
|
||||
bash examples/backtranslation/tokenized_bleu.sh \
|
||||
wmt17 \
|
||||
en-de \
|
||||
data-bin/wmt18_en_de \
|
||||
data-bin/wmt18_en_de/code \
|
||||
$CHECKPOINT_DIR/checkpoint.avg10.pt
|
||||
# BLEU4 = 32.35, 64.4/38.9/26.2/18.3 (BP=0.977, ratio=0.977, syslen=60729, reflen=62152)
|
||||
# compare to 32.35 in Table 1, which is also for tokenized BLEU
|
||||
|
||||
# generally it's better to report (detokenized) sacrebleu:
|
||||
bash examples/backtranslation/sacrebleu.sh \
|
||||
wmt17 \
|
||||
en-de \
|
||||
data-bin/wmt18_en_de \
|
||||
data-bin/wmt18_en_de/code \
|
||||
$CHECKPOINT_DIR/checkpoint.avg10.pt
|
||||
# BLEU+case.mixed+lang.en-de+numrefs.1+smooth.exp+test.wmt17+tok.13a+version.1.4.3 = 31.5 64.3/38.2/25.6/17.6 (BP = 0.971 ratio = 0.971 hyp_len = 59515 ref_len = 61287)
|
||||
```
|
||||
|
||||
|
||||
## Citation
|
||||
```bibtex
|
||||
@inproceedings{edunov2018backtranslation,
|
||||
title = {Understanding Back-Translation at Scale},
|
||||
author = {Edunov, Sergey and Ott, Myle and Auli, Michael and Grangier, David},
|
||||
booktitle = {Conference of the Association for Computational Linguistics (ACL)},
|
||||
year = 2018,
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/python3
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import argparse
|
||||
import fileinput
|
||||
import hashlib
|
||||
import sys
|
||||
from multiprocessing import Pool
|
||||
|
||||
|
||||
def get_hashes_and_lines(raw_line):
|
||||
hash = hashlib.md5(raw_line).hexdigest()
|
||||
return hash, raw_line
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--workers", type=int, default=10)
|
||||
parser.add_argument("files", nargs="*", help="input files")
|
||||
args = parser.parse_args()
|
||||
|
||||
seen = set()
|
||||
with fileinput.input(args.files, mode="rb") as h:
|
||||
pool = Pool(args.workers)
|
||||
results = pool.imap_unordered(get_hashes_and_lines, h, 1000)
|
||||
for i, (hash, raw_line) in enumerate(results):
|
||||
if hash not in seen:
|
||||
seen.add(hash)
|
||||
sys.stdout.buffer.write(raw_line)
|
||||
if i % 1000000 == 0:
|
||||
print(i, file=sys.stderr, end="", flush=True)
|
||||
elif i % 100000 == 0:
|
||||
print(".", file=sys.stderr, end="", flush=True)
|
||||
print(file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import argparse
|
||||
import fileinput
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Extract back-translations from the stdout of fairseq-generate. "
|
||||
"If there are multiply hypotheses for a source, we only keep the first one. "
|
||||
)
|
||||
)
|
||||
parser.add_argument("--output", required=True, help="output prefix")
|
||||
parser.add_argument(
|
||||
"--srclang", required=True, help="source language (extracted from H-* lines)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tgtlang", required=True, help="target language (extracted from S-* lines)"
|
||||
)
|
||||
parser.add_argument("--minlen", type=int, help="min length filter")
|
||||
parser.add_argument("--maxlen", type=int, help="max length filter")
|
||||
parser.add_argument("--ratio", type=float, help="ratio filter")
|
||||
parser.add_argument("files", nargs="*", help="input files")
|
||||
args = parser.parse_args()
|
||||
|
||||
def validate(src, tgt):
|
||||
srclen = len(src.split(" ")) if src != "" else 0
|
||||
tgtlen = len(tgt.split(" ")) if tgt != "" else 0
|
||||
if (
|
||||
(args.minlen is not None and (srclen < args.minlen or tgtlen < args.minlen))
|
||||
or (
|
||||
args.maxlen is not None
|
||||
and (srclen > args.maxlen or tgtlen > args.maxlen)
|
||||
)
|
||||
or (
|
||||
args.ratio is not None
|
||||
and (max(srclen, tgtlen) / float(min(srclen, tgtlen)) > args.ratio)
|
||||
)
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
def safe_index(toks, index, default):
|
||||
try:
|
||||
return toks[index]
|
||||
except IndexError:
|
||||
return default
|
||||
|
||||
with open(args.output + "." + args.srclang, "w") as src_h, open(
|
||||
args.output + "." + args.tgtlang, "w"
|
||||
) as tgt_h:
|
||||
for line in tqdm(fileinput.input(args.files)):
|
||||
if line.startswith("S-"):
|
||||
tgt = safe_index(line.rstrip().split("\t"), 1, "")
|
||||
elif line.startswith("H-"):
|
||||
if tgt is not None:
|
||||
src = safe_index(line.rstrip().split("\t"), 2, "")
|
||||
if validate(src, tgt):
|
||||
print(src, file=src_h)
|
||||
print(tgt, file=tgt_h)
|
||||
tgt = None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPTS=mosesdecoder/scripts
|
||||
TOKENIZER=$SCRIPTS/tokenizer/tokenizer.perl
|
||||
NORM_PUNC=$SCRIPTS/tokenizer/normalize-punctuation.perl
|
||||
REM_NON_PRINT_CHAR=$SCRIPTS/tokenizer/remove-non-printing-char.perl
|
||||
BPEROOT=subword-nmt/subword_nmt
|
||||
|
||||
|
||||
BPE_CODE=wmt18_en_de/code
|
||||
SUBSAMPLE_SIZE=25000000
|
||||
LANG=de
|
||||
|
||||
|
||||
OUTDIR=wmt18_${LANG}_mono
|
||||
orig=orig
|
||||
tmp=$OUTDIR/tmp
|
||||
mkdir -p $OUTDIR $tmp
|
||||
|
||||
|
||||
URLS=(
|
||||
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2007.de.shuffled.gz"
|
||||
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2008.de.shuffled.gz"
|
||||
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2009.de.shuffled.gz"
|
||||
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2010.de.shuffled.gz"
|
||||
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2011.de.shuffled.gz"
|
||||
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2012.de.shuffled.gz"
|
||||
"http://www.statmt.org/wmt14/training-monolingual-news-crawl/news.2013.de.shuffled.gz"
|
||||
"http://www.statmt.org/wmt15/training-monolingual-news-crawl-v2/news.2014.de.shuffled.v2.gz"
|
||||
"http://data.statmt.org/wmt16/translation-task/news.2015.de.shuffled.gz"
|
||||
"http://data.statmt.org/wmt17/translation-task/news.2016.de.shuffled.gz"
|
||||
"http://data.statmt.org/wmt18/translation-task/news.2017.de.shuffled.deduped.gz"
|
||||
)
|
||||
FILES=(
|
||||
"news.2007.de.shuffled.gz"
|
||||
"news.2008.de.shuffled.gz"
|
||||
"news.2009.de.shuffled.gz"
|
||||
"news.2010.de.shuffled.gz"
|
||||
"news.2011.de.shuffled.gz"
|
||||
"news.2012.de.shuffled.gz"
|
||||
"news.2013.de.shuffled.gz"
|
||||
"news.2014.de.shuffled.v2.gz"
|
||||
"news.2015.de.shuffled.gz"
|
||||
"news.2016.de.shuffled.gz"
|
||||
"news.2017.de.shuffled.deduped.gz"
|
||||
)
|
||||
|
||||
|
||||
cd $orig
|
||||
for ((i=0;i<${#URLS[@]};++i)); do
|
||||
file=${FILES[i]}
|
||||
if [ -f $file ]; then
|
||||
echo "$file already exists, skipping download"
|
||||
else
|
||||
url=${URLS[i]}
|
||||
wget "$url"
|
||||
fi
|
||||
done
|
||||
cd ..
|
||||
|
||||
|
||||
if [ -f $tmp/monolingual.${SUBSAMPLE_SIZE}.${LANG} ]; then
|
||||
echo "found monolingual sample, skipping shuffle/sample/tokenize"
|
||||
else
|
||||
gzip -c -d -k $(for FILE in "${FILES[@]}"; do echo $orig/$FILE; done) \
|
||||
| shuf -n $SUBSAMPLE_SIZE \
|
||||
| perl $NORM_PUNC $LANG \
|
||||
| perl $REM_NON_PRINT_CHAR \
|
||||
| perl $TOKENIZER -threads 8 -a -l $LANG \
|
||||
> $tmp/monolingual.${SUBSAMPLE_SIZE}.${LANG}
|
||||
fi
|
||||
|
||||
|
||||
if [ -f $tmp/bpe.monolingual.${SUBSAMPLE_SIZE}.${LANG} ]; then
|
||||
echo "found BPE monolingual sample, skipping BPE step"
|
||||
else
|
||||
python $BPEROOT/apply_bpe.py -c $BPE_CODE \
|
||||
< $tmp/monolingual.${SUBSAMPLE_SIZE}.${LANG} \
|
||||
> $tmp/bpe.monolingual.${SUBSAMPLE_SIZE}.${LANG}
|
||||
fi
|
||||
|
||||
|
||||
if [ -f $tmp/bpe.monolingual.dedup.${SUBSAMPLE_SIZE}.${LANG} ]; then
|
||||
echo "found deduplicated monolingual sample, skipping deduplication step"
|
||||
else
|
||||
python deduplicate_lines.py $tmp/bpe.monolingual.${SUBSAMPLE_SIZE}.${LANG} \
|
||||
> $tmp/bpe.monolingual.dedup.${SUBSAMPLE_SIZE}.${LANG}
|
||||
fi
|
||||
|
||||
|
||||
if [ -f $OUTDIR/bpe.monolingual.dedup.00.de ]; then
|
||||
echo "found sharded data, skipping sharding step"
|
||||
else
|
||||
split --lines 1000000 --numeric-suffixes \
|
||||
--additional-suffix .${LANG} \
|
||||
$tmp/bpe.monolingual.dedup.${SUBSAMPLE_SIZE}.${LANG} \
|
||||
$OUTDIR/bpe.monolingual.dedup.
|
||||
fi
|
||||
@@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
# Adapted from https://github.com/facebookresearch/MIXER/blob/master/prepareData.sh
|
||||
|
||||
echo 'Cloning Moses github repository (for tokenization scripts)...'
|
||||
git clone https://github.com/moses-smt/mosesdecoder.git
|
||||
|
||||
echo 'Cloning Subword NMT repository (for BPE pre-processing)...'
|
||||
git clone https://github.com/rsennrich/subword-nmt.git
|
||||
|
||||
SCRIPTS=mosesdecoder/scripts
|
||||
TOKENIZER=$SCRIPTS/tokenizer/tokenizer.perl
|
||||
CLEAN=$SCRIPTS/training/clean-corpus-n.perl
|
||||
NORM_PUNC=$SCRIPTS/tokenizer/normalize-punctuation.perl
|
||||
REM_NON_PRINT_CHAR=$SCRIPTS/tokenizer/remove-non-printing-char.perl
|
||||
BPEROOT=subword-nmt/subword_nmt
|
||||
BPE_TOKENS=32000
|
||||
|
||||
URLS=(
|
||||
"http://statmt.org/wmt13/training-parallel-europarl-v7.tgz"
|
||||
"http://statmt.org/wmt13/training-parallel-commoncrawl.tgz"
|
||||
"http://data.statmt.org/wmt18/translation-task/training-parallel-nc-v13.tgz"
|
||||
"http://data.statmt.org/wmt18/translation-task/rapid2016.tgz"
|
||||
"http://data.statmt.org/wmt17/translation-task/dev.tgz"
|
||||
"http://statmt.org/wmt14/test-full.tgz"
|
||||
)
|
||||
FILES=(
|
||||
"training-parallel-europarl-v7.tgz"
|
||||
"training-parallel-commoncrawl.tgz"
|
||||
"training-parallel-nc-v13.tgz"
|
||||
"rapid2016.tgz"
|
||||
"dev.tgz"
|
||||
"test-full.tgz"
|
||||
)
|
||||
CORPORA=(
|
||||
"training/europarl-v7.de-en"
|
||||
"commoncrawl.de-en"
|
||||
"training-parallel-nc-v13/news-commentary-v13.de-en"
|
||||
"rapid2016.de-en"
|
||||
)
|
||||
|
||||
if [ ! -d "$SCRIPTS" ]; then
|
||||
echo "Please set SCRIPTS variable correctly to point to Moses scripts."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OUTDIR=wmt18_en_de
|
||||
|
||||
src=en
|
||||
tgt=de
|
||||
lang=en-de
|
||||
prep=$OUTDIR
|
||||
tmp=$prep/tmp
|
||||
orig=orig
|
||||
|
||||
mkdir -p $orig $tmp $prep
|
||||
|
||||
cd $orig
|
||||
|
||||
for ((i=0;i<${#URLS[@]};++i)); do
|
||||
file=${FILES[i]}
|
||||
if [ -f $file ]; then
|
||||
echo "$file already exists, skipping download"
|
||||
else
|
||||
url=${URLS[i]}
|
||||
wget "$url"
|
||||
if [ -f $file ]; then
|
||||
echo "$url successfully downloaded."
|
||||
else
|
||||
echo "$url not successfully downloaded."
|
||||
exit 1
|
||||
fi
|
||||
if [ ${file: -4} == ".tgz" ]; then
|
||||
tar zxvf $file
|
||||
elif [ ${file: -4} == ".tar" ]; then
|
||||
tar xvf $file
|
||||
fi
|
||||
fi
|
||||
done
|
||||
cd ..
|
||||
|
||||
echo "pre-processing train data..."
|
||||
for l in $src $tgt; do
|
||||
rm $tmp/train.tags.$lang.tok.$l
|
||||
for f in "${CORPORA[@]}"; do
|
||||
cat $orig/$f.$l | \
|
||||
perl $NORM_PUNC $l | \
|
||||
perl $REM_NON_PRINT_CHAR | \
|
||||
perl $TOKENIZER -threads 8 -a -l $l >> $tmp/train.tags.$lang.tok.$l
|
||||
done
|
||||
done
|
||||
|
||||
echo "pre-processing test data..."
|
||||
for l in $src $tgt; do
|
||||
if [ "$l" == "$src" ]; then
|
||||
t="src"
|
||||
else
|
||||
t="ref"
|
||||
fi
|
||||
grep '<seg id' $orig/test-full/newstest2014-deen-$t.$l.sgm | \
|
||||
sed -e 's/<seg id="[0-9]*">\s*//g' | \
|
||||
sed -e 's/\s*<\/seg>\s*//g' | \
|
||||
sed -e "s/\’/\'/g" | \
|
||||
perl $TOKENIZER -threads 8 -a -l $l > $tmp/test.$l
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "splitting train and valid..."
|
||||
for l in $src $tgt; do
|
||||
awk '{if (NR%100 == 0) print $0; }' $tmp/train.tags.$lang.tok.$l > $tmp/valid.$l
|
||||
awk '{if (NR%100 != 0) print $0; }' $tmp/train.tags.$lang.tok.$l > $tmp/train.$l
|
||||
done
|
||||
|
||||
TRAIN=$tmp/train.de-en
|
||||
BPE_CODE=$prep/code
|
||||
rm -f $TRAIN
|
||||
for l in $src $tgt; do
|
||||
cat $tmp/train.$l >> $TRAIN
|
||||
done
|
||||
|
||||
echo "learn_bpe.py on ${TRAIN}..."
|
||||
python $BPEROOT/learn_bpe.py -s $BPE_TOKENS < $TRAIN > $BPE_CODE
|
||||
|
||||
for L in $src $tgt; do
|
||||
for f in train.$L valid.$L test.$L; do
|
||||
echo "apply_bpe.py to ${f}..."
|
||||
python $BPEROOT/apply_bpe.py -c $BPE_CODE < $tmp/$f > $tmp/bpe.$f
|
||||
done
|
||||
done
|
||||
|
||||
perl $CLEAN -ratio 1.5 $tmp/bpe.train $src $tgt $prep/train 1 250
|
||||
perl $CLEAN -ratio 1.5 $tmp/bpe.valid $src $tgt $prep/valid 1 250
|
||||
|
||||
for L in $src $tgt; do
|
||||
cp $tmp/bpe.test.$L $prep/test.$L
|
||||
done
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ $# -ne 5 ]; then
|
||||
echo "usage: $0 [dataset=wmt14/full] [langpair=en-de] [databin] [bpecode] [model]"
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
DATASET=$1
|
||||
LANGPAIR=$2
|
||||
DATABIN=$3
|
||||
BPECODE=$4
|
||||
MODEL=$5
|
||||
|
||||
SRCLANG=$(echo $LANGPAIR | cut -d '-' -f 1)
|
||||
TGTLANG=$(echo $LANGPAIR | cut -d '-' -f 2)
|
||||
|
||||
|
||||
BPEROOT=examples/backtranslation/subword-nmt/subword_nmt
|
||||
if [ ! -e $BPEROOT ]; then
|
||||
BPEROOT=subword-nmt/subword_nmt
|
||||
if [ ! -e $BPEROOT ]; then
|
||||
echo 'Cloning Subword NMT repository (for BPE pre-processing)...'
|
||||
git clone https://github.com/rsennrich/subword-nmt.git
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
sacrebleu -t $DATASET -l $LANGPAIR --echo src \
|
||||
| sacremoses tokenize -a -l $SRCLANG -q \
|
||||
| python $BPEROOT/apply_bpe.py -c $BPECODE \
|
||||
| fairseq-interactive $DATABIN --path $MODEL \
|
||||
-s $SRCLANG -t $TGTLANG \
|
||||
--beam 5 --remove-bpe --buffer-size 1024 --max-tokens 8000 \
|
||||
| grep ^H- | cut -f 3- \
|
||||
| sacremoses detokenize -l $TGTLANG -q \
|
||||
| sacrebleu -t $DATASET -l $LANGPAIR
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ $# -ne 5 ]; then
|
||||
echo "usage: $0 [dataset=wmt14/full] [langpair=en-de] [databin] [bpecode] [model]"
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
DATASET=$1
|
||||
LANGPAIR=$2
|
||||
DATABIN=$3
|
||||
BPECODE=$4
|
||||
MODEL=$5
|
||||
|
||||
SRCLANG=$(echo $LANGPAIR | cut -d '-' -f 1)
|
||||
TGTLANG=$(echo $LANGPAIR | cut -d '-' -f 2)
|
||||
|
||||
|
||||
BPEROOT=examples/backtranslation/subword-nmt/subword_nmt
|
||||
if [ ! -e $BPEROOT ]; then
|
||||
BPEROOT=subword-nmt/subword_nmt
|
||||
if [ ! -e $BPEROOT ]; then
|
||||
echo 'Cloning Subword NMT repository (for BPE pre-processing)...'
|
||||
git clone https://github.com/rsennrich/subword-nmt.git
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
TMP_REF=$(mktemp)
|
||||
|
||||
sacrebleu -t $DATASET -l $LANGPAIR --echo ref -q \
|
||||
| sacremoses normalize -l $TGTLANG -q \
|
||||
| sacremoses tokenize -a -l $TGTLANG -q \
|
||||
> $TMP_REF
|
||||
|
||||
sacrebleu -t $DATASET -l $LANGPAIR --echo src -q \
|
||||
| sacremoses normalize -l $SRCLANG -q \
|
||||
| sacremoses tokenize -a -l $SRCLANG -q \
|
||||
| python $BPEROOT/apply_bpe.py -c $BPECODE \
|
||||
| fairseq-interactive $DATABIN --path $MODEL \
|
||||
-s $SRCLANG -t $TGTLANG \
|
||||
--beam 5 --remove-bpe --buffer-size 1024 --max-tokens 8000 \
|
||||
| grep ^H- | cut -f 3- \
|
||||
| fairseq-score --ref $TMP_REF
|
||||
|
||||
rm -f $TMP_REF
|
||||
@@ -0,0 +1,99 @@
|
||||
# Fine-tuning BART on GLUE tasks
|
||||
|
||||
### 1) Download the data from GLUE website (https://gluebenchmark.com/tasks) using following commands:
|
||||
```bash
|
||||
wget https://gist.githubusercontent.com/W4ngatang/60c2bdb54d156a41194446737ce03e2e/raw/17b8dd0d724281ed7c3b2aeeda662b92809aadd5/download_glue_data.py
|
||||
python download_glue_data.py --data_dir glue_data --tasks all
|
||||
```
|
||||
|
||||
### 2) Preprocess GLUE task data (same as RoBERTa):
|
||||
```bash
|
||||
./examples/roberta/preprocess_GLUE_tasks.sh glue_data <glue_task_name>
|
||||
```
|
||||
`glue_task_name` is one of the following:
|
||||
`{ALL, QQP, MNLI, QNLI, MRPC, RTE, STS-B, SST-2, CoLA}`
|
||||
Use `ALL` for preprocessing all the glue tasks.
|
||||
|
||||
### 3) Fine-tuning on GLUE task:
|
||||
Example fine-tuning cmd for `RTE` task
|
||||
```bash
|
||||
TOTAL_NUM_UPDATES=2036 # 10 epochs through RTE for bsz 16
|
||||
WARMUP_UPDATES=61 # 6 percent of the number of updates
|
||||
LR=1e-05 # Peak LR for polynomial LR scheduler.
|
||||
NUM_CLASSES=2
|
||||
MAX_SENTENCES=16 # Batch size.
|
||||
BART_PATH=/path/to/bart/model.pt
|
||||
|
||||
CUDA_VISIBLE_DEVICES=0,1 fairseq-train RTE-bin/ \
|
||||
--restore-file $BART_PATH \
|
||||
--batch-size $MAX_SENTENCES \
|
||||
--max-tokens 4400 \
|
||||
--task sentence_prediction \
|
||||
--add-prev-output-tokens \
|
||||
--layernorm-embedding \
|
||||
--share-all-embeddings \
|
||||
--share-decoder-input-output-embed \
|
||||
--reset-optimizer --reset-dataloader --reset-meters \
|
||||
--required-batch-size-multiple 1 \
|
||||
--init-token 0 \
|
||||
--arch bart_large \
|
||||
--criterion sentence_prediction \
|
||||
--num-classes $NUM_CLASSES \
|
||||
--dropout 0.1 --attention-dropout 0.1 \
|
||||
--weight-decay 0.01 --optimizer adam --adam-betas "(0.9, 0.98)" --adam-eps 1e-08 \
|
||||
--clip-norm 0.0 \
|
||||
--lr-scheduler polynomial_decay --lr $LR --total-num-update $TOTAL_NUM_UPDATES --warmup-updates $WARMUP_UPDATES \
|
||||
--fp16 --fp16-init-scale 4 --threshold-loss-scale 1 --fp16-scale-window 128 \
|
||||
--max-epoch 10 \
|
||||
--find-unused-parameters \
|
||||
--best-checkpoint-metric accuracy --maximize-best-checkpoint-metric;
|
||||
```
|
||||
|
||||
For each of the GLUE task, you will need to use following cmd-line arguments:
|
||||
|
||||
Model | MNLI | QNLI | QQP | RTE | SST-2 | MRPC | CoLA | STS-B
|
||||
---|---|---|---|---|---|---|---|---
|
||||
`--num-classes` | 3 | 2 | 2 | 2 | 2 | 2 | 2 | 1
|
||||
`--lr` | 5e-6 | 1e-5 | 1e-5 | 1e-5 | 5e-6 | 2e-5 | 2e-5 | 2e-5
|
||||
`bsz` | 128 | 32 | 32 | 32 | 128 | 64 | 64 | 32
|
||||
`--total-num-update` | 30968 | 33112 | 113272 | 1018 | 5233 | 1148 | 1334 | 1799
|
||||
`--warmup-updates` | 1858 | 1986 | 6796 | 61 | 314 | 68 | 80 | 107
|
||||
|
||||
For `STS-B` additionally add `--regression-target --best-checkpoint-metric loss` and remove `--maximize-best-checkpoint-metric`.
|
||||
|
||||
**Note:**
|
||||
|
||||
a) `--total-num-updates` is used by `--polynomial_decay` scheduler and is calculated for `--max-epoch=10` and `--batch-size=32/64/128` depending on the task.
|
||||
|
||||
b) Above cmd-args and hyperparams are tested on Nvidia `V100` GPU with `32gb` of memory for each task. Depending on the GPU memory resources available to you, you can use increase `--update-freq` and reduce `--batch-size`.
|
||||
|
||||
### Inference on GLUE task
|
||||
After training the model as mentioned in previous step, you can perform inference with checkpoints in `checkpoints/` directory using following python code snippet:
|
||||
|
||||
```python
|
||||
from fairseq.models.bart import BARTModel
|
||||
|
||||
bart = BARTModel.from_pretrained(
|
||||
'checkpoints/',
|
||||
checkpoint_file='checkpoint_best.pt',
|
||||
data_name_or_path='RTE-bin'
|
||||
)
|
||||
|
||||
label_fn = lambda label: bart.task.label_dictionary.string(
|
||||
[label + bart.task.label_dictionary.nspecial]
|
||||
)
|
||||
ncorrect, nsamples = 0, 0
|
||||
bart.cuda()
|
||||
bart.eval()
|
||||
with open('glue_data/RTE/dev.tsv') as fin:
|
||||
fin.readline()
|
||||
for index, line in enumerate(fin):
|
||||
tokens = line.strip().split('\t')
|
||||
sent1, sent2, target = tokens[1], tokens[2], tokens[3]
|
||||
tokens = bart.encode(sent1, sent2)
|
||||
prediction = bart.predict('sentence_classification_head', tokens).argmax().item()
|
||||
prediction_label = label_fn(prediction)
|
||||
ncorrect += int(prediction_label == target)
|
||||
nsamples += 1
|
||||
print('| Accuracy: ', float(ncorrect)/float(nsamples))
|
||||
```
|
||||
@@ -0,0 +1,243 @@
|
||||
# BART: Denoising Sequence-to-Sequence Pre-training for Natural Language Generation, Translation, and Comprehension
|
||||
|
||||
[https://arxiv.org/pdf/1910.13461.pdf]
|
||||
|
||||
## Introduction
|
||||
|
||||
BART is sequence-to-sequence model trained with denoising as pretraining objective. We show that this pretraining objective is more generic and show that we can match [RoBERTa](../roberta) results on SQuAD and GLUE and gain state-of-the-art results on summarization (XSum, CNN dataset), long form generative question answering (ELI5) and dialog response genration (ConvAI2). See the associated paper for more details.
|
||||
|
||||
## Pre-trained models
|
||||
|
||||
Model | Description | # params | Download
|
||||
---|---|---|---
|
||||
`bart.base` | BART model with 6 encoder and decoder layers | 140M | [bart.base.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/bart.base.tar.gz)
|
||||
`bart.large` | BART model with 12 encoder and decoder layers | 400M | [bart.large.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/bart.large.tar.gz)
|
||||
`bart.large.mnli` | `bart.large` finetuned on `MNLI` | 400M | [bart.large.mnli.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/bart.large.mnli.tar.gz)
|
||||
`bart.large.cnn` | `bart.large` finetuned on `CNN-DM` | 400M | [bart.large.cnn.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/bart.large.cnn.tar.gz)
|
||||
`bart.large.xsum` | `bart.large` finetuned on `Xsum` | 400M | [bart.large.xsum.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/bart.large.xsum.tar.gz)
|
||||
|
||||
## Results
|
||||
|
||||
**[GLUE (Wang et al., 2019)](https://gluebenchmark.com/)**
|
||||
_(dev set, single model, single-task finetuning)_
|
||||
|
||||
Model | MNLI | QNLI | QQP | RTE | SST-2 | MRPC | CoLA | STS-B
|
||||
---|---|---|---|---|---|---|---|---
|
||||
`roberta.large` | 90.2 | 94.7 | 92.2 | 86.6 | 96.4 | 90.9 | 68.0 | 92.4
|
||||
`bart.large` | 89.9 | 94.9 | 92.5 | 87.0 | 96.6 | 90.4 | 62.8 | 91.2
|
||||
|
||||
**[SQuAD (Rajpurkar et al., 2018)](https://rajpurkar.github.io/SQuAD-explorer/)**
|
||||
_(dev set, no additional data used)_
|
||||
|
||||
Model | SQuAD 1.1 EM/F1 | SQuAD 2.0 EM/F1
|
||||
---|---|---
|
||||
`roberta.large` | 88.9/94.6 | 86.5/89.4
|
||||
`bart.large` | 88.8/94.6 | 86.1/89.2
|
||||
|
||||
**[CNN/Daily Mail](http://nlpprogress.com/english/summarization.html)**
|
||||
_(test set, no additional data used)_
|
||||
|
||||
Model | R1 | R2 | RL
|
||||
---|---|---|---
|
||||
`BERTSUMEXTABS` | 42.13 | 19.60 | 39.18
|
||||
`bart.large` | 44.16 | 21.28 | 40.90
|
||||
|
||||
## Example usage
|
||||
|
||||
##### Load BART from torch.hub (PyTorch >= 1.1):
|
||||
```python
|
||||
import torch
|
||||
bart = torch.hub.load('pytorch/fairseq', 'bart.large')
|
||||
bart.eval() # disable dropout (or leave in train mode to finetune)
|
||||
```
|
||||
|
||||
##### Load BART (for PyTorch 1.0 or custom models):
|
||||
```python
|
||||
# Download bart.large model
|
||||
wget https://dl.fbaipublicfiles.com/fairseq/models/bart.large.tar.gz
|
||||
tar -xzvf bart.large.tar.gz
|
||||
|
||||
# Load the model in fairseq
|
||||
from fairseq.models.bart import BARTModel
|
||||
bart = BARTModel.from_pretrained('/path/to/bart.large', checkpoint_file='model.pt')
|
||||
bart.eval() # disable dropout (or leave in train mode to finetune)
|
||||
```
|
||||
|
||||
##### Apply Byte-Pair Encoding (BPE) to input text:
|
||||
```python
|
||||
tokens = bart.encode('Hello world!')
|
||||
assert tokens.tolist() == [0, 31414, 232, 328, 2]
|
||||
bart.decode(tokens) # 'Hello world!'
|
||||
```
|
||||
|
||||
##### Extract features from BART:
|
||||
```python
|
||||
# Extract the last layer's features
|
||||
last_layer_features = bart.extract_features(tokens)
|
||||
assert last_layer_features.size() == torch.Size([1, 5, 1024])
|
||||
|
||||
# Extract all layer's features from decoder (layer 0 is the embedding layer)
|
||||
all_layers = bart.extract_features(tokens, return_all_hiddens=True)
|
||||
assert len(all_layers) == 13
|
||||
assert torch.all(all_layers[-1] == last_layer_features)
|
||||
```
|
||||
|
||||
##### Use BART for sentence-pair classification tasks:
|
||||
```python
|
||||
# Download BART already finetuned for MNLI
|
||||
bart = torch.hub.load('pytorch/fairseq', 'bart.large.mnli')
|
||||
bart.eval() # disable dropout for evaluation
|
||||
|
||||
# Encode a pair of sentences and make a prediction
|
||||
tokens = bart.encode('BART is a seq2seq model.', 'BART is not sequence to sequence.')
|
||||
bart.predict('mnli', tokens).argmax() # 0: contradiction
|
||||
|
||||
# Encode another pair of sentences
|
||||
tokens = bart.encode('BART is denoising autoencoder.', 'BART is version of autoencoder.')
|
||||
bart.predict('mnli', tokens).argmax() # 2: entailment
|
||||
```
|
||||
|
||||
##### Register a new (randomly initialized) classification head:
|
||||
```python
|
||||
bart.register_classification_head('new_task', num_classes=3)
|
||||
logprobs = bart.predict('new_task', tokens)
|
||||
```
|
||||
|
||||
##### Batched prediction:
|
||||
```python
|
||||
import torch
|
||||
from fairseq.data.data_utils import collate_tokens
|
||||
|
||||
bart = torch.hub.load('pytorch/fairseq', 'bart.large.mnli')
|
||||
bart.eval()
|
||||
|
||||
batch_of_pairs = [
|
||||
['BART is a seq2seq model.', 'BART is not sequence to sequence.'],
|
||||
['BART is denoising autoencoder.', 'BART is version of autoencoder.'],
|
||||
]
|
||||
|
||||
batch = collate_tokens(
|
||||
[bart.encode(pair[0], pair[1]) for pair in batch_of_pairs], pad_idx=1
|
||||
)
|
||||
|
||||
logprobs = bart.predict('mnli', batch)
|
||||
print(logprobs.argmax(dim=1))
|
||||
# tensor([0, 2])
|
||||
```
|
||||
|
||||
##### Using the GPU:
|
||||
```python
|
||||
bart.cuda()
|
||||
bart.predict('new_task', tokens)
|
||||
```
|
||||
|
||||
#### Filling masks:
|
||||
|
||||
BART can be used to fill multiple `<mask>` tokens in the input.
|
||||
```python
|
||||
bart = torch.hub.load('pytorch/fairseq', 'bart.base')
|
||||
bart.eval()
|
||||
bart.fill_mask(['The cat <mask> on the <mask>.'], topk=3, beam=10)
|
||||
# [[('The cat was on the ground.', tensor(-0.6183)), ('The cat was on the floor.', tensor(-0.6798)), ('The cat sleeps on the couch.', tensor(-0.6830))]]
|
||||
```
|
||||
|
||||
Note that by default we enforce the output length to match the input length.
|
||||
This can be disabled by setting ``match_source_len=False``:
|
||||
```
|
||||
bart.fill_mask(['The cat <mask> on the <mask>.'], topk=3, beam=10, match_source_len=False)
|
||||
# [[('The cat was on the ground.', tensor(-0.6185)), ('The cat was asleep on the couch.', tensor(-0.6276)), ('The cat was on the floor.', tensor(-0.6800))]]
|
||||
```
|
||||
|
||||
Example code to fill masks for a batch of sentences using GPU
|
||||
```
|
||||
bart.cuda()
|
||||
bart.fill_mask(['The cat <mask> on the <mask>.', 'The dog <mask> on the <mask>.'], topk=3, beam=10)
|
||||
# [[('The cat was on the ground.', tensor(-0.6183)), ('The cat was on the floor.', tensor(-0.6798)), ('The cat sleeps on the couch.', tensor(-0.6830))], [('The dog was on the ground.', tensor(-0.6190)), ('The dog lay on the ground.', tensor(-0.6711)),
|
||||
('The dog was asleep on the couch', tensor(-0.6796))]]
|
||||
```
|
||||
|
||||
#### Evaluating the `bart.large.mnli` model:
|
||||
|
||||
Example python code snippet to evaluate accuracy on the MNLI `dev_matched` set.
|
||||
```python
|
||||
label_map = {0: 'contradiction', 1: 'neutral', 2: 'entailment'}
|
||||
ncorrect, nsamples = 0, 0
|
||||
bart.cuda()
|
||||
bart.eval()
|
||||
with open('glue_data/MNLI/dev_matched.tsv') as fin:
|
||||
fin.readline()
|
||||
for index, line in enumerate(fin):
|
||||
tokens = line.strip().split('\t')
|
||||
sent1, sent2, target = tokens[8], tokens[9], tokens[-1]
|
||||
tokens = bart.encode(sent1, sent2)
|
||||
prediction = bart.predict('mnli', tokens).argmax().item()
|
||||
prediction_label = label_map[prediction]
|
||||
ncorrect += int(prediction_label == target)
|
||||
nsamples += 1
|
||||
print('| Accuracy: ', float(ncorrect)/float(nsamples))
|
||||
# Expected output: 0.9010
|
||||
```
|
||||
|
||||
#### Evaluating the `bart.large.cnn` model:
|
||||
Follow instructions [here](https://github.com/abisee/cnn-dailymail) to download and process into data-files such that `test.source` and `test.target` has one line for each non-tokenized sample.
|
||||
|
||||
```python
|
||||
bart = torch.hub.load('pytorch/fairseq', 'bart.large.cnn')
|
||||
bart.cuda()
|
||||
bart.eval()
|
||||
bart.half()
|
||||
count = 1
|
||||
bsz = 32
|
||||
with open('test.source') as source, open('test.hypo', 'w') as fout:
|
||||
sline = source.readline().strip()
|
||||
slines = [sline]
|
||||
for sline in source:
|
||||
if count % bsz == 0:
|
||||
with torch.no_grad():
|
||||
hypotheses_batch = bart.sample(slines, beam=4, lenpen=2.0, max_len_b=140, min_len=55, no_repeat_ngram_size=3)
|
||||
|
||||
for hypothesis in hypotheses_batch:
|
||||
fout.write(hypothesis + '\n')
|
||||
fout.flush()
|
||||
slines = []
|
||||
|
||||
slines.append(sline.strip())
|
||||
count += 1
|
||||
if slines != []:
|
||||
hypotheses_batch = bart.sample(slines, beam=4, lenpen=2.0, max_len_b=140, min_len=55, no_repeat_ngram_size=3)
|
||||
for hypothesis in hypotheses_batch:
|
||||
fout.write(hypothesis + '\n')
|
||||
fout.flush()
|
||||
```
|
||||
|
||||
Install `files2rouge` from [here](https://github.com/pltrdy/files2rouge).
|
||||
|
||||
```bash
|
||||
export CLASSPATH=/path/to/stanford-corenlp-full-2016-10-31/stanford-corenlp-3.7.0.jar
|
||||
|
||||
# Tokenize hypothesis and target files.
|
||||
cat test.hypo | java edu.stanford.nlp.process.PTBTokenizer -ioFileList -preserveLines > test.hypo.tokenized
|
||||
cat test.target | java edu.stanford.nlp.process.PTBTokenizer -ioFileList -preserveLines > test.hypo.target
|
||||
files2rouge test.hypo.tokenized test.hypo.target
|
||||
# Expected output: (ROUGE-2 Average_F: 0.21238)
|
||||
```
|
||||
|
||||
|
||||
## Finetuning
|
||||
|
||||
- [Finetuning on GLUE](README.glue.md)
|
||||
- [Finetuning on CNN-DM](README.summarization.md)
|
||||
|
||||
## Citation
|
||||
|
||||
```bibtex
|
||||
@article{lewis2019bart,
|
||||
title = {BART: Denoising Sequence-to-Sequence Pre-training for Natural
|
||||
Language Generation, Translation, and Comprehension},
|
||||
author = {Mike Lewis and Yinhan Liu and Naman Goyal and Marjan Ghazvininejad and
|
||||
Abdelrahman Mohamed and Omer Levy and Veselin Stoyanov
|
||||
and Luke Zettlemoyer },
|
||||
journal={arXiv preprint arXiv:1910.13461},
|
||||
year = {2019},
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,121 @@
|
||||
# Fine-tuning BART on CNN-Dailymail summarization task
|
||||
|
||||
### 1) Download the CNN and Daily Mail data and preprocess it into data files with non-tokenized cased samples.
|
||||
|
||||
Follow the instructions [here](https://github.com/abisee/cnn-dailymail) to download the original CNN and Daily Mail datasets. To preprocess the data, refer to the pointers in [this issue](https://github.com/pytorch/fairseq/issues/1391) or check out the code [here](https://github.com/artmatsak/cnn-dailymail).
|
||||
|
||||
Follow the instructions [here](https://github.com/EdinburghNLP/XSum) to download the original Extreme Summarization datasets, or check out the code [here](https://github.com/EdinburghNLP/XSum/tree/master/XSum-Dataset), Please keep the raw dataset and make sure no tokenization nor BPE on the dataset.
|
||||
|
||||
### 2) BPE preprocess:
|
||||
|
||||
```bash
|
||||
wget -N 'https://dl.fbaipublicfiles.com/fairseq/gpt2_bpe/encoder.json'
|
||||
wget -N 'https://dl.fbaipublicfiles.com/fairseq/gpt2_bpe/vocab.bpe'
|
||||
wget -N 'https://dl.fbaipublicfiles.com/fairseq/gpt2_bpe/dict.txt'
|
||||
|
||||
TASK=cnn_dm
|
||||
for SPLIT in train val
|
||||
do
|
||||
for LANG in source target
|
||||
do
|
||||
python -m examples.roberta.multiprocessing_bpe_encoder \
|
||||
--encoder-json encoder.json \
|
||||
--vocab-bpe vocab.bpe \
|
||||
--inputs "$TASK/$SPLIT.$LANG" \
|
||||
--outputs "$TASK/$SPLIT.bpe.$LANG" \
|
||||
--workers 60 \
|
||||
--keep-empty;
|
||||
done
|
||||
done
|
||||
```
|
||||
|
||||
### 3) Binarize dataset:
|
||||
```bash
|
||||
fairseq-preprocess \
|
||||
--source-lang "source" \
|
||||
--target-lang "target" \
|
||||
--trainpref "${TASK}/train.bpe" \
|
||||
--validpref "${TASK}/val.bpe" \
|
||||
--destdir "${TASK}-bin/" \
|
||||
--workers 60 \
|
||||
--srcdict dict.txt \
|
||||
--tgtdict dict.txt;
|
||||
```
|
||||
|
||||
### 4) Fine-tuning on CNN-DM summarization task:
|
||||
Example fine-tuning CNN-DM
|
||||
```bash
|
||||
TOTAL_NUM_UPDATES=20000
|
||||
WARMUP_UPDATES=500
|
||||
LR=3e-05
|
||||
MAX_TOKENS=2048
|
||||
UPDATE_FREQ=4
|
||||
BART_PATH=/path/to/bart/model.pt
|
||||
|
||||
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 fairseq-train cnn_dm-bin \
|
||||
--restore-file $BART_PATH \
|
||||
--max-tokens $MAX_TOKENS \
|
||||
--task translation \
|
||||
--source-lang source --target-lang target \
|
||||
--truncate-source \
|
||||
--layernorm-embedding \
|
||||
--share-all-embeddings \
|
||||
--share-decoder-input-output-embed \
|
||||
--reset-optimizer --reset-dataloader --reset-meters \
|
||||
--required-batch-size-multiple 1 \
|
||||
--arch bart_large \
|
||||
--criterion label_smoothed_cross_entropy \
|
||||
--label-smoothing 0.1 \
|
||||
--dropout 0.1 --attention-dropout 0.1 \
|
||||
--weight-decay 0.01 --optimizer adam --adam-betas "(0.9, 0.999)" --adam-eps 1e-08 \
|
||||
--clip-norm 0.1 \
|
||||
--lr-scheduler polynomial_decay --lr $LR --total-num-update $TOTAL_NUM_UPDATES --warmup-updates $WARMUP_UPDATES \
|
||||
--fp16 --update-freq $UPDATE_FREQ \
|
||||
--skip-invalid-size-inputs-valid-test \
|
||||
--find-unused-parameters;
|
||||
```
|
||||
Above is expected to run on `1` node with `8 32gb-V100`.
|
||||
Expected training time is about `5 hours`. Training time can be reduced with distributed training on `4` nodes and `--update-freq 1`.
|
||||
|
||||
Use TOTAL_NUM_UPDATES=15000 UPDATE_FREQ=2 for Xsum task
|
||||
|
||||
### Inference for CNN-DM test data using above trained checkpoint.
|
||||
After training the model as mentioned in previous step, you can perform inference with checkpoints in `checkpoints/` directory using following python code snippet:
|
||||
|
||||
```python
|
||||
import torch
|
||||
from fairseq.models.bart import BARTModel
|
||||
|
||||
bart = BARTModel.from_pretrained(
|
||||
'checkpoints/',
|
||||
checkpoint_file='checkpoint_best.pt',
|
||||
data_name_or_path='cnn_dm-bin'
|
||||
)
|
||||
|
||||
bart.cuda()
|
||||
bart.eval()
|
||||
bart.half()
|
||||
count = 1
|
||||
bsz = 32
|
||||
with open('cnn_dm/test.source') as source, open('cnn_dm/test.hypo', 'w') as fout:
|
||||
sline = source.readline().strip()
|
||||
slines = [sline]
|
||||
for sline in source:
|
||||
if count % bsz == 0:
|
||||
with torch.no_grad():
|
||||
hypotheses_batch = bart.sample(slines, beam=4, lenpen=2.0, max_len_b=140, min_len=55, no_repeat_ngram_size=3)
|
||||
|
||||
for hypothesis in hypotheses_batch:
|
||||
fout.write(hypothesis + '\n')
|
||||
fout.flush()
|
||||
slines = []
|
||||
|
||||
slines.append(sline.strip())
|
||||
count += 1
|
||||
if slines != []:
|
||||
hypotheses_batch = bart.sample(slines, beam=4, lenpen=2.0, max_len_b=140, min_len=55, no_repeat_ngram_size=3)
|
||||
for hypothesis in hypotheses_batch:
|
||||
fout.write(hypothesis + '\n')
|
||||
fout.flush()
|
||||
```
|
||||
Use beam=6, lenpen=1.0, max_len_b=60, min_len=10 for Xsum Generation
|
||||
@@ -0,0 +1,88 @@
|
||||
# Neural Machine Translation with Byte-Level Subwords
|
||||
|
||||
https://arxiv.org/abs/1909.03341
|
||||
|
||||
We provide an implementation of byte-level byte-pair encoding (BBPE), taking IWSLT 2017 Fr-En translation as
|
||||
example.
|
||||
|
||||
## Data
|
||||
Get data and generate fairseq binary dataset:
|
||||
```bash
|
||||
bash ./get_data.sh
|
||||
```
|
||||
|
||||
## Model Training
|
||||
Train Transformer model with Bi-GRU embedding contextualization (implemented in `gru_transformer.py`):
|
||||
```bash
|
||||
# VOCAB=bytes
|
||||
# VOCAB=chars
|
||||
VOCAB=bbpe2048
|
||||
# VOCAB=bpe2048
|
||||
# VOCAB=bbpe4096
|
||||
# VOCAB=bpe4096
|
||||
# VOCAB=bpe16384
|
||||
```
|
||||
```bash
|
||||
fairseq-train "data/bin_${VOCAB}" --task translation --user-dir examples/byte_level_bpe/gru_transformer \
|
||||
--arch gru_transformer --encoder-layers 2 --decoder-layers 2 --dropout 0.3 --share-all-embeddings \
|
||||
--optimizer adam --adam-betas '(0.9, 0.98)' \
|
||||
--lr 5e-4 --lr-scheduler inverse_sqrt --warmup-updates 4000 \
|
||||
--criterion label_smoothed_cross_entropy --label-smoothing 0.1 \
|
||||
--log-format 'simple' --log-interval 100 --save-dir "checkpoints/${VOCAB}" \
|
||||
--batch-size 100 --max-update 100000 --update-freq 2
|
||||
```
|
||||
|
||||
## Generation
|
||||
`fairseq-generate` requires bytes (BBPE) decoder to convert byte-level representation back to characters:
|
||||
```bash
|
||||
# BPE=--bpe bytes
|
||||
# BPE=--bpe characters
|
||||
BPE=--bpe byte_bpe --sentencepiece-model-path data/spm_bbpe2048.model
|
||||
# BPE=--bpe sentencepiece --sentencepiece-model data/spm_bpe2048.model
|
||||
# BPE=--bpe byte_bpe --sentencepiece-model-path data/spm_bbpe4096.model
|
||||
# BPE=--bpe sentencepiece --sentencepiece-model data/spm_bpe4096.model
|
||||
# BPE=--bpe sentencepiece --sentencepiece-model data/spm_bpe16384.model
|
||||
```
|
||||
|
||||
```bash
|
||||
fairseq-generate "data/bin_${VOCAB}" --task translation --user-dir examples/byte_level_bpe/gru_transformer \
|
||||
--source-lang fr --gen-subset test --sacrebleu --path "checkpoints/${VOCAB}/checkpoint_last.pt" \
|
||||
--tokenizer moses --moses-target-lang en ${BPE}
|
||||
```
|
||||
When using `fairseq-interactive`, bytes (BBPE) encoder/decoder is required to tokenize input data and detokenize model predictions:
|
||||
```bash
|
||||
fairseq-interactive "data/bin_${VOCAB}" --task translation --user-dir examples/byte_level_bpe/gru_transformer \
|
||||
--path "checkpoints/${VOCAB}/checkpoint_last.pt" --input data/test.fr --tokenizer moses --moses-source-lang fr \
|
||||
--moses-target-lang en ${BPE} --buffer-size 1000 --max-tokens 10000
|
||||
```
|
||||
|
||||
## Results
|
||||
| Vocabulary | Model | BLEU |
|
||||
|:-------------:|:-------------:|:-------------:|
|
||||
| Joint BPE 16k ([Kudo, 2018](https://arxiv.org/abs/1804.10959)) | 512d LSTM 2+2 | 33.81 |
|
||||
| Joint BPE 16k | Transformer base 2+2 (w/ GRU) | 36.64 (36.72) |
|
||||
| Joint BPE 4k | Transformer base 2+2 (w/ GRU) | 35.49 (36.10) |
|
||||
| Joint BBPE 4k | Transformer base 2+2 (w/ GRU) | 35.61 (35.82) |
|
||||
| Joint BPE 2k | Transformer base 2+2 (w/ GRU) | 34.87 (36.13) |
|
||||
| Joint BBPE 2k | Transformer base 2+2 (w/ GRU) | 34.98 (35.43) |
|
||||
| Characters | Transformer base 2+2 (w/ GRU) | 31.78 (33.30) |
|
||||
| Bytes | Transformer base 2+2 (w/ GRU) | 31.57 (33.62) |
|
||||
|
||||
|
||||
## Citation
|
||||
```
|
||||
@misc{wang2019neural,
|
||||
title={Neural Machine Translation with Byte-Level Subwords},
|
||||
author={Changhan Wang and Kyunghyun Cho and Jiatao Gu},
|
||||
year={2019},
|
||||
eprint={1909.03341},
|
||||
archivePrefix={arXiv},
|
||||
primaryClass={cs.CL}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Contact
|
||||
Changhan Wang ([changhan@fb.com](mailto:changhan@fb.com)),
|
||||
Kyunghyun Cho ([kyunghyuncho@fb.com](mailto:kyunghyuncho@fb.com)),
|
||||
Jiatao Gu ([jgu@fb.com](mailto:jgu@fb.com))
|
||||
@@ -0,0 +1,254 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import os.path as op
|
||||
from collections import namedtuple
|
||||
from multiprocessing import cpu_count
|
||||
from typing import List, Optional
|
||||
|
||||
import sentencepiece as sp
|
||||
from fairseq.data.encoders.byte_bpe import ByteBPE
|
||||
from fairseq.data.encoders.byte_utils import byte_encode
|
||||
from fairseq.data.encoders.bytes import Bytes
|
||||
from fairseq.data.encoders.characters import Characters
|
||||
from fairseq.data.encoders.moses_tokenizer import MosesTokenizer
|
||||
from fairseq.data.encoders.sentencepiece_bpe import SentencepieceBPE
|
||||
|
||||
|
||||
SPLITS = ["train", "valid", "test"]
|
||||
|
||||
|
||||
def _convert_xml(in_path: str, out_path: str):
|
||||
with open(in_path) as f, open(out_path, "w") as f_o:
|
||||
for s in f:
|
||||
ss = s.strip()
|
||||
if not ss.startswith("<seg"):
|
||||
continue
|
||||
ss = ss.replace("</seg>", "").split('">')
|
||||
assert len(ss) == 2
|
||||
f_o.write(ss[1].strip() + "\n")
|
||||
|
||||
|
||||
def _convert_train(in_path: str, out_path: str):
|
||||
with open(in_path) as f, open(out_path, "w") as f_o:
|
||||
for s in f:
|
||||
ss = s.strip()
|
||||
if ss.startswith("<"):
|
||||
continue
|
||||
f_o.write(ss.strip() + "\n")
|
||||
|
||||
|
||||
def _get_bytes(in_path: str, out_path: str):
|
||||
with open(in_path) as f, open(out_path, "w") as f_o:
|
||||
for s in f:
|
||||
f_o.write(Bytes.encode(s.strip()) + "\n")
|
||||
|
||||
|
||||
def _get_chars(in_path: str, out_path: str):
|
||||
with open(in_path) as f, open(out_path, "w") as f_o:
|
||||
for s in f:
|
||||
f_o.write(Characters.encode(s.strip()) + "\n")
|
||||
|
||||
|
||||
def pretokenize(in_path: str, out_path: str, src: str, tgt: str):
|
||||
Args = namedtuple(
|
||||
"Args",
|
||||
[
|
||||
"moses_source_lang",
|
||||
"moses_target_lang",
|
||||
"moses_no_dash_splits",
|
||||
"moses_no_escape",
|
||||
],
|
||||
)
|
||||
args = Args(
|
||||
moses_source_lang=src,
|
||||
moses_target_lang=tgt,
|
||||
moses_no_dash_splits=False,
|
||||
moses_no_escape=False,
|
||||
)
|
||||
pretokenizer = MosesTokenizer(args)
|
||||
with open(in_path) as f, open(out_path, "w") as f_o:
|
||||
for s in f:
|
||||
f_o.write(pretokenizer.encode(s.strip()) + "\n")
|
||||
|
||||
|
||||
def _convert_to_bchar(in_path_prefix: str, src: str, tgt: str, out_path: str):
|
||||
with open(out_path, "w") as f_o:
|
||||
for lang in [src, tgt]:
|
||||
with open(f"{in_path_prefix}.{lang}") as f:
|
||||
for s in f:
|
||||
f_o.write(byte_encode(s.strip()) + "\n")
|
||||
|
||||
|
||||
def _get_bpe(in_path: str, model_prefix: str, vocab_size: int):
|
||||
arguments = [
|
||||
f"--input={in_path}",
|
||||
f"--model_prefix={model_prefix}",
|
||||
f"--model_type=bpe",
|
||||
f"--vocab_size={vocab_size}",
|
||||
"--character_coverage=1.0",
|
||||
"--normalization_rule_name=identity",
|
||||
f"--num_threads={cpu_count()}",
|
||||
]
|
||||
sp.SentencePieceTrainer.Train(" ".join(arguments))
|
||||
|
||||
|
||||
def _apply_bbpe(model_path: str, in_path: str, out_path: str):
|
||||
Args = namedtuple("Args", ["sentencepiece_model_path"])
|
||||
args = Args(sentencepiece_model_path=model_path)
|
||||
tokenizer = ByteBPE(args)
|
||||
with open(in_path) as f, open(out_path, "w") as f_o:
|
||||
for s in f:
|
||||
f_o.write(tokenizer.encode(s.strip()) + "\n")
|
||||
|
||||
|
||||
def _apply_bpe(model_path: str, in_path: str, out_path: str):
|
||||
Args = namedtuple("Args", ["sentencepiece_model"])
|
||||
args = Args(sentencepiece_model=model_path)
|
||||
tokenizer = SentencepieceBPE(args)
|
||||
with open(in_path) as f, open(out_path, "w") as f_o:
|
||||
for s in f:
|
||||
f_o.write(tokenizer.encode(s.strip()) + "\n")
|
||||
|
||||
|
||||
def _concat_files(in_paths: List[str], out_path: str):
|
||||
with open(out_path, "w") as f_o:
|
||||
for p in in_paths:
|
||||
with open(p) as f:
|
||||
for r in f:
|
||||
f_o.write(r)
|
||||
|
||||
|
||||
def preprocess_iwslt17(
|
||||
root: str,
|
||||
src: str,
|
||||
tgt: str,
|
||||
bpe_size: Optional[int],
|
||||
need_chars: bool,
|
||||
bbpe_size: Optional[int],
|
||||
need_bytes: bool,
|
||||
):
|
||||
# extract bitext
|
||||
in_root = op.join(root, f"{src}-{tgt}")
|
||||
for lang in [src, tgt]:
|
||||
_convert_train(
|
||||
op.join(in_root, f"train.tags.{src}-{tgt}.{lang}"),
|
||||
op.join(root, f"train.{lang}"),
|
||||
)
|
||||
_convert_xml(
|
||||
op.join(in_root, f"IWSLT17.TED.dev2010.{src}-{tgt}.{lang}.xml"),
|
||||
op.join(root, f"valid.{lang}"),
|
||||
)
|
||||
_convert_xml(
|
||||
op.join(in_root, f"IWSLT17.TED.tst2015.{src}-{tgt}.{lang}.xml"),
|
||||
op.join(root, f"test.{lang}"),
|
||||
)
|
||||
# pre-tokenize
|
||||
for lang in [src, tgt]:
|
||||
for split in SPLITS:
|
||||
pretokenize(
|
||||
op.join(root, f"{split}.{lang}"),
|
||||
op.join(root, f"{split}.moses.{lang}"),
|
||||
src,
|
||||
tgt,
|
||||
)
|
||||
# tokenize with BPE vocabulary
|
||||
if bpe_size is not None:
|
||||
# learn vocabulary
|
||||
concated_train_path = op.join(root, "train.all")
|
||||
_concat_files(
|
||||
[op.join(root, "train.moses.fr"), op.join(root, "train.moses.en")],
|
||||
concated_train_path,
|
||||
)
|
||||
bpe_model_prefix = op.join(root, f"spm_bpe{bpe_size}")
|
||||
_get_bpe(concated_train_path, bpe_model_prefix, bpe_size)
|
||||
os.remove(concated_train_path)
|
||||
# apply
|
||||
for lang in [src, tgt]:
|
||||
for split in SPLITS:
|
||||
_apply_bpe(
|
||||
bpe_model_prefix + ".model",
|
||||
op.join(root, f"{split}.moses.{lang}"),
|
||||
op.join(root, f"{split}.moses.bpe{bpe_size}.{lang}"),
|
||||
)
|
||||
# tokenize with bytes vocabulary
|
||||
if need_bytes:
|
||||
for lang in [src, tgt]:
|
||||
for split in SPLITS:
|
||||
_get_bytes(
|
||||
op.join(root, f"{split}.moses.{lang}"),
|
||||
op.join(root, f"{split}.moses.bytes.{lang}"),
|
||||
)
|
||||
# tokenize with characters vocabulary
|
||||
if need_chars:
|
||||
for lang in [src, tgt]:
|
||||
for split in SPLITS:
|
||||
_get_chars(
|
||||
op.join(root, f"{split}.moses.{lang}"),
|
||||
op.join(root, f"{split}.moses.chars.{lang}"),
|
||||
)
|
||||
# tokenize with byte-level BPE vocabulary
|
||||
if bbpe_size is not None:
|
||||
# learn vocabulary
|
||||
bchar_path = op.join(root, "train.bchar")
|
||||
_convert_to_bchar(op.join(root, "train.moses"), src, tgt, bchar_path)
|
||||
bbpe_model_prefix = op.join(root, f"spm_bbpe{bbpe_size}")
|
||||
_get_bpe(bchar_path, bbpe_model_prefix, bbpe_size)
|
||||
os.remove(bchar_path)
|
||||
# apply
|
||||
for lang in [src, tgt]:
|
||||
for split in SPLITS:
|
||||
_apply_bbpe(
|
||||
bbpe_model_prefix + ".model",
|
||||
op.join(root, f"{split}.moses.{lang}"),
|
||||
op.join(root, f"{split}.moses.bbpe{bbpe_size}.{lang}"),
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=str, default="data")
|
||||
parser.add_argument(
|
||||
"--bpe-vocab",
|
||||
default=None,
|
||||
type=int,
|
||||
help="Generate tokenized bitext with BPE of size K."
|
||||
"Default to None (disabled).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--bbpe-vocab",
|
||||
default=None,
|
||||
type=int,
|
||||
help="Generate tokenized bitext with BBPE of size K."
|
||||
"Default to None (disabled).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--byte-vocab",
|
||||
action="store_true",
|
||||
help="Generate tokenized bitext with bytes vocabulary",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--char-vocab",
|
||||
action="store_true",
|
||||
help="Generate tokenized bitext with chars vocabulary",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
preprocess_iwslt17(
|
||||
args.root,
|
||||
"fr",
|
||||
"en",
|
||||
args.bpe_vocab,
|
||||
args.char_vocab,
|
||||
args.bbpe_vocab,
|
||||
args.byte_vocab,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
PY_BIN_ROOT=
|
||||
|
||||
# PyPI dependency
|
||||
${PY_BIN_ROOT}pip install sentencepiece sacremoses
|
||||
|
||||
# Get data
|
||||
if [ ! -d "data" ]; then
|
||||
mkdir data
|
||||
fi
|
||||
|
||||
if [ ! -f "data/fr-en.tgz" ]; then
|
||||
wget https://wit3.fbk.eu/archive/2017-01-trnted/texts/fr/en/fr-en.tgz -P data
|
||||
tar xvf data/fr-en.tgz -C data
|
||||
fi
|
||||
${PY_BIN_ROOT}python get_bitext.py --bpe-vocab 16384 --byte-vocab --char-vocab
|
||||
for VOCAB_SIZE in 2048 4096; do
|
||||
${PY_BIN_ROOT}python get_bitext.py --bpe-vocab ${VOCAB_SIZE} --bbpe-vocab ${VOCAB_SIZE}
|
||||
done
|
||||
rm -r data/fr-en data/fr-en.tgz
|
||||
|
||||
# Generate binary dataset
|
||||
${PY_BIN_ROOT}/fairseq-preprocess --source-lang fr --target-lang en --destdir data/bin_bpe16384 --joined-dictionary \
|
||||
--workers "$(nproc)" --trainpref data/train.moses.bpe16384 --validpref data/valid.moses.bpe16384 \
|
||||
--testpref data/test.moses.bpe16384
|
||||
|
||||
${PY_BIN_ROOT}/fairseq-preprocess --source-lang fr --target-lang en --destdir data/bin_bytes --joined-dictionary \
|
||||
--workers "$(nproc)" --trainpref data/train.moses.bytes --validpref data/valid.moses.bytes \
|
||||
--testpref data/test.moses.bytes
|
||||
|
||||
${PY_BIN_ROOT}/fairseq-preprocess --source-lang fr --target-lang en --destdir data/bin_chars --joined-dictionary \
|
||||
--workers "$(nproc)" --trainpref data/train.moses.chars --validpref data/valid.moses.chars \
|
||||
--testpref data/test.moses.chars
|
||||
|
||||
for VOCAB_SIZE in 2048 4096; do
|
||||
for TYPE in bbpe bpe; do
|
||||
${PY_BIN_ROOT}/fairseq-preprocess --source-lang fr --target-lang en --destdir "data/bin_${TYPE}${VOCAB_SIZE}" \
|
||||
--joined-dictionary --workers "$(nproc)" --trainpref "data/train.moses.${TYPE}${VOCAB_SIZE}" \
|
||||
--validpref "data/valid.moses.${TYPE}${VOCAB_SIZE}" --testpref "data/test.moses.${TYPE}${VOCAB_SIZE}"
|
||||
done
|
||||
done
|
||||
@@ -0,0 +1,107 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from fairseq.models import register_model, register_model_architecture
|
||||
from fairseq.models.transformer import TransformerEncoder, TransformerModel
|
||||
|
||||
|
||||
@register_model("gru_transformer")
|
||||
class GRUTransformerModel(TransformerModel):
|
||||
@classmethod
|
||||
def build_encoder(cls, args, src_dict, embed_tokens):
|
||||
return GRUTransformerEncoder(args, src_dict, embed_tokens)
|
||||
|
||||
|
||||
class GRUTransformerEncoder(TransformerEncoder):
|
||||
def __init__(self, args, dictionary, embed_tokens):
|
||||
super().__init__(args, dictionary, embed_tokens)
|
||||
self.emb_ctx = nn.GRU(
|
||||
input_size=embed_tokens.embedding_dim,
|
||||
hidden_size=embed_tokens.embedding_dim // 2,
|
||||
num_layers=1,
|
||||
bidirectional=True,
|
||||
)
|
||||
|
||||
def forward_embedding(self, src_tokens):
|
||||
# embed tokens and positions
|
||||
x = embed = self.embed_scale * self.embed_tokens(src_tokens)
|
||||
if self.embed_positions is not None:
|
||||
x = embed + self.embed_positions(src_tokens)
|
||||
|
||||
# contextualize embeddings
|
||||
x = x.transpose(0, 1)
|
||||
x = self.dropout_module(x)
|
||||
x, _ = self.emb_ctx.forward(x)
|
||||
x = x.transpose(0, 1)
|
||||
|
||||
if self.layernorm_embedding is not None:
|
||||
x = self.layernorm_embedding(x)
|
||||
x = self.dropout_module(x)
|
||||
return x, embed
|
||||
|
||||
|
||||
@register_model_architecture("gru_transformer", "gru_transformer")
|
||||
def gru_transformer_base_architecture(args):
|
||||
args.encoder_embed_path = getattr(args, "encoder_embed_path", None)
|
||||
args.encoder_embed_dim = getattr(args, "encoder_embed_dim", 512)
|
||||
args.encoder_ffn_embed_dim = getattr(args, "encoder_ffn_embed_dim", 2048)
|
||||
args.encoder_layers = getattr(args, "encoder_layers", 6)
|
||||
args.encoder_attention_heads = getattr(args, "encoder_attention_heads", 8)
|
||||
args.encoder_normalize_before = getattr(args, "encoder_normalize_before", False)
|
||||
args.encoder_learned_pos = getattr(args, "encoder_learned_pos", False)
|
||||
args.decoder_embed_path = getattr(args, "decoder_embed_path", None)
|
||||
args.decoder_embed_dim = getattr(args, "decoder_embed_dim", args.encoder_embed_dim)
|
||||
args.decoder_ffn_embed_dim = getattr(
|
||||
args, "decoder_ffn_embed_dim", args.encoder_ffn_embed_dim
|
||||
)
|
||||
args.decoder_layers = getattr(args, "decoder_layers", 6)
|
||||
args.decoder_attention_heads = getattr(args, "decoder_attention_heads", 8)
|
||||
args.decoder_normalize_before = getattr(args, "decoder_normalize_before", False)
|
||||
args.decoder_learned_pos = getattr(args, "decoder_learned_pos", False)
|
||||
args.attention_dropout = getattr(args, "attention_dropout", 0.0)
|
||||
args.activation_dropout = getattr(args, "activation_dropout", 0.0)
|
||||
args.activation_fn = getattr(args, "activation_fn", "relu")
|
||||
args.dropout = getattr(args, "dropout", 0.1)
|
||||
args.adaptive_softmax_cutoff = getattr(args, "adaptive_softmax_cutoff", None)
|
||||
args.adaptive_softmax_dropout = getattr(args, "adaptive_softmax_dropout", 0)
|
||||
args.share_decoder_input_output_embed = getattr(
|
||||
args, "share_decoder_input_output_embed", False
|
||||
)
|
||||
args.share_all_embeddings = getattr(args, "share_all_embeddings", False)
|
||||
args.no_token_positional_embeddings = getattr(
|
||||
args, "no_token_positional_embeddings", False
|
||||
)
|
||||
args.adaptive_input = getattr(args, "adaptive_input", False)
|
||||
args.no_cross_attention = getattr(args, "no_cross_attention", False)
|
||||
args.cross_self_attention = getattr(args, "cross_self_attention", False)
|
||||
args.layer_wise_attention = getattr(args, "layer_wise_attention", False)
|
||||
|
||||
args.decoder_output_dim = getattr(
|
||||
args, "decoder_output_dim", args.decoder_embed_dim
|
||||
)
|
||||
args.decoder_input_dim = getattr(args, "decoder_input_dim", args.decoder_embed_dim)
|
||||
|
||||
args.no_scale_embedding = getattr(args, "no_scale_embedding", False)
|
||||
args.layernorm_embedding = getattr(args, "layernorm_embedding", False)
|
||||
|
||||
|
||||
@register_model_architecture("gru_transformer", "gru_transformer_big")
|
||||
def gru_transformer_big(args):
|
||||
args.encoder_embed_dim = getattr(args, "encoder_embed_dim", 1024)
|
||||
args.encoder_ffn_embed_dim = getattr(args, "encoder_ffn_embed_dim", 4096)
|
||||
args.encoder_attention_heads = getattr(args, "encoder_attention_heads", 16)
|
||||
args.encoder_normalize_before = getattr(args, "encoder_normalize_before", False)
|
||||
args.decoder_embed_dim = getattr(args, "decoder_embed_dim", 1024)
|
||||
args.decoder_ffn_embed_dim = getattr(args, "decoder_ffn_embed_dim", 4096)
|
||||
args.decoder_attention_heads = getattr(args, "decoder_attention_heads", 16)
|
||||
args.dropout = getattr(args, "dropout", 0.3)
|
||||
gru_transformer_base_architecture(args)
|
||||
@@ -0,0 +1,75 @@
|
||||
# CamemBERT: a Tasty French Language Model
|
||||
|
||||
## Introduction
|
||||
|
||||
[CamemBERT](https://arxiv.org/abs/1911.03894) is a pretrained language model trained on 138GB of French text based on RoBERTa.
|
||||
|
||||
Also available in [github.com/huggingface/transformers](https://github.com/huggingface/transformers/).
|
||||
|
||||
## Pre-trained models
|
||||
|
||||
| Model | #params | Download | Arch. | Training data |
|
||||
|--------------------------------|---------|--------------------------------------------------------------------------------------------------------------------------|-------|-----------------------------------|
|
||||
| `camembert` / `camembert-base` | 110M | [camembert-base.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/camembert-base.tar.gz) | Base | OSCAR (138 GB of text) |
|
||||
| `camembert-large` | 335M | [camembert-large.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/camembert-large.tar.gz) | Large | CCNet (135 GB of text) |
|
||||
| `camembert-base-ccnet` | 110M | [camembert-base-ccnet.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/camembert-base-ccnet.tar.gz) | Base | CCNet (135 GB of text) |
|
||||
| `camembert-base-wikipedia-4gb` | 110M | [camembert-base-wikipedia-4gb.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/camembert-base-wikipedia-4gb.tar.gz) | Base | Wikipedia (4 GB of text) |
|
||||
| `camembert-base-oscar-4gb` | 110M | [camembert-base-oscar-4gb.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/camembert-base-oscar-4gb.tar.gz) | Base | Subsample of OSCAR (4 GB of text) |
|
||||
| `camembert-base-ccnet-4gb` | 110M | [camembert-base-ccnet-4gb.tar.gz](https://dl.fbaipublicfiles.com/fairseq/models/camembert-base-ccnet-4gb.tar.gz) | Base | Subsample of CCNet (4 GB of text) |
|
||||
|
||||
## Example usage
|
||||
|
||||
### fairseq
|
||||
##### Load CamemBERT from torch.hub (PyTorch >= 1.1):
|
||||
```python
|
||||
import torch
|
||||
camembert = torch.hub.load('pytorch/fairseq', 'camembert')
|
||||
camembert.eval() # disable dropout (or leave in train mode to finetune)
|
||||
```
|
||||
|
||||
##### Load CamemBERT (for PyTorch 1.0 or custom models):
|
||||
```python
|
||||
# Download camembert model
|
||||
wget https://dl.fbaipublicfiles.com/fairseq/models/camembert-base.tar.gz
|
||||
tar -xzvf camembert.tar.gz
|
||||
|
||||
# Load the model in fairseq
|
||||
from fairseq.models.roberta import CamembertModel
|
||||
camembert = CamembertModel.from_pretrained('/path/to/camembert')
|
||||
camembert.eval() # disable dropout (or leave in train mode to finetune)
|
||||
```
|
||||
|
||||
##### Filling masks:
|
||||
```python
|
||||
masked_line = 'Le camembert est <mask> :)'
|
||||
camembert.fill_mask(masked_line, topk=3)
|
||||
# [('Le camembert est délicieux :)', 0.4909118115901947, ' délicieux'),
|
||||
# ('Le camembert est excellent :)', 0.10556942224502563, ' excellent'),
|
||||
# ('Le camembert est succulent :)', 0.03453322499990463, ' succulent')]
|
||||
```
|
||||
|
||||
##### Extract features from Camembert:
|
||||
```python
|
||||
# Extract the last layer's features
|
||||
line = "J'aime le camembert !"
|
||||
tokens = camembert.encode(line)
|
||||
last_layer_features = camembert.extract_features(tokens)
|
||||
assert last_layer_features.size() == torch.Size([1, 10, 768])
|
||||
|
||||
# Extract all layer's features (layer 0 is the embedding layer)
|
||||
all_layers = camembert.extract_features(tokens, return_all_hiddens=True)
|
||||
assert len(all_layers) == 13
|
||||
assert torch.all(all_layers[-1] == last_layer_features)
|
||||
```
|
||||
|
||||
## Citation
|
||||
If you use our work, please cite:
|
||||
|
||||
```bibtex
|
||||
@inproceedings{martin2020camembert,
|
||||
title={CamemBERT: a Tasty French Language Model},
|
||||
author={Martin, Louis and Muller, Benjamin and Su{\'a}rez, Pedro Javier Ortiz and Dupont, Yoann and Romary, Laurent and de la Clergerie, {\'E}ric Villemonte and Seddah, Djam{\'e} and Sagot, Beno{\^\i}t},
|
||||
booktitle={Proceedings of the 58th Annual Meeting of the Association for Computational Linguistics},
|
||||
year={2020}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,123 @@
|
||||
# (Vectorized) Lexically constrained decoding with dynamic beam allocation
|
||||
|
||||
This page provides instructions for how to use lexically constrained decoding in Fairseq.
|
||||
Fairseq implements the code described in the following papers:
|
||||
|
||||
* [Fast Lexically Constrained Decoding With Dynamic Beam Allocation](https://www.aclweb.org/anthology/N18-1119/) (Post & Vilar, 2018)
|
||||
* [Improved Lexically Constrained Decoding for Translation and Monolingual Rewriting](https://www.aclweb.org/anthology/N19-1090/) (Hu et al., 2019)
|
||||
|
||||
## Quick start
|
||||
|
||||
Constrained search is enabled by adding the command-line argument `--constraints` to `fairseq-interactive`.
|
||||
Constraints are appended to each line of input, separated by tabs. Each constraint (one or more tokens)
|
||||
is a separate field.
|
||||
|
||||
The following command, using [Fairseq's WMT19 German--English model](https://github.com/pytorch/fairseq/blob/master/examples/wmt19/README.md),
|
||||
translates the sentence *Die maschinelle Übersetzung ist schwer zu kontrollieren.* with the constraints
|
||||
"hard" and "to influence".
|
||||
|
||||
echo -e "Die maschinelle Übersetzung ist schwer zu kontrollieren.\thard\ttoinfluence" \
|
||||
| normalize.py | tok.py \
|
||||
| fairseq-interactive /path/to/model \
|
||||
--path /path/to/model/model1.pt \
|
||||
--bpe fastbpe \
|
||||
--bpe-codes /path/to/model/bpecodes \
|
||||
--constraints \
|
||||
-s de -t en \
|
||||
--beam 10
|
||||
|
||||
(tok.py and normalize.py can be found in the same directory as this README; they are just shortcuts around Fairseq's WMT19 preprocessing).
|
||||
This will generate the following output:
|
||||
|
||||
[snip]
|
||||
S-0 Die masch@@ in@@ elle Über@@ setzung ist schwer zu kontrollieren .
|
||||
W-0 1.844 seconds
|
||||
C-0 hard
|
||||
C-0 influence
|
||||
H-0 -1.5333266258239746 Mach@@ ine trans@@ lation is hard to influence .
|
||||
D-0 -1.5333266258239746 Machine translation is hard to influence .
|
||||
P-0 -0.5434 -0.1423 -0.1930 -0.1415 -0.2346 -1.8031 -0.1701 -11.7727 -0.1815 -0.1511
|
||||
|
||||
By default, constraints are generated in the order supplied, with any number (zero or more) of tokens generated
|
||||
between constraints. If you wish for the decoder to order the constraints, then use `--constraints unordered`.
|
||||
Note that you may want to use a larger beam.
|
||||
|
||||
## Implementation details
|
||||
|
||||
The heart of the implementation is in `fairseq/search.py`, which adds a `LexicallyConstrainedBeamSearch` instance.
|
||||
This instance of beam search tracks the progress of each hypothesis in the beam through the set of constraints
|
||||
provided for each input sentence. It does this using one of two classes, both found in `fairseq/token_generation_contstraints.py`:
|
||||
|
||||
* OrderedConstraintState: assumes the `C` input constraints will be generated in the provided order
|
||||
* UnorderedConstraintState: tries to apply `C` (phrasal) constraints in all `C!` orders
|
||||
|
||||
## Differences from Sockeye
|
||||
|
||||
There are a number of [differences from Sockeye's implementation](https://awslabs.github.io/sockeye/inference.html#lexical-constraints).
|
||||
|
||||
* Generating constraints in the order supplied (the default option here) is not available in Sockeye.
|
||||
* Due to an improved beam allocation method, there is no need to prune the beam.
|
||||
* Again due to better allocation, beam sizes as low as 10 or even 5 are often sufficient.
|
||||
* [The vector extensions described in Hu et al.](https://github.com/edwardjhu/sockeye/tree/trie_constraints) (NAACL 2019) were never merged
|
||||
into the main Sockeye branch.
|
||||
|
||||
## Citation
|
||||
|
||||
The paper first describing lexical constraints for seq2seq decoding is:
|
||||
|
||||
```bibtex
|
||||
@inproceedings{hokamp-liu-2017-lexically,
|
||||
title = "Lexically Constrained Decoding for Sequence Generation Using Grid Beam Search",
|
||||
author = "Hokamp, Chris and
|
||||
Liu, Qun",
|
||||
booktitle = "Proceedings of the 55th Annual Meeting of the Association for Computational Linguistics (Volume 1: Long Papers)",
|
||||
month = jul,
|
||||
year = "2017",
|
||||
address = "Vancouver, Canada",
|
||||
publisher = "Association for Computational Linguistics",
|
||||
url = "https://www.aclweb.org/anthology/P17-1141",
|
||||
doi = "10.18653/v1/P17-1141",
|
||||
pages = "1535--1546",
|
||||
}
|
||||
```
|
||||
|
||||
The fairseq implementation uses the extensions described in
|
||||
|
||||
```bibtex
|
||||
@inproceedings{post-vilar-2018-fast,
|
||||
title = "Fast Lexically Constrained Decoding with Dynamic Beam Allocation for Neural Machine Translation",
|
||||
author = "Post, Matt and
|
||||
Vilar, David",
|
||||
booktitle = "Proceedings of the 2018 Conference of the North {A}merican Chapter of the Association for Computational Linguistics: Human Language Technologies, Volume 1 (Long Papers)",
|
||||
month = jun,
|
||||
year = "2018",
|
||||
address = "New Orleans, Louisiana",
|
||||
publisher = "Association for Computational Linguistics",
|
||||
url = "https://www.aclweb.org/anthology/N18-1119",
|
||||
doi = "10.18653/v1/N18-1119",
|
||||
pages = "1314--1324",
|
||||
}
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```bibtex
|
||||
@inproceedings{hu-etal-2019-improved,
|
||||
title = "Improved Lexically Constrained Decoding for Translation and Monolingual Rewriting",
|
||||
author = "Hu, J. Edward and
|
||||
Khayrallah, Huda and
|
||||
Culkin, Ryan and
|
||||
Xia, Patrick and
|
||||
Chen, Tongfei and
|
||||
Post, Matt and
|
||||
Van Durme, Benjamin",
|
||||
booktitle = "Proceedings of the 2019 Conference of the North {A}merican Chapter of the Association for Computational Linguistics: Human Language Technologies, Volume 1 (Long and Short Papers)",
|
||||
month = jun,
|
||||
year = "2019",
|
||||
address = "Minneapolis, Minnesota",
|
||||
publisher = "Association for Computational Linguistics",
|
||||
url = "https://www.aclweb.org/anthology/N19-1090",
|
||||
doi = "10.18653/v1/N19-1090",
|
||||
pages = "839--850",
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import sys
|
||||
|
||||
from sacremoses.normalize import MosesPunctNormalizer
|
||||
|
||||
|
||||
def main(args):
|
||||
normalizer = MosesPunctNormalizer(lang=args.lang, penn=args.penn)
|
||||
for line in sys.stdin:
|
||||
print(normalizer.normalize(line.rstrip()), flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--lang", "-l", default="en")
|
||||
parser.add_argument("--penn", "-p", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
main(args)
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import sys
|
||||
|
||||
import sacremoses
|
||||
|
||||
|
||||
def main(args):
|
||||
"""Tokenizes, preserving tabs"""
|
||||
mt = sacremoses.MosesTokenizer(lang=args.lang)
|
||||
|
||||
def tok(s):
|
||||
return mt.tokenize(s, return_str=True)
|
||||
|
||||
for line in sys.stdin:
|
||||
parts = list(map(tok, line.split("\t")))
|
||||
print(*parts, sep="\t", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--lang", "-l", default="en")
|
||||
parser.add_argument("--penn", "-p", action="store_true")
|
||||
parser.add_argument("--fields", "-f", help="fields to tokenize")
|
||||
args = parser.parse_args()
|
||||
|
||||
main(args)
|
||||
@@ -0,0 +1,25 @@
|
||||
# Convolutional Sequence to Sequence Learning (Gehring et al., 2017)
|
||||
|
||||
## Pre-trained models
|
||||
|
||||
Description | Dataset | Model | Test set(s)
|
||||
---|---|---|---
|
||||
Convolutional <br> ([Gehring et al., 2017](https://arxiv.org/abs/1705.03122)) | [WMT14 English-French](http://statmt.org/wmt14/translation-task.html#Download) | [download (.tar.bz2)](https://dl.fbaipublicfiles.com/fairseq/models/wmt14.v2.en-fr.fconv-py.tar.bz2) | newstest2014: <br> [download (.tar.bz2)](https://dl.fbaipublicfiles.com/fairseq/data/wmt14.v2.en-fr.newstest2014.tar.bz2) <br> newstest2012/2013: <br> [download (.tar.bz2)](https://dl.fbaipublicfiles.com/fairseq/data/wmt14.v2.en-fr.ntst1213.tar.bz2)
|
||||
Convolutional <br> ([Gehring et al., 2017](https://arxiv.org/abs/1705.03122)) | [WMT14 English-German](http://statmt.org/wmt14/translation-task.html#Download) | [download (.tar.bz2)](https://dl.fbaipublicfiles.com/fairseq/models/wmt14.en-de.fconv-py.tar.bz2) | newstest2014: <br> [download (.tar.bz2)](https://dl.fbaipublicfiles.com/fairseq/data/wmt14.en-de.newstest2014.tar.bz2)
|
||||
Convolutional <br> ([Gehring et al., 2017](https://arxiv.org/abs/1705.03122)) | [WMT17 English-German](http://statmt.org/wmt17/translation-task.html#Download) | [download (.tar.bz2)](https://dl.fbaipublicfiles.com/fairseq/models/wmt17.v2.en-de.fconv-py.tar.bz2) | newstest2014: <br> [download (.tar.bz2)](https://dl.fbaipublicfiles.com/fairseq/data/wmt17.v2.en-de.newstest2014.tar.bz2)
|
||||
|
||||
## Example usage
|
||||
|
||||
See the [translation README](../translation/README.md) for instructions on reproducing results for WMT'14 En-De and
|
||||
WMT'14 En-Fr using the `fconv_wmt_en_de` and `fconv_wmt_en_fr` model architectures.
|
||||
|
||||
## Citation
|
||||
|
||||
```bibtex
|
||||
@inproceedings{gehring2017convs2s,
|
||||
title = {Convolutional Sequence to Sequence Learning},
|
||||
author = {Gehring, Jonas, and Auli, Michael and Grangier, David and Yarats, Denis and Dauphin, Yann N},
|
||||
booktitle = {Proc. of ICML},
|
||||
year = 2017,
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,61 @@
|
||||
# Cross-lingual Retrieval for Iterative Self-Supervised Training
|
||||
|
||||
https://arxiv.org/pdf/2006.09526.pdf
|
||||
|
||||
## Introduction
|
||||
|
||||
CRISS is a multilingual sequence-to-sequnce pretraining method where mining and training processes are applied iteratively, improving cross-lingual alignment and translation ability at the same time.
|
||||
|
||||
## Requirements:
|
||||
|
||||
* faiss: https://github.com/facebookresearch/faiss
|
||||
* mosesdecoder: https://github.com/moses-smt/mosesdecoder
|
||||
* flores: https://github.com/facebookresearch/flores
|
||||
* LASER: https://github.com/facebookresearch/LASER
|
||||
|
||||
## Unsupervised Machine Translation
|
||||
##### 1. Download and decompress CRISS checkpoints
|
||||
```
|
||||
cd examples/criss
|
||||
wget https://dl.fbaipublicfiles.com/criss/criss_3rd_checkpoints.tar.gz
|
||||
tar -xf criss_checkpoints.tar.gz
|
||||
```
|
||||
##### 2. Download and preprocess Flores test dataset
|
||||
Make sure to run all scripts from examples/criss directory
|
||||
```
|
||||
bash download_and_preprocess_flores_test.sh
|
||||
```
|
||||
|
||||
##### 3. Run Evaluation on Sinhala-English
|
||||
```
|
||||
bash unsupervised_mt/eval.sh
|
||||
```
|
||||
|
||||
## Sentence Retrieval
|
||||
##### 1. Download and preprocess Tatoeba dataset
|
||||
```
|
||||
bash download_and_preprocess_tatoeba.sh
|
||||
```
|
||||
|
||||
##### 2. Run Sentence Retrieval on Tatoeba Kazakh-English
|
||||
```
|
||||
bash sentence_retrieval/sentence_retrieval_tatoeba.sh
|
||||
```
|
||||
|
||||
## Mining
|
||||
##### 1. Install faiss
|
||||
Follow instructions on https://github.com/facebookresearch/faiss/blob/master/INSTALL.md
|
||||
##### 2. Mine pseudo-parallel data between Kazakh and English
|
||||
```
|
||||
bash mining/mine_example.sh
|
||||
```
|
||||
|
||||
## Citation
|
||||
```bibtex
|
||||
@article{tran2020cross,
|
||||
title={Cross-lingual retrieval for iterative self-supervised training},
|
||||
author={Tran, Chau and Tang, Yuqing and Li, Xian and Gu, Jiatao},
|
||||
journal={arXiv preprint arXiv:2006.09526},
|
||||
year={2020}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
SPM_ENCODE=flores/scripts/spm_encode.py
|
||||
DATA=data_tmp
|
||||
SPM_MODEL=criss_checkpoints/sentence.bpe.model
|
||||
DICT=criss_checkpoints/dict.txt
|
||||
|
||||
download_data() {
|
||||
CORPORA=$1
|
||||
URL=$2
|
||||
|
||||
if [ -f $CORPORA ]; then
|
||||
echo "$CORPORA already exists, skipping download"
|
||||
else
|
||||
echo "Downloading $URL"
|
||||
wget $URL -O $CORPORA --no-check-certificate || rm -f $CORPORA
|
||||
if [ -f $CORPORA ]; then
|
||||
echo "$URL successfully downloaded."
|
||||
else
|
||||
echo "$URL not successfully downloaded."
|
||||
rm -f $CORPORA
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ -f flores ]]; then
|
||||
echo "flores already cloned"
|
||||
else
|
||||
git clone https://github.com/facebookresearch/flores
|
||||
fi
|
||||
|
||||
mkdir -p $DATA
|
||||
download_data $DATA/wikipedia_en_ne_si_test_sets.tgz "https://github.com/facebookresearch/flores/raw/master/data/wikipedia_en_ne_si_test_sets.tgz"
|
||||
pushd $DATA
|
||||
pwd
|
||||
tar -vxf wikipedia_en_ne_si_test_sets.tgz
|
||||
popd
|
||||
|
||||
|
||||
for lang in ne_NP si_LK; do
|
||||
datadir=$DATA/${lang}-en_XX-flores
|
||||
rm -rf $datadir
|
||||
mkdir -p $datadir
|
||||
TEST_PREFIX=$DATA/wikipedia_en_ne_si_test_sets/wikipedia.test
|
||||
python $SPM_ENCODE \
|
||||
--model ${SPM_MODEL} \
|
||||
--output_format=piece \
|
||||
--inputs ${TEST_PREFIX}.${lang:0:2}-en.${lang:0:2} ${TEST_PREFIX}.${lang:0:2}-en.en \
|
||||
--outputs $datadir/test.bpe.${lang}-en_XX.${lang} $datadir/test.bpe.${lang}-en_XX.en_XX
|
||||
|
||||
# binarize data
|
||||
fairseq-preprocess \
|
||||
--source-lang ${lang} --target-lang en_XX \
|
||||
--testpref $datadir/test.bpe.${lang}-en_XX \
|
||||
--destdir $datadir \
|
||||
--srcdict ${DICT} \
|
||||
--joined-dictionary \
|
||||
--workers 4
|
||||
done
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
SPM_ENCODE=flores/scripts/spm_encode.py
|
||||
DATA=data_tmp
|
||||
SPM_MODEL=criss_checkpoints/sentence.bpe.model
|
||||
DICT=criss_checkpoints/dict.txt
|
||||
|
||||
if [[ -f flores ]]; then
|
||||
echo "flores already cloned"
|
||||
else
|
||||
git clone https://github.com/facebookresearch/flores
|
||||
fi
|
||||
if [[ -f LASER ]]; then
|
||||
echo "LASER already cloned"
|
||||
else
|
||||
git clone https://github.com/facebookresearch/LASER
|
||||
fi
|
||||
mkdir -p data_tmp
|
||||
declare -A lang_tatoeba_map=( ["ar_AR"]="ara" ["de_DE"]="deu" ["es_XX"]="spa" ["et_EE"]="est" ["fi_FI"]="fin" ["fr_XX"]="fra" ["hi_IN"]="hin" ["it_IT"]="ita" ["ja_XX"]="jpn" ["ko_KR"]="kor" ["kk_KZ"]="kaz" ["nl_XX"]="nld" ["ru_RU"]="rus" ["tr_TR"]="tur" ["vi_VN"]="vie" ["zh_CN"]="cmn")
|
||||
for lang in ar_AR de_DE es_XX et_EE fi_FI fr_XX hi_IN it_IT ja_XX kk_KZ ko_KR nl_XX ru_RU tr_TR vi_VN zh_CN; do
|
||||
lang_tatoeba=${lang_tatoeba_map[$lang]}
|
||||
echo $lang_tatoeba
|
||||
datadir=$DATA/${lang}-en_XX-tatoeba
|
||||
rm -rf $datadir
|
||||
mkdir -p $datadir
|
||||
TEST_PREFIX=LASER/data/tatoeba/v1/tatoeba
|
||||
python $SPM_ENCODE \
|
||||
--model ${SPM_MODEL} \
|
||||
--output_format=piece \
|
||||
--inputs ${TEST_PREFIX}.${lang_tatoeba}-eng.${lang_tatoeba} ${TEST_PREFIX}.${lang_tatoeba}-eng.eng \
|
||||
--outputs $datadir/test.bpe.${lang}-en_XX.${lang} $datadir/test.bpe.${lang}-en_XX.en_XX
|
||||
|
||||
# binarize data
|
||||
fairseq-preprocess \
|
||||
--source-lang ${lang} --target-lang en_XX \
|
||||
--testpref $datadir/test.bpe.${lang}-en_XX \
|
||||
--destdir $datadir \
|
||||
--srcdict ${DICT} \
|
||||
--joined-dictionary \
|
||||
--workers 4
|
||||
done
|
||||
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python3 -u
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
import argparse
|
||||
import glob
|
||||
from subprocess import check_call
|
||||
|
||||
try:
|
||||
import faiss
|
||||
|
||||
has_faiss = True
|
||||
except ImportError:
|
||||
has_faiss = False
|
||||
import numpy as np
|
||||
|
||||
|
||||
GB = 1024 * 1024 * 1024
|
||||
|
||||
|
||||
def call(cmd):
|
||||
print(cmd)
|
||||
check_call(cmd, shell=True)
|
||||
|
||||
|
||||
def get_batches(directory, lang, prefix="all_avg_pool"):
|
||||
print(f"Finding in {directory}/{prefix}.{lang}*")
|
||||
files = glob.glob(f"{directory}/{prefix}.{lang}*")
|
||||
emb_files = []
|
||||
txt_files = []
|
||||
for emb_fi in files:
|
||||
emb_files.append(emb_fi)
|
||||
txt_fi = emb_fi.replace(prefix, "sentences")
|
||||
txt_files.append(txt_fi)
|
||||
return emb_files, txt_files
|
||||
|
||||
|
||||
def load_batch(emb_file, dim):
|
||||
embeddings = np.fromfile(emb_file, dtype=np.float32)
|
||||
num_rows = int(embeddings.shape[0] / dim)
|
||||
embeddings = embeddings.reshape((num_rows, dim))
|
||||
faiss.normalize_L2(embeddings)
|
||||
return embeddings
|
||||
|
||||
|
||||
def knnGPU_sharded(x_batches_f, y_batches_f, dim, k, direction="x2y"):
|
||||
if not has_faiss:
|
||||
raise ImportError("Please install Faiss")
|
||||
sims = []
|
||||
inds = []
|
||||
xfrom = 0
|
||||
xto = 0
|
||||
for x_batch_f in x_batches_f:
|
||||
yfrom = 0
|
||||
yto = 0
|
||||
x_batch = load_batch(x_batch_f, dim)
|
||||
xto = xfrom + x_batch.shape[0]
|
||||
bsims, binds = [], []
|
||||
for y_batch_f in y_batches_f:
|
||||
y_batch = load_batch(y_batch_f, dim)
|
||||
neighbor_size = min(k, y_batch.shape[0])
|
||||
yto = yfrom + y_batch.shape[0]
|
||||
print("{}-{} -> {}-{}".format(xfrom, xto, yfrom, yto))
|
||||
idx = faiss.IndexFlatIP(dim)
|
||||
idx = faiss.index_cpu_to_all_gpus(idx)
|
||||
idx.add(y_batch)
|
||||
bsim, bind = idx.search(x_batch, neighbor_size)
|
||||
|
||||
bsims.append(bsim)
|
||||
binds.append(bind + yfrom)
|
||||
yfrom += y_batch.shape[0]
|
||||
del idx
|
||||
del y_batch
|
||||
bsims = np.concatenate(bsims, axis=1)
|
||||
binds = np.concatenate(binds, axis=1)
|
||||
aux = np.argsort(-bsims, axis=1)
|
||||
sim_batch = np.zeros((x_batch.shape[0], k), dtype=np.float32)
|
||||
ind_batch = np.zeros((x_batch.shape[0], k), dtype=np.int64)
|
||||
for i in range(x_batch.shape[0]):
|
||||
for j in range(k):
|
||||
sim_batch[i, j] = bsims[i, aux[i, j]]
|
||||
ind_batch[i, j] = binds[i, aux[i, j]]
|
||||
sims.append(sim_batch)
|
||||
inds.append(ind_batch)
|
||||
xfrom += x_batch.shape[0]
|
||||
del x_batch
|
||||
sim = np.concatenate(sims, axis=0)
|
||||
ind = np.concatenate(inds, axis=0)
|
||||
return sim, ind
|
||||
|
||||
|
||||
def score(sim, fwd_mean, bwd_mean, margin):
|
||||
return margin(sim, (fwd_mean + bwd_mean) / 2)
|
||||
|
||||
|
||||
def score_candidates(
|
||||
sim_mat, candidate_inds, fwd_mean, bwd_mean, margin, verbose=False
|
||||
):
|
||||
print(" - scoring {:d} candidates".format(sim_mat.shape[0]))
|
||||
scores = np.zeros(candidate_inds.shape)
|
||||
for i in range(scores.shape[0]):
|
||||
for j in range(scores.shape[1]):
|
||||
k = int(candidate_inds[i, j])
|
||||
scores[i, j] = score(sim_mat[i, j], fwd_mean[i], bwd_mean[k], margin)
|
||||
return scores
|
||||
|
||||
|
||||
def load_text(files):
|
||||
all_sentences = []
|
||||
for fi in files:
|
||||
with open(fi) as sentence_fi:
|
||||
for line in sentence_fi:
|
||||
all_sentences.append(line.strip())
|
||||
print(f"Read {len(all_sentences)} sentences")
|
||||
return all_sentences
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Mine bitext")
|
||||
parser.add_argument("--src-lang", help="Source language")
|
||||
parser.add_argument("--tgt-lang", help="Target language")
|
||||
parser.add_argument(
|
||||
"--dict-path", help="Path to dictionary file", default="dict.txt"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--spm-path", help="Path to SPM model file", default="sentence.bpe.model"
|
||||
)
|
||||
parser.add_argument("--dim", type=int, default=1024, help="Embedding dimension")
|
||||
parser.add_argument("--mem", type=int, default=5, help="Memory in GB")
|
||||
parser.add_argument("--src-dir", help="Source directory")
|
||||
parser.add_argument("--tgt-dir", help="Target directory")
|
||||
parser.add_argument("--output", help="Output path")
|
||||
parser.add_argument(
|
||||
"--neighborhood", type=int, default=4, help="Embedding dimension"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--threshold", type=float, default=1.06, help="Threshold on mined bitext"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--valid-size",
|
||||
type=int,
|
||||
default=2000,
|
||||
help="Number of sentences used for validation set",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--min-count",
|
||||
type=int,
|
||||
default=50000,
|
||||
help="Min num sentences used for each language",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
x_batches_f, x_sents_f = get_batches(args.src_dir, args.src_lang)
|
||||
y_batches_f, y_sents_f = get_batches(args.tgt_dir, args.tgt_lang)
|
||||
margin = lambda a, b: a / b
|
||||
y2x_sim, y2x_ind = knnGPU_sharded(
|
||||
y_batches_f, x_batches_f, args.dim, args.neighborhood, direction="y2x"
|
||||
)
|
||||
x2y_sim, x2y_ind = knnGPU_sharded(
|
||||
x_batches_f, y_batches_f, args.dim, args.neighborhood, direction="x2y"
|
||||
)
|
||||
|
||||
x2y_mean = x2y_sim.mean(axis=1)
|
||||
y2x_mean = y2x_sim.mean(axis=1)
|
||||
fwd_scores = score_candidates(x2y_sim, x2y_ind, x2y_mean, y2x_mean, margin)
|
||||
bwd_scores = score_candidates(y2x_sim, y2x_ind, y2x_mean, x2y_mean, margin)
|
||||
fwd_best = x2y_ind[np.arange(x2y_sim.shape[0]), fwd_scores.argmax(axis=1)]
|
||||
bwd_best = y2x_ind[np.arange(y2x_sim.shape[0]), bwd_scores.argmax(axis=1)]
|
||||
indices = np.stack(
|
||||
(
|
||||
np.concatenate((np.arange(x2y_ind.shape[0]), bwd_best)),
|
||||
np.concatenate((fwd_best, np.arange(y2x_ind.shape[0]))),
|
||||
),
|
||||
axis=1,
|
||||
)
|
||||
scores = np.concatenate((fwd_scores.max(axis=1), bwd_scores.max(axis=1)))
|
||||
|
||||
x_sentences = load_text(x_sents_f)
|
||||
y_sentences = load_text(y_sents_f)
|
||||
|
||||
threshold = args.threshold
|
||||
min_count = args.min_count
|
||||
seen_src, seen_trg = set(), set()
|
||||
directory = args.output
|
||||
call(f"mkdir -p {directory}")
|
||||
src_out = open(
|
||||
f"{directory}/all.{args.src_lang}",
|
||||
mode="w",
|
||||
encoding="utf-8",
|
||||
errors="surrogateescape",
|
||||
)
|
||||
tgt_out = open(
|
||||
f"{directory}/all.{args.tgt_lang}",
|
||||
mode="w",
|
||||
encoding="utf-8",
|
||||
errors="surrogateescape",
|
||||
)
|
||||
scores_out = open(
|
||||
f"{directory}/all.scores", mode="w", encoding="utf-8", errors="surrogateescape"
|
||||
)
|
||||
count = 0
|
||||
for i in np.argsort(-scores):
|
||||
src_ind, trg_ind = indices[i]
|
||||
if src_ind not in seen_src and trg_ind not in seen_trg:
|
||||
seen_src.add(src_ind)
|
||||
seen_trg.add(trg_ind)
|
||||
if scores[i] > threshold or count < min_count:
|
||||
if x_sentences[src_ind]:
|
||||
print(scores[i], file=scores_out)
|
||||
print(x_sentences[src_ind], file=src_out)
|
||||
print(y_sentences[trg_ind], file=tgt_out)
|
||||
count += 1
|
||||
else:
|
||||
print(f"Ignoring sentence: {x_sentences[src_ind]}")
|
||||
src_out.close()
|
||||
tgt_out.close()
|
||||
scores_out.close()
|
||||
|
||||
print(f"Found {count} pairs for threshold={threshold}")
|
||||
with open(f"{directory}/all.{args.src_lang}") as all_s, open(
|
||||
f"{directory}/all.{args.tgt_lang}"
|
||||
) as all_t, open(f"{directory}/valid.{args.src_lang}", "w") as valid_s, open(
|
||||
f"{directory}/valid.{args.tgt_lang}", "w"
|
||||
) as valid_t, open(
|
||||
f"{directory}/train.{args.src_lang}", "w"
|
||||
) as train_s, open(
|
||||
f"{directory}/train.{args.tgt_lang}", "w"
|
||||
) as train_t:
|
||||
count = 0
|
||||
for s_line, t_line in zip(all_s, all_t):
|
||||
s_line = s_line.split("\t")[1]
|
||||
t_line = t_line.split("\t")[1]
|
||||
if count >= args.valid_size:
|
||||
train_s.write(s_line)
|
||||
train_t.write(t_line)
|
||||
else:
|
||||
valid_s.write(s_line)
|
||||
valid_t.write(t_line)
|
||||
count += 1
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
source_lang=kk_KZ
|
||||
target_lang=en_XX
|
||||
MODEL=criss_checkpoints/criss.3rd.pt
|
||||
SPM=criss_checkpoints/sentence.bpe.model
|
||||
SPLIT=test
|
||||
LANG_DICT=criss_checkpoints/lang_dict.txt
|
||||
SPM_ENCODE=flores/scripts/spm_encode.py
|
||||
SAVE_ENCODER=save_encoder.py
|
||||
ENCODER_SAVE_ROOT=sentence_embeddings/$MODEL
|
||||
DICT=criss_checkpoints/dict.txt
|
||||
THRESHOLD=1.02
|
||||
MIN_COUNT=500
|
||||
|
||||
DATA_DIR=data_tmp
|
||||
SAVE_DIR=mining/${source_lang}_${target_lang}_mined
|
||||
ENCODER_SAVE_DIR=${ENCODER_SAVE_ROOT}/${source_lang}-${target_lang}
|
||||
INPUT_DIR=$DATA_DIR/${source_lang}-${target_lang}-tatoeba
|
||||
|
||||
mkdir -p $ENCODER_SAVE_DIR/${target_lang}
|
||||
mkdir -p $ENCODER_SAVE_DIR/${source_lang}
|
||||
mkdir -p $SAVE_DIR
|
||||
|
||||
## Save encoder outputs
|
||||
|
||||
# Save encoder outputs for source sentences
|
||||
python $SAVE_ENCODER \
|
||||
${INPUT_DIR} \
|
||||
--path ${MODEL} \
|
||||
--task translation_multi_simple_epoch \
|
||||
--lang-pairs ${source_lang}-${target_lang} \
|
||||
--lang-dict ${LANG_DICT} \
|
||||
--gen-subset ${SPLIT} \
|
||||
--bpe 'sentencepiece' \
|
||||
-s ${source_lang} -t ${target_lang} \
|
||||
--sentencepiece-model ${SPM} \
|
||||
--remove-bpe 'sentencepiece' \
|
||||
--beam 1 \
|
||||
--lang-tok-style mbart \
|
||||
--encoder-save-dir ${ENCODER_SAVE_DIR}/${source_lang}
|
||||
|
||||
## Save encoder outputs for target sentences
|
||||
python $SAVE_ENCODER \
|
||||
${INPUT_DIR} \
|
||||
--path ${MODEL} \
|
||||
--lang-pairs ${source_lang}-${target_lang} \
|
||||
--lang-dict ${LANG_DICT} \
|
||||
--task translation_multi_simple_epoch \
|
||||
--gen-subset ${SPLIT} \
|
||||
--bpe 'sentencepiece' \
|
||||
-t ${source_lang} -s ${target_lang} \
|
||||
--sentencepiece-model ${SPM} \
|
||||
--remove-bpe 'sentencepiece' \
|
||||
--beam 1 \
|
||||
--lang-tok-style mbart \
|
||||
--encoder-save-dir ${ENCODER_SAVE_DIR}/${target_lang}
|
||||
|
||||
## Mining
|
||||
python mining/mine.py \
|
||||
--src-lang ${source_lang} \
|
||||
--tgt-lang ${target_lang} \
|
||||
--dim 1024 \
|
||||
--mem 10 \
|
||||
--neighborhood 4 \
|
||||
--src-dir ${ENCODER_SAVE_DIR}/${source_lang} \
|
||||
--tgt-dir ${ENCODER_SAVE_DIR}/${target_lang} \
|
||||
--output $SAVE_DIR \
|
||||
--threshold ${THRESHOLD} \
|
||||
--min-count ${MIN_COUNT} \
|
||||
--valid-size 100 \
|
||||
--dict-path ${DICT} \
|
||||
--spm-path ${SPM} \
|
||||
|
||||
|
||||
## Process and binarize mined data
|
||||
python $SPM_ENCODE \
|
||||
--model ${SPM} \
|
||||
--output_format=piece \
|
||||
--inputs mining/${source_lang}_${target_lang}_mined/train.${source_lang} mining/${source_lang}_${target_lang}_mined/train.${target_lang} \
|
||||
--outputs mining/${source_lang}_${target_lang}_mined/train.bpe.${source_lang} mining/${source_lang}_${target_lang}_mined/train.bpe.${target_lang}
|
||||
|
||||
python $SPM_ENCODE \
|
||||
--model ${SPM} \
|
||||
--output_format=piece \
|
||||
--inputs mining/${source_lang}_${target_lang}_mined/valid.${source_lang} mining/${source_lang}_${target_lang}_mined/valid.${target_lang} \
|
||||
--outputs mining/${source_lang}_${target_lang}_mined/valid.bpe.${source_lang} mining/${source_lang}_${target_lang}_mined/valid.bpe.${target_lang}
|
||||
|
||||
|
||||
fairseq-preprocess \
|
||||
--source-lang ${source_lang} \
|
||||
--target-lang ${target_lang} \
|
||||
--trainpref mining/${source_lang}_${target_lang}_mined/train.bpe \
|
||||
--validpref mining/${source_lang}_${target_lang}_mined/valid.bpe \
|
||||
--destdir mining/${source_lang}_${target_lang}_mined \
|
||||
--srcdict ${DICT} \
|
||||
--joined-dictionary \
|
||||
--workers 8
|
||||
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3 -u
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
"""
|
||||
Translate pre-processed data with a trained model.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from fairseq import checkpoint_utils, options, progress_bar, tasks, utils
|
||||
from fairseq.sequence_generator import EnsembleModel
|
||||
|
||||
|
||||
def get_avg_pool(
|
||||
models, sample, prefix_tokens, src_dict, remove_bpe, has_langtok=False
|
||||
):
|
||||
model = EnsembleModel(models)
|
||||
|
||||
# model.forward normally channels prev_output_tokens into the decoder
|
||||
# separately, but SequenceGenerator directly calls model.encoder
|
||||
encoder_input = {
|
||||
k: v for k, v in sample["net_input"].items() if k != "prev_output_tokens"
|
||||
}
|
||||
|
||||
# compute the encoder output for each beam
|
||||
encoder_outs = model.forward_encoder(encoder_input)
|
||||
np_encoder_outs = encoder_outs[0].encoder_out.cpu().numpy().astype(np.float32)
|
||||
encoder_mask = 1 - encoder_outs[0].encoder_padding_mask.cpu().numpy().astype(
|
||||
np.float32
|
||||
)
|
||||
encoder_mask = np.expand_dims(encoder_mask.T, axis=2)
|
||||
if has_langtok:
|
||||
encoder_mask = encoder_mask[1:, :, :]
|
||||
np_encoder_outs = np_encoder_outs[1, :, :]
|
||||
masked_encoder_outs = encoder_mask * np_encoder_outs
|
||||
avg_pool = (masked_encoder_outs / encoder_mask.sum(axis=0)).sum(axis=0)
|
||||
return avg_pool
|
||||
|
||||
|
||||
def main(args):
|
||||
assert args.path is not None, "--path required for generation!"
|
||||
assert (
|
||||
not args.sampling or args.nbest == args.beam
|
||||
), "--sampling requires --nbest to be equal to --beam"
|
||||
assert (
|
||||
args.replace_unk is None or args.raw_text
|
||||
), "--replace-unk requires a raw text dataset (--raw-text)"
|
||||
|
||||
args.beam = 1
|
||||
utils.import_user_module(args)
|
||||
|
||||
if args.max_tokens is None:
|
||||
args.max_tokens = 12000
|
||||
print(args)
|
||||
use_cuda = torch.cuda.is_available() and not args.cpu
|
||||
|
||||
# Load dataset splits
|
||||
task = tasks.setup_task(args)
|
||||
task.load_dataset(args.gen_subset)
|
||||
|
||||
# Set dictionaries
|
||||
try:
|
||||
src_dict = getattr(task, "source_dictionary", None)
|
||||
except NotImplementedError:
|
||||
src_dict = None
|
||||
tgt_dict = task.target_dictionary
|
||||
|
||||
# Load ensemble
|
||||
print("| loading model(s) from {}".format(args.path))
|
||||
models, _model_args = checkpoint_utils.load_model_ensemble(
|
||||
args.path.split(":"),
|
||||
arg_overrides=eval(args.model_overrides),
|
||||
task=task,
|
||||
)
|
||||
|
||||
# Optimize ensemble for generation
|
||||
for model in models:
|
||||
model.make_generation_fast_(
|
||||
beamable_mm_beam_size=None if args.no_beamable_mm else args.beam,
|
||||
need_attn=args.print_alignment,
|
||||
)
|
||||
if args.fp16:
|
||||
model.half()
|
||||
if use_cuda:
|
||||
model.cuda()
|
||||
|
||||
# Load alignment dictionary for unknown word replacement
|
||||
# (None if no unknown word replacement, empty if no path to align dictionary)
|
||||
align_dict = utils.load_align_dict(args.replace_unk)
|
||||
|
||||
# Load dataset (possibly sharded)
|
||||
itr = task.get_batch_iterator(
|
||||
dataset=task.dataset(args.gen_subset),
|
||||
max_tokens=args.max_tokens,
|
||||
max_positions=utils.resolve_max_positions(
|
||||
task.max_positions(),
|
||||
),
|
||||
ignore_invalid_inputs=args.skip_invalid_size_inputs_valid_test,
|
||||
required_batch_size_multiple=args.required_batch_size_multiple,
|
||||
num_shards=args.num_shards,
|
||||
shard_id=args.shard_id,
|
||||
num_workers=args.num_workers,
|
||||
).next_epoch_itr(shuffle=False)
|
||||
|
||||
num_sentences = 0
|
||||
source_sentences = []
|
||||
shard_id = 0
|
||||
all_avg_pool = None
|
||||
encoder_has_langtok = (
|
||||
hasattr(task.args, "encoder_langtok")
|
||||
and task.args.encoder_langtok is not None
|
||||
and hasattr(task.args, "lang_tok_replacing_bos_eos")
|
||||
and not task.args.lang_tok_replacing_bos_eos
|
||||
)
|
||||
with progress_bar.build_progress_bar(args, itr) as t:
|
||||
for sample in t:
|
||||
if sample is None:
|
||||
print("Skipping None")
|
||||
continue
|
||||
sample = utils.move_to_cuda(sample) if use_cuda else sample
|
||||
if "net_input" not in sample:
|
||||
continue
|
||||
|
||||
prefix_tokens = None
|
||||
if args.prefix_size > 0:
|
||||
prefix_tokens = sample["target"][:, : args.prefix_size]
|
||||
|
||||
with torch.no_grad():
|
||||
avg_pool = get_avg_pool(
|
||||
models,
|
||||
sample,
|
||||
prefix_tokens,
|
||||
src_dict,
|
||||
args.post_process,
|
||||
has_langtok=encoder_has_langtok,
|
||||
)
|
||||
if all_avg_pool is not None:
|
||||
all_avg_pool = np.concatenate((all_avg_pool, avg_pool))
|
||||
else:
|
||||
all_avg_pool = avg_pool
|
||||
|
||||
if not isinstance(sample["id"], list):
|
||||
sample_ids = sample["id"].tolist()
|
||||
else:
|
||||
sample_ids = sample["id"]
|
||||
for i, sample_id in enumerate(sample_ids):
|
||||
# Remove padding
|
||||
src_tokens = utils.strip_pad(
|
||||
sample["net_input"]["src_tokens"][i, :], tgt_dict.pad()
|
||||
)
|
||||
|
||||
# Either retrieve the original sentences or regenerate them from tokens.
|
||||
if align_dict is not None:
|
||||
src_str = task.dataset(args.gen_subset).src.get_original_text(
|
||||
sample_id
|
||||
)
|
||||
else:
|
||||
if src_dict is not None:
|
||||
src_str = src_dict.string(src_tokens, args.post_process)
|
||||
else:
|
||||
src_str = ""
|
||||
|
||||
if not args.quiet:
|
||||
if src_dict is not None:
|
||||
print("S-{}\t{}".format(sample_id, src_str))
|
||||
|
||||
source_sentences.append(f"{sample_id}\t{src_str}")
|
||||
|
||||
num_sentences += sample["nsentences"]
|
||||
if all_avg_pool.shape[0] >= 1000000:
|
||||
with open(
|
||||
f"{args.encoder_save_dir}/all_avg_pool.{args.source_lang}.{shard_id}",
|
||||
"w",
|
||||
) as avg_pool_file:
|
||||
all_avg_pool.tofile(avg_pool_file)
|
||||
with open(
|
||||
f"{args.encoder_save_dir}/sentences.{args.source_lang}.{shard_id}",
|
||||
"w",
|
||||
) as sentence_file:
|
||||
sentence_file.writelines(f"{line}\n" for line in source_sentences)
|
||||
all_avg_pool = None
|
||||
source_sentences = []
|
||||
shard_id += 1
|
||||
|
||||
if all_avg_pool is not None:
|
||||
with open(
|
||||
f"{args.encoder_save_dir}/all_avg_pool.{args.source_lang}.{shard_id}", "w"
|
||||
) as avg_pool_file:
|
||||
all_avg_pool.tofile(avg_pool_file)
|
||||
with open(
|
||||
f"{args.encoder_save_dir}/sentences.{args.source_lang}.{shard_id}", "w"
|
||||
) as sentence_file:
|
||||
sentence_file.writelines(f"{line}\n" for line in source_sentences)
|
||||
return None
|
||||
|
||||
|
||||
def cli_main():
|
||||
parser = options.get_generation_parser()
|
||||
parser.add_argument(
|
||||
"--encoder-save-dir",
|
||||
default="",
|
||||
type=str,
|
||||
metavar="N",
|
||||
help="directory to save encoder outputs",
|
||||
)
|
||||
args = options.parse_args_and_arch(parser)
|
||||
main(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli_main()
|
||||
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3 -u
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
import argparse
|
||||
import glob
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
DIM = 1024
|
||||
|
||||
|
||||
def compute_dist(source_embs, target_embs, k=5, return_sim_mat=False):
|
||||
target_ids = [tid for tid in target_embs]
|
||||
source_mat = np.stack(source_embs.values(), axis=0)
|
||||
normalized_source_mat = source_mat / np.linalg.norm(
|
||||
source_mat, axis=1, keepdims=True
|
||||
)
|
||||
target_mat = np.stack(target_embs.values(), axis=0)
|
||||
normalized_target_mat = target_mat / np.linalg.norm(
|
||||
target_mat, axis=1, keepdims=True
|
||||
)
|
||||
sim_mat = normalized_source_mat.dot(normalized_target_mat.T)
|
||||
if return_sim_mat:
|
||||
return sim_mat
|
||||
neighbors_map = {}
|
||||
for i, sentence_id in enumerate(source_embs):
|
||||
idx = np.argsort(sim_mat[i, :])[::-1][:k]
|
||||
neighbors_map[sentence_id] = [target_ids[tid] for tid in idx]
|
||||
return neighbors_map
|
||||
|
||||
|
||||
def load_embeddings(directory, LANGS):
|
||||
sentence_embeddings = {}
|
||||
sentence_texts = {}
|
||||
for lang in LANGS:
|
||||
sentence_embeddings[lang] = {}
|
||||
sentence_texts[lang] = {}
|
||||
lang_dir = f"{directory}/{lang}"
|
||||
embedding_files = glob.glob(f"{lang_dir}/all_avg_pool.{lang}.*")
|
||||
for embed_file in embedding_files:
|
||||
shard_id = embed_file.split(".")[-1]
|
||||
embeddings = np.fromfile(embed_file, dtype=np.float32)
|
||||
num_rows = embeddings.shape[0] // DIM
|
||||
embeddings = embeddings.reshape((num_rows, DIM))
|
||||
|
||||
with open(f"{lang_dir}/sentences.{lang}.{shard_id}") as sentence_file:
|
||||
for idx, line in enumerate(sentence_file):
|
||||
sentence_id, sentence = line.strip().split("\t")
|
||||
sentence_texts[lang][sentence_id] = sentence
|
||||
sentence_embeddings[lang][sentence_id] = embeddings[idx, :]
|
||||
|
||||
return sentence_embeddings, sentence_texts
|
||||
|
||||
|
||||
def compute_accuracy(directory, LANGS):
|
||||
sentence_embeddings, sentence_texts = load_embeddings(directory, LANGS)
|
||||
|
||||
top_1_accuracy = {}
|
||||
|
||||
top1_str = " ".join(LANGS) + "\n"
|
||||
for source_lang in LANGS:
|
||||
top_1_accuracy[source_lang] = {}
|
||||
top1_str += f"{source_lang} "
|
||||
for target_lang in LANGS:
|
||||
top1 = 0
|
||||
top5 = 0
|
||||
neighbors_map = compute_dist(
|
||||
sentence_embeddings[source_lang], sentence_embeddings[target_lang]
|
||||
)
|
||||
for sentence_id, neighbors in neighbors_map.items():
|
||||
if sentence_id == neighbors[0]:
|
||||
top1 += 1
|
||||
if sentence_id in neighbors[:5]:
|
||||
top5 += 1
|
||||
n = len(sentence_embeddings[target_lang])
|
||||
top1_str += f"{top1/n} "
|
||||
top1_str += "\n"
|
||||
|
||||
print(top1_str)
|
||||
print(top1_str, file=open(f"{directory}/accuracy", "w"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Analyze encoder outputs")
|
||||
parser.add_argument("directory", help="Source language corpus")
|
||||
parser.add_argument("--langs", help="List of langs")
|
||||
args = parser.parse_args()
|
||||
langs = args.langs.split(",")
|
||||
compute_accuracy(args.directory, langs)
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
source_lang=kk_KZ
|
||||
target_lang=en_XX
|
||||
MODEL=criss_checkpoints/criss.3rd.pt
|
||||
SPM=criss_checkpoints/sentence.bpe.model
|
||||
SPLIT=test
|
||||
LANG_DICT=criss_checkpoints/lang_dict.txt
|
||||
ENCODER_ANALYSIS=sentence_retrieval/encoder_analysis.py
|
||||
SAVE_ENCODER=save_encoder.py
|
||||
ENCODER_SAVE_ROOT=sentence_embeddings/$MODEL
|
||||
|
||||
|
||||
|
||||
DATA_DIR=data_tmp
|
||||
INPUT_DIR=$DATA_DIR/${source_lang}-${target_lang}-tatoeba
|
||||
ENCODER_SAVE_DIR=${ENCODER_SAVE_ROOT}/${source_lang}-${target_lang}
|
||||
mkdir -p $ENCODER_SAVE_DIR/${target_lang}
|
||||
mkdir -p $ENCODER_SAVE_DIR/${source_lang}
|
||||
|
||||
# Save encoder outputs for source sentences
|
||||
python $SAVE_ENCODER \
|
||||
${INPUT_DIR} \
|
||||
--path ${MODEL} \
|
||||
--task translation_multi_simple_epoch \
|
||||
--lang-dict ${LANG_DICT} \
|
||||
--gen-subset ${SPLIT} \
|
||||
--bpe 'sentencepiece' \
|
||||
--lang-pairs ${source_lang}-${target_lang} \
|
||||
-s ${source_lang} -t ${target_lang} \
|
||||
--sentencepiece-model ${SPM} \
|
||||
--remove-bpe 'sentencepiece' \
|
||||
--beam 1 \
|
||||
--lang-tok-style mbart \
|
||||
--encoder-save-dir ${ENCODER_SAVE_DIR}/${source_lang}
|
||||
|
||||
# Save encoder outputs for target sentences
|
||||
python $SAVE_ENCODER \
|
||||
${INPUT_DIR} \
|
||||
--path ${MODEL} \
|
||||
--lang-dict ${LANG_DICT} \
|
||||
--task translation_multi_simple_epoch \
|
||||
--gen-subset ${SPLIT} \
|
||||
--bpe 'sentencepiece' \
|
||||
--lang-pairs ${target_lang}-${source_lang} \
|
||||
-t ${source_lang} -s ${target_lang} \
|
||||
--sentencepiece-model ${SPM} \
|
||||
--remove-bpe 'sentencepiece' \
|
||||
--beam 1 \
|
||||
--lang-tok-style mbart \
|
||||
--encoder-save-dir ${ENCODER_SAVE_DIR}/${target_lang}
|
||||
|
||||
# Analyze sentence retrieval accuracy
|
||||
python $ENCODER_ANALYSIS --langs "${source_lang},${target_lang}" ${ENCODER_SAVE_DIR}
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
SRC=si_LK
|
||||
TGT=en_XX
|
||||
MODEL=criss_checkpoints/criss.3rd.pt
|
||||
|
||||
MULTIBLEU=mosesdecoder/scripts/generic/multi-bleu.perl
|
||||
MOSES=mosesdecoder
|
||||
REPLACE_UNICODE_PUNCT=$MOSES/scripts/tokenizer/replace-unicode-punctuation.perl
|
||||
NORM_PUNC=$MOSES/scripts/tokenizer/normalize-punctuation.perl
|
||||
REM_NON_PRINT_CHAR=$MOSES/scripts/tokenizer/remove-non-printing-char.perl
|
||||
TOKENIZER=$MOSES/scripts/tokenizer/tokenizer.perl
|
||||
GEN_TMP_DIR=gen_tmp
|
||||
LANG_DICT=criss_checkpoints/lang_dict.txt
|
||||
|
||||
if [ ! -d "mosesdecoder" ]; then
|
||||
git clone https://github.com/moses-smt/mosesdecoder
|
||||
fi
|
||||
mkdir -p $GEN_TMP_DIR
|
||||
fairseq-generate data_tmp/${SRC}-${TGT}-flores \
|
||||
--task translation_multi_simple_epoch \
|
||||
--max-tokens 2000 \
|
||||
--path ${MODEL} \
|
||||
--skip-invalid-size-inputs-valid-test \
|
||||
--beam 5 --lenpen 1.0 --gen-subset test \
|
||||
--remove-bpe=sentencepiece \
|
||||
--source-lang ${SRC} --target-lang ${TGT} \
|
||||
--decoder-langtok --lang-pairs 'en_XX-ar_AR,en_XX-de_DE,en_XX-es_XX,en_XX-fr_XX,en_XX-hi_IN,en_XX-it_IT,en_XX-ja_XX,en_XX-ko_KR,en_XX-nl_XX,en_XX-ru_RU,en_XX-zh_CN,en_XX-tr_TR,en_XX-vi_VN,en_XX-ro_RO,en_XX-my_MM,en_XX-ne_NP,en_XX-si_LK,en_XX-cs_CZ,en_XX-lt_LT,en_XX-kk_KZ,en_XX-gu_IN,en_XX-fi_FI,en_XX-et_EE,en_XX-lv_LV,ar_AR-en_XX,cs_CZ-en_XX,de_DE-en_XX,es_XX-en_XX,et_EE-en_XX,fi_FI-en_XX,fr_XX-en_XX,gu_IN-en_XX,hi_IN-en_XX,it_IT-en_XX,ja_XX-en_XX,kk_KZ-en_XX,ko_KR-en_XX,lt_LT-en_XX,lv_LV-en_XX,my_MM-en_XX,ne_NP-en_XX,nl_XX-en_XX,ro_RO-en_XX,ru_RU-en_XX,si_LK-en_XX,tr_TR-en_XX,vi_VN-en_XX,zh_CN-en_XX,ar_AR-es_XX,es_XX-ar_AR,ar_AR-hi_IN,hi_IN-ar_AR,ar_AR-zh_CN,zh_CN-ar_AR,cs_CZ-es_XX,es_XX-cs_CZ,cs_CZ-hi_IN,hi_IN-cs_CZ,cs_CZ-zh_CN,zh_CN-cs_CZ,de_DE-es_XX,es_XX-de_DE,de_DE-hi_IN,hi_IN-de_DE,de_DE-zh_CN,zh_CN-de_DE,es_XX-hi_IN,hi_IN-es_XX,es_XX-zh_CN,zh_CN-es_XX,et_EE-es_XX,es_XX-et_EE,et_EE-hi_IN,hi_IN-et_EE,et_EE-zh_CN,zh_CN-et_EE,fi_FI-es_XX,es_XX-fi_FI,fi_FI-hi_IN,hi_IN-fi_FI,fi_FI-zh_CN,zh_CN-fi_FI,fr_XX-es_XX,es_XX-fr_XX,fr_XX-hi_IN,hi_IN-fr_XX,fr_XX-zh_CN,zh_CN-fr_XX,gu_IN-es_XX,es_XX-gu_IN,gu_IN-hi_IN,hi_IN-gu_IN,gu_IN-zh_CN,zh_CN-gu_IN,hi_IN-zh_CN,zh_CN-hi_IN,it_IT-es_XX,es_XX-it_IT,it_IT-hi_IN,hi_IN-it_IT,it_IT-zh_CN,zh_CN-it_IT,ja_XX-es_XX,es_XX-ja_XX,ja_XX-hi_IN,hi_IN-ja_XX,ja_XX-zh_CN,zh_CN-ja_XX,kk_KZ-es_XX,es_XX-kk_KZ,kk_KZ-hi_IN,hi_IN-kk_KZ,kk_KZ-zh_CN,zh_CN-kk_KZ,ko_KR-es_XX,es_XX-ko_KR,ko_KR-hi_IN,hi_IN-ko_KR,ko_KR-zh_CN,zh_CN-ko_KR,lt_LT-es_XX,es_XX-lt_LT,lt_LT-hi_IN,hi_IN-lt_LT,lt_LT-zh_CN,zh_CN-lt_LT,lv_LV-es_XX,es_XX-lv_LV,lv_LV-hi_IN,hi_IN-lv_LV,lv_LV-zh_CN,zh_CN-lv_LV,my_MM-es_XX,es_XX-my_MM,my_MM-hi_IN,hi_IN-my_MM,my_MM-zh_CN,zh_CN-my_MM,ne_NP-es_XX,es_XX-ne_NP,ne_NP-hi_IN,hi_IN-ne_NP,ne_NP-zh_CN,zh_CN-ne_NP,nl_XX-es_XX,es_XX-nl_XX,nl_XX-hi_IN,hi_IN-nl_XX,nl_XX-zh_CN,zh_CN-nl_XX,ro_RO-es_XX,es_XX-ro_RO,ro_RO-hi_IN,hi_IN-ro_RO,ro_RO-zh_CN,zh_CN-ro_RO,ru_RU-es_XX,es_XX-ru_RU,ru_RU-hi_IN,hi_IN-ru_RU,ru_RU-zh_CN,zh_CN-ru_RU,si_LK-es_XX,es_XX-si_LK,si_LK-hi_IN,hi_IN-si_LK,si_LK-zh_CN,zh_CN-si_LK,tr_TR-es_XX,es_XX-tr_TR,tr_TR-hi_IN,hi_IN-tr_TR,tr_TR-zh_CN,zh_CN-tr_TR,vi_VN-es_XX,es_XX-vi_VN,vi_VN-hi_IN,hi_IN-vi_VN,vi_VN-zh_CN,zh_CN-vi_VN' \
|
||||
--lang-dict ${LANG_DICT} --lang-tok-style 'mbart' --sampling-method 'temperature' --sampling-temperature '1.0' > $GEN_TMP_DIR/${SRC}_${TGT}.gen
|
||||
cat $GEN_TMP_DIR/${SRC}_${TGT}.gen | grep -P "^T-" | cut -f2 | $REPLACE_UNICODE_PUNCT | $NORM_PUNC -l ${TGT:0:2} | $REM_NON_PRINT_CHAR | $TOKENIZER -no-escape ${TGT:0:2} > $GEN_TMP_DIR/${SRC}_${TGT}.hyp
|
||||
cat $GEN_TMP_DIR/${SRC}_${TGT}.gen | grep -P "^H-" | cut -f3 | $REPLACE_UNICODE_PUNCT | $NORM_PUNC -l ${TGT:0:2} | $REM_NON_PRINT_CHAR | $TOKENIZER -no-escape ${TGT:0:2} > $GEN_TMP_DIR/${SRC}_${TGT}.ref
|
||||
${MULTIBLEU} $GEN_TMP_DIR/${SRC}_${TGT}.ref < $GEN_TMP_DIR/${SRC}_${TGT}.hyp
|
||||
@@ -0,0 +1,77 @@
|
||||
# Cross-Lingual Language Model Pre-training
|
||||
|
||||
Below are some details for training Cross-Lingual Language Models (XLM) - similar to the ones presented in [Lample & Conneau, 2019](https://arxiv.org/pdf/1901.07291.pdf) - in Fairseq. The current implementation only supports the Masked Language Model (MLM) from the paper above.
|
||||
|
||||
## Downloading and Tokenizing Monolingual Data
|
||||
|
||||
Pointers to the monolingual data from wikipedia, used for training the XLM-style MLM model as well as details on processing (tokenization and BPE) it can be found in the [XLM Github Repository](https://github.com/facebookresearch/XLM#download--preprocess-monolingual-data).
|
||||
|
||||
Let's assume the following for the code snippets in later sections to work
|
||||
- Processed data is in the folder: monolingual_data/processed
|
||||
- Each language has 3 files for train, test and validation. For example we have the following files for English:
|
||||
train.en, valid.en
|
||||
- We are training a model for 5 languages: Arabic (ar), German (de), English (en), Hindi (hi) and French (fr)
|
||||
- The vocabulary file is monolingual_data/processed/vocab_mlm
|
||||
|
||||
|
||||
## Fairseq Pre-processing and Binarization
|
||||
|
||||
Pre-process and binarize the data with the MaskedLMDictionary and cross_lingual_lm task
|
||||
|
||||
```bash
|
||||
# Ensure the output directory exists
|
||||
DATA_DIR=monolingual_data/fairseq_processed
|
||||
mkdir -p "$DATA_DIR"
|
||||
|
||||
for lg in ar de en hi fr
|
||||
do
|
||||
|
||||
fairseq-preprocess \
|
||||
--task cross_lingual_lm \
|
||||
--srcdict monolingual_data/processed/vocab_mlm \
|
||||
--only-source \
|
||||
--trainpref monolingual_data/processed/train \
|
||||
--validpref monolingual_data/processed/valid \
|
||||
--testpref monolingual_data/processed/test \
|
||||
--destdir monolingual_data/fairseq_processed \
|
||||
--workers 20 \
|
||||
--source-lang $lg
|
||||
|
||||
# Since we only have a source language, the output file has a None for the
|
||||
# target language. Remove this
|
||||
|
||||
for stage in train test valid
|
||||
|
||||
sudo mv "$DATA_DIR/$stage.$lg-None.$lg.bin" "$stage.$lg.bin"
|
||||
sudo mv "$DATA_DIR/$stage.$lg-None.$lg.idx" "$stage.$lg.idx"
|
||||
|
||||
done
|
||||
|
||||
done
|
||||
```
|
||||
|
||||
## Train a Cross-lingual Language Model similar to the XLM MLM model
|
||||
|
||||
Use the following command to train the model on 5 languages.
|
||||
|
||||
```
|
||||
fairseq-train \
|
||||
--task cross_lingual_lm monolingual_data/fairseq_processed \
|
||||
--save-dir checkpoints/mlm \
|
||||
--max-update 2400000 --save-interval 1 --no-epoch-checkpoints \
|
||||
--arch xlm_base \
|
||||
--optimizer adam --lr-scheduler reduce_lr_on_plateau \
|
||||
--lr-shrink 0.5 --lr 0.0001 --stop-min-lr 1e-09 \
|
||||
--dropout 0.1 \
|
||||
--criterion legacy_masked_lm_loss \
|
||||
--max-tokens 2048 --tokens-per-sample 256 --attention-dropout 0.1 \
|
||||
--dataset-impl lazy --seed 0 \
|
||||
--masked-lm-only \
|
||||
--monolingual-langs 'ar,de,en,hi,fr' --num-segment 5 \
|
||||
--ddp-backend=no_c10d
|
||||
```
|
||||
|
||||
Some Notes:
|
||||
- Using tokens_per_sample greater than 256 can cause OOM (out-of-memory) issues. Usually since MLM packs in streams of text, this parameter doesn't need much tuning.
|
||||
- The Evaluation workflow for computing MLM Perplexity on test data is in progress.
|
||||
- Finetuning this model on a downstream task is something which is not currently available.
|
||||
@@ -0,0 +1,345 @@
|
||||
# Language Models not just for Pre-training: Fast Online Neural Noisy Channel Modeling
|
||||
|
||||
## Introduction
|
||||
- [Yee et al. (2019)](https://www.aclweb.org/anthology/D19-1571.pdf) introduce a simple and effective noisy channel modeling approach for neural machine translation. However, the noisy channel online decoding approach introduced in this paper is too slow to be practical.
|
||||
- To address this, [Bhosale et al. (2020)](http://www.statmt.org/wmt20/pdf/2020.wmt-1.68.pdf) introduces 3 simple approximations to make this approach very fast and practical without much loss in accuracy.
|
||||
- This README provides intructions on how to run online decoding or generation with the noisy channel modeling approach, including ways to make it very fast without much loss in accuracy.
|
||||
|
||||
## Noisy Channel Modeling
|
||||
|
||||
[Yee et al. (2019)](https://www.aclweb.org/anthology/D19-1571.pdf) applies the Bayes Rule to predict `P(y|x)`, the probability of the target `y` given the source `x`.
|
||||
```P(y|x) = P(x|y) * P(y) / P(x)```
|
||||
- `P(x|y)` predicts the source `x` given the target `y` and is referred to as the **channel model**
|
||||
- `P(y)` is a **language model** over the target `y`
|
||||
- `P(x)` is generally not modeled since it is constant for all `y`.
|
||||
|
||||
We use Transformer models to parameterize the direct model `P(y|x)`, the channel model `P(x|y)` and the language model `P(y)`.
|
||||
|
||||
During online decoding with beam search, we generate the top `K2` candidates per beam and score them with the following linear combination of the channel model, the language model as well as the direct model scores.
|
||||
|
||||
```(1 / t) * log(P(y|x) + (1 / s) * ( λ1 * log(P(x|y)) + λ2 * log(P(y) ) )```
|
||||
- `t` - Target Prefix Length
|
||||
- `s` - Source Length
|
||||
- `λ1` - Channel Model Weight
|
||||
- `λ2` - Language Model Weight
|
||||
|
||||
The top `beam_size` candidates based on the above combined scores are chosen to continue the beams in beam search. In beam search with a direct model alone, the scores from the direct model `P(y|x)` are used to choose the top candidates in beam search.
|
||||
|
||||
This framework provides a great way to utlize strong target language models trained on large amounts of unlabeled data. Language models can prefer targets unrelated to the source, so we also need a channel model whose role is to ensure that the target preferred by the language model also translates back to the source.
|
||||
|
||||
### Training Translation Models and Language Models
|
||||
|
||||
For training Transformer models in fairseq for machine translation, refer to instructions [here](https://github.com/pytorch/fairseq/tree/master/examples/translation)
|
||||
|
||||
For training Transformer models in fairseq for language modeling, refer to instructions [here](https://github.com/pytorch/fairseq/tree/master/examples/language_model)
|
||||
|
||||
### Generation with Language Model for German-English translation with fairseq
|
||||
|
||||
Here are instructions to generate using a direct model and a target-side language model.
|
||||
|
||||
Note:
|
||||
- Download and install fairseq as per instructions [here](https://github.com/pytorch/fairseq)
|
||||
- Preprocess and binarize the dataset as per instructions in section [Test Data Preprocessing](#test-data-preprocessing)
|
||||
|
||||
```sh
|
||||
binarized_data=data_dir/binarized
|
||||
direct_model=de_en_seed4.pt
|
||||
lm_model=en_lm.pt
|
||||
lm_data=lm_data
|
||||
wget https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/direct_models/seed4.pt -O ${direct_model}
|
||||
wget https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/lm_model/transformer_lm.pt -O ${lm_model}
|
||||
mkdir -p ${lm_data}
|
||||
wget https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/lm_model/lm_dict/dict.txt -O ${lm_data}/dict.txt
|
||||
|
||||
k2=10
|
||||
lenpen=0.16
|
||||
lm_wt=0.14
|
||||
fairseq-generate ${binarized_data} \
|
||||
--user-dir examples/fast_noisy_channel \
|
||||
--beam 5 \
|
||||
--path ${direct_model} \
|
||||
--lm-model ${lm_model} \
|
||||
--lm-data ${lm_data} \
|
||||
--k2 ${k2} \
|
||||
--combine-method lm_only \
|
||||
--task noisy_channel_translation \
|
||||
--lenpen ${lenpen} \
|
||||
--lm-wt ${lm_wt} \
|
||||
--gen-subset valid \
|
||||
--remove-bpe \
|
||||
--fp16 \
|
||||
--batch-size 10
|
||||
```
|
||||
### Noisy Channel Generation for German-English translation with fairseq
|
||||
|
||||
Here are instructions for noisy channel generation with a direct model, channel model and language model as explained in section [Noisy Channel Modeling](#noisy-channel-modeling).
|
||||
|
||||
Note:
|
||||
- Download and install fairseq as per instructions [here](https://github.com/pytorch/fairseq)
|
||||
- Preprocess and binarize the dataset as per instructions in section [Test Data Preprocessing](#test-data-preprocessing)
|
||||
|
||||
```sh
|
||||
binarized_data=data_dir/binarized
|
||||
direct_model=de_en_seed4.pt
|
||||
lm_model=en_lm.pt
|
||||
lm_data=lm_data
|
||||
ch_model=en_de.big.seed4.pt
|
||||
wget https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/direct_models/seed4.pt -O ${direct_model}
|
||||
wget https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/lm_model/transformer_lm.pt -O ${lm_model}
|
||||
mkdir -p ${lm_data}
|
||||
wget https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/lm_model/lm_dict/dict.txt -O ${lm_data}/dict.txt
|
||||
wget https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/big.seed4.pt -O ${ch_model}
|
||||
|
||||
k2=10
|
||||
lenpen=0.21
|
||||
lm_wt=0.50
|
||||
bw_wt=0.30
|
||||
fairseq-generate ${binarized_data} \
|
||||
--user-dir examples/fast_noisy_channel \
|
||||
--beam 5 \
|
||||
--path ${direct_model} \
|
||||
--lm-model ${lm_model} \
|
||||
--lm-data ${lm_data} \
|
||||
--channel-model ${ch_model} \
|
||||
--k2 ${k2} \
|
||||
--combine-method noisy_channel \
|
||||
--task noisy_channel_translation \
|
||||
--lenpen ${lenpen} \
|
||||
--lm-wt ${lm_wt} \
|
||||
--ch-wt ${bw_wt} \
|
||||
--gen-subset test \
|
||||
--remove-bpe \
|
||||
--fp16 \
|
||||
--batch-size 1
|
||||
```
|
||||
## Fast Noisy Channel Modeling
|
||||
|
||||
[Bhosale et al. (2020)](http://www.statmt.org/wmt20/pdf/2020.wmt-1.68.pdf) introduces 3 approximations that speed up online noisy channel decoding -
|
||||
- Smaller channel models (`Tranformer Base` with 1 encoder and decoder layer each vs. `Transformer Big`)
|
||||
- This involves training a channel model that is possibly smaller and less accurate in terms of BLEU than a channel model of the same size as the direct model.
|
||||
- Since the role of the channel model is mainly to assign low scores to generations from the language model if they don't translate back to the source, we may not need the most accurate channel model for this purpose.
|
||||
- Smaller output vocabulary size for the channel model (~30,000 -> ~1000)
|
||||
- The channel model doesn't need to score the full output vocabulary, it just needs to score the source tokens, which are completely known.
|
||||
- This is specified using the arguments `--channel-scoring-type src_vocab --top-k-vocab 500`
|
||||
- This means that the output vocabulary for the channel model will be the source tokens for all examples in the batch and the top-K most frequent tokens in the vocabulary
|
||||
- This reduces the memory consumption needed to store channel model scores significantly
|
||||
- Smaller number of candidates (`k2`) scored per beam
|
||||
- This is specified by reducing the argument `--k2`
|
||||
|
||||
|
||||
### Fast Noisy Channel Generation for German-English translation with fairseq
|
||||
|
||||
Here are instructions for **fast** noisy channel generation with a direct model, channel model and language model as explained in section [Fast Noisy Channel Modeling](#fast-noisy-channel-modeling). The main differences are that we use a smaller channel model, reduce `--k2`, set `--channel-scoring-type src_vocab --top-k-vocab 500` and increase the `--batch-size`.
|
||||
|
||||
Note:
|
||||
- Download and install fairseq as per instructions [here](https://github.com/pytorch/fairseq)
|
||||
- Preprocess and binarize the dataset as per instructions in section [Test Data Preprocessing](#test-data-preprocessing)
|
||||
|
||||
```sh
|
||||
binarized_data=data_dir/binarized
|
||||
direct_model=de_en_seed4.pt
|
||||
lm_model=en_lm.pt
|
||||
lm_data=lm_data
|
||||
small_ch_model=en_de.base_1_1.seed4.pt
|
||||
wget https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/direct_models/seed4.pt -O ${direct_model}
|
||||
wget https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/lm_model/transformer_lm.pt -O ${lm_model}
|
||||
mkdir -p ${lm_data}
|
||||
wget https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/lm_model/lm_dict/dict.txt -O ${lm_data}/dict.txt
|
||||
wget https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/base_1_1.seed4.pt -O ${small_ch_model}
|
||||
|
||||
k2=3
|
||||
lenpen=0.23
|
||||
lm_wt=0.58
|
||||
bw_wt=0.26
|
||||
fairseq-generate ${binarized_data} \
|
||||
--user-dir examples/fast_noisy_channel \
|
||||
--beam 5 \
|
||||
--path ${direct_model} \
|
||||
--lm-model ${lm_model} \
|
||||
--lm-data ${lm_data} \
|
||||
--channel-model ${small_ch_model} \
|
||||
--k2 ${k2} \
|
||||
--combine-method noisy_channel \
|
||||
--task noisy_channel_translation \
|
||||
--lenpen ${lenpen} \
|
||||
--lm-wt ${lm_wt} \
|
||||
--ch-wt ${bw_wt} \
|
||||
--gen-subset test \
|
||||
--remove-bpe \
|
||||
--fp16 \
|
||||
--batch-size 50 \
|
||||
--channel-scoring-type src_vocab --top-k-vocab 500
|
||||
```
|
||||
|
||||
## Test Data Preprocessing
|
||||
|
||||
For preprocessing and binarizing the test sets for Romanian-English and German-English translation, we use the following script -
|
||||
|
||||
```sh
|
||||
FAIRSEQ=/path/to/fairseq
|
||||
cd $FAIRSEQ
|
||||
SCRIPTS=$FAIRSEQ/mosesdecoder/scripts
|
||||
if [ ! -d "${SCRIPTS}" ]; then
|
||||
echo 'Cloning Moses github repository (for tokenization scripts)...'
|
||||
git clone https://github.com/moses-smt/mosesdecoder.git
|
||||
fi
|
||||
TOKENIZER=$SCRIPTS/tokenizer/tokenizer.perl
|
||||
NORMALIZE=$SCRIPTS/tokenizer/normalize-punctuation.perl
|
||||
|
||||
s=de
|
||||
t=en
|
||||
test=wmt18
|
||||
|
||||
mkdir -p data_dir
|
||||
|
||||
# Tokenization
|
||||
if [ $s == "ro" ] ; then
|
||||
# Note: Get normalise-romanian.py and remove-diacritics.py from
|
||||
# https://github.com/rsennrich/wmt16-scripts/tree/master/preprocess
|
||||
sacrebleu -t $test -l $s-$t --echo src | \
|
||||
$NORMALIZE -l $s | \
|
||||
python normalise-romanian.py | \
|
||||
python remove-diacritics.py | \
|
||||
$TOKENIZER -l $s -a -q > data_dir/$test.$s-$t.$s
|
||||
else
|
||||
sacrebleu -t $test -l $s-$t --echo src | perl $NORMALIZE -l $s | perl $TOKENIZER -threads 8 -a -l $s > data_dir/$test.$s-$t.$s
|
||||
fi
|
||||
|
||||
sacrebleu -t $test -l $s-$t --echo ref | perl $NORMALIZE -l $t | perl $TOKENIZER -threads 8 -a -l $t > data_dir/$test.$s-$t.$t
|
||||
|
||||
|
||||
# Applying BPE
|
||||
src_bpe_code=/path/to/source/language/bpe/code
|
||||
tgt_bpe_code=/path/to/target/language/bpe/code
|
||||
src_dict=/path/to/source/language/dict
|
||||
tgt_dict=/path/to/target/language/dict
|
||||
|
||||
FASTBPE=$FAIRSEQ/fastBPE
|
||||
if [ ! -d "${FASTBPE}" ] ; then
|
||||
git clone https://github.com/glample/fastBPE.git
|
||||
# Follow compilation instructions at https://github.com/glample/fastBPE
|
||||
g++ -std=c++11 -pthread -O3 fastBPE/main.cc -IfastBPE -o fast
|
||||
fi
|
||||
|
||||
${FASTBPE}/fast applybpe data_dir/bpe.$test.$s-$t.$s data_dir/$test.$s-$t.$s ${src_bpe_code}
|
||||
${FASTBPE}/fast applybpe data_dir/bpe.$test.$s-$t.$s data_dir/$test.$s-$t.$s ${tgt_bpe_code}
|
||||
|
||||
fairseq-preprocess -s $s -t $t \
|
||||
--testpref data_dir/bpe.$test.$s-$t \
|
||||
--destdir data_dir/binarized \
|
||||
--srcdict ${src_dict} \
|
||||
--tgtdict ${tgt_dict}
|
||||
```
|
||||
|
||||
## Calculating BLEU
|
||||
|
||||
```sh
|
||||
DETOKENIZER=$SCRIPTS/tokenizer/detokenizer.perl
|
||||
cat ${generation_output} | grep -P "^H" | sort -V | cut -f 3- | $DETOKENIZER -l $t -q -a | sacrebleu -t $test -l $s-$t
|
||||
```
|
||||
|
||||
|
||||
## Romanian-English Translation
|
||||
|
||||
The direct and channel models are trained using bitext data (WMT16) combined with backtranslated data (The monolingual data used for backtranslation comes from http://data.statmt.org/rsennrich/wmt16_backtranslations/ (Sennrich et al., 2016c))
|
||||
|
||||
The backtranslated data is generated using an ensemble of 3 English-Romanian models trained on bitext training data (WMT16) with unrestricted sampling.
|
||||
|
||||
### BPE Codes and Dictionary
|
||||
|
||||
We learn a joint BPE vocabulary of 18K types on the bitext training data which is used for both the source and target.
|
||||
||Path|
|
||||
|----------|------|
|
||||
| BPE Code | [joint_bpe_18k](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/bpe_18k) |
|
||||
| Dictionary | [dict](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/dict) |
|
||||
|
||||
### Direct Models
|
||||
For Ro-En with backtranslation, the direct and channel models use a Transformer-Big architecture.
|
||||
|
||||
| Seed | Model |
|
||||
|----|----|
|
||||
| 2 | [ro_en_seed2.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/direct_models/seed2.pt)
|
||||
| 4 | [ro_en_seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/direct_models/seed4.pt)
|
||||
| 6 | [ro_en_seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/direct_models/seed6.pt)
|
||||
|
||||
### Channel Models
|
||||
For channel models, we follow the same steps as for the direct models. But backtranslated data is generated in the opposite direction using [this Romanian monolingual data](http://data.statmt.org/rsennrich/wmt16_backtranslations/).
|
||||
The best lenpen, LM weight and CH weight are obtained by sweeping over the validation set (wmt16/dev) using beam 5.
|
||||
| Model Size | Lenpen | LM Weight | CH Weight | Seed 2 | Seed 4 | Seed 6 |
|
||||
|----|----|----|----|----|----|----|
|
||||
| `big` | 0.84 | 0.64 | 0.56 | [big.seed2.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/channel_models/big.seed2.pt) | [big.seed2.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/channel_models/big.seed2.pt) | [big.seed2.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/channel_models/big.seed2.pt) |
|
||||
| `base_1_1` | 0.63 | 0.40 | 0.37 | [base_1_1.seed2.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/channel_models/base_1_1.seed2.pt) | [base_1_1.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/channel_models/base_1_1.seed4.pt) | [base_1_1.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/channel_models/base_1_1.seed6.pt) |
|
||||
|
||||
### Language Model
|
||||
The model is trained on de-duplicated English Newscrawl data from 2007-2018 comprising 186 million sentences or 4.5B words after normalization and tokenization.
|
||||
| | Path |
|
||||
|----|----|
|
||||
| `--lm-model` | [transformer_en_lm](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/lm_model/transformer_lm.pt) |
|
||||
| `--lm-data` | [lm_data](https://dl.fbaipublicfiles.com/fast_noisy_channel/ro_en/lm_model/lm_dict)
|
||||
|
||||
## German-English Translation
|
||||
|
||||
### BPE Codes and Dictionaries
|
||||
|
||||
| | Path|
|
||||
|----------|------|
|
||||
| Source BPE Code | [de_bpe_code_24K](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/de_bpe_code_24K) |
|
||||
| Target BPE Code | [en_bpe_code_24K](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/en_bpe_code_24K)
|
||||
| Source Dictionary | [de_dict](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/de_dict) |
|
||||
| Target Dictionary | [en_dict](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/en_dict) |
|
||||
|
||||
### Direct Models
|
||||
We train on WMT’19 training data. Following [Ng et al., 2019](http://statmt.org/wmt19/pdf/53/WMT33.pdf), we apply language identification filtering and remove sentences longer than 250 tokens as well as sentence pairs with a source/target length ratio exceeding 1.5. This results in 26.8M sentence pairs.
|
||||
We use the Transformer-Big architecture for the direct model.
|
||||
|
||||
| Seed | Model |
|
||||
|:----:|----|
|
||||
| 4 | [de_en_seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/direct_models/seed4.pt)
|
||||
| 5 | [de_en_seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/direct_models/seed5.pt)
|
||||
| 6 | [de_en_seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/direct_models/seed6.pt)
|
||||
|
||||
### Channel Models
|
||||
|
||||
We train on WMT’19 training data. Following [Ng et al., 2019](http://statmt.org/wmt19/pdf/53/WMT33.pdf), we apply language identification filtering and remove sentences longer than 250 tokens as well as sentence pairs with a source/target length ratio exceeding 1.5. This results in 26.8M sentence pairs.
|
||||
|
||||
| Model Size | Seed 4 | Seed 5 | Seed 6 |
|
||||
|----|----|----|----|
|
||||
| `big` | [big.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/big.seed4.pt) | [big.seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/big.seed5.pt) | [big.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/big.seed6.pt) |
|
||||
| `big_1_1` | [big_1_1.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/big_1_1.seed4.pt) | [big_1_1.seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/big_1_1.seed5.pt) | [big_1_1.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/big_1_1.seed6.pt) |
|
||||
| `base` | [base.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/base.seed4.pt) | [base.seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/base.seed5.pt) | [base.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/base.seed6.pt) |
|
||||
| `base_1_1` | [base_1_1.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/base_1_1.seed4.pt) | [base_1_1.seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/base_1_1.seed5.pt) | [base_1_1.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/base_1_1.seed6.pt) |
|
||||
| `half` | [half.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/half.seed4.pt) | [half.seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/half.seed5.pt) | [half.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/half.seed6.pt) |
|
||||
| `half_1_1` | [half_1_1.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/half_1_1.seed4.pt) | [half_1_1.seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/half_1_1.seed5.pt) | [half_1_1.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/half_1_1.seed6.pt) |
|
||||
| `quarter` | [quarter.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/quarter.seed4.pt) | [quarter.seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/quarter.seed5.pt) | [quarter.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/quarter.seed6.pt) |
|
||||
| `quarter_1_1` | [quarter_1_1.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/quarter_1_1.seed4.pt) | [quarter_1_1.seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/quarter_1_1.seed5.pt) | [quarter_1_1.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/quarter_1_1.seed6.pt) |
|
||||
| `8th` | [8th.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/8th.seed4.pt) | [8th.seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/8th.seed5.pt) | [8th.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/8th.seed6.pt) |
|
||||
| `8th_1_1` | [8th_1_1.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/8th_1_1.seed4.pt) | [8th_1_1.seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/8th_1_1.seed5.pt) | [8th_1_1.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/8th_1_1.seed6.pt) |
|
||||
| `16th` | [16th.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/16th.seed4.pt) | [16th.seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/16th.seed5.pt) | [16th.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/16th.seed6.pt) |
|
||||
| `16th_1_1` | [16th_1_1.seed4.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/16th_1_1.seed4.pt) | [16th_1_1.seed5.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/16th_1_1.seed5.pt) | [16th_1_1.seed6.pt](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/channel_models/16th_1_1.seed6.pt) |
|
||||
|
||||
### Language Model
|
||||
The model is trained on de-duplicated English Newscrawl data from 2007-2018 comprising 186 million sentences or 4.5B words after normalization and tokenization.
|
||||
| | Path |
|
||||
|----|----|
|
||||
| `--lm-model` | [transformer_en_lm](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/lm_model/transformer_lm.pt) |
|
||||
| `--lm-data` | [lm_data](https://dl.fbaipublicfiles.com/fast_noisy_channel/de_en/lm_model/lm_dict/)
|
||||
|
||||
|
||||
## Citation
|
||||
|
||||
```bibtex
|
||||
@inproceedings{bhosale2020language,
|
||||
title={Language Models not just for Pre-training: Fast Online Neural Noisy Channel Modeling},
|
||||
author={Shruti Bhosale and Kyra Yee and Sergey Edunov and Michael Auli},
|
||||
booktitle={Proceedings of the Fifth Conference on Machine Translation (WMT)},
|
||||
year={2020},
|
||||
}
|
||||
|
||||
@inproceedings{yee2019simple,
|
||||
title={Simple and Effective Noisy Channel Modeling for Neural Machine Translation},
|
||||
author={Yee, Kyra and Dauphin, Yann and Auli, Michael},
|
||||
booktitle={Proceedings of the 2019 Conference on Empirical Methods in Natural Language Processing and the 9th International Joint Conference on Natural Language Processing (EMNLP-IJCNLP)},
|
||||
pages={5700--5705},
|
||||
year={2019}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from . import noisy_channel_translation # noqa
|
||||
from . import noisy_channel_sequence_generator # noqa
|
||||
from . import noisy_channel_beam_search # noqa
|
||||
@@ -0,0 +1,71 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import torch
|
||||
from fairseq.search import Search
|
||||
|
||||
|
||||
class NoisyChannelBeamSearch(Search):
|
||||
|
||||
def __init__(self, tgt_dict):
|
||||
super().__init__(tgt_dict)
|
||||
self.fw_scores_buf = None
|
||||
self.lm_scores_buf = None
|
||||
|
||||
def _init_buffers(self, t):
|
||||
# super()._init_buffers(t)
|
||||
if self.fw_scores_buf is None:
|
||||
self.scores_buf = t.new()
|
||||
self.indices_buf = torch.LongTensor().to(device=t.device)
|
||||
self.beams_buf = torch.LongTensor().to(device=t.device)
|
||||
self.fw_scores_buf = t.new()
|
||||
self.lm_scores_buf = t.new()
|
||||
|
||||
def combine_fw_bw(self, combine_method, fw_cum, bw, step):
|
||||
if combine_method == "noisy_channel":
|
||||
fw_norm = fw_cum.div(step + 1)
|
||||
lprobs = bw + fw_norm
|
||||
elif combine_method == "lm_only":
|
||||
lprobs = bw + fw_cum
|
||||
|
||||
return lprobs
|
||||
|
||||
def step(self, step, fw_lprobs, scores, bw_lprobs, lm_lprobs, combine_method):
|
||||
self._init_buffers(fw_lprobs)
|
||||
bsz, beam_size, vocab_size = fw_lprobs.size()
|
||||
|
||||
if step == 0:
|
||||
# at the first step all hypotheses are equally likely, so use
|
||||
# only the first beam
|
||||
fw_lprobs = fw_lprobs[:, ::beam_size, :].contiguous()
|
||||
bw_lprobs = bw_lprobs[:, ::beam_size, :].contiguous()
|
||||
# nothing to add since we are at the first step
|
||||
fw_lprobs_cum = fw_lprobs
|
||||
|
||||
else:
|
||||
# make probs contain cumulative scores for each hypothesis
|
||||
raw_scores = (scores[:, :, step - 1].unsqueeze(-1))
|
||||
fw_lprobs_cum = (fw_lprobs.add(raw_scores))
|
||||
|
||||
combined_lprobs = self.combine_fw_bw(combine_method, fw_lprobs_cum, bw_lprobs, step)
|
||||
|
||||
# choose the top k according to the combined noisy channel model score
|
||||
torch.topk(
|
||||
combined_lprobs.view(bsz, -1),
|
||||
k=min(
|
||||
# Take the best 2 x beam_size predictions. We'll choose the first
|
||||
# beam_size of these which don't predict eos to continue with.
|
||||
beam_size * 2,
|
||||
combined_lprobs.view(bsz, -1).size(1) - 1, # -1 so we never select pad
|
||||
),
|
||||
out=(self.scores_buf, self.indices_buf),
|
||||
)
|
||||
# save corresponding fw and lm scores
|
||||
self.fw_scores_buf = torch.gather(fw_lprobs_cum.view(bsz, -1), 1, self.indices_buf)
|
||||
self.lm_scores_buf = torch.gather(lm_lprobs.view(bsz, -1), 1, self.indices_buf)
|
||||
# Project back into relative indices and beams
|
||||
self.beams_buf = self.indices_buf // vocab_size
|
||||
self.indices_buf.fmod_(vocab_size)
|
||||
return self.scores_buf, self.fw_scores_buf, self.lm_scores_buf, self.indices_buf, self.beams_buf
|
||||
@@ -0,0 +1,842 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch import Tensor
|
||||
|
||||
from .noisy_channel_beam_search import NoisyChannelBeamSearch
|
||||
from fairseq.sequence_generator import EnsembleModel
|
||||
|
||||
|
||||
class NoisyChannelSequenceGenerator(object):
|
||||
def __init__(
|
||||
self,
|
||||
combine_method,
|
||||
tgt_dict,
|
||||
src_dict=None,
|
||||
beam_size=1,
|
||||
max_len_a=0,
|
||||
max_len_b=200,
|
||||
min_len=1,
|
||||
len_penalty=1.0,
|
||||
unk_penalty=0.0,
|
||||
retain_dropout=False,
|
||||
temperature=1.0,
|
||||
match_source_len=False,
|
||||
no_repeat_ngram_size=0,
|
||||
normalize_scores=True,
|
||||
channel_models=None,
|
||||
k2=10,
|
||||
ch_weight=1.0,
|
||||
channel_scoring_type='log_norm',
|
||||
top_k_vocab=0,
|
||||
lm_models=None,
|
||||
lm_dict=None,
|
||||
lm_weight=1.0,
|
||||
normalize_lm_scores_by_tgt_len=False,
|
||||
):
|
||||
"""Generates translations of a given source sentence,
|
||||
using beam search with noisy channel decoding.
|
||||
|
||||
Args:
|
||||
combine_method (string, optional): Method to combine direct, LM and
|
||||
channel model scores (default: None)
|
||||
tgt_dict (~fairseq.data.Dictionary): target dictionary
|
||||
src_dict (~fairseq.data.Dictionary): source dictionary
|
||||
beam_size (int, optional): beam width (default: 1)
|
||||
max_len_a/b (int, optional): generate sequences of maximum length
|
||||
ax + b, where x is the source length
|
||||
min_len (int, optional): the minimum length of the generated output
|
||||
(not including end-of-sentence)
|
||||
len_penalty (float, optional): length penalty, where <1.0 favors
|
||||
shorter, >1.0 favors longer sentences (default: 1.0)
|
||||
unk_penalty (float, optional): unknown word penalty, where <0
|
||||
produces more unks, >0 produces fewer (default: 0.0)
|
||||
retain_dropout (bool, optional): use dropout when generating
|
||||
(default: False)
|
||||
temperature (float, optional): temperature, where values
|
||||
>1.0 produce more uniform samples and values <1.0 produce
|
||||
sharper samples (default: 1.0)
|
||||
match_source_len (bool, optional): outputs should match the source
|
||||
length (default: False)
|
||||
no_repeat_ngram_size (int, optional): Size of n-grams that we avoid
|
||||
repeating in the generation (default: 0)
|
||||
normalize_scores (bool, optional): normalize scores by the length
|
||||
of the output (default: True)
|
||||
channel_models (List[~fairseq.models.FairseqModel]): ensemble of models
|
||||
translating from the target to the source
|
||||
k2 (int, optional): Top K2 candidates to score per beam at each step (default:10)
|
||||
ch_weight (int, optional): Weight associated with the channel model score
|
||||
assuming that the direct model score has weight 1.0 (default: 1.0)
|
||||
channel_scoring_type (str, optional): String specifying how to score
|
||||
the channel model (default: 'log_norm')
|
||||
top_k_vocab (int, optional): If `channel_scoring_type` is `'src_vocab'` or
|
||||
`'src_vocab_batched'`, then this parameter specifies the number of
|
||||
most frequent tokens to include in the channel model output vocabulary,
|
||||
in addition to the source tokens in the input batch (default: 0)
|
||||
lm_models (List[~fairseq.models.FairseqModel]): ensemble of models
|
||||
generating text in the target language
|
||||
lm_dict (~fairseq.data.Dictionary): LM Model dictionary
|
||||
lm_weight (int, optional): Weight associated with the LM model score
|
||||
assuming that the direct model score has weight 1.0 (default: 1.0)
|
||||
normalize_lm_scores_by_tgt_len (bool, optional): Should we normalize LM scores
|
||||
by the target length? By default, we normalize the combination of
|
||||
LM and channel model scores by the source length
|
||||
"""
|
||||
self.pad = tgt_dict.pad()
|
||||
self.unk = tgt_dict.unk()
|
||||
self.eos = tgt_dict.eos()
|
||||
self.vocab_size = len(tgt_dict)
|
||||
self.beam_size = beam_size
|
||||
# the max beam size is the dictionary size - 1, since we never select pad
|
||||
self.beam_size = min(beam_size, self.vocab_size - 1)
|
||||
self.max_len_a = max_len_a
|
||||
self.max_len_b = max_len_b
|
||||
self.min_len = min_len
|
||||
self.normalize_scores = normalize_scores
|
||||
self.len_penalty = len_penalty
|
||||
self.unk_penalty = unk_penalty
|
||||
self.retain_dropout = retain_dropout
|
||||
self.temperature = temperature
|
||||
self.match_source_len = match_source_len
|
||||
self.no_repeat_ngram_size = no_repeat_ngram_size
|
||||
self.channel_models = channel_models
|
||||
self.src_dict = src_dict
|
||||
self.tgt_dict = tgt_dict
|
||||
self.combine_method = combine_method
|
||||
self.k2 = k2
|
||||
self.ch_weight = ch_weight
|
||||
self.channel_scoring_type = channel_scoring_type
|
||||
self.top_k_vocab = top_k_vocab
|
||||
self.lm_models = lm_models
|
||||
self.lm_dict = lm_dict
|
||||
self.lm_weight = lm_weight
|
||||
self.log_softmax_fn = torch.nn.LogSoftmax(dim=1)
|
||||
self.normalize_lm_scores_by_tgt_len = normalize_lm_scores_by_tgt_len
|
||||
|
||||
self.share_tgt_dict = (self.lm_dict == self.tgt_dict)
|
||||
self.tgt_to_lm = make_dict2dict(tgt_dict, lm_dict)
|
||||
|
||||
self.ch_scoring_bsz = 3072
|
||||
|
||||
assert temperature > 0, '--temperature must be greater than 0'
|
||||
|
||||
self.search = NoisyChannelBeamSearch(tgt_dict)
|
||||
|
||||
@torch.no_grad()
|
||||
def generate(
|
||||
self,
|
||||
models,
|
||||
sample,
|
||||
prefix_tokens=None,
|
||||
bos_token=None,
|
||||
**kwargs
|
||||
):
|
||||
"""Generate a batch of translations.
|
||||
Args:
|
||||
models (List[~fairseq.models.FairseqModel]): ensemble of models
|
||||
sample (dict): batch
|
||||
prefix_tokens (torch.LongTensor, optional): force decoder to begin
|
||||
with these tokens
|
||||
"""
|
||||
model = EnsembleModel(models)
|
||||
incremental_states = torch.jit.annotate(
|
||||
List[Dict[str, Dict[str, Optional[Tensor]]]],
|
||||
[
|
||||
torch.jit.annotate(Dict[str, Dict[str, Optional[Tensor]]], {})
|
||||
for i in range(model.models_size)
|
||||
],
|
||||
)
|
||||
if not self.retain_dropout:
|
||||
model.eval()
|
||||
|
||||
# model.forward normally channels prev_output_tokens into the decoder
|
||||
# separately, but SequenceGenerator directly calls model.encoder
|
||||
encoder_input = {
|
||||
k: v for k, v in sample['net_input'].items()
|
||||
if k != 'prev_output_tokens'
|
||||
}
|
||||
src_tokens = encoder_input['src_tokens']
|
||||
src_lengths_no_eos = (src_tokens.ne(self.eos) & src_tokens.ne(self.pad)).long().sum(dim=1)
|
||||
input_size = src_tokens.size()
|
||||
# batch dimension goes first followed by source lengths
|
||||
bsz = input_size[0]
|
||||
src_len = input_size[1]
|
||||
beam_size = self.beam_size
|
||||
|
||||
if self.match_source_len:
|
||||
max_len = src_lengths_no_eos.max().item()
|
||||
else:
|
||||
max_len = min(
|
||||
int(self.max_len_a * src_len + self.max_len_b),
|
||||
# exclude the EOS marker
|
||||
model.max_decoder_positions() - 1,
|
||||
)
|
||||
|
||||
# compute the encoder output for each beam
|
||||
encoder_outs = model.forward_encoder(encoder_input)
|
||||
new_order = torch.arange(bsz).view(-1, 1).repeat(1, beam_size).view(-1)
|
||||
new_order = new_order.to(src_tokens.device).long()
|
||||
encoder_outs = model.reorder_encoder_out(encoder_outs, new_order)
|
||||
|
||||
src_lengths = encoder_input['src_lengths']
|
||||
# initialize buffers
|
||||
scores = src_tokens.new(bsz * beam_size, max_len + 1).float().fill_(0)
|
||||
lm_prefix_scores = src_tokens.new(bsz * beam_size).float().fill_(0)
|
||||
|
||||
scores_buf = scores.clone()
|
||||
tokens = src_tokens.new(bsz * beam_size, max_len + 2).long().fill_(self.pad)
|
||||
tokens_buf = tokens.clone()
|
||||
tokens[:, 0] = self.eos if bos_token is None else bos_token
|
||||
|
||||
# reorder source tokens so they may be used as a reference in generating P(S|T)
|
||||
src_tokens = reorder_all_tokens(src_tokens, src_lengths, self.src_dict.eos_index)
|
||||
|
||||
src_tokens = src_tokens.repeat(1, beam_size).view(-1, src_len)
|
||||
src_lengths = src_lengths.view(bsz, -1).repeat(1, beam_size).view(bsz*beam_size, -1)
|
||||
|
||||
attn, attn_buf = None, None
|
||||
nonpad_idxs = None
|
||||
|
||||
# The cands_to_ignore indicates candidates that should be ignored.
|
||||
# For example, suppose we're sampling and have already finalized 2/5
|
||||
# samples. Then the cands_to_ignore would mark 2 positions as being ignored,
|
||||
# so that we only finalize the remaining 3 samples.
|
||||
cands_to_ignore = src_tokens.new_zeros(bsz, beam_size).eq(-1) # forward and backward-compatible False mask
|
||||
|
||||
# list of completed sentences
|
||||
finalized = [[] for i in range(bsz)]
|
||||
finished = [False for i in range(bsz)]
|
||||
num_remaining_sent = bsz
|
||||
|
||||
# number of candidate hypos per step
|
||||
cand_size = 2 * beam_size # 2 x beam size in case half are EOS
|
||||
|
||||
# offset arrays for converting between different indexing schemes
|
||||
bbsz_offsets = (torch.arange(0, bsz) * beam_size).unsqueeze(1).type_as(tokens)
|
||||
cand_offsets = torch.arange(0, cand_size).type_as(tokens)
|
||||
|
||||
# helper function for allocating buffers on the fly
|
||||
buffers = {}
|
||||
|
||||
def buffer(name, type_of=tokens): # noqa
|
||||
if name not in buffers:
|
||||
buffers[name] = type_of.new()
|
||||
return buffers[name]
|
||||
|
||||
def is_finished(sent, step, unfin_idx):
|
||||
"""
|
||||
Check whether we've finished generation for a given sentence, by
|
||||
comparing the worst score among finalized hypotheses to the best
|
||||
possible score among unfinalized hypotheses.
|
||||
"""
|
||||
assert len(finalized[sent]) <= beam_size
|
||||
if len(finalized[sent]) == beam_size:
|
||||
return True
|
||||
return False
|
||||
|
||||
def finalize_hypos(step, bbsz_idx, eos_scores, combined_noisy_channel_eos_scores):
|
||||
"""
|
||||
Finalize the given hypotheses at this step, while keeping the total
|
||||
number of finalized hypotheses per sentence <= beam_size.
|
||||
|
||||
Note: the input must be in the desired finalization order, so that
|
||||
hypotheses that appear earlier in the input are preferred to those
|
||||
that appear later.
|
||||
|
||||
Args:
|
||||
step: current time step
|
||||
bbsz_idx: A vector of indices in the range [0, bsz*beam_size),
|
||||
indicating which hypotheses to finalize
|
||||
eos_scores: A vector of the same size as bbsz_idx containing
|
||||
fw scores for each hypothesis
|
||||
combined_noisy_channel_eos_scores: A vector of the same size as bbsz_idx containing
|
||||
combined noisy channel scores for each hypothesis
|
||||
"""
|
||||
assert bbsz_idx.numel() == eos_scores.numel()
|
||||
|
||||
# clone relevant token and attention tensors
|
||||
tokens_clone = tokens.index_select(0, bbsz_idx)
|
||||
tokens_clone = tokens_clone[:, 1:step + 2] # skip the first index, which is EOS
|
||||
assert not tokens_clone.eq(self.eos).any()
|
||||
tokens_clone[:, step] = self.eos
|
||||
attn_clone = attn.index_select(0, bbsz_idx)[:, :, 1:step+2] if attn is not None else None
|
||||
|
||||
# compute scores per token position
|
||||
pos_scores = scores.index_select(0, bbsz_idx)[:, :step+1]
|
||||
pos_scores[:, step] = eos_scores
|
||||
# convert from cumulative to per-position scores
|
||||
pos_scores[:, 1:] = pos_scores[:, 1:] - pos_scores[:, :-1]
|
||||
|
||||
# normalize sentence-level scores
|
||||
if self.normalize_scores:
|
||||
combined_noisy_channel_eos_scores /= (step + 1) ** self.len_penalty
|
||||
|
||||
cum_unfin = []
|
||||
prev = 0
|
||||
for f in finished:
|
||||
if f:
|
||||
prev += 1
|
||||
else:
|
||||
cum_unfin.append(prev)
|
||||
|
||||
sents_seen = set()
|
||||
for i, (idx, score) in enumerate(zip(bbsz_idx.tolist(), combined_noisy_channel_eos_scores.tolist())):
|
||||
unfin_idx = idx // beam_size
|
||||
sent = unfin_idx + cum_unfin[unfin_idx]
|
||||
|
||||
sents_seen.add((sent, unfin_idx))
|
||||
|
||||
if self.match_source_len and step > src_lengths_no_eos[unfin_idx]:
|
||||
score = -math.inf
|
||||
|
||||
def get_hypo():
|
||||
|
||||
if attn_clone is not None:
|
||||
# remove padding tokens from attn scores
|
||||
hypo_attn = attn_clone[i][nonpad_idxs[sent]]
|
||||
_, alignment = hypo_attn.max(dim=0)
|
||||
else:
|
||||
hypo_attn = None
|
||||
alignment = None
|
||||
|
||||
return {
|
||||
'tokens': tokens_clone[i],
|
||||
'score': score,
|
||||
'attention': hypo_attn, # src_len x tgt_len
|
||||
'alignment': alignment,
|
||||
'positional_scores': pos_scores[i],
|
||||
}
|
||||
|
||||
if len(finalized[sent]) < beam_size:
|
||||
finalized[sent].append(get_hypo())
|
||||
|
||||
newly_finished = []
|
||||
for sent, unfin_idx in sents_seen:
|
||||
# check termination conditions for this sentence
|
||||
if not finished[sent] and is_finished(sent, step, unfin_idx):
|
||||
finished[sent] = True
|
||||
newly_finished.append(unfin_idx)
|
||||
return newly_finished
|
||||
|
||||
def noisy_channel_rescoring(lprobs, beam_size, bsz, src_tokens, tokens, k):
|
||||
"""Rescore the top k hypothesis from each beam using noisy channel modeling
|
||||
Returns:
|
||||
new_fw_lprobs: the direct model probabilities after pruning the top k
|
||||
new_ch_lm_lprobs: the combined channel and language model probabilities
|
||||
new_lm_lprobs: the language model probabilities after pruning the top k
|
||||
"""
|
||||
with torch.no_grad():
|
||||
lprobs_size = lprobs.size()
|
||||
if prefix_tokens is not None and step < prefix_tokens.size(1):
|
||||
probs_slice = lprobs.view(bsz, -1, lprobs.size(-1))[:, 0, :]
|
||||
cand_scores = torch.gather(
|
||||
probs_slice, dim=1,
|
||||
index=prefix_tokens[:, step].view(-1, 1).data
|
||||
).expand(-1, beam_size).contiguous().view(bsz*beam_size, 1)
|
||||
cand_indices = prefix_tokens[:, step].view(-1, 1).expand(bsz, beam_size).data.contiguous().view(bsz*beam_size, 1)
|
||||
|
||||
# need to calculate and save fw and lm probs for prefix tokens
|
||||
fw_top_k = cand_scores
|
||||
fw_top_k_idx = cand_indices
|
||||
k = 1
|
||||
else:
|
||||
# take the top k best words for every sentence in batch*beam
|
||||
fw_top_k, fw_top_k_idx = torch.topk(lprobs.view(beam_size*bsz, -1), k=k)
|
||||
eos_idx = torch.nonzero(fw_top_k_idx.view(bsz*beam_size*k, -1) == self.eos)[:, 0]
|
||||
ch_scores = fw_top_k.new_full((beam_size*bsz*k, ), 0)
|
||||
src_size = torch.sum(src_tokens[:, :] != self.src_dict.pad_index, dim=1, keepdim=True, dtype=fw_top_k.dtype)
|
||||
|
||||
if self.combine_method != "lm_only":
|
||||
temp_src_tokens_full = src_tokens[:, :].repeat(1, k).view(bsz*beam_size*k, -1)
|
||||
not_padding = temp_src_tokens_full[:, 1:] != self.src_dict.pad_index
|
||||
cur_tgt_size = step+2
|
||||
|
||||
# add eos to all candidate sentences except those that already end in eos
|
||||
eos_tokens = tokens[:, 0].repeat(1, k).view(-1, 1)
|
||||
eos_tokens[eos_idx] = self.tgt_dict.pad_index
|
||||
|
||||
if step == 0:
|
||||
channel_input = torch.cat((fw_top_k_idx.view(-1, 1), eos_tokens), 1)
|
||||
else:
|
||||
# move eos from beginning to end of target sentence
|
||||
channel_input = torch.cat((tokens[:, 1:step + 1].repeat(1, k).view(-1, step), fw_top_k_idx.view(-1, 1), eos_tokens), 1)
|
||||
|
||||
ch_input_lengths = torch.tensor(np.full(channel_input.size(0), cur_tgt_size))
|
||||
ch_input_lengths[eos_idx] = cur_tgt_size-1
|
||||
if self.channel_scoring_type == "unnormalized":
|
||||
ch_encoder_output = channel_model.encoder(channel_input, src_lengths=ch_input_lengths)
|
||||
ch_decoder_output, _ = channel_model.decoder(temp_src_tokens_full, encoder_out=ch_encoder_output, features_only=True)
|
||||
del ch_encoder_output
|
||||
ch_intermed_scores = channel_model.decoder.unnormalized_scores_given_target(ch_decoder_output, target_ids=temp_src_tokens_full[:, 1:])
|
||||
ch_intermed_scores = ch_intermed_scores.float()
|
||||
ch_intermed_scores *= not_padding.float()
|
||||
ch_scores = torch.sum(ch_intermed_scores, dim=1)
|
||||
elif self.channel_scoring_type == "k2_separate":
|
||||
for k_idx in range(k):
|
||||
k_eos_tokens = eos_tokens[k_idx::k, :]
|
||||
if step == 0:
|
||||
k_ch_input = torch.cat((fw_top_k_idx[:, k_idx:k_idx+1], k_eos_tokens), 1)
|
||||
else:
|
||||
# move eos from beginning to end of target sentence
|
||||
k_ch_input = torch.cat((tokens[:, 1:step + 1], fw_top_k_idx[:, k_idx:k_idx+1], k_eos_tokens), 1)
|
||||
k_ch_input_lengths = ch_input_lengths[k_idx::k]
|
||||
k_ch_output = channel_model(k_ch_input, k_ch_input_lengths, src_tokens)
|
||||
k_ch_lprobs = channel_model.get_normalized_probs(k_ch_output, log_probs=True)
|
||||
k_ch_intermed_scores = torch.gather(k_ch_lprobs[:, :-1, :], 2, src_tokens[:, 1:].unsqueeze(2)).squeeze(2)
|
||||
k_ch_intermed_scores *= not_padding.float()
|
||||
ch_scores[k_idx::k] = torch.sum(k_ch_intermed_scores, dim=1)
|
||||
elif self.channel_scoring_type == "src_vocab":
|
||||
ch_encoder_output = channel_model.encoder(channel_input, src_lengths=ch_input_lengths)
|
||||
ch_decoder_output, _ = channel_model.decoder(temp_src_tokens_full, encoder_out=ch_encoder_output, features_only=True)
|
||||
|
||||
del ch_encoder_output
|
||||
ch_lprobs = normalized_scores_with_batch_vocab(
|
||||
channel_model.decoder,
|
||||
ch_decoder_output, src_tokens, k, bsz, beam_size,
|
||||
self.src_dict.pad_index, top_k=self.top_k_vocab)
|
||||
ch_scores = torch.sum(ch_lprobs, dim=1)
|
||||
elif self.channel_scoring_type == "src_vocab_batched":
|
||||
ch_bsz_size = temp_src_tokens_full.shape[0]
|
||||
ch_lprobs_list = [None] * len(range(0, ch_bsz_size, self.ch_scoring_bsz))
|
||||
for i, start_idx in enumerate(range(0, ch_bsz_size, self.ch_scoring_bsz)):
|
||||
end_idx = min(start_idx + self.ch_scoring_bsz, ch_bsz_size)
|
||||
temp_src_tokens_full_batch = temp_src_tokens_full[start_idx:end_idx, :]
|
||||
channel_input_batch = channel_input[start_idx:end_idx, :]
|
||||
ch_input_lengths_batch = ch_input_lengths[start_idx:end_idx]
|
||||
ch_encoder_output_batch = channel_model.encoder(channel_input_batch, src_lengths=ch_input_lengths_batch)
|
||||
ch_decoder_output_batch, _ = channel_model.decoder(temp_src_tokens_full_batch, encoder_out=ch_encoder_output_batch, features_only=True)
|
||||
ch_lprobs_list[i] = normalized_scores_with_batch_vocab(
|
||||
channel_model.decoder,
|
||||
ch_decoder_output_batch, src_tokens, k, bsz, beam_size,
|
||||
self.src_dict.pad_index, top_k=self.top_k_vocab,
|
||||
start_idx=start_idx, end_idx=end_idx)
|
||||
ch_lprobs = torch.cat(ch_lprobs_list, dim=0)
|
||||
ch_scores = torch.sum(ch_lprobs, dim=1)
|
||||
else:
|
||||
ch_output = channel_model(channel_input, ch_input_lengths, temp_src_tokens_full)
|
||||
ch_lprobs = channel_model.get_normalized_probs(ch_output, log_probs=True)
|
||||
ch_intermed_scores = torch.gather(ch_lprobs[:, :-1, :], 2, temp_src_tokens_full[:, 1:].unsqueeze(2)).squeeze().view(bsz*beam_size*k, -1)
|
||||
ch_intermed_scores *= not_padding.float()
|
||||
ch_scores = torch.sum(ch_intermed_scores, dim=1)
|
||||
|
||||
else:
|
||||
cur_tgt_size = 0
|
||||
ch_scores = ch_scores.view(bsz*beam_size, k)
|
||||
expanded_lm_prefix_scores = lm_prefix_scores.unsqueeze(1).expand(-1, k).flatten()
|
||||
|
||||
if self.share_tgt_dict:
|
||||
lm_scores = get_lm_scores(lm, tokens[:, :step + 1].view(-1, step+1), lm_incremental_states, fw_top_k_idx.view(-1, 1), torch.tensor(np.full(tokens.size(0), step+1)), k)
|
||||
else:
|
||||
new_lm_input = dict2dict(tokens[:, :step + 1].view(-1, step+1), self.tgt_to_lm)
|
||||
new_cands = dict2dict(fw_top_k_idx.view(-1, 1), self.tgt_to_lm)
|
||||
lm_scores = get_lm_scores(lm, new_lm_input, lm_incremental_states, new_cands, torch.tensor(np.full(tokens.size(0), step+1)), k)
|
||||
|
||||
lm_scores.add_(expanded_lm_prefix_scores)
|
||||
ch_lm_scores = combine_ch_lm(self.combine_method, ch_scores, lm_scores, src_size, cur_tgt_size)
|
||||
# initialize all as min value
|
||||
new_fw_lprobs = ch_scores.new(lprobs_size).fill_(-1e17).view(bsz*beam_size, -1)
|
||||
new_ch_lm_lprobs = ch_scores.new(lprobs_size).fill_(-1e17).view(bsz*beam_size, -1)
|
||||
new_lm_lprobs = ch_scores.new(lprobs_size).fill_(-1e17).view(bsz*beam_size, -1)
|
||||
new_fw_lprobs[:, self.pad] = -math.inf
|
||||
new_ch_lm_lprobs[:, self.pad] = -math.inf
|
||||
new_lm_lprobs[:, self.pad] = -math.inf
|
||||
|
||||
new_fw_lprobs.scatter_(1, fw_top_k_idx, fw_top_k)
|
||||
new_ch_lm_lprobs.scatter_(1, fw_top_k_idx, ch_lm_scores)
|
||||
new_lm_lprobs.scatter_(1, fw_top_k_idx, lm_scores.view(-1, k))
|
||||
return new_fw_lprobs, new_ch_lm_lprobs, new_lm_lprobs
|
||||
|
||||
def combine_ch_lm(combine_type, ch_scores, lm_scores1, src_size, tgt_size):
|
||||
if self.channel_scoring_type == "unnormalized":
|
||||
ch_scores = self.log_softmax_fn(
|
||||
ch_scores.view(-1, self.beam_size * self.k2)
|
||||
).view(ch_scores.shape)
|
||||
ch_scores = ch_scores * self.ch_weight
|
||||
lm_scores1 = lm_scores1 * self.lm_weight
|
||||
|
||||
if combine_type == "lm_only":
|
||||
# log P(T|S) + log P(T)
|
||||
ch_scores = lm_scores1.view(ch_scores.size())
|
||||
elif combine_type == "noisy_channel":
|
||||
# 1/t log P(T|S) + 1/s log P(S|T) + 1/t log P(T)
|
||||
if self.normalize_lm_scores_by_tgt_len:
|
||||
ch_scores.div_(src_size)
|
||||
lm_scores_norm = lm_scores1.view(ch_scores.size()).div(tgt_size)
|
||||
ch_scores.add_(lm_scores_norm)
|
||||
# 1/t log P(T|S) + 1/s log P(S|T) + 1/s log P(T)
|
||||
else:
|
||||
ch_scores.add_(lm_scores1.view(ch_scores.size()))
|
||||
ch_scores.div_(src_size)
|
||||
|
||||
return ch_scores
|
||||
|
||||
if self.channel_models is not None:
|
||||
channel_model = self.channel_models[0] # assume only one channel_model model
|
||||
else:
|
||||
channel_model = None
|
||||
|
||||
lm = EnsembleModel(self.lm_models)
|
||||
lm_incremental_states = torch.jit.annotate(
|
||||
List[Dict[str, Dict[str, Optional[Tensor]]]],
|
||||
[
|
||||
torch.jit.annotate(Dict[str, Dict[str, Optional[Tensor]]], {})
|
||||
for i in range(lm.models_size)
|
||||
],
|
||||
)
|
||||
|
||||
reorder_state = None
|
||||
batch_idxs = None
|
||||
for step in range(max_len + 1): # one extra step for EOS marker
|
||||
# reorder decoder internal states based on the prev choice of beams
|
||||
if reorder_state is not None:
|
||||
if batch_idxs is not None:
|
||||
# update beam indices to take into account removed sentences
|
||||
corr = batch_idxs - torch.arange(batch_idxs.numel()).type_as(batch_idxs)
|
||||
reorder_state.view(-1, beam_size).add_(corr.unsqueeze(-1) * beam_size)
|
||||
model.reorder_incremental_state(incremental_states, reorder_state)
|
||||
encoder_outs = model.reorder_encoder_out(encoder_outs, reorder_state)
|
||||
|
||||
lm.reorder_incremental_state(lm_incremental_states, reorder_state)
|
||||
|
||||
fw_lprobs, avg_attn_scores = model.forward_decoder(
|
||||
tokens[:, :step + 1], encoder_outs, incremental_states, temperature=self.temperature,
|
||||
)
|
||||
|
||||
fw_lprobs[:, self.pad] = -math.inf # never select pad
|
||||
fw_lprobs[:, self.unk] -= self.unk_penalty # apply unk penalty
|
||||
fw_lprobs, ch_lm_lprobs, lm_lprobs = noisy_channel_rescoring(fw_lprobs, beam_size, bsz, src_tokens, tokens, self.k2)
|
||||
|
||||
# handle min and max length constraints
|
||||
if step >= max_len:
|
||||
fw_lprobs[:, :self.eos] = -math.inf
|
||||
fw_lprobs[:, self.eos + 1:] = -math.inf
|
||||
elif step < self.min_len:
|
||||
fw_lprobs[:, self.eos] = -math.inf
|
||||
|
||||
# handle prefix tokens (possibly with different lengths)
|
||||
if prefix_tokens is not None and step < prefix_tokens.size(1):
|
||||
prefix_toks = prefix_tokens[:, step].unsqueeze(-1).repeat(1, beam_size).view(-1)
|
||||
prefix_mask = prefix_toks.ne(self.pad)
|
||||
|
||||
prefix_fw_lprobs = fw_lprobs.gather(-1, prefix_toks.unsqueeze(-1))
|
||||
fw_lprobs[prefix_mask] = -math.inf
|
||||
fw_lprobs[prefix_mask] = fw_lprobs[prefix_mask].scatter_(
|
||||
-1, prefix_toks[prefix_mask].unsqueeze(-1), prefix_fw_lprobs
|
||||
)
|
||||
|
||||
prefix_ch_lm_lprobs = ch_lm_lprobs.gather(-1, prefix_toks.unsqueeze(-1))
|
||||
ch_lm_lprobs[prefix_mask] = -math.inf
|
||||
ch_lm_lprobs[prefix_mask] = ch_lm_lprobs[prefix_mask].scatter_(
|
||||
-1, prefix_toks[prefix_mask].unsqueeze(-1), prefix_ch_lm_lprobs
|
||||
)
|
||||
|
||||
prefix_lm_lprobs = lm_lprobs.gather(-1, prefix_toks.unsqueeze(-1))
|
||||
lm_lprobs[prefix_mask] = -math.inf
|
||||
lm_lprobs[prefix_mask] = lm_lprobs[prefix_mask].scatter_(
|
||||
-1, prefix_toks[prefix_mask].unsqueeze(-1), prefix_lm_lprobs
|
||||
)
|
||||
|
||||
# if prefix includes eos, then we should make sure tokens and
|
||||
# scores are the same across all beams
|
||||
eos_mask = prefix_toks.eq(self.eos)
|
||||
if eos_mask.any():
|
||||
# validate that the first beam matches the prefix
|
||||
first_beam = tokens[eos_mask].view(-1, beam_size, tokens.size(-1))[:, 0, 1:step + 1]
|
||||
eos_mask_batch_dim = eos_mask.view(-1, beam_size)[:, 0]
|
||||
target_prefix = prefix_tokens[eos_mask_batch_dim][:, :step]
|
||||
assert (first_beam == target_prefix).all()
|
||||
|
||||
def replicate_first_beam(tensor, mask):
|
||||
tensor = tensor.view(-1, beam_size, tensor.size(-1))
|
||||
tensor[mask] = tensor[mask][:, :1, :]
|
||||
return tensor.view(-1, tensor.size(-1))
|
||||
|
||||
# copy tokens, scores and lprobs from the first beam to all beams
|
||||
tokens = replicate_first_beam(tokens, eos_mask_batch_dim)
|
||||
scores = replicate_first_beam(scores, eos_mask_batch_dim)
|
||||
|
||||
fw_lprobs = replicate_first_beam(fw_lprobs, eos_mask_batch_dim)
|
||||
ch_lm_lprobs = replicate_first_beam(ch_lm_lprobs, eos_mask_batch_dim)
|
||||
lm_lprobs = replicate_first_beam(lm_lprobs, eos_mask_batch_dim)
|
||||
|
||||
if self.no_repeat_ngram_size > 0:
|
||||
# for each beam and batch sentence, generate a list of previous ngrams
|
||||
gen_ngrams = [{} for bbsz_idx in range(bsz * beam_size)]
|
||||
for bbsz_idx in range(bsz * beam_size):
|
||||
gen_tokens = tokens[bbsz_idx].tolist()
|
||||
for ngram in zip(*[gen_tokens[i:] for i in range(self.no_repeat_ngram_size)]):
|
||||
gen_ngrams[bbsz_idx][tuple(ngram[:-1])] = \
|
||||
gen_ngrams[bbsz_idx].get(tuple(ngram[:-1]), []) + [ngram[-1]]
|
||||
|
||||
# Record attention scores
|
||||
if avg_attn_scores is not None:
|
||||
if attn is None:
|
||||
attn = scores.new(bsz * beam_size, src_tokens.size(1), max_len + 2)
|
||||
attn_buf = attn.clone()
|
||||
nonpad_idxs = src_tokens.ne(self.pad)
|
||||
attn[:, :, step + 1].copy_(avg_attn_scores)
|
||||
|
||||
scores = scores.type_as(fw_lprobs)
|
||||
scores_buf = scores_buf.type_as(fw_lprobs)
|
||||
|
||||
self.search.set_src_lengths(src_lengths_no_eos)
|
||||
|
||||
if self.no_repeat_ngram_size > 0:
|
||||
def calculate_banned_tokens(bbsz_idx):
|
||||
# before decoding the next token, prevent decoding of ngrams that have already appeared
|
||||
ngram_index = tuple(tokens[bbsz_idx, step + 2 - self.no_repeat_ngram_size:step + 1].tolist())
|
||||
return gen_ngrams[bbsz_idx].get(ngram_index, [])
|
||||
|
||||
if step + 2 - self.no_repeat_ngram_size >= 0:
|
||||
# no banned tokens if we haven't generated no_repeat_ngram_size tokens yet
|
||||
banned_tokens = [calculate_banned_tokens(bbsz_idx) for bbsz_idx in range(bsz * beam_size)]
|
||||
else:
|
||||
banned_tokens = [[] for bbsz_idx in range(bsz * beam_size)]
|
||||
|
||||
for bbsz_idx in range(bsz * beam_size):
|
||||
fw_lprobs[bbsz_idx, banned_tokens[bbsz_idx]] = -math.inf
|
||||
|
||||
combined_noisy_channel_scores, fw_lprobs_top_k, lm_lprobs_top_k, cand_indices, cand_beams = self.search.step(
|
||||
step,
|
||||
fw_lprobs.view(bsz, -1, self.vocab_size),
|
||||
scores.view(bsz, beam_size, -1)[:, :, :step], ch_lm_lprobs.view(bsz, -1, self.vocab_size),
|
||||
lm_lprobs.view(bsz, -1, self.vocab_size), self.combine_method
|
||||
)
|
||||
|
||||
# cand_bbsz_idx contains beam indices for the top candidate
|
||||
# hypotheses, with a range of values: [0, bsz*beam_size),
|
||||
# and dimensions: [bsz, cand_size]
|
||||
cand_bbsz_idx = cand_beams.add(bbsz_offsets)
|
||||
|
||||
# finalize hypotheses that end in eos (except for candidates to be ignored)
|
||||
eos_mask = cand_indices.eq(self.eos)
|
||||
eos_mask[:, :beam_size] &= ~cands_to_ignore
|
||||
|
||||
# only consider eos when it's among the top beam_size indices
|
||||
eos_bbsz_idx = torch.masked_select(
|
||||
cand_bbsz_idx[:, :beam_size], mask=eos_mask[:, :beam_size]
|
||||
)
|
||||
|
||||
finalized_sents = set()
|
||||
if eos_bbsz_idx.numel() > 0:
|
||||
eos_scores = torch.masked_select(
|
||||
fw_lprobs_top_k[:, :beam_size], mask=eos_mask[:, :beam_size]
|
||||
)
|
||||
combined_noisy_channel_eos_scores = torch.masked_select(
|
||||
combined_noisy_channel_scores[:, :beam_size],
|
||||
mask=eos_mask[:, :beam_size],
|
||||
)
|
||||
|
||||
# finalize hypo using channel model score
|
||||
finalized_sents = finalize_hypos(
|
||||
step, eos_bbsz_idx, eos_scores, combined_noisy_channel_eos_scores)
|
||||
|
||||
num_remaining_sent -= len(finalized_sents)
|
||||
|
||||
assert num_remaining_sent >= 0
|
||||
if num_remaining_sent == 0:
|
||||
break
|
||||
|
||||
if len(finalized_sents) > 0:
|
||||
new_bsz = bsz - len(finalized_sents)
|
||||
|
||||
# construct batch_idxs which holds indices of batches to keep for the next pass
|
||||
batch_mask = cand_indices.new_ones(bsz)
|
||||
batch_mask[cand_indices.new(finalized_sents)] = 0
|
||||
batch_idxs = torch.nonzero(batch_mask).squeeze(-1)
|
||||
|
||||
eos_mask = eos_mask[batch_idxs]
|
||||
cand_beams = cand_beams[batch_idxs]
|
||||
bbsz_offsets.resize_(new_bsz, 1)
|
||||
cand_bbsz_idx = cand_beams.add(bbsz_offsets)
|
||||
|
||||
lm_lprobs_top_k = lm_lprobs_top_k[batch_idxs]
|
||||
|
||||
fw_lprobs_top_k = fw_lprobs_top_k[batch_idxs]
|
||||
cand_indices = cand_indices[batch_idxs]
|
||||
if prefix_tokens is not None:
|
||||
prefix_tokens = prefix_tokens[batch_idxs]
|
||||
src_lengths_no_eos = src_lengths_no_eos[batch_idxs]
|
||||
cands_to_ignore = cands_to_ignore[batch_idxs]
|
||||
|
||||
scores = scores.view(bsz, -1)[batch_idxs].view(new_bsz * beam_size, -1)
|
||||
scores_buf.resize_as_(scores)
|
||||
tokens = tokens.view(bsz, -1)[batch_idxs].view(new_bsz * beam_size, -1)
|
||||
tokens_buf.resize_as_(tokens)
|
||||
src_tokens = src_tokens.view(bsz, -1)[batch_idxs].view(new_bsz * beam_size, -1)
|
||||
src_lengths = src_lengths.view(bsz, -1)[batch_idxs].view(new_bsz * beam_size, -1)
|
||||
lm_prefix_scores = lm_prefix_scores.view(bsz, -1)[batch_idxs].view(new_bsz * beam_size, -1).squeeze()
|
||||
|
||||
if attn is not None:
|
||||
attn = attn.view(bsz, -1)[batch_idxs].view(new_bsz * beam_size, attn.size(1), -1)
|
||||
attn_buf.resize_as_(attn)
|
||||
bsz = new_bsz
|
||||
else:
|
||||
batch_idxs = None
|
||||
|
||||
# Set active_mask so that values > cand_size indicate eos or
|
||||
# ignored hypos and values < cand_size indicate candidate
|
||||
# active hypos. After this, the min values per row are the top
|
||||
# candidate active hypos.
|
||||
eos_mask[:, :beam_size] |= cands_to_ignore
|
||||
active_mask = torch.add(
|
||||
eos_mask.type_as(cand_offsets) * cand_size,
|
||||
cand_offsets[: eos_mask.size(1)],
|
||||
)
|
||||
|
||||
# get the top beam_size active hypotheses, which are just the hypos
|
||||
# with the smallest values in active_mask
|
||||
active_hypos, new_cands_to_ignore = buffer('active_hypos'), buffer('new_cands_to_ignore')
|
||||
torch.topk(
|
||||
active_mask, k=beam_size, dim=1, largest=False,
|
||||
out=(new_cands_to_ignore, active_hypos)
|
||||
)
|
||||
|
||||
# update cands_to_ignore to ignore any finalized hypos
|
||||
cands_to_ignore = new_cands_to_ignore.ge(cand_size)[:, :beam_size]
|
||||
assert (~cands_to_ignore).any(dim=1).all()
|
||||
|
||||
active_bbsz_idx = buffer('active_bbsz_idx')
|
||||
torch.gather(
|
||||
cand_bbsz_idx, dim=1, index=active_hypos,
|
||||
out=active_bbsz_idx,
|
||||
)
|
||||
active_scores = torch.gather(
|
||||
fw_lprobs_top_k, dim=1, index=active_hypos,
|
||||
out=scores[:, step].view(bsz, beam_size),
|
||||
)
|
||||
|
||||
active_bbsz_idx = active_bbsz_idx.view(-1)
|
||||
active_scores = active_scores.view(-1)
|
||||
|
||||
# copy tokens and scores for active hypotheses
|
||||
torch.index_select(
|
||||
tokens[:, :step + 1], dim=0, index=active_bbsz_idx,
|
||||
out=tokens_buf[:, :step + 1],
|
||||
)
|
||||
torch.gather(
|
||||
cand_indices, dim=1, index=active_hypos,
|
||||
out=tokens_buf.view(bsz, beam_size, -1)[:, :, step + 1],
|
||||
)
|
||||
if step > 0:
|
||||
torch.index_select(
|
||||
scores[:, :step], dim=0, index=active_bbsz_idx,
|
||||
out=scores_buf[:, :step],
|
||||
)
|
||||
torch.gather(
|
||||
fw_lprobs_top_k, dim=1, index=active_hypos,
|
||||
out=scores_buf.view(bsz, beam_size, -1)[:, :, step],
|
||||
)
|
||||
torch.gather(
|
||||
lm_lprobs_top_k, dim=1, index=active_hypos,
|
||||
out=lm_prefix_scores.view(bsz, beam_size)
|
||||
)
|
||||
|
||||
# copy attention for active hypotheses
|
||||
if attn is not None:
|
||||
torch.index_select(
|
||||
attn[:, :, :step + 2], dim=0, index=active_bbsz_idx,
|
||||
out=attn_buf[:, :, :step + 2],
|
||||
)
|
||||
|
||||
# swap buffers
|
||||
tokens, tokens_buf = tokens_buf, tokens
|
||||
scores, scores_buf = scores_buf, scores
|
||||
if attn is not None:
|
||||
attn, attn_buf = attn_buf, attn
|
||||
|
||||
# reorder incremental state in decoder
|
||||
reorder_state = active_bbsz_idx
|
||||
|
||||
# sort by score descending
|
||||
for sent in range(len(finalized)):
|
||||
finalized[sent] = sorted(finalized[sent], key=lambda r: r['score'], reverse=True)
|
||||
|
||||
return finalized
|
||||
|
||||
|
||||
def get_lm_scores(model, input_tokens, incremental_states, cand_tokens, input_len, k):
|
||||
with torch.no_grad():
|
||||
lm_lprobs, avg_attn_scores = model.forward_decoder(
|
||||
input_tokens, encoder_outs=None, incremental_states=incremental_states,
|
||||
)
|
||||
|
||||
lm_lprobs_size = lm_lprobs.size(0)
|
||||
probs_next_wrd = torch.gather(lm_lprobs.repeat(1, k).view(lm_lprobs_size*k, -1), 1, cand_tokens).squeeze().view(-1)
|
||||
|
||||
return probs_next_wrd
|
||||
|
||||
|
||||
def make_dict2dict(old_dict, new_dict):
|
||||
dict2dict_map = {}
|
||||
for sym in old_dict.symbols:
|
||||
dict2dict_map[old_dict.index(sym)] = new_dict.index(sym)
|
||||
return dict2dict_map
|
||||
|
||||
|
||||
def dict2dict(tokens, dict2dict_map):
|
||||
if tokens.device == torch.device('cpu'):
|
||||
tokens_tmp = tokens
|
||||
else:
|
||||
tokens_tmp = tokens.cpu()
|
||||
return tokens_tmp.map_(
|
||||
tokens_tmp,
|
||||
lambda _, val, dict2dict_map=dict2dict_map : dict2dict_map[float(val)]
|
||||
).to(tokens.device)
|
||||
|
||||
|
||||
def reorder_tokens(tokens, lengths, eos):
|
||||
# reorder source tokens so they may be used as reference for P(S|T)
|
||||
return torch.cat((tokens.new([eos]), tokens[-lengths:-1], tokens[:-lengths]), 0)
|
||||
|
||||
|
||||
def reorder_all_tokens(tokens, lengths, eos):
|
||||
# used to reorder src tokens from [<pad> <w1> <w2> .. <eos>] to [<eos> <w1> <w2>...<pad>]
|
||||
# so source tokens can be used to predict P(S|T)
|
||||
return torch.stack([reorder_tokens(token, length, eos) for token, length in zip(tokens, lengths)])
|
||||
|
||||
|
||||
def normalized_scores_with_batch_vocab(
|
||||
model_decoder, features, target_ids, k, bsz, beam_size,
|
||||
pad_idx, top_k=0, vocab_size_meter=None, start_idx=None,
|
||||
end_idx=None, **kwargs):
|
||||
"""
|
||||
Get normalized probabilities (or log probs) from a net's output
|
||||
w.r.t. vocab consisting of target IDs in the batch
|
||||
"""
|
||||
if model_decoder.adaptive_softmax is None:
|
||||
weight = model_decoder.output_projection.weight
|
||||
vocab_ids = torch.unique(
|
||||
torch.cat(
|
||||
(torch.unique(target_ids), torch.arange(top_k, device=target_ids.device))
|
||||
)
|
||||
)
|
||||
id_map = dict(zip(vocab_ids.tolist(), range(len(vocab_ids))))
|
||||
mapped_target_ids = target_ids.cpu().apply_(
|
||||
lambda x, id_map=id_map: id_map[x]
|
||||
).to(target_ids.device)
|
||||
expanded_target_ids = mapped_target_ids[:, :].repeat(1, k).view(bsz*beam_size*k, -1)
|
||||
if start_idx is not None and end_idx is not None:
|
||||
expanded_target_ids = expanded_target_ids[start_idx:end_idx, :]
|
||||
logits = F.linear(features, weight[vocab_ids, :])
|
||||
log_softmax = F.log_softmax(logits, dim=-1, dtype=torch.float32)
|
||||
intermed_scores = torch.gather(
|
||||
log_softmax[:, :-1, :],
|
||||
2,
|
||||
expanded_target_ids[:, 1:].unsqueeze(2),
|
||||
).squeeze()
|
||||
not_padding = expanded_target_ids[:, 1:] != pad_idx
|
||||
intermed_scores *= not_padding.float()
|
||||
return intermed_scores
|
||||
else:
|
||||
raise ValueError("adaptive softmax doesn't work with " +
|
||||
"`normalized_scores_with_batch_vocab()`")
|
||||
@@ -0,0 +1,127 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from fairseq.tasks.translation import TranslationTask
|
||||
from fairseq.tasks.language_modeling import LanguageModelingTask
|
||||
from fairseq import checkpoint_utils
|
||||
import argparse
|
||||
from fairseq.tasks import register_task
|
||||
import torch
|
||||
|
||||
|
||||
@register_task("noisy_channel_translation")
|
||||
class NoisyChannelTranslation(TranslationTask):
|
||||
"""
|
||||
Rescore the top k candidates from each beam using noisy channel modeling
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def add_args(parser):
|
||||
"""Add task-specific arguments to the parser."""
|
||||
TranslationTask.add_args(parser)
|
||||
# fmt: off
|
||||
parser.add_argument('--channel-model', metavar='FILE',
|
||||
help='path to P(S|T) model. P(S|T) and P(T|S) must share source and target dictionaries.')
|
||||
parser.add_argument('--combine-method', default='lm_only',
|
||||
choices=['lm_only', 'noisy_channel'],
|
||||
help="""method for combining direct and channel model scores.
|
||||
lm_only: decode with P(T|S)P(T)
|
||||
noisy_channel: decode with 1/t P(T|S) + 1/s(P(S|T)P(T))""")
|
||||
parser.add_argument('--normalize-lm-scores-by-tgt-len', action='store_true', default=False,
|
||||
help='normalize lm score by target length instead of source length')
|
||||
parser.add_argument('--channel-scoring-type', default='log_norm', choices=['unnormalized', 'log_norm', 'k2_separate', 'src_vocab', 'src_vocab_batched'],
|
||||
help="Normalize bw scores with log softmax or return bw scores without log softmax")
|
||||
parser.add_argument('--top-k-vocab', default=0, type=int,
|
||||
help='top k vocab IDs to use with `src_vocab` in channel model scoring')
|
||||
parser.add_argument('--k2', default=50, type=int,
|
||||
help='the top k2 candidates to rescore with the noisy channel model for each beam')
|
||||
parser.add_argument('--ch-wt', default=1, type=float,
|
||||
help='weight for the channel model')
|
||||
parser.add_argument('--lm-model', metavar='FILE',
|
||||
help='path to lm model file, to model P(T). P(T) must share the same vocab as the direct model on the target side')
|
||||
parser.add_argument('--lm-data', metavar='FILE',
|
||||
help='path to lm model training data for target language, used to properly load LM with correct dictionary')
|
||||
parser.add_argument('--lm-wt', default=1, type=float,
|
||||
help='the weight of the lm in joint decoding')
|
||||
# fmt: on
|
||||
|
||||
def build_generator(
|
||||
self, models, args, seq_gen_cls=None, extra_gen_cls_kwargs=None
|
||||
):
|
||||
if getattr(args, "score_reference", False):
|
||||
raise NotImplementedError()
|
||||
else:
|
||||
from .noisy_channel_sequence_generator import NoisyChannelSequenceGenerator
|
||||
use_cuda = torch.cuda.is_available() and not self.args.cpu
|
||||
assert self.args.lm_model is not None, '--lm-model required for noisy channel generation!'
|
||||
assert self.args.lm_data is not None, '--lm-data required for noisy channel generation to map between LM and bitext vocabs'
|
||||
if self.args.channel_model is not None:
|
||||
import copy
|
||||
ch_args_task = copy.deepcopy(self.args)
|
||||
tmp = ch_args_task.source_lang
|
||||
ch_args_task.source_lang = ch_args_task.target_lang
|
||||
ch_args_task.target_lang = tmp
|
||||
ch_args_task._name = 'translation'
|
||||
channel_task = TranslationTask.setup_task(ch_args_task)
|
||||
|
||||
arg_dict = {}
|
||||
arg_dict['task'] = 'language_modeling'
|
||||
arg_dict['sample_break_mode'] = 'eos'
|
||||
arg_dict['data'] = self.args.lm_data
|
||||
arg_dict['output_dictionary_size'] = -1
|
||||
lm_args = argparse.Namespace(**arg_dict)
|
||||
lm_task = LanguageModelingTask.setup_task(lm_args)
|
||||
lm_dict = lm_task.output_dictionary
|
||||
|
||||
if self.args.channel_model is not None:
|
||||
channel_models, _ = checkpoint_utils.load_model_ensemble(self.args.channel_model.split(':'), task=channel_task)
|
||||
|
||||
for model in channel_models:
|
||||
model.make_generation_fast_(
|
||||
beamable_mm_beam_size=None if args.no_beamable_mm else args.beam,
|
||||
need_attn=args.print_alignment,
|
||||
)
|
||||
if self.args.fp16:
|
||||
model.half()
|
||||
if use_cuda:
|
||||
model.cuda()
|
||||
else:
|
||||
channel_models = None
|
||||
|
||||
lm_models, _ = checkpoint_utils.load_model_ensemble(self.args.lm_model.split(':'), task=lm_task)
|
||||
|
||||
for model in lm_models:
|
||||
model.make_generation_fast_(
|
||||
beamable_mm_beam_size=None if args.no_beamable_mm else args.beam,
|
||||
need_attn=args.print_alignment,
|
||||
)
|
||||
if self.args.fp16:
|
||||
model.half()
|
||||
if use_cuda:
|
||||
model.cuda()
|
||||
return NoisyChannelSequenceGenerator(
|
||||
combine_method=self.args.combine_method,
|
||||
tgt_dict=self.target_dictionary,
|
||||
src_dict=self.source_dictionary,
|
||||
beam_size=getattr(args, 'beam', 5),
|
||||
max_len_a=getattr(args, 'max_len_a', 0),
|
||||
max_len_b=getattr(args, 'max_len_b', 200),
|
||||
min_len=getattr(args, 'min_len', 1),
|
||||
len_penalty=getattr(args, 'lenpen', 1),
|
||||
unk_penalty=getattr(args, 'unkpen', 0),
|
||||
temperature=getattr(args, 'temperature', 1.),
|
||||
match_source_len=getattr(args, 'match_source_len', False),
|
||||
no_repeat_ngram_size=getattr(args, 'no_repeat_ngram_size', 0),
|
||||
normalize_scores=(not getattr(args, 'unnormalized', False)),
|
||||
channel_models=channel_models,
|
||||
k2=getattr(self.args, 'k2', 50),
|
||||
ch_weight=getattr(self.args, 'ch_wt', 1),
|
||||
channel_scoring_type=self.args.channel_scoring_type,
|
||||
top_k_vocab=self.args.top_k_vocab,
|
||||
lm_models=lm_models,
|
||||
lm_dict=lm_dict,
|
||||
lm_weight=getattr(self.args, 'lm_wt', 1),
|
||||
normalize_lm_scores_by_tgt_len=getattr(self.args, 'normalize_lm_scores_by_tgt_len', False),
|
||||
)
|
||||
@@ -0,0 +1,64 @@
|
||||
# GottBERT: a pure German language model
|
||||
|
||||
## Introduction
|
||||
|
||||
[GottBERT](http://arxiv.org/abs/2012.02110) is a pretrained language model trained on 145GB of German text based on RoBERTa.
|
||||
|
||||
## Example usage
|
||||
|
||||
### fairseq
|
||||
##### Load GottBERT from torch.hub (PyTorch >= 1.1):
|
||||
```python
|
||||
import torch
|
||||
gottbert = torch.hub.load('pytorch/fairseq', 'gottbert-base')
|
||||
gottbert.eval() # disable dropout (or leave in train mode to finetune)
|
||||
```
|
||||
|
||||
##### Load GottBERT (for PyTorch 1.0 or custom models):
|
||||
```python
|
||||
# Download gottbert model
|
||||
wget https://dl.gottbert.de/fairseq/models/gottbert-base.tar.gz
|
||||
tar -xzvf gottbert.tar.gz
|
||||
|
||||
# Load the model in fairseq
|
||||
from fairseq.models.roberta import GottbertModel
|
||||
gottbert = GottbertModel.from_pretrained('/path/to/gottbert')
|
||||
gottbert.eval() # disable dropout (or leave in train mode to finetune)
|
||||
```
|
||||
|
||||
##### Filling masks:
|
||||
```python
|
||||
masked_line = 'Gott ist <mask> ! :)'
|
||||
gottbert.fill_mask(masked_line, topk=3)
|
||||
# [('Gott ist gut ! :)', 0.3642110526561737, ' gut'),
|
||||
# ('Gott ist überall ! :)', 0.06009674072265625, ' überall'),
|
||||
# ('Gott ist großartig ! :)', 0.0370681993663311, ' großartig')]
|
||||
```
|
||||
|
||||
##### Extract features from GottBERT
|
||||
|
||||
```python
|
||||
# Extract the last layer's features
|
||||
line = "Der erste Schluck aus dem Becher der Naturwissenschaft macht atheistisch , aber auf dem Grunde des Bechers wartet Gott !"
|
||||
tokens = gottbert.encode(line)
|
||||
last_layer_features = gottbert.extract_features(tokens)
|
||||
assert last_layer_features.size() == torch.Size([1, 27, 768])
|
||||
|
||||
# Extract all layer's features (layer 0 is the embedding layer)
|
||||
all_layers = gottbert.extract_features(tokens, return_all_hiddens=True)
|
||||
assert len(all_layers) == 13
|
||||
assert torch.all(all_layers[-1] == last_layer_features)
|
||||
```
|
||||
## Citation
|
||||
If you use our work, please cite:
|
||||
|
||||
```bibtex
|
||||
@misc{scheible2020gottbert,
|
||||
title={GottBERT: a pure German Language Model},
|
||||
author={Raphael Scheible and Fabian Thomczyk and Patric Tippmann and Victor Jaravine and Martin Boeker},
|
||||
year={2020},
|
||||
eprint={2012.02110},
|
||||
archivePrefix={arXiv},
|
||||
primaryClass={cs.CL}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,89 @@
|
||||
# Jointly Learning to Align and Translate with Transformer Models (Garg et al., 2019)
|
||||
|
||||
This page includes instructions for training models described in [Jointly Learning to Align and Translate with Transformer Models (Garg et al., 2019)](https://arxiv.org/abs/1909.02074).
|
||||
|
||||
## Training a joint alignment-translation model on WMT'18 En-De
|
||||
|
||||
##### 1. Extract and preprocess the WMT'18 En-De data
|
||||
```bash
|
||||
./prepare-wmt18en2de_no_norm_no_escape_no_agressive.sh
|
||||
```
|
||||
|
||||
##### 2. Generate alignments from statistical alignment toolkits e.g. Giza++/FastAlign.
|
||||
In this example, we use FastAlign.
|
||||
```bash
|
||||
git clone git@github.com:clab/fast_align.git
|
||||
pushd fast_align
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
popd
|
||||
ALIGN=fast_align/build/fast_align
|
||||
paste bpe.32k/train.en bpe.32k/train.de | awk -F '\t' '{print $1 " ||| " $2}' > bpe.32k/train.en-de
|
||||
$ALIGN -i bpe.32k/train.en-de -d -o -v > bpe.32k/train.align
|
||||
```
|
||||
|
||||
##### 3. Preprocess the dataset with the above generated alignments.
|
||||
```bash
|
||||
fairseq-preprocess \
|
||||
--source-lang en --target-lang de \
|
||||
--trainpref bpe.32k/train \
|
||||
--validpref bpe.32k/valid \
|
||||
--testpref bpe.32k/test \
|
||||
--align-suffix align \
|
||||
--destdir binarized/ \
|
||||
--joined-dictionary \
|
||||
--workers 32
|
||||
```
|
||||
|
||||
##### 4. Train a model
|
||||
```bash
|
||||
fairseq-train \
|
||||
binarized \
|
||||
--arch transformer_wmt_en_de_big_align --share-all-embeddings \
|
||||
--optimizer adam --adam-betas '(0.9, 0.98)' --clip-norm 0.0 --activation-fn relu\
|
||||
--lr 0.0002 --lr-scheduler inverse_sqrt --warmup-updates 4000 --warmup-init-lr 1e-07 \
|
||||
--dropout 0.3 --attention-dropout 0.1 --weight-decay 0.0 \
|
||||
--max-tokens 3500 --label-smoothing 0.1 \
|
||||
--save-dir ./checkpoints --log-interval 1000 --max-update 60000 \
|
||||
--keep-interval-updates -1 --save-interval-updates 0 \
|
||||
--load-alignments --criterion label_smoothed_cross_entropy_with_alignment \
|
||||
--fp16
|
||||
```
|
||||
|
||||
Note that the `--fp16` flag requires you have CUDA 9.1 or greater and a Volta GPU or newer.
|
||||
|
||||
If you want to train the above model with big batches (assuming your machine has 8 GPUs):
|
||||
- add `--update-freq 8` to simulate training on 8x8=64 GPUs
|
||||
- increase the learning rate; 0.0007 works well for big batches
|
||||
|
||||
##### 5. Evaluate and generate the alignments (BPE level)
|
||||
```bash
|
||||
fairseq-generate \
|
||||
binarized --gen-subset test --print-alignment \
|
||||
--source-lang en --target-lang de \
|
||||
--path checkpoints/checkpoint_best.pt --beam 5 --nbest 1
|
||||
```
|
||||
|
||||
##### 6. Other resources.
|
||||
The code for:
|
||||
1. preparing alignment test sets
|
||||
2. converting BPE level alignments to token level alignments
|
||||
3. symmetrizing bidirectional alignments
|
||||
4. evaluating alignments using AER metric
|
||||
can be found [here](https://github.com/lilt/alignment-scripts)
|
||||
|
||||
## Citation
|
||||
|
||||
```bibtex
|
||||
@inproceedings{garg2019jointly,
|
||||
title = {Jointly Learning to Align and Translate with Transformer Models},
|
||||
author = {Garg, Sarthak and Peitz, Stephan and Nallasamy, Udhyakumar and Paulik, Matthias},
|
||||
booktitle = {Conference on Empirical Methods in Natural Language Processing (EMNLP)},
|
||||
address = {Hong Kong},
|
||||
month = {November},
|
||||
url = {https://arxiv.org/abs/1909.02074},
|
||||
year = {2019},
|
||||
}
|
||||
```
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
echo 'Cloning Moses github repository (for tokenization scripts)...'
|
||||
git clone https://github.com/moses-smt/mosesdecoder.git
|
||||
|
||||
SCRIPTS=mosesdecoder/scripts
|
||||
TOKENIZER=$SCRIPTS/tokenizer/tokenizer.perl
|
||||
CLEAN=$SCRIPTS/training/clean-corpus-n.perl
|
||||
REM_NON_PRINT_CHAR=$SCRIPTS/tokenizer/remove-non-printing-char.perl
|
||||
|
||||
URLS=(
|
||||
"http://statmt.org/wmt13/training-parallel-europarl-v7.tgz"
|
||||
"http://statmt.org/wmt13/training-parallel-commoncrawl.tgz"
|
||||
"http://data.statmt.org/wmt18/translation-task/training-parallel-nc-v13.tgz"
|
||||
"http://data.statmt.org/wmt18/translation-task/rapid2016.tgz"
|
||||
"http://data.statmt.org/wmt17/translation-task/dev.tgz"
|
||||
"http://statmt.org/wmt14/test-full.tgz"
|
||||
)
|
||||
CORPORA=(
|
||||
"training/europarl-v7.de-en"
|
||||
"commoncrawl.de-en"
|
||||
"training-parallel-nc-v13/news-commentary-v13.de-en"
|
||||
"rapid2016.de-en"
|
||||
)
|
||||
|
||||
if [ ! -d "$SCRIPTS" ]; then
|
||||
echo "Please set SCRIPTS variable correctly to point to Moses scripts."
|
||||
exit
|
||||
fi
|
||||
|
||||
src=en
|
||||
tgt=de
|
||||
lang=en-de
|
||||
prep=wmt18_en_de
|
||||
tmp=$prep/tmp
|
||||
orig=orig
|
||||
dev=dev/newstest2012
|
||||
codes=32000
|
||||
bpe=bpe.32k
|
||||
|
||||
mkdir -p $orig $tmp $prep $bpe
|
||||
|
||||
cd $orig
|
||||
|
||||
for ((i=0;i<${#URLS[@]};++i)); do
|
||||
url=${URLS[i]}
|
||||
file=$(basename $url)
|
||||
if [ -f $file ]; then
|
||||
echo "$file already exists, skipping download"
|
||||
else
|
||||
wget "$url"
|
||||
if [ -f $file ]; then
|
||||
echo "$url successfully downloaded."
|
||||
else
|
||||
echo "$url not successfully downloaded."
|
||||
exit 1
|
||||
fi
|
||||
if [ ${file: -4} == ".tgz" ]; then
|
||||
tar zxvf $file
|
||||
elif [ ${file: -4} == ".tar" ]; then
|
||||
tar xvf $file
|
||||
fi
|
||||
fi
|
||||
done
|
||||
cd ..
|
||||
|
||||
echo "pre-processing train data..."
|
||||
for l in $src $tgt; do
|
||||
rm -rf $tmp/train.tags.$lang.tok.$l
|
||||
for f in "${CORPORA[@]}"; do
|
||||
cat $orig/$f.$l | \
|
||||
perl $REM_NON_PRINT_CHAR | \
|
||||
perl $TOKENIZER -threads 8 -l $l -no-escape >> $tmp/train.tags.$lang.tok.$l
|
||||
done
|
||||
done
|
||||
|
||||
echo "pre-processing test data..."
|
||||
for l in $src $tgt; do
|
||||
if [ "$l" == "$src" ]; then
|
||||
t="src"
|
||||
else
|
||||
t="ref"
|
||||
fi
|
||||
grep '<seg id' $orig/test-full/newstest2014-deen-$t.$l.sgm | \
|
||||
sed -e 's/<seg id="[0-9]*">\s*//g' | \
|
||||
sed -e 's/\s*<\/seg>\s*//g' | \
|
||||
sed -e "s/\’/\'/g" | \
|
||||
perl $TOKENIZER -threads 8 -l $l -no-escape > $tmp/test.$l
|
||||
echo ""
|
||||
done
|
||||
|
||||
# apply length filtering before BPE
|
||||
perl $CLEAN -ratio 1.5 $tmp/train.tags.$lang.tok $src $tgt $tmp/train 1 100
|
||||
|
||||
# use newstest2012 for valid
|
||||
echo "pre-processing valid data..."
|
||||
for l in $src $tgt; do
|
||||
rm -rf $tmp/valid.$l
|
||||
cat $orig/$dev.$l | \
|
||||
perl $REM_NON_PRINT_CHAR | \
|
||||
perl $TOKENIZER -threads 8 -l $l -no-escape >> $tmp/valid.$l
|
||||
done
|
||||
|
||||
mkdir output
|
||||
mv $tmp/{train,valid,test}.{$src,$tgt} output
|
||||
|
||||
#BPE
|
||||
git clone https://github.com/glample/fastBPE.git
|
||||
pushd fastBPE
|
||||
g++ -std=c++11 -pthread -O3 fastBPE/main.cc -IfastBPE -o fast
|
||||
popd
|
||||
fastBPE/fast learnbpe $codes output/train.$src output/train.$tgt > $bpe/codes
|
||||
for split in {train,valid,test}; do for lang in {en,de}; do fastBPE/fast applybpe $bpe/$split.$lang output/$split.$lang $bpe/codes; done; done
|
||||
@@ -0,0 +1,39 @@
|
||||
# Adaptive Input Representations for Neural Language Modeling (Baevski and Auli, 2018)
|
||||
|
||||
## Pre-trained models
|
||||
|
||||
Description | Parameters | Dataset | Model and Test set(s)
|
||||
---|---:|---|---
|
||||
Adaptive Inputs <br> ([Baevski and Auli, 2018](https://arxiv.org/abs/1809.10853)) | 1026M | [Google Billion Words](https://github.com/ciprian-chelba/1-billion-word-language-modeling-benchmark) | [download (.tar.bz2)](https://dl.fbaipublicfiles.com/fairseq/models/lm/adaptive_lm_gbw_huge.tar.bz2)
|
||||
Adaptive Inputs <br> ([Baevski and Auli, 2018](https://arxiv.org/abs/1809.10853)) | 247M | [WikiText-103](https://blog.einstein.ai/the-wikitext-long-term-dependency-language-modeling-dataset/) | [download (.tar.bz2)](https://dl.fbaipublicfiles.com/fairseq/models/lm/adaptive_lm_wiki103.v2.tar.bz2)
|
||||
|
||||
## Training an LM with adaptive inputs
|
||||
|
||||
First, see the general [language modeling README](README.md) for instructions on
|
||||
preprocessing the WikiText-103 data.
|
||||
|
||||
Then use the following training command to train a model with adaptive inputs
|
||||
using the `transformer_lm_wiki103` model architecture:
|
||||
```bash
|
||||
fairseq-train --task language_modeling \
|
||||
data-bin/wikitext-103 \
|
||||
--save-dir checkpoints/transformer_wikitext-103 \
|
||||
--arch transformer_lm_wiki103 \
|
||||
--max-update 286000 --lr 1.0 --t-mult 2 --lr-period-updates 270000 --lr-scheduler cosine --lr-shrink 0.75 \
|
||||
--warmup-updates 16000 --warmup-init-lr 1e-07 --stop-min-lr 1e-09 --optimizer nag --min-lr 0.0001 --clip-norm 0.1 \
|
||||
--criterion adaptive_loss --max-tokens 3072 --update-freq 3 --tokens-per-sample 3072 --seed 1 \
|
||||
--sample-break-mode none --skip-invalid-size-inputs-valid-test --ddp-backend=no_c10d
|
||||
```
|
||||
|
||||
## Citation
|
||||
|
||||
```bibtex
|
||||
@inproceedings{
|
||||
baevski2018adaptive,
|
||||
title={Adaptive Input Representations for Neural Language Modeling},
|
||||
author={Alexei Baevski and Michael Auli},
|
||||
booktitle={International Conference on Learning Representations},
|
||||
year={2019},
|
||||
url={https://openreview.net/forum?id=ByxZX20qFQ},
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
# Language Modeling with Gated Convolutional Networks (Dauphin et al., 2017)
|
||||
|
||||
## Example usage
|
||||
|
||||
First download and preprocess the data following the main [language modeling README](README.md).
|
||||
|
||||
Then to train a convolutional LM using the `fconv_lm_dauphin_wikitext103`
|
||||
architecture:
|
||||
```bash
|
||||
fairseq-train --task language_modeling \
|
||||
data-bin/wikitext-103 \
|
||||
--save-dir checkpoints/fconv_wikitext-103 \
|
||||
--arch fconv_lm_dauphin_wikitext103 \
|
||||
--adaptive-softmax-cutoff 10000,20000,200000 \
|
||||
--dropout 0.2 \
|
||||
--criterion adaptive_loss \
|
||||
--optimizer nag --clip-norm 0.1 --weight-decay 5e-06 \
|
||||
--lr 1.0 --lr-scheduler reduce_lr_on_plateau --lr-shrink 0.5 \
|
||||
--max-tokens 1024 --tokens-per-sample 1024 \
|
||||
--ddp-backend no_c10d \
|
||||
--max-epoch 35
|
||||
```
|
||||
|
||||
And evaluate with:
|
||||
```bash
|
||||
fairseq-eval-lm data-bin/wikitext-103 --path checkpoints/fconv_wiki103/checkpoint_best.pt
|
||||
```
|
||||
|
||||
## Citation
|
||||
|
||||
```bibtex
|
||||
@inproceedings{dauphin2017language,
|
||||
title={Language Modeling with Gated Convolutional Networks},
|
||||
author={Dauphin, Yann N and Fan, Angela and Auli, Michael and Grangier, David},
|
||||
booktitle={Proceedings of the 34th International Conference on Machine Learning-Volume 70},
|
||||
pages={933--941},
|
||||
year={2017},
|
||||
organization={JMLR}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,123 @@
|
||||
# Neural Language Modeling
|
||||
|
||||
## Pre-trained models
|
||||
|
||||
Model | Description | Dataset | Download
|
||||
---|---|---|---
|
||||
`transformer_lm.gbw.adaptive_huge` | Adaptive Inputs <br> ([Baevski and Auli, 2018](https://arxiv.org/abs/1809.10853)) <br> 1026M params | [Google Billion Words](https://github.com/ciprian-chelba/1-billion-word-language-modeling-benchmark) | [download (.tar.bz2)](https://dl.fbaipublicfiles.com/fairseq/models/lm/adaptive_lm_gbw_huge.tar.bz2)
|
||||
`transformer_lm.wiki103.adaptive` | Adaptive Inputs <br> ([Baevski and Auli, 2018](https://arxiv.org/abs/1809.10853)) <br> 247M params | [WikiText-103](https://blog.einstein.ai/the-wikitext-long-term-dependency-language-modeling-dataset) | [download (.tar.bz2)](https://dl.fbaipublicfiles.com/fairseq/models/lm/adaptive_lm_wiki103.v2.tar.bz2)
|
||||
`transformer_lm.wmt19.en` | English LM <br> ([Ng et al., 2019](https://arxiv.org/abs/1907.06616)) | [WMT News Crawl](http://data.statmt.org/news-crawl/) | [download (.tar.gz)](https://dl.fbaipublicfiles.com/fairseq/models/lm/wmt19.en.tar.gz)
|
||||
`transformer_lm.wmt19.de` | German LM <br> ([Ng et al., 2019](https://arxiv.org/abs/1907.06616)) | [WMT News Crawl](http://data.statmt.org/news-crawl/) | [download (.tar.gz)](https://dl.fbaipublicfiles.com/fairseq/models/lm/wmt19.de.tar.gz)
|
||||
`transformer_lm.wmt19.ru` | Russian LM <br> ([Ng et al., 2019](https://arxiv.org/abs/1907.06616)) | [WMT News Crawl](http://data.statmt.org/news-crawl/) | [download (.tar.gz)](https://dl.fbaipublicfiles.com/fairseq/models/lm/wmt19.ru.tar.gz)
|
||||
|
||||
## Example usage
|
||||
|
||||
We require a few additional Python dependencies for preprocessing:
|
||||
```bash
|
||||
pip install fastBPE sacremoses
|
||||
```
|
||||
|
||||
To sample from a language model using PyTorch Hub:
|
||||
```python
|
||||
import torch
|
||||
|
||||
# List available models
|
||||
torch.hub.list('pytorch/fairseq') # [..., 'transformer_lm.wmt19.en', ...]
|
||||
|
||||
# Load an English LM trained on WMT'19 News Crawl data
|
||||
en_lm = torch.hub.load('pytorch/fairseq', 'transformer_lm.wmt19.en', tokenizer='moses', bpe='fastbpe')
|
||||
en_lm.eval() # disable dropout
|
||||
|
||||
# Move model to GPU
|
||||
en_lm.cuda()
|
||||
|
||||
# Sample from the language model
|
||||
en_lm.sample('Barack Obama', beam=1, sampling=True, sampling_topk=10, temperature=0.8)
|
||||
# "Barack Obama is coming to Sydney and New Zealand (...)"
|
||||
|
||||
# Compute perplexity for a sequence
|
||||
en_lm.score('Barack Obama is coming to Sydney and New Zealand')['positional_scores'].mean().neg().exp()
|
||||
# tensor(15.1474)
|
||||
|
||||
# The same interface can be used with custom models as well
|
||||
from fairseq.models.transformer_lm import TransformerLanguageModel
|
||||
custom_lm = TransformerLanguageModel.from_pretrained('/path/to/model/dir', 'checkpoint100.pt', tokenizer='moses', bpe='fastbpe')
|
||||
custom_lm.sample('Barack Obama', beam=5)
|
||||
# "Barack Obama (...)"
|
||||
```
|
||||
|
||||
## Training a transformer language model with the CLI tools
|
||||
|
||||
### 1) Preprocess the data
|
||||
|
||||
First download and prepare the [WikiText-103 dataset](https://www.salesforce.com/products/einstein/ai-research/the-wikitext-dependency-language-modeling-dataset/):
|
||||
```bash
|
||||
cd examples/language_model/
|
||||
bash prepare-wikitext-103.sh
|
||||
cd ../..
|
||||
```
|
||||
|
||||
Next preprocess/binarize the data:
|
||||
```bash
|
||||
TEXT=examples/language_model/wikitext-103
|
||||
fairseq-preprocess \
|
||||
--only-source \
|
||||
--trainpref $TEXT/wiki.train.tokens \
|
||||
--validpref $TEXT/wiki.valid.tokens \
|
||||
--testpref $TEXT/wiki.test.tokens \
|
||||
--destdir data-bin/wikitext-103 \
|
||||
--workers 20
|
||||
```
|
||||
|
||||
### 2) Train a language model
|
||||
|
||||
Next we'll train a basic transformer language model on wikitext-103. For more
|
||||
advanced usage, see the [adaptive inputs README](README.adaptive_inputs.md).
|
||||
|
||||
To train a basic LM (assumes 2 GPUs):
|
||||
```
|
||||
$ fairseq-train --task language_modeling \
|
||||
data-bin/wikitext-103 \
|
||||
--save-dir checkpoints/transformer_wikitext-103 \
|
||||
--arch transformer_lm --share-decoder-input-output-embed \
|
||||
--dropout 0.1 \
|
||||
--optimizer adam --adam-betas '(0.9, 0.98)' --weight-decay 0.01 --clip-norm 0.0 \
|
||||
--lr 0.0005 --lr-scheduler inverse_sqrt --warmup-updates 4000 --warmup-init-lr 1e-07 \
|
||||
--tokens-per-sample 512 --sample-break-mode none \
|
||||
--max-tokens 2048 --update-freq 16 \
|
||||
--fp16 \
|
||||
--max-update 50000
|
||||
```
|
||||
|
||||
If you run out of memory, try reducing `--max-tokens` (max number of tokens per
|
||||
batch) or `--tokens-per-sample` (max sequence length). You can also adjust
|
||||
`--update-freq` to accumulate gradients and simulate training on a different
|
||||
number of GPUs.
|
||||
|
||||
### 3) Evaluate
|
||||
|
||||
```bash
|
||||
fairseq-eval-lm data-bin/wikitext-103 \
|
||||
--path checkpoints/transformer_wiki103/checkpoint_best.pt \
|
||||
--batch-size 2 \
|
||||
--tokens-per-sample 512 \
|
||||
--context-window 400
|
||||
# | Evaluated 245569 tokens in 56.1s (4379.02 tokens/s)
|
||||
# | Loss: 3.4164, Perplexity: 30.46
|
||||
```
|
||||
|
||||
*Note:* The `--context-window` option controls how much context is provided to
|
||||
each token when computing perplexity. When the window size is 0, the dataset is
|
||||
chunked into segments of length 512 and perplexity is computed over each segment
|
||||
normally. However, this results in worse (higher) perplexity since tokens that
|
||||
appear earlier in each segment have less conditioning. When the maximum window
|
||||
size is used (511 in this case), then we compute perplexity for each token
|
||||
fully conditioned on 511 tokens of context. This slows down evaluation
|
||||
significantly, since we must run a separate forward pass for every token in the
|
||||
dataset, but results in better (lower) perplexity.
|
||||
|
||||
|
||||
## Convolutional language models
|
||||
|
||||
Please see the [convolutional LM README](README.conv.md) for instructions on
|
||||
training convolutional language models.
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
# Adapted from https://github.com/facebookresearch/MIXER/blob/master/prepareData.sh
|
||||
|
||||
URLS=(
|
||||
"https://s3.amazonaws.com/research.metamind.io/wikitext/wikitext-103-v1.zip"
|
||||
)
|
||||
FILES=(
|
||||
"wikitext-103-v1.zip"
|
||||
)
|
||||
|
||||
for ((i=0;i<${#URLS[@]};++i)); do
|
||||
file=${FILES[i]}
|
||||
if [ -f $file ]; then
|
||||
echo "$file already exists, skipping download"
|
||||
else
|
||||
url=${URLS[i]}
|
||||
wget "$url"
|
||||
if [ -f $file ]; then
|
||||
echo "$url successfully downloaded."
|
||||
else
|
||||
echo "$url not successfully downloaded."
|
||||
exit -1
|
||||
fi
|
||||
if [ ${file: -4} == ".tgz" ]; then
|
||||
tar zxvf $file
|
||||
elif [ ${file: -4} == ".tar" ]; then
|
||||
tar xvf $file
|
||||
elif [ ${file: -4} == ".zip" ]; then
|
||||
unzip $file
|
||||
fi
|
||||
fi
|
||||
done
|
||||
cd ..
|
||||
@@ -0,0 +1,77 @@
|
||||
# Deep Transformers with Latent Depth (Li et al., 2020)
|
||||
|
||||
[https://arxiv.org/abs/2009.13102](https://arxiv.org/abs/2009.13102).
|
||||
|
||||
## Introduction
|
||||
|
||||
We present a probabilistic framework to automatically learn which layer(s) to use by learning the posterior distributions of layer selection. As an extension of this framework, we propose a novel method to train one shared Transformer network for multilingual machine translation with different layer selection posteriors for each language pair.
|
||||
|
||||
## Training a multilingual model with latent depth
|
||||
|
||||
Below is an example of training with latent depth in decoder for one-to-many (O2M) related languages. We use the same preprocessed (numberized and binarized) TED8 dataset as in [Balancing Training for Multilingual Neural Machine Translation (Wang et al., 2020)](https://github.com/cindyxinyiwang/multiDDS), which could be generated by [the script](https://github.com/cindyxinyiwang/multiDDS/blob/multiDDS/util_scripts/prepare_multilingual_data.sh) the author provided.
|
||||
```bash
|
||||
lang_pairs_str="eng-aze,eng-bel,eng-ces,eng-glg,eng-por,eng-rus,eng-slk,eng-tur"
|
||||
databin_dir=<path to binarized data>
|
||||
|
||||
fairseq-train ${databin_dir} \
|
||||
--user-dir examples/latent_depth/latent_depth_src \
|
||||
--lang-pairs "${lang_pairs_str}" \
|
||||
--arch multilingual_transformer_iwslt_de_en \
|
||||
--task multilingual_translation_latent_depth \
|
||||
--criterion label_smoothed_cross_entropy --label-smoothing 0.1 \
|
||||
--share-encoders \
|
||||
--share-decoders \
|
||||
--decoder-langtok \
|
||||
--share-decoder-input-output-embed \
|
||||
--dropout 0.3 --attention-dropout 0.3 \
|
||||
--optimizer adam --adam-eps 1e-06 --adam-betas '(0.9, 0.98)' \
|
||||
--lr-scheduler inverse_sqrt --stop-min-lr 1e-9 --warmup-init-lr 1e-7 --warmup-updates 8000 \
|
||||
--max-tokens 4096 --update-freq 1 \
|
||||
--lr 0.0015 \
|
||||
--clip-norm 1.0 \
|
||||
--seed 2 \
|
||||
--ddp-backend=no_c10d \
|
||||
--encoder-layers 12 \
|
||||
--decoder-layers 24 \
|
||||
--decoder-latent-layer \
|
||||
--sparsity-weight 0.1 \
|
||||
--anneal-updates 5000 \
|
||||
--soft-update 500 \
|
||||
--target-layers 12 \
|
||||
--share-weight 0.1
|
||||
```
|
||||
## Inference command
|
||||
|
||||
```bash
|
||||
lang_pairs_str="eng-aze,eng-bel,eng-ces,eng-glg,eng-por,eng-rus,eng-slk,eng-tur"
|
||||
databin_dir=<path to binarized data>
|
||||
model_path=<path to checkpoint>
|
||||
src_lang=<source language to translate from>
|
||||
tgt_lang=<target language to translate to>
|
||||
gen_data=<name of data split, e.g. valid, test, etc>
|
||||
|
||||
fairseq-generate ${databin_dir} \
|
||||
--path ${model_path} \
|
||||
--task multilingual_translation_latent_depth \
|
||||
--decoder-latent-layer \
|
||||
--lang-pairs "${lang_pairs_str}" \
|
||||
-s ${src_lang} -t ${tgt_lang} \
|
||||
--gen-subset $gen_data \
|
||||
--scoring sacrebleu \
|
||||
--remove-bpe 'sentencepiece' \
|
||||
--lenpen 1.0 \
|
||||
--beam 5 \
|
||||
--decoder-langtok \
|
||||
--max-tokens 4096
|
||||
```
|
||||
|
||||
|
||||
## Citation
|
||||
```bibtex
|
||||
@article{li2020deep,
|
||||
title={Deep Transformers with Latent Depth},
|
||||
author={Li, Xian and Stickland, Asa Cooper and Tang, Yuqing and Kong, Xiang},
|
||||
journal={arXiv preprint arXiv:2009.13102},
|
||||
year={2020}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from . import multilingual_translation_latent_depth # noqa
|
||||
from .loss import latent_depth # noqa
|
||||
from .models import latent_multilingual_transformer # noqa
|
||||
from .modules import latent_layers # noqa
|
||||
@@ -0,0 +1,99 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import math
|
||||
|
||||
import torch
|
||||
from torch.nn.modules.loss import _Loss
|
||||
|
||||
|
||||
class LatentLayersKLLoss(_Loss):
|
||||
def __init__(self, args):
|
||||
super().__init__()
|
||||
self.args = args
|
||||
|
||||
def forward(self, layer_samples, lang_idx, update_num, sample_size):
|
||||
prior = self.args.prior
|
||||
samples = layer_samples[lang_idx]
|
||||
eps = 1e-7
|
||||
if prior == "uniform":
|
||||
# uniform prior
|
||||
kl_loss = (samples * (torch.log(samples + eps) - math.log(0.5))).sum(-1)
|
||||
elif prior == "agged_posterior":
|
||||
# aggregated posterior
|
||||
y_t = torch.stack([x.detach() for x in layer_samples], dim=0)
|
||||
agged_q = torch.sum(y_t, dim=0)
|
||||
row_norm = agged_q.sum(-1)
|
||||
normed_agg_q = agged_q / row_norm
|
||||
kl_loss = (
|
||||
samples * (torch.log(samples + eps) - torch.log(normed_agg_q + eps))
|
||||
).sum(-1)
|
||||
else:
|
||||
raise NotImplementedError("The specified prior is not implemented.")
|
||||
|
||||
# normalized by number of layers
|
||||
kl_loss /= layer_samples[0].size()[0]
|
||||
kl_weight = min(
|
||||
self.args.sparsity_weight,
|
||||
(update_num - self.args.soft_update)
|
||||
* self.args.sparsity_weight
|
||||
/ self.args.anneal_updates,
|
||||
)
|
||||
kl_loss *= kl_weight * sample_size
|
||||
return kl_loss
|
||||
|
||||
|
||||
class LatentLayersSparsityLoss(_Loss):
|
||||
def __init__(self, args):
|
||||
super().__init__()
|
||||
self.args = args
|
||||
|
||||
def is_valid(self, update_num):
|
||||
if self.args.target_layers <= 0:
|
||||
return False
|
||||
return update_num > (self.args.soft_update + self.args.anneal_updates)
|
||||
|
||||
def forward(self, layer_samples_list, update_num, sample_size):
|
||||
batch_loss = 0
|
||||
share_loss = 0
|
||||
global_sparsity_loss = 0
|
||||
layer_samples = torch.stack(layer_samples_list, dim=0)
|
||||
if (
|
||||
self.args.target_layers > 0 or self.args.share_weight > 0
|
||||
) and update_num > (self.args.soft_update + self.args.anneal_updates):
|
||||
# anneal sparsity weight
|
||||
if update_num < (self.args.anneal_updates + self.args.soft_update):
|
||||
weight_anneal = 0
|
||||
elif update_num < (2 * self.args.anneal_updates + self.args.soft_update):
|
||||
weight_anneal = (
|
||||
(update_num - self.args.soft_update - self.args.anneal_updates)
|
||||
* self.args.share_weight
|
||||
/ self.args.anneal_updates
|
||||
)
|
||||
else:
|
||||
weight_anneal = 1
|
||||
# compute ratio among languages
|
||||
layer_utilization = torch.sum(layer_samples, dim=0)
|
||||
layer_utilization /= layer_samples.size()[0]
|
||||
if self.args.share_weight > 0:
|
||||
# encouraging sharing across languages
|
||||
share_loss = sum(
|
||||
-1.0 * v * math.log(v) for v in layer_utilization if v > 0
|
||||
)
|
||||
batch_loss += (
|
||||
weight_anneal * self.args.share_weight * sample_size * share_loss
|
||||
)
|
||||
if self.args.target_layers > 0:
|
||||
# computed expected number of layers selected
|
||||
expeted_layers = sum(layer_utilization)
|
||||
# compute l2 loss wrt target number of layers
|
||||
global_sparsity_loss = (expeted_layers - self.args.target_layers) ** 2
|
||||
batch_loss += (
|
||||
weight_anneal
|
||||
* self.args.share_weight
|
||||
* sample_size
|
||||
* global_sparsity_loss
|
||||
)
|
||||
return batch_loss
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from fairseq.models import register_model, register_model_architecture
|
||||
from fairseq.models.multilingual_transformer import MultilingualTransformerModel
|
||||
from fairseq.models.transformer import (
|
||||
TransformerDecoder,
|
||||
TransformerEncoder,
|
||||
base_architecture,
|
||||
)
|
||||
|
||||
from .latent_transformer import LatentTransformerDecoder, LatentTransformerEncoder
|
||||
|
||||
|
||||
@register_model("latent_multilingual_transformer")
|
||||
class LatentMultilingualTransformerModel(MultilingualTransformerModel):
|
||||
"""A variant of standard multilingual Transformer models which encoder and/or
|
||||
decoders supports latent depth, as is in "Deep Transformer with Latent Depth"
|
||||
(https://arxiv.org/abs/2009.13102).
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def add_args(parser):
|
||||
"""Add model-specific arguments to the parser."""
|
||||
MultilingualTransformerModel.add_args(parser)
|
||||
parser.add_argument(
|
||||
'--soft-select',
|
||||
action='store_true',
|
||||
help='use soft samples in training an inference',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--sampling-tau',
|
||||
type=float,
|
||||
default=5.,
|
||||
help='sampling temperature',
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_module_class(cls, is_encoder, args, lang_dict, embed_tokens, langs):
|
||||
if is_encoder:
|
||||
if hasattr(args, "encoder_latent_layer") and args.encoder_latent_layer:
|
||||
return LatentTransformerEncoder(
|
||||
args, lang_dict, embed_tokens, num_logits=len(langs)
|
||||
)
|
||||
else:
|
||||
return TransformerEncoder(args, lang_dict, embed_tokens)
|
||||
else:
|
||||
if hasattr(args, "decoder_latent_layer") and args.decoder_latent_layer:
|
||||
return LatentTransformerDecoder(
|
||||
args, lang_dict, embed_tokens, num_logits=len(langs)
|
||||
)
|
||||
else:
|
||||
return TransformerDecoder(args, lang_dict, embed_tokens)
|
||||
|
||||
|
||||
@register_model_architecture(
|
||||
"latent_multilingual_transformer", "latent_multilingual_transformer"
|
||||
)
|
||||
def latent_multilingual_architecture(args):
|
||||
args.encoder_embed_dim = getattr(args, "encoder_embed_dim", 512)
|
||||
args.encoder_ffn_embed_dim = getattr(args, "encoder_ffn_embed_dim", 1024)
|
||||
args.encoder_attention_heads = getattr(args, "encoder_attention_heads", 4)
|
||||
args.encoder_layers = getattr(args, "encoder_layers", 12)
|
||||
args.decoder_embed_dim = getattr(args, "decoder_embed_dim", 512)
|
||||
args.decoder_ffn_embed_dim = getattr(args, "decoder_ffn_embed_dim", 1024)
|
||||
args.decoder_attention_heads = getattr(args, "decoder_attention_heads", 4)
|
||||
args.decoder_layers = getattr(args, "decoder_layers", 24)
|
||||
args.share_encoders = getattr(args, "share_encoders", True)
|
||||
args.share_decoders = getattr(args, "share_decoders", True)
|
||||
args.share_encoder_embeddings = getattr(args, "share_encoder_embeddings", True)
|
||||
args.share_decoder_embeddings = getattr(args, "share_decoder_embeddings", True)
|
||||
|
||||
base_architecture(args)
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import torch.nn as nn
|
||||
from fairseq.models.fairseq_encoder import EncoderOut
|
||||
from fairseq.models.transformer import TransformerDecoder, TransformerEncoder
|
||||
from fairseq.modules import TransformerDecoderLayer, TransformerEncoderLayer
|
||||
from torch import Tensor
|
||||
|
||||
from ..modules.latent_layers import LayerSelect
|
||||
|
||||
|
||||
class LatentTransformerEncoder(TransformerEncoder):
|
||||
"""Latent depth (https://arxiv.org/abs/2009.13102) implemented in
|
||||
TransformerEncoder.
|
||||
"""
|
||||
|
||||
def __init__(self, args, dictionary, embed_tokens, num_logits=1):
|
||||
self.num_logits = num_logits
|
||||
self.num_layers = args.encoder_layers
|
||||
super().__init__(args, dictionary, embed_tokens)
|
||||
self.layer_select = LayerSelect(
|
||||
num_layers=self.num_layers,
|
||||
num_logits=self.num_logits,
|
||||
soft_select=getattr(args, "soft_select", False),
|
||||
sampling_tau=getattr(args, "sampling_tau", 5.),
|
||||
)
|
||||
self.lang_idx = None
|
||||
self.layers = nn.ModuleList(
|
||||
[self._build_encoder_layer(args, idx) for idx in range(args.encoder_layers)]
|
||||
)
|
||||
|
||||
def set_lang_idx(self, lang_idx):
|
||||
self.lang_idx = lang_idx
|
||||
|
||||
def _build_encoder_layer(self, args, idx=None):
|
||||
return LatentTransformerEncoderLayer(args, idx, layer_select=self.layer_select)
|
||||
|
||||
def forward(self, src_tokens, src_lengths, return_all_hiddens: bool = False):
|
||||
self.layer_select.sample(self.lang_idx)
|
||||
return super().forward(src_tokens, src_lengths, return_all_hiddens)
|
||||
|
||||
|
||||
class LatentTransformerEncoderLayer(TransformerEncoderLayer):
|
||||
"""Encoder layer with each (non_residual) block weighted by samples of Bernouli
|
||||
or Gumbel Signmoid samples.
|
||||
|
||||
Args:
|
||||
args (argparse.Namespace): parsed command-line arguments from standard
|
||||
TransformerEncoderLayer.
|
||||
idx (int): layer index (used to retrieve samples).
|
||||
layer_select (LayerSelect, optional): instance of LayerSelect module with logits
|
||||
parameters and sampling method.
|
||||
"""
|
||||
|
||||
def __init__(self, args, idx, layer_select=None):
|
||||
super().__init__(args)
|
||||
self.idx = idx
|
||||
self.layer_select = layer_select
|
||||
|
||||
def residual_connection(self, x, residual):
|
||||
return residual + x * self.layer_select(self.idx)
|
||||
|
||||
|
||||
class LatentTransformerDecoder(TransformerDecoder):
|
||||
"""Latent depth (https://arxiv.org/abs/2009.13102) implemented in
|
||||
TransformerDecoder.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, args, dictionary, embed_tokens, no_encoder_attn=False, num_logits=1
|
||||
):
|
||||
self.num_logits = num_logits
|
||||
self.num_layers = args.decoder_layers
|
||||
super().__init__(
|
||||
args, dictionary, embed_tokens, no_encoder_attn=no_encoder_attn
|
||||
)
|
||||
self.layer_select = LayerSelect(
|
||||
num_layers=self.num_layers,
|
||||
num_logits=self.num_logits,
|
||||
soft_select=getattr(args, "soft_select", False),
|
||||
sampling_tau=getattr(args, "sampling_tau", 5.),
|
||||
)
|
||||
self.lang_idx = None
|
||||
self.layers = nn.ModuleList(
|
||||
[
|
||||
self._build_decoder_layer(args, no_encoder_attn, idx)
|
||||
for idx in range(args.decoder_layers)
|
||||
]
|
||||
)
|
||||
|
||||
def set_lang_idx(self, lang_idx):
|
||||
self.lang_idx = lang_idx
|
||||
|
||||
def _build_decoder_layer(self, args, no_encoder_attn=False, idx=None):
|
||||
return LatentTransformerDecoderLayer(
|
||||
args, idx, layer_select=self.layer_select, no_encoder_attn=no_encoder_attn
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
prev_output_tokens,
|
||||
encoder_out: Optional[EncoderOut] = None,
|
||||
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None,
|
||||
features_only: bool = False,
|
||||
alignment_layer: Optional[int] = None,
|
||||
alignment_heads: Optional[int] = None,
|
||||
src_lengths: Optional[Any] = None,
|
||||
return_all_hiddens: bool = False,
|
||||
):
|
||||
self.layer_select.sample(self.lang_idx)
|
||||
return super().forward(
|
||||
prev_output_tokens=prev_output_tokens,
|
||||
encoder_out=encoder_out,
|
||||
incremental_state=incremental_state,
|
||||
features_only=features_only,
|
||||
alignment_layer=alignment_layer,
|
||||
src_lengths=src_lengths,
|
||||
return_all_hiddens=return_all_hiddens,
|
||||
)
|
||||
|
||||
|
||||
class LatentTransformerDecoderLayer(TransformerDecoderLayer):
|
||||
"""Decoder layer with each (non_residual) block weighted by samples of Bernouli
|
||||
or Gumbel Signmoid samples.
|
||||
|
||||
Args:
|
||||
args (argparse.Namespace): parsed command-line arguments from standard
|
||||
TransformerDecoderLayer.
|
||||
idx (int): layer index (used to retrieve samples).
|
||||
layer_select (LayerSelect, optional): instance of LayerSelect module with logits
|
||||
parameters and sampling method.
|
||||
no_encoder_attn (bool, optional): whether to attend to encoder outputs
|
||||
(default: False).
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
args,
|
||||
idx,
|
||||
layer_select=None,
|
||||
no_encoder_attn=False,
|
||||
add_bias_kv=False,
|
||||
add_zero_attn=False,
|
||||
):
|
||||
super().__init__(args, no_encoder_attn, add_bias_kv, add_zero_attn)
|
||||
self.idx = idx
|
||||
self.layer_select = layer_select
|
||||
|
||||
def residual_connection(self, x, residual):
|
||||
return residual + x * self.layer_select(self.idx)
|
||||
@@ -0,0 +1,75 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class LayerSelect(nn.Module):
|
||||
"""Compute samples (from a Gumbel-Sigmoid distribution) which is used as
|
||||
either (soft) weighting or (hard) selection of residual connection.
|
||||
https://arxiv.org/abs/2009.13102
|
||||
"""
|
||||
def __init__(self, num_layers, num_logits, soft_select=False, sampling_tau=5.):
|
||||
super(LayerSelect, self).__init__()
|
||||
self.layer_logits = torch.nn.Parameter(
|
||||
torch.Tensor(num_logits, num_layers),
|
||||
requires_grad=True,
|
||||
)
|
||||
self.hard_select = not soft_select
|
||||
self.tau = sampling_tau
|
||||
self.detach_grad = False
|
||||
self.layer_samples = [None] * num_logits
|
||||
|
||||
def sample(self, logit_idx):
|
||||
"""To leverage the efficiency of distributed training, samples for all
|
||||
layers are computed at once for each logit_idx. Logits are parameters
|
||||
learnt independent of each other.
|
||||
|
||||
Args:
|
||||
logit_idx: The index of logit parameters used for sampling.
|
||||
"""
|
||||
assert logit_idx is not None
|
||||
self.samples = self._gumbel_sigmoid(
|
||||
self.layer_logits[logit_idx, :].detach()
|
||||
if self.detach_grad
|
||||
else self.layer_logits[logit_idx, :],
|
||||
dim=-1,
|
||||
tau=self.tau,
|
||||
hard=self.hard_select,
|
||||
)
|
||||
self.layer_samples[logit_idx] = self.samples
|
||||
|
||||
def forward(self, i):
|
||||
sample = self.samples[i]
|
||||
return sample
|
||||
|
||||
def _gumbel_sigmoid(
|
||||
self, logits, tau=1, hard=False, eps=1e-10, dim=-1, threshold=0.5
|
||||
):
|
||||
# ~Gumbel(0,1)
|
||||
gumbels1 = (
|
||||
-torch.empty_like(logits, memory_format=torch.legacy_contiguous_format)
|
||||
.exponential_()
|
||||
.log()
|
||||
)
|
||||
gumbels2 = (
|
||||
-torch.empty_like(logits, memory_format=torch.legacy_contiguous_format)
|
||||
.exponential_()
|
||||
.log()
|
||||
)
|
||||
# Difference of two gumbels because we apply a sigmoid
|
||||
gumbels1 = (logits + gumbels1 - gumbels2) / tau
|
||||
y_soft = gumbels1.sigmoid()
|
||||
if hard:
|
||||
# Straight through.
|
||||
y_hard = torch.zeros_like(
|
||||
logits, memory_format=torch.legacy_contiguous_format
|
||||
).masked_fill(y_soft > threshold, 1.0)
|
||||
ret = y_hard - y_soft.detach() + y_soft
|
||||
else:
|
||||
# Reparametrization trick.
|
||||
ret = y_soft
|
||||
return ret
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
from fairseq.tasks import register_task
|
||||
from fairseq.tasks.multilingual_translation import MultilingualTranslationTask
|
||||
|
||||
from .loss.latent_depth import LatentLayersKLLoss, LatentLayersSparsityLoss
|
||||
|
||||
|
||||
@register_task("multilingual_translation_latent_depth")
|
||||
class MultilingualTranslationTaskLatentDepth(MultilingualTranslationTask):
|
||||
"""A task for multiple translation with latent depth.
|
||||
|
||||
See `"Deep Transformer with Latent Depth"
|
||||
(Li et al., 2020) <https://arxiv.org/pdf/2009.13102.pdf>`_.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def add_args(parser):
|
||||
"""Add task-specific arguments to the parser."""
|
||||
# fmt: off
|
||||
MultilingualTranslationTask.add_args(parser)
|
||||
parser.add_argument('--encoder-latent-layer', action='store_true', help='latent layer selection in encoder')
|
||||
parser.add_argument('--decoder-latent-layer', action='store_true', help='latent layer selection in decoder')
|
||||
parser.add_argument('--target-layers', default=-1, type=int,
|
||||
help='number of effective layers to learn; -1 means no constraint')
|
||||
parser.add_argument('--sparsity-weight', default=0.0, type=float,
|
||||
help='weight for sparsity loss')
|
||||
parser.add_argument('--share-weight', default=0.0, type=float,
|
||||
help='weight for sharing loss')
|
||||
parser.add_argument('--soft-update', default=1, type=int,
|
||||
help='number of updates with soft sampling')
|
||||
parser.add_argument('--anneal-updates', default=1, type=int,
|
||||
help='number of updates to anneal the KL loss weight')
|
||||
parser.add_argument('--prior', default="uniform", type=str,
|
||||
help='prior used for computing KL loss')
|
||||
# fmt: on
|
||||
|
||||
def __init__(self, args, dicts, training):
|
||||
super().__init__(args, dicts, training)
|
||||
self.src_langs, self.tgt_langs = zip(
|
||||
*[(lang.split("-")[0], lang.split("-")[1]) for lang in args.lang_pairs]
|
||||
)
|
||||
if self.training and self.encoder_latent_layer:
|
||||
assert self.args.share_encoders
|
||||
if self.training and self.decoder_latent_layer:
|
||||
assert self.args.share_decoders
|
||||
if training or self.encoder_latent_layer or self.decoder_latent_layer:
|
||||
self.lang_pairs = args.lang_pairs
|
||||
else:
|
||||
self.lang_pairs = ["{}-{}".format(args.source_lang, args.target_lang)]
|
||||
self.eval_lang_pairs = self.lang_pairs
|
||||
self.model_lang_pairs = self.lang_pairs
|
||||
if self.training and (self.encoder_latent_layer or self.decoder_latent_layer):
|
||||
self.kl_loss = LatentLayersKLLoss(self.args)
|
||||
self.sparsity_loss = LatentLayersSparsityLoss(self.args)
|
||||
|
||||
def _per_lang_pair_train_loss(
|
||||
self, lang_pair, model, update_num, criterion, sample, optimizer, ignore_grad
|
||||
):
|
||||
src, tgt = lang_pair.split("-")
|
||||
if self.encoder_latent_layer:
|
||||
src_lang_idx = self.src_lang_idx_dict[src]
|
||||
model.models[lang_pair].encoder.set_lang_idx(src_lang_idx)
|
||||
model.models[lang_pair].encoder.layer_select.hard_select = (
|
||||
update_num > self.args.soft_update
|
||||
)
|
||||
if self.decoder_latent_layer:
|
||||
tgt_lang_idx = self.tgt_lang_idx_dict[tgt]
|
||||
model.models[lang_pair].decoder.set_lang_idx(tgt_lang_idx)
|
||||
model.models[lang_pair].decoder.layer_select.hard_select = (
|
||||
update_num > self.args.soft_update
|
||||
)
|
||||
|
||||
loss, sample_size, logging_output = criterion(
|
||||
model.models[lang_pair], sample[lang_pair]
|
||||
)
|
||||
if self.encoder_latent_layer:
|
||||
none_samples = sum(
|
||||
1 if x is None else 0
|
||||
for x in model.models[lang_pair].encoder.layer_select.layer_samples
|
||||
)
|
||||
if none_samples == 0 or self.args.prior != "agged_posterior":
|
||||
loss += self.kl_loss(
|
||||
model.models[lang_pair].encoder.layer_select.layer_samples,
|
||||
src_lang_idx,
|
||||
update_num,
|
||||
sample_size,
|
||||
)
|
||||
if self.decoder_latent_layer:
|
||||
none_samples = sum(
|
||||
1 if x is None else 0
|
||||
for x in model.models[lang_pair].decoder.layer_select.layer_samples
|
||||
)
|
||||
if none_samples == 0 or self.args.prior != "agged_posterior":
|
||||
loss += self.kl_loss(
|
||||
model.models[lang_pair].decoder.layer_select.layer_samples,
|
||||
tgt_lang_idx,
|
||||
update_num,
|
||||
sample_size,
|
||||
)
|
||||
if ignore_grad:
|
||||
loss *= 0
|
||||
|
||||
if hasattr(self, "sparsity_loss") and self.sparsity_loss.is_valid(update_num):
|
||||
# need to retain the graph if sparsity loss needs to be added
|
||||
loss.backward(retain_graph=True)
|
||||
else:
|
||||
optimizer.backward(loss)
|
||||
|
||||
return loss, sample_size, logging_output
|
||||
|
||||
def train_step(
|
||||
self, sample, model, criterion, optimizer, update_num, ignore_grad=False
|
||||
):
|
||||
agg_loss, agg_sample_size, agg_logging_output = super().train_step(
|
||||
sample, model, criterion, optimizer, update_num, ignore_grad
|
||||
)
|
||||
# compute auxiliary loss from layere sparsity, based on all samples from all languages
|
||||
if hasattr(self, "sparsity_loss") and self.sparsity_loss.is_valid(update_num):
|
||||
sparsity_loss = 0
|
||||
if self.encoder_latent_layer:
|
||||
sparsity_loss += self.sparsity_loss(
|
||||
next(
|
||||
iter(model.models.values())
|
||||
).encoder.layer_select.layer_samples,
|
||||
update_num,
|
||||
agg_sample_size,
|
||||
)
|
||||
if self.decoder_latent_layer:
|
||||
sparsity_loss += self.sparsity_loss(
|
||||
next(
|
||||
iter(model.models.values())
|
||||
).decoder.layer_select.layer_samples,
|
||||
update_num,
|
||||
agg_sample_size,
|
||||
)
|
||||
if sparsity_loss > 0:
|
||||
optimizer.backward(sparsity_loss)
|
||||
return agg_loss, agg_sample_size, agg_logging_output
|
||||
|
||||
def _per_lang_pair_valid_loss(self, lang_pair, model, criterion, sample):
|
||||
src, tgt = lang_pair.split("-")
|
||||
if self.encoder_latent_layer:
|
||||
src_lang_idx = self.src_lang_idx_dict[src]
|
||||
model.models[lang_pair].encoder.set_lang_idx(src_lang_idx)
|
||||
if self.decoder_latent_layer:
|
||||
tgt_lang_idx = self.tgt_lang_idx_dict[tgt]
|
||||
model.models[lang_pair].decoder.set_lang_idx(tgt_lang_idx)
|
||||
loss, sample_size, logging_output = criterion(
|
||||
model.models[lang_pair], sample[lang_pair]
|
||||
)
|
||||
return loss, sample_size, logging_output
|
||||
|
||||
def inference_step(
|
||||
self, generator, models, sample, prefix_tokens=None, constraints=None
|
||||
):
|
||||
if self.encoder_latent_layer or self.decoder_latent_layer:
|
||||
for model in models:
|
||||
if self.encoder_latent_layer:
|
||||
assert model.encoder.layer_select is not None
|
||||
src_lang_idx = self.src_lang_idx_dict[self.args.source_lang]
|
||||
model.encoder.set_lang_idx(src_lang_idx)
|
||||
if self.decoder_latent_layer:
|
||||
assert model.decoder.layer_select is not None
|
||||
tgt_lang_idx = self.tgt_lang_idx_dict[self.args.target_lang]
|
||||
model.decoder.set_lang_idx(tgt_lang_idx)
|
||||
return super().inference_step(
|
||||
generator, models, sample, prefix_tokens, constraints
|
||||
)
|
||||
|
||||
@property
|
||||
def encoder_latent_layer(self):
|
||||
return (
|
||||
hasattr(self.args, "encoder_latent_layer")
|
||||
and self.args.encoder_latent_layer
|
||||
)
|
||||
|
||||
@property
|
||||
def decoder_latent_layer(self):
|
||||
return (
|
||||
hasattr(self.args, "decoder_latent_layer")
|
||||
and self.args.decoder_latent_layer
|
||||
)
|
||||
|
||||
@property
|
||||
def src_lang_idx_dict(self):
|
||||
return {lang: lang_idx for lang_idx, lang in enumerate(self.src_langs)}
|
||||
|
||||
@property
|
||||
def tgt_lang_idx_dict(self):
|
||||
return {lang: lang_idx for lang_idx, lang in enumerate(self.tgt_langs)}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user