前一节中的 Sigmoid 函数为我们提供了一种方法:对一个输入样本 $x$,计算概率 $P(y = 1|x)$。

那么,如何决定将哪个类别分配给一个测试样本 $x$ 呢? 对于给定的 $x$,如果概率 $P(y = 1|x)$ 大于 0.5,就判定为“是”(即类别 1),否则判定为“否”(即类别 0)。 我们将 0.5 称为决策边界(decision boundary):

$$ \text{decision}(x) = \begin{cases} 1 & \text{if } P(y = 1|x) > 0.5 \\ 0 & \text{otherwise} \end{cases} $$

接下来,我们通过一些语言任务的例子,来展示如何使用逻辑回归作为分类器。

4.3.1 情感分类

假设我们正在对电影评论文本进行二元情感分类,需要判断是否将情感类别“+”或“−”分配给一篇评论文档 doc。 我们将每个输入样本表示为下表所示的 6 个特征 $x_1$ 到 $x_6$;图 4.2 展示了一个小型测试文档及其提取出的特征向量。

变量定义图 5.2 中的值
$x_1$文档中正面情感词典词的数量3
$x_2$文档中负面情感词典词的数量2
$x_3$$\begin{cases} 1 & \text{if “no” in doc} \\ 0 & \text{otherwise} \end{cases}$1
$x_4$第一和第二人称代词的数量3
$x_5$$\begin{cases} 1 & \text{if “!” in doc} \\ 0 & \text{otherwise} \end{cases}$0
$x_6$文档词数的自然对数(ln)$\ln(66) = 4.19$

图 4.2 一个小型测试文档示例,展示了提取出的特征向量 $x$。

我们暂时假设已经为这些特征学习到了对应的实数值权重,且这 6 个特征的权重为 [2.5, −5.0, −1.2, 0.5, 2.0, 0.7],偏置项 $b = 0.1$。 (下一节将介绍权重是如何学习的。) 例如,权重 $w_1$ 表示正面情感词(如 greatniceenjoyable 等)的数量对判断为正面情感的重要性,而 $w_2$ 则表示负面情感词的重要性。 注意 $w_1 = 2.5$ 为正值,而 $w_2 = −5.0$ 为负值,这意味着负面词与正面情感的判定呈负相关,并且其重要性大约是正面词的两倍。

给定这 6 个特征和输入评论 $x$,我们可以使用公式 (4.5) 计算 $P(+|x)$ 和 $P(−|x)$:

$$ \begin{align*} p(+|x) = P(y = 1|x) &= \sigma(\mathbf{w} \cdot \mathbf{x} + b) \\ &= \sigma([2.5, -5.0, -1.2, 0.5, 2.0, 0.7] \cdot [3, 2, 1, 3, 0, 4.19] + 0.1) \\ &= \sigma(0.833) \\ &= 0.70 \\ p(−|x) &= P(y = 0|x) = 1 - \sigma(\mathbf{w} \cdot \mathbf{x} + b) \\ &= 0.30 \tag{4.8} \end{align*} $$

4.3.2 其他分类任务与特征

逻辑回归被应用于各种自然语言处理(NLP)任务中,输入的任何属性都可以作为特征。 考虑句号消歧(period disambiguation)任务:判断句号是句子的结尾还是单词的一部分。方法是将每个句号分类为两类之一:EOS(句子结束)或非 EOS。 我们可以使用如下所示的特征 $x_1$,表示当前词为小写,该特征可能具有正值权重。 或者使用一个特征表示当前词出现在我们的缩写词典中(如“Prof.”),该特征可能具有负值权重。 特征也可以表达多个属性的组合。 例如,大写词后的句号更可能是 EOS,但如果该词本身是“St.”且前一个词也是大写,则该句号很可能属于“street”一词的缩写,跟在街名后面,而非句子结尾。

