# tx_project2
**Repository Path**: jellyzhang123/tx_project2
## Basic Information
- **Project Name**: tx_project2
- **Description**: 智能营销文案
- **Primary Language**: Python
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2022-07-19
- **Last Updated**: 2022-07-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Assignment 1
## Files
#### data/
data_utils.py: Helper functions or classes used in data processing.
process.py: Process a raw dataset into a sample file.
#### model/
config.py: Define configuration parameters.
dataset.py: Define the format of samples used in the model.
evaluate.py: Evaluate the loss in the dev set.
model.py: Define the model.
predict.py: Generate a summary.
test_vocab.py: Testing vocab.
train.py: Train the model.
utils.py: Helper functions or classes used for the model.
vocal.py: Define the vocabulary object.
#### saved_model/
Save the trained model objects here.
#### files/
Place data files here.
#### runs/
Save logs here for TensorboardX.
## TO-DO list:
### 必备资料
为了完成以下任务,我们需要逐步熟悉、掌握Pytorch框架,所以请大家在完成每一个模块时先查阅一下[Pytorch的API文档](https://pytorch.org/docs/stable/index.html),弄清楚要使用的模块是做什么的以及如何使用。此外,模型的具体实现,需要参见论文 [*Get To The Point: Summarization with Pointer-Generator Networks*](https://arxiv.org/pdf/1704.04368.pdf)。
### 模块1: 词典处理
#### model/vocab.py:
##### 任务1: 完成add_words函数。
向词典里加入一个新词,需要完成对word2index、index2word和word2count三个变量的更新。
#### model/dataset.py:
##### 任务2: 完成PairDataset类中的build_vocab函数。
需要实现控制数据集词典的大小(从config.max_vocab_size)读取这一参数。建议使用python的collection模块中的Counter来做,这个数据类型跟dict很像,但有两个好处:
1. 加入新的key时不需要判断是否存在,会自动将其对应的值初始化为0。
2. 可以通过most_common函数来获取数量最多的k个key。
Counter的用法详见https://docs.python.org/2/library/collections.html。
#### model/utils.py:
##### 任务3: 完成source2ids函数。
当我们训练好模型要对测试集进行测试时,测试集中的样本往往会包含OOV tokens。这个函数需要你将在词典中的token映射到相应的index,对于oov tokens则需要记录下来并返回。
##### 任务4: 完成outputids2words函数。
与任务3相反的过程,将token id映射到对应的词,并输出。
#### 测试
```shell
python test_vocab.py
```
###### 示例输出
### 模块2: 模型搭建
#### model/model.py:
##### 任务1: 完成Encoder。
1. 定义embedding层和BiLSTM层。
2. 实现前向传导(输入输出详见代码)。
###### 你可能用到的Pytorch模块:
nn.Embedding
nn.LSTM
##### 任务2: 完成Decoder。
1. 定义embedding层和LSTM层(单向);定义两个线性层(前馈层)W1和W2。
2. 实现前向传导。具体实现参见论文中的公式(4)和(5)。代码中会给出每一个步骤的提示。
###### 你可能用到的Pytorch模块:
nn.Embedding
nn.LSTM
nn.Linear
Tensor.view
torch.cat
nn.functional.softmax
##### 任务3: 完成Attention。
1. 定义三个线性层Wh、Ws和v。维度详见论文中的公式(1)和(2)。
2. 定义前向传导。
a. 处理decoder的隐状态h和c,将二者拼接得到s_t,并处理成合理的shape。
b. 参考论文中的公式(1)和(2),实现attention weights的计算。
c. 由于训练过程中会对batch中的样本进行padding,对于进行了padding的输入我们需要把填充的位置的attention weights给过滤掉(padding mask),然后对剩下位置的attention weights进行归一化。
d. 根据论文中的公式(3)计算context vector (hint: 可以使用torch.bmm)。
###### 你可能用到的Pytorch模块:
nn.Linear
Tensor.expand_as
Tensor.view
Tensor.squeeze
torch.cat
nn.functional.tanh
nn.functional.softmax
torch.bmm
##### 任务4: 完成整个model的前向传导。
1. 对输入序列x进行处理,对于oov的token,需要将他们的index转换成\ token (hint: 可以使用torch.where)。
2. 生成输入序列x的padding mask (hint: 可以使用torch.ne)。
3. 得到encoder的输出和隐状态,并对隐状态进行降维后作为decoder的初始隐状态。
4. 对于每一个time step,以输入序列y的y_t作为输入,y_t+1作为target,计算attention,然后用decoder得到p_vocab,找到target对应的词在p_vocab中对应的概率target_probs (hint: 可以使用torch.gather),然后计算time step t的损失(NLL loss,详见论文公式(6))。然后加上padding mask。
5. 计算整个序列的平均loss,详见论文公式(7)。
6. 计算整个batch的平均loss并返回。
###### 你可能用到的Pytorch模块:
torch.full
torch.ne
Tensor.view
Tensor.squeeze
Tensor.unsqueeze
torch.cat
torch.gather
torch.log
torch.sum
torch.mean
##### 任务5 (optional):
由于我们的encoder用了BiLSTM,而decoder用的是单向的LSTM,使用encoder的输出作为decoder初始隐状态时,需要对encoder的隐状态进行降维。实现的方式可以有多种,可以对两个方向的隐状态简单相加,也可以定义一个前馈层来做这个事。这里我们用一个ReduceState的模块以简单相加的形式来实现,具体见代码。你们可以对这个模块做一些更多的操作,设计自己觉得更为合理的方式来对encoder的隐状态进行处理。
```python
class ReduceState(nn.Module):
"""
Since the encoder has a bidirectional LSTM layer while the decoder has a
unidirectional LSTM layer, we add this module to reduce the hidden states
output by the encoder (merge two directions) before input the hidden states
nto the decoder.
"""
def __init__(self):
super(ReduceState, self).__init__()
def forward(self, hidden):
"""The forward propagation of reduce state module.
Args:
hidden (tuple):
Hidden states of encoder,
each with shape (2, batch_size, hidden_units).
Returns:
tuple:
Reduced hidden states,
each with shape (1, batch_size, hidden_units).
"""
h, c = hidden
h_reduced = torch.sum(h, dim=0, keepdim=True)
c_reduced = torch.sum(c, dim=0, keepdim=True)
return (h_reduced, c_reduced)
```
#### 测试
需完成模块3后进行测试。
### 模块3: 训练
#### model/train.py
##### 任务1: 实现训练过程。
模板如下:
```python
optimizer = None # Choose an optimizer from torch.optim
batch_losses = []
for batch in batches:
model.train() # Sets the module in training mode.
optimizer.zero_grad() # Clear gradients.
batch_loss = model(**params)# Calculate loss for a batch.
batch_losses.append(loss.item())
batch_loss.backward() # Backpropagation.
optimizer.step() # Update weights.
epoch_loss = torch.mean(batch_losses)
```
需要注意的是,论文里的优化方式和训练参数未必适合我们的数据集,大家可以自己尝试选择合适的优化方式和训练参数,推荐使用Adam作为optimizer。
##### 任务2: 在适当的位置实现梯度剪裁。
Hint: 参见torch.nn.utils模块下的clip_grad_norm_函数。
##### 任务3: 用TensorboardX记录训练过程的损失并实现可视化。
Hint: 创建一个SummaryWriter对象,调用add_scalar函数来记录损失,记得写完要调用close函数。详见[TensorboardX](https://tensorboardx.readthedocs.io/en/latest/tensorboard.html)文档。
#### 测试
```shell
python train.py
```
###### 示例输出
terminal
tensorboard
如果确认前面的模块实现无误,可以训练你的模型,模型需要长时间运行,为了防止其意外中断,可以使用以下命令,让模型在后台运行,同时又可以实时看到输出。
```shell
nohup python train.py 2>&1 | tee out/train.out &
```
执行后会生成一个文件train.out,训练过程的输出可以在train.out中查看。
### 模块4: 解码
#### model/predict.py:
##### 任务1: 实现Greedy search。
这一块比较简单,跟着代码的提示,用encoder编码输入,传递每一个time step的信息给decoder,计算attention,得到decoder的p_vocab,根据p_vocab选出概率最大的词作为下一个token。
##### 任务2: 实现Beam search。
1. 看懂Beam这一个类需要传递的变量(实现在model/utils.py中)。
2. 完成best_k函数。这里做的事情与greedy search很接近,不过要选出最好的k个token,然后扩展出k个新的beam容器。
3. 完成beam search函数。初始化encoder、attention和decoder的输入,然后对于每一个decode step,对于现有的k个beam,我们分别利用best_k函数来得到各自最佳的k个extended beam,也就是每个decode step我们会得到k*k个新的beam,然后只保留分数最高的k个,作为下一轮需要扩展的k个beam。为了只保留分数最高的k个beam,我们可以用一个堆(heap)来实现,堆的中只保存k个节点,根结点保存分数最低的beam,python实现堆的方法详见https://docs.python.org/2/library/heapq.html。
4. Hint: 用heapq模块时,各种操作(push, pop)需要比较堆中元素的大小,如果以tuple的形式来存储,会从tuple的第一个位置开始比较,如果第一个位置的值相同,会继续比较后面的位置的值。所以建议以(分数,对象id,对象)三元组的形式来存储,其中对象id的作用是快速break ties。
#### 测试
```shell
python predict.py
```
###### 示例输出
```shell
python rouge_eval.py
```
###### 示例输出
####