我用10万张凭证,训练出了一个序时账清洗功能

我用10万张凭证,训练出了一个序时账清洗功能

我用10万张凭证,训练出了一个序时账清洗功能-寻找资源网
我用10万张凭证,训练出了一个序时账清洗功能
此内容为付费阅读,请付费后查看
5
立即购买
您当前未登录!建议登陆后购买,可保存购买订单
seekresource@163.com
1919588043
QQ1919588043
寻找资源网
微信小店:寻网百货
付费阅读
商城已上线,快去看看吧!
大家好,今天分享一个新的功能,序时账清洗。
这是我在做另一个项目时遇到的一个支线问题,于是就先尝试来处理了一下。
简单来说,就是对序时账进行聚类清洗,比如,一份序时账里,凭证和摘要都很乱很杂,我想做的,就是将一份序时账各种各样的凭证,归类到不同的业务场景里。
图片
比如说,销售,存货采购,长期资产采购,税费,日常经营费用等等,每个大框里分别装好它们对应的凭证。
如果能实现这样的效果,这对于编现金流量表、会计分录测试也是很有帮助的。
我之前尝试用过NLP文本聚类的一些算法来处理,但结果不甚理想,具体的功能在我之前发的工具箱里:基米工具箱v0.3开源发布
那么现在,我就来分享一下新的算法,还是老样式,我们分为两个部分,一是讲如何使用,二是讲具体的技术细节。

一、如何使用

使用地址

代码开源地址

我把这个功能和之前的会计分录测试、对方科目分析都整合起来了:

图片
现在我们进入序时账清洗功能:
图片

1. 上传序时账并配置字段

我们先点击Browse files上传序时账并配置相应的字段:
图片
全局计数器管理那里有显示已积累10万张凭证,就是我的训练数据。
在streamlit页面上,因为没有配置数据库,每次跑基于电脑内存,都是一次性的,不会有累积的效果,所以不用担心数据隐私的问题。
其中,制单人列是选填列,我的逻辑考虑是这样的:有的企业记账分工比较明确,比如有的人专门只负责销售模块,有的人专门只负责长期资产模块,所以制单人信息对于区分业务模块也是有帮助的。
当然如果分工不是那么明确的,那就不用填。
配置制单人字段的话,会要求人员选择对应的岗位:
图片
如为应收会计,那这个人员对应的凭证,在计算得分上,会稍偏向销售模块。
不填也基本没影响,配置字段后可以点击右侧的开始分类了。

2. 分类结果

我们逐个看一下页面信息:
2.1 分类概览
图片
就是显示凭证都被分类到哪个业务模块了,这里我将它命名为“桶”,就像每张凭证都是块石头,我们分业务模块,就是把每块石头分别丢向不一样的业务桶。
2.2 详细结果
图片
点击下载分类结果,即是最终的结果,拿到的是一份已经分类好的序时账。
下载出来后看最右侧,有个“业务分类”列。
还有一个分数明细,就是可以看到一张凭证在每个业务桶那的得分是多少,得分最高者就命中该业务桶。
2.3 PMI矩阵
图片
这个我们后面在技术细节里会讲到。
2.4 关键词命中
图片
每个业务桶都有预设的关键词,这里可以看到每个关键词的命中情况。
2.5 纠错
图片
这个功能在streamlit上是无效的,因为streamlit上每次跑完都是一次性的,不记录数据,所以可以不用管。
而如果在本地运行,这是可以纠错的,就告诉程序,哪一张凭证它分错了,然后它就会记录下来。
我这里是我本地运行的,所以带上了我之前的纠错记录,我给程序纠错过312张凭证。
2.6 自动词
图片
这里就呈现,程序“自己找的”一些关键词。
因为面临的序时账是多种多样的,我们有预设一些固定的词,但各家公司情况肯定不一样,所以有些是程序自己找的词。
这里会呈现它找的高频词、低频词,它都会留着记录。
然后觉得它找的词不对的,可以进行删除。
当然,因为这也是累积的,所以streamlit版本还是用不了,即便我们进行了纠错、删除,下一轮跑它还是“没有记忆”的。
>版本特性:
这里也是莫得办法,给大家的streamlit版本可以说就是“阉割”版的,没有记忆功能,这里有数据隐私安全的考虑,也有技术上的考虑。
不过我觉得现在这个版本也是基本够用的。
后续会考虑做成skill版本,在本地运行并且让AI来搞定一些稍复杂的判断。

