|
| 1 | +## 简介 |
| 2 | + |
| 3 | +`torchtext` 可以方便的完成模型训练的前期数据预处理工作,常见功能有: |
| 4 | + |
| 5 | +- 将句子转化成分词列表 |
| 6 | +- 构建当前语料库中单词的词汇表,实现word和id之间的映射 |
| 7 | +- 将数据集转化为一个一个的batch,训练时直接使用 |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | +## 基本原理 |
| 14 | + |
| 15 | + |
| 16 | + |
| 17 | +- 三大核心要素:`field`、`dataset`、`iterator` |
| 18 | + - `field`定义指定字段的处理方法 |
| 19 | + - `dataset`定义语料库 |
| 20 | + - `iterator`定义训练和测试模型时候的迭代器,里面是一个一个的`batch` |
| 21 | + |
| 22 | + |
| 23 | + |
| 24 | +## 代码分析 |
| 25 | + |
| 26 | +#### field |
| 27 | + |
| 28 | +- 一般情况下定义两个field:`text_field`和`label_field`分别定义文本域和标签域的处理方法,常见的属性为: |
| 29 | + |
| 30 | + - lower = False:是否把数据转化为小写 |
| 31 | + - sequential = True:是否把数据表示成序列,如果是False, 不能使用分词 |
| 32 | + - 特别注意`pad_token='<pad>'`和`unk_token='<unk>'`,在label中不建议使用,可以定义为`None` |
| 33 | + ```python |
| 34 | + def word_cut(text): |
| 35 | + text = re.compile(r'[^A-Za-z0-9\u4e00-\u9fa5]').sub(' ', text) |
| 36 | + # 将非中文字符、非a-z, 非A-Z,非0-9 全部替换为' ' |
| 37 | + return [word.strip() for word in jieba.cut(text) if word.strip()] |
| 38 | + |
| 39 | + |
| 40 | + text_field = data.Field(lower=True, tokenize=word_cut) |
| 41 | + label_field = data.Field(sequential=False, unk_token=None, pad_token=None) |
| 42 | + ``` |
| 43 | + |
| 44 | +- 之后传递`text_field`和`label_field`的时候是传递了对象的引用,所以会直接修改两个`field`对象 |
| 45 | + |
| 46 | + |
| 47 | + |
| 48 | +#### dataset |
| 49 | + |
| 50 | +- dataset定义语料库,将文本转换为一个个example对象,准备好训练集和测试集之后,可以使用内置的split函数划分训练集和测试集,其中split参数如下 |
| 51 | + |
| 52 | + - path:数据所在的路径 |
| 53 | + - format:数据的格式,tsv为 `\t` 分割的数据集 |
| 54 | + - skip_header:是否跳过第一行 |
| 55 | + - train、test:训练集和测试集文件名 |
| 56 | + - fields:`list(tuple(string, field))` ,string为文件中每一列的列名,field为之前创建的field对象,定义了该个字段的处理方法,None表示忽略这个字段 |
| 57 | + |
| 58 | + ```python |
| 59 | + train_dataset, test_dataset = data.TabularDataset.splits( |
| 60 | + path='data', format='tsv', skip_header=True, |
| 61 | + train='train.tsv', test='test.tsv', |
| 62 | + fields=[ |
| 63 | + ('index', None), |
| 64 | + ('label', label_field), |
| 65 | + ('text', text_field) |
| 66 | + ] |
| 67 | + ) |
| 68 | + ``` |
| 69 | + |
| 70 | + |
| 71 | + |
| 72 | +#### vocab |
| 73 | + |
| 74 | +- 此时完成了将文本转化为语料库的工作,但此时语料库中为 一个分词后的句子 + 一个标签 的形式,还需要处理成模型的输入,需要建立当前字段的语料库,方法是field中定义的build_vocab函数 |
| 75 | + |
| 76 | + - 如果使用与训练词向量,需要在函数中指定vectors参数,该参数是一个Vectors对象 |
| 77 | + |
| 78 | + ```python |
| 79 | + label_field.build_vocab(train_dataset, test_dataset) |
| 80 | + |
| 81 | + if args.static and args.pretrained_name and args.pretrained_path: |
| 82 | + vectors = Vectors(name=args.pretrained_name, cache=args.pretrained_path) |
| 83 | + text_field.build_vocab(train_dataset, test_dataset, vectors=vectors) |
| 84 | + else: |
| 85 | + text_field.build_vocab(train_dataset, test_dataset) |
| 86 | + ``` |
| 87 | + |
| 88 | + |
| 89 | + |
| 90 | +- `build_vocab` 完成之后,对应的field之中会创建一个vocab对象,为词汇表 |
| 91 | + - `itos`:一个list,获取对应index的word,`word = itos[index]` |
| 92 | + - `stoi`:一个dict,获取对应word的index,`index = stoi[word]` |
| 93 | + - `len(field.vocab)`:返回词汇表的长度 |
| 94 | + |
| 95 | +- 模型中可以使用如下代码定义部分层 |
| 96 | + |
| 97 | + ```python |
| 98 | + self.embedding = nn.Embedding(len(text_field.vocab), embedding_dimension) |
| 99 | + output_size = len(label_field.vocab) |
| 100 | + self.fc = nn.Linear(input_size, output_size) |
| 101 | + ``` |
| 102 | + |
| 103 | + - 需要注意,因为field在创建的时候默认的`pad_token='<pad>', unk_token='<unk>'`,如果不做处理的话 `output_size`中会因为两个默认的token儿变大,所以定义field的时候可以将两个设置为None,参见 [field](#field) |
| 104 | + |
| 105 | + |
| 106 | + |
| 107 | +#### iterator |
| 108 | + |
| 109 | +- 创建完语料库之后,需要对语料库batch化,使用`Iterator.splits`方法划分训练集和测试集合的迭代器 |
| 110 | + |
| 111 | + ```python |
| 112 | + train_iter, test_iter = data.Iterator.splits( |
| 113 | + (train_dataset, test_dataset), |
| 114 | + batch_sizes=(args.batch_size, len(test_dataset)), |
| 115 | + sort_key=lambda x: len(x.text), |
| 116 | + **kwargs # device=-1, repeat=False, shuffle=True |
| 117 | + ) |
| 118 | + ``` |
| 119 | + |
| 120 | + - 第一个tuple指明了划分的两个dataset,两个dataset里面有指向两个field的引用,所以里面iterator里面可以拿到词汇表 |
| 121 | + - batch_sizes指明了两个iterator的batch大小 |
| 122 | + - sort_key用于排序 |
| 123 | + |
| 124 | + |
| 125 | + |
| 126 | +## 使用 |
| 127 | + |
| 128 | +#### 数据集训练 |
| 129 | + |
| 130 | +- 创建完`torchtext`的`iterator`对象之后,iter里面内置了 `__iter(self)__` 函数,使用方法为: |
| 131 | + |
| 132 | + ```python |
| 133 | + for batch in train_iter: |
| 134 | + feature, target = batch.text, batch.label |
| 135 | + feature.data.t_() |
| 136 | + logits = model(feature) |
| 137 | + ``` |
| 138 | + |
| 139 | + |
| 140 | + |
| 141 | +#### 一般分类 |
| 142 | + |
| 143 | +- 模型训练完成之后需要模型在所有的数据上都能时候,而非训练测试两个数据。对于一般的数据处理思路如下: |
| 144 | + |
| 145 | + - 首先将文本分词 |
| 146 | + - **将分词后的文本转化为数值tensor** |
| 147 | + - 将tensor放到模型中,获取预测结果 |
| 148 | + |
| 149 | +- 在 [vocab](#vocab) 中提到了每个field在build_vocab完成之后会创建一个vocab对象,完成string和int之间的转化 |
| 150 | + |
| 151 | + ```python |
| 152 | + # 保存词汇表 |
| 153 | + with open("data/vocab.pkl", "wb") as f: |
| 154 | + pickle.dump(vocab, f) |
| 155 | + |
| 156 | + # 读取词汇表 |
| 157 | + with open("data/vocab.pkl", "rb") as f: |
| 158 | + vocab = pickle.load(f) |
| 159 | + ``` |
| 160 | + |
| 161 | +- 利用vocab对象中的stoi属性获取单词在词汇表中的index |
| 162 | + |
| 163 | + ```python |
| 164 | + text = "外观漂亮,安全性佳,动力够强,油耗够低" |
| 165 | + text = re.compile(r'[^A-Za-z0-9\u4e00-\u9fa5]').sub(' ', text) |
| 166 | + words = [word.strip() for word in jieba.cut(text) if word.strip()] |
| 167 | + indexes = [vocab.stoi[word] for word in words] # list(int) |
| 168 | + ``` |
| 169 | + |
| 170 | + |
| 171 | + |
0 commit comments