vlambda博客
学习文章列表

[转载]【R语言新书】前言Ⅰ—谈如何学习编程语言

0.1 怎么学习编程语言?

编程语言是人与计算机沟通的一种形式语言,根据设计好的编程元素和语法规则,来严格规范地表达我们想要做的事情的每一步(程序代码),使得计算机能够明白并正确执行,得到期望的结果。

编程语言和数学语言很像,数学语言是最适合表达科学理论的形式语言,用数学符号、数学定义和逻辑推理,来规范严格地表达科学理论。

很多人说:“学数学,就是靠大量刷题”,“学编程,就是照着别人的代码敲代码”。

我不认可这种观点,这样的学习方法事倍功半,关键是这样做你学不会真正的数学,也学不会真正的编程!

那么应该怎么学习编程语言呢?

就好比要成为一个好的厨师,首先得熟悉各种常见食材的特点秉性,掌握各种基本的烹饪方法,然后就能根据客人需要随意组合食材和烹饪方法制作出各种可口的大餐。

数学的食材就是定义,烹饪方法就是逻辑推理,一旦你真正掌握了定义+逻辑推理,各种基本的数学题都不再话下,而且你还学会了数学知识。

同理,编程的食材和烹饪方法就是编程元素语法规则,比如数据结构(容器)、分支/循环结构、自定义函数等,一旦你掌握了这些编程元素语法规则,根据问题的需要,你就能信手拈来优化组合它们,从而自己写出代码解决问题。

所以,学习任何一门编程语言,根据我的经验,有这么几点建议(步骤):

1. 理解该编程语言的核心思想,比如 Python 是面向对象,R 语言是面向函数也面向对象,另外,高级编程语言还都倡导向量化编程。在核心思想的引领下去学它去思考去写代码。

2. 学习该编程语言的基础知识,这些基础知识本质上是相通的同样的东西,只是在不同编程语言下披上了其特有的外衣(编程语法),基础知识包括:数据类型及数据结构(容器)、分支/循环结构、自定义函数、文件读写、可视化等。

3. 前两步完成之后,就算基本入门【脚注1】了,可以根据需要,根据遇到的问题,借助网络搜索、借助帮助,遇到问题解决问题,逐步提升,用的越多会的越多,也越熟练

脚注1: 至少要经历过一种编程语言的入门,再学习其它编程语言就会很快。

以上是学习编程语言的正确、快速、有效的方法,切忌不学基础语法,用到哪就突击哪,找别人的代码一顿不知其所以然的瞎改,这样始终无法入门,更谈不上将来提高。

再来谈一个学编程过程中普遍存在的问题:如何跨越能看懂别人的代码自己写代码的鸿沟。

很多人绝大多数人在编程学习过程中,都要经历这样一个过程:

  1. 学习基本语法
  2. 能看懂和调试别人的代码
 (编程之门)
3. 自己写代码
前两步没有任何难度,谁都可以做到。从第 2 步到第 3 步是一个“坎”,很多人困惑于此而无法真正进入编程之门。网上也有很多讲到如何跨越这步的文章和说法,但基本都是脱离实际操作的空谈,比如多敲书上的代码之类,治标不治本(只能提升编程基本知识)。
我的理念说白了也很简单,无非就是:分解问题 + 实例梳理 + 翻译及调试,具体来说,
  • 将难以入手大问题分解为可以逐步解决的小问题
  • 用计算机的思维去思考解决每步小问题
  • 借助类比的简单实例和代码片段,梳理出详细算法步骤
  • 将详细算法步骤用逐片段地用编程语法翻译成代码并调试通过
关于调试补充一点,可以说高级编程语言的程序代码就是逐片段调试出来的,借助简单实例按照算法步骤,从上一步的结果调试得到下一步的结果,依次向前推进直到到达最终的结果。还有一点经验之谈:写代码时,随时跟踪关注每一步执行,变量、数据的值是否到达你所期望的值,非常有必要!
这是我用数学建模的思维得出的科学的操作步骤。为什么大家普遍自己写代码解决具体问题时感觉无从下手呢?
这是因为你总想一步就从问题代码,没有中间的过程,即使编程高手也做不到。当然,编程高手可能能缩减这个过程,但不能省略这个过程。其实你平时看编程书是被欺骗了:编程书上只写问题(或者有简单分析)紧接着就是代码,给人的感觉就是应该从问题直接到代码,大家都是这么写出来的。其实不然。
所以,改变从问题直接到代码思维固式,按我上面说的步骤去操作,每一步是不是都不难解决。那么,自然就从无从下手,到能锻炼自己写代码了。
开始你可能只能自己写代码解决比较简单的问题,但是这种成就感再加上慢慢锻炼下来,你自己写代码的能力会越来越强,能解决问题也会越来越复杂,当然前提是,你已经真正掌握了基本编程语法,可以随意取用。当然二者也是相辅相成、共同促进和提高的关系。
好,说清了这个道理,下面拿一个具体的小案例来演示一下。
例 1.1 计算并绘制 ROC 曲线
ROC 曲线是二分类机器学习模型的性能评价指标,已知测试集或验证集每个样本真实类别及其模型预测概率值,就可以计算并绘制 ROC 曲线。
先来梳理一下问题,ROC 曲线是在不同分类阈值上对比真正率(TPR)与假正率(FPR)的曲线。
分类阈值就是根据预测概率判定预测类别的阈值,要让该阈值从 0 到 1 以足够小的步长变化,对于每个阈值 `c`,比如 0.85, 则预测概率  [转载]【R语言新书】前言Ⅰ—谈如何学习编程语言  判定为 "Pos",  [转载]【R语言新书】前言Ⅰ—谈如何学习编程语言  判定为 "Neg". 这样就得到了预测类别。
根据真实类别和预测类别,就能计算混淆矩阵:
[转载]【R语言新书】前言Ⅰ—谈如何学习编程语言
进一步就可以计算:
[转载]【R语言新书】前言Ⅰ—谈如何学习编程语言
有一个阈值,就能计算一组 TPR 和 FPR,循环迭代都计算出来并保存。再以 FPR 为 x 轴,以 TPR 为 y 轴绘图,则得到 ROC 曲线。
于是,梳理一下经过分解后的问题:
  • 让分类阈值以某步长在 [1,0] 上变化取值;
  • 对某一个阈值,
