春招项目经历的整理


  整理一下迄今为止做过的一些大大小小项目的经历。

数学建模

  2018年参与了当年的全国大学生数学建模竞赛拿到了全国一等奖,记得应该是浙江省第一名的顺位上推拿的,但可惜时间太久远很多东西忘记了,所以华为的主管面详细问起这个项目就没讲清楚挂了。

题目

  设计一款高温防护服,包含三层织物Ⅰ,Ⅱ,Ⅲ以及最内层和皮肤之间的空气层Ⅳ层。

  1. 已知环境温度75摄氏度,Ⅱ层,Ⅳ层厚度分别为6mm, 5mm,假设假人体表起始温度为37摄氏度,工作90分钟,建立模型计算温度随时间和空间的分布。
  2. 当环境温度为65摄氏度,Ⅳ层厚度为5.5mm,求II层的最优厚度,保证工作时间为60分钟时假人体表温度不超过47摄氏度,且超过44摄氏度的时间不超过5分钟。
  3. 假设环境温度为80摄氏度,求Ⅱ层和Ⅳ层的最优厚度,保证工作三十分钟时,假人体表温度不超过47摄氏度,且超过44摄氏度的时间不超过5分钟。

问题解决

  假设各层在试验期间不发生形变且厚度均匀,假设织物表面的热传递均匀即热传递可以看作一个一维的径向传递,我们可以将防护服的模型抽象成下图这样一个模型。
模型抽象
  各层之间的边界分别记为 w 1 , w 2 , w 3 , w 4 , w 5 w_1, w_2, w_3, w_4, w_5 w1,w2,w3,w4,w5

第一问

  针对第一问,利用物理学上的热传导的规律,傅里叶定律和牛顿冷却定律,可以求得各层最终的稳态温度,从而求得隔热服模型的边界条件,于是问题就变成了对偏微分方程的求解,利用CN差分对偏微分方程进行差分,就能得到各层温度根据时间变化的规律。
  这一问采用CN差分是为了取显式和隐式的二者的优点,显式差分后一时刻或后一位置的状态仅仅取决于前面时刻和前面位置的状态,利用之前的状态可以直接求解,其优点是计算简单,但缺点是对步长有要求,否则很难达到稳定结果;而隐式差分当前时刻当前位置的状态则要取决于后面的时候后面的位置,不能通过显式的计算得到,它的优点是通常具有无条件稳定性。CN差分不仅拥有无条件稳定性,并且计算的也远比隐式差分简便。

第二问

  在第一问对内部的热传递的情况进行建模后,第二问我们将Ⅱ层厚度设为未知数x,外部温度设为65,代入第一问的模型中,得到一组方程,这一组方程的约束条件分别是60分钟时内部温度不高于47,高于44的时间不超过五分钟,这样就建立起了一个但目标规划的模型,但是由于约束条件非线性,所以不能进行直接的求解,在这一问我们采用了遗传算法对单目标规划模型进行求解。
  遗传算法是模拟自然界的自然选择交叉变异来对单目标规划模型的最优解进行逼近的一种算法。初始化一批初始种群,随机赋予厚度,选择二进制数字来表示厚度,利用对应的最终厚度作为适应度的衡量标准,选择适应度高的个体进行复制和一定概率的选择和变异,得到下一批种群,繁殖一定代数,求这一带适应度最高的厚度作为本次迭代的结果,一共进行十次迭代,对最终的结果求平均值。

第三问

  思想类似第二问,其实可以直接用遗传算法求解多目标规划,但当时我们选择了为两层赋予一定权值,由于第一问总结得出空气层的隔热性能优于织物层,所以我们的目标是尽可能减少织物厚度,而保留较大的空气层厚度,可以实现更加有效的防护,所以我们将多目标转换为了 m i n ( x 2 + x 4 ) min (x_2+x_4) min(x2+x4)转换为了 m i n ( 1.3 x 4 ) min(1.3x_4) min(1.3x4), 即把Ⅱ层的厚度设置为Ⅳ层的0.3, 在进行类似于第二问的单目标规划。

信息检索

题目

  给一个tsv文件,里面包含了qid, pid, query, passage四列,分别代表query的id,passage的id,query的内容,passage的内容,每一个query有1000个candidate passage,要求分别利用vector space,BM25,以及query likelihood模型为每个query选出前100篇passage作为检索结果,其中腰围query likelihood模型进行三种不同的平滑处理,包括Dirichlet,Laplace以及Lidstone。

