分类 软件开发 下的文章

用朴素贝叶斯分类器解决现实世界里的机器学习问题。

 title=

朴素贝叶斯 Naïve Bayes 是一种分类技术,它是许多分类器建模算法的基础。基于朴素贝叶斯的分类器是简单、快速和易用的机器学习技术之一,而且在现实世界的应用中很有效。

朴素贝叶斯是从 贝叶斯定理 Bayes' theorem 发展来的。贝叶斯定理由 18 世纪的统计学家 托马斯·贝叶斯 提出,它根据与一个事件相关联的其他条件来计算该事件发生的概率。比如,帕金森氏病 患者通常嗓音会发生变化,因此嗓音变化就是与预测帕金森氏病相关联的症状。贝叶斯定理提供了计算目标事件发生概率的方法,而朴素贝叶斯是对该方法的推广和简化。

解决一个现实世界里的问题

这篇文章展示了朴素贝叶斯分类器解决现实世界问题(相对于完整的商业级应用)的能力。我会假设你对机器学习有基本的了解,所以文章里会跳过一些与机器学习预测不大相关的步骤,比如 数据打乱 date shuffling 数据切片 data splitting 。如果你是机器学习方面的新手或者需要一个进修课程,请查看 《An introduction to machine learning today》 和 《Getting started with open source machine learning》。

朴素贝叶斯分类器是 有监督的 supervised 、属于 生成模型 generative 的、非线性的、属于 参数模型 parametric 的和 基于概率的 probabilistic

在这篇文章里,我会演示如何用朴素贝叶斯预测帕金森氏病。需要用到的数据集来自 UCI 机器学习库。这个数据集包含许多语音信号的指标,用于计算患帕金森氏病的可能性;在这个例子里我们将使用这些指标中的前 8 个:

  • MDVP:Fo(Hz):平均声带基频
  • MDVP:Fhi(Hz):最高声带基频
  • MDVP:Flo(Hz):最低声带基频
  • MDVP:Jitter(%)MDVP:Jitter(Abs)MDVP:RAPMDVP:PPQJitter:DDP:5 个衡量声带基频变化的指标

这个例子里用到的数据集,可以在我的 GitHub 仓库 里找到。数据集已经事先做了打乱和切片。

用 Python 实现机器学习

接下来我会用 Python 来解决这个问题。我用的软件是:

  • Python 3.8.2
  • Pandas 1.1.1
  • scikit-learn 0.22.2.post1

Python 有多个朴素贝叶斯分类器的实现,都是开源的,包括:

  • NLTK Naïve Bayes:基于标准的朴素贝叶斯算法,用于文本分类
  • NLTK Positive Naïve Bayes:NLTK Naïve Bayes 的变体,用于对只标注了一部分的训练集进行二分类
  • Scikit-learn Gaussian Naïve Bayes:提供了部分拟合方法来支持数据流或很大的数据集(LCTT 译注:它们可能无法一次性导入内存,用部分拟合可以动态地增加数据)
  • Scikit-learn Multinomial Naïve Bayes:针对离散型特征、实例计数、频率等作了优化
  • Scikit-learn Bernoulli Naïve Bayes:用于各个特征都是二元变量/布尔特征的情况

在这个例子里我将使用 sklearn Gaussian Naive Bayes

我的 Python 实现在 naive_bayes_parkinsons.py 里,如下所示:

import pandas as pd

# x_rows 是我们所使用的 8 个特征的列名
x_rows=['MDVP:Fo(Hz)','MDVP:Fhi(Hz)','MDVP:Flo(Hz)',
        'MDVP:Jitter(%)','MDVP:Jitter(Abs)','MDVP:RAP','MDVP:PPQ','Jitter:DDP']
y_rows=['status'] # y_rows 是类别的列名,若患病,值为 1,若不患病,值为 0

# 训练

# 读取训练数据
train_data = pd.read_csv('parkinsons/Data_Parkinsons_TRAIN.csv')
train_x = train_data[x_rows]
train_y = train_data[y_rows]
print("train_x:\n", train_x)
print("train_y:\n", train_y)

# 导入 sklearn Gaussian Naive Bayes,然后进行对训练数据进行拟合
from sklearn.naive_bayes import GaussianNB

gnb = GaussianNB()
gnb.fit(train_x, train_y)

# 对训练数据进行预测
predict_train = gnb.predict(train_x)
print('Prediction on train data:', predict_train)

# 在训练数据上的准确率
from sklearn.metrics import accuracy_score
accuracy_train = accuracy_score(train_y, predict_train)
print('Accuray score on train data:', accuracy_train)

# 测试

# 读取测试数据
test_data = pd.read_csv('parkinsons/Data_Parkinsons_TEST.csv')
test_x = test_data[x_rows]
test_y = test_data[y_rows]

# 对测试数据进行预测
predict_test = gnb.predict(test_x)
print('Prediction on test data:', predict_test)

# 在测试数据上的准确率
accuracy_test = accuracy_score(test_y, predict_test)
print('Accuray score on test data:', accuracy_train)

运行这个 Python 脚本:

$ python naive_bayes_parkinsons.py

train_x:
      MDVP:Fo(Hz)  MDVP:Fhi(Hz) ...  MDVP:RAP  MDVP:PPQ  Jitter:DDP
0        152.125       161.469  ...   0.00191   0.00226     0.00574
1        120.080       139.710  ...   0.00180   0.00220     0.00540
2        122.400       148.650  ...   0.00465   0.00696     0.01394
3        237.323       243.709  ...   0.00173   0.00159     0.00519
..           ...           ...           ...  ...       ...       ...        
155      138.190       203.522  ...   0.00406   0.00398     0.01218

[156 rows x 8 columns]

train_y:
      status
0         1
1         1
2         1
3         0
..      ...
155       1

[156 rows x 1 columns]

Prediction on train data: [1 1 1 0 ... 1]
Accuracy score on train data: 0.6666666666666666

Prediction on test data: [1 1 1 1 ... 1
 1 1]
Accuracy score on test data: 0.6666666666666666

在训练集和测试集上的准确率都是 67%。它的性能还可以进一步优化。你想尝试一下吗?你可以在下面的评论区给出你的方法。

背后原理

朴素贝叶斯分类器从贝叶斯定理发展来的。贝叶斯定理用于计算条件概率,或者说贝叶斯定理用于计算当与一个事件相关联的其他事件发生时,该事件发生的概率。简而言之,它解决了这个问题:如果我们已经知道事件 x 发生在事件 y 之前的概率,那么当事件 x 再次发生时,事件 y 发生的概率是多少? 贝叶斯定理用一个先验的预测值来逐渐逼近一个最终的 后验概率。贝叶斯定理有一个基本假设,就是所有的参数重要性相同(LCTT 译注:即相互独立)。