- 计算预测类别
- 计算混淆矩阵
- 计算 TPR 和 FPR
  • 循环迭代,计算所有阈值的 TPR 和 FPR
  • 根据 TPR 和 FPR 数据绘图
下面拿一个小数据算例,借助代码片段来推演上述过程。现在读者不用纠结于代码,更重要的是体会,自己写代码解决实际问题的这样一个思考过程。
 
   
   
 
library(tidyverse)
df = tibble(
ID = 1:10,
`真实类别`=c("Pos","Pos","Pos","Neg","Pos","Neg","Neg","Neg","Pos","Neg"),
`预测概率`=c(0.95,0.86,0.69,0.65,0.59,0.52,0.39,0.28,0.15,0.06)
)
knitr::kable(df)
[转载]【R语言新书】前言Ⅰ—谈如何学习编程语言
先来解决对某一个阈值,计算 TPR 和 FPR。以 c = 0.85 为例。
计算预测类别,实际上就是 `If-else` 语句根据条件赋值,当然是用整洁的 `tidyverse` 来做。顺便多做一件事情:把类别变量转化为因子型,以保证 "Pos" 和 "Neg" 的正确顺序,与混淆矩阵中一致。
 
   
   
 
c = 0.85
df1 = df %>%
mutate(
`预测类别` = ifelse(预测概率 >= c, "Pos", "Neg"),
`预测类别` = factor(预测类别, levels = c("Pos", "Neg")),
`真实类别` = factor(真实类别, levels = c("Pos", "Neg"))
)
knitr::kable(df1)
[转载]【R语言新书】前言Ⅰ—谈如何学习编程语言
计算混淆矩阵,实际上就是统计交叉频数,本来为 "Pos" 预测为 "Pos" 的有多少,等等。用 R 自带的 `table()` 函数就能实现:
 
   
   
 
cm = table(df1$预测类别, df1$真实类别)
cm
[转载]【R语言新书】前言Ⅰ—谈如何学习编程语言
计算 TPR 和 FPR。根据其的计算公式,从混淆矩阵中取数计算即可。这里咱们再高级一点,用向量化计算来实现,毕竟高级编程语言提倡向量化嘛。关于向量化编程,说白了就是,对一列/矩阵/多维数组的数同时做同样的操作,既提升程序效率又大大简洁代码。
向量化编程,关键是要用整体考量的思维来思考、来表示运算。比如这里计算 TPR 和 FPR,通过观察可以发现:混淆矩阵的第 1 行各元素,都除以其所在列和,正好是 TPR 和 FPR。
 
   
   
 
cm["Pos",] / colSums(cm)
[转载]【R语言新书】前言Ⅰ—谈如何学习编程语言
这就完成了本问题的核心部分。接下来,要循环迭代对每个阈值,都计算一遍 TPR 和 FPR。用 `for` 循环当然可以,但咱们仍然更高级一点:泛函式编程。
先把上述计算封装为一个自定义函数,该函数只要接受一个前文原始的数据框 `df` 和一个阈值 `c`,就能返回来你想要的 TPR 和 FPR。然后,再把该函数应用到数据框 `df` 和一系列的阈值上,循环迭代自然就完成了。这就是 泛函式编程
 
   
   
 
cal_ROC = function(df, c) {
df = df %>%
mutate(
`预测类别` = ifelse(预测概率 >= c, "Pos", "Neg"),
`预测类别` = factor(预测类别, levels = c("Pos", "Neg")),
`真实类别` = factor(真实类别, levels = c("Pos", "Neg"))
)
cm = table(df$预测类别, df$真实类别)
t = cm["Pos",] / colSums(cm)
list(TPR = t[[1]], FPR = t[[2]])
}
测试一下这个自定义函数,能不能算出来刚才的梳理时结果:
 
   
   
 
cal_ROC(df, 0.85)
[转载]【R语言新书】前言Ⅰ—谈如何学习编程语言
没问题,下面将该函数应用到一系列的阈值上(循环迭代),并一步到位将每次计算的两个结果按行合并到一起,这就彻底完成数据计算:
 
   
   
 
c = seq(1, 0, -0.02)
rocs = map_dfr(c, cal_ROC, df = df)
head(rocs) # 查看前 6 个结果

最后,用著名的 `ggplot2` 包绘制 ROC 曲线图形:
 
   
   
 
rocs %>%
ggplot(aes(FPR, TPR)) +
geom_line(size = 2, color = "steelblue") +
geom_point(shape = "diamond", size = 4, color = "red") +
theme_bw()
以上就是我所主张的学习编程的正确方法,我不认为照着别人的编程书敲代码是学习编程的好方法。

——————————
非常欢迎大家提出改进的意见和和建议!

本文是转载的张敬信老 师的R语言新书《R语言编程—基于tidyverse》 的第一章。大家可在知乎上查看张老师的原文(https://zhuanlan.zhihu.com/p/198185888)。