$$ \begin{align*} x_1 &= \begin{cases} 1 & \text{if “}Case(w_i) = \text{Lower”} \\ 0 & \text{otherwise} \end{cases} \\ x_2 &= \begin{cases} 1 & \text{if “}w_i \in \text{AcronymDict”} \\ 0 & \text{otherwise} \end{cases} \\ x_3 &= \begin{cases} 1 & \text{if “}w_i = \text{St. }\& Case(w_{i-1}) = \text{Upper”} \\ 0 & \text{otherwise} \end{cases} \end{align*} $$

特征的设计与学习:在传统模型中,特征通常由人工设计,设计过程需要仔细分析训练集,结合语言学直觉、相关文献以及系统早期版本在训练集上的错误分析结果。 我们还可以考虑特征交互(feature interactions),即由更基础的特征组合而成的复杂特征。 上面句号消歧的例子中就包含这样一个特征:当“St.”前面的词是大写时,其后的句号更不可能是句子的结尾。 特征也可以通过特征模板(feature templates) 自动生成,特征模板是对特征的抽象描述。 例如,用于句号消歧的 bigram 模板可能会为训练集中每个出现在句号前的词对创建一个特征。 因此,特征空间是稀疏的,因为我们只需为训练集中实际出现在该位置的 n-gram 创建特征。 特征通常通过对字符串描述进行哈希处理来生成。 用户描述的一个特征,如“bigram(American breakfast)”,会被哈希为一个唯一的整数 $i$,该整数即作为特征编号 $f_i$。

从上一段可以看出,手工设计特征需要大量的人工投入。 因此,近年来的 NLP 系统倾向于避免使用人工设计的特征,转而专注于表示学习(representation learning):即从输入数据中以无监督方式自动学习特征的方法。 我们将在第 5 章和第 6 章介绍表示学习的相关方法。

输入特征的缩放:当不同的输入特征具有极不相同的数值范围时,通常需要对它们进行重新缩放,使其具有可比的范围。 我们通过标准化(standardize) 输入值,使其均值为0,标准差为1(这种变换有时称为 z-score 变换)。 具体来说,如果 $µ_i$ 是输入数据集中 $m$ 个样本上特征 $x_i$ 的均值,$\sigma_i$ 是特征 $x_i$ 在整个输入数据集上的标准差,我们可以用如下方式计算得到的新特征 $x_i'$ 来替代原始特征 $x_i$:

$$ \begin{align*} µ_i &= \frac{1}{m} \sum_{j=1}^m x_i^{(j)} \\ \sigma_i &= \sqrt{\frac{1}{m}\sum_{j=1}^m (x_i^{(j)} − µ_i)^2} \\ x'_i &= \frac{x_i − µ_i}{\sigma_i} \tag{4.9} \end{align*} $$

另一种方法是将输入特征值归一化到 0 和 1 之间:

$$ x'_i = \frac{x_i − \min(x_i)}{\max(x_i) − \min(x_i)} \tag{4.10} $$

使输入数据具有相近的数值范围,有助于在不同特征之间进行比较。 数据缩放在大型神经网络中尤为重要,因为它有助于加速梯度下降的收敛过程。

4.3.3 同时处理多个样本

我们已经展示了用于单个样本的逻辑回归公式。 但在实际应用中,我们通常需要一次性处理包含多个样本的整个测试集。 假设我们有一个测试集,包含 $m$ 个待分类的测试样本。 我们将继续使用第 63 页的记号,其中带括号的上标表示数据集(无论是训练集还是测试集)中的样本索引。 因此,每个测试样本 $x^{(i)}$ 都有一个特征向量 $\mathbf{x}^{(i)}$,其中 $1 \leq i \leq m$。 (与之前一样,我们用粗体表示向量和矩阵。)

一种计算每个输出值 $\hat{y}^{(i)}$ 的方法是使用一个 for 循环,逐个计算每个测试样本:

$$ \begin{gather*} \text{foreach } x^{(i)} \text{ in input } [x^{(1)}, x^{(2)}, \cdots, x^{(m)}] \\ \hat{y}^{(i)} = \sigma(\mathbf{w} \cdot \mathbf{x}^{(i)} + b) \tag{4.11} \end{gather*} $$

那么,对于前3个测试样本,我们将分别计算预测值 $\hat{y}^{(i)}$ 如下:

