首页
关于
Search
1
阿里云更换SSL证书
308 阅读
2
Nginx+Lua脚本控制负载均衡
175 阅读
3
地址相似度算法
166 阅读
4
【学习笔记】ES新特性
163 阅读
5
原生Hadoop搭建
140 阅读
默认分类
技术
JAVA
python
前端
大数据
运维
登录
Search
标签搜索
学习笔记
Javascript
前端
算法
负载均衡
Lua
Nginx
Kafka
Hive
Hbase
大数据
Hadoop
gitlab
CSS
HTML
ES语法
规则引擎
Drools
模型
springboot
枉自朝朝暮暮
累计撰写
12
篇文章
累计收到
1
条评论
首页
栏目
默认分类
技术
JAVA
python
前端
大数据
运维
页面
关于
搜索到
4
篇与
的结果
2023-08-01
地址相似度算法
地址相似度算法给到两个中文的地址,如何尽可能准确的获取这两个地址的相似度以判断这两个地址是否接近或一致?一般思路会有两种。一种是直接计算两个字符串的相似度,这种情况可能会因为模糊内容越多导致相似度计算不准确的情况,比如两个地区的同一个名字的街道,实际上在地理层面却相聚甚远。一种就是直接通过该地址的GPS数据进行距离计算,如果经纬度相近,则可以认为这两个地址是相近,但无法确认它们是同一个地址,除非经纬度基本一致。为了合理的判断两个地址的相似度以及地理上的相近情况。可以结合上面两种算法,共同计算相似度结果。对于字符串相似度算法有很多,比如余弦相似度、矩阵相似度、字符串编辑距离等等算法。本文会采用编辑距离算法中的Jaro-Winkler算法。1.编辑距离算法public class JaroWinklerDistance { private static final float threshold = 0.8f; public static float getDegree(String s1, String s2) { int[] mtp = matches(s1, s2); // 返回匹配数目(m) float m = (float) mtp[0]; if (m == 0) { return 0f; } // Jaro Distance float j = ((m / s1.length() + m / s2.length() + (m - mtp[1]) / m)) / 3; // 计算Jaro-Winkler Distance, 这里调整分数的因数=Math.min(0.1f, 1f / mtp[3]) float jw = j < threshold ? j : j + Math.min(0.1f, 1f / mtp[3]) * mtp[2] * (1 - j); return jw; } private static int[] matches(String s1, String s2) { String max, min; if (s1.length() > s2.length()) { max = s1; min = s2; } else { max = s2; min = s1; } // 两个分别来自s1和s2的字符如果相距不超过 floor(max(|s1|,|s2|) / 2) -1, 我们就认为这两个字符串是匹配的, // 因此,查找时, // 超过此距离则停止 int range = Math.max(max.length() / 2 - 1, 0); // 短的字符串, 与长字符串匹配的索引位 int[] matchIndexes = new int[min.length()]; Arrays.fill(matchIndexes, -1); // 长字符串匹配的标记 boolean[] matchFlags = new boolean[max.length()]; // 匹配的数目 int matches = 0; // 外层循环,字符串最短的开始 for (int mi = 0; mi < min.length(); mi++) { char c1 = min.charAt(mi); // 可能匹配的距离,包括从给定位置从前查找和从后查找 for (int xi = Math.max(mi - range, 0), xn = Math.min(mi + range + 1, max.length()); xi < xn; xi++) { // 排除被匹配过的字符,若找到匹配的字符,则停止 if (!matchFlags[xi] && c1 == max.charAt(xi)) { matchIndexes[mi] = xi; matchFlags[xi] = true; matches++; break; } } } // 记录min字符串里匹配的字符串,保持顺序 char[] ms1 = new char[matches]; // 记录max字符串里匹配的字符串,保持顺序 char[] ms2 = new char[matches]; for (int i = 0, si = 0; i < min.length(); i++) { if (matchIndexes[i] != -1) { ms1[si] = min.charAt(i); si++; } } for (int i = 0, si = 0; i < max.length(); i++) { if (matchFlags[i]) { ms2[si] = max.charAt(i); si++; } } // 查找换位的数目 int transpositions = 0; for (int mi = 0; mi < ms1.length; mi++) { if (ms1[mi] != ms2[mi]) { transpositions++; } } // 查找相同前缀的数目 int prefix = 0; for (int mi = 0; mi < min.length(); mi++) { if (s1.charAt(mi) == s2.charAt(mi)) { prefix++; } else { break; } } // 返回匹配数目(m),换位的数目(t),相同的前缀的数目,字符串最长 return new int[] { matches, transpositions / 2, prefix, max.length() }; } }2.GPS距离计算根据中文地址获取GPS数据,可以通过地址库或者其他厂商的API获得,需要地址的具体的经度和纬度数据,再根据经纬度计算出两地相距的距离。根据距离给定一个相似度。public class GpsUtils { /** * 地球半径 */ private static final double EARTH_RADIUS = 6378137; private static double rad(String d) { return Double.valueOf(d) * Math.PI / 180.0; } public static double distance(Location location1, Location location2) { if (location1 != null && location2 != null) { return distance(location1.getLng(), location1.getLat(), location2.getLng(), location2.getLat()); } return Integer.MAX_VALUE; } /** * 根据两点间经纬度坐标(double值),计算两点间距离,单位为米 * * @param lng1 * @param lat1 * @param lng2 * @param lat2 * @return */ public static double distance(String lng1, String lat1, String lng2, String lat2) { double radLat1 = rad(lat1); double radLat2 = rad(lat2); double a = radLat1 - radLat2; double b = rad(lng1) - rad(lng2); double s = 2 * Math.asin(Math.sqrt( Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))); s = s * EARTH_RADIUS; s = Math.round(s * 10000) / 10000; return s; } /** * 根据距离计算匹配度(参考) * * @param distance * @return */ public static float getDegreeByDistance(double distance) { float matchedDegree = 0f; if (distance > 10000) { matchedDegree = 0.0f; //( >10000) 0 } else if (distance > 5000) { matchedDegree = 0.1f; //(5000~10000] 0.1 } else if (distance > 2000) { matchedDegree = 0.2f; //(2000~5000] 0.2 } else if (distance > 1000) { matchedDegree = 0.3f; //(1000~2000] 0.3 } else if (distance > 500) { matchedDegree = 0.4f; //(500~1000] 0.4 } else if (distance > 200) { matchedDegree = 0.5f; //(200~500] 0.5 } else if (distance > 100) { matchedDegree = 0.6f; //(100~200] 0.6 } else if (distance > 50) { matchedDegree = 0.7f; //(50~100] 0.7 } else if (distance > 20) { matchedDegree = 0.8f;//(20~50] 0.8 } else if (distance > 10) { matchedDegree = 0.9f; //(10~20] 0.9 } else { matchedDegree = 0.95f;//[0~10] 0.9 } return matchedDegree; } }public class Location { private static final long serialVersionUID = 1L; /**经度*/ private String lng; /**纬度*/ private String lat; public Location(){ } public Location(String lng, String lat) { super(); this.lng = lng; this.lat = lat; } public String getLng() { return lng; } public void setLng(String lng) { this.lng = lng; } public String getLat() { return lat; } public void setLat(String lat) { this.lat = lat; } @Override public String toString() { return "Location [lng=" + lng + ", lat=" + lat + "]"; } } 3.最终计算(参考)//根据gps距离计算得到的匹配度 double distance = GpsUtils.distance(location1, location2); float gpsMatchedDegree = GpsUtils.getDegreeByDistance(distance); if (gpsMatchedDegree == 0.0f) { System.out.println("GPS距离过大"); return; } System.out.println("GPS匹配度:" + gpsMatchedDegree); //字符串相似度匹配 float stringDegree = JaroWinklerDistance.getDegree(address1, address2); System.out.println("字符串相似匹配度: " + stringDegree); float result = 0.0f; //根据两个匹配度确认最终相似度 if (stringDegree >= 0.5f && gpsMatchedDegree >= 0.5f) { result = Math.min(stringDegree, gpsMatchedDegree); } else if (stringDegree < 0.5f && gpsMatchedDegree < 0.5f) { result = Math.max(stringDegree, gpsMatchedDegree); } else { result = (stringDegree + gpsMatchedDegree) / 2; } System.out.println("最终匹配度: " + result);
2023年08月01日
166 阅读
0 评论
0 点赞
2022-05-02
springboot几种优雅关闭服务的方式
springboot几种优雅关闭服务的方式通过kill pid命令可触发的关闭服务资源的几种方式,kill -9 pid 无法触发!1.JVM自带的shutdownHookRuntime.getRuntime().addShutdownHook(new Thread(() -> log.info("shutdown!!!")));比如在启动函数中添加:@SpringBootApplication @Slf4j public class WebApplication { public static void main(String[] args) { java.lang.Runtime.getRuntime().addShutdownHook(new Thread(()->{ log.info("程序退出啦!"); })); SpringApplication.run(WebApplication.class, args); } }2.实现DisposableBean接口/* * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.beans.factory; /** * Interface to be implemented by beans that want to release resources on destruction. * A {@link BeanFactory} will invoke the destroy method on individual destruction of a * scoped bean. An {@link org.springframework.context.ApplicationContext} is supposed * to dispose all of its singletons on shutdown, driven by the application lifecycle. * * <p>A Spring-managed bean may also implement Java's {@link AutoCloseable} interface * for the same purpose. An alternative to implementing an interface is specifying a * custom destroy method, for example in an XML bean definition. For a list of all * bean lifecycle methods, see the {@link BeanFactory BeanFactory javadocs}. * * @author Juergen Hoeller * @since 12.08.2003 * @see InitializingBean * @see org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName() * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#destroySingletons() * @see org.springframework.context.ConfigurableApplicationContext#close() */ //由想要在销毁时释放资源的 Bean 实现的接口。BeanFactory 将在单个销毁作用域内的 Bean 时调用 destroy 方法。一个组织 springframework。上下文。ApplicationContext 应该在关闭时释放其所有单例,由应用程序生命周期驱动。Spring 管理的 bean 也可以出于相同的目的实现 Java 的 AutoCloseable 接口。实现接口的替代方法是指定自定义销毁方法,例如在 XML Bean 定义中。有关所有 Bean 生命周期方法的列表,请参阅 BeanFactory javadocs。 public interface DisposableBean { /** * Invoked by the containing {@code BeanFactory} on destruction of a bean. * @throws Exception in case of shutdown errors. Exceptions will get logged * but not rethrown to allow other beans to release their resources as well. */ //由包含的 BeanFactory 在销毁 Bean 时调用。抛出:异常 – 在发生关机错误的情况下。异常将被记录,但不会被重新抛出,以允许其他 Bean 也释放其资源。 void destroy() throws Exception; } 3.监听spring的ContextClosedEventspring提供了各种监听事件,可以参照以下示例(内容来自Spring官网)Sr.NoSpring Built-in EventsDescription1ContextRefreshedEventThis event is published when the ApplicationContext is either initialized or refreshed. This can also be raised using the refresh() method on the ConfigurableApplicationContext interface.2ContextStartedEventThis event is published when the ApplicationContext is started using the start() method on the ConfigurableApplicationContext interface. You can poll your database or you can restart any stopped application after receiving this event.3ContextStoppedEventThis event is published when the ApplicationContext is stopped using the stop() method on the ConfigurableApplicationContext interface. You can do required housekeep work after receiving this event.4ContextClosedEventThis event is published when the ApplicationContext is closed using the close() method on the ConfigurableApplicationContext interface. A closed context reaches its end of life; it cannot be refreshed or restarted.5RequestHandledEventThis is a web-specific event telling all beans that an HTTP request has been serviced.实现ApplicationListener接口,监听ContextClosedEvent@Component @Slf4j public class ShutdownListener implements ApplicationListener<ContextClosedEvent> { @Override public void onApplicationEvent(ContextClosedEvent event) { log.info("shutdown!!! "); } }4.使用注解@PreDestroyjavax.annotation.PreDestroy,/** * The PreDestroy annotation is used on methods as a callback notification to * signal that the instance is in the process of being removed by the * container. The method annotated with PreDestroy is typically used to * release resources that it has been holding. This annotation MUST be * supported by all container managed objects that support PostConstruct * except the application client container in Java EE 5. The method on which * the PreDestroy annotation is applied MUST fulfill all of the following * criteria: * <p> * <ul> * <li>The method MUST NOT have any parameters except in the case of * interceptors in which case it takes an InvocationContext object as * defined by the Interceptors specification.</li> * <li>The method defined on an interceptor class MUST HAVE one of the * following signatures: * <p> * void <METHOD>(InvocationContext) * <p> * Object <METHOD>(InvocationContext) throws Exception * <p> * <i>Note: A PreDestroy interceptor method must not throw application * exceptions, but it may be declared to throw checked exceptions including * the java.lang.Exception if the same interceptor method interposes on * business or timeout methods in addition to lifecycle events. If a * PreDestroy interceptor method returns a value, it is ignored by * the container.</i> * </li> * <li>The method defined on a non-interceptor class MUST HAVE the * following signature: * <p> * void <METHOD>() * </li> * <li>The method on which PreDestroy is applied MAY be public, protected, * package private or private.</li> * <li>The method MUST NOT be static.</li> * <li>The method MAY be final.</li> * <li>If the method throws an unchecked exception it is ignored except in the * case of EJBs where the EJB can handle exceptions.</li> * </ul> * * @see javax.annotation.PostConstruct * @see javax.annotation.Resource * @since Common Annotations 1.0 */ @Documented @Retention (RUNTIME) @Target(METHOD) public @interface PreDestroy { }PreDestroy 注释在方法上用作回调通知,以指示实例正在被容器删除。使用 PreDestroy 批注的方法通常用于释放它一直持有的资源。除 Java EE 5 中的应用程序客户机容器外,所有支持 PostConstruct 的容器托管对象都必须支持此注释。使用如下: @PreDestroy public void destroy() { log.info("Pre destroy shutdown!!! "); }对于@PreDestroy会存在一个不生效的情况,对于Scope为prototype的bean,Spring不会调用@PreDestory标记的方法。
2022年05月02日
119 阅读
0 评论
0 点赞
2021-09-10
Drools基础使用
Drools基础应用1. 为什么要使用规则擎if ("周一".equals(day)) { System.out.println("I will play basketball"); } else if ("周二".equals(day)) { System.out.println("I will do shopping"); } else if ("周三".equals(day)) { System.out.println("I will do shopping"); } else if ("周四".equals(day)) { System.out.println("I will do shopping"); } else if (...) { ... } else { ... }产品需求1:周二不应该do shopping,应该要play switch产品需求2:周五、周六、周日呢?实际生产情况可能涉及的规则更为复杂多变,如何修改上述代码呢。可能有人会使用策略模式、模板方法、装饰器、工厂方法之类,但那仅仅是针对于代码层面的优化。但针对于复杂的业务逻辑,还需要做到以下几点:简化if else结构,让业务逻辑和数据分离!分理处的业务逻辑必须要易于编写,至少单独编写这些业务逻辑,要比写代码快!分离出的业务逻辑必须要比原来的代码更容易读懂!分离出的业务逻辑必须比原来的易于维护,至少改动这些逻辑,应用程序不用重启!基于此种需求,规则引擎诞生了。规则引擎是一种嵌套在应用程序中的组件,它实现了将业务规则从应用程序代码中分离出来。规则引擎使用特定的语法编写业务规则,规则引擎可以接受数据输入、解释业务规则、并根据业务规则做出相应的决策。市面上的规则引擎框架有很多,Aviator、Drools、RasyRules、RuleBook、Ikexpression、MVEL、JRules、JLisa、QuickRules等等。但是更多的公司还是选择Drools作为生产应用的规则引擎。2.Drools简介Drools是由Java语言开发的开源业务规则引擎,其具有一个易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准、速度快、效率高。业务分析师或者审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。Drools具有以下优点:活跃的社区支持简洁易用快速的执行速度:Rete算法在Java开发人员中流行2.1 HelloWorld2.1.1引入Maven依赖 <!-- drools --> <dependency> <groupId>org.kie</groupId> <artifactId>kie-internal</artifactId> <version>7.10.0.Final</version> </dependency> <dependency> <groupId>org.kie</groupId> <artifactId>kie-api</artifactId> <version>7.10.0.Final</version> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-core</artifactId> <version>7.10.0.Final</version> </dependency> <dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>7.10.0.Final</version> </dependency>2.1.2创建一个JavaBeanpublic class DailyProgram { int day; String plan = "无"; public int getDay() { return day; } public void setDay(int day) { this.day = day; } public String getPlan() { return plan; } public void setPlan(String plan) { this.plan = plan; } }2.1.3创建kmodule.xml,放在resources目录的META-INF文件夹下<?xml version="1.0" encoding="UTF-8"?> <kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule"> <kbase name="daily_program" packages="rules"> <ksession name="daily_program_session" type="stateful"/> </kbase> </kmodule>2.1.4创建规则文件dailyprogram.drlpackage wang.myrule; import wang.smalleyes.drools.bean.DailyProgram; function String hello(String name){ return "Hello " + name + "!"; } rule "day1" when $dailyProgram: DailyProgram(day == 1); then $dailyProgram.setPlan("I will play basketball"); System.out.println( "exec rule day1 ... " ); System.out.println( hello("day1") ); end 2.1.5测试规则运行public void testRules1(){ //创建KieServices KieServices kieServices = KieServices.get(); //创建KieServices,classPathContainer默认获取META-INF下的kmodule.xml KieContainer kieContainer = kieServices.getKieClasspathContainer(); //按名称指定获取KieSession KieSession kieSession = kieContainer.newKieSession("daily_program_session"); DailyProgram dailyProgram = new DailyProgram(); dailyProgram.setDay(1); kieSession.insert(dailyProgram); int fireCount = kieSession.fireAllRules(); kieSession.dispose(); System.err.println(dailyProgram.getPlan()); System.out.println("规则执行击中" + fireCount + "条规则"); }运行结果如下:2.2重要的APIKieServices:KIE整体的入口,可以用来创建下面这些API等。KieContainer:KieBase的容器,根据kmodule.xml里描述的信息获取具体的KieSession、KieBase;KieBase:就是一个知识仓库,包含了若干的规则、流程、方法等,在Drools中主要就是规则和方法,KieBase本身并不包含运行时的数据之类的,如果需要执行规则KieBase中的规则的话,就需要根据KieBase创建KieSessionKieSession:KieSession就是一个跟Drools引擎打交道的会话,其基于KieBase创建,它会包含运行时数据,包含“事实 Fact”,并对运行时数据事实进行规则运算KieModule:是一个包含了多个kiebase定义的容器。一般用kmodule.xml来表示2.3KieSessionKieSession在创建时可指定创建为有状态Session或无状态Session。有状态Session:它可以在交互的同时,保持住状态。这就是有状态的会话。当我们不想再使用KieSession的时候,那么我们必须显示的调用dispose()方法来声明下。有状态Session使用fireAllRules()方法执行规则。无状态Session:无状态的KieSession会将这种会话描述为一个函数,具有无状态以及原子性。理想情况下,函数不应该具有任何间接影响。无状态Session使用execute()方法执行规则。在Kmodule.xml文件中定义Kbase下的Kession,由属性type可以控制无状态(stateless)或者有状态(stateful)。或者通过以下API创建kieContainer.newStatelessKieSession(”daily_program_session“);有状态和无状态的区别在于多次会话时,有状态会保留前次计算的信息,而无状态多次运算之间是不相互影响的。2.4规则文件语法2.4.1 package在一个规则文件当中 package 是必须的,而且必须放置在文件的第一行。package 的名字是随意的,不必必须对应物理路径,这里跟 java 的package 的概念不同,只是逻辑上的区分,但建议与文件路径一致。同一的 package 下定义的 function 和 query 等可以直接使用。如:package wang.myrule;2.4.2 import导入规则文件需要的外部变量,使用方法跟 java 相同。 和 java 的 import 一样, 还可以导入类中的某一个可访问的静态方法。import wang.smalleyes.drools.bean.DailyProgram;2.4.3 globalglobal用于定义全局变量。它可以让应用程序的对象在规则文件中能够被访问。通常,可以用来为规则文件提供数据或服务。在规则执行前由kieSession注入。@Autowired private EmailService emailService ... kieSession.setGlobal("emailService", emailService); drl文件中的用法:import wang.smalleyes.drools.EmailService; global EmailService $emailService; rule "rule1" when ... then $emailService.sendEmail() end2.4.4 方法定义——function将语义代码放置在规则文件中的一种方式,就相当于java类中的方法package wang.myrule; import wang.smalleyes.drools.bean.DailyProgram; function String hello(String name){ return "Hello " + name + "!" } rule "rule1" when ... then hello("tom"); end2.4.5规则定义rule "rule1" //规则名称,全局唯一 attributes //规则属性 when LHS //规则执行条件 then RHS //规则执行结果 end常用的attributes规则属性:no-loop:定义当前的规则是否不允许多次循环执行,默认是 false,也就是当前的规则只要满足条件,可以无限次执行。当执行Update、insert方法更新Fact对象时,会触发规则执行。salience:规则执行顺序,值越大,越优先执行3.Springboot整合问题在本地运行时,规则中的globals引用的外部服务,始终无法被Drools找到,显示Null。经过Debug后,发现是引用了SpringBoot的dev-tools依赖,开启了热加载。原因:dev-tools开启后,被Spring管理的bean的类加载器使用了自定义的RestartClassLoader。而Drools在运行时,获取的类的类加载器是AppClassLoader。因为类加载器不一致,并不是由双亲委派模型的下的上级类加载器加载进来,对于一个类,由不同的类加载器实例加载的话,会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同Class实例(所有通过正常双亲委派模式的类加载器加载的classpath下的和ext下的所有类在方法区都是同一个类,堆中的Class实例也是同一个)。因为两者不一致,所以drools在workingmemory中使用的是AppAppClassLoader的实例自然无法找到由RestartClassLoader加载的实例。解决方式:将Drools升级至7.23.0FINAL或更高版本。取出SpringBoot的dev-tools依赖。4.Drools热部署——实时更新KieBase@Test public void testRules(){ KieServices kieServices = KieServices.get() KieContainer kieContainer = kieServices.getKieclasspathContainer() KieSession kieSession = kiecontainer.newKieSession("daily_program_ session") KnowledgeBaseImpl kieBase(KnowledgeBaseImpl)kieContainer.getKieBase("daily_program"); //实时更新规则 insertRules(kieBase) DailyProgram dailyProgram = new DailyProgram(); dailyProgram.setDay(1); kieSession.insert(dailyProgram) DailyProgram dailyProgram2 = new DailyProgram(); dailyProgram2.setDay(2); kieSession.insert(dailyProgram2) int firecount = kiesession,fireAllRules() kieSession.dispose() System.err.println(dailyProgram.getPlan()); System.err.println(dailyProgram2.getPlan()); System.out.println("规则执行击中”+firecount +"条规则") } private void insertRules(KnowledgeBaseImpl kieBase){ String rule = "package com.myrule;\n" + "\n" + "import wang.smalleyes.drools.bean.DailyProgram;\n" + "\n" + "rule \"day2\"\n" + "\n" + "when\n" + "$dailyProgram: DailyProgram(day == 2);\n" + "then\n" + "$dailyProgram.setPlan(\"I will do shopping\");\n" + "System.err.println(\"exec rule day2 ...\");\n" + "end\n"; KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(kieBase); kbuilder .add(ResourceFactory.newByteArrayResource(rule.getBytes()) .setSourcePath("com/myrule/new.drl") ,ResourceType.DRL). if(kbuilder.hasErrors()){ System.err.println("规则加载异常"); } Collection<KiePackage> kiePackages = kbuilder.getKnowledgePackages(); kieBase.addPackages(kiePackages); }KnowledgeBuilder:在业务代码中收集已编写的规则,并对规则文件进行编译,生成编译好的KnowledgePackage集合,提供给其他API使用。KiePackage:编译好的规则包执行结果:
2021年09月10日
114 阅读
0 评论
0 点赞
2021-08-02
模型概率值转换分值方法
PMML模型概率值转换分数数据组给出的PMML模型文件通过Java的jpmml运行后获取的值是一个(0,1)的概率值,指的是概率为1的值(二分类模型)。项目上需要将其均匀转换成一个分数,但是不同模型所属场景不同,需要转换的目标也不同。大致有两种情况,一种是模型阈值为0.5,一种是模型阈值不为0.5,这两种的Java转换思路如下。0-100标准分数且模型阈值不为0.5。这种情况一般模型会给出一个模型阈值,即区分好坏样本的中间值,一般的二分类模型可能是0.5,即大于0.5则为1,小于0.5则为0。但是实际生成中这个值会根据模型情况变动,不一定为0.5,这时候就需要结合该值进行转换,转换后0-100的分数中间值为50。/** * @param p 特征重要性 * @param modelScore 模型概率 * @return 模型分数 */ private String exchange(Double p, String modelScore) { //如果是科学计算,需要转换下 if (modelScore.contains("E")) { modelScore = new BigDecimal(modelScore).toPlainString(); } double x = Double.parseDouble(modelScore); double k; if (x > p) { if (p > 1 - 1E-6) { k = 1 / (x - p); } else { k = 1 / (1 - p); } } else { if (p < 1E-6) { k = 1; } else { k = 1 / p; } } //50+50*转化斜率*(概率值与模型阈值的差),String expression = "50+50*k*(x-p)"; double score = Math.abs(50 + 50 * k * (x - p)); return String.format("%.2f", score); }例如,当模型阈值为0.45,模型概率值为0.5,映射到0-100分数为:50 + 50 * 1 / (1 - 0.45) * (0.5 - 0.45) = 54.55大致推导过程可以将映射过程分为两段即[0, k)映射到[0-50), [k,1]映射到[50,100]。并合并成通项公式,转换到其他区间也是如此。模型阈值为0.5,并转换为300-900/** * 模型分输出分值, 0-1概率,转换为300-900 * 1-高风险, 0-低风险 * @param targetValue 目标值 * @return */ private String exchange(Double targetValue){ String modelScore = ""; if(targetValue == null){ return modelScore; } BigDecimal original = BigDecimal.valueOf(targetValue); int score = new BigDecimal(300).add(new BigDecimal(600).multiply(original)).intValue(); //极值处理 if(score == 300){ score = score + RandomUtils.nextInt(0,10); } if(score == 900){ score = score - RandomUtils.nextInt(0,10); } modelScore = String.valueOf(score); return modelScore; }公式如下newScore = 300 + ((oldScore - 0) * (900 - 300) / (1 - 0)) = 300 + 600 * oldScore推导如下:假设我们有一个原始区间 [a, b],我们想要将其均匀放大到目标区间 [c, d]。为了完成这个映射,我们可以使用以下的线性变换公式:x_new = c + ((x_old - a) * (d - c) / (b - a))其中:x_old 是原始区间 [a, b] 内的任意数。x_new 是 x_old 映射到目标区间 [c, d] 后的对应数。a 和 b 是原始区间的下限和上限。c 和 d 是目标区间的下限和上限。这个公式的原理是,首先计算 x_old 在原始区间内的相对位置(通过 (x_old - a) / (b - a) 得到,其值在 0 到 1 之间),然后将这个相对位置乘以目标区间的长度 (d - c),最后加上目标区间的下限 c,从而得到 x_new。但该公式仅用于模型阈值为0.5的情况,也就是均匀分布,均匀放大。
2021年08月02日
139 阅读
0 评论
0 点赞