贝叶斯计算主要包括以下步骤:

  1. 计算总的先验概率:
    P(患病)P(患病) 和 P(不患病)P(不患病)
  2. 计算 8 种指标各自是某个值时的后验概率 (value1,...,value8 分别是 MDVP:Fo(Hz),...,Jitter:DDP 的取值):
    P(value1,\ldots,value8\ |\ 患病)P(value1,…,value8 ∣ 患病)
    P(value1,\ldots,value8\ |\ 不患病)P(value1,…,value8 ∣ 不患病)
  3. 将第 1 步和第 2 步的结果相乘,最终得到患病和不患病的后验概率:
    P(患病\ |\ value1,\ldots,value8) \propto P(患病) \times P(value1,\ldots,value8\ |\ 患病)P(患病 ∣ value1,…,value8)∝P(患病)×P(value1,…,value8 ∣ 患病)
    P(不患病\ |\ value1,\ldots,value8) \propto P(不患病) \times P(value1,\ldots,value8\ |\ 不患病)P(不患病 ∣ value1,…,value8)∝P(不患病)×P(value1,…,value8 ∣ 不患病)

上面第 2 步的计算非常复杂,朴素贝叶斯将它作了简化:

  1. 计算总的先验概率:
    P(患病)P(患病) 和 P(不患病)P(不患病)
  2. 对 8 种指标里的每个指标,计算其取某个值时的后验概率:
    P(value1\ |\ 患病),\ldots,P(value8\ |\ 患病)P(value1 ∣ 患病),…,P(value8 ∣ 患病)
    P(value1\ |\ 不患病),\ldots,P(value8\ |\ 不患病)P(value1 ∣ 不患病),…,P(value8 ∣ 不患病)
  3. 将第 1 步和第 2 步的结果相乘,最终得到患病和不患病的后验概率:
    P(患病\ |\ value1,\ldots,value8) \propto P(患病) \times P(value1\ |\ 患病) \times \ldots \times P(value8\ |\ 患病)P(患病 ∣ value1,…,value8)∝P(患病)×P(value1 ∣ 患病)×…×P(value8 ∣ 患病)
    P(不患病\ |\ value1,\ldots,value8) \propto P(不患病) \times P(value1\ |\ 不患病) \times \ldots \times P(value8\ |\ 不患病)P(不患病 ∣ value1,…,value8)∝P(不患病)×P(value1 ∣ 不患病)×…×P(value8 ∣ 不患病)

这只是一个很初步的解释,还有很多其他因素需要考虑,比如数据类型的差异,稀疏数据,数据可能有缺失值等。

超参数

朴素贝叶斯作为一个简单直接的算法,不需要超参数。然而,有的版本的朴素贝叶斯实现可能提供一些高级特性(比如超参数)。比如,GaussianNB 就有 2 个超参数:

  • priors:先验概率,可以事先指定,这样就不必让算法从数据中计算才能得出。
  • var\_smoothing:考虑数据的分布情况,当数据不满足标准的高斯分布时,这个超参数会发挥作用。

损失函数

为了坚持简单的原则,朴素贝叶斯使用 0-1 损失函数。如果预测结果与期望的输出相匹配,损失值为 0,否则为 1。

优缺点

优点:朴素贝叶斯是最简单、最快速的算法之一。
优点:在数据量较少时,用朴素贝叶斯仍可作出可靠的预测。
缺点:朴素贝叶斯的预测只是估计值,并不准确。它胜在速度而不是准确度。
缺点:朴素贝叶斯有一个基本假设,就是所有特征相互独立,但现实情况并不总是如此。

从本质上说,朴素贝叶斯是贝叶斯定理的推广。它是最简单最快速的机器学习算法之一,用来进行简单和快速的训练和预测。朴素贝叶斯提供了足够好、比较准确的预测。朴素贝叶斯假设预测特征之间是相互独立的。已经有许多朴素贝叶斯的开源的实现,它们的特性甚至超过了贝叶斯算法的实现。


via: https://opensource.com/article/21/1/machine-learning-python

作者:Girish Managoli 选题:lujun9972 译者:tanloong 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

借助终端上的 XML 工具包 XMLStarlet,你就是 XML 之星。

 title=

学习解析 XML 通常被认为是一件复杂的事情,但它不一定是这样。XML 是高度严格结构化的,所以也是相对来说可预测的。也有许多其他工具可以帮助你使这项工作易于管理。

我最喜欢的 XML 实用程序之一是 XMLStarlet,这是一个用于终端的 XML 工具包,借助这个 XML 工具包,你可以验证、解析、编辑、格式化和转换 XML 数据。XMLStarLet 是个相对较小的命令,但浏览 XML 却充满潜力,因此本文演示了如何使用它来查询 XML 数据。

安装

XMLStarLet 默认安装在 CentOS、Fedora,和许多其他现代 Linux 发行版上,所以你可以打开终端,输入 xmlstarlet 来访问它。如果 XMLStarLet 还没有被安装,你的操作系统则会为你安装它。

或者,你可以用包管理器安装 xmlstarlet

$ sudo dnf install xmlstarlet

在 macOS 上,可以使用 MacPortsHomebrew。在 Windows 上,可以使用 Chocolatey

如果都失败了,你可以从 Sourceforge 上的源代码 手动安装它。

用 XMLStarlet 解析 XML

有许多工具可以帮助解析和转换 XML 数据,包括允许你 编写自己的解析器 的软件库,和复杂的命令,如 fopxsltproc。不过有时你不需要处理 XML 数据;你只需要一个方便的方法从 XML 数据中来提取、更新或验证重要数据。对于随手的 XML 交互,我使用 xmlstarlet,这是常见的处理 XML任务的一个典型的“瑞士军刀”式应用。通过运行 --help 命令,你可以看到它提供哪些选项:

$ xmlstarlet --help
Usage: xmlstarlet [<options>] <command> [<cmd-options>]
where <command> is one of:
  ed    (or edit)      - Edit/Update XML document(s)
  sel   (or select)    - Select data or query XML document(s) (XPATH, etc)
  tr    (or transform) - Transform XML document(s) using XSLT
  val   (or validate)  - Validate XML document(s) (well-formed/DTD/XSD/RelaxNG)
  fo    (or format)    - Format XML document(s)
  el    (or elements)  - Display element structure of XML document
  c14n  (or canonic)   - XML canonicalization
  ls    (or list)      - List directory as XML
