2018年NLP达观杯-复盘

前言

我是xx学校的一名研二学生,接触机器学习一年,深度学习半年了,但是基础还是不够扎实,去找实习时,面试官问我一些理论基础,很多答不好,而且要推导公式的话更加不会了。

之前也和我大哥(一个大佬,现在在微软实习)一起做过蚂蚁金服的文本相似度计算NLP比赛,使用的是text-cnn, lstm等等一些深度学习的模型。但我在这个过程中,比较懒,没有深入解读代码,只做了一少部分特征提取和调差的工作。导致在面试的时候,我连模型的结构都画不出来。

后来比较幸运的是,SAS公司愿意让我去他那实习,负责语言种类的识别工作,现在用的是Python进行开发,任务还能接受,而下班时间也比较早,所以想既然有稳定而且比较轻松的实习了,还是需要把一些基础知识再打扎实才行。
基于上面这些原因,而又在机缘巧合之下,在某个公众号里发现了自学西瓜书+带打比赛这个活动,于是报名参加。(说来惭愧,虽然我确实也看了一些西瓜书,有了一点收获,但并没有达到自己期望和老师要求的那个标准,有时候就偷懒了)还有一点,我想只靠自己在没有队友的情况下,做一两个比赛,我觉得只有这样才能不依靠别人,完全弄懂整个项目,对自己的提升也是很大的。

下面我就来介绍一下这次我做的(学习到的)这个比赛。
一些代码,是参考jian老师的:(代码可能不是最完美的,但是作为学习足够了)
https://github.com/MLjian/TextClassificationImplement
比赛地址:
http://www.dcjingsai.com/common/cmpt/“达观杯”文本智能处理挑战赛_竞赛信息.html

数据预处理

在这个过程中,我考虑到我的电脑性能原因,不想让它一次性把所有的文件读入。而是采取把整个文件切分为好几个文件并保存,然后再一次读入到内存。

拆分数据