$$ \begin{align*} P(y^{(1)} = 1|x^{(1)}) &= \sigma(\mathbf{w} \cdot \mathbf{x}^{(1)} + b) \\ P(y^{(2)} = 1|x^{(2)}) &= \sigma(\mathbf{w} \cdot \mathbf{x}^{(2)} + b) \\ P(y^{(3)} = 1|x^{(3)}) &= \sigma(\mathbf{w} \cdot \mathbf{x}^{(3)} + b) \end{align*} $$

但事实上,可以对原始公式 4.5 稍作修改,以更高效地完成这一计算。 我们将使用矩阵运算,通过一次矩阵操作为所有样本分配类别!

首先,我们将每个输入样本 $x$ 的所有输入特征向量打包成一个单一的输入矩阵 $\mathbf{X}$,其中每一行 $i$ 是一个行向量,由输入样本 $x^{(i)}$ 的特征向量(即向量 $\mathbf{x}^{(i)}$)构成。 假设每个样本有 $f$ 个特征和对应的权重,则 $\mathbf{X}$ 将是一个形状为 $[m \times f]$ 的矩阵,如下所示:

$$ \mathbf{X} = \begin{bmatrix} x_1^{(1)} & x_2^{(1)} & \cdots & x_f^{(1)} \\ x_1^{(2)} & x_2^{(2)} & \cdots & x_f^{(2)} \\ x_1^{(3)} & x_2^{(3)} & \cdots & x_f^{(3)} \\ \vdots & \vdots & \ddots & \vdots \end{bmatrix} \tag{4.12} $$

现在,将偏置项 $b$ 表示为一个长度为 $m$ 的向量 $\mathbf{b}$,其中每个元素都是标量 $b$,即 $\mathbf{b} = [b, b, \cdots, b]$;将输出向量表示为 $\hat{\mathbf{y}} = [\hat{y}^{(1)}, \hat{y}^{(2)}, \cdots, \hat{y}^{(m)}]$(每个输入 $x^{(i)}$ 及其特征向量 $\mathbf{x}^{(i)}$ 对应一个标量输出 $\hat{y}^{(i)}$);并将权重向量 $\mathbf{w}$ 表示为一个列向量。这样,就可以通过一次矩阵乘法和一次加法计算所有输出:

$$ \hat{\mathbf{y}} = \sigma(\mathbf{X}\mathbf{w} + \mathbf{b}) \tag{4.13} $$

你应该验证一下,公式 4.13 计算的结果与公式 4.11 中的 for 循环是相同的。 例如,输出向量 $\hat{\mathbf{y}}$ 的第一个元素 $\hat{y}^{(1)}$ 将正确地计算为:

$$ \hat{y}^{(1)} = [x_1^{(1)}, x_2^{(1)}, \cdots, x_f^{(1)}] \cdot [w_1, w_2, ..., w_f] + b \tag{4.14} $$

请注意,为了使矩阵乘法能够正确进行,我们必须调整 $\mathbf{X}$ 和 $\mathbf{w}$ 在公式 4.5 中的顺序。 下面是再次写出的公式 (5.13),并标出了各矩阵/向量的形状:

$$ \begin{align*} \hat{\mathbf{y}} &= \sigma&(\mathbf{X} &\mathbf{w} + &\mathbf{b}) \\ (m \times 1) & &(m \times f) &(f \times 1) &(m \times 1) \tag{4.15} \end{align*} $$

现代编译器和计算硬件可以非常高效地执行这种矩阵运算,从而使计算速度大大加快,这在处理非常大的训练或测试数据集时尤为重要。

顺便提一下,如果我们选择将 $\mathbf{X}$ 定义为一个由列向量构成的矩阵(每个输入样本对应一个列向量),而不是行向量,那么我们本可以保持 $\mathbf{X}$ 和 $\mathbf{w}$ 在原始公式中的顺序(即 $\hat{\mathbf{y}} = \sigma(\mathbf{wX} + \mathbf{b})$),此时 $\mathbf{X}$ 的形状将是 $[f \times m]$。 但通常情况下,我们习惯将输入表示为行向量。