[...]

你可以通过在这些子命令的末尾附加 -help 来获得进一步的帮助:

$ xmlstarlet sel --help
  -Q or --quiet             - do not write anything to standard output.
  -C or --comp              - display generated XSLT
  -R or --root              - print root element &lt;xsl-select&gt;
  -T or --text              - output is text (default is XML)
  -I or --indent            - indent output
[...]

用 sel 命令选择数据

可以使用 xmlstarlet select(简称 sel)命令查看 XML 格式的数据。下面是一个简单的 XML 文档:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xml>
  <os>
   <linux>
    <distribution>
      <name>Fedora</name>
      <release>7</release>
      <codename>Moonshine</codename>
      <spins>
        <name>Live</name>
        <name>Fedora</name>
        <name>Everything</name>
      </spins>
    </distribution>

    <distribution>
      <name>Fedora Core</name>
      <release>6</release>
      <codename>Zod</codename>
      <spins></spins>
    </distribution>
   </linux>
  </os>    
</xml>

在 XML 文件中查找数据时,你的第一个任务是关注要探索的节点。如果知道节点的路径,请使用 -value of 选项指定完整路径。你越早浏览 文档对象模型(DOM)树,就可以看到更多信息:

$ xmlstarlet select --template \
  --value-of /xml/os/linux/distribution \
  --nl myfile.xml
      Fedora
      7
      Moonshine
     
        Live
        Fedora
        Everything     
     
      Fedora Core
      6
      Zod

--nl 代表“新的一行”,它插入大量的空白,以确保在输入结果后,终端在新的一行显示。我已经删除了样本输出中的一些多余空间。

通过进一步深入 DOM 树来凝聚关注点:

$ xmlstarlet select --template \
  --value-of /xml/os/linux/distribution/name \
  --nl myfile.xml
Fedora
Fedora Core

条件选择

用于导航和解析 XML 的最强大工具之一被称为 XPath。它规范了 XML 搜索中使用的语法,并从 XML 库调用函数。XMLStarlet 能够解析 XPath 表达式,因此可以使用 XPath 函数来有条件的进行选择。XPath 具有丰富的函数,由 W3C 提供了详细文档,但我觉得 Mozilla 的 XPath 文档 更简洁。

可以使用方括号作为测试函数,将元素的内容与某个值进行比较。下面是对 <name> 元素的值的测试,它仅返回与特定匹配相关联的版本号。

想象一下,示例 XML 文件包含以 1 开头的所有 Fedora 版本。要查看与旧名称 “Fedora Core” 关联的所有版本号(该项目从版本 7 开始删除了名称中的 “Core”),请执行以下操作:

$ xmlstarlet sel --template \
  --value-of '/xml/os/linux/distribution[name = "Fedora Core"]/release' \
  --nl myfile.xml
6
5
4
3
2
1

通过将路径的 --value-of 更改为 /xml/os/linux/distribution[name=“Fedora Core”]/codename,你便可以查看这些版本的所有代号。

匹配路径和获取目标值

将 XML 标记视为节点的一个好处是,一旦找到节点,就可以将其视为当前的数据的“目录”。它不是一个真正的目录,至少不是文件系统意义上的目录,但它是一个可以查询的数据集合。为了帮助你将目标和“里面”的数据分开,XMLStarlet 把你试图用 --match 选项匹配的内容和用 --value-of 选项匹配的数据值进行了区分。

假设你知道 <spin> 节点包含几个元素。这就是你的目标节点。一旦到了这里,就可以使用 --value-of 指定想要哪个元素的值。要查看所有元素,可以使用点(.)来代表当前位置:

$ xmlstarlet sel --template \
  --match '/xml/os/linux/distribution/spin' \
  --value-of '.' --nl myfile.xml \
Live
Fedora
Everything

与浏览 DOM 一样,可以使用 XPath 表达式来限制返回数据的范围。在本例中,我使用 last() 函数来检索 spin 节点中的最后一个元素:

$ xmlstarlet select --template \
  --match '/xml/os/linux/distribution/spin' \
  --value-of '*[last()]' --nl myfile.xml
Everything

在本例中,我使用 position() 函数选择 spin 节点中的特定元素:

$ xmlstarlet select --template \
  --match '/xml/os/linux/distribution/spin' \
  --value-of '*[position() = 2]' --nl myfile.xml
Fedora

--match--value 选项可以重叠,因此如何将它们一起使用取决于你自己。对于示例 XML,这两个表达式执行的是相同的操作:

$ xmlstarlet select  --template \
  --match '/xml/os/linux/distribution/spin' \
  --value-of '.' \
  --nl myfile.xml
Live
Fedora
Everything     

$ xmlstarlet select --template \
  --match '/xml/os/linux/distribution' \
  --value-of 'spin' \
  --nl myfile.xml
Live
Fedora
Everything

熟悉 XML

XML 有时看起来过于冗长和笨拙,但为与之交互和构建的工具却总是让我吃惊。如果你想要好好使用 XML,那么 XMLStarlet 可能是一个很好的切入点。下次要打开 XML 文件查看其结构化数据时,请尝试使用 XMLStarlet,看看是否可以查询这些数据。当你对 XML 越熟悉时,它就越能作为一种健壮灵活的数据格式而为你服务。


via: https://opensource.com/article/21/7/parse-xml-linux

作者:Seth Kenlon 选题:lujun9972 译者:zepoch 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

学习 Java 如何外理数据的读与写。

 title=

当你写一个程序时,你的应用程序可能需要读取和写入存储在用户计算机上的文件。这在你想加载或存储配置选项,你需要创建日志文件,或你的用户想要保存工作以待后用的情况下是很常见的。每种语言处理这项任务的方式都有所不同。本文演示了如何用 Java 处理数据文件。

安装 Java

不管你的计算机是什么平台,你都可以从 AdoptOpenJDK 安装 Java。这个网站提供安全和开源的 Java 构建。在 Linux 上,你的软件库中也可能找到 AdoptOpenJDK 的构建。

我建议你使用最新的长期支持(LTS)版本。最新的非 LTS 版本对希望尝试最新 Java 功能的开发者来说是最好的,但它很可能超过大多数用户所安装的版本 —— 要么是系统上默认安装的,要么是以前为其他 Java 应用安装的。使用 LTS 版本可以确保你与大多数用户所安装的版本保持一致。

一旦你安装好了 Java,就可以打开你最喜欢的文本编辑器并准备开始写代码了。你可能还想要研究一下 Java 集成开发环境。BlueJ 是新程序员的理想选择,而 Eclipse 和 Netbeans 对中级和有经验的编码者更友好。

