введение
Недавно я узнал о сверточных нейронных сетях и хочу начать небольшой проект для практики. Набор данных этого проекта поступает из github, а содержание — положительная и отрицательная послепродажная оценка автомобилей. С помощью pytorch, реализовано обучение модели и завершена вторая оценка некоторой оценки в наборе тестов.Классификация.
Принцип: используйте свертку для извлечения характеристик локальных объектов и сбора ключевой информации, аналогичной N-граммам.
1. Предварительная обработка данных
При обработке естественного языка неизбежной темой является вектор слов.Я использую библиотеку инструментов torchtext, чтобы реализовать построение вектора слов.
токенизатор
def tokenizer(text): # create a tokenizer function
regex = re.compile(r'[^\u4e00-\u9fa5aA-Za-z0-9]')
text = regex.sub(' ', text)
return [word for word in jieba.cut(text) if word.strip()]
Сегментатор слов использует библиотеку jieba инструмента сегментации китайских слов для выполнения сегментации слов и возвращает сегментированные слова в виде списка.
удалить стоп-слова
def get_stop_words():
file_object = open('D:\\MyStudy\\program\\text-classification-master\\text-cnn\\data\\stopwords.txt',encoding='UTF-8')
stop_words = []
for line in file_object.readlines():
line = line[:-1]
line = line.strip()
stop_words.append(line)
return stop_words
Загрузите список стоп-слов заранее и верните стоп-слова в виде списка после обработки.
обработка данных
def load_data(args):
print('加载数据中...')
stop_words = get_stop_words() # 加载停用词表
'''
如果需要设置文本的长度,则设置fix_length,否则torchtext自动将文本长度处理为最大样本长度
text = data.Field(sequential=True, tokenize=tokenizer, fix_length=args.max_len, stop_words=stop_words)
'''
text = data.Field(sequential=True, lower=True, tokenize=tokenizer, stop_words=stop_words)
label = data.Field(sequential=False)
text.tokenize = tokenizer
train, val = data.TabularDataset.splits(
path='D:\\MyStudy\\program\\text-classification-master\\text-cnn\\data\\',
skip_header=True,
train='train.tsv',
validation='validation.tsv',
format='tsv',
fields=[('index', None), ('label', label), ('text', text)],
)
if args.static:
text.build_vocab(train, val, vectors=Vectors(name="data\\eco_article.vector")) # 此处改为你自己的词向量
args.embedding_dim = text.vocab.vectors.size()[-1]
args.vectors = text.vocab.vectors
else: text.build_vocab(train, val)
label.build_vocab(train, val)
train_iter, val_iter = data.Iterator.splits(
(train, val),
sort_key=lambda x: len(x.text),
batch_sizes=(args.batch_size, len(val)), # 训练集设置batch_size,验证集整个集合用于测试
device=-1
)
args.vocab_size = len(text.vocab)
args.label_num = len(label.vocab)
return train_iter, val_iter
Шаги для использования torchtext обычно таковы: 1. Используйте data.Field() для определения объекта и предустановленных параметров, где текст и метка определяются отдельно. 2. Используйте data.TabularDataset().spilts() для чтения файла и получения двух частей train и val. 3. Создайте векторы слов, используйте text.build_vocab(trian, val), label.build_vocab(trian, val) для построения векторов слов обучающего текста и меток. 4. Используйте data.Iterator.splits() для создания bacth.
С этого момента предварительная обработка данных завершена.
2. Создание модели
Примите архитектуру CNN. С помощью питорч. Общая сетевая архитектура: уровень внедрения, обработка измерений, уровень свертки, функция активации, уровень пула, многоканальное извлечение признаков, уровень исключения и полностью подключенный уровень.
Встраиваемый слой
Построенный вектор слова встроен, а параметры встраиваемого слояразмер вектора словаиВстроенное измерение.
сверточный слой
Преобразуйте выходное измерение встраивающего слоя в размерность, подходящую для ввода сверточного слоя, и сохраните в нем трехканальный параллельный сверточный слой с помощью self.convs=nn.MoudleList (nn.conv2() для fsz в filter_sizes) , и вернуть том Список слоев.
функция активации
x = F.relu(conv(x) for conv in self.convs) имплантирует нелинейность
объединение, субдискретизация
Извлечение и слияние многоканальных признаков
x = [x_item.view(x_item.size(0), -1) for x_item in x] Сглаживает размеры результатов различных операций ядра свертки.
Dropout предотвращает переоснащение
Выход полностью подключенного слоя
Код для части создания модели выглядит следующим образом
class TextCNN(nn.Module):
# 多通道textcnn
def __init__(self, args):
super(TextCNN, self).__init__()
self.args = args
label_num = args.label_num # 标签的个数
filter_num = args.filter_num # 卷积核的个数
filter_sizes = [int(fsz) for fsz in args.filter_sizes.split(',')]
vocab_size = args.vocab_size
embedding_dim = args.embedding_dim
self.embedding = nn.Embedding(vocab_size, embedding_dim)
if args.static: # 如果使用预训练词向量,则提前加载,当不需要微调时设置freeze为True
self.embedding = self.embedding.from_pretrained(args.vectors, freeze=not args.fine_tune)
self.convs = nn.ModuleList(
[nn.Conv2d(1, filter_num, (fsz, embedding_dim)) for fsz in filter_sizes])
self.dropout = nn.Dropout(args.dropout)
self.linear = nn.Linear(len(filter_sizes)*filter_num, label_num)
def forward(self, x):
# 输入x的维度为(batch_size, max_len), max_len可以通过torchtext设置或自动获取为训练样本的最大=长度
x = self.embedding(x) # 经过embedding,x的维度为(batch_size, max_len, embedding_dim)
# 经过view函数x的维度变为(batch_size, input_chanel=1, w=max_len, h=embedding_dim)
x = x.view(x.size(0), 1, x.size(1), self.args.embedding_dim)
# 经过卷积运算,x中每个运算结果维度为(batch_size, out_chanel, w, h=1)
x = [F.relu(conv(x)) for conv in self.convs]
# 经过最大池化层,维度变为(batch_size, out_chanel, w=1, h=1)
x = [F.max_pool2d(input=x_item, kernel_size=(x_item.size(2), x_item.size(3))) for x_item in x]
# 将不同卷积核运算结果维度(batch,out_chanel,w,h=1)展平为(batch, outchanel*w*h)
x = [x_item.view(x_item.size(0), -1) for x_item in x]
# 将不同卷积核提取的特征组合起来,维度变为(batch, sum:outchanel*w*h)
x = torch.cat(x, 1)
# dropout层
x = self.dropout(x)
# 全连接层
logits = self.linear(x)
return logits
3. Обучение и оптимизация модели
После создания модели войдите в процесс обучения, предварительно задав гиперпараметры.
parser = argparse.ArgumentParser(description='TextCNN text classifier')
parser.add_argument('-lr', type=float, default=0.001, help='学习率')
parser.add_argument('-batch-size', type=int, default=128)
parser.add_argument('-epoch', type=int, default=20)
parser.add_argument('-filter-num', type=int, default=200, help='卷积核的个数')
parser.add_argument('-filter-sizes', type=str, default='6,7,8', help='不同卷积核大小')
parser.add_argument('-embedding-dim', type=int, default=128, help='词向量的维度')
parser.add_argument('-dropout', type=float, default=0.4)
parser.add_argument('-label-num', type=int, default=2, help='标签个数')
parser.add_argument('-static', type=bool, default=False, help='是否使用预训练词向量')
parser.add_argument('-fine-tune', type=bool, default=True, help='预训练词向量是否要微调')
parser.add_argument('-cuda', type=bool, default=False)
parser.add_argument('-log-interval', type=int, default=1, help='经过多少iteration记录一次训练状态')
parser.add_argument('-test-interval', type=int, default=100,help='经过多少iteration对验证集进行测试')
parser.add_argument('-early-stopping', type=int, default=1000, help='早停时迭代的次数')
parser.add_argument('-save-best', type=bool, default=True, help='当得到更好的准确度是否要保存')
parser.add_argument('-save-dir', type=str, default='model_dir', help='存储训练模型位置')
def train(args):
train_iter, dev_iter = data_processor.load_data(args) # 将数据分为训练集和验证集
print('加载数据完成')
model = TextCNN(args)
if args.cuda: model.cuda()
optimizer = torch.optim.Adam(model.parameters(), lr=args.lr)
steps = 0
best_acc = 0
last_step = 0
model.train()
for epoch in range(1, args.epoch + 1):
for batch in train_iter:
feature, target = batch.text, batch.label
# t_()函数表示将(max_len, batch_size)转置为(batch_size, max_len)
with torch.no_grad():
feature.t_()
target.sub_(1)
if args.cuda:
feature, target = feature.cuda(), target.cuda()
optimizer.zero_grad()
logits = model(feature)
loss = F.cross_entropy(logits, target)
loss.backward()
optimizer.step()
steps += 1
if steps % args.log_interval == 0:
# torch.max(logits, 1)函数:返回每一行中最大值的那个元素,且返回其索引(返回最大元素在这一行的列索引)
corrects = (torch.max(logits, 1)[1] == target).sum()
train_acc = 100.0 * corrects / batch.batch_size
sys.stdout.write(
'\rBatch[{}] - loss: {:.6f} acc: {:.4f}%({}/{})'.format(steps,
loss.item(),
train_acc,
corrects,
batch.batch_size))
if steps % args.test_interval == 0:
dev_acc = eval(dev_iter, model, args)
if dev_acc > best_acc:
best_acc = dev_acc
last_step = steps
if args.save_best:
print('Saving best model, acc: {:.4f}%\n'.format(best_acc))
save(model, args.save_dir, 'best', steps)
else:
if steps - last_step >= args.early_stopping:
print('\nearly stop by {} steps, acc: {:.4f}%'.format(args.early_stopping, best_acc))
raise KeyboardInterrupt
Процесс обучения сначала создает экземпляр модели, а затем определяет оптимизатор.Я использую оптимизатор Adam, а затем базовую операцию обучения pytorch.
for epoch in eopch_num:
for bacth in batches:
optimizer.zero_grad()#梯度清零
logits = model(feature)
loss = F.cross_entropy(logits,targets)#交叉熵函数
loss.backward()#反向传播
optimizer.step()
step + =1
Процесс тестирования проверочного набора аналогичен процессу обучения.
def eval(data_iter, model, args):
corrects, avg_loss = 0, 0
for batch in data_iter:
feature, target = batch.text, batch.label
with torch.no_grad():
feature.t_()
target.sub_(1)
if args.cuda:
feature, target = feature.cuda(), target.cuda()
logits = model(feature)
loss = F.cross_entropy(logits, target)
avg_loss += loss.item()
corrects += (torch.max(logits, 1)
[1].view(target.size()) == target).sum()
size = len(data_iter.dataset)
avg_loss /= size
accuracy = 100.0 * corrects / size
print('\nEvaluation - loss: {:.6f} acc: {:.4f}%({}/{}) \n'.format(avg_loss,
accuracy,
corrects,
size))
return accuracy
def save(model, save_dir, save_prefix, steps):
if not os.path.isdir(save_dir):
os.makedirs(save_dir)
save_prefix = os.path.join(save_dir, save_prefix)
save_path = '{}_steps_{}.pt'.format(save_prefix, steps)
torch.save(model.state_dict(), save_path)
train(args)
После обучения правильная скорость проверочного набора может достигать 90%. Ссылка на код отСсылка на сайт