├── CodeCraft-2019.cpp ├── README.md ├── checker.hpp ├── map.hpp ├── pytorch_plate ├── model │ ├── config.json │ └── customize_service.py ├── resnet_train.py └── train.ipynb ├── scheduler.hpp ├── task ├── 2019华为软件精英挑战赛-决赛-任务书-v3.0.docx ├── 2019华为软件精英挑战赛-决赛-需求变更-v3.1.docx ├── 2019华为软件精英挑战赛-初赛-任务书-v1.5.docx ├── 2019华为软件精英挑战赛-复赛-任务书-v2.1.docx └── 2019华为软件精英挑战赛-复赛-需求变更-v2.1.docx └── 参赛历程-reku.md /CodeCraft-2019.cpp: -------------------------------------------------------------------------------- 1 | #define RUN 2 | #include "map.hpp" 3 | #include "checker.hpp" 4 | #include "scheduler.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | int main(int argc, char *argv[]) { 13 | std::cout << "Begin" << std::endl; 14 | if (argc < 6) { 15 | std::cout << "please input args: carPath, roadPath, crossPath, presetAnswerPath, answerPath" << 16 | std::endl; 17 | exit(1); 18 | } 19 | std::string carPath(argv[1]); 20 | std::string roadPath(argv[2]); 21 | std::string crossPath(argv[3]); 22 | std::string presetAnswerPath(argv[4]); 23 | std::string answerPath(argv[5]); 24 | std::cout << "carPath is " << carPath << std::endl; 25 | std::cout << "roadPath is " << roadPath << std::endl; 26 | std::cout << "crossPath is " << crossPath << std::endl; 27 | std::cout << "presetAnswerPath is " << presetAnswerPath << std::endl; 28 | std::cout << "answerPath is " << answerPath << std::endl; 29 | auto mp = std::unique_ptr(new Map(carPath, roadPath, crossPath, presetAnswerPath)); 30 | mp->print(); 31 | std::vector I(mp->n_cars); 32 | iota(I.begin(), I.end(), 0); 33 | 34 | #ifdef CHECK 35 | auto Sch = mp->read_answer(answerPath); 36 | for (int i = 0; i < mp->n_cars; i++) 37 | if (mp->cars[i].preset && Sch[i].Path.empty()) 38 | Sch[i] = mp->Preset[i]; 39 | int ttt = clock(); 40 | std::unique_ptr ch(new Checker(*mp)); 41 | int s = ch->run(Sch, I, 100000); 42 | printf(s == DEADLOCK ? "DEADLOCK\n" : s == FINISH ? "FINISH\n" : "TIMEUP\n"); 43 | printf("判题器用时: %.1f\n", double(clock() - ttt) * 1000 / CLOCKS_PER_SEC); 44 | ch->print(); 45 | #endif 46 | #ifdef RUN 47 | std::vector Sch(mp->n_cars); 48 | // 读入预置路径 49 | for (int i = 0; i < mp->n_cars; i++) if (mp->cars[i].preset) Sch[i] = mp->Preset[i]; 50 | 51 | int max_ts = 100000, preset_ts, preset_tpri, preset_te; 52 | std::tie(preset_ts, preset_tpri, preset_te) = 53 | std::unique_ptr(new Scheduler(*mp))->change_preset(Sch, 100000, 0.1); 54 | 55 | auto ser = std::unique_ptr(new Scheduler(*mp)); 56 | int ts, tpri, te; 57 | std::tie(ts, tpri, te) = ser->gen_schedule(Sch, -1, max_ts, max_ts * (1 + mp->a_), true); 58 | printf("Margin -1: %d, %d, %d\n", ts, tpri, te); 59 | mp->print_schedule(Sch, answerPath); 60 | 61 | int best_te = te; 62 | int l = 0, r = tpri - preset_tpri; 63 | while (l + 1 < r) { 64 | if (double(clock() - start_time) / CLOCKS_PER_SEC > MAX_PROGRAM_SECS) break; 65 | int margin = l + (r - l + 1) / 3; 66 | printf("Margin: %d checking\n", margin); 67 | if (margin + preset_tpri >= tpri) { puts("Margin too large"), r = margin; continue; } 68 | try { 69 | std::tie(ts, tpri, te) = ser->gen_schedule(Sch, margin, ts * (1 + mp->a_), best_te, false); 70 | printf("Margin %d: %d, %d, %d\n", margin, ts, tpri, te); 71 | if (te < best_te) { 72 | best_te = te; 73 | mp->print_schedule(Sch, answerPath); 74 | } 75 | r = margin; 76 | } catch (std::logic_error &e) { 77 | std::cout << "Margin " << margin << ": " << e.what() << std::endl; 78 | l = margin; 79 | } 80 | } 81 | printf("Best Te: %d\n", best_te); 82 | #endif 83 | printf("程序运行用时: %.1f\n", double(clock() - start_time) * 1000 / CLOCKS_PER_SEC); 84 | return 0; 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 判题器 2 | * 完整运行一次用时在1s以内 3 | * 支持单时间片运行 4 | * 记录了一些路况信息,如道路在某时刻的车辆数,路上车辆的平均速度以及车辆通过道路的平均时差 (时差定义为真实用时与预估用时的差) 5 | 6 | ### 调度器 7 | * 主要思想是按照时间片枚举,对于非预置车辆,二分当前时刻发车数量,用判题器检测是否可以发车。通过限制单时间片的最大发车数量以及二分次数减少计算时间 8 | * 每个时间片的候选车辆按照规则排序,优先发送:1. 优先车辆; 2. 预计发车后可能导致te增大的车辆; 3. 车速慢的车辆; 4. 预计用时长的车辆 9 | * 判题器对象ahead维护未来路况,用于规划路径 10 | * 判题器对象current维护当前路况,用于在发车失败时快速回到未发车状态,无需每次都从0时刻跑判题器 11 | * 规划路径采用Dijkstra算法(伪最短路,因为边的权重是动态的) 12 | * 路权计算比较玄学,建议直接看代码。主要思想是预估车辆驶入到某条道路上的时刻,用ahead查询前后的路况信息(如路上车辆数),结合车辆自身车速,道路和路口属性等因素计算出道路和路口的cost,再和预计通过道路所用的时间进行加权得到最终权重 13 | 14 | ### 关于需求更改 15 | * 为了防止写出新bug,只做了一些简单的处理。修改预置车辆时间均直接改为计划出发时间,修改路径均改为最短路。 16 | * 对每一辆预置车辆,比较其预置路径预计用时+计划出发时间与实际出发时间+最短路预计用时,选择预计最终到达时间较小的方案 17 | * 有两种排序方案: 1. 优先修改修改后预计到达时间减少最多的; 2. 优先修改未修改前到达时间最晚的 18 | * 排序后,二分修改的预置车辆数,如果不会出现死锁,就尽可能地修改更多预置车辆 19 | * 两种排序方案取使得只跑预置车辆的te较小的 20 | 21 | ### 关于优先车辆 22 | 非优先车辆发车太多可能会把优先车辆堵在路上,导致最终成绩很差。因此我们引入了唯一的一个可调参数margin,用于限制优先车辆的tpri,保证其与预计tpri的下界的差不会超过margin。第一次运行不设限制,确保有answer生成,然后对margin二分,期望找到更好的answer 23 | 24 | ### 关于车牌识别 25 | 我们的方法非常简单,就是用pytorch自带的resnet-34模型训练一个端到端的网络。准确度大概在98%左右,希望和99%的大佬交流一下 26 | 27 | ### 其他 28 | * 在规划路径时,路况只考虑预置车辆以及在本时刻之前已发车的非预置车辆,没有考虑到本时刻及之后发车的非预置车辆 29 | * 设想过在预计车辆通过道路的时间时用机器学习模型,不过因为我们是C++不太方便,所以没有尝试过 30 | * 欢迎大佬们开issue讨论,或者留下自己repo的链接 31 | -------------------------------------------------------------------------------- /checker.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHECKER_H 2 | #define CHECKER_H 3 | 4 | #include "map.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | enum { WAIT, END }; // 车辆状态 16 | enum { DEADLOCK, FINISH, OK, TIMEUP }; // 系统状态 17 | enum { SUCCESS, FAIL, FULL, LIMIT, GOAL }; // 尝试行驶的结果 18 | 19 | 20 | struct CarInfo { 21 | // 行驶车辆的状态 22 | // rd: 车辆目前所在道路在其规划路径上的位置(0下标) 23 | // pos: 对路上车辆表示其位置,合法值为从1到道路长度 24 | // lane: 车辆当前所在的车道编号 25 | // state: 车辆状态,等待或终止 26 | // dir: 车辆当前方向 27 | // ts_on: 车辆驶入当前道路的时刻 28 | // ts_at: 车辆准备过路口的时刻 29 | int prior, speed, rd, pos, lane, ts_on, ts_at; 30 | char state, dir; 31 | const Car &car; 32 | const Schedule &sch; 33 | const Map ∓ 34 | CarInfo(const Car &car, const Schedule &sch, const Map &mp) : 35 | prior(car.prior), speed(car.speed), car(car), sch(sch), mp(mp) {} 36 | // 找到车辆的行进方向 37 | int get_dir() { 38 | const auto &Path = sch.Path; 39 | if (rd + 1 == int(Path.size())) return FIN; 40 | int now_rd = Path[rd].first, nxt_rd = Path[rd + 1].first; 41 | int a = mp.roads[now_rd].from, b = mp.roads[now_rd].to, 42 | c = mp.roads[nxt_rd].from, d = mp.roads[nxt_rd].to; 43 | int cross_i = (a == c || a == d) ? a : b; 44 | int i = mp.RI[cross_i][now_rd], j = mp.RI[cross_i][nxt_rd]; 45 | int x = (j - i + 4) & 3; 46 | return x == 1 ? LEFT : x == 2 ? STRAIGHT : RIGHT; 47 | } 48 | }; 49 | 50 | struct Checker { 51 | const Map ∓ 52 | const double alpha = 0.95; 53 | int n, n_p, n_pre, n_pre_p, n_arrive, n_arrive_p, n_sent, n_wait; 54 | int ts, tpri, tsum, tsumpri, te, tesum, prev_ts = 0; 55 | 56 | // 对已达终点的车辆,表示其实际调度时间 57 | std::vector car_time; 58 | // 道路正反方向上每个时刻的车辆数,平均车速和用时偏差 59 | std::vector>> road_info[2]; 60 | // 维护当前时刻道路上的车辆数 61 | std::vector road_cnt_cur[2]; 62 | // 维护当前时刻道路上的平均车速 63 | std::vector road_speed_cur[2]; 64 | // 道路上车辆的真实用时和预测用时的平均差值 65 | std::vector road_bias[2]; 66 | // 路口平均延时,偷懒没有分时间片记录 67 | std::vector cross_pass_cnt; 68 | std::vector cross_bias; 69 | // 每个时刻预计出发的车辆 70 | std::vector> start_ts; 71 | // 当前时刻每条道路上车库中待出发的车辆 72 | std::vector> ready[2][2]; 73 | // 正/反向道路车道 74 | std::vector>> car_roads[2]; 75 | 76 | Checker(const Map &mp) : mp(mp) { 77 | for (int is_forward : {0, 1}) { 78 | car_roads[is_forward].resize(mp.n_roads); 79 | for (int i = 0; i < mp.n_roads; i++) { 80 | car_roads[is_forward][i].resize(mp.roads[i].num); 81 | } 82 | } 83 | n = n_p = n_pre = n_pre_p = n_arrive = n_arrive_p = n_sent = ts = tsum = tpri = tsumpri = 0; 84 | car_time.assign(mp.n_cars, 0); 85 | for (int is_forward : {0, 1}) { 86 | road_info[is_forward].resize(mp.n_roads); 87 | road_cnt_cur[is_forward].assign(mp.n_roads, 0); 88 | road_speed_cur[is_forward].assign(mp.n_roads, 0.0); 89 | road_bias[is_forward].assign(mp.n_roads, 0.0); 90 | ready[is_forward][0].resize(mp.n_roads); 91 | ready[is_forward][1].resize(mp.n_roads); 92 | for (int i = 0; i < mp.n_roads; i++) { 93 | road_info[is_forward][i].assign(1, {-1, 0, 0.0, 0.0}); 94 | ready[is_forward][0][i].clear(), ready[is_forward][1][i].clear(); 95 | } 96 | } 97 | cross_pass_cnt.assign(mp.n_crosses, 0); 98 | cross_bias.assign(mp.n_crosses, 0.0); 99 | } 100 | 101 | Checker(const Checker &o): mp(o.mp), n(o.n), n_p(o.n_p), n_pre(o.n_pre), n_pre_p(o.n_pre_p), 102 | n_arrive(o.n_arrive), n_arrive_p(o.n_arrive_p), n_sent(o.n_sent), n_wait(o.n_wait), 103 | ts(o.ts), tpri(o.tpri), tsum(o.tsum), tsumpri(o.tsumpri), te(o.te), tesum(o.tesum), 104 | car_time(o.car_time), start_ts(o.start_ts) { 105 | auto copy_list = [](std::list &to, const std::list &from) { 106 | to.resize(from.size()); 107 | auto it1 = to.begin(); 108 | auto it2 = from.begin(); 109 | for (; it1 != to.end(); ++it1, ++it2) 110 | *it1 = new CarInfo(**it2); 111 | }; 112 | for (int is_forward : {0, 1}) { 113 | road_info[is_forward] = o.road_info[is_forward]; 114 | road_cnt_cur[is_forward] = o.road_cnt_cur[is_forward]; 115 | road_speed_cur[is_forward] = o.road_speed_cur[is_forward]; 116 | road_bias[is_forward] = o.road_bias[is_forward]; 117 | car_roads[is_forward] = o.car_roads[is_forward]; 118 | for (size_t i = 0; i < car_roads[is_forward].size(); i++) 119 | for (size_t j = 0; j < car_roads[is_forward][i].size(); j++) 120 | copy_list(car_roads[is_forward][i][j], o.car_roads[is_forward][i][j]); 121 | } 122 | for (int i = 0; i < 2; i++) 123 | for (int j = 0; j < 2; j++) { 124 | ready[i][j].resize(o.ready[i][j].size()); 125 | for (size_t k = 0; k < ready[i][j].size(); k++) 126 | copy_list(ready[i][j][k], o.ready[i][j][k]); 127 | } 128 | cross_pass_cnt = o.cross_pass_cnt; 129 | cross_bias = o.cross_bias; 130 | } 131 | 132 | ~Checker() { 133 | auto delete_list = [](std::list &L) { 134 | while (!L.empty()) delete L.back(), L.pop_back(); 135 | }; 136 | for (int i = 0; i < 2; i++) for (int j = 0; j < 2; j++) 137 | for (auto &L : ready[i][j]) delete_list(L); 138 | for (int is_forward : {0, 1}) for (auto &car_road : car_roads[is_forward]) 139 | for (auto &lane : car_road) delete_list(lane); 140 | } 141 | 142 | void print() { 143 | printf("车辆数: %d / %d (%d / %d)\n", n, mp.n_cars, n_p, mp.n_prior); 144 | printf("T: %d, Tsum: %d\n", ts, tsum); 145 | printf("Tpri: %d, Tsumpri: %d\n", tpri, tsumpri); 146 | printf("Te: %d, Tesum: %d\n", te, tesum); 147 | fflush(stdout); 148 | } 149 | 150 | // 车道内调度 151 | void do_lane(const std::list &lane, const Road &road, bool head, bool init) { 152 | if (lane.empty() || (lane.size() < 2 && !head)) return; 153 | auto it = lane.begin(); 154 | int pre_pos = road.len, pre_state = WAIT; 155 | if (!head) pre_pos = (*it)->pos - 1, pre_state = (*it)->state, ++it; 156 | for (bool ok; it != lane.end(); ++it) { 157 | auto p = *it; 158 | if (p->state == END) break; 159 | int speed = std::min(p->speed, road.limit); 160 | ok = true; 161 | if (pre_pos - p->pos >= speed) p->pos += speed; 162 | else if (pre_state != WAIT) p->pos = pre_pos; 163 | else ok = false; 164 | if (!ok && !init) break; 165 | if (ok) p->state = END, n_wait--; 166 | pre_pos = p->pos - 1, pre_state = p->state; 167 | } 168 | } 169 | 170 | // 找到道路road_i上即将通过路口cross_i的第一优先级的车辆 171 | CarInfo *get_first(int cross_i, int road_i) { 172 | if (road_i == -1) return nullptr; 173 | const auto &road = mp.roads[road_i]; 174 | bool is_forward = cross_i == road.to; 175 | if (!is_forward && !road.bi) return nullptr; 176 | if (road_cnt_cur[is_forward][road_i] == 0) return nullptr; 177 | const auto &car_road = car_roads[is_forward][road_i]; 178 | int pos = -1, prior = -1; 179 | CarInfo *c = nullptr; 180 | for (const auto &lane : car_road) { 181 | if (lane.empty()) continue; 182 | const auto cp = lane.front(); 183 | if (cp->state == END) continue; 184 | if (std::tie(cp->prior, cp->pos) > std::tie(prior, pos)) 185 | prior = cp->prior, pos = cp->pos, c = cp; 186 | } 187 | return c; 188 | } 189 | 190 | // 尝试将车辆cp从道路ri1行驶到道路ri2 191 | int drive(CarInfo *cp, int ri1, int ri2, int cross_i) { 192 | int s2; 193 | bool is_forward1 = (ri1 != -1 && mp.roads[ri1].to == cross_i); 194 | bool is_forward2 = (ri2 != -1 && mp.roads[ri2].from == cross_i); 195 | 196 | if (ri2 == -1) { // 行驶到终点 197 | assert(ri1 != -1); 198 | // 驶出ri1 199 | auto &lane = car_roads[is_forward1][ri1][cp->lane]; 200 | lane.pop_front(), do_lane(lane, mp.roads[ri1], true, false); 201 | //更新车辆相关统计量 202 | n_arrive++, n_wait--, n_arrive_p += bool(cp->prior); 203 | tsum += ts - cp->car.start, car_time[cp->car.i] = ts - cp->sch.start; 204 | if (cp->prior) tpri = ts - mp.early_prior, tsumpri += ts - cp->car.start; 205 | // 更新道路相关统计量 206 | int &rcnt = road_cnt_cur[is_forward1][ri1]; 207 | rcnt--; 208 | double &rspeed = road_speed_cur[is_forward1][ri1]; 209 | if (rcnt == 0) rspeed = 0.0; 210 | else rspeed -= (std::min(cp->speed, mp.roads[ri1].limit) - rspeed) / rcnt; 211 | if (!cp->car.preset) { 212 | auto &bias = road_bias[is_forward1][ri1]; 213 | double d = ts - cp->ts_on - cp->sch.Path.back().second; 214 | bias = bias * alpha + d * (1 - alpha); 215 | } 216 | // 更新路口相关统计量 217 | int &ccnt = cross_pass_cnt[cross_i]; 218 | ccnt++; 219 | double &cbias = cross_bias[cross_i]; 220 | cbias += (ts - cp->ts_at - cbias) / ccnt; 221 | return delete cp, GOAL; 222 | } 223 | 224 | // 计算s2 225 | if (ri1 == -1) s2 = std::min(cp->speed, mp.roads[ri2].limit); 226 | else { 227 | int v2 = std::min(mp.roads[ri2].limit, cp->speed); 228 | int s1 = mp.roads[ri1].len - cp->pos; 229 | s2 = v2 - s1; 230 | } 231 | 232 | if (s2 <= 0) { // 因限速规则无法行驶到ri2 233 | assert(ri1 != -1); 234 | cp->state = END, cp->pos = mp.roads[ri1].len, n_wait--; 235 | auto &lane = car_roads[is_forward1][ri1][cp->lane]; 236 | return do_lane(lane, mp.roads[ri1], false, false), LIMIT; 237 | } 238 | 239 | // 枚举车道尝试上路 240 | auto &lanes = car_roads[is_forward2][ri2]; 241 | for (int i = 0; i < (int)lanes.size(); i++) { 242 | int front_pos = lanes[i].empty() ? std::numeric_limits::max() : lanes[i].back()->pos; 243 | if (front_pos > s2) { 244 | cp->pos = std::min(s2, mp.roads[ri2].len); 245 | } else if (lanes[i].back()->state == WAIT) { 246 | return FAIL; 247 | } else if (front_pos > 1) { 248 | cp->pos = front_pos - 1; 249 | } else continue; 250 | // 行驶到ri2上 251 | if (ri1 != -1) { 252 | // 驶出ri1 253 | auto &lane = car_roads[is_forward1][ri1][cp->lane]; 254 | lane.pop_front(), do_lane(lane, mp.roads[ri1], true, false); 255 | // 更新车辆相关统计量 256 | cp->rd++, n_wait--; 257 | // 更新道路相关统计量 258 | int &rcnt1 = road_cnt_cur[is_forward1][ri1]; 259 | rcnt1--; 260 | double &rspeed1 = road_speed_cur[is_forward1][ri1]; 261 | if (rcnt1 == 0) rspeed1 = 0.0; 262 | else rspeed1 -= (std::min(cp->speed, mp.roads[ri1].limit) - rspeed1) / rcnt1; 263 | if (!cp->car.preset) { 264 | double &bias = road_bias[is_forward1][ri1]; 265 | double d = ts - cp->ts_on - cp->sch.Path[cp->rd - 1].second; 266 | bias = bias * alpha + d * (1 - alpha); 267 | } 268 | } else n_sent++, cp->rd = 0; 269 | // 更新车辆状态 270 | cp->state = END, cp->lane = i, cp->dir = cp->get_dir(), cp->ts_on = ts; 271 | // 驶入ri2 272 | lanes[i].push_back(cp); 273 | // 更新道路相关统计量 274 | int &rcnt2 = road_cnt_cur[is_forward2][ri2]; 275 | double &speed2 = road_speed_cur[is_forward2][ri2]; 276 | rcnt2++, speed2 += (std::min(cp->speed, mp.roads[ri2].limit) - speed2) / rcnt2; 277 | // 更新路口相关统计量 278 | int &ccnt = cross_pass_cnt[cross_i]; 279 | ccnt++; 280 | double &cbias = cross_bias[cross_i]; 281 | cbias += (ts - cp->ts_at - cbias) / ccnt; 282 | cp->ts_at = -1; 283 | return SUCCESS; 284 | } 285 | // 所有车道均被占满且车道尾部的车辆都是终止态 286 | cp->state = END; 287 | if (ri1 != -1) { 288 | cp->pos = mp.roads[ri1].len, n_wait--; 289 | auto &lane = car_roads[is_forward1][ri1][cp->lane]; 290 | do_lane(lane, mp.roads[ri1], false, false); 291 | } 292 | return FULL; 293 | } 294 | 295 | int run_step(const std::vector &Sch) { 296 | // 添加当前时刻预计出发车辆到车库中 297 | sort(start_ts[ts].begin(), start_ts[ts].end()); 298 | for (auto car_i : start_ts[ts]) { 299 | int road_i = Sch[car_i].Path.front().first; 300 | const auto &car = mp.cars[car_i]; 301 | bool is_forward = car.from == mp.roads[road_i].from; 302 | auto cp = new CarInfo(car, Sch[car.i], mp); 303 | cp->ts_at = ts; 304 | ready[is_forward][car.prior][road_i].push_back(cp); 305 | } 306 | start_ts[ts].clear(); 307 | std::vector C(mp.n_crosses, 1); // 路口是否需要遍历 308 | auto drive_ready = [&](int road_i, bool is_forward, bool is_prior) { 309 | auto &L = ready[is_forward][is_prior][road_i]; 310 | int cross_i = is_forward ? mp.roads[road_i].from : mp.roads[road_i].to; 311 | for (auto it = L.begin(); it != L.end();) { 312 | int s = drive(*it, -1, road_i, cross_i); 313 | if (s == FULL) break; 314 | if (s == SUCCESS) { 315 | L.erase(it++); 316 | C[cross_i] = 1; 317 | } else ++it; 318 | } 319 | }; 320 | // 调度不过路口的车 321 | n_wait = 0; 322 | for (int road_i = 0; road_i < mp.n_roads; road_i++) { 323 | auto &road = mp.roads[road_i]; 324 | for (int i = 0; i < mp.roads[road_i].num; i++) { 325 | for (auto cp : car_roads[1][road_i][i]) 326 | cp->state = WAIT, n_wait++; 327 | do_lane(car_roads[1][road_i][i], road, true, true); 328 | if (mp.roads[road_i].bi) { 329 | for (auto cp : car_roads[0][road_i][i]) 330 | cp->state = WAIT, n_wait++; 331 | do_lane(car_roads[0][road_i][i], road, true, true); 332 | } 333 | } 334 | drive_ready(road_i, true, true); 335 | if (mp.roads[road_i].bi) drive_ready(road_i, false, true); 336 | } 337 | // 更新路上车辆数和车速 338 | for (int i = 0; i < mp.n_roads; i++) { 339 | if (abs(road_cnt_cur[1][i] - std::get<1>(road_info[1][i].back())) > 0) 340 | road_info[1][i].emplace_back(ts, road_cnt_cur[1][i], road_speed_cur[1][i], road_bias[1][i]); 341 | if (mp.roads[i].bi) { 342 | if (abs(road_cnt_cur[0][i] - std::get<1>(road_info[0][i].back())) > 0) 343 | road_info[0][i].emplace_back(ts, road_cnt_cur[0][i], road_speed_cur[0][i], road_bias[0][i]); 344 | } 345 | } 346 | // 调度路口中的车 347 | std::vector> First(mp.n_crosses, std::vector(4, nullptr)); 348 | // 计算路口各道路当前第一优先级车辆 349 | for (int i = 0; i < mp.n_crosses; i++) { 350 | for (int j = 0; j < 4; j++) { 351 | auto cp = get_first(i, mp.crosses[i].roads[j]); 352 | if (cp != nullptr && cp->ts_at == -1) cp->ts_at = ts; 353 | First[i][j] = cp; 354 | } 355 | } 356 | for (bool ok = false; !ok;) { 357 | ok = true; 358 | for (int cross_i = 0; cross_i < mp.n_crosses; cross_i++) { 359 | if (!C[cross_i]) continue; 360 | C[cross_i] = 0; 361 | for (int road_i : mp.InG[cross_i]) { 362 | const auto &road = mp.roads[road_i]; 363 | bool is_forward = cross_i == road.to; 364 | for (;;) { 365 | auto &FC = First[cross_i]; 366 | const auto &RIC = mp.RI[cross_i]; 367 | int ri = RIC[road_i]; 368 | auto cp = FC[RIC[road_i]]; 369 | if (cp == nullptr) break; 370 | const auto &TurnCR = mp.Turn[ri][cross_i]; 371 | int dir = cp->dir, road_i2 = TurnCR[dir]; 372 | // 行驶方向优先级冲突判断 373 | int prior = cp->prior; 374 | if (dir >= STRAIGHT) { 375 | int rl = TurnCR[LEFT], rr = TurnCR[RIGHT]; 376 | auto cl = rl == -1 ? nullptr : FC[RIC[rl]], cr = rr == -1 ? nullptr : FC[RIC[rr]]; 377 | if (cl && cl->prior > prior && cl->dir == LEFT) break; 378 | if (cr && cr->prior > prior && cr->dir == RIGHT) break; 379 | } else if (dir == LEFT) { 380 | int rr = TurnCR[RIGHT], ro = TurnCR[STRAIGHT]; 381 | auto co = ro == -1 ? nullptr : FC[RIC[ro]], cr = rr == -1 ? nullptr : FC[RIC[rr]]; 382 | if (cr && cr->prior >= prior && cr->dir >= STRAIGHT) break; 383 | if (co && co->prior > prior && co->dir == RIGHT) break; 384 | } else if (dir == RIGHT) { 385 | int rl = TurnCR[LEFT], ro = TurnCR[STRAIGHT]; 386 | auto cl = rl == -1 ? nullptr : FC[RIC[rl]], co = ro == -1 ? nullptr : FC[RIC[ro]]; 387 | if (cl && cl->prior >= prior && cl->dir >= STRAIGHT) break; 388 | if (co && co->prior >= prior && co->dir == LEFT) break; 389 | } 390 | // 没有冲突,尝试行车 391 | int t = drive(cp, road_i, road_i2, cross_i); 392 | if (t == FAIL) break; 393 | C[road.from] = C[road.to] = 1; 394 | ok = false; 395 | auto ncp = get_first(cross_i, road_i); 396 | if (ncp != nullptr && ncp->ts_at == -1) ncp->ts_at = ts; 397 | FC[ri] = ncp; 398 | // 优先车辆上路 399 | drive_ready(road_i, is_forward, true); 400 | } 401 | } 402 | } 403 | } 404 | if (n_wait) return DEADLOCK; 405 | // 车库中的车上路 406 | for (int i = 0; i < mp.n_roads; i++) { 407 | drive_ready(i, true, true), drive_ready(i, true, false); 408 | if (mp.roads[i].bi) 409 | drive_ready(i, false, true), drive_ready(i, false, false); 410 | } 411 | if (n_arrive == n) return FINISH; 412 | return OK; 413 | } 414 | 415 | int run(const std::vector &Sch, const std::vector &I, int max_ts) { 416 | prev_ts = ts; 417 | if (max_ts > (int)start_ts.size()) start_ts.resize(max_ts); 418 | for (auto car_i : I) { 419 | assert(Sch[car_i].start >= ts); 420 | n++; 421 | start_ts[Sch[car_i].start].push_back(car_i); 422 | if (mp.cars[car_i].prior) n_p++; 423 | if (mp.cars[car_i].preset) n_pre++; 424 | if (mp.cars[car_i].prior && mp.cars[car_i].preset) n_pre_p++; 425 | } 426 | for (; ts < max_ts; ts++) { 427 | int r = run_step(Sch); 428 | if (r == FINISH) 429 | te = int(mp.a_ * tpri + ts + 0.5), tesum = int(mp.b_ * tsumpri + tsum + 0.5); 430 | if (r != OK) return r; 431 | } 432 | return TIMEUP; 433 | } 434 | 435 | // 查询t时刻道路上的信息: 车辆数,平均车速,平均时差 436 | std::tuple get_info(int road_i, bool is_forward, int t) { 437 | const auto &V = road_info[is_forward][road_i]; 438 | auto p = std::upper_bound(V.begin(), V.end(), 439 | std::make_tuple(t, std::numeric_limits::max(), 440 | std::numeric_limits::max(), 441 | std::numeric_limits::max())); 442 | if (p == V.begin()) return std::make_tuple(0, 0.0, 0.0); 443 | --p; 444 | return std::make_tuple(std::get<1>(*p), std::get<2>(*p), std::get<3>(*p)); 445 | }; 446 | }; 447 | #endif /* ifndef CHECKER_H */ 448 | -------------------------------------------------------------------------------- /map.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MAP_H 2 | #define MAP_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | enum { LEFT, RIGHT, STRAIGHT, FIN }; 19 | struct Car { int i, id, from, to, speed, start, prior = 0, preset = 0; }; 20 | struct Road { int i, id, len, limit, num, from, to, bi; }; 21 | struct Cross { int i, id, roads[4]; }; 22 | struct Schedule { int start, change; std::vector> Path; }; 23 | 24 | struct Map { 25 | // 用0~n_cars/n_roads/n_crosses的idx表示车辆,道路,路口 26 | int n_cars, n_roads, n_crosses; 27 | std::unordered_map cross_idx, car_idx, road_idx; // 原始id向idx表示转化 28 | std::vector cars; 29 | std::vector roads; 30 | std::vector crosses; 31 | std::vector>> G; // 地图 32 | std::vector> InG; // 每个路口的道路 33 | std::vector> Turn[4]; // 道路转向后的道路, Turn[INDEX][CROSS][DIR] 34 | std::vector> RI; // 道路在路口处的index 35 | std::vector Preset; 36 | 37 | // 地图统计量 38 | double a_, b_; // 加权系数 39 | int n_prior = 0, n_preset = 0, n_pp = 0; // 各种车辆数 40 | int mav = -1, mav_prior = -1, miv = 1e9, miv_prior = 1e9; // 车速 41 | int late = -1, early = 1e9, late_prior = -1, 42 | late_preset = -1, late_pp = -1, early_prior = 1e9; // 发车时间 43 | int n_from, n_to, n_from_p, n_to_p; // 出发到达点分布 44 | std::vector cross_nlanes, cross_nroads; // 路口车道数,道路数 45 | 46 | Map(const std::string &car_path, const std::string &road_path, 47 | const std::string &cross_path, const std::string &preset_path) { 48 | load(car_path, road_path, cross_path, preset_path); 49 | } 50 | 51 | // 载入地图 52 | void load(const std::string &car_path, const std::string &road_path, 53 | const std::string &cross_path, const std::string &preset_path) { 54 | // 初始化 55 | n_cars = n_roads = n_crosses = 0; 56 | cross_idx.clear(), car_idx.clear(), road_idx.clear(); 57 | cars.clear(), roads.clear(), crosses.clear(); 58 | G.clear(), InG.clear(); 59 | Preset.clear(); 60 | std::set From, To, PFrom, PTo; 61 | std::string s; 62 | // 读入车辆文件 63 | std::ifstream in_car(car_path); 64 | while (getline(in_car, s)) { 65 | if (s.front() == '(') { 66 | Car c; 67 | #ifdef NOPRESET 68 | sscanf(s.c_str(), "(%d,%d,%d,%d,%d)", &c.id, &c.from, 69 | &c.to, &c.speed, &c.start); 70 | #else 71 | sscanf(s.c_str(), "(%d,%d,%d,%d,%d,%d,%d)", &c.id, &c.from, 72 | &c.to, &c.speed, &c.start, &c.prior, &c.preset); 73 | #endif 74 | mav = std::max(mav, c.speed), miv = std::min(miv, c.speed); 75 | late = std::max(late, c.start), early = std::min(early, c.start); 76 | From.insert(c.from), To.insert(c.to); 77 | if (c.prior) { 78 | n_prior++; 79 | mav_prior = std::max(mav_prior, c.speed), miv_prior = std::min(miv_prior, c.speed); 80 | late_prior = std::max(late_prior, c.start), early_prior = std::min(early_prior, c.start); 81 | PFrom.insert(c.from), PTo.insert(c.to); 82 | } 83 | if (c.preset) late_preset = std::max(late_preset, c.start), n_preset++; 84 | if (c.prior && c.preset) late_pp = std::max(late_pp, c.start), n_pp++; 85 | cars.emplace_back(c), n_cars++; 86 | } 87 | } 88 | n_from = From.size(), n_to = To.size(), n_from_p = PFrom.size(), n_to_p = PTo.size(); 89 | sort(cars.begin(), cars.end(), [](const Car & a, const Car & b) { return a.id < b.id; }); 90 | for (int i = 0; i < n_cars; i++) car_idx[cars[i].id] = cars[i].i = i; 91 | Preset.resize(n_cars); 92 | // 读入路口文件 93 | std::ifstream in_cross(cross_path); 94 | while (getline(in_cross, s)) { 95 | if (s.front() == '(') { 96 | Cross c; 97 | sscanf(s.c_str(), "(%d,%d,%d,%d,%d)", &c.id, &c.roads[0], &c.roads[1], &c.roads[2], &c.roads[3]); 98 | crosses.emplace_back(c), n_crosses++; 99 | } 100 | } 101 | sort(crosses.begin(), crosses.end(), [](const Cross & a, const Cross & b) { return a.id < b.id; }); 102 | for (int i = 0; i < n_crosses; i++) cross_idx[crosses[i].id] = crosses[i].i = i; 103 | G.resize(n_crosses), InG.resize(n_crosses); 104 | // 读入道路文件 105 | std::ifstream in_road(road_path); 106 | while (getline(in_road, s)) { 107 | if (s.front() == '(') { 108 | Road r; 109 | sscanf(s.c_str(), "(%d,%d,%d,%d,%d,%d,%d)", 110 | &r.id, &r.len, &r.limit, &r.num, &r.from, &r.to, &r.bi); 111 | roads.emplace_back(r), n_roads++; 112 | } 113 | } 114 | sort(roads.begin(), roads.end(), [](const Road & a, const Road & b) { return a.id < b.id; }); 115 | for (int i = 0; i < n_roads; i++) { 116 | auto &r = roads[i]; 117 | int u = cross_idx[r.from], v = cross_idx[r.to]; 118 | G[u].emplace_back(v, i), InG[v].push_back(i); 119 | if (r.bi) G[v].emplace_back(u, i), InG[u].push_back(i); 120 | road_idx[r.id] = r.i = i; 121 | } 122 | // 读入预置车辆路线 123 | #ifndef NOPRESET 124 | std::ifstream in_preset(preset_path); 125 | while (getline(in_preset, s)) { 126 | if (s.front() == '(') { 127 | for (int i = 0; i < (int)s.size(); i++) if (!isdigit(s[i])) s[i] = ' '; 128 | std::stringstream ss(s); 129 | int id, car_i, road_id; 130 | ss >> id, car_i = car_idx[id]; 131 | ss >> Preset[car_i].start; 132 | late_preset = std::max(late_preset, Preset[car_i].start); 133 | if (cars[car_i].prior) late_pp = std::max(late_pp, Preset[car_i].start); 134 | while (ss >> road_id) Preset[car_i].Path.emplace_back(road_id, 0.0); 135 | } 136 | } 137 | #endif 138 | // 将数据结构中的原始id转化成idx表示 139 | for (auto &car : cars) { 140 | car.from = cross_idx[car.from]; 141 | car.to = cross_idx[car.to]; 142 | } 143 | for (auto &road : roads) { 144 | road.from = cross_idx[road.from]; 145 | road.to = cross_idx[road.to]; 146 | } 147 | for (auto &cross : crosses) { 148 | for (int i = 0; i < 4; i++) { 149 | if (cross.roads[i] != -1) 150 | cross.roads[i] = road_idx[cross.roads[i]]; 151 | } 152 | } 153 | for (int i = 0; i < n_cars; i++) if (cars[i].preset) 154 | for (auto &r : Preset[i].Path) r.first = road_idx[r.first]; 155 | // 计算系数 156 | auto round5 = [](double x) { return int(x * 100000 + 0.5) / 100000.; }; 157 | double x = round5(n_cars / double(std::max(1, n_prior))); 158 | double y = round5(round5(mav / double(miv)) / round5(mav_prior / double(miv_prior))); 159 | double z = round5(round5(late / double(early)) / round5(late_prior / double(early_prior))); 160 | double w = round5(n_from / double(std::max(1, n_from_p))); 161 | double v = round5(n_to / double(std::max(1, n_to_p))); 162 | a_ = 0.05 * x + 0.2375 * (y + z + w + v); 163 | b_ = 0.8 * x + 0.05 * (y + z + w + v); 164 | if (!n_prior) a_ = b_ = 0.0; 165 | 166 | // 预处理路口和道路的关系 167 | for (int i = 0; i < 4; i++) { 168 | Turn[i].resize(n_crosses); 169 | for (int j = 0; j < n_crosses; j++) 170 | for (int k = 0; k < 4; k++) 171 | Turn[i][j][k] = -1; 172 | } 173 | RI.assign(n_crosses, std::vector(n_roads, -1)); 174 | auto get_road = [&](int road_i, int cross_i, int dir) { 175 | const auto &croads = crosses[cross_i].roads; 176 | int i = std::find(croads, croads + 4, road_i) - croads; 177 | i += (dir == RIGHT) ? 3 : dir == LEFT ? 1 : 2, i %= 4; 178 | return croads[i]; 179 | }; 180 | cross_nlanes.assign(n_crosses, 0), cross_nroads.assign(n_crosses, 0); 181 | for (int cross_i = 0; cross_i < n_crosses; cross_i++) { 182 | for (int i = 0; i < 4; i++) { 183 | int road_i = crosses[cross_i].roads[i]; 184 | if (road_i == -1) continue; 185 | cross_nlanes[cross_i] += roads[road_i].num * (roads[road_i].bi + 1); 186 | cross_nroads[cross_i]++; 187 | RI[cross_i][road_i] = i; 188 | Turn[i][cross_i][RIGHT] = get_road(road_i, cross_i, RIGHT); 189 | Turn[i][cross_i][LEFT] = get_road(road_i, cross_i, LEFT); 190 | Turn[i][cross_i][STRAIGHT] = get_road(road_i, cross_i, STRAIGHT); 191 | } 192 | } 193 | // 去除无法达到的车速 194 | int max_limit = 0; 195 | for (int i = 0; i < n_roads; i++) { 196 | roads[i].limit = std::min(roads[i].limit, mav); 197 | max_limit = std::max(roads[i].limit, max_limit); 198 | } 199 | for (int i = 0; i < n_cars; i++) cars[i].speed = std::min(cars[i].speed, max_limit); 200 | } 201 | 202 | void print() { 203 | printf("总道路数: %d\n", n_roads); 204 | printf("总路口数: %d\n", n_crosses); 205 | printf("车辆数: %d (%d), 预设: %d (%d)\n", n_cars, n_prior, n_preset, n_pp); 206 | printf("最高车速: %d (%d)\n", mav, mav_prior); 207 | printf("最低车速: %d (%d)\n", miv, miv_prior); 208 | printf("最早发车: %d (%d)\n", early, early_prior); 209 | printf("最晚发车: %d (%d)\n", late, late_prior); 210 | printf("预设最晚发车: %d (%d)\n", late_preset, late_pp); 211 | printf("出发分布: %d (%d)\n", n_from, n_from_p); 212 | printf("到达分布: %d (%d)\n", n_to, n_to_p); 213 | printf("a: %.6f, b: %.6f\n", a_, b_); 214 | } 215 | 216 | std::vector read_answer(const std::string &ans_path) { 217 | std::string s; 218 | std::ifstream ans(ans_path); 219 | std::vector Sch(n_cars); 220 | while (getline(ans, s)) { 221 | if (s.front() == '(') { 222 | for (int i = 0; i < (int)s.size(); i++) if (!isdigit(s[i])) s[i] = ' '; 223 | std::stringstream ss(s); 224 | int id, car_i, road_id; 225 | ss >> id, car_i = car_idx[id]; 226 | ss >> Sch[car_i].start; 227 | while (ss >> road_id) Sch[car_i].Path.emplace_back(road_idx[road_id], 0.0); 228 | } 229 | } 230 | return Sch; 231 | } 232 | 233 | void print_schedule(const std::vector &Sch, const std::string &filename = "") { 234 | assert((int)Sch.size() == n_cars); 235 | std::stringstream ss; 236 | for (int i = 0; i < n_cars; i++) { 237 | if (!cars[i].preset || Sch[i].change) { 238 | ss << "(" << cars[i].id << "," << Sch[i].start; 239 | for (auto r : Sch[i].Path) ss << "," << roads[r.first].id; 240 | ss << ")\n"; 241 | } 242 | } 243 | std::string s = ss.str(); 244 | auto h = std::hash()(s); 245 | std::cout << "Answer Hash Value: " << h << std::endl; 246 | if (filename.empty()) std::cout << s; 247 | else std::ofstream(filename) << s; 248 | } 249 | }; 250 | 251 | #endif /* ifndef MAP_H */ 252 | -------------------------------------------------------------------------------- /pytorch_plate/model/config.json: -------------------------------------------------------------------------------- 1 | {"model_type": "PyTorch", "runtime": "python3.6", "metrics": {"f1": 0.39542,"accuracy": 0.987426,"precision": 0.395875,"recall": 0.394966},"dependencies": [],"model_algorithm": "car-gre","apis": [{"protocol": "http","url": "/","request": {"Content-type": "multipart/form-data","data": {"type": "object","properties": {"images": {"type": "file"}}}},"method": "post","response": {"Content-type": "multipart/form-data","data": {"required": ["car_id"],"type": "object","properties": {"car_id": {"type": "string"}}}}}], "dependencies": [{ 2 | "installer": "pip", 3 | "packages": [{ 4 | "restraint": "", 5 | "package_version": "", 6 | "package_name": "opencv-python" 7 | } 8 | ] 9 | }]} -------------------------------------------------------------------------------- /pytorch_plate/model/customize_service.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import cv2 4 | import string 5 | import numpy as np 6 | from PIL import Image 7 | import torch 8 | import torchvision.models as models 9 | import torch.nn as nn 10 | from torch.autograd import Variable 11 | import torch.optim as optim 12 | from torch.optim import lr_scheduler 13 | from torchvision import datasets, models, transforms 14 | from torch.utils.data import Dataset 15 | from model_service.pytorch_model_service import PTServingBaseService 16 | import logging 17 | head = '%(asctime)-15s %(message)s' 18 | logging.basicConfig(level=logging.INFO, format=head) 19 | logging.info('start') 20 | 21 | num_classes = 43 22 | num_labels = 9 23 | index = {"深": 0, "秦": 1, "京": 2, "海": 3, "成": 4, "南": 5, "杭": 6, "苏": 7, "松": 8, 24 | "0": 9, "1": 10, "2": 11, "3": 12, "4": 13,"5": 14, "6": 15, "7": 16, "8": 17, "9": 18, 25 | "A": 19,"B": 20, "C": 21, "D": 22, "E": 23, "F": 24, "G": 25, "H": 26, "J": 27, "K": 28, 26 | "L": 29, "M": 30, "N": 31, "P": 32, "Q": 33, "R": 34, "S": 35, "T": 36, "U": 37, "V": 38, 27 | "W": 39, "X": 40, "Y": 41, "Z": 42}; 28 | chars = ["深", "秦", "京", "海", "成", "南", "杭", "苏", "松", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", 29 | "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", 30 | "Y", "Z"]; 31 | 32 | class lpr_service(PTServingBaseService): 33 | def __init__(self, model_name, model_path): 34 | super(lpr_service, self).__init__(model_name, model_path) 35 | device = torch.device('cpu') 36 | self.model = models.resnet34(num_classes=num_classes*num_labels) 37 | self.model.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 38 | self.model.load_state_dict(torch.load(os.path.join(self.model_path), map_location=device)) 39 | self.model.eval() 40 | #self.normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) 41 | #self.normalize = transforms.Normalize(mean=[0.315, 0.351, 0.474], std=[0.232, 0.228, 0.181]) 42 | self.normalize = transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) 43 | self.transform = transforms.Compose([ 44 | #transforms.Resize((224, 224)), 45 | transforms.Resize((70, 356)), 46 | transforms.ToTensor(), 47 | self.normalize, 48 | ]) 49 | 50 | def _preprocess(self, data): 51 | logging.info("__preprocess__") 52 | for k, v in data.items(): 53 | for file_name, file_content in v.items(): 54 | image = Image.open(file_content) 55 | image = image.convert('RGB') 56 | image = self.transform(image) 57 | break 58 | return image[None] 59 | 60 | def _postprocess(self, probs): 61 | logging.info("_postprocess") 62 | probs = probs.view(num_classes, num_labels) 63 | labels = torch.argmax(probs, dim=0).numpy() 64 | pred = ''.join([chars[i] if i >= 9 else str(i) for i in labels]) 65 | return pred 66 | 67 | def _inference(self, data): 68 | logging.info("_inference") 69 | return self.model(data) 70 | -------------------------------------------------------------------------------- /pytorch_plate/resnet_train.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import os 4 | import cv2 5 | import string 6 | import random 7 | import numpy as np 8 | from PIL import Image, ImageFilter 9 | import torch 10 | import torchvision.models as models 11 | import torch.nn as nn 12 | from torch.autograd import Variable 13 | import torch.optim as optim 14 | from torch.optim import lr_scheduler 15 | from torchvision import datasets, models, transforms 16 | from torch.utils.data import Dataset 17 | import argparse 18 | import moxing as mox 19 | import logging 20 | import sys 21 | 22 | mox.file.shift('os', 'mox') 23 | sys.path.insert(0, "../../python") 24 | sys.setrecursionlimit(1000000) 25 | random.seed(2019) 26 | torch.manual_seed(2019) 27 | 28 | num_classes = 43 29 | num_labels = 9 30 | index = {"深": 0, "秦": 1, "京": 2, "海": 3, "成": 4, "南": 5, "杭": 6, "苏": 7, "松": 8, 31 | "0": 9, "1": 10, "2": 11, "3": 12, "4": 13,"5": 14, "6": 15, "7": 16, "8": 17, "9": 18, 32 | "A": 19,"B": 20, "C": 21, "D": 22, "E": 23, "F": 24, "G": 25, "H": 26, "J": 27, "K": 28, 33 | "L": 29, "M": 30, "N": 31, "P": 32, "Q": 33, "R": 34, "S": 35, "T": 36, "U": 37, "V": 38, 34 | "W": 39, "X": 40, "Y": 41, "Z": 42}; 35 | chars = ["深", "秦", "京", "海", "成", "南", "杭", "苏", "松", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", 36 | "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", 37 | "Y", "Z"]; 38 | 39 | class GaussBulr(object): 40 | def __init__(self, radius=2): 41 | self.radius = radius 42 | def __call__(self,img): 43 | return img.filter(ImageFilter.GaussianBlur(random.randint(1, self.radius))) 44 | 45 | class AddGaussNoise(object): 46 | def __init__(self, mean=0, std=2): 47 | self.mean = mean 48 | self.std = std 49 | def __call__(self,img): 50 | img = cv2.cvtColor(np.asarray(img),cv2.COLOR_RGB2BGR) 51 | noise = np.zeros(img.shape, dtype=np.uint8) 52 | mean = random.random() * self.mean 53 | std = random.random() * self.std 54 | cv2.randn(noise, (mean, mean, mean), (std, std, std)) 55 | img = img + noise 56 | img = Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)) 57 | return img 58 | 59 | class LicensePlateDataset(Dataset): 60 | def __init__(self, root_dir, transform=None, train=True, test=False): 61 | self.root_dir = root_dir 62 | self.data = open(os.path.join(root_dir, 'train-data-label.txt'), encoding='utf-8').readlines() 63 | #normalize = transforms.Normalize(mean=[0.315, 0.351, 0.474], std=[0.232, 0.228, 0.181]) 64 | normalize = transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]) 65 | if transform is None: 66 | if train: 67 | self.transform = transforms.Compose([ 68 | #transforms.RandomHorizontalFlip(), 69 | #transforms.ColorJitter(0.1, 0.1, 0.1, 0.1), 70 | #GaussBulr(2), 71 | #AddGaussNoise(), 72 | #transforms.RandomAffine(degrees=2,translate=(0.01,0.01),shear=2), 73 | transforms.Resize((70, 356)), 74 | #transforms.RandomAffine(degrees=10,translate=(0.01,0.01),scale=(0.8,1.0),shear=10), 75 | transforms.ToTensor(), 76 | normalize 77 | ]) 78 | else: 79 | self.transform = transforms.Compose([ 80 | transforms.Resize((70, 356)), 81 | transforms.ToTensor(), 82 | normalize 83 | ]) 84 | else: 85 | self.transform = transform 86 | if not test: 87 | random.seed(2019) 88 | random.shuffle(self.data) 89 | if train: 90 | self.data = self.data[:int(0.9*len(self.data))] 91 | else: 92 | self.data = self.data[int(0.9*len(self.data)):] 93 | self.data = [line.split(',') for line in self.data] 94 | self.data = [[image.strip(), label.strip()] for image, label in self.data] 95 | 96 | def __len__(self): 97 | return len(self.data) 98 | 99 | def __getitem__(self, idx): 100 | img_name = os.path.join(self.root_dir, 'train-data', self.data[idx][1]) 101 | with Image.open(img_name) as img: 102 | image = img.convert('RGB') 103 | image = self.transform(image) 104 | label = np.array([index[i] for i in self.data[idx][0]]) 105 | label = torch.from_numpy(label) 106 | return image, label 107 | 108 | def accuracy(output, target): 109 | #batch_size * num_class * num_label 110 | #bath_size * num_label 111 | hit = 0 112 | count = 0 113 | with torch.no_grad(): 114 | batch_size = target.size(0) 115 | pred = torch.argmax(output, dim=1) 116 | hit += torch.sum(torch.eq(pred, target)).item() 117 | count += batch_size * num_labels 118 | return hit, count 119 | 120 | def validate(val_loader, model, criterion): 121 | model.eval() 122 | losses = 0.0 123 | total_hit = 0 124 | total_cnt = 0 125 | with torch.no_grad(): 126 | for i, (input, target) in enumerate(val_loader): 127 | input = input.cuda() 128 | target = target.cuda() 129 | output = model(input) 130 | output = output.view(-1, num_classes, num_labels) 131 | loss = criterion(output, target) 132 | losses += loss.item() 133 | hit, count = accuracy(output, target) 134 | total_hit += hit 135 | total_cnt += count 136 | logging.info("validate loss : %.6f acc : %.6f" % (losses, 1.0 * total_hit / total_cnt)) 137 | return 1.0 * total_hit / total_cnt 138 | 139 | def train(args, my_data): 140 | head = '%(asctime)-15s %(message)s' 141 | logging.basicConfig(level=logging.INFO, format=head) 142 | logging.info('start with arguments %s', args) 143 | lr=0.001 144 | batch_size = 16 145 | epochs = 600 146 | model = models.resnet34(num_classes=num_classes*num_labels) 147 | model.avgpool = nn.AdaptiveAvgPool2d((1, 1)) 148 | criterion = nn.CrossEntropyLoss() 149 | optimizer = torch.optim.Adam(model.parameters(), lr) 150 | #optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=0.00001) 151 | #scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=[60], gamma=0.1) 152 | #scheduler_update = optim.lr_scheduler.StepLR(optimizer, step_size=1, gamma=10.0) 153 | model.cuda() 154 | criterion.cuda() 155 | train_dataset = LicensePlateDataset(my_data) 156 | val_dataset = LicensePlateDataset(my_data, train=False) 157 | train_loader = torch.utils.data.DataLoader( 158 | train_dataset, batch_size=batch_size, shuffle=True, 159 | num_workers=8, drop_last=True) 160 | val_loader = torch.utils.data.DataLoader( 161 | val_dataset, 162 | batch_size=batch_size, shuffle=False, 163 | num_workers=8) 164 | best_acc = 0.0 165 | stop_counter = 0 166 | for epoch in range(epochs): 167 | model.train() 168 | #scheduler.step() 169 | #if epoch == 10: 170 | # scheduler_update.step() 171 | losses = 0.0 172 | total_hit = 0 173 | total_cnt = 0 174 | for i, (input, target) in enumerate(train_loader): 175 | input = input.cuda() 176 | target = target.cuda() 177 | output = model(input) 178 | output = output.view(-1, num_classes, num_labels) 179 | loss = criterion(output, target) 180 | #print(target[0]) 181 | optimizer.zero_grad() 182 | loss.backward() 183 | optimizer.step() 184 | loss = loss.item() 185 | losses += loss 186 | hit, count = accuracy(output, target) 187 | total_hit += hit 188 | total_cnt += count 189 | if i % 50 == 0: 190 | logging.info('[Epoch %d, Batch %5d] loss: %.6f acc: %.6f' % (epoch + 1, i + 1, loss, 1.0 * hit / count)) 191 | logging.info("train loss : %.6f acc : %.6f" % (losses, 1.0 * total_hit / total_cnt)) 192 | acc = validate(val_loader, model, criterion) 193 | if acc >= best_acc: 194 | if acc >= 0.97: 195 | torch.save(model.state_dict(), os.path.join(args.train_url, 'model-resnet34-epoch%d.pth' % epoch)) 196 | best_acc = acc 197 | stop_counter = 0 198 | else: 199 | stop_counter += 1 200 | if stop_counter == 10 and best_acc >= 0.98667: 201 | break 202 | logging.info("best acc : %.6f" % best_acc) 203 | 204 | if __name__ == '__main__': 205 | parser = argparse.ArgumentParser(description="license plate recognition", 206 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 207 | 208 | # 运行时参数,在线训练默认会传入data_url参数,指示训练的数据集路径,也就是在训练作业中选定的数据所在文件夹 209 | parser.add_argument('--data_url', type=str, default='s3://obs-car-reg', help='the training data') 210 | 211 | # 运行时参数,在线训练默认会传入train_url参数,指示训练的输出的模型所在路径,也就是在训练作业中选定的模型输出文件夹 212 | parser.add_argument('--train_url', type=str, default='s3://obs-car-reg//model', help='the path model saved') 213 | args, unkown = parser.parse_known_args() 214 | 215 | # 复制OSB路径文件至当前容器/cache/my_data目录下,后续操作直接使用/cache/my_data进行数据引用 216 | mox.file.copy_parallel(src_url= args.data_url, dst_url='/cache/my_data') 217 | my_data = '/cache/my_data' 218 | train(args=args, my_data=my_data) 219 | -------------------------------------------------------------------------------- /pytorch_plate/train.ipynb: -------------------------------------------------------------------------------- 1 | {"cells": [{"metadata": {"trusted": true}, "cell_type": "code", "source": "%matplotlib inline\nfrom matplotlib import pyplot as plt\n\nimport os\nimport cv2\nimport string\nimport numpy as np\nfrom PIL import Image\nimport torch\nimport torchvision.models as models\nimport torch.nn as nn\nfrom torch.autograd import Variable\nimport torch.optim as optim\nfrom torch.optim import lr_scheduler\nfrom torchvision import datasets, models, transforms\nfrom torch.utils.data import Dataset", "execution_count": 1, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "import os\nhome_path = os.environ['HOME']+'/work/licence-plate' # \u8f93\u5165\u4e0b\u8f7d\u5230\u5bb9\u5668\u4e2d\u7684\u5730\u5740\uff0c\u5982home_path=os.environ['HOME']+'/work'\ndata_path = home_path + '/data'\n!mkdir -p $data_path\n\ndataset_url='https://codecraft-2019.obs.cn-north-1.myhuaweicloud.com/data/train-data.zip'\n\n!wget $dataset_url -O $data_path/train-data.zip -P $data_path \nimport zipfile\ndataset_file = data_path + '/train-data.zip'\nzip = zipfile.ZipFile(dataset_file)\nzip.extractall(data_path)\nzip.close()\n!rm -rf $dataset_file\n", "execution_count": 2, "outputs": [{"output_type": "stream", "text": "--2019-04-18 09:21:25-- https://codecraft-2019.obs.cn-north-1.myhuaweicloud.com/data/train-data.zip\nResolving codecraft-2019.obs.cn-north-1.myhuaweicloud.com (codecraft-2019.obs.cn-north-1.myhuaweicloud.com)... 100.125.40.3, 100.125.40.34\nConnecting to codecraft-2019.obs.cn-north-1.myhuaweicloud.com (codecraft-2019.obs.cn-north-1.myhuaweicloud.com)|100.125.40.3|:443... connected.\nHTTP request sent, awaiting response... 200 OK\nLength: 47549916 (45M) [application/zip]\nSaving to: \u2018/home/jovyan/work/licence-plate/data/train-data.zip\u2019\n\ntrain-data.zip 100%[===================>] 45.35M 44.8MB/s in 1.0s \n\n2019-04-18 09:21:26 (44.8 MB/s) - \u2018/home/jovyan/work/licence-plate/data/train-data.zip\u2019 saved [47549916/47549916]\n\n", "name": "stdout"}]}, {"metadata": {"scrolled": true, "trusted": true}, "cell_type": "code", "source": "torch.manual_seed(2019)\nindex = {u'\u6df1':0, u'\u79e6':1, u'\u4eac':2, u'\u6d77':3, u'\u6210':4, u'\u5357':5, u'\u676d':6, u'\u82cf':7, u'\u677e':8}\nfor i, ch in enumerate(string.digits):\n index[ch] = 9 + i \nfor i, ch in enumerate(string.ascii_uppercase):\n index[ch] = 19 + i\nchars = [0] * 45\nfor ch, idx in index.items():\n chars[idx] = ch", "execution_count": 3, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "class LicensePlateDataset(Dataset):\n def __init__(self, root_dir):\n self.root_dir = root_dir\n self.labels = open(os.path.join(root_dir, 'train-data-label.txt'), encoding='utf-8').readlines()\n self.labels = [line.split(',') for line in self.labels]\n self.labels = [[img.strip(), label.strip()] for img, label in self.labels]\n self.normalize = normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n self.transform = transforms.Compose([\n transforms.Resize((224, 224)),\n transforms.ToTensor(),\n self.normalize,\n ])\n\n def __len__(self):\n return len(self.labels)\n \n def __getitem__(self, idx):\n img_name = os.path.join(self.root_dir, 'train-data', self.labels[idx][1])\n with Image.open(img_name) as img:\n image = img.convert('RGB')\n image = self.transform(image)\n label = np.array([index[i] for i in self.labels[idx][0]])\n label = torch.from_numpy(label)\n #print(label)\n return image, label", "execution_count": 4, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "# def train(train_loader, model, criterion, optimizer, epoch):\n# model.train()\n# loss100 = 0.0\n# for i, (input, target) in enumerate(train_loader):\n# output = model(input)\n# loss = criterion(output, target)\n# optimizer.zero_grad()\n# loss.backward()\n# optimizer.step()\n# loss100 += loss.item()\n# if i % 100 == 99:\n# print('[Epoch %d, Batch %5d] loss: %.3f' % (epoch + 1, i + 1, loss100 / 100))\n# loss100 = 0.0", "execution_count": 5, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "def accuracy(output, target):\n #batch_size * num_class * num_label\n #bath_size * num_label\n num_label = 9\n hit = 0.0\n total = 0.0\n with torch.no_grad():\n batch_size = target.size(0)\n pred = torch.argmax(output, dim=1)\n hit = 1.0 * torch.sum(torch.eq(pred, target)).item()\n total = 1.0 * batch_size * num_label\n return 1.0 * hit / total", "execution_count": 6, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "def validate(val_loader, model, criterion):\n model.eval()\n losses = 0.0\n acc = 0.0\n cnt = 0\n with torch.no_grad():\n for i, (input, target) in enumerate(val_loader):\n input = input.cuda()\n target = target.cuda()\n cnt += 1\n output = model(input)\n output = output.view(-1, 45, 9)\n loss = criterion(output, target)\n losses += loss.item()\n acc += accuracy(output, target)\n print(\"loss : %f acc : %f\" % (losses, acc / cnt))", "execution_count": 7, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "", "execution_count": null, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "lr = 0.001\nbatch_size = 128\nepochs = 10\nmodel = models.resnet50(num_classes=405)\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr)\n# optimizer = torch.optim.SGD(model.parameters(), lr,\n# momentum=0.9,\n# weight_decay=0.0001)\nmodel.cuda()\ncriterion.cuda()\nroot_dir = 'work/licence-plate/data/'\ntrain_dataset = LicensePlateDataset(root_dir)\nval_dataset = LicensePlateDataset(root_dir)\ntrain_loader = torch.utils.data.DataLoader(\n train_dataset, batch_size=batch_size, shuffle=True,\n num_workers=0)\n\nval_loader = torch.utils.data.DataLoader(\n val_dataset,\n batch_size=batch_size, shuffle=False,\n num_workers=0)", "execution_count": 8, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "for epoch in range(epochs):\n #train(train_loader, model, criterion, optimizer, epoch)\n model.train()\n loss = 0.0\n acc = 0.0\n for i, (input, target) in enumerate(train_loader):\n# print(input.shape)\n# print(target.shape)\n# #print(input, target)\n input = input.cuda()\n target = target.cuda()\n output = model(input)\n output = output.view(-1, 45, 9)\n loss = criterion(output, target)\n #print(target[0])\n optimizer.zero_grad()\n loss.backward()\n optimizer.step()\n loss += loss.item()\n acc += accuracy(output, target)\n print('[Epoch %d, Batch %5d] loss: %.3f acc: %.3f' % (epoch + 1, i + 1, loss, acc))\n loss = 0.0\n acc = 0.0\n#torch.save(model, 'work/model.pkl')\ntorch.save(model.state_dict(), 'work/model-resnet50.pth')", "execution_count": null, "outputs": [{"output_type": "stream", "text": "[Epoch 1, Batch 1] loss: 8.173 acc: 0.015\n[Epoch 1, Batch 2] loss: 8.255 acc: 0.046\n[Epoch 1, Batch 3] loss: 8.368 acc: 0.048\n[Epoch 1, Batch 4] loss: 7.954 acc: 0.040\n[Epoch 1, Batch 5] loss: 7.924 acc: 0.057\n[Epoch 1, Batch 6] loss: 7.458 acc: 0.049\n[Epoch 1, Batch 7] loss: 7.464 acc: 0.043\n[Epoch 1, Batch 8] loss: 7.198 acc: 0.045\n[Epoch 1, Batch 9] loss: 7.161 acc: 0.040\n[Epoch 1, Batch 10] loss: 7.016 acc: 0.036\n[Epoch 1, Batch 11] loss: 6.842 acc: 0.053\n[Epoch 1, Batch 12] loss: 6.947 acc: 0.042\n[Epoch 1, Batch 13] loss: 6.951 acc: 0.054\n[Epoch 1, Batch 14] loss: 6.897 acc: 0.032\n[Epoch 1, Batch 15] loss: 6.842 acc: 0.043\n[Epoch 1, Batch 16] loss: 7.131 acc: 0.049\n[Epoch 1, Batch 17] loss: 6.852 acc: 0.042\n[Epoch 1, Batch 18] loss: 6.855 acc: 0.042\n[Epoch 1, Batch 19] loss: 6.740 acc: 0.057\n[Epoch 1, Batch 20] loss: 6.825 acc: 0.041\n[Epoch 1, Batch 21] loss: 6.759 acc: 0.051\n[Epoch 1, Batch 22] loss: 6.810 acc: 0.042\n[Epoch 1, Batch 23] loss: 6.799 acc: 0.046\n[Epoch 1, Batch 24] loss: 6.721 acc: 0.047\n[Epoch 1, Batch 25] loss: 6.769 acc: 0.046\n[Epoch 1, Batch 26] loss: 6.704 acc: 0.049\n[Epoch 1, Batch 27] loss: 6.755 acc: 0.037\n[Epoch 1, Batch 28] loss: 6.694 acc: 0.046\n[Epoch 1, Batch 29] loss: 6.736 acc: 0.042\n[Epoch 1, Batch 30] loss: 6.719 acc: 0.049\n[Epoch 1, Batch 31] loss: 6.704 acc: 0.051\n[Epoch 1, Batch 32] loss: 6.668 acc: 0.059\n[Epoch 2, Batch 1] loss: 6.662 acc: 0.055\n[Epoch 2, Batch 2] loss: 6.678 acc: 0.055\n[Epoch 2, Batch 3] loss: 6.663 acc: 0.049\n[Epoch 2, Batch 4] loss: 6.689 acc: 0.057\n[Epoch 2, Batch 5] loss: 6.684 acc: 0.039\n[Epoch 2, Batch 6] loss: 6.689 acc: 0.043\n[Epoch 2, Batch 7] loss: 6.733 acc: 0.045\n[Epoch 2, Batch 8] loss: 6.716 acc: 0.048\n[Epoch 2, Batch 9] loss: 6.690 acc: 0.056\n[Epoch 2, Batch 10] loss: 6.690 acc: 0.043\n", "name": "stdout"}]}, {"metadata": {"scrolled": true, "trusted": true}, "cell_type": "code", "source": "validate(val_loader, model, criterion)", "execution_count": null, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "image_name = 'work/licence-plate/data/train-data/2c9ec2debce77459.jpg'\nimage = np.asarray(bytearray(open(image_name, 'rb').read()), dtype=\"uint8\")\n\nbin_file = cv2.imdecode(image, cv2.IMREAD_COLOR)\n\nimg = cv2.imread(image_name)\n#plt.imshow(img)\nplt.imshow(bin_file)\n\nnormalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\ntransform = transforms.Compose([\n transforms.Resize((224, 224)),\n transforms.ToTensor(),\n normalize,\n ])\nwith Image.open(image_name) as img:\n image = img.convert('RGB')\n \n# image = Image.fromarray(cv2.cvtColor(bin_file,cv2.COLOR_BGR2RGB)) \n# image = image.convert('RGB')\n\nimage = transform(image)\nimage = image[None]\ninputs = image.cuda()\noutput = model(inputs)\noutput = output.view(45, 9)\nmm = torch.argmax(output, dim=0)\nprint([chars[i] for i in mm])", "execution_count": null, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "# resnet50 = models.resnet50(pretrained=True)\n# !ls work", "execution_count": null, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "# img = cv2.imread('2b6b3180b74c55b0.jpg')\n# img = cv2.resize(img, (224,224))\n# plt.imshow(img)\n\n\n# image = transforms.ToTensor()(img)\n# image = transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])(image)\n# image = image[None]\n# inputs = Variable(image)\n# output = resnet50(inputs)\n# torch.argmax(output)", "execution_count": null, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "# #\u4fee\u6539\u5206\u7c7b\u7684\u6570\u91cf\u4e3a2\n# num_ftrs = resnet50.fc.in_features\n# resnet50.fc = nn.Linear(num_ftrs, 44 * 9)\n\n\n# img = cv2.imread('/bigdata/gemfield/github/data/val/1/img_0.jpg')\n# img = cv2.resize(img, (224,224))\n# image = transforms.ToTensor()(img)\n# image = transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])(image)\n# image = image[None]\n# inputs = Variable(image.cuda())\n# output = resnet50(inputs)\n\n# outputs = torch.stack([nn.Softmax(dim=0)(i) for i in output])\n# outputs = outputs.mean(0)\n# p, preds = torch.max(outputs, 0)\n\n# # tensor -> scaler\n# print(p.data.cpu().item())\n# print(preds.data.cpu().item())", "execution_count": null, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "# model = torch.load(model.pkl')\n# img = cv2.imread('/bigdata/gemfield/github/data/val/1/img_0.jpg')\n# img = cv2.resize(img, (224,224))\n# image = transforms.ToTensor()(img)\n# image = transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])(image)\n# image = image[None]\n# inputs = Variable(image\n# output = model(inputs)", "execution_count": null, "outputs": []}, {"metadata": {"trusted": true}, "cell_type": "code", "source": "# torch.manual_seed(20)\n# loss = nn.CrossEntropyLoss()\n# input = torch.randn(3, 5, 2,requires_grad=True)\n# target = torch.empty(3, 2, dtype=torch.long).random_(5)\n# print(input)\n# print(target)\n# output = loss(input, target)\n# output.backward()\n# print(output.item())\n# output = loss(input[:,:,0], target[:,0])\n# output.backward()\n# print(output.item())\n# output = loss(input[:,:,1], target[:,1])\n# output.backward()\n# print(output.item())\n# print(target.size(0))\n# mm = torch.argmax(input, dim=1)\n# a = torch.eq(mm, target)\n# print(a)\n# print(target.size())\n# accuracy(input, target)", "execution_count": null, "outputs": []}], "metadata": {"kernelspec": {"name": "python3", "display_name": "Python 3", "language": "python"}, "language_info": {"name": "python", "version": "3.6.4", "mimetype": "text/x-python", "codemirror_mode": {"name": "ipython", "version": 3}, "pygments_lexer": "ipython3", "nbconvert_exporter": "python", "file_extension": ".py"}}, "nbformat": 4, "nbformat_minor": 2} -------------------------------------------------------------------------------- /scheduler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SCHEDULER_H 2 | #define SCHEDULER_H 3 | 4 | #include "map.hpp" 5 | #include "checker.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | const int MAX_PROGRAM_SECS = 850; 22 | int start_time = clock(); 23 | 24 | struct Scheduler { 25 | using PII = std::pair; 26 | using PDI = std::pair; 27 | using PID = std::pair; 28 | 29 | const Map ∓ 30 | // max_per_ts: 每个时间片非预置车辆最多发车数 31 | // n_try: 二分发车最大次数 32 | int max_per_ts, n_try; 33 | // ahead: 预测未来路况 34 | // current: 维护当前时刻路况 35 | std::unique_ptr ahead, current; 36 | // 定义时差为车辆真实用时-预计用时 37 | // mse: 本时刻出发车辆时差的均方误差 38 | // time_avg: 本时刻出发车辆真实用时的均值 39 | // time_avg_all: 已出发非预置车辆真实用时的均值 40 | // alpha: time_avg_all计算的衰减系数 41 | double mse = 0, time_avg = 0, time_avg_all = 0; 42 | static constexpr double alpha = 0.98; 43 | std::vector car_time; // 车辆预估用时 44 | 45 | Scheduler(const Map &mp): mp(mp), car_time(mp.n_cars) { 46 | max_per_ts = n_try = 0; 47 | // 以道路数(含正反方向)作为max_per_ts的值 48 | for (const auto &road : mp.roads) 49 | max_per_ts += int(bool(road.bi) + 1); 50 | max_per_ts = std::max(256, max_per_ts); 51 | n_try = std::max(3, 32 - __builtin_clz(max_per_ts) - 6); 52 | } 53 | 54 | // 根据路况估计车辆行驶过某条道路时的行驶时间和cost 55 | // speed: 车辆速度 56 | // cross_i: 起点路口 57 | // road_i: 道路 58 | // shortest: cost是否用最短路 59 | std::pair calc_dist(int speed, int cross_i, int road_i, int ts, bool shortest) { 60 | // 几个玄学参数 61 | // road_pow: 道路cost计算时随路上车辆数增长,略大于线性增长 62 | // road_cof, cross_cof: 对道路cost和路口cost的加权系数 63 | static constexpr double road_pow = 1.2, road_cof = 2, cross_cof = 0.01; 64 | const auto &road = mp.roads[road_i]; 65 | bool is_forward = road.from == cross_i; 66 | 67 | double cross_bias = ahead->cross_bias[cross_i]; // 过路口的车辆平均时差 68 | int cnt1, cnt2; 69 | double road_speed1, road_speed2, road_bias1, road_bias2; 70 | double rspeed = std::min(speed, road.limit); // 车速在道路上的车速 71 | // 查询进入路口时的路况和预计出路口时刻的路况 72 | std::tie(cnt1, road_speed1, road_bias1) = ahead->get_info(road_i, is_forward, ts); 73 | std::tie(cnt2, road_speed2, road_bias2) = ahead->get_info(road_i, is_forward, 74 | ts + road.len / rspeed); 75 | if (road_speed1 < 1) road_speed1 = rspeed; 76 | if (road_speed2 < 1) road_speed2 = rspeed; 77 | double cnt = 0.5 * (cnt1 + cnt2); 78 | double road_speed = 0.5 * (road_speed1 + road_speed2); 79 | double road_bias = road_bias2; 80 | 81 | if (cnt * road_speed > road.len) // 车辆数较多,预估车速取道路平均车速 82 | rspeed = std::min(road_speed, rspeed); 83 | 84 | // 道路cost,玄学公式。让车辆数少,车道数多的道路cost更小 85 | double road_cost = pow(cnt, road_pow) / road.len / (road.num * road.num) * road_cof; 86 | // 路口cost,玄学公式。让分叉数多,车道数多的路口cost更大 (因为这样的路口更容易产生路口等待) 87 | double cross_cost = (mp.cross_nroads[cross_i] + sqrt( mp.cross_nlanes[cross_i])) * cross_cof; 88 | 89 | // 预估行驶时间 90 | double w = std::max(1.0, road.len / rspeed + road_bias + cross_bias); 91 | // 道路路口的cost与行驶时间进行加权的最终cost。玄学公式 92 | double c = (road_cost + cross_cost) / rspeed * time_avg_all + w; 93 | if (shortest) c = w; 94 | return std::make_pair(w, c); 95 | } 96 | 97 | // 预估一辆车既定路线的用时和cost 98 | void estimate(int car_i, Schedule &sch) { 99 | const auto &car = mp.cars[car_i]; 100 | int u = car.from; 101 | double dist = 0., e = 0., w, c; 102 | for (auto &p : sch.Path) { 103 | int road_i = p.first; 104 | const auto &road = mp.roads[road_i]; 105 | int v = u == road.from ? road.to : road.from; 106 | int t = std::max(0, int(dist + 0.5) + sch.start); 107 | std::tie(w, c) = calc_dist(car.speed, u, road_i, t, false); 108 | dist += w, e += c, u = v; 109 | p.second = w; 110 | } 111 | assert(u == car.to); 112 | car_time[car_i] = dist; 113 | } 114 | 115 | // 按照路径cost计算最短路 116 | std::tuple, std::vector, std::vector> 117 | dijkstra(int from, int speed, int ts, int to, bool shortest) { 118 | static constexpr double INF = 1LL << 60; 119 | std::vector Pre(mp.n_crosses); 120 | std::priority_queue, std::greater> PQ; 121 | std::vector Dist(mp.n_crosses, INF), E(mp.n_crosses, INF); 122 | PQ.emplace(0, from), Dist[from] = 0, E[from] = 0; 123 | std::vector Closed(mp.n_cars, 0); 124 | while (!PQ.empty()) { 125 | int u; 126 | double eu; 127 | std::tie(eu, u) = PQ.top(), PQ.pop(); 128 | if (to != -1 && u == to) break; 129 | if (Closed[u]) continue; 130 | Closed[u] = 1; 131 | for (const auto &p : mp.G[u]) { 132 | int v = p.first, road_i = p.second; 133 | if (Closed[v]) continue; 134 | if (mp.roads[road_i].limit == 0) continue; 135 | double w, c; 136 | int t = std::max(0, int(Dist[u] + 0.5) + ts); 137 | std::tie(w, c) = calc_dist(speed, u, road_i, t, shortest); 138 | if (E[u] + c < E[v]) { 139 | E[v] = E[u] + c, Dist[v] = Dist[u] + w, Pre[v] = {road_i, w}; 140 | PQ.emplace(E[v], v); 141 | } 142 | } 143 | } 144 | return std::make_tuple(Dist, E, Pre); 145 | } 146 | 147 | // 生成路径 148 | void get_path(int car_i, std::vector &Path, int ts, bool shortest) { 149 | const auto &car = mp.cars[car_i]; 150 | std::vector Pre; 151 | std::vector Dist, E; 152 | std::tie(Dist, E, Pre) = dijkstra(car.from, car.speed, ts, car.to, shortest); 153 | int tmp = car.to; 154 | Path.clear(); 155 | while (tmp != car.from) { 156 | const auto &road = mp.roads[Pre[tmp].first]; 157 | Path.emplace_back(Pre[tmp]); 158 | tmp = road.from == tmp ? road.to : road.from; 159 | } 160 | reverse(Path.begin(), Path.end()); 161 | car_time[car_i] = Dist[car.to]; 162 | } 163 | 164 | // 批量预估车辆用时 165 | bool estimate_time_multi(const std::vector &I, int ts, bool shortest) { 166 | static const int MAX_TURN = 3000; 167 | if (mp.n_crosses > MAX_TURN) return false; 168 | std::map speed_cnt; 169 | std::vector Speed; 170 | for (auto i : I) speed_cnt[mp.cars[i].speed]++; 171 | while (speed_cnt.size() * mp.n_crosses > MAX_TURN) speed_cnt.erase(speed_cnt.begin()); 172 | for (auto p : speed_cnt) Speed.push_back(p.first); 173 | // 按照车速和出发路口将车辆划分 174 | std::vector> SCI(Speed.size() * mp.n_crosses); 175 | for (auto i : I) { 176 | int si = std::lower_bound(Speed.begin(), Speed.end(), mp.cars[i].speed) - Speed.begin(); 177 | int idx = si * mp.n_crosses + mp.cars[i].from; 178 | SCI[idx].push_back(i); 179 | } 180 | for (size_t i = 0; i < Speed.size(); i++) { 181 | int speed = Speed[i]; 182 | for (int from = 0; from < mp.n_crosses; from++) { 183 | std::vector Pre; 184 | std::vector Dist, E; 185 | std::tie(Dist, E, Pre) = dijkstra(from, speed, ts, -1, shortest); 186 | for (auto car_i : SCI[i * mp.n_crosses + from]) { 187 | int to = mp.cars[car_i].to; 188 | car_time[car_i] = Dist[to]; 189 | } 190 | } 191 | } 192 | return true; 193 | } 194 | 195 | // 修改部分预置车辆的实际出发时间或路径 196 | // 返回预置车辆的ts, tpri和te 197 | std::tuple change_preset(std::vector &Sch, int max_ts = 100000, 198 | double portion = 0.1) { 199 | enum { CHANGE_TIME, CHANGE_PATH }; 200 | assert(portion >= 0.0 && portion <= 1.0); 201 | // 找出所有的预置车辆 202 | std::vector I; 203 | for (int i = 0; i < mp.n_cars; i++) if (mp.cars[i].preset) I.push_back(i); 204 | std::vector arrive_true(mp.n_cars), arrive_change_time(mp.n_cars), 205 | arrive_change_path(mp.n_cars), arrive_estimate(mp.n_cars), Label(mp.n_cars); 206 | 207 | ahead = std::unique_ptr(new Checker(mp)); 208 | ahead->run(Sch, I, max_ts); 209 | for (auto i : I) arrive_true[i] = ahead->car_time[i] + Sch[i].start; 210 | for (auto i : I) arrive_change_time[i] = ahead->car_time[i] + mp.cars[i].start; 211 | 212 | ahead = std::unique_ptr(new Checker(mp)); 213 | std::vector dummy_path; 214 | for (auto i : I) { 215 | get_path(i, dummy_path, Sch[i].start, mp.cars[i].prior); 216 | arrive_change_path[i] = car_time[i] + Sch[i].start; 217 | } 218 | 219 | for (auto i : I) { 220 | Label[i] = arrive_change_path[i] < arrive_change_time[i] ? CHANGE_PATH : CHANGE_TIME; 221 | arrive_estimate[i] = std::min(arrive_change_path[i], arrive_change_time[i]); 222 | } 223 | // 按照节省的时间排序 224 | auto cmp = [&](int i, int j) { 225 | int deltai = arrive_true[i] - arrive_estimate[i]; 226 | int deltaj = arrive_true[j] - arrive_estimate[j]; 227 | int savei = mp.cars[i].prior ? (mp.a_ + 1) * deltai : deltai; 228 | int savej = mp.cars[j].prior ? (mp.a_ + 1) * deltaj : deltaj; 229 | return savei > savej; 230 | }; 231 | std::stable_sort(I.begin(), I.end(), cmp); 232 | int l = 0, r = int(portion * I.size()) + 1; 233 | auto backup1 = Sch; 234 | while (l + 1 < r) { 235 | int m = (l + r) / 2; 236 | auto Tmp = backup1; 237 | for (int i = 0; i < m; i++) { 238 | int car_i = I[i]; 239 | if (Label[car_i] == CHANGE_PATH) { 240 | get_path(car_i, Tmp[car_i].Path, Tmp[car_i].start, true); 241 | } else Tmp[car_i].start = mp.cars[car_i].start; 242 | Tmp[car_i].change = 1; 243 | } 244 | int s = std::unique_ptr(new Checker(mp))->run(Tmp, I, max_ts); 245 | if (s == FINISH) backup1 = Tmp, l = m; 246 | else r = m; 247 | } 248 | printf("预置车辆修改路径%d条\n", l); 249 | auto checker = std::unique_ptr(new Checker(mp)); 250 | checker->run(backup1, I, max_ts); 251 | int ts1, tpri1, te1; 252 | std::tie(ts1, tpri1, te1) = std::make_tuple(checker->ts, checker->tpri, checker->te); 253 | 254 | // 按照预计到达时间排序 255 | auto cmp2 = [&](int i, int j) { 256 | int arrivei = mp.cars[i].prior ? (mp.a_ + 1) * (arrive_true[i] - mp.early_prior) : 257 | arrive_true[i]; 258 | int arrivej = mp.cars[j].prior ? (mp.a_ + 1) * (arrive_true[j] - mp.early_prior) : 259 | arrive_true[j]; 260 | return arrivei > arrivej; 261 | }; 262 | auto backup2 = Sch; 263 | std::stable_sort(I.begin(), I.end(), cmp2); 264 | while (l + 1 < r) { 265 | int m = (l + r) / 2; 266 | auto Tmp = backup2; 267 | for (int i = 0; i < m; i++) { 268 | int car_i = I[i]; 269 | if (Label[car_i] == CHANGE_PATH) { 270 | get_path(car_i, Tmp[car_i].Path, Tmp[car_i].start, true); 271 | } else Tmp[car_i].start = mp.cars[car_i].start; 272 | Tmp[car_i].change = 1; 273 | } 274 | int s = std::unique_ptr(new Checker(mp))->run(Tmp, I, max_ts); 275 | if (s == FINISH) backup2 = Tmp, l = m; 276 | else r = m; 277 | } 278 | printf("预置车辆修改路径%d条\n", l); 279 | checker = std::unique_ptr(new Checker(mp)); 280 | checker->run(backup2, I, max_ts); 281 | int ts2, tpri2, te2; 282 | std::tie(ts2, tpri2, te2) = std::make_tuple(checker->ts, checker->tpri, checker->te); 283 | 284 | // 比较两种方案,采用使预置车辆te最小的方案 285 | if (te1 < te2) return Sch = backup1, std::make_tuple(ts1, tpri1, te1); 286 | else return Sch = backup2, std::make_tuple(ts2, tpri2, te2); 287 | } 288 | 289 | 290 | // margin: 优先车辆Tpri允许比预计值延后的范围,-1不限制 291 | // 如果成功生成,返回ts, tpri和te 292 | std::tuple gen_schedule(std::vector &Sch, int margin = -1, 293 | int max_ts = 100000, int max_te = INT_MAX, bool print = true) { 294 | time_avg = time_avg_all = mse = 0.0; // 重置统计量 295 | 296 | std::vector P; // 当前时刻还没有出发的预置车辆 297 | // In[ts], PreIn[ts]: 计划出发时间为ts的普通/预置车辆集合 298 | std::vector> In(max_ts), PreIn(max_ts); 299 | for (int i = 0; i < mp.n_cars; i++) { 300 | if (mp.cars[i].preset) PreIn[Sch[i].start].push_back(i); 301 | else In[mp.cars[i].start].push_back(i); 302 | } 303 | // P按时刻排序,出发时刻早的排在末尾 304 | for (int t = max_ts - 1; t >= 0; t--) for (auto i : PreIn[t]) P.push_back(i); 305 | 306 | // 生成只跑预置车辆的路况 307 | ahead = std::unique_ptr(new Checker(mp)); 308 | int s = ahead->run(Sch, P, max_ts); 309 | if (s == TIMEUP) throw std::logic_error("预设车辆系统调度超时"); 310 | if (s == DEADLOCK) throw std::logic_error("预设车辆死锁"); 311 | if (print) { 312 | printf("预置车辆T: %d\n", ahead->ts); 313 | printf("预置车辆Te: %d\n", ahead->te); 314 | } 315 | 316 | // 预计ts和tpri的最小值 317 | int init_ts = ahead->ts, init_tpri = ahead->tpri; 318 | for (int i = 0; i < mp.n_cars; i++) { 319 | if (mp.cars[i].preset) car_time[i] = ahead->car_time[i]; 320 | else { 321 | get_path(i, Sch[i].Path, mp.cars[i].start, false); 322 | init_ts = std::max(init_ts, int(car_time[i] + mp.cars[i].start + 0.5)); 323 | if (mp.cars[i].prior) 324 | init_tpri = std::max(init_tpri, int(car_time[i] + mp.cars[i].start + 0.5 - mp.early_prior)); 325 | } 326 | } 327 | if (print) { 328 | printf("预计最小T: %d\n", init_ts); 329 | printf("预计最小Tpri: %d\n", init_tpri); 330 | } 331 | 332 | // 按时间片发车 333 | current = std::unique_ptr(new Checker(mp)); // 重置当前路况 334 | int n_sent = 0, n_sent_p = 0; // 已发车数 335 | 336 | for (int ts = 0; n_sent < mp.n_cars && ts < max_ts; ts++) { 337 | int cur = clock(); 338 | int program_secs = double(cur - start_time) / CLOCKS_PER_SEC + 0.5; 339 | if (program_secs > MAX_PROGRAM_SECS) throw std::logic_error("程序运行超时"); 340 | if (print) printf("程序已用时: %d secs\n", program_secs); 341 | 342 | // 确定能够发车的数量 343 | int sz = int(In[ts].size()); 344 | // int l = 0, r = std::max(0, std::min(max_per_ts, sz) - int(PreIn[ts].size())) + 1; 345 | int l = 0, r = std::max(0, std::min(max_per_ts, sz)) + 1; 346 | if (print) printf("最多发车数: %d\n", r - 1); 347 | 348 | // 确定发车顺序 349 | estimate_time_multi(In[ts], ts, false); // 预估所有待发车辆的用时 350 | // 按照 是否是优先车辆,是否可能对te造成影响,车速与预计用时进行排序 351 | int prior_t = std::max(init_tpri, ahead->tpri) + mp.early_prior - 10; 352 | int all_t = std::max(init_ts, ahead->ts) - 10; 353 | stable_sort(In[ts].begin(), In[ts].end(), [&](int i, int j) { 354 | const Car &ci = mp.cars[i], &cj = mp.cars[j]; 355 | if (ci.prior != cj.prior) return ci.prior < cj.prior; 356 | bool b1 = car_time[i] + ts > (ci.prior ? prior_t : all_t); 357 | bool b2 = car_time[j] + ts > (cj.prior ? prior_t : all_t); 358 | if (b1 != b2) return b1 < b2; 359 | return std::make_tuple(-ci.speed, car_time[i]) < 360 | std::make_tuple(-cj.speed, car_time[j]); 361 | }); 362 | 363 | // 规划候选待车辆的路径 364 | for (int i = 0; i < r - 1; i++) { 365 | int car_i = In[ts][sz - i - 1]; 366 | Sch[car_i].start = ts; 367 | get_path(car_i, Sch[car_i].Path, ts, false); 368 | } 369 | 370 | bool flag = false; // 是否可以成功发车 371 | std::unique_ptr backup; 372 | // 判断优先级最高的m辆车是否可以在本时刻发车 373 | auto check = [&](int m) { 374 | if (!m) return true; 375 | std::unique_ptr tmp(new Checker(*current)); 376 | for (int j = 0; j < m; j++) P.push_back(In[ts][sz - 1 - j]); 377 | int s = tmp->run(Sch, P, max_ts); 378 | for (int j = 0; j < m; j++) P.pop_back(); 379 | bool ret = s == FINISH; 380 | if (margin >= 0 && tmp->tpri > init_tpri + margin) return false; 381 | if (ret) { 382 | flag = true; 383 | backup = std::unique_ptr(new Checker(*tmp)); 384 | } 385 | return ret; 386 | }; 387 | if (check(r - 1)) { 388 | l = r - 1; 389 | } else { 390 | int n_bs = 0; 391 | while (l + 1 < r) { 392 | if (n_bs++ == n_try) break; 393 | int m = (l + r) >> 1; 394 | if (!m) break; 395 | if (check(m)) l = m; 396 | else r = m; 397 | } 398 | } 399 | // 防止候选车辆内部造成死锁 400 | if (l == 0 && r > 1 && current->n == current->n_arrive) if (check(1)) l = 1; 401 | 402 | // 更新未来路况 403 | if (flag) ahead = std::unique_ptr(new Checker(*backup)); 404 | if (print) { 405 | printf("%d: %d / %d, %d / %d\n", ts, l, 406 | sz, int(PreIn[ts].size()), int(P.size())); 407 | ahead->print(); 408 | } 409 | 410 | // 当前时刻发车的车辆 411 | std::vector Add; 412 | // 本时刻发车的非预置车辆加入已发车列表 413 | for (int i = 0; i < l; i++) Add.push_back(In[ts].back()), In[ts].pop_back(); 414 | // 未发车辆移动到下一时刻的待发车辆列表中 415 | if (ts + 1 < max_ts) { 416 | if (In[ts].size() > In[ts + 1].size()) swap(In[ts], In[ts + 1]); 417 | for (auto car_i : In[ts]) In[ts + 1].push_back(car_i); 418 | In[ts].clear(); 419 | } 420 | // 本时刻预设车辆加入已发车列表中 421 | while (!P.empty() && Sch[P.back()].start == ts) 422 | Add.push_back(P.back()), P.pop_back(); 423 | 424 | // 更新各统计量 425 | if (!Add.empty()) for (int i = 0, nn = 0, sz = int(Add.size()); i < sz; i++) { 426 | int car_i = Add[i]; 427 | if (current->car_time[car_i]) continue; 428 | if (mp.cars[car_i].preset) continue; 429 | if (!nn) mse = time_avg = 0; 430 | nn++; 431 | double x = ahead->car_time[car_i]; 432 | double d = ahead->car_time[car_i] - car_time[car_i]; 433 | time_avg += (x - time_avg) / nn; 434 | time_avg_all = time_avg_all < 1 ? x : time_avg_all * alpha + (1 - alpha) * x; 435 | mse += (d * d - mse) / nn; 436 | } 437 | 438 | current->run(Sch, Add, ts + 1); // 车辆上路,更新当前路况 439 | n_sent += Add.size(); 440 | for (auto i : Add) if (mp.cars[i].prior) n_sent_p++; 441 | 442 | if (margin >= 0 && ts > init_tpri + margin + mp.early_prior && current->n_p < mp.n_prior) 443 | throw std::logic_error("优先车辆在时限内未全部发车"); 444 | if (ahead->te > max_te) throw std::logic_error("超过te上限"); 445 | 446 | if (print) { 447 | printf("用时偏差RMSE / 用时均值: %.3f / %.3f = %.3f\n", sqrt(mse), time_avg, 448 | sqrt(mse) / std::max(1.0, time_avg)); 449 | printf("car_time: "); 450 | for (int i = 0; i < std::min(5, l); i++) printf("%.3f ", car_time[Add[i]]); 451 | printf("\n路上车辆数量: %d (%d)\n", current->n - current->n_arrive, 452 | current->n_p - current->n_arrive_p); 453 | printf("运行用时: %.1f\n\n", double(clock() - cur) * 1000 / CLOCKS_PER_SEC), cur = clock(); 454 | } 455 | } 456 | if (n_sent < mp.n_cars) throw std::logic_error("规定时间内未全部发车"); 457 | ahead->print(); 458 | return std::make_tuple(ahead->ts, ahead->tpri, ahead->te); 459 | } 460 | }; 461 | 462 | 463 | #endif /* ifndef SCHEDULER_H */ 464 | -------------------------------------------------------------------------------- /task/2019华为软件精英挑战赛-决赛-任务书-v3.0.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongroo/Huawei-CodeCraft-2019/919a48fba7cf351a39617e0afc054d4f520cd1ae/task/2019华为软件精英挑战赛-决赛-任务书-v3.0.docx -------------------------------------------------------------------------------- /task/2019华为软件精英挑战赛-决赛-需求变更-v3.1.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongroo/Huawei-CodeCraft-2019/919a48fba7cf351a39617e0afc054d4f520cd1ae/task/2019华为软件精英挑战赛-决赛-需求变更-v3.1.docx -------------------------------------------------------------------------------- /task/2019华为软件精英挑战赛-初赛-任务书-v1.5.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongroo/Huawei-CodeCraft-2019/919a48fba7cf351a39617e0afc054d4f520cd1ae/task/2019华为软件精英挑战赛-初赛-任务书-v1.5.docx -------------------------------------------------------------------------------- /task/2019华为软件精英挑战赛-复赛-任务书-v2.1.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongroo/Huawei-CodeCraft-2019/919a48fba7cf351a39617e0afc054d4f520cd1ae/task/2019华为软件精英挑战赛-复赛-任务书-v2.1.docx -------------------------------------------------------------------------------- /task/2019华为软件精英挑战赛-复赛-需求变更-v2.1.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kongroo/Huawei-CodeCraft-2019/919a48fba7cf351a39617e0afc054d4f520cd1ae/task/2019华为软件精英挑战赛-复赛-需求变更-v2.1.docx -------------------------------------------------------------------------------- /参赛历程-reku.md: -------------------------------------------------------------------------------- 1 | ### 初赛 2 | 我们很早就听说了华为软件精英挑战赛,但是最开始并没有想着参赛,觉得那么多学校队伍参赛,就算有奖金也轮不到我们来拿,而且也有认识的同学参加了去年的软挑,最后喜迎一轮游。后来初赛已经进行了好多天,才被冼学长拉着参赛。因为赛事规则是交通车辆调度,我们先想了队伍口号“已经在路上了”,然后根据这个“已经在路上了”的鸽子表情包,决定了我们的队名叫“咕咕咕”。这个队名其实也隐含了我们信心不足,可能在中途会鸽掉比赛的含义。 3 | 4 | 最开始的一周我们三个人苦读交通规则,但是并没有什么特别好的思路,对于死锁解决的思路更是一头雾水。姜学长表示我们应该要写一个判题器,这样至少可以方便的调参,不然会被提交次数限制调参的次数。但是当时我觉得既然是初赛取前32名,应该瞎搞搞就可以进复赛了,而且留给我们的时间也不太多了。最后决定三个人分头乱搞,谁搞的结果比较好就用谁的。每个人的思路基本都是用最短路和每批发车数量来乱调参,不停尝试直到没有死锁。姜学长写出了一个不是很准确的但是对调参很有帮助的判题器,进行了迅猛调参,在比赛前夜冲到了赛区第三,是我们三个里面的最好成绩。所以我们最后采用了姜学长的调度程序,这个程序就成为了我们后来每一版程序的基础,姜学长也成为了本次比赛的主代码手。 5 | 6 | 在正式赛的时候,因为调参过于迅猛,参数出现了严重的过拟合。正式赛数据量很大,而我们的判题器跑的速度也很慢,调度时间和死锁更判不准,在正式赛中无法跑出符合期望的结果。看到群里大佬们不停的秀出自己成绩的截图,我们的心情也越来越苦涩...幸运的是最后我们获得了赛区24名,有惊无险的进入杭厦赛区复赛,如果是在成渝或者西北赛区,我们就一轮游了。 7 | 8 | ### 复赛 9 | 初赛的结果告诉了我们,我们的调参能力是不太行的,而且因为复赛预制车辆的加入,整个道路会更加容易死锁,如果想取得好一点的成绩,必须要利用判题器生成没有死锁的答案。经过我们的讨论,我们选择了按时间片枚举,二分每个时刻出发车辆数的算法框架,二分的时候用判题器进行快速的判断。这是一个很重要而且很幸运的决策,到最后决赛我们的整体算法也没有脱离这个思路,而且根据决赛颁奖仪式上出题老师们的比较来看,我们这个通过二分激进的发车确实要比均匀发车的效果要好。但是这个算法一次要跑好多遍判题器,对判题器的速度要求很高,而且我们的判题器还有很多bug,所以当务之急就是要尽快的优化和修正判题器。 10 | 11 | 确定这个思路没几天,我们就利用了错误的判题器幸运的跑出了4600多的成绩,从这个成绩开始我们就踏上了漫长的“霸榜”之路。这个成绩给了我们很大鼓励,之前我们并不认为我们有希望进入全国总决赛,但是出了这个成绩之后,我们甚至还想在全国总决赛上面拿奖。虽然这个成绩很不错,但是我们只要稍微调整一下参数,就会产生死锁,而且本地的判题器无法检测,这让我们非常苦恼。后续的工作基本都是在优化判题器速度和修正判题器正确性。我们三个人都一遍一遍的调试检查代码,对着论坛上面的判题器流程图和自己的代码度过了无数个难眠的夜晚,期间也检查出了许多bug,但是最后结果还是无法一致,后来我们甚至想到用其他语言重写一个判题器跟我们的C++判题器进行对拍...最后终于,冼学长凭借着老辣的代码经验,找到了一个隐藏在深处的逻辑bug,我们终于获得了一个跟线上完全一致的高速判题器!就是这个判题器成为了我们夺冠的最重要的法宝。 12 | 13 | 来到了杭研院的正式赛现场,我们的心情还是非常紧张的。我们当时需要调整大概七个参数,在讨论并实现完需求变更之后,就一直在调参。结果忙中有错,因为正式赛是两张地图,有的时候调参只是一个地图效果好而另一个地图效果很差。在还有二十多分钟结束比赛的时候,我提交了一个在图一上面效果很好的代码,结果跑完发现在图二上面的效果特别差,还不如我们最开始修改完需求提交的成绩,跟我们调出来的最佳成绩更是差了十万八千里。这时比赛还有十分钟结束,如果现在提交代码,肯定在比赛结束前无法跑出结果,而且我们之前比较优的参数的打包文件还因为误操作并没有保存。这时我还是决定回忆一个比较优的参数,然后紧张提交一发...全场的参赛队员都去参加华为的家庭日活动了,只有我们三个还有HR哥哥在盯着屏幕看我们最后一发的结果,因为如果这次提交死锁,我们之前的努力就都白费了。所幸最后跑出了2498的总成绩,虽然离我们的历史最佳2480还差一点点,但是也还算不错。后来出了结果,我们还发觉我们的复赛正式赛成绩是全国第一,非常激动,给了我们非常大的信心。 14 | 15 | ### 决赛 16 | 决赛的时候增加了车牌识别的需求,姜学长在决赛阶段继续主要负责车辆调度算法的调整,所以车牌识别部分就由我跟冼学长来负责。因为我们三个人基本都没有任何CV的经验,开始的时候我们讨论了很多思路,也参考了很多业界成熟的做法,也通过HR结识了一些车牌识别的专家与我们进行讨论。我觉得主要的困难在于数据量不足,模型方面倒是比较简单。起初想用GAN来生成数据用于训练,后来通过观测数据,我们觉得本来给我们的车牌就不够真实,再用给我们的虚假的车牌去训练GAN网络生成更加虚假的车牌,效果应该不会很好。于是我们形成了两套方案,第一套是通过opencv等方式完全自己生成训练集,训练集中没有任何官方给出的数据;第二套方案就是简单的划分官方数据为训练集和测试集然后进行训练。我跟冼学长每个思路各实现一个,发现两个思路的效果其实差不多,决定现场赛的时候看实际的效果来选择模型。 17 | 18 | 在车辆调度部分,因为在复赛的时候因为调参,出现了很多差错,而且决赛的赛制跟复赛还有很大的差异:决赛时期的地图是看不到的,所以直接对着某些地图调参可能并没有什么用。于是对大部分参数,都调整到一个比较优的状态,然后对一个比较关键的参数在程序里面直接进行二分。在寻路优化算法这方面,我们也添加了很多统计量,效果也优化了不少。 19 | 20 | 调整好车牌识别之后,我们也一直在练习赛中登顶,但是成渝赛区和江山赛区的很多队伍跟我们在练习赛的成绩也很接近。在比赛现场,我们的位置跟最接近我们的FatCat队背靠背,看到这个位置安排之后,我们三个都感觉心里一紧... 21 | 22 | 正式竞赛的前半段还是比较轻松的,很快实现了需求变更,提交之后感觉优化效果也不错,最主要的是没有任何参数可以调整,所以有段时间觉得无所事事。但是在最后半小时我们突然发现,本地判题器的全部车辆总调度时间跟线上的对不上,顿时产生了“我们判题器写错了,我们要死锁了”的想法。后来经过冷静阅读任务书,发现总调度时间要看每个车辆的单独识别率,我们根本无从得知这个单独识别率,所谓的本地和线上对不上只是自己吓自己。不过这么明显的问题,我们在练习赛的时候竟然没有发现,感觉之前做的工作还是有一些不足。 23 | 24 | 虽然我们获得了全国总冠军,但是过程中并没有像想象中的一骑绝尘。在某一轮中,我们被亚军的江山赛区的队伍超过;在之前的一轮的复盘过程中,我们还发现所有队伍中调度时间最短的那支队伍,车牌识别率竟然只有百分之五十,如果他们的车牌识别做的好一点,恐怕冠军就要易主了。 25 | 26 | 最后,感谢华为,感谢软挑,明年咕咕咕还会再来的! 27 | 28 | --------------------------------------------------------------------------------