利用 Java 读取文件

Java 使用 File 类来加载文件。

这个例子创建了一个叫 Ingest 的类来读取文件中数据。当你要在 Java 中打开一个文件时,你创建了一个 Scanner 对象,它可以逐行扫描你提供的文件。事实上,Scanner 与文本编辑器中的光标是相同的概念,这样你可以用 Scanner 的一些方法(如 nextLine)来控制这个“光标”以进行读写。

import java.io.File;
import java.util.Scanner;
import java.io.FileNotFoundException;

public class Ingest {
  public static void main(String[] args) {
   
      try {
          File myFile = new File("example.txt");
          Scanner myScanner = new Scanner(myFile);
          while (myScanner.hasNextLine()) {
              String line = myScanner.nextLine();
              System.out.println(line);
          }
          myScanner.close();
      } catch (FileNotFoundException ex) {
          ex.printStackTrace();  
      } //try
    } //main
} //class

这段代码首先在假设存在一个名为 example.txt 的文件的情况下创建了变量 myfile。如果该文件不存在,Java 就会“抛出一个异常”(如它所说的,这意味着它在你试图做的事情中发现了一个错误),这个异常是被非常特定的 FileNotFoundException 类所“捕获”。事实上,有一个专门的类来处理这个明确的错误,这说明这个错误是多么常见。

接下来,它创建了一个 Scanner 并将文件加载到其中。我把它叫做 myScanner,以区别于它的通用类模板。接着,一个 while 循环将 myScanner 逐行送入文件中,只要 存在 下一行。这就是 hasNextLine 方法的作用:它检测“光标”之后是否还有数据。你可以通过在文本编辑器中打开一个文件来模拟这个过程:你的光标从文件的第一行开始,你可以用键盘控制光标来向下扫描文件,直到你走完了所有的行。

while 循环创建了一个变量 line,并将文件当前行的数据分配给它。然后将 line 的内容打印出来以提供反馈。一个更有用的程序可能会解析每一行的内容,从而提取它所包含的任何重要数据。

在这个过程结束时,关闭 myScanner 对象。

运行代码

将你的代码保存到 Ingest.java 文件(这是一个 Java 惯例,将类名的首字母大写,并以类名来命名相应的文件)。如果你试图运行这个简单的应用程序,你可能会接收到一个错误信息,这是因为还没有 example.txt 文件供应用程序加载:

$ java ./Ingest.java
java.io.FileNotFoundException:
example.txt (No such file or directory)

正好可以编写一个将数据写入文件的 Java 应用程序,多么完美的时机!

利用 Java 将数据写入文件

无论你是存储用户使用你的应用程序创建的数据,还是仅仅存储关于用户在应用程序中做了什么的元数据(例如,游戏保存或最近播放的歌曲),有很多很好的理由来存储数据供以后使用。在 Java 中,这是通过 FileWriter 类实现的,这次先打开一个文件,向其中写入数据,然后关闭该文件。

import java.io.FileWriter;
import java.io.IOException;

public class Exgest {
  public static void main(String[] args) {
    try {
        FileWriter myFileWriter = new FileWriter("example.txt", true);
        myFileWriter.write("Hello world\n");
        myFileWriter.close();
    } catch (IOException ex) {
        System.out.println(ex);
    } // try
  } // main
}

这个类的逻辑和流程与读取文件类似。但它不是一个 Scanner,而是以一个文件的名字为参数创建的一个 FileWriter 对象。FileWriter 语句末尾的 true 标志告诉 FileWriter 将文本 追加 到文件的末尾。要覆盖一个文件的内容,请移除 true 标志。

`FileWriter myFileWriter = new FileWriter("example.txt", true);`

因为我在向文件中写入纯文本,所以我在写入文件的数据(Hello world)的结尾处手动添加了换行符(\n)。

试试代码

将这段代码保存到 Exgest.java 文件,遵循 Java 的惯例,使文件名为与类名相匹配。

既然你已经掌握了用 Java 创建和读取数据的方法,你可以按相反的顺序尝试运行你的新应用程序。

$ java ./Exgest.java
$ java ./Ingest.java
Hello world
$

因为程序是把数据追加到文件末尾,所以你可以重复执行你的应用程序以多次写入数据,只要你想把更多的数据添加到你的文件中。

$ java ./Exgest.java
$ java ./Exgest.java
$ java ./Exgest.java
$ java ./Ingest.java
Hello world
Hello world
Hello world
$

Java 和数据

你不会经常向文件中写入原始文本;事实上,你可能会使用一个其它的类库以写入特定的格式。例如,你可能使用 XML 类库来写复杂的数据,使用 INI 或 YAML 类库来写配置文件,或者使用各种专门类库来写二进制格式,如图像或音频。

更完整的信息,请参阅 OpenJDK 文档


via: https://opensource.com/article/21/3/io-java

作者:Seth Kenlon 选题:lujun9972 译者:piaoshi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

进一步学习自然语言处理的基本概念

 title=

之前的文章 里,我介绍了 自然语言处理 natural language processing (NLP)和宾夕法尼亚大学研发的 自然语言处理工具包 Natural Language Toolkit (NLTK)。我演示了用 Python 解析文本和定义 停顿词 stopword 的方法,并介绍了 语料库 corpus 的概念。语料库是由文本构成的数据集,通过提供现成的文本数据来辅助文本处理。在这篇文章里,我将继续用各种语料库对文本进行对比和分析。

这篇文章主要包括以下部分:

  • 词网 WordNet 同义词集 synset
  • 相似度比较 Similarity comparison
  • Tree 树库 treebank
  • 命名实体识别 Named entity recognition

词网和同义词集

词网 WordNet 是 NLTK 里的一个大型词汇数据库语料库。词网包含各单词的诸多 认知同义词 cognitive synonyms (认知同义词常被称作“ 同义词集 synset ”)。在词网里,名词、动词、形容词和副词,各自被组织成一个同义词的网络。

词网是一个很有用的文本分析工具。它有面向多种语言的版本(汉语、英语、日语、俄语和西班牙语等),也使用多种许可证(从开源许可证到商业许可证都有)。初代版本的词网由普林斯顿大学研发,面向英语,使用 类 MIT 许可证 MIT-like license

因为一个词可能有多个意义或多个词性,所以可能与多个同义词集相关联。每个同义词集通常提供下列属性:

属性定义例子
名称 Name 此同义词集的名称单词 code 有 5 个同义词集,名称分别是 code.n.01code.n.02code.n.03code.v.01code.v.02
词性 POS 此同义词集的词性单词 code 有 3 个名词词性的同义词集和 2 个动词词性的同义词集
定义 Definition 该词作对应词性时的定义动词 code 的一个定义是:(计算机科学)数据或计算机程序指令的 象征性排列 symbolic arrangement
例子 Example 使用该词的例子code 一词的例子:We should encode the message for security reasons
词元 Lemma 与该词相关联的其他同义词集(包括那些不一定严格地是该词的同义词,但可以大体看作同义词的);词元直接与其他词元相关联,而不是直接与 单词 word 相关联code.v.02 的词元是 code.v.02.enciphercode.v.02.ciphercode.v.02.cyphercode.v.02.encryptcode.v.02.inscribecode.v.02.write_in_code
反义词 Antonym 意思相反的词词元 encode.v.01.encode 的反义词是 decode.v.01.decode
上义词 Hypernym 该词所属的一个范畴更大的词code.v.01 的一个上义词是 tag.v.01
分项词 Meronym 属于该词组成部分的词computer 的一个分项词是 chip
总项词 Holonym 该词作为组成部分所属的词window 的一个总项词是 computer screen

同义词集还有一些其他属性,在 <你的 Python 安装路径>/Lib/site-packages 下的 nltk/corpus/reader/wordnet.py,你可以找到它们。

下面的代码或许可以帮助理解。

这个函数:

from nltk.corpus import wordnet

def synset_info(synset):
    print("Name", synset.name())
    print("POS:", synset.pos())
    print("Definition:", synset.definition())
    print("Examples:", synset.examples())
    print("Lemmas:", synset.lemmas())
    print("Antonyms:", [lemma.antonyms() for lemma in synset.lemmas() if len(lemma.antonyms()) > 0])
    print("Hypernyms:", synset.hypernyms())
    print("Instance Hypernyms:", synset.instance_hypernyms())
    print("Part Holonyms:", synset.part_holonyms())
    print("Part Meronyms:", synset.part_meronyms())
    print()


synsets = wordnet.synsets('code')
print(len(synsets), "synsets:")
for synset in synsets:
    synset_info(synset)

将会显示:

5 synsets:
Name code.n.01
POS: n
Definition: a set of rules or principles or laws (especially written ones)
Examples: []
Lemmas: [Lemma('code.n.01.code'), Lemma('code.n.01.codification')]
Antonyms: []
Hypernyms: [Synset('written_communication.n.01')]
Instance Hpernyms: []
Part Holonyms: []
Part Meronyms: []

...

Name code.n.03
POS: n
Definition: (computer science) the symbolic arrangement of data or instructions in a computer program or the set of such instructions
Examples: []
Lemmas: [Lemma('code.n.03.code'), Lemma('code.n.03.computer_code')]
Antonyms: []
Hypernyms: [Synset('coding_system.n.01')]
Instance Hpernyms: []
Part Holonyms: []
Part Meronyms: []

...

Name code.v.02
POS: v
Definition: convert ordinary language into code
Examples: ['We should encode the message for security reasons']
Lemmas: [Lemma('code.v.02.code'), Lemma('code.v.02.encipher'), Lemma('code.v.02.cipher'), Lemma('code.v.02.cypher'), Lemma('code.v.02.encrypt'), Lemma('code.v.02.inscribe'), Lemma('code.v.02.write_in_code')]
Antonyms: []
Hypernyms: [Synset('encode.v.01')]
Instance Hpernyms: []
Part Holonyms: []
Part Meronyms: []

同义词集 synset 词元 lemma 在词网里是按照树状结构组织起来的,下面的代码会给出直观的展现:

def hypernyms(synset):
    return synset.hypernyms()

synsets = wordnet.synsets('soccer')
for synset in synsets:
    print(synset.name() + " tree:")
    pprint(synset.tree(rel=hypernyms))
    print()