#11万条数据
import pandas as pd
import time
start = time.time()
file_path = '../new_data/train_set.csv' #要拆分文件的位置
reader = pd.read_csv(file_path, chunksize=20000)
count = 0
for chunk in reader:
    print('save train_set%s.csv'%count)
    chunk.to_csv('train_set'+str(count)+'.csv', index=0)
    use = time.time()-start
    print('{:.0f}m {:.0f}s ...'.format(use//60, use%60))
    count += 1
#可参考 https://blog.csdn.net/zm714981790/article/details/51375475

拆分后得到如下:
好几个文件
这样总共这些文件有2.56G,分成了好几份,对于我来说好很多。但如果你电脑比较好,能直接读进去,那么这一步没有必要。不过我这样捣鼓了一下,对于Python的dataframe等包的数据处理熟悉了一些。

合并数据

之后再读这几个文件并且合并的代码是:

def read_files(numbers=6, name='train_set'):
    dataframe_list = []
    for i in range(numbers):
	    path = name+str(i)+'.csv'
	    print(path)
	    dataframe_list.append(pd.read_csv(path))
    dataframe = pd.concat(dataframe_list, ignore_index=True)
    return dataframe

df_train = read_files(6, 'train_set')
df_test = read_files(6, 'test_set')

选择需要的数据

接下来的任务是把选择一些有用的数据(扔掉一些我们不用的):

df_train.drop(columns=['article', 'id'], inplace=True)
df_test.drop(columns=['article'], inplace=True)

数据说明中显示:
'article’是字级别上的,'word_seg’是词级别上的。
也就是说,比赛举办方已经把单词给我们切好了,不需要自己手动分词(如用“结巴分词”等工具),而且他已经把单词数字化(脱敏),这其实也省了我们一些工作。
一般的比赛我们是要自己分词,而且分词的效果对模型和结果影响较大。而这分好词了,那么就直接使用’word_seg’即可。
当然这只是一个比较简略的版本,如果要进行后续工作的话,我觉得可以把’article’也用上,至少可以用来做模型融合,投票选最优等工作。

特征工程

将数据集中的字符文本转换成数字向量,以便计算机能够进行处理(一段文字 —> 一个向量) ,实际上就是转化为tfidf的形式。

vectorizer = TfidfVectorizer(ngram_range=(1, 2), min_df=3, max_df=0.9)
vectorizer.fit(df_train['word_seg'])
x_train = vectorizer.transform(df_train['word_seg'])
x_test = vectorizer.transform(df_test['word_seg'])
y_train = df_train['class']-1

上面时jian老师写的代码,我对他的一些参数做了调整,发现效果更好。

vectorizer = TfidfVectorizer(ngram_range=(1,2), min_df=3, max_df=0.9, use_idf=1, smooth_idf=0, sublinear_tf=1)

这里其实我有一个疑问,就是现在的代码只用了df_train['word_seg']来训练vectorizer,而对df_test['word_seg']没有进行fit。
但好像后面的代码好像做了优化,使用了df_all = pd.concat(objs=[df_train, df_test], axis=0),然后再用df_all进行fit,我觉得后面这种效果可能好一点,因为覆盖了全部的样本。

训练分类器

classifier = LinearSVC()
classifier.fit(x_train, y_train)

我尝试了一些分类器,但如果只使用传统机器学习的单模型,发现线性支持向量分类LinearSVC()效果最好。
这里面也有一些参数可以调节,但是我调了一些后发现,并没有明显的提升,索性也就直接使用默认的了。虽然调参是很重要的,但我之前只想着学会用一个一个不同的模型,重点不在这里。

预测

y_test = classifier.predict(x_test)

使用已经训练好的classifier来分类x_test。

保存至本地

df_test['class'] = y_test.tolist()
df_test['class'] = df_test['class'] + 1
df_result = df_test.loc[:, ['id', 'class']]
df_result.to_csv('../results/beginner.csv', index=False)

把训练后的结果保存到本地,然后进行提交。

改进

划分训练集和验证集

X_train, X_vali, Y_train, Y_vali = train_test_split(df_train[['word_seg']], df_train[['class']], test_size=0.1, random_state=1)

因为直接使用简单的训练和预测,没办法在调参的时候看到我们的模型效果,只有提交答案时才知道,而每天上传次数有限。所以我采用了train_test_split()将训练集进行划分,验证集可以看到模型好不好,这样对调参很有帮助。

评分函数

from sklearn.metrics import f1_score
my_score = f1_score(np.array(Y_vali), y_vali_pred, average='macro')
print('评分:',my_score)

用上面这些代码可以知道评分是多少,效果好不好。虽然可能和提交后的评分不一样,但还是可以作为评判的一个标准。

结果

根据上面的这些代码,我得到的成绩是 0.777977,按道理再进行后面的一些改进会有较好的提升,但实际上这个成绩却是我的最高成绩[捂脸]。

自动调参

pipeline = Pipeline([('tfidf', featurer),('clf', classifier)])

parameters = {'tfidf__ngram_range': ((1, 2), (1, 3)),
              'tfidf__min_df': (4, 6, 8),
              'tfidf__max_df':(0.7, 0.9, 1.6),
              'clf__C': (1.0, 2.0, 3.0)}

skf = StratifiedKFold(n_splits=5, random_state=1)
gs = GridSearchCV(estimator=pipeline, param_grid=parameters, n_jobs= 1, scoring='f1_macro', cv=skf, verbose=3)
gs.fit(df_train_x, df_train_y)

可以根据上面这些代码进行自动调参,理论上是一种比较好的省事的方法,但我觉得有点华而不实,对于一些小的模型还可以用一用,但对我们这个比赛,训练一次就得30分钟~2小时(根据自己电脑配置不同,耗时不同),那么这个自动调参就得花很多很多时间。
所以我的建议是,自己手动调参,如果某个参数对模型有优化,再接着调节。首先将参数变化大一点调,不要太细致了,否则看不出差别。
另外一点,最好对这个参数有理论上的了解,知道它对应什么意思,将来有什么变化,做到有目的性的调参。

k折交叉

for train_idx, vali_idx in skf.split(x_train, y_train):
    i += 1
    """获取训练集和验证集"""
    f_train_x = x_train[train_idx]
    f_train_y = y_train[train_idx]
    f_vali_x = x_train[vali_idx]
    f_vali_y = y_train[vali_idx]
...
...
"""对K个模型的结果进行融合,融合策略:投票机制"""
preds_arr = np.array(preds).T
y_test = []
for pred in preds_arr:
    result_vote = np.argmax(np.bincount(pred))
    y_test.append(result_vote)

上面的代码是k折交叉,和使用投票机制进行融合的代码。
我自己也试了一次,采用5折交叉,时间大概是之前的5倍,得到结果后,我信心满满的去提交,但得分却并没有我想象的那么好,原因我还不是很了解。

多个特征的提取

特征
jian老师的代码里面有很多个特征,但由于训练提取的时间也很长,所以我只采用了几个特征。比如对于tf(词频)和tfidf(词频-逆文档)这两种特征,我知道tfidf是tf的一种改进,那么就只使用了tfidf,效果我想应该要比tf要好。
不过这也不一定,特别是使用模型融合时,很多一些你觉得没有用的特征或模型,融合在一起却好很多,此所谓“三个臭皮匠,赛过诸葛亮”。

我这次提取的特征是doc2vec,lsa,tfidf这几个。

把这几个特征得到后,可以用下面的代码,变成一个大的训练集和测试集:

x_train = np.concatenate((x_train_1, x_train_2, x_train_3), axis=1)
x_test = np.concatenate((x_test_1, x_test_2, x_test_3), axis=1)

后面可以再对它使用LinearSVC()或者其他的模型来训练。
此外,我还使用了号称比赛的大杀器lightgbm,但效果也不是很好,可能没用到家,还需努力。

保存特征/变量

import pickle
...
data = (x_train, y_train, x_test)
fp = open('./data_tfidf.pkl', 'wb')
pickle.dump(data, fp)
fp.close()

通过以上代码,就可以把一些特征/模型保存到本地,而且使用起来非常方便。这也是我这次比赛才学到的东西。

读取特征

f1 = open('./data_lda.pkl', 'rb')
x_train_1, y_train, x_test_1 = pickle.load(f1)
f1.close()

f2 = open('./data_s_lsvc_l2_143w_lsa.pkl', 'rb')
x_train_2, y_train, x_test_2 = pickle.load(f2)
f2.close()

f3 = open('./data_doc2vec_25.pkl', 'rb')
x_train_3, _, x_test_3 = pickle.load(f3)
f3.close()

使用上面的代码,可以把保存在磁盘里的特征读取到内存里。

总结

以上,就是我现在所做的全部工作,老师还给了dl的相关代码进行特征提取,但我还没看,不太会。
总而言之,还是学到了一些东西。这篇文章,算是对这次比赛的一个总结,也是自己学习过程的记录。如果你有缘看到,希望能给你一点点的帮助。
欢迎一起学习,这样才能走得更远~
谢谢!

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值