美文网首页NLP(MLP)的机器学习
#物流快递服务哪家强——基于Paddle的NLP对物流快递的服务

#物流快递服务哪家强——基于Paddle的NLP对物流快递的服务

作者: 伦文聚 | 来源:发表于2021-08-16 16:24 被阅读0次

    前言

    首先声明一下,这个是一个技术文章,中间可能会涉及到一些公司,名称均已经做脱敏处理,请勿对号入座。标题纯属是做一下标题党,请勿当真。

    其次无论是基于什么的NLP,都会涉及到训练样本的问题,由于服务的复杂性,样本的分类可能会有所偏差,导致最终结果会有偏差,请大家理解,请勿当真,仅当作技术思路的参考。

    背景

    作为一个消费者,我们常常可以看到各种数据,例如经常公开的一些行业数据,XX快递 X月的**万票投诉率是多少,申诉率又是多少等等这些非常专业的数据,这些数据都考虑了各家的业务量的大小了,非常科学。但是我们也知道譬如犯罪率,不能光看一个犯罪率数据,还得多方面看,因为一个盗窃和一个命案在犯罪率的统计上,都是一样的,但是我们都知道这个背后的治安问题是不一样严重的。同样用投诉率等一样会存在这个问题,我们不得不思考,在当前机器学习已经在大量应用于各个行业的情况下,有没有其他角度来看我们的快递/物流服务?作为一个技术的爱好者,这里做一些抛砖引玉的做法,本文一共分两个部分,第一个是利用机器学习进行投诉分类,另外一个是利用机器学习进行投诉的评价,本文是利用机器学习进行投诉分类供参考。也欢迎进行技术讨论。情感分类的后续我有空再放出来(解决前面说的用率值进行统计的缺陷)。

    说明

    1、所有样本来源sina黑猫投诉平台的开放数据,所以本文不会提供原始的数据供大家下载,如果有人需要复现,请自己想办法解决;

    2、为了用于训练的样本尽可能准确,本文使用的样本均是脱敏后,提供给不同的人,让不同的人去分类,采用少数服从多数的分类原则最终确定样本的分类;

    3、由于服务的复杂性,一个投诉样本里面,可能会存在多种分类可能,但是这里只采用一个分类,如:

    **快递送达快*驿站,快*驿站却找理由不派送,每次都是同样的理由,没时间只能自取,不送货,态度特别不好,没有经过同意直接放驿站,给*通本地网点打电话,一直说他们联系快*驿站,结果联系了几天还是不派送,联系*通在线客服投诉,在线客服却不登记,不授予投诉。本人收件地址已经备注,不要放驿站。
    

    这个投诉内容分类,分类是“未经允许放驿站”?“不送货上门”?“服务态度”?都有可能,不同的人可能有不同的意见,这里采用一个投诉只能有一个分类,然后多人进行这个样本进行标记分类,最后服从多数人的分类来确定最后的分类。

    4、由于数据是来源于互联网的平台的数据,而数据是消费者自己输入的,可能会一面之词的情况,不代表任何立场,仅用于学术讨论。

    运行环境

    1、操作系统 Ubuntu20.4
    2、Python3.9
    3、paddle2.1
    4、Tesla K20(之前的GPU计算卡被烧了,现在矿工把计算卡都炒上天了,木有钱买新卡,只能把N年前退役的K20拿出来用)
    

    各位看官觉得有用的话,可以打赏下买个新的计算卡

    整体思路

    image.png

    如图示,获取到数据后,抽取一部分出来作为样本,进行打标,然后对模型进行训练,用训练后的模型对未打标分类的投诉数据进行预测,预测结果作为分类结果,再用结果进行分析。

    注意事项

    1、样本的平衡性,由于采用机器学习,所以对于不同分类的样本数据,大家要进行合理的控制,如果样本不均衡,可能会导致结果的失真。这是和机器学习的特效是有关的,举个例子来说:

    如果100个样本里面,有99个男人,1个是女人,那么最后训练出来的模型尽管看起来ACC非常高,但是实际可能不如人意。因为随便抽一个出来,预测是男人的准确率都可以到99%,所以无论是训练的样本还是验证的样本,我们都应该尽可能的平衡,每个分类都是差不多的数量的样本。
    

    2、分词过程中无意义的词汇的过滤。投诉的原始数据中,有很多客户的描述得非常详尽,但是对于我们的机器学习来说,有时反而是一种阻碍,例如:

    假如样本数据中,刘1刀~刘100刀是冠军,王1刀~王100刀是亚军,那么很有可能给一个叫王*刀的给他预测,预测结果就是亚军,但是我们知道这个预测并不科学,但是在机器学习中,他们洞察出来的结果就是王*刀,是亚军的概率是99%以上😄
    

    在开始动手撸代码前,先看一下结果


    image.png

    从这里看,模型的分类区分度还是不错的
    今天先写到这里,要准备回家做饭了,如果大家想看,记得点赞+收藏,点赞越多,我更新动力越足。
    ———————————————————————————————————————
    接着更新:
    下面我们正式开始看看怎样做吧。

    Setp1:分类标准

    在开始学习前,我们先确定标准分类,这里我们一共分10类(这个分类或许有不合理的地方,但是大家当作技术研究探讨使用就好,因为分类没有绝对的标准,例如很多信息不更新,其实是由于货物丢(或者是虚假丢货)了,但是这个事情我们不能确定,而客户投诉内容只是说货物中途几天不动,我们只能归类为信息更新不及时),分别为:

    破损丢失
    信息不更新
    虚假签收
    未经允许放驿站
    其他
    乱收费
    派送不上门
    不上门取件
    时效
    虚假物流信息
    服务态度
    

    Setp2:样本打标

    前面提到,我们打标是一个非常关键的事情,我们机器学习就譬如是教会小朋友明辨是非,而样本则是我们的教材,如果我们的教材出问题了,教学的结果可能就是错误的。这里为了追求尽可能的相对科学,我们把数据脱敏后,多人进行标记,然后采用投票的原则,投出最后的分类。


    image.png

    例如,上面的例子,分别给3个人进行打标,其中2个分类为信息不更新,1个人分类为时效问题,这里我们根据最后分类结果投票的结果,选用分类为信息不更新,尽管说分类为时效也是有一定的道理的,但是无论如何我们都需要确定下来一个唯一的分类(突然让我想起疯泉的故事😓……正常的反而被认为是疯的,但是这就是机器学习……😓)。

    Setp3:数据准备

    获取到的数据比较多,我们不可能对所有的投诉都进行人工分类,这也违背了我们这期的目的,我们对一些数据进行打标后,每个分类抽取100个样本,然后按照下面的格式生成一个txt文件

    投诉内容_!_分类
    

    这里需要注意,这里我用的分割符号是_ ! _,并不是",",因为如果用其他符号,很容易和投诉内容中的符号重叠,导致分割不准确,所以这里用了组合符合来做分割符,当然,你也可以按照你的习惯来,不过建议是多个符号组合的分割符号。
    由于机器学习不能直接对中文进行学习,我们需要将中文进行转换成为编码

    #coding=utf-8
    '''
    生成字典,如果没有特殊情况,可以使用默认字典就好,如果需要优化,可以使用默认字典+分词字典
    '''
    import os
    import io
    import utils.jiebainfer as jiebainfer
    import utils.myfile as myfile
    
    data_root_path = './dataset'
    data_path = os.path.join(data_root_path, 'list.txt')
    
    def create_dict(data_path, data_root_path):
        print('准备生成字典')
        dict_set = set()
        dict_words_set = set()
        type_dict=set()
        with io.open(data_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
            print(lines)
        stopwords=myfile.Readstopword(os.path.join(data_root_path, 'stop_words.txt'))
        for line in lines:
            title = line.split('_!_')[0].replace('\n', '')
            for s in title:
                dict_set.add(s)
            strlist=jiebainfer.split(title)
            for word in strlist:
                content_str = ''
                for i in word:
                    if myfile.is_chinese(i):
                        content_str = content_str+i
                if word in stopwords:
                    print(content_str+'是停用词')
                else:
                    dict_words_set.add(content_str)
            type = line.split('_!_')[-1].replace('\n', '')
            type_dict.add(type)
        dict_make(data_root_path,'character_dict.txt', dict_set)
        dict_make(data_root_path,'word_dict.txt', dict_words_set)
        dict_make(data_root_path,'type_dict.txt', type_dict)
    
        print("数据字典生成完成!")
    
    
    def dict_make(dict_path,name, dict_set):
        dict_path = os.path.join(dict_path, name)
        dict_list = []
        i = 0
        for s in dict_set:
            dict_list.append([s, i])
            i += 1
        dict_txt = dict(dict_list)
        end_dict = {"<unk>": i}
        dict_txt.update(end_dict)
        with io.open(dict_path, 'w', encoding='utf-8') as f:
            f.write(str(dict_txt))
    
    if __name__ == '__main__':
        create_dict(data_path, data_root_path)
    

    我们把样本txt文件放到

    ./dataset/list.txt
    

    运行上面的python,则可以生成单个字的词典和用结巴分词的词典,这里需要注意的是,用分词词典,词典会比较大,因为中国的汉字就那么几千个,但是组成的词却是可以很多的,但是正是由于这样,用分词的词典的准确度会高于单字作为词典的(样本足够的情况下)。(今天先写这里,待续……)
    ————————————————————————————————————
    完成字典工作后,我们需要把前的样本分为训练样本和验证训练效果的两组样本,这里我们验证按照20%的比例从总样本集中抽取,并且为了得到先对比较客观的准确率数据,我们的验证样本不和训练样本重复(其实在学习样本不多的情况下,这两个样本是可以重叠,但是这样会导致训练过程中看到的准确率偏高,但是由于训练样本增多了,其实效果会更加好,但是实际没有看到的数据高)

    #coding=utf-8
    
    '''
    读取词典和分类词典,将文本转化为训练和验证的数据
    '''
    import os
    import io
    import utils.jiebainfer as jiebainfer
    import utils.myfile as myfile
    
    data_root_path='./dataset'
    
    def create_data_list(dir):
        # 清空历史数据
        with io.open(dir + 'test_list.txt', 'w') as f:
            pass
        with io.open(dir + 'train_list.txt', 'w') as f:
            pass
        with io.open(data_root_path + 'error.txt', 'w') as f:
            pass
    
        with io.open(os.path.join(data_root_path, 'word_dict.txt'), 'r', encoding='utf-8') as f_data:
            dict_txt = eval(f_data.readlines()[0])
            print('字典长度{}'.format(len(dict_txt.keys())))
            print('字典最大序列{}'.format(len(dict_txt.keys())-1))
    
        with io.open(os.path.join(data_root_path, 'type_dict.txt'), 'r', encoding='utf-8') as f_data:
            type_txt = eval(f_data.readlines()[0])
            print('分类字典长度{}'.format(len(type_txt.keys())))
            print('分类字典最大序列{}'.format(len(type_txt.keys())-1))
    
        with io.open(os.path.join(dir, 'list.txt'), 'r', encoding='utf-8') as f_data:
            lines = f_data.readlines()
        i = 0
        errorstrlist=[]
        for line in lines:
            title = line.split('_!_')[0].replace('\n', '')
            l = line.split('_!_')[1]
            print(l,title)
            # 对title分词
            words_list=jiebainfer.split(title)
            if i % 5 == 0:
                makelistfile(dir,'test_list.txt',words_list,dict_txt,errorstrlist,type_txt,l)
            else:
                makelistfile(dir,'train_list.txt',words_list,dict_txt,errorstrlist,type_txt,l)
            i += 1
    
        # 无法编码的字符记录下来
        errorrec(errorstrlist)
        # 保存新的词典
        savedict(dict_txt)
        print("数据列表生成完成!")
    
    def makelistfile(dir,filename,words_list,dict_txt,errorstrlist,type_txt,l):
        # 读取停用词
        stopwords=myfile.Readstopword(os.path.join(data_root_path, 'stop_words.txt'))
        labs = ""
        with io.open(os.path.join(dir, filename), 'a', encoding='utf-8') as f_train:
            for s in words_list:
                # 只保留中文
                content_str = ''
                for k in s:
                    if myfile.is_chinese(k):
                        content_str = content_str+k
                # 判断是否是停用词
                if content_str in stopwords:
                    print(content_str+'是停用词')
                else:
                    try:
                        lab = str(dict_txt[content_str])
                    except:
                                # lab = str(dict_txt['<unk>'])
                        if not content_str in errorstrlist:
                            errorstrlist.append(content_str)
                                # 动态增加到词典
                        dict_txt[content_str]=len(dict_txt.keys())
                        lab = str(dict_txt[content_str])
                    labs = labs + lab + ','
            labs = labs[:-1]
            ln=str(type_txt[l.replace('\n','')])
            labs = labs + '\t' + ln + '\n'
            size=labs.split(',')
            if  len(size)>0 and len(l)>0:
                f_train.write(labs)
            else:
                print('特征不够,抛弃')
    
    def savedict(dict):
        '''
        保存新的词典
        '''
        with io.open(data_root_path + 'newdict.txt', 'w') as f:
            f.write(str(dict))
            f.close()
    
    def errorrec(strlist):
        '''
        编码过程中遇到生僻字,无法编码,记录一下,方便优化字典
        '''
        with io.open(data_root_path + 'error.txt', 'a') as f:
            for string in strlist:
                f.write(string)
            f.close()
    
    if __name__ == '__main__':
        create_data_list(data_root_path)
    
    

    在这里,我们还把动态扩充词典的功能加上了,后面如果需要增加样本,而之前的分词词典没有的,会动态增加到新的词典中,这样可以使的词典进行动态的变化(如果使用单字词典,建议使用网上的汉字字典,基本上不用再次动态扩充)

    相关文章

      网友评论

        本文标题:#物流快递服务哪家强——基于Paddle的NLP对物流快递的服务

        本文链接:https://www.haomeiwen.com/subject/ktzmbltx.html