code.n.01 tree:
[Synset('code.n.01'),
 [Synset('written_communication.n.01'),
   ...

code.n.02 tree:
[Synset('code.n.02'),
 [Synset('coding_system.n.01'),
   ...

code.n.03 tree:
[Synset('code.n.03'),
   ...

code.v.01 tree:
[Synset('code.v.01'),
 [Synset('tag.v.01'),
   ...

code.v.02 tree:
[Synset('code.v.02'),
 [Synset('encode.v.01'),
   ...

词网并没有涵盖所有的单词和其信息(现今英语有约 17,0000 个单词,最新版的 词网 涵盖了约 15,5000 个),但它开了个好头。掌握了“词网”的各个概念后,如果你觉得它词汇少,不能满足你的需要,可以转而使用其他工具。或者,你也可以打造自己的“词网”!

自主尝试

使用 Python 库,下载维基百科的 “open source” 页面,并列出该页面所有单词的 同义词集 synset 词元 lemma

相似度比较

相似度比较的目的是识别出两篇文本的相似度,在搜索引擎、聊天机器人等方面有很多应用。

比如,相似度比较可以识别 footballsoccer 是否有相似性。

syn1 = wordnet.synsets('football')
syn2 = wordnet.synsets('soccer')

# 一个单词可能有多个 同义词集,需要把 word1 的每个同义词集和 word2 的每个同义词集分别比较
for s1 in syn1:
    for s2 in syn2:
        print("Path similarity of: ")
        print(s1, '(', s1.pos(), ')', '[', s1.definition(), ']')
        print(s2, '(', s2.pos(), ')', '[', s2.definition(), ']')
        print("   is", s1.path_similarity(s2))
        print()
Path similarity of:
Synset('football.n.01') ( n ) [ any of various games played with a ball (round or oval) in which two teams try to kick or carry or propel the ball into each other's goal ]
Synset('soccer.n.01') ( n ) [ a football game in which two teams of 11 players try to kick or head a ball into the opponents' goal ]
   is 0.5

Path similarity of:
Synset('football.n.02') ( n ) [ the inflated oblong ball used in playing American football ]
Synset('soccer.n.01') ( n ) [ a football game in which two teams of 11 players try to kick or head a ball into the opponents' goal ]
   is 0.05

两个词各个同义词集之间 路径相似度 path similarity 最大的是 0.5,表明它们关联性很大( 路径相似度 path similarity 指两个词的意义在 上下义关系的词汇分类结构 hypernym/hypnoym taxonomy 中的最短距离)。

那么 codebug 呢?这两个计算机领域的词的相似度是:

Path similarity of:
Synset('code.n.01') ( n ) [ a set of rules or principles or laws (especially written ones) ]
Synset('bug.n.02') ( n ) [ a fault or defect in a computer program, system, or machine ]
   is 0.1111111111111111
...
Path similarity of:
Synset('code.n.02') ( n ) [ a coding system used for transmitting messages requiring brevity or secrecy ]
Synset('bug.n.02') ( n ) [ a fault or defect in a computer program, system, or machine ]
   is 0.09090909090909091
...
Path similarity of:
Synset('code.n.03') ( n ) [ (computer science) the symbolic arrangement of data or instructions in a computer program or the set of such instructions ]
Synset('bug.n.02') ( n ) [ a fault or defect in a computer program, system, or machine ]
   is 0.09090909090909091

这些是这两个词各同义词集之间 路径相似度 path similarity 的最大值,这些值表明两个词是有关联性的。

NLTK 提供多种 相似度计分器 similarity scorers ,比如:

  • path\_similarity
  • lch\_similarity
  • wup\_similarity
  • res\_similarity
  • jcn\_similarity
  • lin\_similarity

要进一步了解这些 相似度计分器 similarity scorers ,请查看 WordNet Interface 的 Similarity 部分。

自主尝试

使用 Python 库,从维基百科的 Category: Lists of computer terms 生成一个术语列表,然后计算各术语之间的相似度。

树和树库

使用 NLTK,你可以把文本表示成树状结构以便进行分析。

这里有一个例子:

这是一份简短的文本,对其做预处理和词性标注:

import nltk

text = "I love open source"
# Tokenize to words
words = nltk.tokenize.word_tokenize(text)
# POS tag the words
words_tagged = nltk.pos_tag(words)

要把文本转换成树状结构,你必须定义一个 语法 grammar 。这个例子里用的是一个基于 Penn Treebank tags 的简单语法。

# A simple grammar to create tree
grammar = "NP: {&lt;JJ&gt;&lt;NN&gt;}"

然后用这个 语法 grammar 创建一颗 tree

# Create tree
parser = nltk.RegexpParser(grammar)
tree = parser.parse(words_tagged)
pprint(tree)

运行上面的代码,将得到:

Tree('S', [('I', 'PRP'), ('love', 'VBP'), Tree('NP', [('open', 'JJ'), ('source', 'NN')])])

你也可以图形化地显示结果。

tree.draw()

 title=

这个树状结构有助于准确解读文本的意思。比如,用它可以找到文本的 主语):

subject_tags = ["NN", "NNS", "NP", "NNP", "NNPS", "PRP", "PRP$"]
def subject(sentence_tree):
    for tagged_word in sentence_tree:
        # A crude logic for this case -  first word with these tags is considered subject
        if tagged_word[1] in subject_tags:
            return tagged_word[0]

print("Subject:", subject(tree))

结果显示主语是 I

Subject: I

这是一个比较基础的文本分析步骤,可以用到更广泛的应用场景中。 比如,在聊天机器人方面,如果用户告诉机器人:“给我妈妈 Jane 预订一张机票,1 月 1 号伦敦飞纽约的”,机器人可以用这种分析方法解读这个指令:

动作: 预订
动作的对象: 机票
乘客: Jane
出发地: 伦敦
目的地: 纽约
日期: (明年)1 月 1 号

树库 treebank 指由许多预先标注好的 tree 构成的语料库。现在已经有面向多种语言的树库,既有开源的,也有限定条件下才能免费使用的,以及商用的。其中使用最广泛的是面向英语的宾州树库。宾州树库取材于 华尔街日报 Wall Street Journal 。NLTK 也包含了宾州树库作为一个子语料库。下面是一些使用 树库 treebank 的方法:

words = nltk.corpus.treebank.words()
print(len(words), "words:")
print(words)

tagged_sents = nltk.corpus.treebank.tagged_sents()
print(len(tagged_sents), "sentences:")
print(tagged_sents)

100676 words:
['Pierre', 'Vinken', ',', '61', 'years', 'old', ',', ...]
3914 sentences:
[[('Pierre', 'NNP'), ('Vinken', 'NNP'), (',', ','), ('61', 'CD'), ('years', 'NNS'), ('old', 'JJ'), (',', ','), ('will', 'MD'), ('join', 'VB'), ('the', 'DT'), ('board', 'NN'), ('as', 'IN'), ('a', 'DT'), ('nonexecutive', 'JJ'), ('director', 'NN'), ...]

查看一个句子里的各个 标签 tags

sent0 = tagged_sents[0]
pprint(sent0)
[('Pierre', 'NNP'),
 ('Vinken', 'NNP'),
 (',', ','),
 ('61', 'CD'),
 ('years', 'NNS'),
...

定义一个 语法 grammar 来把这个句子转换成树状结构:

grammar = '''
    Subject: {<NNP><NNP>}
    SubjectInfo: {<CD><NNS><JJ>}
    Action: {<MD><VB>}
    Object: {<DT><NN>}
    Stopwords: {<IN><DT>}
    ObjectInfo: {<JJ><NN>}
    When: {<NNP><CD>}
'''
parser = nltk.RegexpParser(grammar)
tree = parser.parse(sent0)
print(tree)
(S
  (Subject Pierre/NNP Vinken/NNP)
  ,/,
  (SubjectInfo 61/CD years/NNS old/JJ)
  ,/,
  (Action will/MD join/VB)
  (Object the/DT board/NN)
  as/IN
  a/DT
  (ObjectInfo nonexecutive/JJ director/NN)
  (Subject Nov./NNP)
  29/CD
  ./.)

图形化地显示:

tree.draw()

 title=

trees 树库 treebanks 的概念是文本分析的一个强大的组成部分。

自主尝试

使用 Python 库,下载维基百科的 “open source” 页面,将得到的文本以图形化的树状结构展现出来。

命名实体识别

无论口语还是书面语都包含着重要数据。文本处理的主要目标之一,就是提取出关键数据。几乎所有应用场景所需要提取关键数据,比如航空公司的订票机器人或者问答机器人。 NLTK 为此提供了一个 命名实体识别 named entity recognition 的功能。

这里有一个代码示例:

sentence = 'Peterson first suggested the name "open source" at Palo Alto, California'

验证这个句子里的 人名 name 地名 place 有没有被识别出来。照例先预处理:

import nltk

words = nltk.word_tokenize(sentence)
pos_tagged = nltk.pos_tag(words)

运行 命名实体标注器 named-entity tagger

ne_tagged = nltk.ne_chunk(pos_tagged)
print("NE tagged text:")
print(ne_tagged)
print()
NE tagged text:
(S
  (PERSON Peterson/NNP)
  first/RB
  suggested/VBD
  the/DT
  name/NN
  ``/``
  open/JJ
  source/NN
  ''/''
  at/IN
  (FACILITY Palo/NNP Alto/NNP)
  ,/,
  (GPE California/NNP))

上面的结果里,命名实体被识别出来并做了标注;只提取这个 tree 里的命名实体:

print("Recognized named entities:")
for ne in ne_tagged:
    if hasattr(ne, "label"):
        print(ne.label(), ne[0:])
Recognized named entities:
PERSON [('Peterson', 'NNP')]
FACILITY [('Palo', 'NNP'), ('Alto', 'NNP')]
GPE [('California', 'NNP')]

图形化地显示:

ne_tagged.draw()

 title=

NLTK 内置的 命名实体标注器 named-entity tagger ,使用的是宾州法尼亚大学的 Automatic Content Extraction(ACE)程序。该标注器能够识别 组织机构 ORGANIZATION 、人名 PERSON 、地名 LOCATION 、设施 FACILITY 地缘政治实体 geopolitical entity 等常见 实体 entites

NLTK 也可以使用其他 标注器 tagger ,比如 Stanford Named Entity Recognizer. 这个经过训练的标注器用 Java 写成,但 NLTK 提供了一个使用它的接口(详情请查看 nltk.parse.stanfordnltk.tag.stanford)。

自主尝试

使用 Python 库,下载维基百科的 “open source” 页面,并识别出对 开源 open source 有影响力的人的名字,以及他们为 开源 open source 做贡献的时间和地点。

高级实践

如果你准备好了,尝试用这篇文章以及此前的文章介绍的知识构建一个 超级结构 superstructure

使用 Python 库,下载维基百科的 “Category: Computer science page”,然后:

  • 找出其中频率最高的 单词 unigrams 、二元搭配 bigrams 三元搭配 trigrams ,将它们作为一个关键词列表或者技术列表。相关领域的学生或者工程师需要了解这样一份列表里的内容。
  • 图形化地显示这个领域里重要的人名、技术、日期和地点。这会是一份很棒的信息图。
  • 构建一个搜索引擎。你的搜索引擎性能能够超过维基百科吗?

下一步?

自然语言处理是 应用构建 application building 的典型支柱。NLTK 是经典、丰富且强大的工具集,提供了为现实世界构建有吸引力、目标明确的应用的工作坊。

在这个系列的文章里,我用 NLTK 作为例子,展示了自然语言处理可以做什么。自然语言处理和 NLTK 还有太多东西值得探索,这个系列的文章只是帮助你探索它们的切入点。

如果你的需求增长到 NLTK 已经满足不了了,你可以训练新的模型或者向 NLTK 添加新的功能。基于 NLTK 构建的新的自然语言处理库正在不断涌现,机器学习也正被深度用于自然语言处理。


via: https://opensource.com/article/20/8/nlp-python-nltk

作者:Girish Managoli 选题:lujun9972 译者:tanloong 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

每种编程语言都有其独特的完成任务的方式,这也说明了为什么有这么多语言可供选择。

 title=

在 Jim Hall 的《不同的编程语言如何完成相同的事情》文章中,他演示了 13 种不同的语言如何使用不同的语法来完成同一个任务。经验是,编程语言往往有很多相似之处。一旦你了解了一种编程语言,你就可以通过理解它的语法和结构来学习另一种。

本着同样的精神,Jim 的文章比较了不同编程语言如何读写数据。无论数据来自配置文件还是用户创建的文件,在存储设备上处理数据都是程序员的常见任务。以这种方式涵盖所有编程语言是不切实际的,但最近的 Opensource.com 系列文章提供了对这些编程语言采用的不同方法的深入了解:

读写数据

用计算机读写数据的过程和你在现实生活中读写数据的过程类似。要访问书中的数据,你首先要打开它,然后阅读单词或将生词写入书中,然后合上书。

当程序需要从文件中读取数据时,你向程序传入一个文件位置,然后计算机将该数据读入内存中并解析它。同样,当程序需要将数据写入文件时,计算机会将新数据放入系统的内存写入缓冲区,然后将其同步到存储设备上的文件中。

下面是这些操作的一些伪代码:

  1. 在内存中加载文件。
  2. 读取文件内容,或将数据写入文件。
  3. 关闭文件。

从文件中读取数据

Opensource.com 系列文章的语言中,你可以看到读取文件的三种趋势。

C

在 C 语言中,打开文件可能涉及检索单个字符(直到 EOF 指示符,表示文件结束)或一个数据块,具体取决于你的需求和方法。根据你的目标,它可能感觉像一个主要是手工的过程,但这正是其他语言所模仿的。

FILE *infile;
int ch;

infile = fopen(argv[1], "r");
 
do {
  ch = fgetc(infile);
  if (ch != EOF) {
    printf("%c", ch);
  }
 } while (ch != EOF);

fclose(infile);

你还可以选择将文件的某些部分加载到系统缓冲区中,然后在缓冲区外工作。

FILE *infile;
char buffer[300];
 
infile = fopen(argv[1], "r");

while (!feof(infile)) {
  size_t buffer_length;
  buffer_length = fread(buffer, sizeof(char), 300, infile);
}

printf("%s", buffer);
fclose(infile);

C++

C++ 简化了一些步骤,允许你将数据解析为字符串。

std::string sFilename = "example.txt";

std::ifstream fileSource(sFilename);
std::string buffer;

while (fileSource >> buffer) {
  std::cout << buffer << std::endl;
}

Java

Java 和 Groovy 类似于 C++。它们使用名为 Scanner 的类来设置数据流或对象,这样就会包含你选择的文件内容。你可以通过标记(字节、行、整数等)扫描文件。

File myFile = new File("example.txt");

Scanner myScanner = new Scanner(myFile);
  while (myScanner.hasNextLine()) {
    String line = myScanner.nextLine();
    System.out.println(line);
  }

myScanner.close();

Groovy

def myFile = new File('example.txt')

def myScanner = new Scanner(myFile)
  while (myScanner.hasNextLine()) {
    def line = myScanner.nextLine()
    println(line)
  }

myScanner.close()

Lua

Lua 和 Python 进一步抽象了整个过程。你不必有意识地创建数据流,你只需给一个变量赋值为 open 函数的返回值,然后解析该变量的内容。这种方式快速,最简且容易。

myFile = io.open('example.txt', 'r')

lines = myFile:read("*all")
print(lines)

myFile:close()

Python

f = open('example.tmp', 'r')

for line in f:
    print(line)

f.close()

向文件中写入数据

就写代码来说,写入是读取的逆过程。因此,将数据写入文件的过程与从文件中读取数据基本相同,只是使用了不同的函数。

C

在 C 语言中,你可以使用 fputc 函数将字符写入文件:

fputc(ch, outfile);

或者,你可以使用 fwrite 将数据写入缓冲区。

fwrite(buffer, sizeof(char), buffer_length, outfile);

C++

因为 C++ 使用 ifstream 库为数据打开缓冲区,所以你可以像 C 语言那样将数据写入缓冲区(C++ 库除外)。

std::cout << buffer << std::endl;

Java

在 Java 中,你可以使用 FileWriter 类来创建一个可以写入数据的对象。它的工作方式与 Scanner 类非常相似,只是方向相反。

FileWriter myFileWriter = new FileWriter("example.txt", true);
myFileWriter.write("Hello world\n");
myFileWriter.close();

Groovy

类似地,Groovy 使用 FileWriter,但使用了稍微 “groovy” 的语法。

new FileWriter("example.txt", true).with {
  write("Hello world\n")
  flush()
}

Lua

Lua 和 Python 很相似,都使用名为 open 的函数来加载文件,writer 函数来写入数据,close 函数用于关闭文件。

myFile = io.open('example.txt', 'a')
io.output(myFile)
io.write("hello world\n")
io.close(myFile)

Python

myFile = open('example.txt', 'w')
myFile.write('hello world')
myFile.close()

File 模式

许多语言在打开文件时会指定一个“模式”。模式有很多,但这是常见的定义:

  • w 表示写入
  • r 表示读取
  • r+ 表示可读可写
  • a 表示追加

某些语言,例如 Java 和 Groovy,允许你根据用于加载文件的类来确定模式。

无论编程语言以何种方式来确定文件模式,你都需要确保你是在 追加 数据,除非你打算用新数据覆盖文件。编程语言不像文件选择器那样,没有内置的提示来警告你防止数据丢失。

新语言和旧把戏

每种编程语言都有其独特完成任务的方式,这就是为什么有这么多语言可供选择。你可以而且应该选择最合适你的语言。但是,你一旦了解了编程的基本结构,你可以随意尝试其他语言,而不必担心不知道如何完成基本任务。通常情况下,实现目标的途径是相似的,所以只要你牢记基本概念,它们就很容易学习。


via: https://opensource.com/article/21/7/programming-read-write

作者:Alan Smithee 选题:lujun9972 译者:MjSeven 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

当编写程序时,我花费了大量时间在编写好的示例上。我从未见过有人写过关于如何写出好的示例,所以我就写了一下如何写出一份好的示例。

基础思路就是从你写的真实代码开始,然后删除不相关的细节,使其成为一个独立的例子,而不是无中生有地想出一些例子。

我将会谈论两种示例:基于真实案例的示例和奇怪的示例

好的示例是真实的

为了说明为什么好的案例应该是真实的,我们就先讨论一个不真实的案例。假设我们在试图解释 Python 的 lambda 函数(这只是我想到的第一个概念)。你可以举一个例子,使用 map 和 lambda 来让一组数字变为原先的两倍。

numbers = [1, 2, 3, 4]
squares = map(lambda x: x * x, numbers)

我觉得这个示例不是真实的,有如下两方面的原因:

  • 将一组数字作平方运算不是在真实的程序中完成的事,除非是欧拉项目或某种东西(更多的可能是针对列表的操作)
  • map 在 Python 中并不常用,即便是做这个我也更愿意写 [x*x for x in numbers]

一个更加真实的 Python lambdas 的示例是使用 sort 函数,就像这样:

children = [{"name": "ashwin", "age": 12}, {"name": "radhika", "age": 3}]
sorted_children = sorted(children, key=lambda x: x['age'])

但是这个示例是被精心设计的(为什么我们需要对这些孩子按照年龄进行排序呢?)。所以我们如何来做一个真实的示例呢?

如何让你的示例真实起来:看你所写实际代码

我认为最简单的来生成一个例子的方法就是,不是凭空出现一个例子(就像我用那个儿童的例子),而只是从真正的代码开始!

举一个例子吧,如果我要用 sort.+key 来编写一串 Python 代码,我会发现很多我按某个标准对列表进行排序的真实例子,例如:

  • tasks.sort(key=lambda task: task['completed_time'])
  • emails = reversed(sorted(emails, key=lambda x:x['receivedAt']))
  • sorted_keysizes = sorted(scores.keys(), key=scores.get)
  • shows = sorted(dates[date], key=lambda x: x['time']['performanceTime'])

在这里很容易看到一个规律——这些基本是按时间排序的!因此,你可以明白如何将按时间排序的某些对象(电子邮件、事件等)的简单实例轻松地放在一起。

现实的例子有助于“布道”你试图解释的概念

当我试图去解释一个想法(就好比 Python Lambdas)的时候,我通常也会试图说服读者,说这是值得学习的想法。Python lambdas 是如此的有用!当我去试图说服某个人 lambdas 是很好用的时候,让他想象一下 lambdas 如何帮助他们完成一项他们将要去做的任务或是以及一项他们以前做过的任务,对说服他会很有帮助。

从真实代码中提炼出示例可能需要很长时间

我给出如何使用 lambdasort 函数的解释例子是十分简单的,它并不需要花费我很长时间来想出来,但是将真实的代码提炼出为一个独立的示例则是会需要花费很长的时间!

举个例子,我想在这篇文章中融入一些奇怪的 CSS 行为的例子来说明创造一个奇怪的案例是十分有趣的。我花费了两个小时来解决我这周遇到的一个实际的问题,确保我理解 CSS 的实际情况,并将其变成一个小示例。

最后,它“仅仅”用了 五行 HTML 和一点点的 CSS 来说明了这个问题,看起来并不想是我花费了好多小时写出来的。但是最初它却是几百行的 JS/CSS/JavaScript,它需要花费很长时间来将所有的代码化为核心的很少的代码。

但我认为花点时间把示例讲得非常简单明了是值得的——如果有成百上千的人在读你的示例,你就节省了他们这么多时间!

就这么多了!

我觉得还有更多关于示例可以去讲的——几种不同类型的有用示例,例如:

  • 可以更多的改变人的思维而不是直接提供使用的惊喜读者的示例代码
  • 易于复制粘贴以用作初始化的示例

也许有一天我还会再写一些呢? :smiley:


via: https://jvns.ca/blog/2021/07/08/writing-great-examples/

作者:Julia Evans 选题:lujun9972 译者:zepoch 校对:turbokernel

本文由 LCTT 原创编译,Linux中国 荣誉推出