二、技术细节

这是具体的计算公式:
图片
看着复杂,但实际很简单,就是用的小学二年级学过的加法和乘法。
下面技术部分,我们就围绕这个得分公式进行展开:
其中,得分公式我们分为了5个模块:
  • 结构分
  • 关键词偏置
  • 金额特征
  • 纠错增强
  • 制单人偏置

1. 结构分

图片
先看这一块,有一个参数λ、一个v向量,一个w’向量
1.1 v向量:凭证向量
那我问你,一张凭证里,核算了好几个会计科目,谁才是主角?
图片
比如,这一张凭证里,固定资产就是主角,这种凭证说的是采购固定资产。
这我们人类是一眼看得出来的,但程序应该怎么看出来?
即,程序应该把注意力attention放在哪里?
对此,我进行了两点设计:
  • 金额权重:首先,大就是好,好就是大。在一份凭证里,主角的金额应该是偏大的,其他配角会计科目只能围绕着它转,如果金额都不够大,那它就不算主角
  • 清晰度系数:其次,主角应该是有“明确形象”的,不然也不叫主角,得叫路人甲。像固定资产、应付职工薪酬这种词就很清晰,而银行存款、应交税费这种经常陪衬的科目就很路人
    因此,具体的设计公式为:
    图片
    首先,计算借方、贷方金额的绝对值合计。
    然后,计算每个科目在总金额之间的占比,这个占比再乘以清晰度系数。
    清晰度系数是直接硬编码的:
    图片