预处理

  由于passage和query都是string类型的自然语言,所以首先我对他们进行了预处理,利用python的nltk包进行了去除停用词,标点符号,以及将所有的字母转化为了小写字母。

解法

  首先建立一个inverted index,用于存储后面我们在模型中所需要的所有信息,比如某一个词汇在某一篇文章里出现了多少次,在一个query的多少篇candidate passage里出现等, index的结构如下:
index

Vector Space模型

  在这个模型中,对某一个query,我对它的每一个单词,求了它在query和每一篇candidate passage中出现的次数tf,并求了idf(用于表示该单词在多少篇passage里出现,衡量词汇的常见程度),将两个tf与idf相乘分别作为query和passage的一个维度,然后我们就可以将每一个query和他的candidate passage都表示为一个向量,求余弦相似度,从高到低排列。

Language模型

按照query是从passage中生成出来的可能性排序。可能性等于query中每一个词在passage中出现的频率之积。存在一个问题,只要有一个词没出现过,积就为零,所以应该做平滑处理。

Laplace

让每个词的出现次数都加一

Lidstone

让每个词的出现次数加一个小于1的数

Dirichlet

考虑每一个词在query的所有candidate passage里出现的概率。 下图中1800是这篇passage的总词数,2000是我们自己定义的一个常数,15是在本文中出现的次数,160000是在所有candidate中出现的次数。
dirichlet

评估

Average Precision

AP

NDCG

DCG
  但DCG存在一个问题,若所有的candidate相关性都很差,其实无法考证我们的模型优劣,所以我们将检索到的所有passage按relevancy降序排列,再求一次DCG,这个结果叫做IDCG,是检索到的文章所能达到的最大的DCG值,用原本检索到的顺序求得的DCG除以IDCG,若接近1,说明模型已经尽力,否则说明模型本身就存在较大的问题。

C++线性回归

  用c++写一个命令行程序,要求包含数据生成,数据读取,数据拟合的功能,并且为各个模块编写单元测试。

获得数据

  要求程序提供两种获得数据的方法,一种是从文件中读取数据,还有一种是生成大致为 y = θ 0 + θ 1 x y=\theta_0+\theta_1x y=θ0+θ1x的数据。

  1. 首先创建一个纯虚基类,仅包含一个纯虚函数用于获得数据。
  2. 然后创建两个头文件,分别包含一个读取数据的类和生成数据的类,均为第一步创建的纯虚类的子类,同样在头文件内部并不定义方法,仅仅对各自所需要的方法和成员做一个声明。
  3. 创建两个cpp文件,分别include第二步创建的两个头文件,在cpp文件内定义两个类声明的函数。
  4. 最后将头文件和源文件都加到cmakelist文件里。

  在这个过程中用到的一些知识点包括纯虚函数纯虚类,在我的这篇博客里做了一个大概的梳理和我自己的理解;依赖注入,通过构造函数或者setter将所需的依赖从外部注入,减少类的责任,降低类和类之间的耦合程度;析构函数,执行对象析构,释放内存,防止内存泄漏;头文件的首部要加上#ifndef,这是为了防止头文件被多个源文件引用而被多次编译导致大量声明冲突。

  对此其实还存在一些问题:

  • 为什么要引入纯虚函数?
  • 为什么要将类的成员函数的声明和实现分开在头文件和源文件内?

  对于第二个问题做了一些查找,众说纷纭,有的人说是因为c这么做所以c++也这么做,是因为落后,也有人说是为了方便代码的共享,共同协作的人只需要include这个头文件即可,我个人感觉确实方便实现代码的共享。

拟合数据

  和生成数据类似的,也是创建一个虚基类,派生两个具体实现类,分别用正规方程和梯度下降来做拟合,然后在另外的cpp文件实现。
  实现过程并不困难,这里利用了EIGEN库来进行向量运算。
正规方程公式: θ = ( X T X ) − 1 X T y \theta = (X^TX)^{-1}X^Ty θ=(XTX)1XTy
批梯度下降:迭代制定代数的如下公式 g r a d i e n t = 2 m X T ( X θ − y ) gradient = \frac{2}{m}X^T(X\theta-y) gradient=m2XT(Xθy) θ = θ − e t a ∗ g r a d i e n t \theta = \theta - eta*gradient θ=θetagradient
  注意这里的X都是在每一个x前面加了一个1构成的矩阵,这样才能和 θ \theta θ进行运算,eta指的梯度下降的步长。

