小编最近在闭关修改,忙于开发和研究算法,所以更新的速度严重滞后,今天终于可以分享成果了。
废话不多说,直接上图上代码!!!!!!!!!!!!!!
以上是系统总体的介绍,完成了大部分功能,还有很多细节需要处理。
系统主要的亮点是不仅有考试系统,还有在线学习的功能。组卷能手动也能自动,同时加了遗传算法。
首先了解一下概念(转载):
一、什么是遗传算法
遗传算法的首次提出是在1967年,Holland教授的学生Bagley在其博士论文中提到"GeneticAlgorithm" ,即GA;1975年Holland教授出版了第一本系统叙述其内容的专著,奠定理论基础。今天,遗传算法已经成为十分流行的算法,在组合优化,自动控制,图像处理,机器学习等领域都有很广泛的运用。
二、遗传算法的原理
遗传的算法的背景是达尔文的进化论,没错!算法的求解就是一个不停地繁衍、遗传、淘汰、进化的过程,以求出最适合的根。
三、进化论知识
作为遗传算法生物背景的介绍,下面内容了解即可:
种群(Population):生物的进化以群体的形式进行,这样的一个群体称为种群。
个体:组成种群的单个生物。
基因 ( Gene ) :一个遗传因子。
染色体 ( Chromosome ) :包含一组的基因。
生存竞争,适者生存:对环境适应度高的、牛B的个体参与繁殖的机会比较多,后代就会越来越多。适应度低的个体参与繁殖的机会比较少,后代就会越来越少。
遗传与变异:新个体会遗传父母双方各一部分的基因,同时有一定的概率发生基因变异。
简单说来就是:繁殖过程,会发生基因交叉( Crossover ) ,基因突变 ( Mutation ) ,适应度( Fitness )低的个体会被逐步淘汰,而适应度高的个体会越来越多。那么经过N代的自然选择后,保存下来的个体都是适应度很高的,其中很可能包含史上产生的适应度最高的那个个体。
遗传算法有3个最基本的操作:选择,交叉,变异。
选择:选择一些染色体来产生下一代。一种常用的选择策略是 “比例选择”,也就是个体被选中的概率与其适应度函数值成正比。假设群体的个体总数是M,那么那么一个体Xi被选中的概率为f(Xi)/( f(X1) + f(X2) + …….. + f(Xn) ) 。
交叉(Crossover):2条染色体交换部分基因,来构造下一代的2条新的染色体。例如:
交叉前:
00000|011100000000|10000
11100|000001111110|00101
交叉后:
00000|000001111110|10000
11100|011100000000|00101
染色体交叉是以一定的概率发生的,这个概率记为Pc 。
变异(Mutation):在繁殖过程,新产生的染色体中的基因会以一定的概率出错,称为变异。变异发生的概率记为Pm 。例如:
变异前:
000001110000000010000
变异后:
000001110000100010000
四、基本遗传算法优化
精英主义(Elitist Strategy)选择:是基本遗传算法的一种优化。为了防止进化过程中产生的最优解被交叉和变异所破坏,可以将每一代中的最优解原封不动的复制到下一代中。
五、系统主要功能介绍
命题与组卷:主要录入试题和试卷的创建
1、题目管理
对题目信息的管理,完成增、删、改、查、导入、导出功能。
2、试卷管理
创建试卷,完成完成增、删、改、查、导入、导出、手动组卷、自动组卷功能。
查看试卷,对已经组好的试卷可以查看详细内容。
手动组卷,创建试卷时,可以手动从题库中筛选试题。
自动组卷,创建试卷时,可以根据用户的要求随机从题库中筛选试题(通过遗传算法实现)。
测试管理,可以将生成的试卷给用户填写。分两种状态,可以启动试卷,也可以停止。
成绩统计,主要针对用户的考试成绩和作业成绩进行统计。
评阅管理,对已完成的试卷和作业进行评阅,系统先会自动打分,教师角色可以再次人式评阅。
前端学生登录,要以看到教师在后台新创建并启动的试卷
查看成绩,学生可以查看自己已经完成的试题成绩。
以上是系统部分功能,如果需要更详细的内容请私信小编。
六、接下来看一下主要的代码:
package com.online.study.common.arithmetic;
import com.online.study.entity.Question;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
/**
* 遗传算法组卷实现类
*/
@Component
public class GA {
/**
* 变异概率
*/
private static final double mutationRate = 0.085;
/**
* 精英主义
*/
private static final boolean elitism = true;
/**
* 淘汰数组大小
*/
private static final int tournamentSize = 5;
// 进化种群
public static Population evolvePopulation(Population pop, RuleBean rule, List<Question> questionList) {
Population newPopulation = new Population(pop.getLength());
int elitismOffset;
// 精英主义
if (elitism) {
elitismOffset = 1;
// 保留上一代最优秀个体
GetPaper fitness = pop.getFitness();
fitness.setId(0);
newPopulation.setPaper(0, fitness);
}
// 种群交叉操作,从当前的种群pop来创建下一代种群newPopulation
for (int i = elitismOffset; i < newPopulation.getLength(); i++) {
// 较优选择parent
GetPaper parent1 = select(pop);
GetPaper parent2 = select(pop);
while (parent2.getId() == parent1.getId()) {
parent2 = select(pop);
}
// 交叉
GetPaper child = crossover(parent1, parent2, rule, questionList);
child.setId(i);
newPopulation.setPaper(i, child);
}
// 种群变异操作
GetPaper tmpPaper;
for (int i = elitismOffset; i < newPopulation.getLength(); i++) {
tmpPaper = newPopulation.getPaper(i);
mutate(tmpPaper, questionList);
// 计算知识点覆盖率与适应度
tmpPaper.setKpCoverage(rule);
tmpPaper.setAdaptationDegree(rule, Global.KP_WEIGHT, Global.DIFFCULTY_WEIGHt);
}
return newPopulation;
}
/**
* 交叉算子
*
* @param parent1
* @param parent2
* @return
*/
public static GetPaper crossover(GetPaper parent1, GetPaper parent2, RuleBean rule, List<Question> questionList) {
GetPaper child = new GetPaper(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));
}
// 继承parent2中未被child继承的question
// 防止出现重复的元素
for (int i = 0; i < startPos; i++) {
if (!child.containsQuestion(parent2.getQuestion(i))) {
child.saveQuestion(i, parent2.getQuestion(i));
} else {
int type = getTypeByIndex(i, rule);
// 选择指定类型和知识点的试题数组
Question[] singleArray = questionList.stream().filter(q -> q.getType() == type).filter(q -> q.getCtypeId() == rule.getCtypeId()).filter(q -> q.getPointId() == 1 || q.getPointId() == 2).toArray(Question[]::new);
child.saveQuestion(i, singleArray[(int) (Math.random() * singleArray.length)]);
}
}
for (int i = endPos; i < parent2.getQuestionSize(); i++) {
if (!child.containsQuestion(parent2.getQuestion(i))) {
child.saveQuestion(i, parent2.getQuestion(i));
} else {
int type = getTypeByIndex(i, rule);
//查数据库
Question[] singleArray = questionList.stream().filter(q -> q.getType() == type).filter(q -> q.getCtypeId() == rule.getCtypeId()).filter(q -> q.getPointId() == 1 || q.getPointId() == 2).toArray(Question[]::new);
child.saveQuestion(i, singleArray[(int) (Math.random() * singleArray.length)]);
}
}
return child;
}
private static int getTypeByIndex(int index, RuleBean rule) {
int type = 0;
// 单选
if (index < rule.getSingleNum()) {
type = 1;
} else if (index < rule.getSingleNum() + rule.getCompleteNum()) {
// 填空
type = 2;
} else {
// 主观
type = 3;
}
return type;
}
/**
* 突变算子 每个个体的每个基因都有可能突变
*
* @param paper
*/
public static void mutate(GetPaper paper, List<Question> questionList) {
Question tmpQuestion;
List<Question> list;
int index;
for (int i = 0; i < paper.getQuestionSize(); i++) {
if (Math.random() < mutationRate) {
// 进行突变,第i道
tmpQuestion = paper.getQuestion(i);
Question tempQuetion2 = paper.getQuestion(i);
// 从题库中获取和变异的题目类型一样分数相同的题目(不包含变异题目)
list = questionList.stream().filter(q -> q.getType() == tempQuetion2.getType()).filter(q -> q.getCtypeId() == tempQuetion2.getCtypeId()).filter(q -> q.getScore() == tempQuetion2.getScore()).collect(Collectors.toList());
if (list.size() > 0) {
// 随机获取一道
index = (int) (Math.random() * list.size());
// 设置分数
list.get(index).setScore(tmpQuestion.getScore());
paper.saveQuestion(i, list.get(index));
}
}
}
}
/**
* 选择算子
*
* @param population
*/
private static GetPaper 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())));
//System.out.println("选择算子========================" + i);
}
return pop.getFitness();
}
}
package com.online.study.common.arithmetic;
import com.online.study.entity.Question;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Random;
/**
* 种群,即多套试卷
*/
@Component
public class Population {
public Population() {
}
/**
* 试卷集合
*/
private GetPaper[] papers;
/**
* 初始种群
*
* @param populationSize 种群规模
* @param initFlag 初始化标志 true-初始化
* @param rule 规则bean
*/
public Population(int populationSize, boolean initFlag, RuleBean rule,List<Question> questionList) {
papers = new GetPaper[populationSize];
if (initFlag) {
GetPaper paper;
Random random = new Random();
for (int i = 0; i < populationSize; i++) {
paper = new GetPaper();
paper.setId(i + 1);
while (paper.getTotalScore() != rule.getTotalMark()) {
paper.getQuestionList().clear();
// 单选题
if (rule.getSingleNum() > 0) {
generateQuestion(1, random, rule.getSingleNum(), rule.getSingleScore(),rule.getCtypeId(),paper,questionList);
}
// 填空题
if (rule.getCompleteNum() > 0) {
generateQuestion(2, random, rule.getCompleteNum(), rule.getCompleteScore(),rule.getCtypeId(),paper,questionList);
}
// 主观题
if (rule.getSubjectiveNum() > 0) {
generateQuestion(3, random, rule.getSubjectiveNum(), rule.getSubjectiveScore(),rule.getCtypeId(),paper,questionList);
}
}
// 计算试卷知识点覆盖率
paper.setKpCoverage(rule);
// 计算试卷适应度
paper.setAdaptationDegree(rule, Global.KP_WEIGHT, Global.DIFFCULTY_WEIGHt);
papers[i] = paper;
}
}
}
private void generateQuestion(int type, Random random, int qustionNum, double score,int ctypeId,GetPaper paper,List<Question> questionList) {
Question[] singleArray = questionList.stream().filter(q->q.getType()==type).filter(q->q.getCtypeId()==ctypeId).filter(q->q.getPointId()==1 || q.getPointId()==2).toArray(Question[]::new);
if (singleArray.length < qustionNum) {
return;
}
Question tmpQuestion=null;
for (int j = 0; j < qustionNum; j++) {
int index = random.nextInt(singleArray.length - j);
// 初始化分数
singleArray[index].setScore(score);
// 保证不会重复添加试题
if(paper.isExit(paper.getQuestionList(),singleArray[index].getId())) {
paper.addQuestion(singleArray[index]);
}
}
}
/**
* 获取种群中最优秀个体
*
* @return
*/
public GetPaper getFitness() {
GetPaper paper = papers[0];
for (int i = 1; i < papers.length; i++) {
if (paper.getAdaptationDegree() < papers[i].getAdaptationDegree()) {
paper = papers[i];
}
}
return paper;
}
public Population(int populationSize) {
papers = new GetPaper[populationSize];
}
/**
* 获取种群中某个个体
*
* @param index
* @return
*/
public GetPaper getPaper(int index) {
return papers[index];
}
/**
* 设置种群中某个个体
*
* @param index
* @param paper
*/
public void setPaper(int index, GetPaper paper) {
papers[index] = paper;
}
/**
* 返回种群规模
*
* @return
*/
public int getLength() {
return papers.length;
}
}
controller调用算法
/***遗传算法组*/
@PostMapping("/takePaperAuto")
public Result takePaperAuto(@RequestBody PaperDTO paperDTO) {
// 删除老的试卷
UpdateWrapper<PaperQuestion> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("paper_id", paperDTO.getPaperId());
paperQuestionService.remove(updateWrapper);
QueryWrapper<Question> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("ctype_id",paperDTO.getCtypeId());
// 根据课程id查出所有该课程的题目,然后再根据type去分
List<Question> questionList = questionService.list(queryWrapper);
List<Question> type1List = questionList.stream().filter(question -> question.getType() == 1).collect(Collectors.toList()); // 选择
List<Question> type2List = questionList.stream().filter(question -> question.getType() == 2).collect(Collectors.toList()); // 填空
List<Question> type3List = questionList.stream().filter(question -> question.getType() == 3).collect(Collectors.toList()); // 问答
if (null!=paperDTO.getType1() && type1List.size() < paperDTO.getType1()) {
throw new ServiceException("-1", "选择题数量不足");
}
if (null!=paperDTO.getType2() && type2List.size() < paperDTO.getType2()) {
throw new ServiceException("-1", "判断题数量不足");
}
if (null!=paperDTO.getType3() && type3List.size() < paperDTO.getType3()) {
throw new ServiceException("-1", "问答题数量不足");
}
// 开始随机组卷
List<PaperQuestion> paperQuestion = getPaperQuestionByGA(paperDTO,paperDTO.getPaperId(),questionList);
paperQuestionService.saveBatch(paperQuestion);
return Result.success();
}