像一些高清晰度的词,固定资产、主营业务收入、生产成本,我们放大它的权重占比,乘以1.5倍,一些低清晰度的,管理费用(谁知道它核算了什么),降低权重占比,乘以0.3倍。
而银行存款这种绝对的万金油,直接清晰度为0,金额权重再大,乘以0还是0。
据此,我们就可以把每一张凭证算出每一个v向量。
1.2 w’向量:偏好向量
这里w’向量的设计是比较复杂的,它就与训练数据相关。
1.2.1 w向量:原始偏好向量
首先,每个业务桶,对于不同会计科目,是有“偏好的”。
比如说,职工薪酬业务模块,就偏好应付职工薪酬,生产制造模块,就偏好制造费用,存货采购模块,就偏好原材料,等等。
这跟前面的清晰度系数的设计如出一辙。
因此,我们先人工硬编码了一个原始偏好向量,比如:
图片
存货采购,它偏好好的科目是原材料、在途物资、存货采购、委托加工物资等,它次偏好好的科目是应付票据,其他科目它不怎么偏好。
1.2.2 R矩阵:PMI相关性矩阵
这个矩阵要做的就是一件事,让业务桶“爱屋及乌”。
比如,存货采购这个业务桶,它的偏好会计科目是原材料,那假如有个科目经常跟原材料一起出现,那存货采购就会爱屋及乌,也偏好这个会计科目。
因此,第一件事,先算每个科目共同出现的频率:
图片
这就是PMI值(点间互信息),比如我们在实际测试里算出来的:
图片
我们对PMI进行了截断设置,最小值为0,最大值为7。
这里显示的就是所得税费用和递延所得税资产的PMI值为7,说明它们几乎是绑定在一块的。
这里有个问题:我家序时账会计科目很稀疏,样本不足怎么办?
这就引出来了通用矩阵R。
通用R矩阵
对于会计核算,基本都是有一惯性的、有共识的,比如计提应付职工薪酬,大部分企业就是会把费用和应付职工薪酬这个科目放在一起核算。
因此,我们就可以进行数据训练,我们训练出一个比较牢靠的通用R矩阵,哪怕现在跑的这份序时账很稀疏,我们就参考这个通用R矩阵就好了。
然后,这又出现了个问题:假如,我们训练数据里遇到了极端情况,比如有的序时账把管理费用和固定资产经常放一起核算,这导致在这份序时账里管理费用和固定资产共现非常高,那怎么办?
而这,就引出了一个更妙的设计,大数定律。
大数定律的简单解释就是,比如你抛一枚硬币,抛10次,可能出现正面3次,反面7次的情况,但如果你抛1万次、1000万次,正反面的分布基本就是在50%左右。
也就是说,只要样本足够多,概率的结果就会收敛到一个“客观状态”。
这里面还有非常严谨的数学证明,跟文章内容无关我就不展开了,主要我也不会。
因此,我们就可以利用大数定律,只要我们跑够了非常多的序时账,会计科目的核算规律会逐渐收敛到客观状态,比如,计提工资就应该是费用科目和应付职工薪酬,像一些极端的核算情况,那就会被扔到边缘。
我们只需要记录跑每次序时账时有多少凭证、各个科目的共现程度如何这些信息,然后逐次累加起来就行,像这样:
图片
特殊R矩阵
同时,为了照顾一些特殊情况,我们没有用通用R来百分百主导结果:
图片
我们进行了权重分配,通用R(历史数据)占比80%,专属R(公司当前数据)占比20%。
图片
这里就是设置权重的,可以调高或调低,不过20%我认为算是适当的。
1.2.3 相关性传播后的w向量
图片
我们用前面的w原始偏好向量,乘以R矩阵:它的效果就是让业务桶爱屋及乌。
另外,根据工程实际效果,我们加上了L2归一化
关于L2归一化我在这一篇文章中有所提及:银行流水匹配算法2.0开源,更快更准
首先,w向量乘以R矩阵,会得到一个新的向量,这个新向量出现了一个问题,里面的数值太大了。
因为w向量我们初始设计的数值都是1分、0.8分之类的,但是R矩阵的PMI值可以高达7分,因此,矩阵相乘后,w向量里的某些分量会被放大。
比如,最初我们的存货采购偏好向量是这样的:
图片
经过相关性传播后可能变成了这样:
图片
分值受到共现的影响太大了,假如此时一份本应该计入销售模块的凭证里,出现了“产成品”这样的科目,会直接被存货采购错误地拿走。
实战里,在未进行L2归一化时,业务模块分类出现了较大的混淆,存货采购和长期资产错误地拿走了很多属于销售模块的凭证。
因此,我们加入了L2归一化这一块补丁,它可以让数值变得更加柔和:
图片
1.2.4 λ增强系数
在让数值归一化后,我又发现了个问题:因为w’经过了归一化,同时v向量本身也是做过金额归一化的,二者点积之后,结果变得有点小了,结构分拿分太少了。
在这只能又打了一个补丁,设置了一个λ系数,直接硬编码为1.67,即将结构分全部放大1.67倍。
于是这就构成了结构分部分的那样的计算公式。

2. 关键词偏置

