遗传算法实现智能组卷
0.需求:用户选择知识点、年级、难度系数、题型、题目总数量,一键智能生成试卷,如下
1.遗传算法:
1.0 参考博文:
理论概念详解:https://www.jianshu.com/p/ae5157c26af9、https://www.cnblogs.com/artwl/archive/2011/05/19/2051556.html
实践:https://www.cnblogs.com/artwl/archive/2011/05/20/2052262.html、https://blog.csdn.net/qq_25237663/article/details/51742621
demo代码地址: https://github.com/jslixiaolin/GADemo
1.1定义
遗传算法以一种群体中的所有个体为对象,并利用随机化技术指导对一个被编码的参数空间进行高效搜索。其中,选择、交叉和变异构成了遗传算法的遗传操作;参数编码、初始群体的设定、适应度函数的设计、遗传操作设计、控制参数设定五个要素组成了遗传算法的核心内容。
1.2 遗传算法过程图解
1.3 大体实现过程:如上图
- 随机产生种群
- 根据策略判断个体的适应度,是否符合优化准则?若符合,输出最佳个体及最优解,结束。否则,进行下一步
- 依据适应度选择父母,适应度高的个体被选中的概率高,适应度低的被淘汰
- 用父母的染色体按照一定的方法进行交叉,产生新的子代
- 对子代染色体进行变异
由交叉和变异产生新一代种群,返回步骤2,直到最优解产生。
1.4 具体实现细节
1.4.1 编码coding(建立从基因型到表现型的映射关系):二进制编码法、浮点编码法、符号编码法(每种编码方法的优点和缺点可在博文中详细查看)。因为我们是根据题目的难度系数(0.0-5.0)进行智能组卷,所以采用的是浮点编码法
1.4.2 评价种群中个体适应度(fitness function)
- 定义:适应度函数也称为评价函数,是根据目标函数确定的用于区分群体中个体好坏的标准。适应度函数总是非负的,而目标函数总是有正有负,故需要在目标函数与适应度函数之间进行变换。在智能组卷系统中,评价个体适应度即是根据每道题目的难度系数进行筛选
- 评价个体适应程度的一般过程为:
1.对个体编码串进行解码处理,解码后可得到个体的表现型
2.由个体的表现型可计算出个体的目标函数值
3.根据最优化问题的类型,由目标函数值按照一定的转换规则求出个体的适应度
1.4.3 选择(selection)
- 定义:遗传算法的选择操作就是用来确定如何从父代群体中按照某些方法选取那些个体,以便遗传到下一代群体。选择操作用来确定重组或交叉个体,以及被选个体将产生多少个子代个体。
- 如何建立一种概率关系,避免适应度低的个体被选中?
1. 轮盘赌选择(选择误差较大)
2. 随机竞争选择(通过轮盘赌选择一对,让两个个体竞争,选满为止)
3. 最佳保留选择(首先通过轮盘赌选择执行遗传算法的选择操作,再将当前群体中适应度最高的个体结构完整地复制到下一代群体中)
4. 无回放随机选择,也叫期望值选择(根据每一个个体在下一代群体中的生存期望来进行随机选择运算)
5. 确定式选择(按照某一种确定的方式来进行选择操作)
6. 无回放余数随机选择(可确保适应度比平均适应度高一些的个体被遗传到下一代群体中,因而误差比较小)
7. 均匀排序(对群体中的个体按其适应度大小进行排序,基于这个排序来分配各个个体被选中的概率)
8. 最佳保存策略(当前群体中适应度最高的个体不参与交叉和变异运算,而是用它来代替本代群体中经过交叉、变异等操作后所产生的适应度最低的个体)
9. 随机联赛选择(每次选取几个个体中适应度最高的个体,遗传到下一代群体中)
10. 排挤选择(新生的子代个体将代替或排挤相似的旧父代个体,提高群体的多样性)
1.4.4 遗传–染色体交叉(crossover)
- 定义:遗传算法的交叉操作,是指两个相互配对的染色体按照某种方式相互交换其部分基因,从而形成两个新的个体
- 适用于二进制编码个体或浮点数编码个体的交叉算子:
1. 单点交叉:在个体编码串中只随机设置一个交叉点,然后在该点相互交换两个配对个体的部分染色体
2. 两点交叉与多点交叉
两点交叉:在个体编码串中随机设置两个交叉点,然后进行部分基因交换
多点交叉:同上类似
3. 均匀交叉(也称一致交叉):两个配对个体的每个基因座上的基因都以相同的交叉概率进行交换,从而形成两个新的个体
4. 算术交叉:两个个体的线性组合从而产生两个新的个体。该操作对象一般是由浮点数编码表示的编码
1.4.5 变异–基因突变(Mutation)
- 定义:遗传算法中的变异运算,是将个体染色体编码串中的某些基因座上的基因值用该基因座上的其他等位基因来替换,从而形成新的个体
- 以下变异算子适用于二进制编码和浮点数编码的个体:
1. 基本位变异:对个体编码串中以变异概率、随机指定的某一位或者某几位基因座上的值做变异运算
2. 均匀变异:分别用符合某一范围内均匀分布的随机数,以某一较小的概率来替换个体编码中各个基因座上的原有基因值。(特别适用于算法初级运行阶段)
3. 边界变异:随机地取基因座上的两个对应边界基因值之一去替代原有的基因值。特别适用于最优点或接近于可行解的边界时的一类问题
4. 非均匀变异:对原有的基因值做随机扰动,以扰动后的结果作为变异后的新基因值,对每个基因座都以相同的概率进行变异运算之后,相当于整个解向量在整个解空间做了一次轻微的变动
5.高斯近似变异:在进行变异时用一个均值μ、方差σ^2的正态分布的一个随机数来替换原有基因值。其操作过程与均匀变异类似。
二、遗传算法自动组卷中的使用
自动组卷是根据出卷者给定的约束条件(目前考虑试题总数量、总分、知识点分布、难度系数、题型比例等因素),搜索题库中与特征相匹配的试题,从而抽取最优的试题组合。由此可见,智能组卷问题是一个具有多重约束的组合优化问题
1.染色体的编码以及初始群体的设计
用遗传算法求解问题,首先要将问题的解空间映射成一组代码串,即染色体的编码问题。在传统的遗传算法中采用二进制编码。用二进制编码时,题库里的每一道题都要出现在这个二进制位串中,1表示该题选中,0表示该题未选中。这样的二进制位串较长,且在进行变异和交叉遗传算子操作时,各种题型的题目数量不好控制。采用实数编码方案,将一份试卷映射为一个染色体,组成该试卷的每道题的题号作为基因,基因的值直接用试题号表示,每种题型的题号放在一起,按题型分段,在随后的遗传算子操作时也按段进行,保证了每种题型的题目总数不变。比如,要组一份《C语言程序设计》试卷,其单选题6道,多选题4道,判断题5道,填空题5道,问答题3道,则染色体编码是:
(1、6、13、12、10、4 | 18、22、25、28 | 52、36、67、11、123 | 31、35、32、47、44 | 99、85、45)
单选题 多选题 判断题 填空题 问答题
试卷初始种群不是采用完全随机的方法产生,而是根据总题数、题型比例、总分等要求随机产生,使得初始种群一开始就满足了题数、总分等要求。这样加快遗传算法的收敛并减少迭代次数。可以采用分组实数编码,可以克服以往二进制编码搜索空间过大和编码长度过长的缺点,同时取消了个体的解码时间,提高了求解速度。
2.适应度函数的设计
适应度函数是用来评判试卷群体中个体的优劣程度的指标,遗传算法利用适应度值这一信息来指导搜索方向,而不需要适应度函数连续或可导以及其他辅助信息。因为题数、总分等要求在初始化种群时已经考虑,这里只剩下知识点分布和难度系数要考虑得了。所以适应度函数跟试卷难度系数和知识点分布有关。
**试卷难度系数公式:P=∑Di×Si/∑Si;其中i=1,2,…N,N是试卷所含的题目数,Di,Si分别是第i题的难度系数和分数。
知识点分布用一个个体知识点的覆盖率来衡量,例如期望本试卷包含N个知识点,而一个个体中所有题目知识点的并集中包含M个(M<=N),则知识点的覆盖率为M/N。用户的期望难度系数EP与试卷难度系数P之差越小越好,知识点覆盖率越大越好,因此适应度函数为:f=1-(1-M/N)f1-|EP-P|f2。其中f1为知识点分布的权重,f2为难度系数所占权重。当f1=0时退化为只限制试题难度系数,当f2=0时退化为只限制知识点分布。
3.遗传算子的设计
- 选择算子:选择算子的作用在于根据个体的优劣程度决定他在下一代是被淘汰还是被复制。通过选择,将使适应度高的个体有较大的生存机会。本系统采用轮盘赌选择方法,它是目前遗传算法中最常用也是最经典的选择方法。其具体实现为:规模为M的群体P中各个个体的适应度为P={A1、A2、…Am},其被选择概率为:Ai/∑Ai(i从0到m)。
- 交叉算子:由于在编码时采用的是分段实数编码,所以在进行交叉时采用分段单点交叉(按照题型分段来进行交叉),整个染色体就表现为多点交叉。交叉的实现过程:将群体中的染色体进行任意两两配对,对每对染色体产生一个[0,N-2]的随机数r,r即为分段点,将r后的两道题目互换(保证分值相加一样)得到下一代。交叉后生成的子代有可能因存在重复的题号而非法。出现这种情况要将出现的题号换成该段中没有出现的题号,从而得到新子代。
- 变异算子:在遗传算法中,变异概率一般较小。这里不分段进行变异,而是对某段上的某个基因进行变异。变异的操作如下:在[1,n]范围内随机生成一个变异位置P,以一定的原则从题库中选择一个变异基因,变异基因的选择原则为:与原基因题型相同的,分数相同,与至少包含原题目一个有效知识点(期望试卷中也有此知识点)。
4.具体实现
4.1设计两张表
-
一张表t_exam_smart_paper用于记录生成的试卷信息,具体字段如下图:
-
另外一张表t_exam_smart_paper_question用于存储组卷记录中的试题信息,具体字段如下图:
4.2 代码实现
1. 统一查询临时题库
1.1 参数(根据具体业务决定)
获取临时题库的业务层代码
public QuestionSetParam getQuestionSetNew(SmartRuleParamNew ruleParam) {
long time1, time2;
time1 = System.currentTimeMillis();
Byte difficulty = ruleParam.getDifficulty();
// 难度区间
Map<String, Object> rangeMap = getCoefficientRange(difficulty);
BigDecimal minVal = (BigDecimal) rangeMap.get("minVal");
BigDecimal maxVal = (BigDecimal) rangeMap.get("maxVal");
QuestionSetParam questionSetParam = new QuestionSetParam();
List<SmartPaperQuestionResponse> questionResponses;
// 知识点
String pointIds = "";
if (ruleParam.getPointIds() != null && ruleParam.getPointIds().length > 0){
pointIds = StringUtils.join(ruleParam.getPointIds(),",");
}
// 查询数据库
Byte stageId = ruleParam.getStageId();
Integer[] questionTypes = ruleParam.getQuestionTypes();
if (questionTypes != null && questionTypes.length > 0){
for (Integer questionType : questionTypes) {
byte questionTypeNew = questionType.byteValue();
if (questionType == 1 || questionType == 2) {
// 选择题
questionResponses = cQService.getQuestionListForApp(questionTypeNew, ruleParam.getStageName(stageId), minVal, maxVal, pointIds);
} else {
// 非选择题
questionResponses = qAService.getQuestionListForApp(questionTypeNew, ruleParam.getStageName(stageId), minVal, maxVal, pointIds);
}
questionSetParam.setByType(questionType, questionResponses);
}
}
time2 = System.currentTimeMillis();
logger.info("获取临时题库耗时:" + (time2 - time1) + "ms");
return questionSetParam;
}
返回实体QuestionSetParam
@Data
@ToString
public class QuestionSetParam implements Serializable {
private static final long serialVersionUID = -6100898488059029612L;
// 每种题型对应的列表
private List<SmartPaperQuestionResponse> answerList = new ArrayList<>();
private List<SmartPaperQuestionResponse> singleCList = new ArrayList<>();
private List<SmartPaperQuestionResponse> multiCList = new ArrayList<>();
private List<SmartPaperQuestionResponse> completeAList = new ArrayList<>();
private List<SmartPaperQuestionResponse> shortAList = new ArrayList<>();
private List<SmartPaperQuestionResponse> analysisAList = new ArrayList<>();
private List<SmartPaperQuestionResponse> essayAList = new ArrayList<>();
private List<SmartPaperQuestionResponse> thinkingAList = new ArrayList<>();
/**
* 获取对应题型的题目列表
* @param questionType 题型: 0问答题 1单选题 2多选题 3填空题 4简答题 5辨析题 6论述题 7思考题
* @return
*/
public List<SmartPaperQuestionResponse> getByType(int questionType){
switch (questionType){
case 0:
return getAnswerList();
case 1:
return getSingleCList();
case 2:
return getMultiCList();
case 3:
return getCompleteAList();
case 4:
return getShortAList();
case 5:
return getAnalysisAList();
case 6:
return getEssayAList();
case 7:
return getThinkingAList();
default:
return null;
}
}
/**
* 通过题型设置对应的题目列表
* @param questionType 题型: 0问答题 1单选题 2多选题 3填空题 4简答题 5辨析题 6论述题 7思考题
* @param list 题目列表
*/
public void setByType(int questionType,List<SmartPaperQuestionResponse> list){
switch (questionType){
case 0:
setAnswerList(list);
break;
case 1:
setSingleCList(list);
break;
case 2:
setMultiCList(list);
break;
case 3:
setCompleteAList(list);
break;
case 4:
setShortAList(list);
break;
case 5:
setAnalysisAList(list);
break;
case 6:
setEssayAList(list);
break;
case 7:
setThinkingAList(list);
break;
default:
break;
}
}
/**
* 获取数据库中对应题型的题目数量
* @param questionType 题型: 0问答题 1单选题 2多选题 3填空题 4简答题 5辨析题 6论述题 7思考题
* @return
*/
public Integer getTypeNum(int questionType){
switch (questionType){
case 0:
return getAnswerList().size();
case 1:
return getSingleCList().size();
case 2:
return getMultiCList().size();
case 3:
return getCompleteAList().size();
case 4:
return getShortAList().size();
case 5:
return getAnalysisAList().size();
case 6:
return getEssayAList().size();
case 7:
return getThinkingAList().size();
default:
return 0;
}
}
}
1. 设置迭代计数器和适应度期望值
// 迭代计数器
int count = 0;
int runCount = 10;
2. 初始化种群(设置种群规模、初始化标志以及组卷规则)
Population population = new Population(20, true, ruleParam);
public Population(int populationSize, boolean initFlag, SmartRuleParamNew ruleParam, Double expectCoefficient, QuestionSetParam questionSetParam) {
papers = new SmartPaperResponse[populationSize];
if (initFlag) {
SmartPaperResponse paper;
// 题型集合
Integer[] questionTypes = ruleParam.getQuestionTypes();
int multiple = ruleParam.getTotalNumber() / questionTypes.length;
// // 随机分配各个题型的题数,不超过总题数
// Integer[] questionNumList = {qtp.getAnswerNum(), qtp.getSingleCNum(), qtp.getMultiCNum(), qtp.getCompleteANum(),
// qtp.getShortANum(), qtp.getAnalysisANum(), qtp.getEssayANum(), qtp.getThinkingANum()};
Random random = new Random();
Integer totalNumber;
for (int i = 0; i < populationSize; i++) {
totalNumber = ruleParam.getTotalNumber();
paper = new SmartPaperResponse();
paper.setId(i + 1L);
paper.getQuestionList().clear();
if (multiple > 0) {
int typeNum = ruleParam.getTotalNumber() / multiple;
while (paper.getQuestionSize() != ruleParam.getTotalNumber() || totalNumber > 0) {
int sumNumber = 0;
for (Integer questionType : questionTypes) {
sumNumber += questionSetParam.getTypeNum(questionType);
if (totalNumber <= typeNum) {
typeNum = totalNumber;
}
generateQuestionNew(random, typeNum, questionSetParam.getByType(questionType), paper);
totalNumber = totalNumber - typeNum;
if (totalNumber == 0) {
if (paper.getQuestionSize() >= ruleParam.getTotalNumber()) {
break;
} else {
generateQuestionNew(random, (ruleParam.getTotalNumber() - paper.getQuestionSize()), questionSetParam.getByType(questionType), paper);
}
}
}
if (paper.getQuestionSize() >= sumNumber){
break;
}
}
} else {
throw new BaseException("您填写的题目数过少, 必须要大于您选的题型数量");
}
// 计算试卷知识点覆盖率
// if (ruleParam.getCategoryIds() != null && ruleParam.getCategoryIds().length > 0){
// paper.setKpCoverage(ruleParam.getCategoryIds());
// }
// 计算试卷适应度
paper.setAdaptationDegreeNew(Global.KP_WEIGHT, Global.DIFFICULTY_WEIGHT, expectCoefficient);
papers[i] = paper;
}
}
}
- 向试卷中添加各个类型的题目,并且保证不会添加重复的题目
/**
* 向试卷中添加对应题型的题目
*
* @param random 随机类
* @param questionNum 题目数量
* @param questionResponses 题目列表
* @param paper 智能组卷返回结果
*/
private void generateQuestionNew(Random random, Integer questionNum, List<SmartPaperQuestionResponse> questionResponses, SmartPaperResponse paper) {
if (questionResponses.size() < questionNum) {
for (int i = 0; i < questionResponses.size(); i++) {
if (!paper.containsQuestion(questionResponses.get(i))) {
paper.addQuestion(questionResponses.get(i));
}
// Collections.swap(questionResponses,questionResponses.size() - i - 1, i);
}
} else {
for (int j = 0; j < questionNum; j++) {
int index = random.nextInt(questionResponses.size() - j);
// 初始化分数
// questionResponses[index].setQuestionScore(score);
if (!paper.containsQuestion(questionResponses.get(index))) {
paper.addQuestion(questionResponses.get(index));
}
// 保证不会重复添加试题
Collections.swap(questionResponses, questionResponses.size() - j - 1, index);
}
}
}
- 计算试卷知识点覆盖率(由于项目尚在初期,题目数量还不够,防止出现题目数量不对等的情况,所以暂不引进知识点的关联)
/**
* 计算知识点覆盖率 公式为:个体包含的知识点/期望包含的知识点
*
* @param rule
*/
public void setKpCoverage(SmartRuleParam rule) {
if (kPCoverage == 0) {
List<String> categoryIds = Arrays.asList(rule.getCategoryIds());
Set<String> result = new HashSet<String>();
result.addAll(categoryIds);
Set<String> pointIds = new HashSet<>();
if ( questionList != null && questionList.size() > 0 ){
for (SmartPaperQuestionResponse response : questionList) {
if (response.getCategoryIds() != null){
List<String> list = Arrays.asList(response.getCategoryIds().split(","));
pointIds.addAll(list);
}
}
}
// Set<String> another = questionList.stream().map(questionBean -> String.valueOf(questionBean.getCategoryIds())).collect(Collectors.toSet());
// 交集操作
result.retainAll(pointIds);
Double resultSize = Double.valueOf(result.size());
Double categorySize = Double.valueOf(categoryIds.size());
kPCoverage = resultSize / categorySize;
}
}
- 计算试卷适应度
/**
* 计算个体适应度 公式为:f=1-(1-M/N)*f1-|EP-P|*f2
* 其中M/N为知识点覆盖率,EP为期望难度系数,P为种群个体难度系数,f1为知识点分布的权重
* ,f2为难度系数所占权重。当f1=0时退化为只限制试题难度系数,当f2=0时退化为只限制知识点分布
*
* @param rule 组卷规则
* @param f1 知识点分布的权重
* @param f2 难度系数的权重
*/
public void setAdaptationDegree(SmartRuleParam rule, double f1, double f2) {
if (adaptationDegree == 0) {
adaptationDegree = 1 - (1 - getKPCoverage()) * f1 - Math.abs(rule.getDifficulty() - getDifficulty()) * f2;
}
}
3. 进化种群
- 当迭代进化次数小于规定的实际迭代次数并且经过初始化的群体中最优秀的个体适应度小于适应度期望值时,不断进化种群
// 当迭代次数少于规定实际迭代的次数并且群体中适应度最高的个体适应度小于适应度期望值的时候,不断进化种群
while (count < runCount && population.getFitness().getAdaptationDegree() < expand) {
count++;
population = GAUtil.evolvePopulation(population, ruleParam);
logger.info("第 " + count + " 次进化,适应度为: " + population.getFitness().getAdaptationDegree());
}
- 采用精英主义保留上一代种群population最优秀个体
// 精英主义
if (elitism) {
elitismOffset = 1;
// 保留上一代最优秀个体
SmartPaperResponse fitness = population.getFitness();
fitness.setId(0L);
newPopulation.setPaper(0, fitness);
}
- 种群交叉操作,从当前的种群population来创建下一代种群newPopulation
// 种群交叉操作,从当前的种群population来创建下一代种群newPopulation
for (int i = elitismOffset; i < newPopulation.getLength(); i++) {
// 较优选择parent
SmartPaperResponse parent1 = select(population);
SmartPaperResponse parent2 = select(population);
while (parent2.getId() == parent1.getId()) {
parent2 = select(population);
}
// 交叉
SmartPaperResponse child = crossover(parent1, parent2, rule);
child.setId(Long.valueOf(i));
newPopulation.setPaper(i, child);
}
选择算子和交叉算子的实现:
- 选择算子的实现如下:
/**
* 选择算子
*
* @param population
*/
private static SmartPaperResponse select(Population population) {
Population pop = new Population(tournamentSize);
for (int i = 0; i < tournamentSize; i++) {
pop.setPaper(i, population.getPaper((int) (Math.random() * population.getLength())));
}
return pop.getFitness();
}
- 交叉算子实现如下:
/**
* 交叉算子
*
* @param parent1
* @param parent2
* @return
*/
public static SmartPaperResponse crossover(SmartPaperResponse parent1, SmartPaperResponse parent2, SmartRuleParam rule) {
SmartPaperResponse child = new SmartPaperResponse(parent1.getQuestionSize());
int s1 = (int) (Math.random() * parent1.getQuestionSize());
int s2 = (int) (Math.random() * parent1.getQuestionSize());
// parent1的startPos endPos之间的序列,会被遗传到下一代
int startPos = s1 < s2 ? s1 : s2;
int endPos = s1 > s2 ? s1 : s2;
for (int i = startPos; i < endPos; i++) {
child.saveQuestion(i, parent1.getQuestion(i));
}
// 难度类型: 简单 中等 困难
BigDecimal minVal;
BigDecimal maxVal;
switch (rule.getDifficulty()){
// 简单
case 1:
// coefficient的范围应该在0.00-1.50之间
minVal = BigDecimal.valueOf(0.00);
maxVal = BigDecimal.valueOf(1.50);
break;
// 中等
case 2:
// coefficient的范围应该在1.50-3.50之间
minVal = BigDecimal.valueOf(1.60);
maxVal = BigDecimal.valueOf(3.50);
break;
// 困难
case 3:
// coefficient的范围应该在3.50-5.00之间
minVal = BigDecimal.valueOf(3.60);
maxVal = BigDecimal.valueOf(5.00);
break;
default:
minVal = BigDecimal.valueOf(0.00);
maxVal = BigDecimal.valueOf(5.00);
}
// 继承parent2中未被child继承的question
// 防止出现重复的元素
// String pointIds = StringUtils.join(rule.getCategoryIds(),",");
for (int i = 0; i < startPos; i++) {
if (!child.containsQuestion(parent2.getQuestion(i))) {
child.saveQuestion(i, parent2.getQuestion(i));
} else {
Byte type = getTypeByIndex(i, rule);
// getQuestionArray()用来选择指定类型和知识点的试题数组
SmartPaperQuestionResponse[] questionResponses;
if (type == 1 || type == 2){
// 选择题
// questionResponses = gaUtil.cqService.getQuestionArray(type, rule.getPhaseId(), rule.getSubjectId(), minVal, maxVal, pointIds);
questionResponses = gaUtil.cqService.getQuestionArrayV1(type, rule.getPhaseId(), rule.getSubjectId(), minVal, maxVal);
}else {
// 问答题
// questionResponses = gaUtil.answersService.getQuestionArray(type, rule.getPhaseId(), rule.getSubjectId(), minVal, maxVal, pointIds);
questionResponses = gaUtil.answersService.getQuestionArrayV1(type, rule.getPhaseId(), rule.getSubjectId(), minVal, maxVal);
}
if (questionResponses.length > 0){
child.saveQuestion(i, questionResponses[(int) (Math.random() * questionResponses.length)]);
}
}
}
for (int i = endPos; i < parent2.getQuestionSize(); i++) {
if (!child.containsQuestion(parent2.getQuestion(i))) {
child.saveQuestion(i, parent2.getQuestion(i));
} else {
Byte type = getTypeByIndex(i, rule);
// getQuestionArray()用来选择指定类型和知识点的试题数组
SmartPaperQuestionResponse[] questionResponses;
if (type == 1 || type == 2){
// 选择题
// questionResponses = gaUtil.cqService.getQuestionArray(type, rule.getPhaseId(), rule.getSubjectId(), minVal, maxVal, pointIds);
questionResponses = gaUtil.cqService.getQuestionArrayV1(type, rule.getPhaseId(), rule.getSubjectId(), minVal, maxVal);
}else {
// 问答题
// questionResponses = gaUtil.answersService.getQuestionArray(type, rule.getPhaseId(), rule.getSubjectId(), minVal, maxVal, pointIds);
questionResponses = gaUtil.answersService.getQuestionArrayV1(type, rule.getPhaseId(), rule.getSubjectId(), minVal, maxVal);
}
if (questionResponses.length > 0){
child.saveQuestion(i, questionResponses[(int) (Math.random() * questionResponses.length)]);
}
}
}
return child;
}
- 种群变异操作:变异算子的实现
// 种群变异操作:本系统中变异概率为0.085,对种群的每个个体的每个基因都有变异机会.变异策略为:在(0,1)之间产生一个随机数,如果小于变异概率,代码如下
SmartPaperResponse tmpPaper;
for (int i = elitismOffset; i < newPopulation.getLength(); i++) {
tmpPaper = newPopulation.getPaper(i);
mutate(rule.getPhaseId(), rule.getSubjectId(), rule.getDifficulty(), tmpPaper);
// 计算知识点覆盖率与适应度
// tmpPaper.setKpCoverage(rule);
tmpPaper.setAdaptationDegree(rule, Global.KP_WEIGHT, Global.DIFFICULTY_WEIGHT);
}
4.获取种群中的最优个体为组卷结果
SmartPaperResponse resultPaper = population.getFitness();
/**
* 获取种群中最优秀个体
*
* @return
*/
public SmartPaperResponse getFitness() {
SmartPaperResponse paper = papers[0];
for (int i = 1; i < papers.length; i++) {
if (paper.getAdaptationDegree() < papers[i].getAdaptationDegree()) {
paper = papers[i];
}
}
return paper;
}