编写测试

  实际上就是在一个cpp文件文件里对各个类的功能进行调用,并对其结果作预测,格式如下:

TEST_CASE("xxx","[xxxxxx]"){
    ...
    REQUIRE(...);
}

  然后同样要把这个cpp文件包括在cmakelist里面,可以用ctest进行测试,也可以运行它的可执行文件来测试。

编写主程序

  利用main函数里的argv和arvc参数进行判断用户的需求,用-h来告知用户如何运行本命令行程序。

编译

  在Linux的命令行中输入mkdir build,进入build文件夹,执行cmake .以及make命令,就可以生成可执行文件在./build/bin目录下了。

  这里想顺便提一下cmake,make和cmakelist,makefile。

  • make,可以看成是一个批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式调用makefile文件中用户指定的命令来进行编译和链接。
  • makefile,包含用户指定的命令,编译和链接。
  • cmake,生成makefile文件给make用。
  • cmakelist,cmake就是通过cmakelist里面的要求生成makefile文件的。

  上面用到cmakelist通过set函数,指定头文件和源文件来生成库,供main函数调用,通过set函数指定哪些包含main函数的文件要生成可执行文件。

C++太阳系运动模型

  编写一个命令行程序,用于模拟太阳系的九颗星体的运动,用户可以在命令行里指定step size和step number。

实现

  定义一个星体类,包含速度,位置,Mu,加速度以及相互作用星体这五个成员变量,前三个变量在创建对象时就通过依赖注入来赋予,相互作用星体是一个vector,通过add和remove方法来增加和该星体存在引力作用的星体,而加速度则是跟据相互作用星体内的星体计算得到的,而模拟运动过程则是通过计算在当前加速度下通过一个较短的time step后星体将处于一个什么样的位置来实现形体的运动。
  根据提供的九大天体的初始状态,计算一年时间后太阳系内部所有天体的状态。

知识点

  项目本身的难度不大,有以下几点需要注意:

  • add和remove相互作用星体方法所需要的参数是智能指针shared_ptr,在做的同时学习了一些关于智能指针的知识。我认为这里用指针传递参数是因为如果直接传递一个星体对象,每一次都要创建一个拷贝,内存浪费太大,而用普通指针则容易引起内存泄露的问题,用shared_ptr可以保证内存的正确释放。
  • 由于在一年的时间内,每经过一个步长的时间都要遍历九个天体,计算加速度,速度和位置,所以很自然地想到利用多线程并行运算。这里用到的是opneMP框架,可以很简单地实现并行运算。
// 设置并行线程数
omp_set_num_threads(9);
// 开始并行的for循环
#pragma omp parallel
{
    (*planets[omp_get_thread_num()]).calculateAcceleration();
    // 以下变量要做同步控制,否则可能会出现数据竞争,critical一次只允许一个线程访问
    #pragma omp critical
    if(stepnum%30 == 0){
        r_com += (*planets[omp_get_thread_num()]).getMu()*(*planets[omp_get_thread_num()]).getPosition();
        MuSum += (*planets[omp_get_thread_num()]).getMu();
        p_total += (*planets[omp_get_thread_num()]).getMu()*(*planets[omp_get_thread_num()]).getVelocity();
    }
}

  除了用critical,也可以用#pragma omp parallel for reduction(+:list)这一语句来进行并行控制,原理是为每个线程拷贝一份list内的变量,最终将所有线程所得到的值相加。

新冠数据分析

学校提供了一系列世卫组织统计的新冠病例相关数据,包含了不同地区在不同时间段内不同性别不同年龄段每天的确诊病例,死亡病例,痊愈病例等数据。数据以Json文件格式存储。我们的任务是对数据进行分析和可视化,寻找影响新冠疫情传播和增长的影响因素。
在load数据时我们需要对数据进行检查,检查文件格式,数据是否符合一定的schema。

处理年龄问题的时候,由于不同地区对年龄段的划分不同,我们要对年龄做一个合并。同时存在一部分数据缺失的问题,可能造成计算比例的时候除数为零,所以要对这些情况进行排查。还有一些地区的确诊病例并非每日更新,有的两天到三天更新一次,所以我们还需要对数据进行平滑处理,利用一个滑动窗口对确诊病例进行平滑。数据中还包含了天气,所以我们还对天气对于病例增长的速率的影响进行了研究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值