光靠会计科目去判断,信息是不够细的,如果考虑上二级科目、摘要这些语义更丰富的词,那信息就更加充分了。
下面我们就来说公式的这一块:
图片
2.1 手工关键词
每个业务桶,也是比较偏好某些关键词的,比如,职工薪酬业务桶,就偏好“工资、社保、公积金”这些关键词。
我们对每个业务桶,先预设好它们偏好的关键词:
图片
比如,一张凭证里,含有“工资”这种强语义的关键词,我们就让职工薪酬桶的得分加一分。
图片
还有就是,有些词具有排他性,比如“报销”这个词通常是针对日常费用的,而不会是生产制造,所以,有些词可以给某个业务桶加分,也可以给某个业务桶扣分。
那么,关键词从哪里来?从摘要、二级科目里来。我们会对摘要、二级科目进行扫描,看下是否命中对应的关键词。
同时,在实测中,还发现这样的问题:
比如,完工入库时,将生产成本结转到库存商品,会出现这样的情况:
借:库存商品
贷:生产成本-直接材料
贷:生产成本-工资
贷:生产成本-社保贷:生产成本-公积金
这里本应该对应的业务活动是生产制造,但在关键词扫描时,一看有“工资、社保、公积金”,程序就会以为是职工薪酬活动。
原因在于:二级科目更偏向于核算场景,摘要才更偏向于业务场景。
因此,我们对二级科目的关键词信号进行抑制,从二级科目列里来的关键词信号衰减40%。
手工关键词偏置得分b的构成为:
图片
2.2 自动词偏置
有个更麻烦的问题,每家公司设计的二级科目、写的摘要很可能都是不同的,我们手工设计的关键词,肯定是没法覆盖的。
在设计初期,我跟kimi讨论这个问题时,它居然老实到想真的去穷举所有关键词,给我发了一份超大的关键词表。
不过,这终究也是不行的,总会有我们无法命中的关键词。
为了解决这个问题,我们的方案就是让程序自己去找,即,从历史数据里去发现强关键词。
为了让程序自己去积累一些词汇,我们进行了如下设计:
2.2.1 核心思路
每次跑完序时账,不是会分好每个业务桶吗?我们就让程序自己去看,这一次在各个业务桶里,哪些关键词出现得最为频繁,然后它自己去拿个笔记偷偷记下来。
跑的序时账越多,它记录下来的关键词就越多。
不过这有个非常关键的问题,就是经过分类的业务桶必须是基本正确的。比如,我们将带有“报销”关键词的凭证,分类到了销售模块,程序在跑完时候复盘发现:原来“报销”这个关键词对应的是销售模块,于是它就记下笔记,以后看到“报销”这个关键词,就往销售模块里面分。
这就导致错误不断累积,它越来越坚信,“报销”这个关键词就应该对应销售模块。
所以,我设计了纠错机制,在初期,需要对跑出来的结果进行比较仔细的检查。
确认跑出来的业务分类基本是正确的,才能开始让程序去记笔记。
2.2.2 数据采集
首先,我们需要一个分词器,tokenizer,于是我们引入了jieba库进行分词:
比如,摘要是:“报销滴滴打车费”,jieba可能会帮忙切分为:“报销”、“滴滴”、“打车费”
于是,经过分词后,就不是一句话传给程序,而是一个个词传给程序了。
2.2.3 哈希存储与三层记忆机制
每跑一份序时账,我们就会给这份序时账分一个哈希值,程序会根据这个哈希值生成存储文件。
这个存储文件里,存的是每份序时账里经过分词器分词后的关键词,不管是高频的、还是低频的,全都存在一起。
图片
比如这里的,fingeprint就是哈希值,是某份序时账,以及,在这份序时账里,这些词在存货采购业务桶里的出现次数。
接着,我们来进行全局计数。
比如,序时账A的存货采购桶里,跑出来的关键词,“预付款”出现了2次,太少了,单看不算高频。
但序时账B的存货采购桶里,“预付款”这个关键词出现了40次。
于是全局加起来就是42次,就变成高频词。
在自动词存储上,我们分为了3层:
  • 第1层:高频词
  • 第2层:低频词,低频词还留着,就是为了怕在后面突然升级为了高频词
  • 第3层:垃圾桶,如果经过5轮以上序时账,这个词出现的频次低于5次,直接扔进垃圾桶,减少存储冗余
存储结构设计如下:
图片
2.2.4 PMI计算
图片
这里的PMI值计算与前面的PMI值计算逻辑基本是一致的。
在前面算的PMI值,我们算的是会计科目之间的关系,这里算的PIM值,是每个关键词与业务桶之间的关系。
图片
2.2.5 根据PMI值给出得分
我们设计了一个分段函数:
图片
当PMI值大于5时,几乎可以说明,这个关键词是对该业务桶特有的,像“社保、公积金”等之于职工薪酬业务桶,因此,最高得分是+1.0。
2.2.6 得分量纲设计
因为关键词偏置得分,分为了手工词(人工预设的)和自动词(程序自己找的),所以,这也可能会发生重复的情形。
比如人工预设了“滴滴”这个词出现在日常费用里,而程序也可能为找到这个词。
因此,为了避免重复加分,所以我们套了一个max:
图片
现在我们已经说完了结构分和关键词偏置,其实这两个模块之间有明确的区别:
  • 结构分:几乎完全基于统计、数学计算
  • 关键词偏置:主要靠人工经验

3. 金额特征

公式的第三部分是金额特征:
图片
3.1 金额特征的设计思路
金额特征最初的设计思路就是想做一个参考值。因为有些业务桶通常是有明确的金额特征的,比如:
  • 日常费用:金额特征偏小
  • 长期资产购置:金额特征偏大
所以,在设计时,是想把金额特征作为一个参考值,辅助业务桶进行分类。
然而,在实际测试里,金额特征却拉垮了,因为几乎每个业务桶的金额特征都是不明显的。
比如,有些费用确实是大额的,比如说支付证券费用,而有些长期资产确实是小额的,比如说采购电脑、地板什么的。
所以,我将其对得分的影响钳制在了0.05,对得分的影响几乎已经是微乎其微,只起一个“随缘看运气”的作用。
3.2 金额特征的计算
因为这模块我几乎已经砍了,详细设计就不说了,贴个图放在这里:
图片

4. 纠错增强

图片
4.1 纠错增加的设计
纠错增强就是前面streamlit页面对应的这里:
图片
在本地版本中,如果我们觉得程序把一张凭证分类错业务桶了,那就可以进行纠错,纠错是长这样的:
图片
在对凭证进行纠错再上传后,本地文件夹里会生成一个`corrections.json`的文件,那就是纠错记录。
程序是如何对我们的纠错记笔记的?它会记3个要素:
  • 会计科目:这笔凭证都有哪些会计科目
  • 关键词:这笔凭证的关键词是什么(jieba分好词给它)
  • 原来错的业务桶:比如原来分在了职工薪酬
图片
像这里,“制造费用,生产成本,管理费用”就是这笔凭证的会计科目,“人工”就是这笔凭证的关键词,“职工薪酬”就是这笔凭证被分错的业务桶。
它这的意思是,从职工薪酬改到生产制造。
4.2 纠错增加命中
程序下次遇到类似的情况时,它会先查这个纠错表,会将它遇到的凭证,与这个纠错表进行详细核对。
其中,会计科目是“纠错指纹”,啥叫纠错指纹,就是只有跟这个会计科目几乎相同,才能命中。
比如,以上图纠错记录为例,程序在遇到的下一张凭证里,凭证所带会计科目是“生产成本,销售费用”,那即使这个凭证也拥有相同的关键词,那纠错也不会命中。
对此,我们设计了集合相似度,比如,纠错指纹是“制造费用,生产成本,管理费用”:
  • 新凭证A会计科目为:[生产成本]❌️无法命中
  • 新凭证B会计科目为:[生产成本,销售费用]❌️无法命中
  • 新凭证C会计科目为:[生产成本,制造费用]✅️可以命中
并不要求纠错指纹完全相等,只要集合相似度大于60%即可命中,大于60%通常就是其中1个会计科目不相似。
这样就能保留纠错机制一定的泛化能力,不太过于死板。

5. 制单人偏置

图片
我们来说最后一个模块,就是制单人偏置。
对应streamlit页面上就是这里:
图片
它对得分影响的效果如下:
图片
比如,如果制单人为应收会计,那销售收入桶会加分,其他桶会扣分。
但扣分也只限于,存货采购、长期资产、职工薪酬,生产制造这些具有明确业务特性的业务桶。
像日常费用、税费等业务桶,制单人通常不是很专一,所以我们不敢把范围设定得太宽,只能收窄一点。

三、最后

序时账清洗功能的灵感,来源于感知机。感知机就像一个简单的分类机器,它的公式构成是这样的:
图片
我的公式构成结构上也和它基本一致,也是向量点积再加上一些偏置项。
当然,训练上是不同的,机器学习中的训练,是机器自己根据结果来调整参数,而我这是设置好了明确的参数变动再传给机器。
同时,这篇文章说实在是有些复杂,其实看个大概结构就好了,如果要二次开发,可以直接让AI来阅读我这一篇文章,人看个大概就行,AI是可以看懂的。
如果文章在公众号里不好拿给AI看,我的开源地址里还有完整的md文章:
图片
当然,这篇技术文章也都是AI写的,我跟AI都相互review过了,AI还是太权威了。
看完不过瘾,那就自己发一篇吧!
© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
相关推荐
评论 抢沙发

请登录后发表评论

    暂无评论内容